diff options
-rw-r--r-- | mesonbuild/build.py | 41 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 90 | ||||
-rw-r--r-- | mesonbuild/interpreter/kwargs.py | 26 | ||||
-rw-r--r-- | mesonbuild/interpreter/type_checking.py | 46 | ||||
-rw-r--r-- | mesonbuild/modules/pkgconfig.py | 9 | ||||
-rw-r--r-- | mesonbuild/modules/qt.py | 16 | ||||
-rw-r--r-- | mesonbuild/modules/unstable_external_project.py | 2 |
7 files changed, 181 insertions, 49 deletions
diff --git a/mesonbuild/build.py b/mesonbuild/build.py index a7051ad..c2649ad 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -535,18 +535,27 @@ class Target(HoldableObject): def get_default_install_dir(self, env: environment.Environment) -> T.Tuple[str, str]: raise NotImplementedError + def get_custom_install_dir(self) -> T.List[T.Union[str, bool]]: + raise NotImplementedError + def get_install_dir(self, environment: environment.Environment) -> T.Tuple[T.Any, str, bool]: # Find the installation directory. default_install_dir, install_dir_name = self.get_default_install_dir(environment) outdirs = self.get_custom_install_dir() - if outdirs[0] is not None and outdirs[0] != default_install_dir and outdirs[0] is not True: + if outdirs and outdirs[0] != default_install_dir and outdirs[0] is not True: # Either the value is set to a non-default value, or is set to # False (which means we want this specific output out of many # outputs to not be installed). custom_install_dir = True else: custom_install_dir = False - outdirs[0] = default_install_dir + # if outdirs is empty we need to set to something, otherwise we set + # only the first value to the default + if outdirs: + outdirs[0] = default_install_dir + else: + outdirs = [default_install_dir] + return outdirs, install_dir_name, custom_install_dir def get_basename(self) -> str: @@ -641,6 +650,8 @@ class Target(HoldableObject): class BuildTarget(Target): known_kwargs = known_build_target_kwargs + install_dir: T.List[T.Union[str, bool]] + def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, sources: T.List['SourceOutputs'], objects, environment: environment.Environment, kwargs): super().__init__(name, subdir, subproject, True, for_machine) @@ -997,7 +1008,7 @@ class BuildTarget(Target): def get_default_install_dir(self, environment: environment.Environment) -> T.Tuple[str, str]: return environment.get_libdir(), '{libdir}' - def get_custom_install_dir(self): + def get_custom_install_dir(self) -> T.List[T.Union[str, bool]]: return self.install_dir def get_custom_install_mode(self) -> T.Optional['FileMode']: @@ -1081,7 +1092,7 @@ class BuildTarget(Target): 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', [None]), + self.install_dir = typeslistify(kwargs.get('install_dir', []), (str, bool)) self.install_mode = kwargs.get('install_mode', None) self.install_tag = stringlistify(kwargs.get('install_tag', [None])) @@ -2292,6 +2303,8 @@ class CustomTarget(Target, CommandBase): 'env', } + install_dir: T.List[T.Union[str, bool]] + def __init__(self, name: str, subdir: str, subproject: str, kwargs: T.Dict[str, T.Any], absolute_paths: bool = False, backend: T.Optional['Backend'] = None): self.typename = 'custom' @@ -2418,15 +2431,19 @@ class CustomTarget(Target, CommandBase): self.install_mode = kwargs.get('install_mode', None) # If only one tag is provided, assume all outputs have the same tag. # Otherwise, we must have as much tags as outputs. - self.install_tag = typeslistify(kwargs.get('install_tag', [None]), (str, bool)) - if len(self.install_tag) == 1: - self.install_tag = self.install_tag * len(self.outputs) - elif len(self.install_tag) != len(self.outputs): - m = f'Target {self.name!r} has {len(self.outputs)} outputs but {len(self.install_tag)} "install_tag"s were found.' + install_tag: T.List[T.Union[str, bool, None]] = typeslistify(kwargs.get('install_tag', []), (str, bool, type(None))) + if not install_tag: + self.install_tag = [None] * len(self.outputs) + elif len(install_tag) == 1: + self.install_tag = install_tag * len(self.outputs) + elif install_tag and len(install_tag) != len(self.outputs): + m = f'Target {self.name!r} has {len(self.outputs)} outputs but {len(install_tag)} "install_tag"s were found.' raise InvalidArguments(m) + else: + self.install_tag = install_tag else: self.install = False - self.install_dir = [None] + self.install_dir = [] self.install_mode = None self.install_tag = [] if kwargs.get('build_always') is not None and kwargs.get('build_always_stale') is not None: @@ -2459,7 +2476,7 @@ class CustomTarget(Target, CommandBase): def should_install(self) -> bool: return self.install - def get_custom_install_dir(self): + def get_custom_install_dir(self) -> T.List[T.Union[str, bool]]: return self.install_dir def get_custom_install_mode(self) -> T.Optional['FileMode']: @@ -2693,7 +2710,7 @@ class CustomTargetIndex(HoldableObject): def extract_all_objects_recurse(self) -> T.List[T.Union[str, 'ExtractedObjects']]: return self.target.extract_all_objects_recurse() - def get_custom_install_dir(self): + def get_custom_install_dir(self) -> T.List[T.Union[str, bool]]: return self.target.get_custom_install_dir() class ConfigurationData(HoldableObject): diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 78c8f95..c5997f2 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -52,11 +52,12 @@ from .interpreterobjects import ( from .type_checking import ( COMMAND_KW, DEPENDS_KW, + DEPEND_FILES_KW, DEPFILE_KW, ENV_KW, INSTALL_MODE_KW, LANGUAGE_KW, - NATIVE_KW, + NATIVE_KW, OVERRIDE_OPTIONS_KW, REQUIRED_KW, NoneType, in_set_validator, @@ -89,6 +90,18 @@ if T.TYPE_CHECKING: build.GeneratedList] +def _output_validator(outputs: T.List[str]) -> T.Optional[str]: + for i in outputs: + if i == '': + return 'Output must not be empty.' + elif i.strip() == '': + return 'Output must not consist only of whitespace.' + elif has_path_sep(i): + return f'Output {i!r} must not contain a path segment.' + + return None + + def stringifyUserArguments(args, quote=False): if isinstance(args, list): return '[%s]' % ', '.join([stringifyUserArguments(x, True) for x in args]) @@ -1626,20 +1639,71 @@ external dependencies (including libraries) must go to "dependencies".''') def func_subdir_done(self, node, args, kwargs): raise SubdirDoneRequest() - @FeatureNewKwargs('custom_target', '0.60.0', ['install_tag']) - @FeatureNewKwargs('custom_target', '0.57.0', ['env']) - @FeatureNewKwargs('custom_target', '0.48.0', ['console']) - @FeatureNewKwargs('custom_target', '0.47.0', ['install_mode', 'build_always_stale']) - @FeatureNewKwargs('custom_target', '0.40.0', ['build_by_default']) - @FeatureNewKwargs('custom_target', '0.59.0', ['feed']) - @permittedKwargs({'input', 'output', 'command', 'install', 'install_dir', 'install_mode', - 'build_always', 'capture', 'depends', 'depend_files', 'depfile', - 'build_by_default', 'build_always_stale', 'console', 'env', - 'feed', 'install_tag'}) @typed_pos_args('custom_target', optargs=[str]) - def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[T.Optional[str]], kwargs: 'TYPE_kwargs') -> build.CustomTarget: - if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']): + @typed_kwargs( + 'custom_target', + COMMAND_KW, + DEPEND_FILES_KW, + DEPENDS_KW, + DEPFILE_KW, + ENV_KW.evolve(since='0.57.0'), + INSTALL_MODE_KW.evolve(since='0.47.0'), + OVERRIDE_OPTIONS_KW, + KwargInfo('build_by_default', (bool, type(None)), since='0.40.0'), + KwargInfo('build_always', (bool, type(None)), deprecated='0.47.0'), + KwargInfo('build_always_stale', (bool, type(None)), since='0.47.0'), + KwargInfo('feed', bool, default=False, since='0.59.0'), + KwargInfo('capture', bool, default=False), + KwargInfo('console', bool, default=False, since='0.48.0'), + KwargInfo('install', bool, default=False), + KwargInfo('install_dir', ContainerTypeInfo(list, (str, bool)), listify=True, default=[]), + KwargInfo( + 'output', + ContainerTypeInfo(list, str, allow_empty=False), + listify=True, + required=True, + default=[], + validator=_output_validator, + ), + KwargInfo( + 'input', + ContainerTypeInfo(list, (str, mesonlib.File, ExternalProgram, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, build.ExtractedObjects, build.GeneratedList)), + listify=True, + default=[], + ), + KwargInfo('install_tag', ContainerTypeInfo(list, (str, bool)), listify=True, default=[], since='0.60.0'), + ) + def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[str], + kwargs: 'kwargs.CustomTarget') -> build.CustomTarget: + if kwargs['depfile'] and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']): FeatureNew.single_use('substitutions in custom_target depfile', '0.47.0', self.subproject) + + # Don't mutate the kwargs + kwargs = kwargs.copy() + + # Remap build_always to build_by_default and build_always_stale + if kwargs['build_always'] is not None and kwargs['build_always_stale'] is not None: + raise InterpreterException('CustomTarget: "build_always" and "build_always_stale" are mutually exclusive') + + if kwargs['build_by_default'] is None and kwargs['install']: + kwargs['build_by_default'] = True + + elif kwargs['build_always'] is not None: + if kwargs['build_by_default'] is None: + kwargs['build_by_default'] = kwargs['build_always'] + kwargs['build_always_stale'] = kwargs['build_by_default'] + + # Set this to None to satisfy process_kwargs + kwargs['build_always'] = None + + # These are are nullaable so that we can konw whether they're explicitly + # set or not. If they haven't been overwritten, set them to their true + # default + if kwargs['build_by_default'] is None: + kwargs['build_by_default'] = False + if kwargs['build_always_stale'] is None: + kwargs['build_always_stale'] = False + return self._func_custom_target_impl(node, args, kwargs) def _func_custom_target_impl(self, node, args, kwargs): diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 4281ee4..2229984 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -10,7 +10,7 @@ from typing_extensions import TypedDict, Literal from .. import build from .. import coredata -from ..mesonlib import MachineChoice, File, FileMode, FileOrString +from ..mesonlib import MachineChoice, File, FileMode, FileOrString, OptionKey from ..programs import ExternalProgram @@ -162,3 +162,27 @@ class RunTarget(TypedDict): command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, ExternalProgram, File]] depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]] env: build.EnvironmentVariables + + +class CustomTarget(TypedDict): + + build_always: bool + build_always_stale: bool + build_by_default: bool + capture: bool + command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, + build.CustomTargetIndex, ExternalProgram, File]] + consonle: bool + depend_files: T.List[FileOrString] + depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]] + depfile: T.Optional[str] + env: build.EnvironmentVariables + feed: bool + input: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, + build.ExtractedObjects, build.GeneratedList, ExternalProgram, File]] + install: bool + install_dir: T.List[T.Union[str, bool]] + install_mode: FileMode + install_tag: T.List[T.Union[str, bool]] + output: T.List[str] + override_options: T.Dict[OptionKey, str] diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index fb63fca..d8f1030 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -6,11 +6,11 @@ import typing as T from .. import compilers -from ..build import EnvironmentVariables, CustomTarget, BuildTarget +from ..build import EnvironmentVariables, CustomTarget, BuildTarget, CustomTargetIndex from ..coredata import UserFeatureOption from ..interpreterbase import TYPE_var from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo -from ..mesonlib import File, FileMode, MachineChoice, listify, has_path_sep +from ..mesonlib import File, FileMode, MachineChoice, listify, has_path_sep, OptionKey from ..programs import ExternalProgram # Helper definition for type checks that are `Optional[T]` @@ -158,13 +158,21 @@ def _env_validator(value: T.Union[EnvironmentVariables, T.List['TYPE_var'], T.Di return None -def _env_convertor(value: T.Union[EnvironmentVariables, T.List[str], T.Dict[str, str], str, None]) -> EnvironmentVariables: - def splitter(input: str) -> T.Tuple[str, str]: - a, b = input.split('=', 1) - return (a.strip(), b.strip()) - if isinstance(value, (str, list)): - return EnvironmentVariables(dict(splitter(v) for v in listify(value))) +def split_equal_string(input: str) -> T.Tuple[str, str]: + """Split a string in the form `x=y` + + This assumes that the string has already been validated to split properly. + """ + a, b = input.split('=', 1) + return (a, b) + + +def _env_convertor(value: T.Union[EnvironmentVariables, T.List[str], T.List[T.List[str]], T.Dict[str, str], str, None]) -> EnvironmentVariables: + if isinstance(value, str): + return EnvironmentVariables(dict([split_equal_string(value)])) + elif isinstance(value, list): + return EnvironmentVariables(dict(split_equal_string(v) for v in listify(value))) elif isinstance(value, dict): return EnvironmentVariables(value) elif value is None: @@ -199,11 +207,29 @@ DEPEND_FILES_KW: KwargInfo[T.List[T.Union[str, File]]] = KwargInfo( default=[], ) -COMMAND_KW: KwargInfo[T.List[T.Union[BuildTarget, CustomTarget, ExternalProgram, File]]] = KwargInfo( +COMMAND_KW: KwargInfo[T.List[T.Union[str, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram, File]]] = KwargInfo( 'command', # TODO: should accept CustomTargetIndex as well? - ContainerTypeInfo(list, (str, BuildTarget, CustomTarget, ExternalProgram, File), allow_empty=False), + ContainerTypeInfo(list, (str, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram, File), allow_empty=False), required=True, listify=True, default=[], ) + +def _override_options_convertor(raw: T.List[str]) -> T.Dict[OptionKey, str]: + output: T.Dict[OptionKey, str] = {} + for each in raw: + k, v = split_equal_string(each) + output[OptionKey.from_string(k)] = v + return output + + +OVERRIDE_OPTIONS_KW: KwargInfo[T.List[str]] = KwargInfo( + 'override_options', + ContainerTypeInfo(list, str), + listify=True, + default=[], + # Reusing the env validator is a littl overkill, but nicer than duplicating the code + validator=_env_validator, + convertor=_override_options_convertor, +) diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 7be4796..c9bec4a 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -381,7 +381,8 @@ class PkgConfigModule(ExtensionModule): if uninstalled: install_dir = os.path.dirname(state.backend.get_target_filename_abs(l)) else: - install_dir = l.get_custom_install_dir()[0] + _i = l.get_custom_install_dir() + install_dir = _i[0] if _i else None if install_dir is False: continue is_custom_target = isinstance(l, (build.CustomTarget, build.CustomTargetIndex)) @@ -471,9 +472,9 @@ class PkgConfigModule(ExtensionModule): raise mesonlib.MesonException('Pkgconfig_gen first positional argument must be a library object') default_name = mainlib.name default_description = state.project_name + ': ' + mainlib.name - install_dir = mainlib.get_custom_install_dir()[0] - if isinstance(install_dir, str): - default_install_dir = os.path.join(install_dir, 'pkgconfig') + install_dir = mainlib.get_custom_install_dir() + if install_dir and isinstance(install_dir[0], str): + default_install_dir = os.path.join(install_dir[0], 'pkgconfig') elif len(args) > 1: raise mesonlib.MesonException('Too many positional arguments passed to Pkgconfig_gen.') diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index 7dafad7..874bcb1 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -556,14 +556,14 @@ class QtBaseModule(ExtensionModule): else: outdir = state.subdir cmd = [self.tools['lrelease'], '@INPUT@', '-qm', '@OUTPUT@'] - lrelease_kwargs = {'output': '@BASENAME@.qm', - 'input': ts, - 'install': kwargs['install'], - 'install_tag': 'i18n', - 'build_by_default': kwargs['build_by_default'], - 'command': cmd} - if install_dir is not None: - lrelease_kwargs['install_dir'] = install_dir + lrelease_kwargs: T.Dict[str, T.Any] = { + 'output': '@BASENAME@.qm', + 'input': ts, + 'install': kwargs['install'], + 'install_dir': install_dir or [], + 'install_tag': 'i18n', + 'build_by_default': kwargs['build_by_default'], + 'command': cmd} lrelease_target = build.CustomTarget(f'qt{self.qt_version}-compile-{ts}', outdir, state.subproject, lrelease_kwargs) translations.append(lrelease_target) if qresource: diff --git a/mesonbuild/modules/unstable_external_project.py b/mesonbuild/modules/unstable_external_project.py index b8e9850..80cf41b 100644 --- a/mesonbuild/modules/unstable_external_project.py +++ b/mesonbuild/modules/unstable_external_project.py @@ -71,7 +71,7 @@ class ExternalProject(NewExtensionModule): self.targets = self._create_targets() - def _configure(self, state: ModuleState): + def _configure(self, state: ModuleState) -> None: if self.configure_command == 'waf': FeatureNew('Waf external project', '0.60.0').use(self.subproject) waf = state.find_program('waf') |