aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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')