diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 5b7a6793b..091bfc8f8 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -39,6 +39,7 @@ from .interpreterbase import FeatureNew if T.TYPE_CHECKING: from .interpreter import Test + from .mesonlib import FileMode, FileOrString pch_kwargs = set(['c_pch', 'cpp_pch']) @@ -122,6 +123,52 @@ class DependencyOverride: self.node = node self.explicit = explicit +class Headers: + + def __init__(self, sources: T.List[File], install_subdir: T.Optional[str], + install_dir: T.Optional[str], install_mode: T.Optional['FileMode']): + self.sources = sources + self.install_subdir = install_subdir + self.custom_install_dir = install_dir + self.custom_install_mode = install_mode + + # TODO: we really don't need any of these methods, but they're preserved to + # keep APIs relying on them working. + + def set_install_subdir(self, subdir: str) -> None: + self.install_subdir = subdir + + def get_install_subdir(self) -> str: + return self.install_subdir + + def get_sources(self) -> T.List[File]: + return self.sources + + def get_custom_install_dir(self) -> T.Optional[str]: + return self.custom_install_dir + + def get_custom_install_mode(self) -> T.Optional['FileMode']: + return self.custom_install_mode + + +class Man: + + def __init__(self, sources: T.List[File], install_dir: T.Optional[str], + install_mode: T.Optional['FileMode']): + self.sources = sources + self.custom_install_dir = install_dir + self.custom_install_mode = install_mode + + def get_custom_install_dir(self) -> T.Optional[str]: + return self.custom_install_dir + + def get_custom_install_mode(self) -> T.Optional['FileMode']: + return self.custom_install_mode + + def get_sources(self) -> T.List['File']: + return self.sources + + class Build: """A class that holds the status of one build including all dependencies and so on. @@ -132,18 +179,18 @@ class Build: self.project_version = None self.environment = environment self.projects = {} - self.targets = OrderedDict() # type: T.Dict[str, 'Target'] - self.run_target_names = set() # type: T.Set[T.Tuple[str, str]] - self.global_args = PerMachine({}, {}) # type: PerMachine[T.Dict[str, T.List[str]]] - self.projects_args = PerMachine({}, {}) # type: PerMachine[T.Dict[str, T.List[str]]] - self.global_link_args = PerMachine({}, {}) # type: PerMachine[T.Dict[str, T.List[str]]] - self.projects_link_args = PerMachine({}, {}) # type: PerMachine[T.Dict[str, T.List[str]]] - self.tests = [] # type: T.List['Test'] - self.benchmarks = [] # type: T.List['Test'] - self.headers = [] - self.man = [] - self.data = [] - self.static_linker = PerMachine(None, None) # type: PerMachine[StaticLinker] + self.targets: T.MutableMapping[str, 'Target'] = OrderedDict() + self.run_target_names: T.Set[T.Tuple[str, str]] = set() + self.global_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) + self.projects_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) + self.global_link_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) + self.projects_link_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) + self.tests: T.List['Test'] = [] + self.benchmarks: T.List['Test'] = [] + self.headers: T.List[Headers] = [] + self.man: T.List[Man] = [] + self.data: T.List[Data] = [] + self.static_linker: PerMachine[StaticLinker] = PerMachine(None, None) self.subprojects = {} self.subproject_dir = '' self.install_scripts = [] @@ -153,7 +200,7 @@ class Build: self.dep_manifest_name = None self.dep_manifest = {} self.stdlibs = PerMachine({}, {}) - self.test_setups = {} # type: T.Dict[str, TestSetup] + self.test_setups: T.Dict[str, TestSetup] = {} self.test_setup_default_name = None self.find_overrides = {} self.searched_programs = set() # The list of all programs that have been searched for. @@ -494,7 +541,8 @@ a hard error in the future.'''.format(name)) class BuildTarget(Target): known_kwargs = known_build_target_kwargs - def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): + def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, + sources: T.List[File], objects, environment: environment.Environment, kwargs): super().__init__(name, subdir, subproject, True, for_machine) unity_opt = environment.coredata.get_builtin_option('unity') self.is_unity = unity_opt == 'on' or (unity_opt == 'subprojects' and subproject != '') @@ -517,7 +565,7 @@ class BuildTarget(Target): self.outputs = [self.filename] self.need_install = False self.pch = {} - self.extra_args = {} + self.extra_args: T.Dict[str, T.List['FileOrString']] = {} self.generated = [] self.d_features = {} self.pic = False @@ -1239,7 +1287,7 @@ You probably should put it in link_with instead.''') ids = [IncludeDirs(x.get_curdir(), x.get_incdirs(), is_system, x.get_extra_build_dirs()) for x in ids] self.include_dirs += ids - def add_compiler_args(self, language, args): + def add_compiler_args(self, language: str, args: T.List['FileOrString']) -> None: args = listify(args) for a in args: if not isinstance(a, (str, File)): @@ -1341,9 +1389,9 @@ You probably should put it in link_with instead.''') m = 'Could not get a dynamic linker for build target {!r}' raise AssertionError(m.format(self.name)) - def get_using_rustc(self): - if len(self.sources) > 0 and self.sources[0].fname.endswith('.rs'): - return True + def get_using_rustc(self) -> bool: + """Is this target a rust target.""" + return self.sources and self.sources[0].fname.endswith('.rs') def get_using_msvc(self): ''' @@ -1544,7 +1592,8 @@ class GeneratedList: class Executable(BuildTarget): known_kwargs = known_exe_kwargs - def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): + def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, + sources: T.List[File], objects, environment: environment.Environment, kwargs): self.typename = 'executable' if 'pie' not in kwargs and 'b_pie' in environment.coredata.base_options: kwargs['pie'] = environment.coredata.base_options['b_pie'].value diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 59e29be1c..a4a9fb2fa 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -50,6 +50,11 @@ import typing as T import importlib +if T.TYPE_CHECKING: + from .envconfig import MachineInfo + from .environment import Environment + from .modules import ExtensionModule + permitted_method_kwargs = { 'partial_dependency': {'compile_args', 'link_args', 'links', 'includes', 'sources'}, @@ -716,34 +721,26 @@ class IncludeDirsHolder(InterpreterObject, ObjectHolder): InterpreterObject.__init__(self) ObjectHolder.__init__(self, idobj) -class Headers(InterpreterObject): +class HeadersHolder(InterpreterObject, ObjectHolder): - def __init__(self, sources, kwargs): + def __init__(self, obj: build.Headers): InterpreterObject.__init__(self) - self.sources = sources - self.install_subdir = kwargs.get('subdir', '') - if os.path.isabs(self.install_subdir): - mlog.deprecation('Subdir keyword must not be an absolute path. This will be a hard error in the next release.') - self.custom_install_dir = kwargs.get('install_dir', None) - self.custom_install_mode = kwargs.get('install_mode', None) - if self.custom_install_dir is not None: - if not isinstance(self.custom_install_dir, str): - raise InterpreterException('Custom_install_dir must be a string.') + ObjectHolder.__init__(self, obj) def set_install_subdir(self, subdir): - self.install_subdir = subdir + self.held_object.install_subdir = subdir def get_install_subdir(self): - return self.install_subdir + return self.held_object.install_subdir def get_sources(self): - return self.sources + return self.held_object.sources def get_custom_install_dir(self): - return self.custom_install_dir + return self.held_object.custom_install_dir def get_custom_install_mode(self): - return self.custom_install_mode + return self.held_object.custom_install_mode class DataHolder(InterpreterObject, ObjectHolder): def __init__(self, data): @@ -771,34 +768,20 @@ class InstallDir(InterpreterObject): self.strip_directory = strip_directory self.from_source_dir = from_source_dir -class Man(InterpreterObject): +class ManHolder(InterpreterObject, ObjectHolder): - def __init__(self, sources, kwargs): + def __init__(self, obj: build.Man): InterpreterObject.__init__(self) - self.sources = sources - self.validate_sources() - self.custom_install_dir = kwargs.get('install_dir', None) - self.custom_install_mode = kwargs.get('install_mode', None) - if self.custom_install_dir is not None and not isinstance(self.custom_install_dir, str): - raise InterpreterException('Custom_install_dir must be a string.') + ObjectHolder.__init__(self, obj) - def validate_sources(self): - for s in self.sources: - try: - num = int(s.split('.')[-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') + def get_custom_install_dir(self) -> T.Optional[str]: + return self.held_object.custom_install_dir - def get_custom_install_dir(self): - return self.custom_install_dir + def get_custom_install_mode(self) -> T.Optional[FileMode]: + return self.held_object.custom_install_mode - def get_custom_install_mode(self): - return self.custom_install_mode - - def get_sources(self): - return self.sources + def get_sources(self) -> T.List[mesonlib.File]: + return self.held_object.sources class GeneratedObjectsHolder(InterpreterObject, ObjectHolder): def __init__(self, held_object): @@ -878,7 +861,7 @@ class BuildTargetHolder(TargetHolder): return self.held_object.name class ExecutableHolder(BuildTargetHolder): - def __init__(self, target, interp): + def __init__(self, target: build.Executable, interp: 'Interpreter'): super().__init__(target, interp) class StaticLibraryHolder(BuildTargetHolder): @@ -1781,14 +1764,37 @@ class CompilerHolder(InterpreterObject): return self.compiler.get_argument_syntax() -ModuleState = collections.namedtuple('ModuleState', [ - 'source_root', 'build_to_src', 'subproject', 'subdir', 'current_lineno', 'environment', - 'project_name', 'project_version', 'backend', 'targets', - 'data', 'headers', 'man', 'global_args', 'project_args', 'build_machine', - 'host_machine', 'target_machine', 'current_node']) +class ModuleState(T.NamedTuple): + + """Object passed to a module when it a method is called. + + holds the current state of the meson process at a given method call in + the interpreter. + """ + + source_root: str + build_to_src: str + subproject: str + subdir: str + current_lineno: str + environment: 'Environment' + project_name: str + project_version: str + backend: str + targets: T.Dict[str, build.Target] + data: T.List[build.Data] + headers: T.List[build.Headers] + man: T.List[build.Man] + global_args: T.Dict[str, T.List[str]] + project_args: T.Dict[str, T.List[str]] + build_machine: 'MachineInfo' + host_machine: 'MachineInfo' + target_machine: 'MachineInfo' + current_node: mparser.BaseNode + class ModuleHolder(InterpreterObject, ObjectHolder): - def __init__(self, modname, module, interpreter): + def __init__(self, modname: str, module: 'ExtensionModule', interpreter: 'Interpreter'): InterpreterObject.__init__(self) ObjectHolder.__init__(self, module) self.modname = modname @@ -4189,19 +4195,43 @@ This will become a hard error in the future.''' % kwargs['input'], location=self @permittedKwargs(permitted_kwargs['install_headers']) def func_install_headers(self, node, args, kwargs): source_files = self.source_strings_to_files(args) - kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) - h = Headers(source_files, kwargs) + 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): + 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.build.headers.append(h) - return h + + return HeadersHolder(h) @FeatureNewKwargs('install_man', '0.47.0', ['install_mode']) @permittedKwargs(permitted_kwargs['install_man']) def func_install_man(self, node, args, kwargs): - fargs = self.source_strings_to_files(args) - kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) - m = Man(fargs, kwargs) + sources = self.source_strings_to_files(args) + for s in sources: + try: + num = int(s.split('.')[-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) + 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.build.man.append(m) - return m + + return ManHolder(m) @FeatureNewKwargs('subdir', '0.44.0', ['if_found']) @permittedKwargs(permitted_kwargs['subdir']) @@ -4251,10 +4281,10 @@ This will become a hard error in the future.''' % kwargs['input'], location=self pass self.subdir = prev_subdir - def _get_kwarg_install_mode(self, kwargs): + def _get_kwarg_install_mode(self, kwargs: T.Dict[str, T.Any]) -> T.Optional[FileMode]: if kwargs.get('install_mode', None) is None: return None - install_mode = [] + install_mode: T.List[str] = [] mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int)) for m in mode: # We skip any arguments that are set to `false` @@ -4806,11 +4836,11 @@ Try setting b_lundef to false instead.'''.format(self.coredata.base_options['b_s if sproj_name != self.subproject_directory_name: raise InterpreterException('Sandbox violation: Tried to grab file %s from a different subproject.' % plain_filename) - def source_strings_to_files(self, sources): - results = [] + def source_strings_to_files(self, sources: T.List[str]) -> T.List[mesonlib.File]: mesonlib.check_direntry_issues(sources) if not isinstance(sources, list): sources = [sources] + results: T.List[mesonlib.File] = [] for s in sources: if isinstance(s, (mesonlib.File, GeneratedListHolder, TargetHolder, CustomTargetIndexHolder,