diff options
21 files changed, 188 insertions, 27 deletions
diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index 3aeea14..9000c40 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -87,6 +87,10 @@ previously reserved to `wrap-file`: `subprojects/packagefiles`. - `diff_files` - *Since 0.63.0* Comma-separated list of local diff files (see [Diff files](#diff-files) below). +- `method` - *Since 1.3.0* The build system used by this subproject. Defaults to `meson`. + Supported methods: + - `meson` requires `meson.build` file. + - `cmake` requires `CMakeLists.txt` file. [See details](#cmake-wraps). ### Specific to wrap-file - `source_url` - download url to retrieve the wrap-file source archive @@ -290,6 +294,26 @@ With such wrap file, `find_program('myprog')` will automatically fallback to use the subproject, assuming it uses `meson.override_find_program('myprog')`. +### CMake wraps + +Since the CMake module does not know the public name of the provided +dependencies, a CMake `.wrap` file cannot use the `dependency_names = foo` +syntax. Instead, the `dep_name = <target_name>_dep` syntax should be used, where +`<target_name>` is the name of a CMake library with all non alphanumeric +characters replaced by underscores `_`. + +For example, a CMake project that contains `add_library(foo-bar ...)` in its +`CMakeList.txt` and that applications would usually find using the dependency +name `foo-bar-1.0` (e.g. via pkg-config) would have a wrap file like this: + +```ini +[wrap-file] +... +method = cmake +[provide] +foo-bar-1.0 = foo_bar_dep +``` + ## Using wrapped projects Wraps provide a convenient way of obtaining a project into your diff --git a/docs/markdown/snippets/wrap.md b/docs/markdown/snippets/wrap.md new file mode 100644 index 0000000..6e03c2e --- /dev/null +++ b/docs/markdown/snippets/wrap.md @@ -0,0 +1,12 @@ +## Automatic fallback to `cmake` subproject + +CMake subprojects have been supported for a while using the `cmake.subproject()` +module method. However until now it was not possible to use a CMake subproject +as fallback in a `dependency()` call. + +A wrap file can now specify the method used to build it by setting the `method` +key in the wrap file's first section. The method defaults to `meson`. + +Supported methods: +- `meson` requires `meson.build` file. +- `cmake` requires `CMakeLists.txt` file. [See details](Wrap-dependency-system-manual.md#cmake-wraps). diff --git a/mesonbuild/interpreter/dependencyfallbacks.py b/mesonbuild/interpreter/dependencyfallbacks.py index 7ef1527..eca6a2c 100644 --- a/mesonbuild/interpreter/dependencyfallbacks.py +++ b/mesonbuild/interpreter/dependencyfallbacks.py @@ -127,7 +127,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject): func_kwargs.setdefault('version', []) if 'default_options' in kwargs and isinstance(kwargs['default_options'], str): func_kwargs['default_options'] = listify(kwargs['default_options']) - self.interpreter.do_subproject(subp_name, 'meson', func_kwargs) + self.interpreter.do_subproject(subp_name, func_kwargs) return self._get_subproject_dep(subp_name, varname, kwargs) def _get_subproject(self, subp_name: str) -> T.Optional[SubprojectHolder]: diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index a5c8a5a..e0c17e6 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -116,8 +116,6 @@ import copy if T.TYPE_CHECKING: import argparse - from typing_extensions import Literal - from . import kwargs as kwtypes from ..backend.backends import Backend from ..interpreterbase.baseobjects import InterpreterObject, TYPE_var, TYPE_kwargs @@ -868,7 +866,7 @@ class Interpreter(InterpreterBase, HoldableObject): 'options': None, 'cmake_options': [], } - return self.do_subproject(args[0], 'meson', kw) + return self.do_subproject(args[0], kw) def disabled_subproject(self, subp_name: str, disabled_feature: T.Optional[str] = None, exception: T.Optional[Exception] = None) -> SubprojectHolder: @@ -877,7 +875,7 @@ class Interpreter(InterpreterBase, HoldableObject): self.subprojects[subp_name] = sub return sub - def do_subproject(self, subp_name: str, method: Literal['meson', 'cmake'], kwargs: kwtypes.DoSubproject) -> SubprojectHolder: + def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_method: T.Optional[wrap.Method] = None) -> SubprojectHolder: disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled') @@ -913,7 +911,7 @@ class Interpreter(InterpreterBase, HoldableObject): r = self.environment.wrap_resolver try: - subdir = r.resolve(subp_name, method) + subdir, method = r.resolve(subp_name, force_method) except wrap.WrapException as e: if not required: mlog.log(e) @@ -1009,8 +1007,8 @@ class Interpreter(InterpreterBase, HoldableObject): prefix = self.coredata.options[OptionKey('prefix')].value from ..modules.cmake import CMakeSubprojectOptions - options = kwargs['options'] or CMakeSubprojectOptions() - cmake_options = kwargs['cmake_options'] + options.cmake_options + options = kwargs.get('options') or CMakeSubprojectOptions() + cmake_options = kwargs.get('cmake_options', []) + options.cmake_options cm_int = CMakeInterpreter(new_build, Path(subdir), Path(subdir_abs), Path(prefix), new_build.environment, self.backend) cm_int.initialise(cmake_options) cm_int.analyse() @@ -1734,7 +1732,7 @@ class Interpreter(InterpreterBase, HoldableObject): 'cmake_options': [], 'options': None, } - self.do_subproject(fallback, 'meson', sp_kwargs) + self.do_subproject(fallback, sp_kwargs) return self.program_from_overrides(args, extra_info) @typed_pos_args('find_program', varargs=(str, mesonlib.File), min_varargs=1) diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index bec1b2a..ee4e844 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -435,7 +435,7 @@ class CmakeModule(ExtensionModule): 'default_options': {}, 'version': [], } - subp = self.interpreter.do_subproject(dirname, 'cmake', kw) + subp = self.interpreter.do_subproject(dirname, kw, force_method='cmake') if not subp.found(): return subp return CMakeSubproject(subp) diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py index 64a09b0..45b711d 100755 --- a/mesonbuild/msubprojects.py +++ b/mesonbuild/msubprojects.py @@ -189,7 +189,7 @@ class Runner: # cached. windows_proof_rmtree(self.repo_dir) try: - self.wrap_resolver.resolve(self.wrap.name, 'meson') + self.wrap_resolver.resolve(self.wrap.name) self.log(' -> New version extracted') return True except WrapException as e: @@ -292,7 +292,7 @@ class Runner: # Delete existing directory and redownload windows_proof_rmtree(self.repo_dir) try: - self.wrap_resolver.resolve(self.wrap.name, 'meson') + self.wrap_resolver.resolve(self.wrap.name) self.update_git_done() return True except WrapException as e: @@ -464,7 +464,7 @@ class Runner: self.log(' -> Already downloaded') return True try: - self.wrap_resolver.resolve(self.wrap.name, 'meson') + self.wrap_resolver.resolve(self.wrap.name) self.log(' -> done') except WrapException as e: self.log(' ->', mlog.red(str(e))) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index c0f0f07..a1bf725 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -45,6 +45,9 @@ from .. import mesonlib if T.TYPE_CHECKING: import http.client + from typing_extensions import Literal + + Method = Literal['meson', 'cmake'] try: # Importing is just done to check if SSL exists, so all warnings @@ -406,7 +409,7 @@ class Resolver: return wrap_name return None - def resolve(self, packagename: str, method: str) -> str: + def resolve(self, packagename: str, force_method: T.Optional[Method] = None) -> T.Tuple[str, Method]: self.packagename = packagename self.directory = packagename self.wrap = self.wraps.get(packagename) @@ -443,17 +446,28 @@ class Resolver: self.dirname = self.wrap.filename rel_path = os.path.relpath(self.dirname, self.source_dir) - if method == 'meson': - buildfile = os.path.join(self.dirname, 'meson.build') - elif method == 'cmake': - buildfile = os.path.join(self.dirname, 'CMakeLists.txt') - else: - raise WrapException('Only the methods "meson" and "cmake" are supported') + # Map each supported method to a file that must exist at the root of source tree. + methods_map: T.Dict[Method, str] = { + 'meson': 'meson.build', + 'cmake': 'CMakeLists.txt', + } + + # Check if this wrap forces a specific method, use meson otherwise. + method = T.cast('T.Optional[Method]', self.wrap.values.get('method', force_method)) + if method and method not in methods_map: + allowed_methods = ', '.join(methods_map.keys()) + raise WrapException(f'Wrap method {method!r} is not supported, must be one of: {allowed_methods}') + if force_method and method != force_method: + raise WrapException(f'Wrap method is {method!r} but we are trying to configure it with {force_method}') + method = method or 'meson' + + def has_buildfile() -> bool: + return os.path.exists(os.path.join(self.dirname, methods_map[method])) # The directory is there and has meson.build? Great, use it. - if os.path.exists(buildfile): + if has_buildfile(): self.validate() - return rel_path + return rel_path, method # Check if the subproject is a git submodule self.resolve_git_submodule() @@ -491,16 +505,14 @@ class Resolver: windows_proof_rmtree(self.dirname) raise - # A meson.build or CMakeLists.txt file is required in the directory - if not os.path.exists(buildfile): - raise WrapException(f'Subproject exists but has no {os.path.basename(buildfile)} file') + if not has_buildfile(): + raise WrapException(f'Subproject exists but has no {methods_map[method]} file.') # At this point, the subproject has been successfully resolved for the # first time so save off the hash of the entire wrap file for future # reference. self.wrap.update_hash_cache(self.dirname) - - return rel_path + return rel_path, method def check_can_download(self) -> None: # Don't download subproject data based on wrap file if requested. diff --git a/test cases/cmake/26 dependency fallback/main.cpp b/test cases/cmake/26 dependency fallback/main.cpp new file mode 100644 index 0000000..9507961 --- /dev/null +++ b/test cases/cmake/26 dependency fallback/main.cpp @@ -0,0 +1,10 @@ +#include <iostream> +#include <cmMod.hpp> + +using namespace std; + +int main(void) { + cmModClass obj("Hello"); + cout << obj.getStr() << endl; + return 0; +} diff --git a/test cases/cmake/26 dependency fallback/meson.build b/test cases/cmake/26 dependency fallback/meson.build new file mode 100644 index 0000000..b36aaac --- /dev/null +++ b/test cases/cmake/26 dependency fallback/meson.build @@ -0,0 +1,30 @@ +project('cmakeSubTest', ['c', 'cpp']) + +# Fallback to a CMake subproject +sub_dep = dependency('cmModLib++') +exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep]) +test('test1', exe1) + +# Subproject contains both meson.build and CMakeLists.txt. It should default +# to meson but wrap force cmake. +subproject('force_cmake') + +testcase expect_error('Wrap method \'notfound\' is not supported, must be one of: meson, cmake') + subproject('broken_method') +endtestcase + +# With method=meson we can't use cmake.subproject() +cmake = import('cmake') +testcase expect_error('Wrap method is \'meson\' but we are trying to configure it with cmake') + cmake.subproject('meson_method') +endtestcase + +# cmake.subproject() force cmake method even if meson.build exists. +testcase expect_error('Subproject exists but has no CMakeLists.txt file.') + cmake.subproject('meson_subp') +endtestcase + +# Without specifying the method it defaults to meson even if CMakeLists.txt exists. +testcase expect_error('Subproject exists but has no meson.build file.') + subproject('cmake_subp') +endtestcase diff --git a/test cases/cmake/26 dependency fallback/subprojects/broken_method.wrap b/test cases/cmake/26 dependency fallback/subprojects/broken_method.wrap new file mode 100644 index 0000000..ce0690a --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/broken_method.wrap @@ -0,0 +1,2 @@ +[wrap-file] +method=notfound diff --git a/test cases/cmake/26 dependency fallback/subprojects/cmMod.wrap b/test cases/cmake/26 dependency fallback/subprojects/cmMod.wrap new file mode 100644 index 0000000..9e6d855 --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/cmMod.wrap @@ -0,0 +1,5 @@ +[wrap-file] +method = cmake + +[provide] +cmModLib++ = cmModLib___dep diff --git a/test cases/cmake/26 dependency fallback/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/26 dependency fallback/subprojects/cmMod/CMakeLists.txt new file mode 100644 index 0000000..d08e55c --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/cmMod/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmMod) +set(CMAKE_CXX_STANDARD 14) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +add_definitions("-DDO_NOTHING_JUST_A_FLAG=1") + +add_library(cmModLib++ SHARED cmMod.cpp) +target_compile_definitions(cmModLib++ PRIVATE MESON_MAGIC_FLAG=21) +target_compile_definitions(cmModLib++ INTERFACE MESON_MAGIC_FLAG=42) + +# Test PCH support +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") + target_precompile_headers(cmModLib++ PRIVATE "cpp_pch.hpp") +endif() + +include(GenerateExportHeader) +generate_export_header(cmModLib++) diff --git a/test cases/cmake/26 dependency fallback/subprojects/cmMod/cmMod.cpp b/test cases/cmake/26 dependency fallback/subprojects/cmMod/cmMod.cpp new file mode 100644 index 0000000..f4cbea0 --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/cmMod/cmMod.cpp @@ -0,0 +1,15 @@ +#include "cmMod.hpp" + +using namespace std; + +#if MESON_MAGIC_FLAG != 21 +#error "Invalid MESON_MAGIC_FLAG (private)" +#endif + +cmModClass::cmModClass(string foo) { + str = foo + " World"; +} + +string cmModClass::getStr() const { + return str; +} diff --git a/test cases/cmake/26 dependency fallback/subprojects/cmMod/cmMod.hpp b/test cases/cmake/26 dependency fallback/subprojects/cmMod/cmMod.hpp new file mode 100644 index 0000000..4445e1f --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/cmMod/cmMod.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "cmmodlib++_export.h" +#include <string> + +#if MESON_MAGIC_FLAG != 42 && MESON_MAGIC_FLAG != 21 +#error "Invalid MESON_MAGIC_FLAG" +#endif + +class CMMODLIB___EXPORT cmModClass { +private: + std::string str; + +public: + cmModClass(std::string foo); + + std::string getStr() const; +}; diff --git a/test cases/cmake/26 dependency fallback/subprojects/cmMod/cpp_pch.hpp b/test cases/cmake/26 dependency fallback/subprojects/cmMod/cpp_pch.hpp new file mode 100644 index 0000000..aa7ceb3 --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/cmMod/cpp_pch.hpp @@ -0,0 +1,2 @@ +#include <vector> +#include <string> diff --git a/test cases/cmake/26 dependency fallback/subprojects/cmake_subp/CMakeLists.txt b/test cases/cmake/26 dependency fallback/subprojects/cmake_subp/CMakeLists.txt new file mode 100644 index 0000000..6443fca --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/cmake_subp/CMakeLists.txt @@ -0,0 +1,2 @@ +cmake_minimum_required(VERSION 3.5) +project(cmModDummy) diff --git a/test cases/cmake/26 dependency fallback/subprojects/force_cmake.wrap b/test cases/cmake/26 dependency fallback/subprojects/force_cmake.wrap new file mode 100644 index 0000000..b24754e --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/force_cmake.wrap @@ -0,0 +1,2 @@ +[wrap-file] +method=cmake diff --git a/test cases/cmake/26 dependency fallback/subprojects/force_cmake/CMakeLists.txt b/test cases/cmake/26 dependency fallback/subprojects/force_cmake/CMakeLists.txt new file mode 100644 index 0000000..497beb9 --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/force_cmake/CMakeLists.txt @@ -0,0 +1,2 @@ +cmake_minimum_required(VERSION 3.5) +project(cmModBoth) diff --git a/test cases/cmake/26 dependency fallback/subprojects/force_cmake/meson.build b/test cases/cmake/26 dependency fallback/subprojects/force_cmake/meson.build new file mode 100644 index 0000000..9264974 --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/force_cmake/meson.build @@ -0,0 +1,4 @@ +project('both methods') + +# Ensure the meson method is not used. +notfound() diff --git a/test cases/cmake/26 dependency fallback/subprojects/meson_method.wrap b/test cases/cmake/26 dependency fallback/subprojects/meson_method.wrap new file mode 100644 index 0000000..e52701e --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/meson_method.wrap @@ -0,0 +1,2 @@ +[wrap-file] +method=meson diff --git a/test cases/cmake/26 dependency fallback/subprojects/meson_subp/meson.build b/test cases/cmake/26 dependency fallback/subprojects/meson_subp/meson.build new file mode 100644 index 0000000..e4746ce --- /dev/null +++ b/test cases/cmake/26 dependency fallback/subprojects/meson_subp/meson.build @@ -0,0 +1 @@ +project('dummy') |