intro: Add extra_files key to intro output (fixes #7310)

pull/7852/head
Daniel Mensinger 4 years ago committed by Jussi Pakkanen
parent 30d78f3981
commit 7c377e5a5d
  1. 4
      docs/markdown/IDE-integration.md
  2. 6
      docs/markdown/snippets/intro_extra_files.md
  3. 68
      mesonbuild/ast/introspection.py
  4. 2
      mesonbuild/backend/backends.py
  5. 5
      mesonbuild/build.py
  6. 24
      mesonbuild/mintro.py
  7. 49
      run_unittests.py
  8. 2
      test cases/unit/57 introspection/sharedlib/meson.build
  9. 3
      test cases/unit/57 introspection/staticlib/meson.build
  10. 10
      test cases/unit/57 introspection/staticlib/static.h

@ -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,
}
```
@ -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.

@ -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.)

@ -194,42 +194,51 @@ class IntrospectionInterpreter(AstInterpreter):
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']}
@ -251,6 +260,7 @@ class IntrospectionInterpreter(AstInterpreter):
'installed': target.should_install(),
'outputs': target.get_outputs(),
'sources': source_nodes,
'extra_files': extraf_nodes,
'kwargs': kwargs,
'node': node,
}

@ -1390,7 +1390,7 @@ class Backend:
This is a limited fallback / reference implementation. The backend should override this method.
'''
if isinstance(target, (build.CustomTarget, build.BuildTarget)):
source_list_raw = target.sources + target.extra_files
source_list_raw = target.sources
source_list = []
for j in source_list_raw:
if isinstance(j, mesonlib.File):

@ -370,6 +370,7 @@ a hard error in the future.'''.format(name))
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__))
@ -518,7 +519,6 @@ class BuildTarget(Target):
self.pch = {}
self.extra_args = {}
self.generated = []
self.extra_files = []
self.d_features = {}
self.pic = False
self.pie = False
@ -1011,7 +1011,7 @@ This will become a hard error in a future Meson release.''')
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))
@ -2058,7 +2058,6 @@ class CustomTarget(Target):
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 = []

@ -28,7 +28,7 @@ from . import mlog
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
@ -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)
@ -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'],
@ -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']
}]
@ -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
}

@ -4308,10 +4308,22 @@ recommended as it is not supported on some platforms''')
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),
@ -4333,6 +4345,8 @@ recommended as it is not supported on some platforms''')
('is_parallel', bool),
('protocol', str),
('depends', list),
('workdir', (str, None)),
('priority', int),
]
buildoptions_keylist = [
@ -4341,6 +4355,8 @@ recommended as it is not supported on some platforms''')
('type', str),
('description', str),
('machine', str),
('choices', (list, None)),
('value', (str, int, bool, list)),
]
buildoptions_typelist = [
@ -4369,6 +4385,9 @@ recommended as it is not supported on some platforms''')
('filename', list),
('build_by_default', bool),
('target_sources', list),
('extra_files', list),
('subproject', (str, None)),
('install_filename', (list, None)),
('installed', bool),
]
@ -4422,7 +4441,7 @@ recommended as it is not supported on some platforms''')
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
@ -9328,18 +9347,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)

@ -1,2 +1,2 @@
SRC_shared = ['shared.cpp']
sharedlib = shared_library('sharedTestLib', SRC_shared)
sharedlib = shared_library('sharedTestLib', SRC_shared, extra_files: ['shared.hpp'])

@ -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)

@ -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

Loading…
Cancel
Save