summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mesonbuild/ast/interpreter.py20
-rw-r--r--mesonbuild/ast/introspection.py50
-rw-r--r--mesonbuild/mintro.py44
-rw-r--r--mesonbuild/mparser.py7
-rw-r--r--mesonbuild/rewriter.py366
-rw-r--r--test cases/rewrite/1 basic/addSrc.json38
-rw-r--r--test cases/rewrite/1 basic/addTgt.json2
-rw-r--r--test cases/rewrite/1 basic/expected_dag.txt160
-rw-r--r--test cases/rewrite/1 basic/info.json20
-rw-r--r--test cases/rewrite/1 basic/meson.build16
-rw-r--r--test cases/rewrite/1 basic/rmSrc.json40
-rw-r--r--test cases/rewrite/1 basic/rmTgt.json5
-rw-r--r--test cases/rewrite/7 tricky dataflow/addSrc.json72
-rw-r--r--test cases/rewrite/7 tricky dataflow/info.json32
-rw-r--r--test cases/rewrite/7 tricky dataflow/meson.build35
-rw-r--r--unittests/allplatformstests.py4
-rw-r--r--unittests/rewritetests.py115
17 files changed, 715 insertions, 311 deletions
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
index e38504d4e..473ad9119 100644
--- a/mesonbuild/ast/interpreter.py
+++ b/mesonbuild/ast/interpreter.py
@@ -11,6 +11,7 @@ import typing as T
from collections import defaultdict
from dataclasses import dataclass
import itertools
+from pathlib import Path
from .. import mparser, mesonlib
from .. import environment
@@ -28,6 +29,8 @@ from ..interpreterbase import (
InterpreterObject,
)
+from ..interpreterbase.helpers import flatten
+
from ..interpreter import (
StringHolder,
BooleanHolder,
@@ -54,7 +57,6 @@ from ..mparser import (
)
if T.TYPE_CHECKING:
- from pathlib import Path
from .visitor import AstVisitor
from ..interpreter import Interpreter
from ..interpreterbase import SubProject, TYPE_var, TYPE_nvar
@@ -110,7 +112,7 @@ class IntrospectionBuildTarget(MesonInterpreterObject):
installed: bool
outputs: T.List[str]
source_nodes: T.List[BaseNode]
- extra_files: T.List[BaseNode]
+ extra_files: BaseNode
kwargs: T.Dict[str, TYPE_var]
node: FunctionNode
@@ -695,6 +697,20 @@ class AstInterpreter(InterpreterBase):
assert isinstance(var_name, str)
self.cur_assignments[var_name].append((self.nesting.copy(), node))
+ def nodes_to_pretty_filelist(self, root_path: Path, subdir: str, nodes: T.List[BaseNode]) -> T.List[T.Union[str, UnknownValue]]:
+ def src_to_abs(src: T.Union[str, IntrospectionFile, UnknownValue]) -> T.Union[str, UnknownValue]:
+ if isinstance(src, str):
+ return os.path.normpath(os.path.join(root_path, subdir, src))
+ elif isinstance(src, IntrospectionFile):
+ return str(src.to_abs_path(root_path))
+ elif isinstance(src, UnknownValue):
+ return src
+ else:
+ raise TypeError
+
+ rtvals: T.List[T.Any] = flatten([self.node_to_runtime_value(sn) for sn in nodes])
+ return [src_to_abs(x) for x in rtvals]
+
def flatten_args(self, args_raw: T.Union[TYPE_nvar, T.Sequence[TYPE_nvar]], include_unknown_args: bool = False) -> T.List[TYPE_var]:
# Make sure we are always dealing with lists
if isinstance(args_raw, list):
diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py
index 26caa727e..147436d4f 100644
--- a/mesonbuild/ast/introspection.py
+++ b/mesonbuild/ast/introspection.py
@@ -15,7 +15,7 @@ from ..compilers import detect_compiler_for
from ..interpreterbase import InvalidArguments, SubProject, UnknownValue
from ..mesonlib import MachineChoice
from ..options import OptionKey
-from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode
+from ..mparser import BaseNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode
from .interpreter import AstInterpreter, IntrospectionBuildTarget, IntrospectionDependency
if T.TYPE_CHECKING:
@@ -237,40 +237,20 @@ class IntrospectionInterpreter(AstInterpreter):
kwargs = self.flatten_kwargs(kwargs_raw, True)
- def traverse_nodes(inqueue: T.List[BaseNode]) -> T.List[BaseNode]:
- res: T.List[BaseNode] = []
- while inqueue:
- curr = inqueue.pop(0)
- arg_node = None
- assert isinstance(curr, BaseNode)
- if isinstance(curr, FunctionNode):
- arg_node = curr.args
- elif isinstance(curr, ArrayNode):
- arg_node = curr.args
- elif isinstance(curr, IdNode):
- # Try to resolve the ID and append the node to the queue
- assert isinstance(curr.value, str)
- var_name = curr.value
- if var_name in self.cur_assignments:
- tmp_node = self.get_cur_value(var_name)
- if isinstance(tmp_node, (ArrayNode, IdNode, FunctionNode)):
- inqueue += [tmp_node]
- elif isinstance(curr, ArithmeticNode):
- inqueue += [curr.left, curr.right]
- if arg_node is None:
- continue
- arg_nodes = arg_node.arguments.copy()
- # Pop the first element if the function is a build target function
- if isinstance(curr, FunctionNode) and curr.func_name.value in BUILD_TARGET_FUNCTIONS:
- arg_nodes.pop(0)
- elementary_nodes = [x for x in arg_nodes if isinstance(x, (str, StringNode))]
- inqueue += [x for x in arg_nodes if isinstance(x, (FunctionNode, ArrayNode, IdNode, ArithmeticNode))]
- if elementary_nodes:
- res += [curr]
- return res
-
- source_nodes = traverse_nodes(srcqueue)
- extraf_nodes = traverse_nodes(extra_queue)
+ oldlen = len(node.args.arguments)
+ source_nodes = node.args.arguments[1:]
+ for k, v in node.args.kwargs.items():
+ assert isinstance(k, IdNode)
+ if k.value == 'sources':
+ source_nodes.append(v)
+ assert oldlen == len(node.args.arguments)
+
+ extraf_nodes = None
+ for k, v in node.args.kwargs.items():
+ assert isinstance(k, IdNode)
+ if k.value == 'extra_files':
+ assert extraf_nodes is None
+ extraf_nodes = v
# Make sure nothing can crash when creating the build class
kwargs_reduced = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs and k in {'install', 'build_by_default', 'build_always', 'name_prefix'}}
diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py
index 09d373652..db62dc06c 100644
--- a/mesonbuild/mintro.py
+++ b/mesonbuild/mintro.py
@@ -19,20 +19,17 @@ from pathlib import Path, PurePath
import sys
import typing as T
-from . import build, mesonlib, options, coredata as cdata
-from .ast import IntrospectionInterpreter, BUILD_TARGET_FUNCTIONS, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstJSONPrinter
+from . import build, environment, mesonlib, options, coredata as cdata
+from .ast import IntrospectionInterpreter, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstJSONPrinter
from .backend import backends
from .dependencies import Dependency
-from . import environment
from .interpreterbase import ObjectHolder, UnknownValue
from .options import OptionKey
-from .mparser import FunctionNode, ArrayNode, ArgumentNode, StringNode
if T.TYPE_CHECKING:
import argparse
from .interpreter import Interpreter
- from .mparser import BaseNode
def get_meson_info_file(info_dir: str) -> str:
return os.path.join(info_dir, 'meson-info.json')
@@ -169,35 +166,14 @@ def get_target_dir(coredata: cdata.CoreData, subdir: str) -> str:
else:
return subdir
-def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]:
- tlist: T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]] = []
- root_dir = Path(intr.source_root)
-
- def nodes_to_paths(node_list: T.List[BaseNode]) -> T.List[Path]:
- res: T.List[Path] = []
- for n in node_list:
- args: T.List[BaseNode] = []
- if isinstance(n, FunctionNode):
- args = list(n.args.arguments)
- if n.func_name.value in BUILD_TARGET_FUNCTIONS:
- args.pop(0)
- elif isinstance(n, ArrayNode):
- args = n.args.arguments
- elif isinstance(n, ArgumentNode):
- args = n.arguments
- for j in args:
- if isinstance(j, StringNode):
- assert isinstance(j.value, str)
- res += [Path(j.value)]
- elif isinstance(j, str):
- res += [Path(j)]
- res = [root_dir / i.subdir / x for x in res]
- res = [x.resolve() for x in res]
- return res
+def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, object]]:
+ tlist = []
+ root_dir = Path(intr.source_root).resolve()
for i in intr.targets:
- sources = nodes_to_paths(i.source_nodes)
- extra_f = nodes_to_paths(i.extra_files)
+ sources = intr.nodes_to_pretty_filelist(root_dir, i.subdir, i.source_nodes)
+ extra_files = intr.nodes_to_pretty_filelist(root_dir, i.subdir, [i.extra_files] if i.extra_files else [])
+
outdir = get_target_dir(intr.coredata, i.subdir)
tlist += [{
@@ -212,11 +188,11 @@ def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[st
'machine': i.machine,
'compiler': [],
'parameters': [],
- 'sources': [str(x) for x in sources],
+ 'sources': sources,
'generated_sources': []
}],
'depends': [],
- 'extra_files': [str(x) for x in extra_f],
+ 'extra_files': extra_files,
'subproject': None, # Subprojects are not supported
'installed': i.installed
}]
diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py
index a0a8d273e..3dd8f0a5e 100644
--- a/mesonbuild/mparser.py
+++ b/mesonbuild/mparser.py
@@ -369,6 +369,13 @@ class ArgumentNode(BaseNode):
mlog.warning('This will be an error in Meson 2.0.')
self.kwargs[name] = value
+ def get_kwarg_or_default(self, name: str, default: BaseNode) -> BaseNode:
+ for k, v in self.kwargs.items():
+ assert isinstance(k, IdNode)
+ if k.value == name:
+ return v
+ return default
+
def set_kwarg_no_check(self, name: BaseNode, value: BaseNode) -> None:
self.kwargs[name] = value
diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py
index 981dfd326..9023033b9 100644
--- a/mesonbuild/rewriter.py
+++ b/mesonbuild/rewriter.py
@@ -11,13 +11,15 @@ from __future__ import annotations
from .ast import IntrospectionInterpreter, BUILD_TARGET_FUNCTIONS, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstPrinter
from .ast.interpreter import IntrospectionBuildTarget, IntrospectionDependency, _symbol
-from .interpreterbase import TV_func
-from mesonbuild.mesonlib import MesonException, setup_vsenv
+from .interpreterbase import UnknownValue, TV_func
+from .interpreterbase.helpers import flatten
+from mesonbuild.mesonlib import MesonException, setup_vsenv, relpath
from . import mlog, environment
from functools import wraps
-from .mparser import Token, ArrayNode, ArgumentNode, AssignmentNode, BaseNode, StringNode, BooleanNode, ElementaryNode, IdNode, FunctionNode, PlusAssignmentNode
-import json, os, re, sys
+from .mparser import Token, ArrayNode, ArgumentNode, ArithmeticNode, AssignmentNode, BaseNode, StringNode, BooleanNode, ElementaryNode, IdNode, FunctionNode, PlusAssignmentNode
+import json, os, re, sys, codecs
import typing as T
+from pathlib import Path
if T.TYPE_CHECKING:
import argparse
@@ -633,6 +635,166 @@ class Rewriter:
return ass
return None
+ def affects_no_other_targets(self, candidate: BaseNode) -> bool:
+ affected = self.interpreter.dataflow_dag.reachable({candidate}, False)
+ affected_targets = [x for x in affected if isinstance(x, FunctionNode) and x.func_name.value in BUILD_TARGET_FUNCTIONS]
+ return len(affected_targets) == 1
+
+ def get_relto(self, target_node: BaseNode, node: BaseNode) -> Path:
+ cwd = Path(os.getcwd())
+ all_paths = self.interpreter.dataflow_dag.find_all_paths(node, target_node)
+ # len(all_paths) == 0 would imply that data does not flow from node to
+ # target_node. This would imply that adding sources to node would not
+ # add the source to the target.
+ assert all_paths
+ if len(all_paths) > 1:
+ return None
+ return (cwd / next(x for x in all_paths[0] if isinstance(x, FunctionNode)).filename).parent
+
+ def add_src_or_extra(self, op: str, target: IntrospectionBuildTarget, newfiles: T.List[str], to_sort_nodes: T.List[T.Union[FunctionNode, ArrayNode]]) -> None:
+ assert op in {'src_add', 'extra_files_add'}
+
+ if op == 'src_add':
+ old: T.Set[T.Union[BaseNode, UnknownValue]] = set(target.source_nodes)
+ elif op == 'extra_files_add':
+ if target.extra_files is None:
+ old = set()
+ else:
+ old = {target.extra_files}
+ tgt_function: FunctionNode = target.node
+
+ cwd = Path(os.getcwd())
+ target_dir_abs = cwd / os.path.dirname(target.node.filename)
+ source_root_abs = cwd / self.interpreter.source_root
+
+ candidates1 = self.interpreter.dataflow_dag.reachable(old, True)
+ # A node is a member of the set `candidates1` exactly if data from this node
+ # flow into one of the `dest` nodes. We assume that this implies that if we
+ # add `foo.c` to this node, then 'foo.c' will be added to one of these
+ # nodes. This assumption is not always true:
+ # ar = ['a.c', 'b.c']
+ # srcs = ar[1]
+ # executable('name', srcs)
+ # Data flows from `ar` to `srcs`, but if we add 'foo.c':
+ # ar = ['a.c', 'b.c', 'foo.c']
+ # srcs = ar[1]
+ # executable('name', srcs)
+ # this does not add 'foo.c' to `srcs`. This is a known bug/limitation of
+ # the meson rewriter that could be fixed by replacing `reachable` with a
+ # more advanced analysis. But this is a lot of work and I think e.g.
+ # `srcs = ar[1]` is rare in real-world projects, so I will just leave
+ # this for now.
+
+ candidates2 = {x for x in candidates1 if isinstance(x, (FunctionNode, ArrayNode))}
+
+ # If we have this meson.build file:
+ # shared = ['shared.c']
+ # executable('foo', shared + ['foo.c'])
+ # executable('bar', shared + ['bar.c'])
+ # and we are tasked with adding 'new.c' to 'foo', we should do e.g this:
+ # shared = ['shared.c']
+ # executable('foo', shared + ['foo.c', 'new.c'])
+ # executable('bar', shared + ['bar.c'])
+ # but never this:
+ # shared = ['shared.c', 'new.c']
+ # executable('foo', shared + ['foo.c'])
+ # executable('bar', shared + ['bar.c'])
+ # We do this by removing the `['shared.c']`-node from `candidates2`.
+ candidates2 = {x for x in candidates2 if self.affects_no_other_targets(x)}
+
+ def path_contains_unknowns(candidate: BaseNode) -> bool:
+ all_paths = self.interpreter.dataflow_dag.find_all_paths(candidate, target.node)
+ for path in all_paths:
+ for el in path:
+ if isinstance(el, UnknownValue):
+ return True
+ return False
+
+ candidates2 = {x for x in candidates2 if not path_contains_unknowns(x)}
+
+ candidates2 = {x for x in candidates2 if self.get_relto(target.node, x) is not None}
+
+ chosen: T.Union[FunctionNode, ArrayNode] = None
+ new_kwarg_flag = False
+ if len(candidates2) > 0:
+ # So that files(['a', 'b']) gets modified to files(['a', 'b', 'c']) instead of files(['a', 'b'], 'c')
+ if len({x for x in candidates2 if isinstance(x, ArrayNode)}) > 0:
+ candidates2 = {x for x in candidates2 if isinstance(x, ArrayNode)}
+
+ # We choose one more or less arbitrary candidate
+ chosen = min(candidates2, key=lambda x: (x.lineno, x.colno))
+ elif op == 'src_add':
+ chosen = target.node
+ elif op == 'extra_files_add':
+ chosen = ArrayNode(_symbol('['), ArgumentNode(Token('', tgt_function.filename, 0, 0, 0, None, '[]')), _symbol(']'))
+
+ # this is fundamentally error prone
+ self.interpreter.dataflow_dag.add_edge(chosen, target.node)
+
+ extra_files_idnode = IdNode(Token('string', tgt_function.filename, 0, 0, 0, None, 'extra_files'))
+ if tgt_function not in self.modified_nodes:
+ self.modified_nodes += [tgt_function]
+ new_extra_files_node: BaseNode
+ if target.node.args.get_kwarg_or_default('extra_files', None) is None:
+ # Target has no extra_files kwarg, create one
+ new_kwarg_flag = True
+ new_extra_files_node = chosen
+ else:
+ new_kwarg_flag = True
+ old_extra_files = target.node.args.get_kwarg_or_default('extra_files', None)
+ target.node.args.kwargs = {k: v for k, v in target.node.args.kwargs.items() if not (isinstance(k, IdNode) and k.value == 'extra_files')}
+ new_extra_files_node = ArithmeticNode('add', old_extra_files, _symbol('+'), chosen)
+
+ tgt_function.args.kwargs[extra_files_idnode] = new_extra_files_node
+
+ newfiles_relto = self.get_relto(target.node, chosen)
+ old_src_list: T.List[T.Any] = flatten([self.interpreter.node_to_runtime_value(sn) for sn in old])
+
+ if op == 'src_add':
+ name = 'Source'
+ elif op == 'extra_files_add':
+ name = 'Extra file'
+ # Generate the new String nodes
+ to_append = []
+ added = []
+
+ old_src_list = [(target_dir_abs / x).resolve() if isinstance(x, str) else x.to_abs_path(source_root_abs) for x in old_src_list if not isinstance(x, UnknownValue)]
+ for _newf in sorted(set(newfiles)):
+ newf = Path(_newf)
+ if os.path.isabs(newf):
+ newf = Path(newf)
+ else:
+ newf = source_root_abs / newf
+ if newf in old_src_list:
+ mlog.log(' -- ', name, mlog.green(str(newf)), 'is already defined for the target --> skipping')
+ continue
+
+ mlog.log(' -- Adding ', name.lower(), mlog.green(str(newf)), 'at',
+ mlog.yellow(f'{chosen.filename}:{chosen.lineno}'))
+ added.append(newf)
+ mocktarget = self.interpreter.funcvals[target.node]
+ assert isinstance(mocktarget, IntrospectionBuildTarget)
+ # print("adding ", str(newf), 'to', mocktarget.name) todo: should we write something to stderr?
+
+ path = relpath(newf, newfiles_relto)
+ path = codecs.encode(path, 'unicode_escape').decode() # Because the StringNode constructor does the inverse
+ token = Token('string', chosen.filename, 0, 0, 0, None, path)
+ to_append += [StringNode(token)]
+
+ assert isinstance(chosen, (FunctionNode, ArrayNode))
+ arg_node = chosen.args
+ # Append to the AST at the right place
+ arg_node.arguments += to_append
+
+ # Mark the node as modified
+ if chosen not in to_sort_nodes:
+ to_sort_nodes += [chosen]
+ # If the extra_files array is newly created, i.e. if new_kwarg_flag is
+ # True, don't mark it as its parent function node already is, otherwise
+ # this would cause double modification.
+ if chosen not in self.modified_nodes and not new_kwarg_flag:
+ self.modified_nodes += [chosen]
+
# Utility function to get a list of the sources from a node
def arg_list_from_node(self, n: BaseNode) -> T.List[BaseNode]:
args = []
@@ -646,18 +808,26 @@ class Rewriter:
args = n.arguments
return args
- def rm_src_or_extra(self, op: str, target: IntrospectionBuildTarget, to_be_removed: T.List[str], to_sort_nodes: T.List[ArgumentNode]) -> None:
+ def rm_src_or_extra(self, op: str, target: IntrospectionBuildTarget, to_be_removed: T.List[str], to_sort_nodes: T.List[T.Union[FunctionNode, ArrayNode]]) -> None:
+ assert op in {'src_rm', 'extra_files_rm'}
+ cwd = Path(os.getcwd())
+ source_root_abs = cwd / self.interpreter.source_root
+
# Helper to find the exact string node and its parent
- def find_node(src: str) -> T.Tuple[BaseNode, StringNode]:
+ def find_node(src: str) -> T.Tuple[T.Optional[BaseNode], T.Optional[StringNode]]:
if op == 'src_rm':
- nodes = target.source_nodes
+ nodes = self.interpreter.dataflow_dag.reachable(set(target.source_nodes), True).union({target.node})
elif op == 'extra_files_rm':
- nodes = target.extra_files
+ nodes = self.interpreter.dataflow_dag.reachable({target.extra_files}, True)
for i in nodes:
- for j in self.arg_list_from_node(i):
- if isinstance(j, StringNode):
- if j.value == src:
- return i, j
+ if isinstance(i, UnknownValue):
+ continue
+ relto = self.get_relto(target.node, i)
+ if relto is not None:
+ for j in self.arg_list_from_node(i):
+ if isinstance(j, StringNode):
+ if os.path.normpath(relto / j.value) == os.path.normpath(source_root_abs / src):
+ return i, j
return None, None
if op == 'src_rm':
@@ -671,22 +841,22 @@ class Rewriter:
if root is None:
mlog.warning(' -- Unable to find', name, mlog.green(i), 'in the target')
continue
+ if not self.affects_no_other_targets(string_node):
+ mlog.warning(' -- Removing the', name, mlog.green(i), 'is too compilicated')
+ continue
- arg_node = None
- if isinstance(root, (FunctionNode, ArrayNode)):
- arg_node = root.args
- elif isinstance(root, ArgumentNode):
- arg_node = root
- assert arg_node is not None
+ if not isinstance(root, (FunctionNode, ArrayNode)):
+ raise NotImplementedError # I'm lazy
# Remove the found string node from the argument list
+ arg_node = root.args
mlog.log(' -- Removing', name, mlog.green(i), 'from',
mlog.yellow(f'{string_node.filename}:{string_node.lineno}'))
arg_node.arguments.remove(string_node)
# Mark the node as modified
- if arg_node not in to_sort_nodes and not isinstance(root, FunctionNode):
- to_sort_nodes += [arg_node]
+ if root not in to_sort_nodes:
+ to_sort_nodes += [root]
if root not in self.modified_nodes:
self.modified_nodes += [root]
@@ -711,106 +881,10 @@ class Rewriter:
if target is not None:
cmd['sources'] = [rel_source(x) for x in cmd['sources']]
- to_sort_nodes = []
-
- if cmd['operation'] == 'src_add':
- node = None
- if target.source_nodes:
- node = target.source_nodes[0]
- else:
- node = target.node
- assert node is not None
-
- # Generate the current source list
- src_list = []
- for src_node in target.source_nodes:
- for j in self.arg_list_from_node(src_node):
- if isinstance(j, StringNode):
- src_list += [j.value]
-
- # Generate the new String nodes
- to_append = []
- for i in sorted(set(cmd['sources'])):
- if i in src_list:
- mlog.log(' -- Source', mlog.green(i), 'is already defined for the target --> skipping')
- continue
- mlog.log(' -- Adding source', mlog.green(i), 'at',
- mlog.yellow(f'{node.filename}:{node.lineno}'))
- token = Token('string', node.filename, 0, 0, 0, None, i)
- to_append += [StringNode(token)]
-
- # Append to the AST at the right place
- arg_node = None
- if isinstance(node, (FunctionNode, ArrayNode)):
- arg_node = node.args
- elif isinstance(node, ArgumentNode):
- arg_node = node
- assert arg_node is not None
- arg_node.arguments += to_append
-
- # Mark the node as modified
- if arg_node not in to_sort_nodes and not isinstance(node, FunctionNode):
- to_sort_nodes += [arg_node]
- if node not in self.modified_nodes:
- self.modified_nodes += [node]
-
- elif cmd['operation'] == 'extra_files_add':
- tgt_function: FunctionNode = target.node
- mark_array = True
- try:
- node = target.extra_files[0]
- except IndexError:
- # Specifying `extra_files` with a list that flattens to empty gives an empty
- # target.extra_files list, account for that.
- try:
- extra_files_key = next(k for k in tgt_function.args.kwargs.keys() if isinstance(k, IdNode) and k.value == 'extra_files')
- node = tgt_function.args.kwargs[extra_files_key]
- except StopIteration:
- # Target has no extra_files kwarg, create one
- node = ArrayNode(_symbol('['), ArgumentNode(Token('', tgt_function.filename, 0, 0, 0, None, '[]')), _symbol(']'))
- tgt_function.args.kwargs[IdNode(Token('string', tgt_function.filename, 0, 0, 0, None, 'extra_files'))] = node
- mark_array = False
- if tgt_function not in self.modified_nodes:
- self.modified_nodes += [tgt_function]
- target.extra_files = [node]
- if isinstance(node, IdNode):
- cv = self.interpreter.get_cur_value(node.value)
- assert isinstance(cv, BaseNode)
- node = cv
- target.extra_files = [node]
- if not isinstance(node, ArrayNode):
- mlog.error('Target', mlog.bold(cmd['target']), 'extra_files argument must be a list', *self.on_error())
- return self.handle_error()
-
- # Generate the current extra files list
- extra_files_list = []
- for i in target.extra_files:
- for j in self.arg_list_from_node(i):
- if isinstance(j, StringNode):
- extra_files_list += [j.value]
-
- # Generate the new String nodes
- to_append = []
- for i in sorted(set(cmd['sources'])):
- if i in extra_files_list:
- mlog.log(' -- Extra file', mlog.green(i), 'is already defined for the target --> skipping')
- continue
- mlog.log(' -- Adding extra file', mlog.green(i), 'at',
- mlog.yellow(f'{node.filename}:{node.lineno}'))
- token = Token('string', node.filename, 0, 0, 0, None, i)
- to_append += [StringNode(token)]
+ to_sort_nodes: T.List[T.Union[FunctionNode, ArrayNode]] = []
- # Append to the AST at the right place
- arg_node = node.args
- arg_node.arguments += to_append
-
- # Mark the node as modified
- if arg_node not in to_sort_nodes:
- to_sort_nodes += [arg_node]
- # If the extra_files array is newly created, don't mark it as its parent function node already is,
- # otherwise this would cause double modification.
- if mark_array and node not in self.modified_nodes:
- self.modified_nodes += [node]
+ if cmd['operation'] in {'src_add', 'extra_files_add'}:
+ self.add_src_or_extra(cmd['operation'], target, cmd['sources'], to_sort_nodes)
elif cmd['operation'] in {'src_rm', 'extra_files_rm'}:
self.rm_src_or_extra(cmd['operation'], target, cmd['sources'], to_sort_nodes)
@@ -823,7 +897,7 @@ class Rewriter:
id_base = re.sub(r'[- ]', '_', cmd['target'])
target_id = id_base + '_exe' if cmd['target_type'] == 'executable' else '_lib'
source_id = id_base + '_sources'
- filename = os.path.join(cmd['subdir'], environment.build_filename)
+ filename = os.path.join(os.getcwd(), self.interpreter.source_root, cmd['subdir'], environment.build_filename)
# Build src list
src_arg_node = ArgumentNode(Token('string', filename, 0, 0, 0, None, ''))
@@ -857,16 +931,16 @@ class Rewriter:
elif cmd['operation'] == 'info':
# T.List all sources in the target
- src_list = []
- for i in target.source_nodes:
- for j in self.arg_list_from_node(i):
- if isinstance(j, StringNode):
- src_list += [j.value]
- extra_files_list = []
- for i in target.extra_files:
- for j in self.arg_list_from_node(i):
- if isinstance(j, StringNode):
- extra_files_list += [j.value]
+
+ cwd = Path(os.getcwd())
+ source_root_abs = cwd / self.interpreter.source_root
+
+ src_list = self.interpreter.nodes_to_pretty_filelist(source_root_abs, target.subdir, target.source_nodes)
+ extra_files_list = self.interpreter.nodes_to_pretty_filelist(source_root_abs, target.subdir, [target.extra_files] if target.extra_files else [])
+
+ src_list = ['unknown' if isinstance(x, UnknownValue) else relpath(x, source_root_abs) for x in src_list]
+ extra_files_list = ['unknown' if isinstance(x, UnknownValue) else relpath(x, source_root_abs) for x in extra_files_list]
+
test_data = {
'name': target.name,
'sources': src_list,
@@ -885,10 +959,16 @@ class Rewriter:
def path_sorter(key: str) -> T.List[T.Tuple[bool, T.List[T.Union[int, str]]]]:
return [(key.count('/') <= idx, alphanum_key(x)) for idx, x in enumerate(key.split('/'))]
- unknown = [x for x in i.arguments if not isinstance(x, StringNode)]
- sources = [x for x in i.arguments if isinstance(x, StringNode)]
+ if isinstance(i, FunctionNode) and i.func_name.value in BUILD_TARGET_FUNCTIONS:
+ src_args = i.args.arguments[1:]
+ target_name = [i.args.arguments[0]]
+ else:
+ src_args = i.args.arguments
+ target_name = []
+ unknown: T.List[BaseNode] = [x for x in src_args if not isinstance(x, StringNode)]
+ sources: T.List[StringNode] = [x for x in src_args if isinstance(x, StringNode)]
sources = sorted(sources, key=lambda x: path_sorter(x.value))
- i.arguments = unknown + T.cast(T.List[BaseNode], sources)
+ i.args.arguments = target_name + unknown + T.cast(T.List[BaseNode], sources)
def process(self, cmd: T.Dict[str, T.Any]) -> None:
if 'type' not in cmd:
@@ -931,7 +1011,7 @@ class Rewriter:
for i in str_list:
if i['file'] in files:
continue
- fpath = os.path.realpath(os.path.join(self.sourcedir, T.cast(str, i['file'])))
+ fpath = os.path.realpath(T.cast(str, i['file']))
fdata = ''
# Create an empty file if it does not exist
if not os.path.exists(fpath):
@@ -1077,12 +1157,22 @@ def run(options: argparse.Namespace) -> int:
if not isinstance(commands, list):
raise TypeError('Command is not a list')
- for i in commands:
- if not isinstance(i, object):
+ for i, cmd in enumerate(commands):
+ if not isinstance(cmd, object):
raise TypeError('Command is not an object')
- rewriter.process(i)
+ rewriter.process(cmd)
+ rewriter.apply_changes()
+
+ if i == len(commands) - 1: # Improves the performance, is not necessary for correctness.
+ break
+
+ rewriter.modified_nodes = []
+ rewriter.to_remove_nodes = []
+ rewriter.to_add_nodes = []
+ # The AST changed, so we need to update every information that was derived from the AST
+ rewriter.interpreter = IntrospectionInterpreter(rewriter.sourcedir, '', rewriter.interpreter.backend, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()])
+ rewriter.analyze_meson()
- rewriter.apply_changes()
rewriter.print_info()
return 0
except Exception as e:
diff --git a/test cases/rewrite/1 basic/addSrc.json b/test cases/rewrite/1 basic/addSrc.json
index b8bc43916..52603f60b 100644
--- a/test cases/rewrite/1 basic/addSrc.json
+++ b/test cases/rewrite/1 basic/addSrc.json
@@ -43,6 +43,24 @@
},
{
"type": "target",
+ "target": "trivialprog10",
+ "operation": "src_add",
+ "sources": ["fileA.cpp", "fileB.cpp", "a1.cpp"]
+ },
+ {
+ "type": "target",
+ "target": "trivialprog11",
+ "operation": "src_add",
+ "sources": ["fileA.cpp", "a1.cpp"]
+ },
+ {
+ "type": "target",
+ "target": "trivialprog12",
+ "operation": "src_add",
+ "sources": ["fileA.cpp", "fileB.cpp", "a1.cpp"]
+ },
+ {
+ "type": "target",
"target": "trivialprog0",
"operation": "info"
},
@@ -90,5 +108,25 @@
"type": "target",
"target": "trivialprog9",
"operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "trivialprog10",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "trivialprog11",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "trivialprog12",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "rightName",
+ "operation": "info"
}
]
diff --git a/test cases/rewrite/1 basic/addTgt.json b/test cases/rewrite/1 basic/addTgt.json
index 2f4e7e256..02d600a82 100644
--- a/test cases/rewrite/1 basic/addTgt.json
+++ b/test cases/rewrite/1 basic/addTgt.json
@@ -1,7 +1,7 @@
[
{
"type": "target",
- "target": "trivialprog10",
+ "target": "trivialprog13",
"operation": "target_add",
"sources": ["new1.cpp", "new2.cpp"],
"target_type": "shared_library"
diff --git a/test cases/rewrite/1 basic/expected_dag.txt b/test cases/rewrite/1 basic/expected_dag.txt
index abc04fc31..c5025b4ad 100644
--- a/test cases/rewrite/1 basic/expected_dag.txt
+++ b/test cases/rewrite/1 basic/expected_dag.txt
@@ -15,69 +15,115 @@ Data flowing to ArrayNode(6:7):
IdNode(6:8)
Data flowing to IdNode(6:8):
IdNode(5:7)
+Data flowing to ArithmeticNode(7:7):
+ ArrayNode(7:7)
+ ArrayNode(8:8)
+Data flowing to ArrayNode(7:7):
+ StringNode(7:8)
+ StringNode(7:20)
+Data flowing to ArrayNode(8:8):
+ StringNode(8:9)
+Data flowing to ArrayNode(9:7):
+ StringNode(9:8)
+ StringNode(9:20)
Data flowing to FunctionNode(10:7):
- StringNode(10:18)
- ArithmeticNode(10:34)
-Data flowing to ArithmeticNode(10:34):
- IdNode(10:34)
- IdNode(10:41)
-Data flowing to IdNode(10:34):
+ IdNode(10:13)
+Data flowing to IdNode(10:13):
+ ArrayNode(9:7)
+Data flowing to ArrayNode(11:7):
+ StringNode(11:8)
+ StringNode(11:20)
+Data flowing to IdNode(12:7):
+ ArrayNode(11:7)
+Data flowing to ArrayNode(13:7):
+ StringNode(13:8)
+ StringNode(13:21)
+Data flowing to FunctionNode(15:13):
+ StringNode(14:7)
+ StringNode(15:26)
+Data flowing to FunctionNode(20:7):
+ StringNode(20:18)
+ ArithmeticNode(20:34)
+Data flowing to ArithmeticNode(20:34):
+ IdNode(20:34)
+ IdNode(20:41)
+Data flowing to IdNode(20:34):
ArrayNode(3:7)
-Data flowing to IdNode(10:41):
+Data flowing to IdNode(20:41):
FunctionNode(4:7)
-Data flowing to FunctionNode(11:7):
- StringNode(11:18)
- IdNode(11:34)
-Data flowing to IdNode(11:34):
+Data flowing to FunctionNode(21:7):
+ StringNode(21:18)
+ IdNode(21:34)
+Data flowing to IdNode(21:34):
ArrayNode(3:7)
-Data flowing to FunctionNode(12:7):
- StringNode(12:18)
- ArrayNode(12:34)
-Data flowing to ArrayNode(12:34):
- IdNode(12:35)
-Data flowing to IdNode(12:35):
+Data flowing to FunctionNode(22:7):
+ StringNode(22:18)
+ ArrayNode(22:34)
+Data flowing to ArrayNode(22:34):
+ IdNode(22:35)
+Data flowing to IdNode(22:35):
FunctionNode(4:7)
-Data flowing to FunctionNode(13:7):
- StringNode(13:18)
- ArrayNode(13:34)
-Data flowing to ArrayNode(13:34):
- StringNode(13:35)
- StringNode(13:47)
-Data flowing to FunctionNode(14:7):
- StringNode(14:18)
- ArrayNode(14:34)
-Data flowing to ArrayNode(14:34):
- StringNode(14:35)
- ArrayNode(14:47)
-Data flowing to ArrayNode(14:47):
- StringNode(14:48)
-Data flowing to FunctionNode(15:7):
- StringNode(15:18)
- ArrayNode(15:34)
-Data flowing to ArrayNode(15:34):
- IdNode(15:35)
- StringNode(15:41)
-Data flowing to IdNode(15:35):
+Data flowing to FunctionNode(23:7):
+ StringNode(23:18)
+ ArrayNode(23:34)
+Data flowing to ArrayNode(23:34):
+ StringNode(23:35)
+ StringNode(23:47)
+Data flowing to FunctionNode(24:7):
+ StringNode(24:18)
+ ArrayNode(24:34)
+Data flowing to ArrayNode(24:34):
+ StringNode(24:35)
+ ArrayNode(24:47)
+Data flowing to ArrayNode(24:47):
+ StringNode(24:48)
+Data flowing to FunctionNode(25:7):
+ StringNode(25:18)
+ ArrayNode(25:34)
+Data flowing to ArrayNode(25:34):
+ IdNode(25:35)
+ StringNode(25:41)
+Data flowing to IdNode(25:35):
FunctionNode(4:7)
-Data flowing to FunctionNode(16:7):
- StringNode(16:18)
- StringNode(16:34)
- StringNode(16:46)
-Data flowing to FunctionNode(17:7):
- StringNode(17:18)
- StringNode(17:34)
- IdNode(17:47)
- StringNode(17:53)
-Data flowing to IdNode(17:47):
+Data flowing to FunctionNode(26:7):
+ StringNode(26:18)
+ StringNode(26:34)
+ StringNode(26:46)
+Data flowing to FunctionNode(27:7):
+ StringNode(27:18)
+ StringNode(27:34)
+ FunctionNode(27:47)
+ StringNode(27:69)
+Data flowing to FunctionNode(27:47):
ArrayNode(3:7)
-Data flowing to FunctionNode(18:7):
- StringNode(18:18)
- IdNode(18:34)
-Data flowing to IdNode(18:34):
+ StringNode(27:60)
+Data flowing to FunctionNode(28:7):
+ StringNode(28:18)
+ IdNode(28:34)
+Data flowing to IdNode(28:34):
IdNode(5:7)
-Data flowing to FunctionNode(19:0):
- StringNode(19:11)
- IdNode(19:27)
-Data flowing to IdNode(19:27):
+Data flowing to FunctionNode(29:0):
+ StringNode(29:11)
+ IdNode(29:27)
+Data flowing to IdNode(29:27):
ArrayNode(6:7)
-
+Data flowing to FunctionNode(30:0):
+ StringNode(30:11)
+ IdNode(30:28)
+Data flowing to IdNode(30:28):
+ ArithmeticNode(7:7)
+Data flowing to FunctionNode(31:0):
+ StringNode(31:11)
+ IdNode(31:28)
+Data flowing to IdNode(31:28):
+ FunctionNode(10:7)
+Data flowing to FunctionNode(32:0):
+ StringNode(32:11)
+ IdNode(32:28)
+Data flowing to IdNode(32:28):
+ IdNode(12:7)
+Data flowing to FunctionNode(33:0):
+ IdNode(33:11)
+ StringNode(33:23)
+Data flowing to IdNode(33:11):
+ FunctionNode(15:13)
diff --git a/test cases/rewrite/1 basic/info.json b/test cases/rewrite/1 basic/info.json
index 0f1a3bd8c..9977f5a87 100644
--- a/test cases/rewrite/1 basic/info.json
+++ b/test cases/rewrite/1 basic/info.json
@@ -53,5 +53,25 @@
"type": "target",
"target": "trivialprog10",
"operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "trivialprog11",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "trivialprog12",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "trivialprog13",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "rightName",
+ "operation": "info"
}
]
diff --git a/test cases/rewrite/1 basic/meson.build b/test cases/rewrite/1 basic/meson.build
index 0f87c4520..5fe952769 100644
--- a/test cases/rewrite/1 basic/meson.build
+++ b/test cases/rewrite/1 basic/meson.build
@@ -4,6 +4,16 @@ src1 = ['main.cpp', 'fileA.cpp']
src2 = files(['fileB.cpp', 'fileC.cpp'])
src3 = src1
src4 = [src3]
+src5 = ['main.cpp', 'fileA.cpp']
+src5 += ['fileB.cpp']
+src6 = ['main.cpp', 'fileA.cpp']
+src6 = files(src6)
+src7 = ['main.cpp', 'fileA.cpp']
+src8 = src7
+src7 = ['fileB.cpp', 'fileC.cpp']
+name = 'rightName'
+trickyName = get_variable('name')
+name = 'wrongName'
# Magic comment
@@ -14,6 +24,10 @@ exe3 = executable('trivialprog3', ['main.cpp', 'fileA.cpp'])
exe4 = executable('trivialprog4', ['main.cpp', ['fileA.cpp']])
exe5 = executable('trivialprog5', [src2, 'main.cpp'])
exe6 = executable('trivialprog6', 'main.cpp', 'fileA.cpp')
-exe7 = executable('trivialprog7', 'fileB.cpp', src1, 'fileC.cpp')
+exe7 = executable('trivialprog7', 'fileB.cpp', get_variable('src1'), 'fileC.cpp')
exe8 = executable('trivialprog8', src3)
executable('trivialprog9', src4)
+executable('trivialprog10', src5)
+executable('trivialprog11', src6)
+executable('trivialprog12', src8)
+executable(trickyName, 'main.cpp')
diff --git a/test cases/rewrite/1 basic/rmSrc.json b/test cases/rewrite/1 basic/rmSrc.json
index 2e7447c68..de56bbe1b 100644
--- a/test cases/rewrite/1 basic/rmSrc.json
+++ b/test cases/rewrite/1 basic/rmSrc.json
@@ -1,12 +1,6 @@
[
{
"type": "target",
- "target": "trivialprog1",
- "operation": "src_rm",
- "sources": ["fileA.cpp"]
- },
- {
- "type": "target",
"target": "trivialprog3",
"operation": "src_rm",
"sources": ["fileA.cpp"]
@@ -21,7 +15,7 @@
"type": "target",
"target": "trivialprog5",
"operation": "src_rm",
- "sources": ["fileB.cpp"]
+ "sources": ["main.cpp"]
},
{
"type": "target",
@@ -37,6 +31,18 @@
},
{
"type": "target",
+ "target": "trivialprog10",
+ "operation": "src_rm",
+ "sources": ["fileA.cpp", "fileB.cpp"]
+ },
+ {
+ "type": "target",
+ "target": "trivialprog11",
+ "operation": "src_rm",
+ "sources": ["fileA.cpp"]
+ },
+ {
+ "type": "target",
"target": "trivialprog0",
"operation": "info"
},
@@ -84,5 +90,25 @@
"type": "target",
"target": "trivialprog9",
"operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "trivialprog10",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "trivialprog11",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "trivialprog12",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "rightName",
+ "operation": "info"
}
]
diff --git a/test cases/rewrite/1 basic/rmTgt.json b/test cases/rewrite/1 basic/rmTgt.json
index dbaf02535..bc6dc302e 100644
--- a/test cases/rewrite/1 basic/rmTgt.json
+++ b/test cases/rewrite/1 basic/rmTgt.json
@@ -13,5 +13,10 @@
"type": "target",
"target": "trivialprog9",
"operation": "target_rm"
+ },
+ {
+ "type": "target",
+ "target": "rightName",
+ "operation": "target_rm"
}
]
diff --git a/test cases/rewrite/7 tricky dataflow/addSrc.json b/test cases/rewrite/7 tricky dataflow/addSrc.json
new file mode 100644
index 000000000..1b6aa485b
--- /dev/null
+++ b/test cases/rewrite/7 tricky dataflow/addSrc.json
@@ -0,0 +1,72 @@
+[
+ {
+ "type": "target",
+ "target": "tgt1",
+ "operation": "src_add",
+ "sources": [
+ "new.c"
+ ]
+ },
+ {
+ "type": "target",
+ "target": "tgt2",
+ "operation": "src_add",
+ "sources": [
+ "new.c"
+ ]
+ },
+ {
+ "type": "target",
+ "target": "tgt3",
+ "operation": "src_add",
+ "sources": [
+ "new.c"
+ ]
+ },
+ {
+ "type": "target",
+ "target": "tgt5",
+ "operation": "src_add",
+ "sources": [
+ "new.c"
+ ]
+ },
+ {
+ "type": "target",
+ "target": "tgt6",
+ "operation": "src_add",
+ "sources": [
+ "new.c"
+ ]
+ },
+ {
+ "type": "target",
+ "target": "tgt1",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "tgt2",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "tgt3",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "tgt4",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "tgt5",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "tgt6",
+ "operation": "info"
+ }
+]
diff --git a/test cases/rewrite/7 tricky dataflow/info.json b/test cases/rewrite/7 tricky dataflow/info.json
new file mode 100644
index 000000000..5fc65dd8c
--- /dev/null
+++ b/test cases/rewrite/7 tricky dataflow/info.json
@@ -0,0 +1,32 @@
+[
+ {
+ "type": "target",
+ "target": "tgt1",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "tgt2",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "tgt3",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "tgt4",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "tgt5",
+ "operation": "info"
+ },
+ {
+ "type": "target",
+ "target": "tgt6",
+ "operation": "info"
+ }
+]
diff --git a/test cases/rewrite/7 tricky dataflow/meson.build b/test cases/rewrite/7 tricky dataflow/meson.build
new file mode 100644
index 000000000..4c183ed97
--- /dev/null
+++ b/test cases/rewrite/7 tricky dataflow/meson.build
@@ -0,0 +1,35 @@
+project('rewrite tricky dataflow', 'c')
+
+# Adding a file to `begin` will add this file to the sources of `tgt1`, but
+# not to any other target. But a buggy rewriter might think that adding a file
+# to `begin` will also add this file to `end` and will refuse to add a file to
+# `begin`.
+begin = ['foo.c']
+tgt1 = library('tgt1', begin)
+distraction = executable('distraction', link_with: tgt1)
+
+
+tgt2_srcs = ['foo.c']
+if meson.host_machine().system() == 'windows' # Some condition that cannot be known statically
+ tgt2_srcs += ['bar.c']
+endif
+executable('tgt2', tgt2_srcs)
+
+
+tgt34_srcs = ['foo.c']
+executable('tgt3', tgt34_srcs)
+if meson.host_machine().system() == 'windows'
+ tgt34_srcs += ['bar.c']
+endif
+executable('tgt4', tgt34_srcs)
+
+
+dont_add_here_5 = ['foo.c']
+ct = custom_target('ct', output: 'out.c', input: dont_add_here_5, command: ['placeholder', '@INPUT@', '@OUTPUT@'])
+executable('tgt5', ct)
+
+
+dont_add_here_6 = ['foo.c']
+gen = generator(find_program('cp'), output: '@BASENAME@_copy.c', arguments: ['@INPUT@', '@OUTPUT@'])
+generated = gen.process(dont_add_here_6)
+executable('tgt6', generated)
diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py
index b983f341c..fa6f11965 100644
--- a/unittests/allplatformstests.py
+++ b/unittests/allplatformstests.py
@@ -3612,6 +3612,8 @@ class AllPlatformTests(BasePlatformTests):
# Account for differences in output
res_wb = [i for i in res_wb if i['type'] != 'custom']
for i in res_wb:
+ if i['id'] == 'test1@exe':
+ i['build_by_default'] = 'unknown'
i['filename'] = [os.path.relpath(x, self.builddir) for x in i['filename']]
for k in ('install_filename', 'dependencies', 'win_subsystem'):
if k in i:
@@ -3730,7 +3732,7 @@ class AllPlatformTests(BasePlatformTests):
},
{
'name': 'bugDep1',
- 'required': True,
+ 'required': 'unknown',
'version': [],
'has_fallback': False,
'conditional': False
diff --git a/unittests/rewritetests.py b/unittests/rewritetests.py
index 70a80347e..75405efc8 100644
--- a/unittests/rewritetests.py
+++ b/unittests/rewritetests.py
@@ -73,6 +73,10 @@ class RewriterTests(BasePlatformTests):
'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp'], 'extra_files': []},
'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp'], 'extra_files': []},
+ 'trivialprog11@exe': {'name': 'trivialprog11', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog12@exe': {'name': 'trivialprog12', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'rightName@exe': {'name': 'rightName', 'sources': ['main.cpp'], 'extra_files': []},
}
}
self.assertEqualIgnoreOrder(out, expected)
@@ -82,16 +86,20 @@ class RewriterTests(BasePlatformTests):
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json'))
expected = {
'target': {
- 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp'], 'extra_files': []},
- 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []},
- 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['a7.cpp', 'fileB.cpp', 'fileC.cpp'], 'extra_files': []},
+ 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp'], 'extra_files': []},
+ 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp', 'a1.cpp', 'a2.cpp'], 'extra_files': []},
+ 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp', 'a7.cpp'], 'extra_files': []},
'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['a5.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []},
- 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['a5.cpp', 'main.cpp', 'fileA.cpp'], 'extra_files': []},
- 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['a3.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp'], 'extra_files': []},
+ 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'a5.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['fileB.cpp', 'fileC.cpp', 'a3.cpp', 'main.cpp'], 'extra_files': []},
'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp', 'a4.cpp'], 'extra_files': []},
- 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []},
- 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []},
- 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []},
+ 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []},
+ 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp', 'a1.cpp', 'a6.cpp' ], 'extra_files': []},
+ 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'a1.cpp'], 'extra_files': []},
+ 'trivialprog11@exe': {'name': 'trivialprog11', 'sources': ['a1.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []},
+ 'trivialprog12@exe': {'name': 'trivialprog12', 'sources': ['a1.cpp', 'fileA.cpp', 'fileB.cpp', 'main.cpp'], 'extra_files': []},
+ 'rightName@exe': {'name': 'rightName', 'sources': ['main.cpp'], 'extra_files': []},
}
}
self.assertEqualIgnoreOrder(out, expected)
@@ -107,7 +115,7 @@ class RewriterTests(BasePlatformTests):
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'], 'extra_files': []}}}
+ expected = {'target': {'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp', 'a1.cpp', 'a2.cpp', 'a6.cpp'], 'extra_files': []}}}
self.assertDictEqual(out, expected)
def test_target_remove_sources(self):
@@ -115,16 +123,20 @@ class RewriterTests(BasePlatformTests):
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'rmSrc.json'))
expected = {
'target': {
- 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileC.cpp'], 'extra_files': []},
- 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp'], 'extra_files': []},
- 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileC.cpp'], 'extra_files': []},
+ 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp'], 'extra_files': []},
+ 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp'], 'extra_files': []},
'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp'], 'extra_files': []},
'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp'], 'extra_files': []},
- 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileC.cpp'], 'extra_files': []},
+ 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['fileB.cpp', 'fileC.cpp'], 'extra_files': []},
'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp'], 'extra_files': []},
- 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileC.cpp', 'main.cpp'], 'extra_files': []},
- 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp'], 'extra_files': []},
- 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp'], 'extra_files': []},
+ 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileC.cpp', 'main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp'], 'extra_files': []},
+ 'trivialprog11@exe': {'name': 'trivialprog11', 'sources': ['main.cpp'], 'extra_files': []},
+ 'trivialprog12@exe': {'name': 'trivialprog12', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'rightName@exe': {'name': 'rightName', 'sources': ['main.cpp'], 'extra_files': []},
}
}
self.assertEqualIgnoreOrder(out, expected)
@@ -136,7 +148,7 @@ class RewriterTests(BasePlatformTests):
def test_target_subdir(self):
self.prime('2 subdirs')
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json'))
- expected = {'name': 'something', 'sources': ['first.c', 'second.c', 'third.c'], 'extra_files': []}
+ expected = {'name': 'something', 'sources': ['third.c', f'sub2{os.path.sep}first.c', f'sub2{os.path.sep}second.c'], 'extra_files': []}
self.assertDictEqual(list(out['target'].values())[0], expected)
# Check the written file
@@ -157,6 +169,9 @@ class RewriterTests(BasePlatformTests):
'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp'], 'extra_files': []},
'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp'], 'extra_files': []},
+ 'trivialprog11@exe': {'name': 'trivialprog11', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog12@exe': {'name': 'trivialprog12', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
}
}
self.assertEqualIgnoreOrder(out, expected)
@@ -178,7 +193,11 @@ class RewriterTests(BasePlatformTests):
'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp'], 'extra_files': []},
'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
- 'trivialprog10@sha': {'name': 'trivialprog10', 'sources': ['new1.cpp', 'new2.cpp'], 'extra_files': []},
+ 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp'], 'extra_files': []},
+ 'trivialprog11@exe': {'name': 'trivialprog11', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog12@exe': {'name': 'trivialprog12', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []},
+ 'trivialprog13@sha': {'name': 'trivialprog13', 'sources': ['new1.cpp', 'new2.cpp'], 'extra_files': []},
+ 'rightName@exe': {'name': 'rightName', 'sources': ['main.cpp'], 'extra_files': []},
}
}
self.assertEqualIgnoreOrder(out, expected)
@@ -193,7 +212,7 @@ class RewriterTests(BasePlatformTests):
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'))
- expected = {'name': 'something', 'sources': ['first.c', 'second.c'], 'extra_files': []}
+ expected = {'name': 'something', 'sources': [f'sub2{os.path.sep}first.c', f'sub2{os.path.sep}second.c'], 'extra_files': []}
self.assertDictEqual(out['target']['94b671c@@something@exe'], expected)
def test_target_source_sorting(self):
@@ -240,16 +259,23 @@ class RewriterTests(BasePlatformTests):
}
}
}
+ for k1, v1 in expected.items():
+ for k2, v2 in v1.items():
+ for k3, v3 in v2.items():
+ if isinstance(v3, list):
+ for i in range(len(v3)):
+ v3[i] = v3[i].replace('/', os.path.sep)
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'], 'extra_files': []}
+ expected1 = {'name': 'myExe', 'sources': ['main.cpp'], 'extra_files': []}
+ expected2 = {'name': 'myExe', 'sources': [f'sub1{os.path.sep}main.cpp'], 'extra_files': []}
self.assertEqual(len(out['target']), 2)
- for val in out['target'].values():
- self.assertDictEqual(expected, val)
+ self.assertDictEqual(expected1, out['target']['myExe@exe'])
+ self.assertDictEqual(expected2, out['target']['9a11041@@myExe@exe'])
def test_kwargs_info(self):
self.prime('3 kwargs')
@@ -359,14 +385,14 @@ class RewriterTests(BasePlatformTests):
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addExtraFiles.json'))
expected = {
'target': {
- 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a2.hpp', 'a6.hpp', 'fileA.hpp', 'main.hpp', 'a7.hpp', 'fileB.hpp', 'fileC.hpp']},
- 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a2.hpp', 'a6.hpp', 'fileA.hpp', 'main.hpp']},
+ 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp'], 'extra_files': ['fileA.hpp', 'main.hpp', 'fileB.hpp', 'fileC.hpp']},
+ 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a2.hpp', 'fileA.hpp', 'main.hpp']},
'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['main.cpp'], 'extra_files': ['a7.hpp', 'fileB.hpp', 'fileC.hpp']},
'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp'], 'extra_files': ['a5.hpp', 'fileA.hpp', 'main.hpp']},
'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp'], 'extra_files': ['a5.hpp', 'main.hpp', 'fileA.hpp']},
- 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp'], 'extra_files': ['a3.hpp', 'main.hpp', 'a7.hpp', 'fileB.hpp', 'fileC.hpp']},
- 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a2.hpp', 'a6.hpp', 'fileA.hpp', 'main.hpp']},
- 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a2.hpp', 'a6.hpp', 'fileA.hpp', 'main.hpp']},
+ 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp'], 'extra_files': ['a3.hpp', 'main.hpp', 'fileB.hpp', 'fileC.hpp']},
+ 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp'], 'extra_files': ['fileA.hpp', 'main.hpp']},
+ 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a6.hpp', 'fileA.hpp', 'main.hpp']},
'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp'], 'extra_files': ['a2.hpp', 'a7.hpp']},
'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp'], 'extra_files': ['a8.hpp', 'a9.hpp']},
'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a4.hpp']},
@@ -383,14 +409,14 @@ class RewriterTests(BasePlatformTests):
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'rmExtraFiles.json'))
expected = {
'target': {
- 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileC.hpp']},
- 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp'], 'extra_files': ['main.hpp']},
- 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['main.cpp'], 'extra_files': ['fileC.hpp']},
+ 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileA.hpp', 'fileB.hpp', 'fileC.hpp']},
+ 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileA.hpp']},
+ 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['main.cpp'], 'extra_files': ['fileB.hpp', 'fileC.hpp']},
'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp'], 'extra_files': ['main.hpp']},
'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp'], 'extra_files': ['main.hpp']},
- 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileC.hpp']},
- 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp'], 'extra_files': ['main.hpp']},
- 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['main.cpp'], 'extra_files': ['main.hpp']},
+ 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp'], 'extra_files': ['fileB.hpp', 'fileC.hpp', 'main.hpp']},
+ 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileA.hpp']},
+ 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileA.hpp']},
'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp'], 'extra_files': []},
'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp'], 'extra_files': []},
'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp'], 'extra_files': []},
@@ -400,7 +426,26 @@ class RewriterTests(BasePlatformTests):
# Check the written file
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
- self.assertDictEqual(out, expected)
+ self.assertEqualIgnoreOrder(out, expected)
+
+ def test_tricky_dataflow(self):
+ self.prime('7 tricky dataflow')
+ out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json'))
+ expected = {
+ 'target': {
+ 'tgt1@sha': {'name': 'tgt1', 'sources': ['foo.c', 'new.c'], 'extra_files': []},
+ 'tgt2@exe': {'name': 'tgt2', 'sources': ['new.c', 'unknown'], 'extra_files': []},
+ 'tgt3@exe': {'name': 'tgt3', 'sources': ['foo.c', 'new.c'], 'extra_files': []},
+ 'tgt4@exe': {'name': 'tgt4', 'sources': ['unknown'], 'extra_files': []},
+ 'tgt5@exe': {'name': 'tgt5', 'sources': ['unknown', 'new.c'], 'extra_files': []},
+ 'tgt6@exe': {'name': 'tgt6', 'sources': ['unknown', 'new.c'], 'extra_files': []},
+ }
+ }
+ self.assertEqualIgnoreOrder(out, expected)
+
+ # Check the written file
+ out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
+ self.assertEqualIgnoreOrder(out, expected)
def test_raw_printer_is_idempotent(self):
test_path = Path(self.unit_test_dir, '120 rewrite')
@@ -437,7 +482,7 @@ class RewriterTests(BasePlatformTests):
# Asserts that AstInterpreter.dataflow_dag is what it should be
def test_dataflow_dag(self):
test_path = Path(self.rewrite_test_dir, '1 basic')
- interpreter = IntrospectionInterpreter(test_path, '', 'ninja', visitors = [AstIDGenerator()])
+ interpreter = IntrospectionInterpreter(test_path, '', 'ninja')
interpreter.analyze()
def sortkey(node):