diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 7782555fb..406723f05 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -25,7 +25,7 @@ from ..backend.backends import Backend from ..compilers.compilers import obj_suffixes from ..dependencies.base import CMakeDependency, ExternalProgram from subprocess import Popen, PIPE, STDOUT -from typing import List +from typing import List, Dict, Optional import os, re backend_generator_map = { @@ -284,6 +284,9 @@ class CMakeInterpreter: self.languages = [] self.targets = [] + # Generated meson data + self.generated_targets = {} + def configure(self, extra_cmake_options: List[str]) -> None: # Find CMake cmake_exe, cmake_vers, _ = CMakeDependency.find_cmake_binary(self.env) @@ -297,7 +300,7 @@ class CMakeInterpreter: cmake_args = cmake_exe.get_command() # Map meson compiler to CMake variables - for lang, comp in self.build.compilers.items(): + for lang, comp in self.env.coredata.compilers.items(): if lang not in language_map: continue cmake_lang = language_map[lang] @@ -530,11 +533,20 @@ class CMakeInterpreter: # Add the nodes to the ast root_cb.lines += [inc_node, src_node, tgt_node, dep_node] - processed[tgt.name] = {'inc': inc_var, 'src': src_var, 'dep': dep_var, 'tgt': tgt_var} + processed[tgt.name] = {'inc': inc_var, 'src': src_var, 'dep': dep_var, 'tgt': tgt_var, 'func': tgt_func} # Now generate the target function calls for i in self.targets: if i.name not in processed: process_target(i) + self.generated_targets = processed return root_cb + + def target_info(self, target: str) -> Optional[Dict[str, str]]: + if target in self.generated_targets: + return self.generated_targets[target] + return None + + def target_list(self) -> List[str]: + return list(self.generated_targets.keys()) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 0e502024a..1ac4cd946 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2247,7 +2247,7 @@ class Interpreter(InterpreterBase): raise InterpreterException('Stdlib definition for %s should have exactly two elements.' % l) projname, depname = di - subproj = self.do_subproject(projname, {}) + subproj = self.do_subproject(projname, 'meson', {}) self.build.cross_stdlibs[l] = subproj.get_variable_method([depname], {}) except KeyError: pass @@ -2416,20 +2416,19 @@ external dependencies (including libraries) must go to "dependencies".''') raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.') @FeatureNewKwargs('subproject', '0.38.0', ['default_options']) - @FeatureNewKwargs('subproject', '0.51.0', ['method', 'cmake_options']) @permittedKwargs(permitted_kwargs['subproject']) @stringArgs def func_subproject(self, nodes, args, kwargs): if len(args) != 1: raise InterpreterException('Subproject takes exactly one argument') dirname = args[0] - return self.do_subproject(dirname, kwargs) + return self.do_subproject(dirname, 'meson', kwargs) def disabled_subproject(self, dirname): self.subprojects[dirname] = SubprojectHolder(None, self.subproject_dir, dirname) return self.subprojects[dirname] - def do_subproject(self, dirname, kwargs): + def do_subproject(self, dirname: str, method: str, kwargs): disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: mlog.log('Subproject', mlog.bold(dirname), ':', 'skipped: feature', mlog.bold(feature), 'disabled') @@ -2437,7 +2436,6 @@ external dependencies (including libraries) must go to "dependencies".''') default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) default_options = coredata.create_options_dict(default_options) - method = kwargs.get('method', 'auto') if dirname == '': raise InterpreterException('Subproject dir name must not be empty.') if dirname[0] == '.': @@ -2463,7 +2461,7 @@ external dependencies (including libraries) must go to "dependencies".''') subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) r = wrap.Resolver(subproject_dir_abs, self.coredata.get_builtin_option('wrap_mode')) try: - resolved = r.resolve(dirname) + resolved = r.resolve(dirname, method) except wrap.WrapException as e: subprojdir = os.path.join(self.subproject_dir, r.directory) if isinstance(e, wrap.WrapNotFoundException): @@ -2483,21 +2481,14 @@ external dependencies (including libraries) must go to "dependencies".''') os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) self.global_args_frozen = True - # Determine the method to use when 'auto' is given - if method == 'auto': - if os.path.exists(os.path.join(subdir_abs, environment.build_filename)): - method = 'meson' - elif os.path.exists(os.path.join(subdir_abs, 'CMakeLists.txt')): - method = 'cmake' - mlog.log() with mlog.nested(): mlog.log('Executing subproject', mlog.bold(dirname), 'method', mlog.bold(method), '\n') try: if method == 'meson': - return self.do_subproject_meson(dirname, subdir, default_options, required, kwargs) + return self._do_subproject_meson(dirname, subdir, default_options, required, kwargs) elif method == 'cmake': - return self.do_subproject_cmake(dirname, subdir, subdir_abs, default_options, required, kwargs) + return self._do_subproject_cmake(dirname, subdir, subdir_abs, default_options, required, kwargs) else: raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname)) # Invalid code is always an error @@ -2513,7 +2504,7 @@ external dependencies (including libraries) must go to "dependencies".''') return self.disabled_subproject(dirname) raise e - def do_subproject_meson(self, dirname, subdir, default_options, required, kwargs, ast=None, build_def_files=None): + def _do_subproject_meson(self, dirname, subdir, default_options, required, kwargs, ast=None, build_def_files=None): with mlog.nested(): new_build = self.build.copy() subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir, @@ -2544,7 +2535,7 @@ external dependencies (including libraries) must go to "dependencies".''') self.build.subprojects[dirname] = subi.project_version return self.subprojects[dirname] - def do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, required, kwargs): + def _do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, required, kwargs): with mlog.nested(): new_build = self.build.copy() prefix = self.coredata.builtins['prefix'].value @@ -2572,7 +2563,8 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.debug('=== END meson.build ===') mlog.debug() - result = self.do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast, cm_int.bs_files) + result = self._do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast, cm_int.bs_files) + result.cm_interpreter = cm_int mlog.log() return result @@ -3164,7 +3156,7 @@ external dependencies (including libraries) must go to "dependencies".''') 'default_options': kwargs.get('default_options', []), 'required': kwargs.get('required', True), } - self.do_subproject(dirname, sp_kwargs) + self.do_subproject(dirname, 'meson', sp_kwargs) return self.get_subproject_dep(display_name, dirname, varname, kwargs) @FeatureNewKwargs('executable', '0.42.0', ['implib']) diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index d72ceca18..8ce5aef89 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -18,8 +18,8 @@ import shutil from . import ExtensionModule, ModuleReturnValue from .. import build, dependencies, mesonlib, mlog -from ..interpreterbase import permittedKwargs -from ..interpreter import ConfigurationDataHolder +from ..interpreterbase import permittedKwargs, FeatureNew, stringArgs, InterpreterObject, ObjectHolder +from ..interpreter import ConfigurationDataHolder, InterpreterException, SubprojectHolder COMPATIBILITIES = ['AnyNewerVersion', 'SameMajorVersion', 'SameMinorVersion', 'ExactVersion'] @@ -43,6 +43,62 @@ unset(_realOrig) unset(_realCurr) ''' +class CMakeSubprojectHolder(InterpreterObject, ObjectHolder): + def __init__(self, subp, pv): + assert(isinstance(subp, SubprojectHolder)) + assert(hasattr(subp, 'cm_interpreter')) + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, subp, pv) + self.methods.update({'get_variable': self.get_variable, + 'dependency': self.dependency, + 'include_directories': self.include_directories, + 'target': self.target, + 'target_type': self.target_type, + 'target_list': self.target_list, + }) + + def _args_to_info(self, args): + if len(args) != 1: + raise InterpreterException('Exactly one argument is required.') + + tgt = args[0] + res = self.held_object.cm_interpreter.target_info(tgt) + if res is None: + raise InterpreterException('The CMake target {} does not exist'.format(tgt)) + + # Make sure that all keys are present (if not this is a bug) + assert(all([x in res for x in ['inc', 'src', 'dep', 'tgt', 'func']])) + return res + + @permittedKwargs({}) + def get_variable(self, args, kwargs): + return self.held_object.get_variable_method(args, kwargs) + + @permittedKwargs({}) + def dependency(self, args, kwargs): + info = self._args_to_info(args) + return self.get_variable([info['dep']], kwargs) + + @permittedKwargs({}) + def include_directories(self, args, kwargs): + info = self._args_to_info(args) + return self.get_variable([info['inc']], kwargs) + + @permittedKwargs({}) + def target(self, args, kwargs): + info = self._args_to_info(args) + return self.get_variable([info['tgt']], kwargs) + + @permittedKwargs({}) + def target_type(self, args, kwargs): + info = self._args_to_info(args) + return info['func'] + + @permittedKwargs({}) + def target_list(self, args, kwargs): + if len(args) > 0: + raise InterpreterException('target_list does not take any parameters.') + return self.held_object.cm_interpreter.target_list() class CmakeModule(ExtensionModule): cmake_detected = False @@ -51,6 +107,7 @@ class CmakeModule(ExtensionModule): def __init__(self, interpreter): super().__init__(interpreter) self.snippets.add('configure_package_config_file') + self.snippets.add('subproject') def detect_voidp_size(self, env): compilers = env.coredata.compilers @@ -210,5 +267,14 @@ class CmakeModule(ExtensionModule): return res + @FeatureNew('subproject', '0.51.0') + @permittedKwargs({'cmake_options'}) + @stringArgs + def subproject(self, interpreter, state, args, kwargs): + if len(args) != 1: + raise InterpreterException('Subproject takes exactly one argument') + dirname = args[0] + return CMakeSubprojectHolder(interpreter.do_subproject(dirname, 'cmake', kwargs), dirname) + def initialize(*args, **kwargs): return CmakeModule(*args, **kwargs) diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py index 4162af6a0..bc6a6ced1 100755 --- a/mesonbuild/msubprojects.py +++ b/mesonbuild/msubprojects.py @@ -163,7 +163,7 @@ def download(wrap, repo_dir, options): return try: r = Resolver(os.path.dirname(repo_dir)) - r.resolve(wrap.name) + r.resolve(wrap.name, 'meson') mlog.log(' -> done') except WrapException as e: mlog.log(' ->', mlog.red(str(e))) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 47db985c2..55f86bc05 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -111,7 +111,7 @@ class Resolver: self.subdir_root = subdir_root self.cachedir = os.path.join(self.subdir_root, 'packagecache') - def resolve(self, packagename): + def resolve(self, packagename: str, method: str): self.packagename = packagename self.directory = packagename # We always have to load the wrap file, if it exists, because it could @@ -125,8 +125,13 @@ class Resolver: meson_file = os.path.join(self.dirname, 'meson.build') cmake_file = os.path.join(self.dirname, 'CMakeLists.txt') + if method not in ['meson', 'cmake']: + raise WrapException('Only the methods "meson" and "cmake" are supported') + # The directory is there and has meson.build? Great, use it. - if os.path.exists(meson_file) or os.path.exists(cmake_file): + if method == 'meson' and os.path.exists(meson_file): + return self.directory + if method == 'cmake' and os.path.exists(cmake_file): return self.directory # Check if the subproject is a git submodule @@ -154,9 +159,11 @@ class Resolver: else: raise WrapException('Unknown wrap type {!r}'.format(self.wrap.type)) - # A meson.build file is required in the directory - if not os.path.exists(meson_file): + # A meson.build or CMakeLists.txt file is required in the directory + if method == 'meson' and not os.path.exists(meson_file): raise WrapException('Subproject exists but has no meson.build file') + if method == 'cmake' and not os.path.exists(cmake_file): + raise WrapException('Subproject exists but has no CMakeLists.txt file') return self.directory