diff options
-rw-r--r-- | mesonbuild/mintro.py | 164 |
1 files changed, 160 insertions, 4 deletions
diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 3bcacfb..3896c92 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -21,11 +21,15 @@ project files and don't need this info.""" import json from . import build, mtest, coredata as cdata +from . import environment from . import mesonlib from . import astinterpreter from . import mparser +from . import mlog +from . import compilers +from . import optinterpreter from .interpreterbase import InvalidArguments -from .backend import ninjabackend +from .backend import ninjabackend, backends import sys, os import pathlib @@ -48,6 +52,8 @@ def add_arguments(parser): help='List external dependencies.') parser.add_argument('--projectinfo', action='store_true', dest='projectinfo', default=False, help='Information about projects.') + parser.add_argument('--backend', choices=cdata.backendlist, dest='backend', default='ninja', + help='The backend to use for the --buildoptions introspection.') parser.add_argument('builddir', nargs='?', default='.', help='The build directory') def determine_installed_path(target, installdata): @@ -129,7 +135,154 @@ def list_target_files(target_name, coredata, builddata): out.append(i) print(json.dumps(out)) -def list_buildoptions(coredata, builddata): +class BuildoptionsOptionHelper: + # mimic an argparse namespace + def __init__(self, cross_file): + self.cross_file = cross_file + self.native_file = None + self.cmd_line_options = {} + +class BuildoptionsInterperter(astinterpreter.AstInterpreter): + # Interpreter to detect the options without a build directory + # Most of the code is stolen from interperter.Interpreter + def __init__(self, source_root, subdir, backend, cross_file=None, subproject='', subproject_dir='subprojects', env=None): + super().__init__(source_root, subdir) + + options = BuildoptionsOptionHelper(cross_file) + self.cross_file = cross_file + if env is None: + self.environment = environment.Environment(source_root, None, options) + else: + self.environment = env + self.subproject = subproject + self.subproject_dir = subproject_dir + self.coredata = self.environment.get_coredata() + self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') + self.backend = backend + self.default_options = {'backend': self.backend} + + self.funcs.update({ + 'project': self.func_project, + 'add_languages': self.func_add_languages + }) + + def detect_compilers(self, lang, need_cross_compiler): + comp, cross_comp = self.environment.detect_compilers(lang, need_cross_compiler) + if comp is None: + return None, None + + self.coredata.compilers[lang] = comp + # Native compiler always exist so always add its options. + new_options = comp.get_options() + if cross_comp is not None: + self.coredata.cross_compilers[lang] = cross_comp + new_options.update(cross_comp.get_options()) + + optprefix = lang + '_' + for k, o in new_options.items(): + if not k.startswith(optprefix): + raise RuntimeError('Internal error, %s has incorrect prefix.' % k) + if k in self.environment.cmd_line_options: + o.set_value(self.environment.cmd_line_options[k]) + self.coredata.compiler_options.setdefault(k, o) + + return comp, cross_comp + + def flatten_args(self, args): + # Resolve mparser.ArrayNode if needed + flattend_args = [] + for i in args: + if isinstance(i, mparser.ArrayNode): + flattend_args += [x.value for x in i.args.arguments] + elif isinstance(i, str): + flattend_args += [i] + else: + pass + return flattend_args + + def add_languages(self, args): + need_cross_compiler = self.environment.is_cross_build() and self.environment.cross_info.need_cross_compiler() + for lang in sorted(args, key=compilers.sort_clink): + lang = lang.lower() + if lang not in self.coredata.compilers: + (comp, _) = self.detect_compilers(lang, need_cross_compiler) + if comp is None: + return + for optname in comp.base_options: + if optname in self.coredata.base_options: + continue + oobj = compilers.base_options[optname] + self.coredata.base_options[optname] = oobj + + def func_project(self, node, args, kwargs): + if len(args) < 1: + raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') + + proj_langs = self.flatten_args(args[1:]) + + if os.path.exists(self.option_file): + oi = optinterpreter.OptionInterpreter(self.subproject) + oi.process(self.option_file) + self.coredata.merge_user_options(oi.options) + + def_opts = kwargs.get('default_options', []) + if isinstance(def_opts, mparser.ArrayNode): + def_opts = [x.value for x in def_opts.args.arguments] + + self.project_default_options = mesonlib.stringlistify(def_opts) + self.project_default_options = cdata.create_options_dict(self.project_default_options) + self.default_options.update(self.project_default_options) + self.coredata.set_default_options(self.default_options, self.subproject, self.environment.cmd_line_options) + + if not self.is_subproject() and 'subproject_dir' in kwargs: + spdirname = kwargs['subproject_dir'] + if isinstance(spdirname, str): + self.subproject_dir = spdirname + if not self.is_subproject(): + subprojects_dir = os.path.join(self.source_root, self.subproject_dir) + if os.path.isdir(subprojects_dir): + for i in os.listdir(subprojects_dir): + if os.path.isdir(os.path.join(subprojects_dir, i)): + self.do_subproject(i) + + self.coredata.init_backend_options(self.backend) + options = {k: v for k, v in self.environment.cmd_line_options.items() if k.startswith('backend_')} + + self.coredata.set_options(options) + self.add_languages(proj_langs) + + def do_subproject(self, dirname): + subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) + subpr = os.path.join(subproject_dir_abs, dirname) + try: + subi = BuildoptionsInterperter(subpr, '', self.backend, cross_file=self.cross_file, subproject=dirname, subproject_dir=self.subproject_dir, env=self.environment) + subi.analyze() + except: + return + + def func_add_languages(self, node, args, kwargs): + return self.add_languages(self.flatten_args(args)) + + def is_subproject(self): + return self.subproject != '' + + def analyze(self): + self.load_root_meson_file() + self.sanity_check_ast() + self.parse_project() + self.run() + +def list_buildoptions_from_source(sourcedir, backend): + # 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 = BuildoptionsInterperter(sourcedir, '', backend.name) + intr.analyze() + # Reenable logging just in case + mlog.enable() + list_buildoptions(intr.coredata) + +def list_buildoptions(coredata): optlist = [] dir_option_names = ['bindir', @@ -301,10 +454,13 @@ def run(options): if options.builddir is not None: datadir = os.path.join(options.builddir, datadir) if options.builddir.endswith('/meson.build') or options.builddir.endswith('\\meson.build') or options.builddir == 'meson.build': + sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11] if options.projectinfo: - sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11] list_projinfo_from_source(sourcedir) return 0 + if options.buildoptions: + list_buildoptions_from_source(sourcedir, options.backend) + return 0 if not os.path.isdir(datadir): print('Current directory is not a build dir. Please specify it or ' 'change the working directory to it.') @@ -330,7 +486,7 @@ def run(options): elif options.buildsystem_files: list_buildsystem_files(builddata) elif options.buildoptions: - list_buildoptions(coredata, builddata) + list_buildoptions(coredata) elif options.tests: list_tests(testdata) elif options.benchmarks: |