diff --git a/docs/markdown/snippets/subp-method.md b/docs/markdown/snippets/subp-method.md new file mode 100644 index 000000000000..161e90c9484f --- /dev/null +++ b/docs/markdown/snippets/subp-method.md @@ -0,0 +1,7 @@ +## Set [[dependency]] and [[subproject]] build method + +Subprojects can already define a build method in their `.wrap` file. It can +now also be done with [[dependency]]'s `fallback_method` and [[subproject]]'s +`method` keyword arguments. Supported values are `meson`, `cmake` and `cargo`. +It defaults to the `method` field in the wrap file if any, otherwise it defaults +to `meson`. diff --git a/docs/yaml/functions/dependency.yaml b/docs/yaml/functions/dependency.yaml index 9d4dfc6d06c9..a4bce64394bd 100644 --- a/docs/yaml/functions/dependency.yaml +++ b/docs/yaml/functions/dependency.yaml @@ -123,6 +123,15 @@ kwargs: If the value is an empty array it has the same effect as `allow_fallback: false`. + fallback_method: + type: str + since: 1.10.0 + description: | + Specifies which method should be used to configure the fallback if the + dependency is not found in the system. Supported values are `meson`, + `cmake` and `cargo`. It defaults to the `method` field in the wrap file + if any, otherwise it defaults to `meson`. + language: type: str since: 0.42.0 diff --git a/docs/yaml/functions/subproject.yaml b/docs/yaml/functions/subproject.yaml index 508837c2ba67..edfdb14e81f0 100644 --- a/docs/yaml/functions/subproject.yaml +++ b/docs/yaml/functions/subproject.yaml @@ -65,3 +65,11 @@ kwargs: default: true description: | Works just the same as in [[dependency]]. + + method: + type: str + since: 1.10.0 + description: | + Specifies which method should be used to configure the subproject. + Supported values are `meson`, `cmake` and `cargo`. It defaults to the + `method` field in the wrap file if any, otherwise it defaults to `meson`. diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index ba08e6d7c1d8..52c4816f8b5a 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -117,7 +117,7 @@ def interpret_package(self, manifest: Manifest, build: builder.Builder, subdir: member = ws.packages_to_member[manifest.package.name] pkg = ws.packages[member] else: - pkg, cached = self._fetch_package(manifest.package.name, manifest.package.api) + pkg, cached = self._fetch_package_from_manifest(manifest) if not cached: # This is an entry point, always enable the 'default' feature. # FIXME: We should have a Meson option similar to `cargo build --no-default-features` @@ -223,15 +223,15 @@ def _require_workspace_member(self, ws: WorkspaceState, member: str) -> PackageS ws.required_members.append(member) return pkg - def _fetch_package(self, package_name: str, api: str) -> T.Tuple[PackageState, bool]: + def _fetch_package(self, package_name: str, api: str) -> PackageState: key = PackageKey(package_name, api) pkg = self.packages.get(key) if pkg: - return pkg, True + return pkg meson_depname = _dependency_name(package_name, api) return self._fetch_package_from_subproject(package_name, meson_depname) - def _fetch_package_from_subproject(self, package_name: str, meson_depname: str) -> T.Tuple[PackageState, bool]: + def _fetch_package_from_subproject(self, package_name: str, meson_depname: str) -> PackageState: subp_name, _ = self.environment.wrap_resolver.find_dep_provider(meson_depname) if subp_name is None: # If Cargo.lock has a different version, this could be a resolution @@ -251,17 +251,28 @@ def _fetch_package_from_subproject(self, package_name: str, meson_depname: str) downloaded = \ subp_name in self.environment.wrap_resolver.wraps and \ self.environment.wrap_resolver.wraps[subp_name].type is not None + if isinstance(manifest, Workspace): ws = self._get_workspace(manifest, subdir) member = ws.packages_to_member[package_name] pkg = self._require_workspace_member(ws, member) - return pkg, False + return pkg + key = PackageKey(package_name, version.api(manifest.package.version)) + pkg = self.packages.get(key) + if pkg: + return pkg + pkg = PackageState(manifest, downloaded) + self.packages[key] = pkg + self._prepare_package(pkg) + return pkg + def _fetch_package_from_manifest(self, manifest: Manifest) -> T.Tuple[PackageState, bool]: + key = PackageKey(manifest.package.name, version.api(manifest.package.version)) pkg = self.packages.get(key) if pkg: return pkg, True - pkg = PackageState(manifest, downloaded) + pkg = PackageState(manifest, downloaded=False) self.packages[key] = pkg self._prepare_package(pkg) return pkg, False @@ -287,7 +298,7 @@ def _dep_package(self, pkg: PackageState, dep: Dependency) -> PackageState: dep_pkg = self._require_workspace_member(ws, dep_member) elif dep.git: _, _, directory = _parse_git_url(dep.git, dep.branch) - dep_pkg, _ = self._fetch_package_from_subproject(dep.package, directory) + dep_pkg = self._fetch_package_from_subproject(dep.package, directory) else: # From all available versions from Cargo.lock, pick the most recent # satisfying the constraints @@ -302,7 +313,7 @@ def _dep_package(self, pkg: PackageState, dep: Dependency) -> PackageState: else: if not dep.meson_version: raise MesonException(f'Cannot determine version of cargo package {dep.package}') - dep_pkg, _ = self._fetch_package(dep.package, dep.api) + dep_pkg = self._fetch_package(dep.package, dep.api) return dep_pkg def _load_manifest(self, subdir: str, workspace: T.Optional[Workspace] = None, member_path: str = '') -> T.Union[Manifest, Workspace]: diff --git a/mesonbuild/dependencies/detect.py b/mesonbuild/dependencies/detect.py index 4cdf16de58fc..5f9294dad927 100644 --- a/mesonbuild/dependencies/detect.py +++ b/mesonbuild/dependencies/detect.py @@ -45,20 +45,21 @@ def get_dep_identifier(name: str, kwargs: T.Dict[str, T.Any]) -> 'TV_DepID': nkwargs.update(kwargs) from ..interpreter import permitted_dependency_kwargs - assert len(permitted_dependency_kwargs) == 19, \ + assert len(permitted_dependency_kwargs) == 20, \ 'Extra kwargs have been added to dependency(), please review if it makes sense to handle it here' for key, value in nkwargs.items(): # 'version' is irrelevant for caching; the caller must check version matches # 'native' is handled above with `for_machine` # 'required' is irrelevant for caching; the caller handles it separately - # 'fallback' and 'allow_fallback' is not part of the cache because, - # once a dependency has been found through a fallback, it should - # be used for the rest of the Meson run. + # 'fallback', 'allow_fallback' and 'fallback_method' are not part of the + # cache because, once a dependency has been found through a fallback, + # it should be used for the rest of the Meson run. # 'default_options' is only used in fallback case # 'not_found_message' has no impact on the dependency lookup # 'include_type' is handled after the dependency lookup - if key in {'version', 'native', 'required', 'fallback', 'allow_fallback', 'default_options', - 'not_found_message', 'include_type'}: + if key in {'version', 'native', 'required', 'fallback', 'allow_fallback', + 'fallback_method', 'default_options', 'not_found_message', + 'include_type'}: continue # All keyword arguments are strings, ints, or lists (or lists of lists) if isinstance(value, list): diff --git a/mesonbuild/interpreter/dependencyfallbacks.py b/mesonbuild/interpreter/dependencyfallbacks.py index f415026aa05c..6acbf5bc7209 100644 --- a/mesonbuild/interpreter/dependencyfallbacks.py +++ b/mesonbuild/interpreter/dependencyfallbacks.py @@ -20,6 +20,7 @@ from .interpreter import Interpreter from ..interpreterbase import TYPE_nkwargs, TYPE_nvar from .interpreterobjects import SubprojectHolder + from ..wrap.wrap import Method class DependencyFallbacksHolder(MesonInterpreterObject): @@ -27,7 +28,8 @@ def __init__(self, interpreter: 'Interpreter', names: T.List[str], allow_fallback: T.Optional[bool] = None, - default_options: T.Optional[T.Dict[str, str]] = None) -> None: + default_options: T.Optional[T.Dict[str, str]] = None, + fallback_method: Method = 'meson') -> None: super().__init__(subproject=interpreter.subproject) self.interpreter = interpreter self.subproject = interpreter.subproject @@ -42,6 +44,7 @@ def __init__(self, self.names: T.List[str] = [] self.forcefallback: bool = False self.nofallback: bool = False + self.fallback_method = fallback_method for name in names: if not name: raise InterpreterException('dependency_fallbacks empty name \'\' is not allowed') @@ -132,7 +135,7 @@ def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs subp_name = self.subproject_name varname = self.subproject_varname func_kwargs.setdefault('version', []) - self.interpreter.do_subproject(subp_name, func_kwargs, forced_options=forced_options) + self.interpreter.do_subproject(subp_name, func_kwargs, forced_options=forced_options, force_method=self.fallback_method) 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 9bfc9be23831..f215b7cd9ef3 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -91,6 +91,7 @@ STATIC_LIB_KWS, VARIABLES_KW, TEST_KWS, + FALLBACK_METHOD_KW, NoneType, in_set_validator, env_convertor_with_method @@ -245,6 +246,7 @@ class InterpreterRuleRelaxation(Enum): 'components', 'default_options', 'fallback', + 'fallback_method', 'include_type', 'language', 'main', @@ -867,6 +869,7 @@ def func_option(self, nodes, args, kwargs): REQUIRED_KW, DEFAULT_OPTIONS.evolve(since='0.38.0'), KwargInfo('version', ContainerTypeInfo(list, str), default=[], listify=True), + FALLBACK_METHOD_KW.evolve(name='method'), ) def func_subproject(self, nodes: mparser.BaseNode, args: T.Tuple[str], kwargs: kwtypes.Subproject) -> SubprojectHolder: kw: kwtypes.DoSubproject = { @@ -876,7 +879,7 @@ def func_subproject(self, nodes: mparser.BaseNode, args: T.Tuple[str], kwargs: k 'options': None, 'cmake_options': [], } - return self.do_subproject(args[0], kw) + return self.do_subproject(args[0], kw, force_method=kwargs['method']) def disabled_subproject(self, subp_name: str, disabled_feature: T.Optional[str] = None, exception: T.Optional[Exception] = None) -> SubprojectHolder: @@ -1809,7 +1812,8 @@ def func_dependency(self, node: mparser.BaseNode, args: T.Tuple[T.List[str]], kw raise InvalidArguments('"allow_fallback" argument must be boolean') fallback = kwargs.get('fallback') default_options = kwargs.get('default_options') - df = DependencyFallbacksHolder(self, names, allow_fallback, default_options) + fallback_method = kwargs.get('fallback_method') + df = DependencyFallbacksHolder(self, names, allow_fallback, default_options, fallback_method) df.set_fallback(fallback) not_found_message = kwargs.get('not_found_message', '') if not isinstance(not_found_message, str): diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index e95d4734133e..bd5f1d25f16a 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -17,6 +17,7 @@ from ..options import OptionKey from ..modules.cmake import CMakeSubprojectOptions from ..programs import ExternalProgram +from ..wrap.wrap import Method as WrapMethod from .type_checking import PkgConfigDefineType, SourcesVarargsType TestArgs = T.Union[str, File, build.Target, ExternalProgram] @@ -315,6 +316,7 @@ class Subproject(ExtractRequired): default_options: T.Dict[OptionKey, options.ElementaryOptionValues] version: T.List[str] + method: T.Optional[WrapMethod] class DoSubproject(ExtractRequired): @@ -491,3 +493,4 @@ class FuncDeclareDependency(TypedDict): class FuncDependency(TypedDict): default_options: T.Dict[OptionKey, options.ElementaryOptionValues] + fallback_method: T.Optional[WrapMethod] diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 4f961bb608ff..c718ec480895 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -17,6 +17,7 @@ from ..mesonlib import (File, FileMode, MachineChoice, has_path_sep, listify, stringlistify, EnvironmentVariables) from ..programs import ExternalProgram +from ..wrap.wrap import METHODS # Helper definition for type checks that are `Optional[T]` NoneType: T.Type[None] = type(None) @@ -892,7 +893,15 @@ def _pkgconfig_define_convertor(x: T.List[str]) -> PkgConfigDefineType: convertor=_pkgconfig_define_convertor, ) +FALLBACK_METHOD_KW: KwargInfo = KwargInfo( + 'fallback_method', + (str, NoneType), + default=None, + validator=in_set_validator(METHODS), + since='1.10.0') + DEPENDENCY_KWS: T.List[KwargInfo] = [ DEFAULT_OPTIONS.evolve(since='0.38.0'), + FALLBACK_METHOD_KW, ] diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 8851b30a1f18..8bf3fd8fcbf6 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -56,6 +56,7 @@ WHITELIST_SUBDOMAIN = 'wrapdb.mesonbuild.com' ALL_TYPES = ['file', 'git', 'hg', 'svn', 'redirect'] +METHODS = {'meson', 'cmake', 'cargo'} if mesonlib.is_windows(): from ..programs import ExternalProgram diff --git a/test cases/rust/31 cargo fallback method/meson.build b/test cases/rust/31 cargo fallback method/meson.build new file mode 100644 index 000000000000..4ad7a5ead38e --- /dev/null +++ b/test cases/rust/31 cargo fallback method/meson.build @@ -0,0 +1,4 @@ +project('cargo fallback method') + +subproject('rust', method : 'cargo') +dependency('foo-0-rs', fallback_method : 'cargo') diff --git a/test cases/rust/31 cargo fallback method/subprojects/rust/Cargo.toml b/test cases/rust/31 cargo fallback method/subprojects/rust/Cargo.toml new file mode 100644 index 000000000000..5c001b707899 --- /dev/null +++ b/test cases/rust/31 cargo fallback method/subprojects/rust/Cargo.toml @@ -0,0 +1,2 @@ +[package] +name = "empty" diff --git a/test cases/rust/31 cargo fallback method/subprojects/rust2.wrap b/test cases/rust/31 cargo fallback method/subprojects/rust2.wrap new file mode 100644 index 000000000000..56be4eaaa606 --- /dev/null +++ b/test cases/rust/31 cargo fallback method/subprojects/rust2.wrap @@ -0,0 +1,4 @@ +[wrap-file] + +[provide] +dependency_names = foo-0-rs diff --git a/test cases/rust/31 cargo fallback method/subprojects/rust2/Cargo.toml b/test cases/rust/31 cargo fallback method/subprojects/rust2/Cargo.toml new file mode 100644 index 000000000000..c764b68e49fb --- /dev/null +++ b/test cases/rust/31 cargo fallback method/subprojects/rust2/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "foo" + +[lib] +path = "lib.rs" diff --git a/test cases/rust/31 cargo fallback method/subprojects/rust2/lib.rs b/test cases/rust/31 cargo fallback method/subprojects/rust2/lib.rs new file mode 100644 index 000000000000..1573adfa4f44 --- /dev/null +++ b/test cases/rust/31 cargo fallback method/subprojects/rust2/lib.rs @@ -0,0 +1,3 @@ +pub fn foo() -> i32 { + 0 +}