diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2019-03-04 17:49:32 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-04 17:49:32 +0200 |
commit | 94bb29738eb00f873c3467eba0bada4ca58d8ab9 (patch) | |
tree | d237f26f114e8b2b5fb350c97861350023e7a71c | |
parent | 17ce9bc0e536067cfa1a05cb057014e3b1c2c449 (diff) | |
parent | 91918262e7c7bb0efea8900ad180194f9c059d4e (diff) | |
download | meson-94bb29738eb00f873c3467eba0bada4ca58d8ab9.zip meson-94bb29738eb00f873c3467eba0bada4ca58d8ab9.tar.gz meson-94bb29738eb00f873c3467eba0bada4ca58d8ab9.tar.bz2 |
Merge pull request #4992 from mensinda/rwCLI
rewriter: Add a CLI and docs
23 files changed, 606 insertions, 172 deletions
diff --git a/docs/markdown/Rewriter.md b/docs/markdown/Rewriter.md new file mode 100644 index 0000000..b6301d6 --- /dev/null +++ b/docs/markdown/Rewriter.md @@ -0,0 +1,236 @@ +--- +short-description: Automatic modification of the build system files +... + +# Meson file rewriter + +Since version 0.50.0, meson has the functionality to perform some basic +modification on the `meson.build` files from the command line. The currently +supported operations are: + +- For build targets: + - Add/Remove source files + - Add/Remove targets + - Modify a select set of kwargs + - Print some JSON information +- For dependencies: + - Modify a select set of kwargs +- For the project function: + - Modify a select set of kwargs + - Modify the default options list + +The rewriter has both, a normal command line interface and a "script mode". The +normal CLI is mostly designed for everyday use. The "script mode", on the +other hand, is meant to be used by external programs (IDEs, graphical +frontends, etc.) + +The rewriter itself is considered stable, however the user interface and the +"script mode" API might change in the future. These changes may also break +backwards comaptibility to older releases. + +We are also open to suggestions for API improvements. + +## Using the rewriter + +All rewriter functions are accessed via `meson rewrite`. The meson rewriter +assumes that it is run inside the project root directory. If this isn't the +case, use `--sourcedir` to specify the actual project source directory. + +### Adding and removing sources + +The most common operations will probably be the adding and removing of source +files to a build target. This can be easily done with: + +```bash +meson rewrite target <target name/id> {add/rm} [list of sources] +``` + +For instance, given the following example + +```meson +src = ['main.cpp', 'fileA.cpp'] + +exe1 = executable('testExe', src) +``` + +the source `fileB.cpp` can be added with: + +```bash +meson rewrite target testExe add fileB.cpp +``` + +After executing this command, the new `meson.build` will look like this: + +```meson +src = ['main.cpp', 'fileA.cpp', 'fileB.cpp'] + +exe1 = executable('testExe', src) +``` + +In this case, `exe1` could also have been used for the target name. This is +possible because the rewriter also searches for assignments and unique meson +IDs, which can be acquired with introspection. If there are multiple targets +with the same name, meson will do nothing and print an error message. + +For more information see the help output of the rewriter target command. + +### Setting the project version + +It is also possible to set kwargs of specific functions with the rewriter. The +general command for setting or removing kwargs is: + +```bash +meson rewriter kwargs {set/delete} <function type> <function ID> <key1> <value1> <key2> <value2> ... +``` + +For instance, setting the project version can be achieved with this command: + +```bash +meson rewriter kwargs set project / version 1.0.0 +``` + +Currently, only the following function types are supported: + +- dependency +- target (any build target, the function ID is the target name/ID) +- project (the function ID must be `/` since project() can only be called once) + +For more information see the help output of the rewriter kwargs command. + +### Setting the project default options + +For setting and deleting default options, use the following command: + +```bash +meson rewrite default-options {set/delete} <opt1> <value1> <opt2> <value2> ... +``` + +## Limitations + +Rewriting a meson file is not guranteed to keep the indentation of the modified +functions. Additionally, comments inside a modified statement will be removed. +Furthermore, all source files will be sorted alphabetically. + +For instance adding `e.c` to srcs in the following code + +```meson +# Important comment + +srcs = [ +'a.c', 'c.c', 'f.c', +# something important about b + 'b.c', 'd.c', 'g.c' +] + +# COMMENT +``` + +would result in the following code: + +```meson +# Important comment + +srcs = [ + 'a.c', + 'b.c', + 'c.c', + 'd.c', + 'e.c', + 'f.c', + 'g.c' +] + +# COMMENT +``` + +## Using the "script mode" + +The "script mode" should be the preferred API for third party programs, since +it offers more flexibility and higher API stability. The "scripts" are stored +in JSON format and executed with `meson rewrite command <JSON file or string>`. + +The JSON format is defined as follows: + +```json +[ + { + "type": "function to execute", + ... + }, { + "type": "other function", + ... + }, + ... +] +``` + +Each object in the main array must have a `type` entry which specifies which +function should be executed. + +Currently, the following functions are supported: + +- target +- kwargs +- default_options + +### Target modification format + +The format for the type `target` is defined as follows: + +```json +{ + "type": "target", + "target": "target ID/name/assignment variable", + "operation": "one of ['src_add', 'src_rm', 'target_rm', 'target_add', 'info']", + "sources": ["list", "of", "source", "files", "to", "add, remove"], + "subdir": "subdir where the new target should be added (only has an effect for operation 'tgt_add')", + "target_type": "function name of the new target -- same as in the CLI (only has an effect for operation 'tgt_add')" +} +``` + +The keys `sources`, `subdir` and `target_type` are optional. + +### kwargs modification format + +The format for the type `target` is defined as follows: + +```json +{ + "type": "kwargs", + "function": "one of ['dependency', 'target', 'project']", + "id": "function ID", + "operation": "one of ['set', 'delete', 'add', 'remove', 'remove_regex', 'info']", + "kwargs": { + "key1": "value1", + "key2": "value2", + ... + } +} +``` + +### Default options modification format + +The format for the type `default_options` is defined as follows: + +```json +{ + "type": "default_options", + "operation": "one of ['set', 'delete']", + "options": { + "opt1": "value1", + "opt2": "value2", + ... + } +} +``` + +For operation `delete`, the values of the `options` can be anything (including `null`) + +## Extracting information + +The rewriter also offers operation `info` for the types `target` and `kwargs`. +When this operation is used, meson will print a JSON dump to stderr, containing +all available information to the rewriter about the build target / function +kwargs in question. + +The output format is currently experimental and may change in the future. diff --git a/docs/markdown/snippets/rewriter.md b/docs/markdown/snippets/rewriter.md new file mode 100644 index 0000000..7a4621d --- /dev/null +++ b/docs/markdown/snippets/rewriter.md @@ -0,0 +1,18 @@ +## Meson file rewriter + +This release adds the functionality to perform some basic modification +on the `meson.build` files from the command line. The currently +supported operations are: + +- For build targets: + - Add/Remove source files + - Add/Remove targets + - Modify a select set of kwargs + - Print some JSON information +- For dependencies: + - Modify a select set of kwargs +- For the project function: + - Modify a select set of kwargs + - Modify the default options list + +For more information see the rewriter documentation. diff --git a/docs/sitemap.txt b/docs/sitemap.txt index 6987641..bea2a31 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -60,6 +60,7 @@ index.md Reference-manual.md Reference-tables.md Style-guide.md + Rewriter.md FAQ.md Reproducible-builds.md howtox.md diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index b2cd3f5..01277f0 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -51,6 +51,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): self.visitors = visitors self.visited_subdirs = {} self.assignments = {} + self.assign_vals = {} self.reverse_assignment = {} self.funcs.update({'project': self.func_do_nothing, 'test': self.func_do_nothing, @@ -161,7 +162,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): self.assignments[node.var_name] += [node.value] # Save a reference to the value node if hasattr(node.value, 'ast_id'): self.reverse_assignment[node.value.ast_id] = node - self.evaluate_statement(node.value) # Evaluate the value just in case + self.assign_vals[node.var_name] += [self.evaluate_statement(node.value)] def evaluate_indexing(self, node): return 0 @@ -200,7 +201,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): self.assignments[node.var_name] = [node.value] # Save a reference to the value node if hasattr(node.value, 'ast_id'): self.reverse_assignment[node.value.ast_id] = node - self.evaluate_statement(node.value) # Evaluate the value just in case + self.assign_vals[node.var_name] = [self.evaluate_statement(node.value)] # Evaluate the value just in case def flatten_args(self, args, include_unknown_args: bool = False): # Resolve mparser.ArrayNode if needed diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 12cb379..5745d29 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -194,7 +194,7 @@ class IntrospectionInterpreter(AstInterpreter): empty_sources = [] # Passing the unresolved sources list causes errors target = targetclass(name, self.subdir, self.subproject, is_cross, empty_sources, objects, self.environment, kwargs_reduced) - self.targets += [{ + new_target = { 'name': target.get_basename(), 'id': target.get_id(), 'type': target.get_typename(), @@ -206,9 +206,10 @@ class IntrospectionInterpreter(AstInterpreter): 'sources': source_nodes, 'kwargs': kwargs, 'node': node, - }] + } - return + self.targets += [new_target] + return new_target def build_library(self, node, args, kwargs): default_library = self.coredata.get_builtin_option('default_library') diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 516c411..822a943 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -18,6 +18,7 @@ import importlib import traceback import argparse import codecs +import shutil from . import mesonlib from . import mlog @@ -29,9 +30,12 @@ from .wrap import wraptool class CommandLineParser: def __init__(self): + self.term_width = shutil.get_terminal_size().columns + self.formater = lambda prog: argparse.HelpFormatter(prog, max_help_position=int(self.term_width / 2), width=self.term_width) + self.commands = {} self.hidden_commands = [] - self.parser = argparse.ArgumentParser(prog='meson') + self.parser = argparse.ArgumentParser(prog='meson', formatter_class=self.formater) self.subparsers = self.parser.add_subparsers(title='Commands', description='If no command is specified it defaults to setup command.') self.add_command('setup', msetup.add_arguments, msetup.run, @@ -52,26 +56,27 @@ class CommandLineParser: help='Manage subprojects') self.add_command('help', self.add_help_arguments, self.run_help_command, help='Print help of a subcommand') + self.add_command('rewrite', lambda parser: rewriter.add_arguments(parser, self.formater), rewriter.run, + help='Modify the project definition') # Hidden commands - self.add_command('rewrite', rewriter.add_arguments, rewriter.run, - help=argparse.SUPPRESS) self.add_command('runpython', self.add_runpython_arguments, self.run_runpython_command, help=argparse.SUPPRESS) self.add_command('unstable-coredata', munstable_coredata.add_arguments, munstable_coredata.run, help=argparse.SUPPRESS) - def add_command(self, name, add_arguments_func, run_func, help): + def add_command(self, name, add_arguments_func, run_func, help, aliases=[]): # FIXME: Cannot have hidden subparser: # https://bugs.python.org/issue22848 if help == argparse.SUPPRESS: - p = argparse.ArgumentParser(prog='meson ' + name) + p = argparse.ArgumentParser(prog='meson ' + name, formatter_class=self.formater) self.hidden_commands.append(name) else: - p = self.subparsers.add_parser(name, help=help) + p = self.subparsers.add_parser(name, help=help, aliases=aliases, formatter_class=self.formater) add_arguments_func(p) p.set_defaults(run_func=run_func) - self.commands[name] = p + for i in [name] + aliases: + self.commands[i] = p def add_runpython_arguments(self, parser): parser.add_argument('script_file') diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index a8b146f..0434274 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -48,6 +48,7 @@ log_depth = 0 log_timestamp_start = None log_fatal_warnings = False log_disable_stdout = False +log_errors_only = False def disable(): global log_disable_stdout @@ -57,6 +58,14 @@ def enable(): global log_disable_stdout log_disable_stdout = False +def set_quiet(): + global log_errors_only + log_errors_only = True + +def set_verbose(): + global log_errors_only + log_errors_only = False + def initialize(logdir, fatal_warnings=False): global log_dir, log_file, log_fatal_warnings log_dir = logdir @@ -152,14 +161,16 @@ def debug(*args, **kwargs): print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes. log_file.flush() -def log(*args, **kwargs): +def log(*args, is_error=False, **kwargs): + global log_errors_only arr = process_markup(args, False) if log_file is not None: print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes. log_file.flush() if colorize_console: arr = process_markup(args, True) - force_print(*arr, **kwargs) + if not log_errors_only or is_error: + force_print(*arr, **kwargs) def _log_error(severity, *args, **kwargs): from .mesonlib import get_error_location_string @@ -187,13 +198,13 @@ def _log_error(severity, *args, **kwargs): raise MesonException("Fatal warnings enabled, aborting") def error(*args, **kwargs): - return _log_error('error', *args, **kwargs) + return _log_error('error', *args, **kwargs, is_error=True) def warning(*args, **kwargs): - return _log_error('warning', *args, **kwargs) + return _log_error('warning', *args, **kwargs, is_error=True) def deprecation(*args, **kwargs): - return _log_error('deprecation', *args, **kwargs) + return _log_error('deprecation', *args, **kwargs, is_error=True) def exception(e, prefix=red('ERROR:')): log() diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index 2619aae..975655c 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -25,21 +25,49 @@ from .ast import IntrospectionInterpreter, build_target_functions, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstPrinter from mesonbuild.mesonlib import MesonException -from . import mlog, mparser, environment +from . import mlog, environment from functools import wraps -from pprint import pprint -from .mparser import Token, ArrayNode, ArgumentNode, AssignmentNode, IdNode, FunctionNode, StringNode -import json, os, re +from typing import List, Dict, Optional +from .mparser import Token, ArrayNode, ArgumentNode, AssignmentNode, BaseNode, BooleanNode, ElementaryNode, IdNode, FunctionNode, StringNode +import json, os, re, sys class RewriterException(MesonException): pass -def add_arguments(parser): - parser.add_argument('--sourcedir', default='.', - help='Path to source directory.') - parser.add_argument('-p', '--print', action='store_true', default=False, dest='print', - help='Print the parsed AST.') - parser.add_argument('command', type=str) +def add_arguments(parser, formater=None): + parser.add_argument('-s', '--sourcedir', type=str, default='.', metavar='SRCDIR', help='Path to source directory.') + parser.add_argument('-V', '--verbose', action='store_true', default=False, help='Enable verbose output') + parser.add_argument('-S', '--skip-errors', dest='skip', action='store_true', default=False, help='Skip errors instead of aborting') + subparsers = parser.add_subparsers(dest='type', title='Rewriter commands', description='Rewrite command to execute') + + # Target + tgt_parser = subparsers.add_parser('target', help='Modify a target', formatter_class=formater) + tgt_parser.add_argument('-s', '--subdir', default='', dest='subdir', help='Subdirectory of the new target (only for the "add_target" action)') + tgt_parser.add_argument('--type', dest='tgt_type', choices=rewriter_keys['target']['target_type'][2], default='executable', + help='Type of the target to add (only for the "add_target" action)') + tgt_parser.add_argument('target', help='Name or ID of the target') + tgt_parser.add_argument('operation', choices=['add', 'rm', 'add_target', 'rm_target', 'info'], + help='Action to execute') + tgt_parser.add_argument('sources', nargs='*', help='Sources to add/remove') + + # KWARGS + kw_parser = subparsers.add_parser('kwargs', help='Modify keyword arguments', formatter_class=formater) + kw_parser.add_argument('operation', choices=rewriter_keys['kwargs']['operation'][2], + help='Action to execute') + kw_parser.add_argument('function', choices=list(rewriter_func_kwargs.keys()), + help='Function type to modify') + kw_parser.add_argument('id', help='ID of the function to modify (can be anything for "project")') + kw_parser.add_argument('kwargs', nargs='*', help='Pairs of keyword and value') + + # Default options + def_parser = subparsers.add_parser('default-options', help='Modify the project default options', formatter_class=formater) + def_parser.add_argument('operation', choices=rewriter_keys['default_options']['operation'][2], + help='Action to execute') + def_parser.add_argument('options', nargs='*', help='Key, value pairs of configuration option') + + # JSON file/command + cmd_parser = subparsers.add_parser('command', help='Execute a JSON array of commands', formatter_class=formater) + cmd_parser.add_argument('json', help='JSON string or file to execute') class RequiredKeys: def __init__(self, keys): @@ -73,7 +101,7 @@ class RequiredKeys: return wrapped class MTypeBase: - def __init__(self, node: mparser.BaseNode): + def __init__(self, node: Optional[BaseNode] = None): if node is None: self.node = self._new_node() else: @@ -85,7 +113,7 @@ class MTypeBase: def _new_node(self): # Overwrite in derived class - return mparser.BaseNode() + return BaseNode() def can_modify(self): return self.node_type is not None @@ -114,57 +142,57 @@ class MTypeBase: mlog.warning('Cannot remove a regex in type', mlog.bold(type(self).__name__), '--> skipping') class MTypeStr(MTypeBase): - def __init__(self, node: mparser.BaseNode): + def __init__(self, node: Optional[BaseNode] = None): super().__init__(node) def _new_node(self): - return mparser.StringNode(mparser.Token('', '', 0, 0, 0, None, '')) + return StringNode(Token('', '', 0, 0, 0, None, '')) def supported_nodes(self): - return [mparser.StringNode] + return [StringNode] def set_value(self, value): self.node.value = str(value) class MTypeBool(MTypeBase): - def __init__(self, node: mparser.BaseNode): + def __init__(self, node: Optional[BaseNode] = None): super().__init__(node) def _new_node(self): - return mparser.StringNode(mparser.Token('', '', 0, 0, 0, None, False)) + return StringNode(Token('', '', 0, 0, 0, None, False)) def supported_nodes(self): - return [mparser.BooleanNode] + return [BooleanNode] def set_value(self, value): self.node.value = bool(value) class MTypeID(MTypeBase): - def __init__(self, node: mparser.BaseNode): + def __init__(self, node: Optional[BaseNode] = None): super().__init__(node) def _new_node(self): - return mparser.StringNode(mparser.Token('', '', 0, 0, 0, None, '')) + return StringNode(Token('', '', 0, 0, 0, None, '')) def supported_nodes(self): - return [mparser.IdNode] + return [IdNode] def set_value(self, value): self.node.value = str(value) class MTypeList(MTypeBase): - def __init__(self, node: mparser.BaseNode): + def __init__(self, node: Optional[BaseNode] = None): super().__init__(node) def _new_node(self): - return mparser.ArrayNode(mparser.ArgumentNode(mparser.Token('', '', 0, 0, 0, None, '')), 0, 0, 0, 0) + return ArrayNode(ArgumentNode(Token('', '', 0, 0, 0, None, '')), 0, 0, 0, 0) def _new_element_node(self, value): # Overwrite in derived class - return mparser.BaseNode() + return BaseNode() def _ensure_array_node(self): - if not isinstance(self.node, mparser.ArrayNode): + if not isinstance(self.node, ArrayNode): tmp = self.node self.node = self._new_node() self.node.args.arguments += [tmp] @@ -178,7 +206,7 @@ class MTypeList(MTypeBase): return False def get_node(self): - if isinstance(self.node, mparser.ArrayNode): + if isinstance(self.node, ArrayNode): if len(self.node.args.arguments) == 1: return self.node.args.arguments[0] return self.node @@ -188,7 +216,7 @@ class MTypeList(MTypeBase): return [] def supported_nodes(self): - return [mparser.ArrayNode] + self.supported_element_nodes() + return [ArrayNode] + self.supported_element_nodes() def set_value(self, value): if not isinstance(value, list): @@ -228,44 +256,44 @@ class MTypeList(MTypeBase): self._remove_helper(regex, self._check_regex_matches) class MTypeStrList(MTypeList): - def __init__(self, node: mparser.BaseNode): + def __init__(self, node: Optional[BaseNode] = None): super().__init__(node) def _new_element_node(self, value): - return mparser.StringNode(mparser.Token('', '', 0, 0, 0, None, str(value))) + return StringNode(Token('', '', 0, 0, 0, None, str(value))) def _check_is_equal(self, node, value) -> bool: - if isinstance(node, mparser.StringNode): + if isinstance(node, StringNode): return node.value == value return False def _check_regex_matches(self, node, regex: str) -> bool: - if isinstance(node, mparser.StringNode): + if isinstance(node, StringNode): return re.match(regex, node.value) is not None return False def supported_element_nodes(self): - return [mparser.StringNode] + return [StringNode] class MTypeIDList(MTypeList): - def __init__(self, node: mparser.BaseNode): + def __init__(self, node: Optional[BaseNode] = None): super().__init__(node) def _new_element_node(self, value): - return mparser.IdNode(mparser.Token('', '', 0, 0, 0, None, str(value))) + return IdNode(Token('', '', 0, 0, 0, None, str(value))) def _check_is_equal(self, node, value) -> bool: - if isinstance(node, mparser.IdNode): + if isinstance(node, IdNode): return node.value == value return False def _check_regex_matches(self, node, regex: str) -> bool: - if isinstance(node, mparser.StringNode): + if isinstance(node, StringNode): return re.match(regex, node.value) is not None return False def supported_element_nodes(self): - return [mparser.IdNode] + return [IdNode] rewriter_keys = { 'default_options': { @@ -280,11 +308,10 @@ rewriter_keys = { }, 'target': { 'target': (str, None, None), - 'operation': (str, None, ['src_add', 'src_rm', 'tgt_rm', 'tgt_add', 'info']), + 'operation': (str, None, ['src_add', 'src_rm', 'target_rm', 'target_add', 'info']), 'sources': (list, [], None), 'subdir': (str, '', None), 'target_type': (str, 'executable', ['both_libraries', 'executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library']), - 'debug': (bool, False, None) } } @@ -322,9 +349,10 @@ rewriter_func_kwargs = { } class Rewriter: - def __init__(self, sourcedir: str, generator: str = 'ninja'): + def __init__(self, sourcedir: str, generator: str = 'ninja', skip_errors: bool = False): self.sourcedir = sourcedir self.interpreter = IntrospectionInterpreter(sourcedir, '', generator, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()]) + self.skip_errors = skip_errors self.modefied_nodes = [] self.to_remove_nodes = [] self.to_add_nodes = [] @@ -351,29 +379,45 @@ class Rewriter: def print_info(self): if self.info_dump is None: return - # Wrap the dump in magic strings - print('!!==JSON DUMP: BEGIN==!!') - print(json.dumps(self.info_dump, indent=2)) - print('!!==JSON DUMP: END==!!') + sys.stderr.write(json.dumps(self.info_dump, indent=2)) + + def on_error(self): + if self.skip_errors: + return mlog.cyan('-->'), mlog.yellow('skipping') + return mlog.cyan('-->'), mlog.red('aborting') + + def handle_error(self): + if self.skip_errors: + return None + raise MesonException('Rewriting the meson.build failed') def find_target(self, target: str): - def check_list(name: str): + def check_list(name: str) -> List[BaseNode]: + result = [] for i in self.interpreter.targets: if name == i['name'] or name == i['id']: - return i - return None + result += [i] + return result - tgt = check_list(target) - if tgt is not None: - return tgt + targets = check_list(target) + if targets: + if len(targets) == 1: + return targets[0] + else: + mlog.error('There are multiple targets matching', mlog.bold(target)) + for i in targets: + mlog.error(' -- Target name', mlog.bold(i['name']), 'with ID', mlog.bold(i['id'])) + mlog.error('Please try again with the unique ID of the target', *self.on_error()) + self.handle_error() + return None # Check the assignments + tgt = None if target in self.interpreter.assignments: node = self.interpreter.assignments[target][0] - if isinstance(node, mparser.FunctionNode): + if isinstance(node, FunctionNode): if node.func_name in ['executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library', 'both_libraries']: - name = self.interpreter.flatten_args(node.args)[0] - tgt = check_list(name) + tgt = self.interpreter.assign_vals[target][0] return tgt @@ -391,7 +435,7 @@ class Rewriter: # Check the assignments if dependency in self.interpreter.assignments: node = self.interpreter.assignments[dependency][0] - if isinstance(node, mparser.FunctionNode): + if isinstance(node, FunctionNode): if node.func_name in ['dependency']: name = self.interpreter.flatten_args(node.args)[0] dep = check_list(name) @@ -429,13 +473,15 @@ class Rewriter: for key, val in sorted(cmd['options'].items()): if key not in options: - mlog.error('Unknown options', mlog.bold(key), '--> skipping') + mlog.error('Unknown options', mlog.bold(key), *self.on_error()) + self.handle_error() continue try: val = options[key].validate_value(val) except MesonException as e: - mlog.error('Unable to set', mlog.bold(key), mlog.red(str(e)), '--> skipping') + mlog.error('Unable to set', mlog.bold(key), mlog.red(str(e)), *self.on_error()) + self.handle_error() continue kwargs_cmd['kwargs']['default_options'] += ['{}={}'.format(key, val)] @@ -446,14 +492,17 @@ class Rewriter: def process_kwargs(self, cmd): mlog.log('Processing function type', mlog.bold(cmd['function']), 'with id', mlog.cyan("'" + cmd['id'] + "'")) if cmd['function'] not in rewriter_func_kwargs: - mlog.error('Unknown function type {} --> skipping'.format(cmd['function'])) - return + mlog.error('Unknown function type', cmd['function'], *self.on_error()) + return self.handle_error() kwargs_def = rewriter_func_kwargs[cmd['function']] # Find the function node to modify node = None arg_node = None if cmd['function'] == 'project': + if cmd['id'] != '/': + mlog.error('The ID for the function type project must be an empty string', *self.on_error()) + self.handle_error() node = self.interpreter.project_node arg_node = node.args elif cmd['function'] == 'target': @@ -468,21 +517,21 @@ class Rewriter: arg_node = node.args if not node: mlog.error('Unable to find the function node') - assert(isinstance(node, mparser.FunctionNode)) - assert(isinstance(arg_node, mparser.ArgumentNode)) + assert(isinstance(node, FunctionNode)) + assert(isinstance(arg_node, ArgumentNode)) # Print kwargs info if cmd['operation'] == 'info': info_data = {} for key, val in sorted(arg_node.kwargs.items()): info_data[key] = None - if isinstance(val, mparser.ElementaryNode): + if isinstance(val, ElementaryNode): info_data[key] = val.value - elif isinstance(val, mparser.ArrayNode): + elif isinstance(val, ArrayNode): data_list = [] for i in val.args.arguments: element = None - if isinstance(i, mparser.ElementaryNode): + if isinstance(i, ElementaryNode): element = i.value data_list += [element] info_data[key] = data_list @@ -494,7 +543,8 @@ class Rewriter: num_changed = 0 for key, val in sorted(cmd['kwargs'].items()): if key not in kwargs_def: - mlog.error('Cannot modify unknown kwarg --> skipping', mlog.bold(key)) + mlog.error('Cannot modify unknown kwarg', mlog.bold(key), *self.on_error()) + self.handle_error() continue # Remove the key from the kwargs @@ -535,7 +585,7 @@ class Rewriter: if num_changed > 0 and node not in self.modefied_nodes: self.modefied_nodes += [node] - def find_assignment_node(self, node: mparser) -> AssignmentNode: + def find_assignment_node(self, node: BaseNode) -> AssignmentNode: if hasattr(node, 'ast_id') and node.ast_id in self.interpreter.reverse_assignment: return self.interpreter.reverse_assignment[node.ast_id] return None @@ -544,13 +594,22 @@ class Rewriter: def process_target(self, cmd): mlog.log('Processing target', mlog.bold(cmd['target']), 'operation', mlog.cyan(cmd['operation'])) target = self.find_target(cmd['target']) - if target is None and cmd['operation'] != 'tgt_add': - mlog.error('Unknown target "{}" --> skipping'.format(cmd['target'])) - if cmd['debug']: - pprint(self.interpreter.targets) - return - if cmd['debug']: - pprint(target) + if target is None and cmd['operation'] != 'target_add': + mlog.error('Unknown target', mlog.bold(cmd['target']), *self.on_error()) + return self.handle_error() + + # Make source paths relative to the current subdir + def rel_source(src: str) -> str: + subdir = os.path.abspath(os.path.join(self.sourcedir, target['subdir'])) + if os.path.isabs(src): + return os.path.relpath(src, subdir) + elif not os.path.exists(src): + return src # Trust the user when the source doesn't exist + # Make sure that the path is relative to the subdir + return os.path.relpath(os.path.abspath(src), subdir) + + if target is not None: + cmd['sources'] = [rel_source(x) for x in cmd['sources']] # Utility function to get a list of the sources from a node def arg_list_from_node(n): @@ -642,34 +701,38 @@ class Rewriter: if root not in self.modefied_nodes: self.modefied_nodes += [root] - elif cmd['operation'] == 'tgt_add': + elif cmd['operation'] == 'target_add': if target is not None: - mlog.error('Can not add target', mlog.bold(cmd['target']), 'because it already exists') - return + mlog.error('Can not add target', mlog.bold(cmd['target']), 'because it already exists', *self.on_error()) + return self.handle_error() + + id_base = re.sub(r'[- ]', '_', cmd['target']) + target_id = id_base + '_exe' if cmd['target_type'] == 'executable' else '_lib' + source_id = id_base + '_sources' # Build src list src_arg_node = ArgumentNode(Token('string', cmd['subdir'], 0, 0, 0, None, '')) src_arr_node = ArrayNode(src_arg_node, 0, 0, 0, 0) src_far_node = ArgumentNode(Token('string', cmd['subdir'], 0, 0, 0, None, '')) src_fun_node = FunctionNode(cmd['subdir'], 0, 0, 0, 0, 'files', src_far_node) - src_ass_node = AssignmentNode(cmd['subdir'], 0, 0, '{}_src'.format(cmd['target']), src_fun_node) + src_ass_node = AssignmentNode(cmd['subdir'], 0, 0, source_id, src_fun_node) src_arg_node.arguments = [StringNode(Token('string', cmd['subdir'], 0, 0, 0, None, x)) for x in cmd['sources']] src_far_node.arguments = [src_arr_node] # Build target tgt_arg_node = ArgumentNode(Token('string', cmd['subdir'], 0, 0, 0, None, '')) tgt_fun_node = FunctionNode(cmd['subdir'], 0, 0, 0, 0, cmd['target_type'], tgt_arg_node) - tgt_ass_node = AssignmentNode(cmd['subdir'], 0, 0, '{}_tgt'.format(cmd['target']), tgt_fun_node) + tgt_ass_node = AssignmentNode(cmd['subdir'], 0, 0, target_id, tgt_fun_node) tgt_arg_node.arguments = [ StringNode(Token('string', cmd['subdir'], 0, 0, 0, None, cmd['target'])), - IdNode(Token('string', cmd['subdir'], 0, 0, 0, None, '{}_src'.format(cmd['target']))) + IdNode(Token('string', cmd['subdir'], 0, 0, 0, None, source_id)) ] src_ass_node.accept(AstIndentationGenerator()) tgt_ass_node.accept(AstIndentationGenerator()) self.to_add_nodes += [src_ass_node, tgt_ass_node] - elif cmd['operation'] == 'tgt_rm': + elif cmd['operation'] == 'target_rm': to_remove = self.find_assignment_node(target['node']) if to_remove is None: to_remove = target['node'] @@ -717,7 +780,7 @@ class Rewriter: # Sort based on line and column in reversed order work_nodes = [{'node': x, 'action': 'modify'} for x in self.modefied_nodes] work_nodes += [{'node': x, 'action': 'rm'} for x in self.to_remove_nodes] - work_nodes = list(sorted(work_nodes, key=lambda x: x['node'].lineno * 1000 + x['node'].colno, reverse=True)) + work_nodes = list(sorted(work_nodes, key=lambda x: (x['node'].lineno, x['node'].colno), reverse=True)) work_nodes += [{'node': x, 'action': 'add'} for x in self.to_add_nodes] # Generating the new replacement string @@ -802,23 +865,92 @@ class Rewriter: with open(val['path'], 'w') as fp: fp.write(val['raw']) -def run(options): - rewriter = Rewriter(options.sourcedir) - rewriter.analyze_meson() - if os.path.exists(options.command): - with open(options.command, 'r') as fp: - commands = json.load(fp) - else: - commands = json.loads(options.command) - - if not isinstance(commands, list): - raise TypeError('Command is not a list') +target_operation_map = { + 'add': 'src_add', + 'rm': 'src_rm', + 'add_target': 'target_add', + 'rm_target': 'target_rm', + 'info': 'info', +} - for i in commands: - if not isinstance(i, object): - raise TypeError('Command is not an object') - rewriter.process(i) +def list_to_dict(in_list: List[str]) -> Dict[str, str]: + if len(in_list) % 2 != 0: + raise TypeError('An even ammount of arguments are required') + result = {} + for i in range(0, len(in_list), 2): + result[in_list[i]] = in_list[i + 1] + return result + +def generate_target(options) -> List[dict]: + return [{ + 'type': 'target', + 'target': options.target, + 'operation': target_operation_map[options.operation], + 'sources': options.sources, + 'subdir': options.subdir, + 'target_type': options.tgt_type, + }] + +def generate_kwargs(options) -> List[dict]: + return [{ + 'type': 'kwargs', + 'function': options.function, + 'id': options.id, + 'operation': options.operation, + 'kwargs': list_to_dict(options.kwargs), + }] + +def generate_def_opts(options) -> List[dict]: + return [{ + 'type': 'default_options', + 'operation': options.operation, + 'options': list_to_dict(options.options), + }] + +def genreate_cmd(options) -> List[dict]: + if os.path.exists(options.json): + with open(options.json, 'r') as fp: + return json.load(fp) + else: + return json.loads(options.json) + +# Map options.type to the actual type name +cli_type_map = { + 'target': generate_target, + 'tgt': generate_target, + 'kwargs': generate_kwargs, + 'default-options': generate_def_opts, + 'def': generate_def_opts, + 'command': genreate_cmd, + 'cmd': genreate_cmd, +} - rewriter.apply_changes() - rewriter.print_info() - return 0 +def run(options): + if not options.verbose: + mlog.set_quiet() + + try: + rewriter = Rewriter(options.sourcedir, skip_errors=options.skip) + rewriter.analyze_meson() + + if options.type is None: + mlog.error('No command specified') + return 1 + + commands = cli_type_map[options.type](options) + + if not isinstance(commands, list): + raise TypeError('Command is not a list') + + for i in commands: + if not isinstance(i, object): + raise TypeError('Command is not an object') + rewriter.process(i) + + rewriter.apply_changes() + rewriter.print_info() + return 0 + except Exception as e: + raise e + finally: + mlog.set_verbose() diff --git a/run_unittests.py b/run_unittests.py index 90a726b..690ea33 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -5187,8 +5187,6 @@ class PythonTests(BasePlatformTests): class RewriterTests(BasePlatformTests): - data_regex = re.compile(r'.*\n!!==JSON DUMP: BEGIN==!!\n(.*)\n!!==JSON DUMP: END==!!\n', re.MULTILINE | re.DOTALL) - def setUp(self): super().setUp() self.maxDiff = None @@ -5196,30 +5194,32 @@ class RewriterTests(BasePlatformTests): def prime(self, dirname): copy_tree(os.path.join(self.rewrite_test_dir, dirname), self.builddir) - def rewrite(self, directory, args): + def rewrite_raw(self, directory, args): if isinstance(args, str): args = [args] - command = self.rewrite_command + ['--sourcedir', directory] + args - p = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + command = self.rewrite_command + ['--verbose', '--skip', '--sourcedir', directory] + args + p = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=60) + print('STDOUT:') print(p.stdout) + print('STDERR:') + print(p.stderr) if p.returncode != 0: if 'MESON_SKIP_TEST' in p.stdout: raise unittest.SkipTest('Project requested skipping.') raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout) - return p.stdout + if not p.stderr: + return {} + return json.loads(p.stderr) - def extract_test_data(self, out): - match = RewriterTests.data_regex.match(out) - result = {} - if match: - result = json.loads(match.group(1)) - return result + def rewrite(self, directory, args): + if isinstance(args, str): + args = [args] + return self.rewrite_raw(directory, ['command'] + args) def test_target_source_list(self): self.prime('1 basic') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'target': { 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp']}, @@ -5239,7 +5239,6 @@ class RewriterTests(BasePlatformTests): def test_target_add_sources(self): self.prime('1 basic') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) - out = self.extract_test_data(out) expected = { 'target': { 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp']}, @@ -5258,13 +5257,21 @@ class RewriterTests(BasePlatformTests): # Check the written file out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) + self.assertDictEqual(out, expected) + + def test_target_add_sources_abs(self): + self.prime('1 basic') + abs_src = [os.path.join(self.builddir, x) for x in ['a1.cpp', 'a2.cpp', 'a6.cpp']] + add = json.dumps([{"type": "target", "target": "trivialprog1", "operation": "src_add", "sources": abs_src}]) + inf = json.dumps([{"type": "target", "target": "trivialprog1", "operation": "info"}]) + self.rewrite(self.builddir, add) + out = self.rewrite(self.builddir, inf) + expected = {'target': {'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}}} self.assertDictEqual(out, expected) def test_target_remove_sources(self): self.prime('1 basic') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'rmSrc.json')) - out = self.extract_test_data(out) expected = { 'target': { 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileC.cpp']}, @@ -5283,26 +5290,22 @@ class RewriterTests(BasePlatformTests): # Check the written file out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) self.assertDictEqual(out, expected) def test_target_subdir(self): self.prime('2 subdirs') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) - out = self.extract_test_data(out) expected = {'name': 'something', 'sources': ['first.c', 'second.c', 'third.c']} self.assertDictEqual(list(out['target'].values())[0], expected) # Check the written file out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) self.assertDictEqual(list(out['target'].values())[0], expected) def test_target_remove(self): self.prime('1 basic') self.rewrite(self.builddir, os.path.join(self.builddir, 'rmTgt.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'target': { @@ -5321,7 +5324,6 @@ class RewriterTests(BasePlatformTests): self.prime('1 basic') self.rewrite(self.builddir, os.path.join(self.builddir, 'addTgt.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'target': { @@ -5344,14 +5346,12 @@ class RewriterTests(BasePlatformTests): self.prime('2 subdirs') self.rewrite(self.builddir, os.path.join(self.builddir, 'rmTgt.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) self.assertDictEqual(out, {}) def test_target_add_subdir(self): self.prime('2 subdirs') self.rewrite(self.builddir, os.path.join(self.builddir, 'addTgt.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = {'name': 'something', 'sources': ['first.c', 'second.c']} self.assertDictEqual(out['target']['94b671c@@something@exe'], expected) @@ -5361,7 +5361,6 @@ class RewriterTests(BasePlatformTests): inf_json = json.dumps([{'type': 'target', 'target': 'exe1', 'operation': 'info'}]) out = self.rewrite(self.builddir, add_json) out = self.rewrite(self.builddir, inf_json) - out = self.extract_test_data(out) expected = { 'target': { 'exe1@exe': { @@ -5401,13 +5400,21 @@ class RewriterTests(BasePlatformTests): } self.assertDictEqual(out, expected) + def test_target_same_name_skip(self): + self.prime('4 same name targets') + out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) + out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) + expected = {'name': 'myExe', 'sources': ['main.cpp']} + self.assertEqual(len(out['target']), 2) + for _, val in out['target'].items(): + self.assertDictEqual(expected, val) + def test_kwargs_info(self): self.prime('3 kwargs') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'kwargs': { - 'project#': {'version': '0.0.1'}, + 'project#/': {'version': '0.0.1'}, 'target#tgt1': {'build_by_default': True}, 'dependency#dep1': {'required': False} } @@ -5418,10 +5425,9 @@ class RewriterTests(BasePlatformTests): self.prime('3 kwargs') self.rewrite(self.builddir, os.path.join(self.builddir, 'set.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'kwargs': { - 'project#': {'version': '0.0.2', 'meson_version': '0.50.0', 'license': ['GPL', 'MIT']}, + 'project#/': {'version': '0.0.2', 'meson_version': '0.50.0', 'license': ['GPL', 'MIT']}, 'target#tgt1': {'build_by_default': False, 'build_rpath': '/usr/local', 'dependencies': 'dep1'}, 'dependency#dep1': {'required': True, 'method': 'cmake'} } @@ -5432,10 +5438,9 @@ class RewriterTests(BasePlatformTests): self.prime('3 kwargs') self.rewrite(self.builddir, os.path.join(self.builddir, 'add.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'kwargs': { - 'project#': {'version': '0.0.1', 'license': ['GPL', 'MIT', 'BSD']}, + 'project#/': {'version': '0.0.1', 'license': ['GPL', 'MIT', 'BSD']}, 'target#tgt1': {'build_by_default': True}, 'dependency#dep1': {'required': False} } @@ -5446,10 +5451,9 @@ class RewriterTests(BasePlatformTests): self.prime('3 kwargs') self.rewrite(self.builddir, os.path.join(self.builddir, 'remove.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'kwargs': { - 'project#': {'version': '0.0.1', 'license': 'GPL'}, + 'project#/': {'version': '0.0.1', 'license': 'GPL'}, 'target#tgt1': {'build_by_default': True}, 'dependency#dep1': {'required': False} } @@ -5460,10 +5464,9 @@ class RewriterTests(BasePlatformTests): self.prime('3 kwargs') self.rewrite(self.builddir, os.path.join(self.builddir, 'remove_regex.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'kwargs': { - 'project#': {'version': '0.0.1', 'default_options': ['buildtype=release', 'debug=true']}, + 'project#/': {'version': '0.0.1', 'default_options': ['buildtype=release', 'debug=true']}, 'target#tgt1': {'build_by_default': True}, 'dependency#dep1': {'required': False} } @@ -5474,10 +5477,9 @@ class RewriterTests(BasePlatformTests): self.prime('3 kwargs') self.rewrite(self.builddir, os.path.join(self.builddir, 'delete.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'kwargs': { - 'project#': {}, + 'project#/': {}, 'target#tgt1': {}, 'dependency#dep1': {'required': False} } @@ -5488,10 +5490,9 @@ class RewriterTests(BasePlatformTests): self.prime('3 kwargs') self.rewrite(self.builddir, os.path.join(self.builddir, 'defopts_set.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'kwargs': { - 'project#': {'version': '0.0.1', 'default_options': ['buildtype=release', 'debug=True', 'cpp_std=c++11']}, + 'project#/': {'version': '0.0.1', 'default_options': ['buildtype=release', 'debug=True', 'cpp_std=c++11']}, 'target#tgt1': {'build_by_default': True}, 'dependency#dep1': {'required': False} } @@ -5502,10 +5503,9 @@ class RewriterTests(BasePlatformTests): self.prime('3 kwargs') self.rewrite(self.builddir, os.path.join(self.builddir, 'defopts_delete.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - out = self.extract_test_data(out) expected = { 'kwargs': { - 'project#': {'version': '0.0.1', 'default_options': ['cpp_std=c++14', 'debug=true']}, + 'project#/': {'version': '0.0.1', 'default_options': ['cpp_std=c++14', 'debug=true']}, 'target#tgt1': {'build_by_default': True}, 'dependency#dep1': {'required': False} } diff --git a/test cases/rewrite/1 basic/addTgt.json b/test cases/rewrite/1 basic/addTgt.json index 432e299..2f4e7e2 100644 --- a/test cases/rewrite/1 basic/addTgt.json +++ b/test cases/rewrite/1 basic/addTgt.json @@ -2,7 +2,7 @@ { "type": "target", "target": "trivialprog10", - "operation": "tgt_add", + "operation": "target_add", "sources": ["new1.cpp", "new2.cpp"], "target_type": "shared_library" } diff --git a/test cases/rewrite/1 basic/rmTgt.json b/test cases/rewrite/1 basic/rmTgt.json index 5774157..dbaf025 100644 --- a/test cases/rewrite/1 basic/rmTgt.json +++ b/test cases/rewrite/1 basic/rmTgt.json @@ -2,16 +2,16 @@ { "type": "target", "target": "exe0", - "operation": "tgt_rm" + "operation": "target_rm" }, { "type": "target", "target": "trivialprog1", - "operation": "tgt_rm" + "operation": "target_rm" }, { "type": "target", "target": "trivialprog9", - "operation": "tgt_rm" + "operation": "target_rm" } ] diff --git a/test cases/rewrite/2 subdirs/addTgt.json b/test cases/rewrite/2 subdirs/addTgt.json index 01e9a6e..2e1e8bc 100644 --- a/test cases/rewrite/2 subdirs/addTgt.json +++ b/test cases/rewrite/2 subdirs/addTgt.json @@ -2,7 +2,7 @@ { "type": "target", "target": "newLib", - "operation": "tgt_add", + "operation": "target_add", "sources": ["new1.cpp", "new2.cpp"], "target_type": "shared_library", "subdir": "sub2" diff --git a/test cases/rewrite/2 subdirs/rmTgt.json b/test cases/rewrite/2 subdirs/rmTgt.json index 73a7b1d..9b112f9 100644 --- a/test cases/rewrite/2 subdirs/rmTgt.json +++ b/test cases/rewrite/2 subdirs/rmTgt.json @@ -2,6 +2,6 @@ { "type": "target", "target": "something", - "operation": "tgt_rm" + "operation": "target_rm" } ] diff --git a/test cases/rewrite/3 kwargs/add.json b/test cases/rewrite/3 kwargs/add.json index e398b7b..2148a1e 100644 --- a/test cases/rewrite/3 kwargs/add.json +++ b/test cases/rewrite/3 kwargs/add.json @@ -2,7 +2,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "set", "kwargs": { "license": "GPL" @@ -11,7 +11,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "add", "kwargs": { "license": ["MIT"] @@ -20,7 +20,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "add", "kwargs": { "license": "BSD" diff --git a/test cases/rewrite/3 kwargs/defopts_delete.json b/test cases/rewrite/3 kwargs/defopts_delete.json index 06232dd..4fe39e2 100644 --- a/test cases/rewrite/3 kwargs/defopts_delete.json +++ b/test cases/rewrite/3 kwargs/defopts_delete.json @@ -2,7 +2,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "set", "kwargs": { "default_options": ["cpp_std=c++14", "buildtype=release", "debug=true"] diff --git a/test cases/rewrite/3 kwargs/info.json b/test cases/rewrite/3 kwargs/info.json index 5fd1a64..0eed404 100644 --- a/test cases/rewrite/3 kwargs/info.json +++ b/test cases/rewrite/3 kwargs/info.json @@ -2,7 +2,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "info" }, { diff --git a/test cases/rewrite/3 kwargs/remove.json b/test cases/rewrite/3 kwargs/remove.json index bd7596f..5dc7836 100644 --- a/test cases/rewrite/3 kwargs/remove.json +++ b/test cases/rewrite/3 kwargs/remove.json @@ -2,7 +2,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "set", "kwargs": { "license": ["GPL", "MIT", "BSD"] @@ -11,7 +11,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "remove", "kwargs": { "license": ["MIT"] @@ -20,7 +20,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "remove", "kwargs": { "license": "BSD" diff --git a/test cases/rewrite/3 kwargs/remove_regex.json b/test cases/rewrite/3 kwargs/remove_regex.json index fcb04eb..1043101 100644 --- a/test cases/rewrite/3 kwargs/remove_regex.json +++ b/test cases/rewrite/3 kwargs/remove_regex.json @@ -2,7 +2,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "set", "kwargs": { "default_options": ["cpp_std=c++14", "buildtype=release", "debug=true"] @@ -11,7 +11,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "remove_regex", "kwargs": { "default_options": ["cpp_std=.*"] diff --git a/test cases/rewrite/3 kwargs/set.json b/test cases/rewrite/3 kwargs/set.json index 7d60c4f..a56c599 100644 --- a/test cases/rewrite/3 kwargs/set.json +++ b/test cases/rewrite/3 kwargs/set.json @@ -2,7 +2,7 @@ { "type": "kwargs", "function": "project", - "id": "", + "id": "/", "operation": "set", "kwargs": { "version": "0.0.2", diff --git a/test cases/rewrite/4 same name targets/addSrc.json b/test cases/rewrite/4 same name targets/addSrc.json new file mode 100644 index 0000000..98d0d1e --- /dev/null +++ b/test cases/rewrite/4 same name targets/addSrc.json @@ -0,0 +1,8 @@ +[ + { + "type": "target", + "target": "myExe", + "operation": "src_add", + "sources": ["a1.cpp", "a2.cpp"] + } +] diff --git a/test cases/rewrite/4 same name targets/info.json b/test cases/rewrite/4 same name targets/info.json new file mode 100644 index 0000000..a9fc2dd --- /dev/null +++ b/test cases/rewrite/4 same name targets/info.json @@ -0,0 +1,12 @@ +[ + { + "type": "target", + "target": "exe1", + "operation": "info" + }, + { + "type": "target", + "target": "exe2", + "operation": "info" + } +] diff --git a/test cases/rewrite/4 same name targets/meson.build b/test cases/rewrite/4 same name targets/meson.build new file mode 100644 index 0000000..384fa2b --- /dev/null +++ b/test cases/rewrite/4 same name targets/meson.build @@ -0,0 +1,6 @@ +project('rewrite same name targets', 'cpp') + +src1 = ['main.cpp'] + +exe1 = executable('myExe', src1) +subdir('sub1') diff --git a/test cases/rewrite/4 same name targets/sub1/meson.build b/test cases/rewrite/4 same name targets/sub1/meson.build new file mode 100644 index 0000000..ac53667 --- /dev/null +++ b/test cases/rewrite/4 same name targets/sub1/meson.build @@ -0,0 +1,3 @@ +src2 = ['main.cpp'] + +exe2 = executable('myExe', src2) |