diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2019-03-04 01:48:58 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-04 01:48:58 +0200 |
commit | 0de4f199b3aa30939436742cfcd706412ea498e9 (patch) | |
tree | 9e8e3bae6cee8ce439febfd18a8d26c71eb54380 /mesonbuild | |
parent | 7f9fb6a08481c26698fb9c8ce4920c456e46bbbd (diff) | |
parent | 586ec5a28cc2ea854f726cb7a6075b0918595bf5 (diff) | |
download | meson-0de4f199b3aa30939436742cfcd706412ea498e9.zip meson-0de4f199b3aa30939436742cfcd706412ea498e9.tar.gz meson-0de4f199b3aa30939436742cfcd706412ea498e9.tar.bz2 |
Merge pull request #4949 from mensinda/introTargetNoBD
mintro: Introspect targets and deps without a build directory
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/ast/__init__.py | 3 | ||||
-rw-r--r-- | mesonbuild/ast/introspection.py | 14 | ||||
-rw-r--r-- | mesonbuild/ast/postprocess.py | 30 | ||||
-rw-r--r-- | mesonbuild/mintro.py | 148 | ||||
-rw-r--r-- | mesonbuild/rewriter.py | 4 |
5 files changed, 152 insertions, 47 deletions
diff --git a/mesonbuild/ast/__init__.py b/mesonbuild/ast/__init__.py index a9370dc..48de523 100644 --- a/mesonbuild/ast/__init__.py +++ b/mesonbuild/ast/__init__.py @@ -16,6 +16,7 @@ # or an interpreter-based tool. __all__ = [ + 'AstConditionLevel', 'AstInterpreter', 'AstIDGenerator', 'AstIndentationGenerator', @@ -28,5 +29,5 @@ __all__ = [ from .interpreter import AstInterpreter from .introspection import IntrospectionInterpreter, build_target_functions from .visitor import AstVisitor -from .postprocess import AstIDGenerator, AstIndentationGenerator +from .postprocess import AstConditionLevel, AstIDGenerator, AstIndentationGenerator from .printer import AstPrinter diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 6ac5929..12cb379 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -137,8 +137,16 @@ class IntrospectionInterpreter(AstInterpreter): if not args: return name = args[0] + has_fallback = 'fallback' in kwargs + required = kwargs.get('required', True) + condition_level = node.condition_level if hasattr(node, 'condition_level') else 0 + if isinstance(required, ElementaryNode): + required = required.value self.dependencies += [{ 'name': name, + 'required': required, + 'has_fallback': has_fallback, + 'conditional': condition_level > 0, 'node': node }] @@ -180,11 +188,11 @@ class IntrospectionInterpreter(AstInterpreter): source_nodes += [curr] # Make sure nothing can crash when creating the build class - kwargs = {} + 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']} is_cross = False objects = [] empty_sources = [] # Passing the unresolved sources list causes errors - target = targetclass(name, self.subdir, self.subproject, is_cross, empty_sources, objects, self.environment, kwargs) + target = targetclass(name, self.subdir, self.subproject, is_cross, empty_sources, objects, self.environment, kwargs_reduced) self.targets += [{ 'name': target.get_basename(), @@ -193,6 +201,8 @@ class IntrospectionInterpreter(AstInterpreter): 'defined_in': os.path.normpath(os.path.join(self.source_root, self.subdir, environment.build_filename)), 'subdir': self.subdir, 'build_by_default': target.build_by_default, + 'installed': target.should_install(), + 'outputs': target.get_outputs(), 'sources': source_nodes, 'kwargs': kwargs, 'node': node, diff --git a/mesonbuild/ast/postprocess.py b/mesonbuild/ast/postprocess.py index e913b4f..8e8732f 100644 --- a/mesonbuild/ast/postprocess.py +++ b/mesonbuild/ast/postprocess.py @@ -84,3 +84,33 @@ class AstIDGenerator(AstVisitor): self.counter[name] = 0 node.ast_id = name + '#' + str(self.counter[name]) self.counter[name] += 1 + +class AstConditionLevel(AstVisitor): + def __init__(self): + self.condition_level = 0 + + def visit_default_func(self, node: mparser.BaseNode): + node.condition_level = self.condition_level + + def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode): + self.visit_default_func(node) + self.condition_level += 1 + node.items.accept(self) + node.block.accept(self) + self.condition_level -= 1 + + def visit_IfClauseNode(self, node: mparser.IfClauseNode): + self.visit_default_func(node) + for i in node.ifs: + i.accept(self) + if node.elseblock: + self.condition_level += 1 + node.elseblock.accept(self) + self.condition_level -= 1 + + def visit_IfNode(self, node: mparser.IfNode): + self.visit_default_func(node) + self.condition_level += 1 + node.condition.accept(self) + node.block.accept(self) + self.condition_level -= 1 diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index b1e6509..243dc5d 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -22,9 +22,11 @@ project files and don't need this info.""" import json from . import build, coredata as cdata from . import mesonlib -from .ast import IntrospectionInterpreter +from .ast import IntrospectionInterpreter, build_target_functions, AstConditionLevel, AstIDGenerator, AstIndentationGenerator from . import mlog from .backend import backends +from .mparser import FunctionNode, ArrayNode, ArgumentNode, StringNode +from typing import List, Optional import sys, os import pathlib @@ -37,7 +39,10 @@ def get_meson_introspection_version(): def get_meson_introspection_required_version(): return ['>=1.0', '<2.0'] -def get_meson_introspection_types(coredata: cdata.CoreData = None, builddata: build.Build = None, backend: backends.Backend = None): +def get_meson_introspection_types(coredata: Optional[cdata.CoreData] = None, + builddata: Optional[build.Build] = None, + backend: Optional[backends.Backend] = None, + sourcedir: Optional[str] = None): if backend and builddata: benchmarkdata = backend.create_test_serialisation(builddata.get_benchmarks()) testdata = backend.create_test_serialisation(builddata.get_tests()) @@ -52,6 +57,7 @@ def get_meson_introspection_types(coredata: cdata.CoreData = None, builddata: bu }, 'buildoptions': { 'func': lambda: list_buildoptions(coredata), + 'no_bd': lambda intr: list_buildoptions_from_source(intr), 'desc': 'List all build options.', }, 'buildsystem_files': { @@ -61,18 +67,26 @@ def get_meson_introspection_types(coredata: cdata.CoreData = None, builddata: bu }, 'dependencies': { 'func': lambda: list_deps(coredata), + 'no_bd': lambda intr: list_deps_from_source(intr), 'desc': 'List external dependencies.', }, + 'scan_dependencies': { + 'no_bd': lambda intr: list_deps_from_source(intr), + 'desc': 'Scan for dependencies used in the meson.build file.', + 'key': 'scan-dependencies', + }, 'installed': { 'func': lambda: list_installed(installdata), 'desc': 'List all installed files and directories.', }, 'projectinfo': { 'func': lambda: list_projinfo(builddata), + 'no_bd': lambda intr: list_projinfo_from_source(sourcedir, intr), 'desc': 'Information about projects.', }, 'targets': { 'func': lambda: list_targets(builddata, installdata, backend), + 'no_bd': lambda intr: list_targets_from_source(intr), 'desc': 'List top level targets.', }, 'tests': { @@ -113,6 +127,46 @@ def list_installed(installdata): res[path] = os.path.join(installdata.prefix, installpath) return res +def list_targets_from_source(intr: IntrospectionInterpreter): + tlist = [] + for i in intr.targets: + sources = [] + for n in i['sources']: + args = [] + if isinstance(n, FunctionNode): + args = list(n.args.arguments) + if n.func_name 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): + sources += [j.value] + elif isinstance(j, str): + sources += [j] + + tlist += [{ + 'name': i['name'], + 'id': i['id'], + 'type': i['type'], + 'defined_in': i['defined_in'], + 'filename': [os.path.join(i['subdir'], x) for x in i['outputs']], + 'build_by_default': i['build_by_default'], + 'target_sources': [{ + 'language': 'unknown', + 'compiler': [], + 'parameters': [], + 'sources': [os.path.normpath(os.path.join(os.path.abspath(intr.source_root), i['subdir'], x)) for x in sources], + 'generated_sources': [] + }], + 'subproject': None, # Subprojects are not supported + 'installed': i['installed'] + }] + + return tlist + def list_targets(builddata: build.Build, installdata, backend: backends.Backend): tlist = [] build_dir = builddata.environment.get_build_dir() @@ -147,15 +201,8 @@ def list_targets(builddata: build.Build, installdata, backend: backends.Backend) tlist.append(t) return tlist -def list_buildoptions_from_source(sourcedir, backend, indent): - # Make sure that log entries in other parts of meson don't interfere with the JSON output - mlog.disable() - backend = backends.get_backend_from_name(backend, None) - intr = IntrospectionInterpreter(sourcedir, '', backend.name) - intr.analyze() - # Reenable logging just in case - mlog.enable() - print(json.dumps(list_buildoptions(intr.coredata), indent=indent)) +def list_buildoptions_from_source(intr: IntrospectionInterpreter) -> List[dict]: + return list_buildoptions(intr.coredata) def list_target_files(target_name: str, targets: list, source_dir: str): sys.stderr.write("WARNING: The --target-files introspection API is deprecated. Use --targets instead.\n") @@ -178,7 +225,7 @@ def list_target_files(target_name: str, targets: list, source_dir: str): return result -def list_buildoptions(coredata: cdata.CoreData): +def list_buildoptions(coredata: cdata.CoreData) -> List[dict]: optlist = [] dir_option_names = ['bindir', @@ -250,6 +297,12 @@ def list_buildsystem_files(builddata: build.Build): filelist = [os.path.join(src_dir, x) for x in filelist] return filelist +def list_deps_from_source(intr: IntrospectionInterpreter): + result = [] + for i in intr.dependencies: + result += [{k: v for k, v in i.items() if k in ['name', 'required', 'has_fallback', 'conditional']}] + return result + def list_deps(coredata: cdata.CoreData): result = [] for d in coredata.deps.values(): @@ -299,15 +352,10 @@ def list_projinfo(builddata: build.Build): result['subprojects'] = subprojects return result -def list_projinfo_from_source(sourcedir, indent): +def list_projinfo_from_source(sourcedir: str, intr: IntrospectionInterpreter): files = find_buildsystem_files_list(sourcedir) files = [os.path.normpath(x) for x in files] - mlog.disable() - intr = IntrospectionInterpreter(sourcedir, '', 'ninja') - intr.analyze() - mlog.enable() - for i in intr.project_data['subprojects']: basedir = os.path.join(intr.subproject_dir, i['name']) i['buildsystem_files'] = [x for x in files if x.startswith(basedir)] @@ -315,23 +363,47 @@ def list_projinfo_from_source(sourcedir, indent): intr.project_data['buildsystem_files'] = files intr.project_data['subproject_dir'] = intr.subproject_dir - print(json.dumps(intr.project_data, indent=indent)) + return intr.project_data + +def print_results(options, results, indent): + if len(results) == 0 and not options.force_dict: + print('No command specified') + return 1 + elif len(results) == 1 and not options.force_dict: + # Make to keep the existing output format for a single option + print(json.dumps(results[0][1], indent=indent)) + else: + out = {} + for i in results: + out[i[0]] = i[1] + print(json.dumps(out, indent=indent)) + return 0 def run(options): datadir = 'meson-private' infodir = 'meson-info' - indent = 4 if options.indent else None if options.builddir is not None: datadir = os.path.join(options.builddir, datadir) infodir = os.path.join(options.builddir, infodir) + indent = 4 if options.indent else None + results = [] + sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11] + intro_types = get_meson_introspection_types(sourcedir=sourcedir) + if 'meson.build' in [os.path.basename(options.builddir), options.builddir]: - sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11] - if options.projectinfo: - list_projinfo_from_source(sourcedir, indent) - return 0 - if options.buildoptions: - list_buildoptions_from_source(sourcedir, options.backend, indent) - return 0 + # Make sure that log entries in other parts of meson don't interfere with the JSON output + mlog.disable() + backend = backends.get_backend_from_name(options.backend, None) + intr = IntrospectionInterpreter(sourcedir, '', backend.name, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()]) + intr.analyze() + # Reenable logging just in case + mlog.enable() + for key, val in intro_types.items(): + if (not options.all and not getattr(options, key, False)) or 'no_bd' not in val: + continue + results += [(key, val['no_bd'](intr))] + return print_results(options, results, indent) + infofile = get_meson_info_file(infodir) if not os.path.isdir(datadir) or not os.path.isdir(infodir) or not os.path.isfile(infofile): print('Current directory is not a meson build directory.' @@ -355,9 +427,6 @@ def run(options): .format(intro_vers, ' and '.join(vers_to_check))) return 1 - results = [] - intro_types = get_meson_introspection_types() - # Handle the one option that does not have its own JSON file (meybe deprecate / remove this?) if options.target_files is not None: targets_file = os.path.join(infodir, 'intro-targets.json') @@ -367,6 +436,8 @@ def run(options): # Extract introspection information from JSON for i in intro_types.keys(): + if 'func' not in intro_types[i]: + continue if not options.all and not getattr(options, i, False): continue curr = os.path.join(infodir, 'intro-{}.json'.format(i)) @@ -376,18 +447,7 @@ def run(options): with open(curr, 'r') as fp: results += [(i, json.load(fp))] - if len(results) == 0 and not options.force_dict: - print('No command specified') - return 1 - elif len(results) == 1 and not options.force_dict: - # Make to keep the existing output format for a single option - print(json.dumps(results[0][1], indent=indent)) - else: - out = {} - for i in results: - out[i[0]] = i[1] - print(json.dumps(out, indent=indent)) - return 0 + return print_results(options, results, indent) updated_introspection_files = [] @@ -408,6 +468,8 @@ def generate_introspection_file(builddata: build.Build, backend: backends.Backen intro_info = [] for key, val in intro_types.items(): + if 'func' not in val: + continue intro_info += [(key, val['func']())] write_intro_info(intro_info, builddata.environment.info_dir) @@ -436,6 +498,8 @@ def write_meson_info_file(builddata: build.Build, errors: list, build_files_upda intro_info = {} for i in intro_types.keys(): + if 'func' not in intro_types[i]: + continue intro_info[i] = { 'file': 'intro-{}.json'.format(i), 'updated': i in updated_introspection_files diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index ec78521..c997434 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -23,7 +23,7 @@ # - move targets # - reindent? -from .ast import IntrospectionInterpreter, build_target_functions, AstIDGenerator, AstIndentationGenerator, AstPrinter +from .ast import IntrospectionInterpreter, build_target_functions, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstPrinter from mesonbuild.mesonlib import MesonException from . import mlog, mparser, environment from functools import wraps @@ -324,7 +324,7 @@ rewriter_func_kwargs = { class Rewriter: def __init__(self, sourcedir: str, generator: str = 'ninja'): self.sourcedir = sourcedir - self.interpreter = IntrospectionInterpreter(sourcedir, '', generator, visitors = [AstIDGenerator(), AstIndentationGenerator()]) + self.interpreter = IntrospectionInterpreter(sourcedir, '', generator, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()]) self.modefied_nodes = [] self.to_remove_nodes = [] self.to_add_nodes = [] |