Merge pull request #8884 from dcbaker/submit/type-and-annotate-install-functions

Add annotations for the various install_* functions
pull/8925/head
Jussi Pakkanen 3 years ago committed by GitHub
commit 6e0a0fd1da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      mesonbuild/backend/backends.py
  2. 14
      mesonbuild/build.py
  3. 241
      mesonbuild/interpreter/interpreter.py
  4. 34
      mesonbuild/interpreter/kwargs.py
  5. 41
      mesonbuild/interpreterbase/decorators.py
  6. 11
      mesonbuild/mesonlib/universal.py
  7. 9
      mesonbuild/minstall.py
  8. 12
      run_unittests.py
  9. 2
      test cases/failing/30 invalid man extension/test.json
  10. 2
      test cases/failing/31 no man extension/test.json
  11. 2
      test cases/failing/95 custom target install data/test.json

@ -42,9 +42,6 @@ if T.TYPE_CHECKING:
from ..interpreter import Interpreter, Test from ..interpreter import Interpreter, Test
from ..mesonlib import FileMode 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 # 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/++. # because the syntax we use for unity builds is specific to C/++/ObjC/++.
# Assembly files cannot be unitified and neither can LLVM IR files # Assembly files cannot be unitified and neither can LLVM IR files
@ -1451,7 +1448,12 @@ class Backend:
for h in headers: for h in headers:
outdir = h.get_custom_install_dir() outdir = h.get_custom_install_dir()
if outdir is None: 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(): for f in h.get_sources():
if not isinstance(f, File): if not isinstance(f, File):
raise MesonException(f'Invalid header type {f!r} can\'t be installed') raise MesonException(f'Invalid header type {f!r} can\'t be installed')

@ -135,7 +135,7 @@ class DependencyOverride(HoldableObject):
class Headers(HoldableObject): class Headers(HoldableObject):
def __init__(self, sources: T.List[File], install_subdir: T.Optional[str], 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): subproject: str):
self.sources = sources self.sources = sources
self.install_subdir = install_subdir self.install_subdir = install_subdir
@ -149,7 +149,7 @@ class Headers(HoldableObject):
def set_install_subdir(self, subdir: str) -> None: def set_install_subdir(self, subdir: str) -> None:
self.install_subdir = subdir self.install_subdir = subdir
def get_install_subdir(self) -> str: def get_install_subdir(self) -> T.Optional[str]:
return self.install_subdir return self.install_subdir
def get_sources(self) -> T.List[File]: def get_sources(self) -> T.List[File]:
@ -158,14 +158,14 @@ class Headers(HoldableObject):
def get_custom_install_dir(self) -> T.Optional[str]: def get_custom_install_dir(self) -> T.Optional[str]:
return self.custom_install_dir 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 return self.custom_install_mode
class Man(HoldableObject): class Man(HoldableObject):
def __init__(self, sources: T.List[File], install_dir: T.Optional[str], 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]): locale: T.Optional[str]):
self.sources = sources self.sources = sources
self.custom_install_dir = install_dir self.custom_install_dir = install_dir
@ -176,7 +176,7 @@ class Man(HoldableObject):
def get_custom_install_dir(self) -> T.Optional[str]: def get_custom_install_dir(self) -> T.Optional[str]:
return self.custom_install_dir 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 return self.custom_install_mode
def get_sources(self) -> T.List['File']: def get_sources(self) -> T.List['File']:
@ -186,7 +186,7 @@ class Man(HoldableObject):
class InstallDir(HoldableObject): class InstallDir(HoldableObject):
def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str, 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]], exclude: T.Tuple[T.Set[str], T.Set[str]],
strip_directory: bool, subproject: str, strip_directory: bool, subproject: str,
from_source_dir: bool = True): from_source_dir: bool = True):
@ -2623,7 +2623,7 @@ class ConfigurationData(HoldableObject):
# during install. # during install.
class Data(HoldableObject): class Data(HoldableObject):
def __init__(self, sources: T.List[File], install_dir: str, 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): rename: T.List[str] = None):
self.sources = sources self.sources = sources
self.install_dir = install_dir self.install_dir = install_dir

@ -85,6 +85,61 @@ def _language_validator(l: T.List[str]) -> T.Optional[str]:
return None 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_KW = KwargInfo(
'native', bool, 'native', bool,
default=False, default=False,
@ -97,6 +152,15 @@ _LANGUAGE_KW = KwargInfo(
validator=_language_validator, validator=_language_validator,
convertor=lambda x: [i.lower() for i in x]) 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): def stringifyUserArguments(args, quote=False):
if isinstance(args, list): 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) self.build.benchmarks.append(t)
mlog.debug('Adding benchmark', mlog.bold(t.name, True)) mlog.debug('Adding benchmark', mlog.bold(t.name, True))
@FeatureNewKwargs('install_headers', '0.47.0', ['install_mode']) @typed_pos_args('install_headers', varargs=(str, mesonlib.File), min_varargs=1)
@permittedKwargs({'install_dir', 'install_mode', 'subdir'}) @typed_kwargs(
def func_install_headers(self, node, args, kwargs): 'install_headers',
source_files = self.source_strings_to_files(args) KwargInfo('install_dir', (str, None)),
install_mode = self._get_kwarg_install_mode(kwargs) KwargInfo('subdir', (str, None)),
_INSTALL_MODE_KW.evolve(since='0.47.0'),
install_subdir = kwargs.get('subdir', '') )
if not isinstance(install_subdir, str): def func_install_headers(self, node: mparser.BaseNode,
raise InterpreterException('subdir keyword argument must be a string') args: T.Tuple[T.List['mesonlib.FileOrString']],
elif os.path.isabs(install_subdir): 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.') 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) h = build.Headers(source_files, install_subdir, kwargs['install_dir'],
if install_dir is not None and not isinstance(install_dir, str): kwargs['install_mode'], self.subproject)
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)
self.build.headers.append(h) self.build.headers.append(h)
return h return h
@FeatureNewKwargs('install_man', '0.47.0', ['install_mode']) @typed_pos_args('install_man', varargs=(str, mesonlib.File), min_varargs=1)
@FeatureNewKwargs('install_man', '0.58.0', ['locale']) @typed_kwargs(
@permittedKwargs({'install_dir', 'install_mode', 'locale'}) 'install_man',
def func_install_man(self, node, args, kwargs): KwargInfo('install_dir', (str, None)),
sources = self.source_strings_to_files(args) 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: for s in sources:
try: try:
num = int(s.split('.')[-1]) num = int(s.rsplit('.', 1)[-1])
except (IndexError, ValueError): except (IndexError, ValueError):
num = 0 num = 0
if num < 1 or num > 8: if not 1 <= num <= 9:
raise InvalidArguments('Man file must have a file extension of a number between 1 and 8') raise InvalidArguments('Man file must have a file extension of a number between 1 and 9')
custom_install_mode = self._get_kwarg_install_mode(kwargs)
custom_install_dir = kwargs.get('install_dir', None) m = build.Man(sources, kwargs['install_dir'], kwargs['install_mode'],
locale = kwargs.get('locale') self.subproject, kwargs['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)
self.build.man.append(m) self.build.man.append(m)
return 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') 'permissions arg to be a string or false')
return FileMode(*install_mode) return FileMode(*install_mode)
@FeatureNewKwargs('install_data', '0.46.0', ['rename']) @typed_pos_args('install_data', varargs=(str, mesonlib.File))
@FeatureNewKwargs('install_data', '0.38.0', ['install_mode']) @typed_kwargs(
@permittedKwargs({'install_dir', 'install_mode', 'rename', 'sources'}) 'install_data',
def func_install_data(self, node, args: T.List, kwargs: T.Dict[str, T.Any]): KwargInfo('install_dir', str),
kwsource = mesonlib.stringlistify(kwargs.get('sources', [])) KwargInfo('sources', ContainerTypeInfo(list, (str, mesonlib.File)), listify=True, default=[]),
raw_sources = args + kwsource KwargInfo('rename', ContainerTypeInfo(list, str), default=[], listify=True, since='0.46.0'),
sources: T.List[mesonlib.File] = [] _INSTALL_MODE_KW.evolve(since='0.38.0'),
source_strings: T.List[str] = [] )
for s in raw_sources: def func_install_data(self, node: mparser.BaseNode,
if isinstance(s, mesonlib.File): args: T.Tuple[T.List['mesonlib.FileOrString']],
sources.append(s) kwargs: 'kwargs.FuncInstallData') -> build.Data:
elif isinstance(s, str): sources = self.source_strings_to_files(args[0] + kwargs['sources'])
source_strings.append(s) rename = kwargs['rename'] or None
else: if rename:
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)
if len(rename) != len(sources): if len(rename) != len(sources):
raise InvalidArguments( raise InvalidArguments(
'"rename" and "sources" argument lists must be the same length if "rename" is given. ' '"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)}.') 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) self.build.data.append(data)
return data return data
@FeatureNewKwargs('install_subdir', '0.42.0', ['exclude_files', 'exclude_directories']) @typed_pos_args('install_subdir', str)
@FeatureNewKwargs('install_subdir', '0.38.0', ['install_mode']) @typed_kwargs(
@permittedKwargs({'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'}) 'install_subdir',
@stringArgs KwargInfo('install_dir', str, required=True),
def func_install_subdir(self, node, args, kwargs): KwargInfo('strip_directory', bool, default=False),
if len(args) != 1: KwargInfo('exclude_files', ContainerTypeInfo(list, str),
raise InvalidArguments('Install_subdir requires exactly one argument.') default=[], listify=True, since='0.42.0',
subdir: str = args[0] validator=lambda x: 'cannot be absolute' if any(os.path.isabs(d) for d in x) else None),
if not isinstance(subdir, str): KwargInfo('exclude_directories', ContainerTypeInfo(list, str),
raise InvalidArguments('install_subdir positional argument 1 must be a string.') default=[], listify=True, since='0.42.0',
if 'install_dir' not in kwargs: validator=lambda x: 'cannot be absolute' if any(os.path.isabs(d) for d in x) else None),
raise InvalidArguments('Missing keyword argument install_dir') _INSTALL_MODE_KW.evolve(since='0.38.0'),
install_dir: str = kwargs['install_dir'] )
if not isinstance(install_dir, str): def func_install_subdir(self, node: mparser.BaseNode, args: T.Tuple[str],
raise InvalidArguments('Keyword argument install_dir not a string.') kwargs: 'kwargs.FuncInstallSubdir') -> build.InstallDir:
if 'strip_directory' in kwargs: exclude = (set(kwargs['exclude_files']), set(kwargs['exclude_directories']))
strip_directory: bool = kwargs['strip_directory'] idir = build.InstallDir(
if not isinstance(strip_directory, bool): self.subdir,
raise InterpreterException('"strip_directory" keyword must be a boolean.') args[0],
else: kwargs['install_dir'],
strip_directory = False kwargs['install_mode'],
if 'exclude_files' in kwargs: exclude,
exclude: T.List[str] = extract_as_list(kwargs, 'exclude_files') kwargs['strip_directory'],
for f in exclude: self.subproject)
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)
self.build.install_dirs.append(idir) self.build.install_dirs.append(idir)
return 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: if project_root / self.subproject_dir in norm.parents:
raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {norm.name} from a nested subproject.') 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']: 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. """Lower inputs to a list of Targets and Files, replacing any strings.

@ -4,13 +4,13 @@
"""Keyword Argument type annotations.""" """Keyword Argument type annotations."""
from mesonbuild import coredata
import typing as T import typing as T
from typing_extensions import TypedDict, Literal from typing_extensions import TypedDict, Literal
from .. import build from .. import build
from ..mesonlib import MachineChoice, File from .. import coredata
from ..mesonlib import MachineChoice, File, FileMode, FileOrString
from .interpreterobjects import EnvironmentVariablesObject from .interpreterobjects import EnvironmentVariablesObject
@ -102,3 +102,33 @@ class DependencyMethodPartialDependency(TypedDict):
class BuildTargeMethodExtractAllObjects(TypedDict): class BuildTargeMethodExtractAllObjects(TypedDict):
recursive: bool 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]

@ -272,6 +272,11 @@ class ContainerTypeInfo:
_T = T.TypeVar('_T') _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]): class KwargInfo(T.Generic[_T]):
@ -329,6 +334,42 @@ class KwargInfo(T.Generic[_T]):
self.convertor = convertor self.convertor = convertor
self.not_set_warning = not_set_warning 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]: def typed_kwargs(name: str, *types: KwargInfo) -> T.Callable[..., T.Any]:
"""Decorator for type checking keyword arguments. """Decorator for type checking keyword arguments.

@ -298,8 +298,8 @@ class FileMode:
'[r-][w-][xsS-]' # Group perms '[r-][w-][xsS-]' # Group perms
'[r-][w-][xtT-]') # Others perms '[r-][w-][xtT-]') # Others perms
def __init__(self, perms: T.Optional[str] = None, owner: T.Optional[str] = None, def __init__(self, perms: T.Optional[str] = None, owner: T.Union[str, int, None] = None,
group: T.Optional[str] = None): group: T.Union[str, int, None] = None):
self.perms_s = perms self.perms_s = perms
self.perms = self.perms_s_to_bits(perms) self.perms = self.perms_s_to_bits(perms)
self.owner = owner self.owner = owner
@ -422,8 +422,11 @@ class File(HoldableObject):
def endswith(self, ending: str) -> bool: def endswith(self, ending: str) -> bool:
return self.fname.endswith(ending) return self.fname.endswith(ending)
def split(self, s: str) -> T.List[str]: def split(self, s: str, maxsplit: int = -1) -> T.List[str]:
return self.fname.split(s) 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: def __eq__(self, other: object) -> bool:
if not isinstance(other, File): if not isinstance(other, File):

@ -64,7 +64,7 @@ build definitions so that it will not break when the change happens.'''
selinux_updates: T.List[str] = [] 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', parser.add_argument('-C', default='.', dest='wd',
help='directory to cd into before running') help='directory to cd into before running')
parser.add_argument('--profile-self', action='store_true', dest='profile', 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() 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: dir_fd: T.Optional[int] = None, follow_symlinks: bool = True) -> None:
# shutil.chown will call os.chown without passing all the parameters # shutil.chown will call os.chown without passing all the parameters
# and particularly follow_symlinks, thus we replace it temporary # 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: 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 # Just sanitize permissions with the default umask
sanitize_permissions(path, default_umask) sanitize_permissions(path, default_umask)
return return
# No chown() on Windows, and must set one of owner/group # 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: try:
set_chown(path, mode.owner, mode.group, follow_symlinks=False) set_chown(path, mode.owner, mode.group, follow_symlinks=False)
except PermissionError as e: except PermissionError as e:

@ -1698,6 +1698,18 @@ class InternalTests(unittest.TestCase):
_(None, mock.Mock(subproject=''), [], {'mode': 'since'}) _(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".*""") 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') @unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release')
class DataTests(unittest.TestCase): class DataTests(unittest.TestCase):

@ -1,7 +1,7 @@
{ {
"stdout": [ "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"
} }
] ]
} }

@ -1,7 +1,7 @@
{ {
"stdout": [ "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"
} }
] ]
} }

@ -1,7 +1,7 @@
{ {
"stdout": [ "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\""
} }
] ]
} }

Loading…
Cancel
Save