diff --git a/docs/markdown/Python-module.md b/docs/markdown/Python-module.md index 66081762d795..8fa99fba75e8 100644 --- a/docs/markdown/Python-module.md +++ b/docs/markdown/Python-module.md @@ -130,6 +130,9 @@ the addition of the following: Additionally, the following diverge from [[shared_module]]'s default behavior: +- `install_dir` may only be a string, boolean, or unset, but an `array` is not + allowed. + - `gnu_symbol_visibility`: if unset, it will default to `'hidden'` on versions of Python that support this (the python headers define `PyMODINIT_FUNC` has default visibility). diff --git a/docs/yaml/functions/_build_target_base.yaml b/docs/yaml/functions/_build_target_base.yaml index 1129533872c2..1c5ee8f919af 100644 --- a/docs/yaml/functions/_build_target_base.yaml +++ b/docs/yaml/functions/_build_target_base.yaml @@ -169,13 +169,17 @@ kwargs: description: When set to true, this executable should be installed. install_dir: - type: str + type: array[str | bool] description: | override install directory for this file. If the value is a relative path, it will be considered relative the `prefix` option. For example, if you want to install plugins into a subdir, you'd use something like this: `install_dir : get_option('libdir') / 'projectname-1.0'`. + This can be set to an array of values to control the the installation path + of build targets with multiple outputs. Currently, that means Vala. Setting + them to `true` means "use the default", and `false` means, don't install. + install_mode: type: array[str | int] since: 0.47.0 diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index dddcf67d8dc7..c51c49b365e3 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -1690,10 +1690,10 @@ def generate_target_install(self, d: InstallData) -> None: # Sanity-check the outputs and install_dirs num_outdirs, num_out = len(outdirs), len(t.get_outputs()) if num_outdirs not in {1, num_out}: - m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \ + m = 'Target {!r} has {} outputs: {!r}, but {} "install_dir"s were found: {!r}.\n' \ "Pass 'false' for outputs that should not be installed and 'true' for\n" \ 'using the default installation directory for an output.' - raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs)) + raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs, outdirs)) assert len(t.install_tag) == num_out install_mode = t.get_custom_install_mode() # because mypy gets confused type narrowing in lists diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 906f55289abd..f9f42914691b 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -22,7 +22,7 @@ from .mesonlib import ( HoldableObject, SecondLevelHolder, File, MesonException, MachineChoice, PerMachine, OrderedSet, listify, - extract_as_list, typeslistify, classify_unity_sources, + extract_as_list, classify_unity_sources, get_filenames_templates_dict, substitute_values, has_path_sep, is_parent_path, relpath, PerMachineDefaultable, MesonBugException, EnvironmentVariables, pickle_load, lazy_property, @@ -1292,8 +1292,7 @@ def process_kwargs(self, kwargs: BuildTargetKeywordArguments) -> None: self.add_deps(deplist) # If an item in this list is False, the output corresponding to # the list index of that item will not be installed - self.install_dir = typeslistify(kwargs.get('install_dir', []), - (str, bool)) + self.install_dir = kwargs.get('install_dir', []) self.install_mode = kwargs.get('install_mode', None) self.install_tag: T.List[T.Optional[str]] = kwargs.get('install_tag') or [None] self.extra_files = kwargs.get('extra_files', []) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 1e206422fece..1049214ca677 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -366,6 +366,7 @@ class _BuildTarget(_BaseBuildTarget): d_import_dirs: T.List[T.Union[str, build.IncludeDirs]] d_module_versions: T.List[T.Union[str, int]] d_unittest: bool + install_dir: T.List[T.Union[str, bool]] rust_crate_type: T.Optional[Literal['bin', 'lib', 'rlib', 'dylib', 'cdylib', 'staticlib', 'proc-macro']] rust_dependency_map: T.Dict[str, str] swift_interoperability_mode: Literal['c', 'cpp'] diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 743046caf208..c2e082e4b05c 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -615,6 +615,12 @@ def _extra_files_validator(args: T.List[T.Union[File, str]]) -> T.Optional[str]: ), INSTALL_MODE_KW, INSTALL_TAG_KW, + KwargInfo( + 'install_dir', + ContainerTypeInfo(list, (str, bool)), + default=[], + listify=True, + ), KwargInfo('implicit_include_directories', bool, default=True, since='0.42.0'), NATIVE_KW, KwargInfo('resources', ContainerTypeInfo(list, str), default=[], listify=True), diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 99602c05ad4c..78798e5c0f7f 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -51,6 +51,8 @@ class FindInstallationKw(ExtractRequired): class ExtensionModuleKw(SharedModuleKw): + # Yes, these are different between SharedModule and ExtensionModule + install_dir: T.Union[str, bool, None] # type: ignore[misc] subdir: NotRequired[T.Optional[str]] MaybePythonProg = T.Union[NonExistingExternalProgram, 'PythonExternalProgram'] @@ -60,7 +62,8 @@ class ExtensionModuleKw(SharedModuleKw): mod_kwargs.update(known_shmod_kwargs) mod_kwargs -= {'name_prefix', 'name_suffix'} -_MOD_KWARGS = [k for k in SHARED_MOD_KWS if k.name not in {'name_prefix', 'name_suffix'}] +_MOD_KWARGS = [k for k in SHARED_MOD_KWS if + k.name not in {'name_prefix', 'name_suffix', 'install_dir'}] class PythonExternalProgram(BasicPythonExternalProgram): @@ -139,19 +142,28 @@ def __init__(self, python: 'PythonExternalProgram', interpreter: 'Interpreter'): @permittedKwargs(mod_kwargs) @typed_pos_args('python.extension_module', str, varargs=(str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList, StructuredSources, ExtractedObjects, BuildTarget)) - @typed_kwargs('python.extension_module', *_MOD_KWARGS, _DEFAULTABLE_SUBDIR_KW, _LIMITED_API_KW, allow_unknown=True) + @typed_kwargs( + 'python.extension_module', + *_MOD_KWARGS, + _DEFAULTABLE_SUBDIR_KW, + _LIMITED_API_KW, + KwargInfo('install_dir', (str, bool, NoneType)), + allow_unknown=True + ) @InterpreterObject.method('extension_module') def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]], kwargs: ExtensionModuleKw) -> 'SharedModule': - if 'install_dir' in kwargs: + if kwargs['install_dir'] is not None: if kwargs['subdir'] is not None: raise InvalidArguments('"subdir" and "install_dir" are mutually exclusive') + # the build_target() method now expects this to be correct. + kwargs['install_dir'] = [kwargs['install_dir']] else: # We want to remove 'subdir', but it may be None and we want to replace it with '' # It must be done this way since we don't allow both `install_dir` # and `subdir` to be set at the same time subdir = kwargs.pop('subdir') or '' - kwargs['install_dir'] = self._get_install_dir_impl(False, subdir) + kwargs['install_dir'] = [self._get_install_dir_impl(False, subdir)] target_suffix = self.suffix diff --git a/test cases/failing/40 custom target outputs not matching install_dirs/test.json b/test cases/failing/40 custom target outputs not matching install_dirs/test.json index f9e2ba781e64..e6ea59770762 100644 --- a/test cases/failing/40 custom target outputs not matching install_dirs/test.json +++ b/test cases/failing/40 custom target outputs not matching install_dirs/test.json @@ -27,7 +27,8 @@ ], "stdout": [ { - "line": "ERROR: Target 'too-few-install-dirs' has 3 outputs: ['toofew.h', 'toofew.c', 'toofew.sh'], but only 2 \"install_dir\"s were found." + "line": "ERROR: Target 'too-few-install-dirs' has 3 outputs: \\['toofew.h', 'toofew.c', 'toofew.sh'\\], but 2 \"install_dir\"s were found: \\['([a-zA-Z]:)?/usr/include', False\\]\\.", + "match": "re" } ] }