aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDylan Baker <dylan@pnwbakers.com>2021-08-10 20:07:39 -0700
committerDaniel Mensinger <daniel@mensinger-ka.de>2021-09-30 21:01:38 +0200
commitc3c30d4b060239654c9b848092692ab346ebed9d (patch)
tree52b2ebd5e5c788fb529dac1717051093311ca255
parentbb706231bd3bfd8983f1a5df24111efe1ad0734d (diff)
downloadmeson-c3c30d4b060239654c9b848092692ab346ebed9d.zip
meson-c3c30d4b060239654c9b848092692ab346ebed9d.tar.gz
meson-c3c30d4b060239654c9b848092692ab346ebed9d.tar.bz2
interpreter: Use typed_kwargs for func_custom_target
This does not convert the build side, or remove any of the checking it does. We still need that for other callers of custom target. What we'll do for those is add an internal interface that defaults things, then we'll be able to have those callers do their own validation, and the CustomTarget validation machinary can be removed. Fixes #9096
-rw-r--r--mesonbuild/build.py41
-rw-r--r--mesonbuild/interpreter/interpreter.py90
-rw-r--r--mesonbuild/interpreter/kwargs.py26
-rw-r--r--mesonbuild/interpreter/type_checking.py46
-rw-r--r--mesonbuild/modules/pkgconfig.py9
-rw-r--r--mesonbuild/modules/qt.py16
-rw-r--r--mesonbuild/modules/unstable_external_project.py2
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')