Skip to content

Commit

Permalink
intro: Add extra_files key to intro output (fixes mesonbuild#7310)
Browse files Browse the repository at this point in the history
  • Loading branch information
mensinda committed Oct 14, 2020
1 parent dccff1f commit fdff9d5
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 56 deletions.
4 changes: 4 additions & 0 deletions docs/markdown/IDE-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ for one target is defined as follows:
"filename": ["list", "of", "generated", "files"],
"build_by_default": true / false,
"target_sources": [],
"extra_files": ["/path/to/file1.hpp", "/path/to/file2.hpp"],
"installed": true / false,
}
```
Expand All @@ -71,6 +72,9 @@ is set to `null`.
The `subproject` key specifies the name of the subproject this target was
defined in, or `null` if the target was defined in the top level project.

*(New in 0.56.0)* The `extra_files` key lists all files specified via the
`extra_files` kwarg of a build target. See [`executable()`](Reference-manual.md#executable).

A target usually generates only one file. However, it is possible for custom
targets to have multiple outputs.

Expand Down
6 changes: 6 additions & 0 deletions docs/markdown/snippets/intro_extra_files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## New `extra_files` key in target introspection

The target introspection (`meson introspect --targets`, `intro-targets.json`)
now has the new `extra_files` key which lists all files specified via the
`extra_files` kwarg of a build target (see `executable()`, etc.)

68 changes: 39 additions & 29 deletions mesonbuild/ast/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,42 +194,51 @@ def build_target(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs_raw: T.Di
return None
name = args[0]
srcqueue = [node]
extra_queue = []

# Process the sources BEFORE flattening the kwargs, to preserve the original nodes
if 'sources' in kwargs_raw:
srcqueue += mesonlib.listify(kwargs_raw['sources'])

if 'extra_files' in kwargs_raw:
extra_queue += mesonlib.listify(kwargs_raw['extra_files'])

kwargs = self.flatten_kwargs(kwargs_raw, True)

source_nodes = [] # type: T.List[BaseNode]
while srcqueue:
curr = srcqueue.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.assignments:
tmp_node = self.assignments[var_name]
if isinstance(tmp_node, (ArrayNode, IdNode, FunctionNode)):
srcqueue += [tmp_node]
elif isinstance(curr, ArithmeticNode):
srcqueue += [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 in build_target_functions:
arg_nodes.pop(0)
elemetary_nodes = [x for x in arg_nodes if isinstance(x, (str, StringNode))]
srcqueue += [x for x in arg_nodes if isinstance(x, (FunctionNode, ArrayNode, IdNode, ArithmeticNode))]
if elemetary_nodes:
source_nodes += [curr]
def traverse_nodes(inqueue: T.List[BaseNode]) -> T.List[BaseNode]:
res = [] # type: 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.assignments:
tmp_node = self.assignments[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 in build_target_functions:
arg_nodes.pop(0)
elemetary_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 elemetary_nodes:
res += [curr]
return res

source_nodes = traverse_nodes(srcqueue)
extraf_nodes = traverse_nodes(extra_queue)

# 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']}
Expand All @@ -251,6 +260,7 @@ def build_target(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs_raw: T.Di
'installed': target.should_install(),
'outputs': target.get_outputs(),
'sources': source_nodes,
'extra_files': extraf_nodes,
'kwargs': kwargs,
'node': node,
}
Expand Down
5 changes: 2 additions & 3 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ def __init__(self, name, subdir, subproject, build_by_default, for_machine: Mach
self.build_always_stale = False
self.option_overrides_base = {}
self.option_overrides_compiler = defaultdict(dict)
self.extra_files = [] # type: T.List[File]
if not hasattr(self, 'typename'):
raise RuntimeError('Target type is not set for target class "{}". This is a bug'.format(type(self).__name__))

Expand Down Expand Up @@ -518,7 +519,6 @@ def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources
self.pch = {}
self.extra_args = {}
self.generated = []
self.extra_files = []
self.d_features = {}
self.pic = False
self.pie = False
Expand Down Expand Up @@ -1011,7 +1011,7 @@ def process_kwargs(self, kwargs, environment):
if self.gnu_symbol_visibility not in permitted:
raise InvalidArguments('GNU symbol visibility arg {} not one of: {}'.format(self.symbol_visibility, ', '.join(permitted)))

def validate_win_subsystem(self, value: str) -> str:
def validate_win_subsystem(self, value: str) -> str:
value = value.lower()
if re.fullmatch(r'(boot_application|console|efi_application|efi_boot_service_driver|efi_rom|efi_runtime_driver|native|posix|windows)(,\d+(\.\d+)?)?', value) is None:
raise InvalidArguments('Invalid value for win_subsystem: {}.'.format(value))
Expand Down Expand Up @@ -2058,7 +2058,6 @@ def __init__(self, name, subdir, subproject, kwargs, absolute_paths=False, backe
self.depend_files = [] # Files that this target depends on but are not on the command line.
self.depfile = None
self.process_kwargs(kwargs, backend)
self.extra_files = []
# Whether to use absolute paths for all files on the commandline
self.absolute_paths = absolute_paths
unknowns = []
Expand Down
24 changes: 17 additions & 7 deletions mesonbuild/mintro.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from .backend import backends
from .mparser import BaseNode, FunctionNode, ArrayNode, ArgumentNode, StringNode
from .interpreter import Interpreter
from ._pathlib import PurePath
from ._pathlib import Path, PurePath
import typing as T
import os
import argparse
Expand Down Expand Up @@ -119,9 +119,10 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]:

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 = [] # type: T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]
for i in intr.targets:
sources = [] # type: T.List[str]
for n in i['sources']:
root_dir = Path(intr.source_root)
def nodes_to_paths(node_list: T.List[BaseNode]) -> T.List[Path]:
res = [] # type: T.List[Path]
for n in node_list:
args = [] # type: T.List[BaseNode]
if isinstance(n, FunctionNode):
args = list(n.args.arguments)
Expand All @@ -134,9 +135,16 @@ def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[st
for j in args:
if isinstance(j, StringNode):
assert isinstance(j.value, str)
sources += [j.value]
res += [Path(j.value)]
elif isinstance(j, str):
sources += [j]
res += [Path(j)]
res = [root_dir / i['subdir'] / x for x in res]
res = [x.resolve() for x in res]
return res

for i in intr.targets:
sources = nodes_to_paths(i['sources'])
extra_f = nodes_to_paths(i['extra_files'])

tlist += [{
'name': i['name'],
Expand All @@ -149,9 +157,10 @@ def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[st
'language': 'unknown',
'compiler': [],
'parameters': [],
'sources': [os.path.normpath(os.path.join(os.path.abspath(intr.source_root), i['subdir'], x)) for x in sources],
'sources': [str(x) for x in sources],
'generated_sources': []
}],
'extra_files': [str(x) for x in extra_f],
'subproject': None, # Subprojects are not supported
'installed': i['installed']
}]
Expand Down Expand Up @@ -182,6 +191,7 @@ def list_targets(builddata: build.Build, installdata: backends.InstallData, back
'filename': [os.path.join(build_dir, target.subdir, x) for x in target.get_outputs()],
'build_by_default': target.build_by_default,
'target_sources': backend.get_introspection_data(idname, target),
'extra_files': [os.path.normpath(os.path.join(src_dir, x.subdir, x.fname)) for x in target.extra_files],
'subproject': target.subproject or None
}

Expand Down
49 changes: 35 additions & 14 deletions run_unittests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4295,10 +4295,22 @@ def test_introspect_json_dump(self):
infodir = os.path.join(self.builddir, 'meson-info')
self.assertPathExists(infodir)

def assertKeyTypes(key_type_list, obj):
def assertKeyTypes(key_type_list, obj, strict: bool = True):
for i in key_type_list:
if isinstance(i[1], (list, tuple)) and None in i[1]:
i = (i[0], tuple([x for x in i[1] if x is not None]))
if i[0] not in obj or obj[i[0]] is None:
continue
self.assertIn(i[0], obj)
self.assertIsInstance(obj[i[0]], i[1])
if strict:
for k in obj.keys():
found = False
for i in key_type_list:
if k == i[0]:
found = True
break
self.assertTrue(found, 'Key "{}" not in expected list'.format(k))

root_keylist = [
('benchmarks', list),
Expand All @@ -4320,6 +4332,8 @@ def assertKeyTypes(key_type_list, obj):
('is_parallel', bool),
('protocol', str),
('depends', list),
('workdir', (str, None)),
('priority', int),
]

buildoptions_keylist = [
Expand All @@ -4328,6 +4342,8 @@ def assertKeyTypes(key_type_list, obj):
('type', str),
('description', str),
('machine', str),
('choices', (list, None)),
('value', (str, int, bool, list)),
]

buildoptions_typelist = [
Expand Down Expand Up @@ -4356,6 +4372,9 @@ def assertKeyTypes(key_type_list, obj):
('filename', list),
('build_by_default', bool),
('target_sources', list),
('extra_files', list),
('subproject', (str, None)),
('install_filename', (list, None)),
('installed', bool),
]

Expand Down Expand Up @@ -4409,7 +4428,7 @@ def assertKeyTypes(key_type_list, obj):
for j in buildoptions_typelist:
if i['type'] == j[0]:
self.assertIsInstance(i['value'], j[1])
assertKeyTypes(j[2], i)
assertKeyTypes(j[2], i, strict=False)
valid_type = True
break

Expand Down Expand Up @@ -9269,18 +9288,20 @@ def main():
'LinuxlikeTests', 'LinuxCrossArmTests', 'LinuxCrossMingwTests',
'WindowsTests', 'DarwinTests']

# Don't use pytest-xdist when running single unit tests since it wastes
# time spawning a lot of processes to distribute tests to in that case.
if not running_single_tests(sys.argv, cases):
try:
import pytest # noqa: F401
# Need pytest-xdist for `-n` arg
import xdist # noqa: F401
pytest_args = ['-n', 'auto', './run_unittests.py']
pytest_args += convert_args(sys.argv[1:])
return subprocess.run(python_command + ['-m', 'pytest'] + pytest_args).returncode
except ImportError:
print('pytest-xdist not found, using unittest instead')
try:
import pytest # noqa: F401
# Need pytest-xdist for `-n` arg
import xdist # noqa: F401
pytest_args = []
# Don't use pytest-xdist when running single unit tests since it wastes
# time spawning a lot of processes to distribute tests to in that case.
if not running_single_tests(sys.argv, cases):
pytest_args += ['-n', 'auto']
pytest_args += ['./run_unittests.py']
pytest_args += convert_args(sys.argv[1:])
return subprocess.run(python_command + ['-m', 'pytest'] + pytest_args).returncode
except ImportError:
print('pytest-xdist not found, using unittest instead')
# Fallback to plain unittest.
return unittest.main(defaultTest=cases, buffer=True)

Expand Down
2 changes: 1 addition & 1 deletion test cases/unit/57 introspection/sharedlib/meson.build
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
SRC_shared = ['shared.cpp']
sharedlib = shared_library('sharedTestLib', SRC_shared)
sharedlib = shared_library('sharedTestLib', SRC_shared, extra_files: ['shared.hpp'])
3 changes: 2 additions & 1 deletion test cases/unit/57 introspection/staticlib/meson.build
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
SRC_static = ['static.c']
staticlib = static_library('staticTestLib', SRC_static)
extra_static = files(['static.h'])
staticlib = static_library('staticTestLib', SRC_static, extra_files: extra_static)
10 changes: 9 additions & 1 deletion test cases/unit/57 introspection/staticlib/static.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
#pragma once

int add_numbers(int a, int b);
#ifdef __cplusplus
extern "C" {
#endif

int add_numbers(int a, int b);

#ifdef __cplusplus
}
#endif

0 comments on commit fdff9d5

Please sign in to comment.