diff options
Diffstat (limited to 'mesonbuild/interpreter/interpreter.py')
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 241 |
1 files changed, 143 insertions, 98 deletions
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. |