diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2023-04-24 22:42:09 -0400 |
---|---|---|
committer | Xavier Claessens <xclaesse@gmail.com> | 2023-09-22 15:50:26 -0400 |
commit | 49e7e3b9ccba7f9b0a135188f892b37d4e52cafc (patch) | |
tree | c2d7cce36113bc1fae778b3a55823672ee28e99f | |
parent | c0da998afa7466d58c12d8a54baf09d09ae3225e (diff) | |
download | meson-49e7e3b9ccba7f9b0a135188f892b37d4e52cafc.zip meson-49e7e3b9ccba7f9b0a135188f892b37d4e52cafc.tar.gz meson-49e7e3b9ccba7f9b0a135188f892b37d4e52cafc.tar.bz2 |
Allow to fallback to cmake subproject
The method can be overridden by setting the `method` key in the wrap
file and always defaults to 'meson'. cmake.subproject() is still needed
in case specific cmake options need to be passed.
This also makes it easier to extend to other methods in the future e.g.
cargo.
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') |