aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mesonbuild/backend/backends.py10
-rw-r--r--mesonbuild/build.py14
-rw-r--r--mesonbuild/interpreter/interpreter.py241
-rw-r--r--mesonbuild/interpreter/kwargs.py34
-rw-r--r--mesonbuild/interpreterbase/decorators.py41
-rw-r--r--mesonbuild/mesonlib/universal.py11
-rw-r--r--mesonbuild/minstall.py9
-rwxr-xr-xrun_unittests.py12
-rw-r--r--test cases/failing/30 invalid man extension/test.json2
-rw-r--r--test cases/failing/31 no man extension/test.json2
-rw-r--r--test cases/failing/95 custom target install data/test.json2
11 files changed, 256 insertions, 122 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index 2239fdd..4e470cf 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -42,9 +42,6 @@ if T.TYPE_CHECKING:
from ..interpreter import Interpreter, Test
from ..mesonlib import FileMode
- InstallType = T.List[T.Tuple[str, str, T.Optional['FileMode']]]
- InstallSubdirsType = T.List[T.Tuple[str, str, T.Optional['FileMode'], T.Tuple[T.Set[str], T.Set[str]]]]
-
# Languages that can mix with C or C++ but don't support unity builds yet
# because the syntax we use for unity builds is specific to C/++/ObjC/++.
# Assembly files cannot be unitified and neither can LLVM IR files
@@ -1451,7 +1448,12 @@ class Backend:
for h in headers:
outdir = h.get_custom_install_dir()
if outdir is None:
- outdir = os.path.join(incroot, h.get_install_subdir())
+ subdir = h.get_install_subdir()
+ if subdir is None:
+ outdir = incroot
+ else:
+ outdir = os.path.join(incroot, subdir)
+
for f in h.get_sources():
if not isinstance(f, File):
raise MesonException(f'Invalid header type {f!r} can\'t be installed')
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 707f5d2..f88bf74 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -135,7 +135,7 @@ class DependencyOverride(HoldableObject):
class Headers(HoldableObject):
def __init__(self, sources: T.List[File], install_subdir: T.Optional[str],
- install_dir: T.Optional[str], install_mode: T.Optional['FileMode'],
+ install_dir: T.Optional[str], install_mode: 'FileMode',
subproject: str):
self.sources = sources
self.install_subdir = install_subdir
@@ -149,7 +149,7 @@ class Headers(HoldableObject):
def set_install_subdir(self, subdir: str) -> None:
self.install_subdir = subdir
- def get_install_subdir(self) -> str:
+ def get_install_subdir(self) -> T.Optional[str]:
return self.install_subdir
def get_sources(self) -> T.List[File]:
@@ -158,14 +158,14 @@ class Headers(HoldableObject):
def get_custom_install_dir(self) -> T.Optional[str]:
return self.custom_install_dir
- def get_custom_install_mode(self) -> T.Optional['FileMode']:
+ def get_custom_install_mode(self) -> 'FileMode':
return self.custom_install_mode
class Man(HoldableObject):
def __init__(self, sources: T.List[File], install_dir: T.Optional[str],
- install_mode: T.Optional['FileMode'], subproject: str,
+ install_mode: 'FileMode', subproject: str,
locale: T.Optional[str]):
self.sources = sources
self.custom_install_dir = install_dir
@@ -176,7 +176,7 @@ class Man(HoldableObject):
def get_custom_install_dir(self) -> T.Optional[str]:
return self.custom_install_dir
- def get_custom_install_mode(self) -> T.Optional['FileMode']:
+ def get_custom_install_mode(self) -> 'FileMode':
return self.custom_install_mode
def get_sources(self) -> T.List['File']:
@@ -186,7 +186,7 @@ class Man(HoldableObject):
class InstallDir(HoldableObject):
def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str,
- install_mode: T.Optional['FileMode'],
+ install_mode: 'FileMode',
exclude: T.Tuple[T.Set[str], T.Set[str]],
strip_directory: bool, subproject: str,
from_source_dir: bool = True):
@@ -2623,7 +2623,7 @@ class ConfigurationData(HoldableObject):
# during install.
class Data(HoldableObject):
def __init__(self, sources: T.List[File], install_dir: str,
- install_mode: T.Optional['FileMode'], subproject: str,
+ install_mode: 'FileMode', subproject: str,
rename: T.List[str] = None):
self.sources = sources
self.install_dir = install_dir
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 2faec4e..f55e58a 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -85,6 +85,61 @@ def _language_validator(l: T.List[str]) -> T.Optional[str]:
return None
+def _install_mode_validator(mode: T.List[T.Union[str, bool, int]]) -> T.Optional[str]:
+ """Validate the `install_mode` keyword argument.
+
+ This is a rather odd thing, it's a scalar, or an array of 3 values in the form:
+ [(str | False), (str | int | False) = False, (str | int | False) = False]
+ Where the second and third arguments are not required, and are considered to
+ default to False.
+ """
+ if not mode:
+ return None
+ if True in mode:
+ return 'can only be a string or false, not true'
+ if len(mode) > 3:
+ return 'may have at most 3 elements'
+
+ perms = mode[0]
+ if not isinstance(perms, (str, bool)):
+ return 'permissions part must be a string or false'
+
+ if isinstance(perms, str):
+ if not len(perms) == 9:
+ return (f'permissions string must be exactly 9 characters, got "{len(perms)}" '
+ 'in the form rwxr-xr-x')
+ for i in [0, 3, 6]:
+ if perms[i] not in {'-', 'r'}:
+ return f'bit {i} must be "-" or "r", not {perms[i]}'
+ for i in [1, 4, 7]:
+ if perms[i] not in {'-', 'w'}:
+ return f'bit {i} must be "-" or "w", not {perms[i]}'
+ for i in [2, 5]:
+ if perms[i] not in {'-', 'x', 's', 'S'}:
+ return f'bit {i} must be "-", "s", "S", or "x", not {perms[i]}'
+ if perms[8] not in {'-', 'x', 't', 'T'}:
+ return f'bit 8 must be "-", "t", "T", or "x", not {perms[8]}'
+
+ if len(mode) >= 2 and not isinstance(mode[1], (int, str, bool)):
+ return 'second componenent must be a string, number, or False if provided'
+ if len(mode) >= 3 and not isinstance(mode[2], (int, str, bool)):
+ return 'third componenent must be a string, number, or False if provided'
+
+ return None
+
+
+def _install_mode_convertor(mode: T.Optional[T.List[T.Union[str, bool, int]]]) -> FileMode:
+ """Convert the DSL form of the `install_mode` keyword arugment to `FileMode`
+
+ This is not required, and if not required returns None
+
+ TODO: It's not clear to me why this needs to be None and not just return an
+ emtpy FileMode.
+ """
+ # this has already been validated by the validator
+ return FileMode(*[m if isinstance(m, str) else None for m in mode])
+
+
_NATIVE_KW = KwargInfo(
'native', bool,
default=False,
@@ -97,6 +152,15 @@ _LANGUAGE_KW = KwargInfo(
validator=_language_validator,
convertor=lambda x: [i.lower() for i in x])
+_INSTALL_MODE_KW = KwargInfo(
+ 'install_mode',
+ ContainerTypeInfo(list, (str, bool, int)),
+ listify=True,
+ default=[],
+ validator=_install_mode_validator,
+ convertor=_install_mode_convertor,
+)
+
def stringifyUserArguments(args, quote=False):
if isinstance(args, list):
@@ -1817,46 +1881,50 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
self.build.benchmarks.append(t)
mlog.debug('Adding benchmark', mlog.bold(t.name, True))
- @FeatureNewKwargs('install_headers', '0.47.0', ['install_mode'])
- @permittedKwargs({'install_dir', 'install_mode', 'subdir'})
- def func_install_headers(self, node, args, kwargs):
- source_files = self.source_strings_to_files(args)
- install_mode = self._get_kwarg_install_mode(kwargs)
-
- install_subdir = kwargs.get('subdir', '')
- if not isinstance(install_subdir, str):
- raise InterpreterException('subdir keyword argument must be a string')
- elif os.path.isabs(install_subdir):
+ @typed_pos_args('install_headers', varargs=(str, mesonlib.File), min_varargs=1)
+ @typed_kwargs(
+ 'install_headers',
+ KwargInfo('install_dir', (str, None)),
+ KwargInfo('subdir', (str, None)),
+ _INSTALL_MODE_KW.evolve(since='0.47.0'),
+ )
+ def func_install_headers(self, node: mparser.BaseNode,
+ args: T.Tuple[T.List['mesonlib.FileOrString']],
+ kwargs: 'kwargs.FuncInstallHeaders') -> build.Headers:
+ source_files = self.source_strings_to_files(args[0])
+ install_subdir = kwargs['subdir']
+ if install_subdir is not None and os.path.isabs(install_subdir):
mlog.deprecation('Subdir keyword must not be an absolute path. This will be a hard error in the next release.')
- install_dir = kwargs.get('install_dir', None)
- if install_dir is not None and not isinstance(install_dir, str):
- raise InterpreterException('install_dir keyword argument must be a string if provided')
-
- h = build.Headers(source_files, install_subdir, install_dir, install_mode, self.subproject)
+ h = build.Headers(source_files, install_subdir, kwargs['install_dir'],
+ kwargs['install_mode'], self.subproject)
self.build.headers.append(h)
return h
- @FeatureNewKwargs('install_man', '0.47.0', ['install_mode'])
- @FeatureNewKwargs('install_man', '0.58.0', ['locale'])
- @permittedKwargs({'install_dir', 'install_mode', 'locale'})
- def func_install_man(self, node, args, kwargs):
- sources = self.source_strings_to_files(args)
+ @typed_pos_args('install_man', varargs=(str, mesonlib.File), min_varargs=1)
+ @typed_kwargs(
+ 'install_man',
+ KwargInfo('install_dir', (str, None)),
+ KwargInfo('locale', (str, None), since='0.58.0'),
+ _INSTALL_MODE_KW.evolve(since='0.47.0')
+ )
+ def func_install_man(self, node: mparser.BaseNode,
+ args: T.Tuple[T.List['mesonlib.FileOrString']],
+ kwargs: 'kwargs.FuncInstallMan') -> build.Man:
+ # We just need to narrow this, because the input is limited to files and
+ # Strings as inputs, so only Files will be returned
+ sources = self.source_strings_to_files(args[0])
for s in sources:
try:
- num = int(s.split('.')[-1])
+ num = int(s.rsplit('.', 1)[-1])
except (IndexError, ValueError):
num = 0
- if num < 1 or num > 8:
- raise InvalidArguments('Man file must have a file extension of a number between 1 and 8')
- custom_install_mode = self._get_kwarg_install_mode(kwargs)
- custom_install_dir = kwargs.get('install_dir', None)
- locale = kwargs.get('locale')
- if custom_install_dir is not None and not isinstance(custom_install_dir, str):
- raise InterpreterException('install_dir must be a string.')
-
- m = build.Man(sources, custom_install_dir, custom_install_mode, self.subproject, locale)
+ if not 1 <= num <= 9:
+ raise InvalidArguments('Man file must have a file extension of a number between 1 and 9')
+
+ m = build.Man(sources, kwargs['install_dir'], kwargs['install_mode'],
+ self.subproject, kwargs['locale'])
self.build.man.append(m)
return m
@@ -1929,82 +1997,55 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
'permissions arg to be a string or false')
return FileMode(*install_mode)
- @FeatureNewKwargs('install_data', '0.46.0', ['rename'])
- @FeatureNewKwargs('install_data', '0.38.0', ['install_mode'])
- @permittedKwargs({'install_dir', 'install_mode', 'rename', 'sources'})
- def func_install_data(self, node, args: T.List, kwargs: T.Dict[str, T.Any]):
- kwsource = mesonlib.stringlistify(kwargs.get('sources', []))
- raw_sources = args + kwsource
- sources: T.List[mesonlib.File] = []
- source_strings: T.List[str] = []
- for s in raw_sources:
- if isinstance(s, mesonlib.File):
- sources.append(s)
- elif isinstance(s, str):
- source_strings.append(s)
- else:
- raise InvalidArguments('Argument must be string or file.')
- sources += self.source_strings_to_files(source_strings)
- install_dir: T.Optional[str] = kwargs.get('install_dir', None)
- if install_dir is not None and not isinstance(install_dir, str):
- raise InvalidArguments('Keyword argument install_dir not a string.')
- install_mode = self._get_kwarg_install_mode(kwargs)
- rename: T.Optional[T.List[str]] = kwargs.get('rename', None)
- if rename is not None:
- rename = mesonlib.stringlistify(rename)
+ @typed_pos_args('install_data', varargs=(str, mesonlib.File))
+ @typed_kwargs(
+ 'install_data',
+ KwargInfo('install_dir', str),
+ KwargInfo('sources', ContainerTypeInfo(list, (str, mesonlib.File)), listify=True, default=[]),
+ KwargInfo('rename', ContainerTypeInfo(list, str), default=[], listify=True, since='0.46.0'),
+ _INSTALL_MODE_KW.evolve(since='0.38.0'),
+ )
+ def func_install_data(self, node: mparser.BaseNode,
+ args: T.Tuple[T.List['mesonlib.FileOrString']],
+ kwargs: 'kwargs.FuncInstallData') -> build.Data:
+ sources = self.source_strings_to_files(args[0] + kwargs['sources'])
+ rename = kwargs['rename'] or None
+ if rename:
if len(rename) != len(sources):
raise InvalidArguments(
'"rename" and "sources" argument lists must be the same length if "rename" is given. '
f'Rename has {len(rename)} elements and sources has {len(sources)}.')
- data = build.Data(sources, install_dir, install_mode, self.subproject, rename)
+ data = build.Data(
+ sources, kwargs['install_dir'], kwargs['install_mode'],
+ self.subproject, rename)
self.build.data.append(data)
return data
- @FeatureNewKwargs('install_subdir', '0.42.0', ['exclude_files', 'exclude_directories'])
- @FeatureNewKwargs('install_subdir', '0.38.0', ['install_mode'])
- @permittedKwargs({'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'})
- @stringArgs
- def func_install_subdir(self, node, args, kwargs):
- if len(args) != 1:
- raise InvalidArguments('Install_subdir requires exactly one argument.')
- subdir: str = args[0]
- if not isinstance(subdir, str):
- raise InvalidArguments('install_subdir positional argument 1 must be a string.')
- if 'install_dir' not in kwargs:
- raise InvalidArguments('Missing keyword argument install_dir')
- install_dir: str = kwargs['install_dir']
- if not isinstance(install_dir, str):
- raise InvalidArguments('Keyword argument install_dir not a string.')
- if 'strip_directory' in kwargs:
- strip_directory: bool = kwargs['strip_directory']
- if not isinstance(strip_directory, bool):
- raise InterpreterException('"strip_directory" keyword must be a boolean.')
- else:
- strip_directory = False
- if 'exclude_files' in kwargs:
- exclude: T.List[str] = extract_as_list(kwargs, 'exclude_files')
- for f in exclude:
- if not isinstance(f, str):
- raise InvalidArguments('Exclude argument not a string.')
- elif os.path.isabs(f):
- raise InvalidArguments('Exclude argument cannot be absolute.')
- exclude_files: T.Set[str] = set(exclude)
- else:
- exclude_files = set()
- if 'exclude_directories' in kwargs:
- exclude: T.List[str] = extract_as_list(kwargs, 'exclude_directories')
- for d in exclude:
- if not isinstance(d, str):
- raise InvalidArguments('Exclude argument not a string.')
- elif os.path.isabs(d):
- raise InvalidArguments('Exclude argument cannot be absolute.')
- exclude_directories: T.Set[str] = set(exclude)
- else:
- exclude_directories = set()
- exclude = (exclude_files, exclude_directories)
- install_mode = self._get_kwarg_install_mode(kwargs)
- idir = build.InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory, self.subproject)
+ @typed_pos_args('install_subdir', str)
+ @typed_kwargs(
+ 'install_subdir',
+ KwargInfo('install_dir', str, required=True),
+ KwargInfo('strip_directory', bool, default=False),
+ KwargInfo('exclude_files', ContainerTypeInfo(list, str),
+ default=[], listify=True, since='0.42.0',
+ validator=lambda x: 'cannot be absolute' if any(os.path.isabs(d) for d in x) else None),
+ KwargInfo('exclude_directories', ContainerTypeInfo(list, str),
+ default=[], listify=True, since='0.42.0',
+ validator=lambda x: 'cannot be absolute' if any(os.path.isabs(d) for d in x) else None),
+ _INSTALL_MODE_KW.evolve(since='0.38.0'),
+ )
+ def func_install_subdir(self, node: mparser.BaseNode, args: T.Tuple[str],
+ kwargs: 'kwargs.FuncInstallSubdir') -> build.InstallDir:
+ exclude = (set(kwargs['exclude_files']), set(kwargs['exclude_directories']))
+ idir = build.InstallDir(
+ self.subdir,
+ args[0],
+ kwargs['install_dir'],
+ kwargs['install_mode'],
+ exclude,
+ kwargs['strip_directory'],
+ self.subproject)
self.build.install_dirs.append(idir)
return idir
@@ -2479,6 +2520,10 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey
if project_root / self.subproject_dir in norm.parents:
raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {norm.name} from a nested subproject.')
+
+ @T.overload
+ def source_strings_to_files(self, sources: T.List['mesonlib.FileOrString']) -> T.List['mesonlib.File']: ...
+
def source_strings_to_files(self, sources: T.List['SourceInputs']) -> T.List['SourceOutputs']:
"""Lower inputs to a list of Targets and Files, replacing any strings.
diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py
index 1cc2082..3c3ecf6 100644
--- a/mesonbuild/interpreter/kwargs.py
+++ b/mesonbuild/interpreter/kwargs.py
@@ -4,13 +4,13 @@
"""Keyword Argument type annotations."""
-from mesonbuild import coredata
import typing as T
from typing_extensions import TypedDict, Literal
from .. import build
-from ..mesonlib import MachineChoice, File
+from .. import coredata
+from ..mesonlib import MachineChoice, File, FileMode, FileOrString
from .interpreterobjects import EnvironmentVariablesObject
@@ -102,3 +102,33 @@ class DependencyMethodPartialDependency(TypedDict):
class BuildTargeMethodExtractAllObjects(TypedDict):
recursive: bool
+
+class FuncInstallSubdir(TypedDict):
+
+ install_dir: str
+ strip_directory: bool
+ exclude_files: T.List[str]
+ exclude_directories: T.List[str]
+ install_mode: FileMode
+
+
+class FuncInstallData(TypedDict):
+
+ install_dir: str
+ sources: T.List[FileOrString]
+ rename: T.List[str]
+ install_mode: FileMode
+
+
+class FuncInstallHeaders(TypedDict):
+
+ install_dir: T.Optional[str]
+ install_mode: FileMode
+ subdir: T.Optional[str]
+
+
+class FuncInstallMan(TypedDict):
+
+ install_dir: T.Optional[str]
+ install_mode: FileMode
+ locale: T.Optional[str]
diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py
index a011b66..b05df5b 100644
--- a/mesonbuild/interpreterbase/decorators.py
+++ b/mesonbuild/interpreterbase/decorators.py
@@ -272,6 +272,11 @@ class ContainerTypeInfo:
_T = T.TypeVar('_T')
+class _NULL_T:
+ """Special null type for evolution, this is an implementation detail."""
+
+
+_NULL = _NULL_T()
class KwargInfo(T.Generic[_T]):
@@ -329,6 +334,42 @@ class KwargInfo(T.Generic[_T]):
self.convertor = convertor
self.not_set_warning = not_set_warning
+ def evolve(self, *,
+ required: T.Union[bool, _NULL_T] = _NULL,
+ listify: T.Union[bool, _NULL_T] = _NULL,
+ default: T.Union[_T, None, _NULL_T] = _NULL,
+ since: T.Union[str, None, _NULL_T] = _NULL,
+ since_values: T.Union[T.Dict[str, str], None, _NULL_T] = _NULL,
+ deprecated: T.Union[str, None, _NULL_T] = _NULL,
+ deprecated_values: T.Union[T.Dict[str, str], None, _NULL_T] = _NULL,
+ validator: T.Union[T.Callable[[_T], T.Optional[str]], None, _NULL_T] = _NULL,
+ convertor: T.Union[T.Callable[[_T], TYPE_var], None, _NULL_T] = _NULL) -> 'KwargInfo':
+ """Create a shallow copy of this KwargInfo, with modifications.
+
+ This allows us to create a new copy of a KwargInfo with modifications.
+ This allows us to use a shared kwarg that implements complex logic, but
+ has slight differences in usage, such as being added to different
+ functions in different versions of Meson.
+
+ The use the _NULL special value here allows us to pass None, which has
+ meaning in many of these cases. _NULL itself is never stored, always
+ being replaced by either the copy in self, or the provided new version.
+ """
+ return type(self)(
+ self.name,
+ self.types,
+ listify=listify if not isinstance(listify, _NULL_T) else self.listify,
+ required=required if not isinstance(required, _NULL_T) else self.required,
+ default=default if not isinstance(default, _NULL_T) else self.default,
+ since=since if not isinstance(since, _NULL_T) else self.since,
+ since_values=since_values if not isinstance(since_values, _NULL_T) else self.since_values,
+ deprecated=deprecated if not isinstance(deprecated, _NULL_T) else self.deprecated,
+ deprecated_values=deprecated_values if not isinstance(deprecated_values, _NULL_T) else self.deprecated_values,
+ validator=validator if not isinstance(validator, _NULL_T) else self.validator,
+ convertor=convertor if not isinstance(convertor, _NULL_T) else self.convertor,
+ )
+
+
def typed_kwargs(name: str, *types: KwargInfo) -> T.Callable[..., T.Any]:
"""Decorator for type checking keyword arguments.
diff --git a/mesonbuild/mesonlib/universal.py b/mesonbuild/mesonlib/universal.py
index 3714ecd..160cc37 100644
--- a/mesonbuild/mesonlib/universal.py
+++ b/mesonbuild/mesonlib/universal.py
@@ -298,8 +298,8 @@ class FileMode:
'[r-][w-][xsS-]' # Group perms
'[r-][w-][xtT-]') # Others perms
- def __init__(self, perms: T.Optional[str] = None, owner: T.Optional[str] = None,
- group: T.Optional[str] = None):
+ def __init__(self, perms: T.Optional[str] = None, owner: T.Union[str, int, None] = None,
+ group: T.Union[str, int, None] = None):
self.perms_s = perms
self.perms = self.perms_s_to_bits(perms)
self.owner = owner
@@ -422,8 +422,11 @@ class File(HoldableObject):
def endswith(self, ending: str) -> bool:
return self.fname.endswith(ending)
- def split(self, s: str) -> T.List[str]:
- return self.fname.split(s)
+ def split(self, s: str, maxsplit: int = -1) -> T.List[str]:
+ return self.fname.split(s, maxsplit=maxsplit)
+
+ def rsplit(self, s: str, maxsplit: int = -1) -> T.List[str]:
+ return self.fname.rsplit(s, maxsplit=maxsplit)
def __eq__(self, other: object) -> bool:
if not isinstance(other, File):
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
index e14a047..7011c42 100644
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -64,7 +64,7 @@ build definitions so that it will not break when the change happens.'''
selinux_updates: T.List[str] = []
-def add_arguments(parser: argparse.Namespace) -> None:
+def add_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument('-C', default='.', dest='wd',
help='directory to cd into before running')
parser.add_argument('--profile-self', action='store_true', dest='profile',
@@ -130,7 +130,8 @@ def append_to_log(lf: T.TextIO, line: str) -> None:
lf.flush()
-def set_chown(path: str, user: T.Optional[str] = None, group: T.Optional[str] = None,
+def set_chown(path: str, user: T.Union[str, int, None] = None,
+ group: T.Union[str, int, None] = None,
dir_fd: T.Optional[int] = None, follow_symlinks: bool = True) -> None:
# shutil.chown will call os.chown without passing all the parameters
# and particularly follow_symlinks, thus we replace it temporary
@@ -182,12 +183,12 @@ def sanitize_permissions(path: str, umask: T.Union[str, int]) -> None:
def set_mode(path: str, mode: T.Optional['FileMode'], default_umask: T.Union[str, int]) -> None:
- if mode is None or (mode.perms_s or mode.owner or mode.group) is None:
+ if mode is None or all(m is None for m in [mode.perms_s, mode.owner, mode.group]):
# Just sanitize permissions with the default umask
sanitize_permissions(path, default_umask)
return
# No chown() on Windows, and must set one of owner/group
- if not is_windows() and (mode.owner or mode.group) is not None:
+ if not is_windows() and (mode.owner is not None or mode.group is not None):
try:
set_chown(path, mode.owner, mode.group, follow_symlinks=False)
except PermissionError as e:
diff --git a/run_unittests.py b/run_unittests.py
index 13149ce..7f74e91 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -1698,6 +1698,18 @@ class InternalTests(unittest.TestCase):
_(None, mock.Mock(subproject=''), [], {'mode': 'since'})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*introduced in '1.1': "testfunc" keyword argument "mode" value "since".*""")
+ def test_typed_kwarg_evolve(self) -> None:
+ k = KwargInfo('foo', str, required=True, default='foo')
+ v = k.evolve(default='bar')
+ self.assertEqual(k.name, 'foo')
+ self.assertEqual(k.name, v.name)
+ self.assertEqual(k.types, str)
+ self.assertEqual(k.types, v.types)
+ self.assertEqual(k.required, True)
+ self.assertEqual(k.required, v.required)
+ self.assertEqual(k.default, 'foo')
+ self.assertEqual(v.default, 'bar')
+
@unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release')
class DataTests(unittest.TestCase):
diff --git a/test cases/failing/30 invalid man extension/test.json b/test cases/failing/30 invalid man extension/test.json
index 3f77a04..3e5f45d 100644
--- a/test cases/failing/30 invalid man extension/test.json
+++ b/test cases/failing/30 invalid man extension/test.json
@@ -1,7 +1,7 @@
{
"stdout": [
{
- "line": "test cases/failing/30 invalid man extension/meson.build:2:0: ERROR: Man file must have a file extension of a number between 1 and 8"
+ "line": "test cases/failing/30 invalid man extension/meson.build:2:0: ERROR: Man file must have a file extension of a number between 1 and 9"
}
]
}
diff --git a/test cases/failing/31 no man extension/test.json b/test cases/failing/31 no man extension/test.json
index 6e1f542..0972da1 100644
--- a/test cases/failing/31 no man extension/test.json
+++ b/test cases/failing/31 no man extension/test.json
@@ -1,7 +1,7 @@
{
"stdout": [
{
- "line": "test cases/failing/31 no man extension/meson.build:2:0: ERROR: Man file must have a file extension of a number between 1 and 8"
+ "line": "test cases/failing/31 no man extension/meson.build:2:0: ERROR: Man file must have a file extension of a number between 1 and 9"
}
]
}
diff --git a/test cases/failing/95 custom target install data/test.json b/test cases/failing/95 custom target install data/test.json
index 64ef530..c8004d6 100644
--- a/test cases/failing/95 custom target install data/test.json
+++ b/test cases/failing/95 custom target install data/test.json
@@ -1,7 +1,7 @@
{
"stdout": [
{
- "line": "test cases/failing/95 custom target install data/meson.build:11:0: ERROR: Argument must be string or file."
+ "line": "test cases/failing/95 custom target install data/meson.build:11:0: ERROR: install_data argument 1 was of type \"CustomTarget\" but should have been one of: \"str\", \"File\""
}
]
}