diff options
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/backend/backends.py | 2 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 99 | ||||
-rw-r--r-- | mesonbuild/build.py | 34 | ||||
-rw-r--r-- | mesonbuild/compilers/__init__.py | 2 | ||||
-rw-r--r-- | mesonbuild/compilers/compilers.py | 5 | ||||
-rw-r--r-- | mesonbuild/compilers/mixins/clike.py | 15 | ||||
-rw-r--r-- | mesonbuild/compilers/swift.py | 3 | ||||
-rw-r--r-- | mesonbuild/environment.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreter/kwargs.py | 1 | ||||
-rw-r--r-- | mesonbuild/interpreter/type_checking.py | 1 | ||||
-rw-r--r-- | mesonbuild/mintro.py | 7 | ||||
-rw-r--r-- | mesonbuild/modules/fs.py | 121 | ||||
-rwxr-xr-x | mesonbuild/msubprojects.py | 17 |
13 files changed, 193 insertions, 118 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 3aab420..45b1559 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -61,7 +61,7 @@ if T.TYPE_CHECKING: # 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 -LANGS_CANT_UNITY = ('d', 'fortran', 'vala') +LANGS_CANT_UNITY = ('d', 'fortran', 'vala', 'rust') @dataclass(eq=False) class RegenInfo: diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index ba75ce7..5206386 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -891,14 +891,14 @@ class NinjaBackend(backends.Backend): self.generate_shlib_aliases(target, self.get_target_dir(target)) + # Generate rules for GeneratedLists + self.generate_generator_list_rules(target) + # If target uses a language that cannot link to C objects, # just generate for that language and return. if isinstance(target, build.Jar): self.generate_jar_target(target) return - if target.uses_rust(): - self.generate_rust_target(target) - return if 'cs' in target.compilers: self.generate_cs_target(target) return @@ -935,8 +935,6 @@ class NinjaBackend(backends.Backend): generated_sources = self.get_target_generated_sources(target) transpiled_sources = [] self.scan_fortran_module_outputs(target) - # Generate rules for GeneratedLists - self.generate_generator_list_rules(target) # Generate rules for building the remaining source files in this target outname = self.get_target_filename(target) @@ -992,6 +990,8 @@ class NinjaBackend(backends.Backend): # this target. We create the Ninja build file elements for this here # because we need `header_deps` to be fully generated in the above loop. for src in generated_source_files: + if not self.environment.is_separate_compile(src): + continue if self.environment.is_llvm_ir(src): o, s = self.generate_llvm_ir_compile(target, src) else: @@ -1050,21 +1050,24 @@ class NinjaBackend(backends.Backend): # Generate compile targets for all the preexisting sources for this target for src in target_sources.values(): - if not self.environment.is_header(src) or is_compile_target: - if self.environment.is_llvm_ir(src): - o, s = self.generate_llvm_ir_compile(target, src) - obj_list.append(o) - elif is_unity and self.get_target_source_can_unity(target, src): - abs_src = os.path.join(self.environment.get_build_dir(), - src.rel_to_builddir(self.build_to_src)) - unity_src.append(abs_src) - else: - o, s = self.generate_single_compile(target, src, False, [], - header_deps + d_generated_deps + fortran_order_deps, - fortran_inc_args) - obj_list.append(o) - compiled_sources.append(s) - source2object[s] = o + if not self.environment.is_separate_compile(src): + continue + if self.environment.is_header(src) and not is_compile_target: + continue + if self.environment.is_llvm_ir(src): + o, s = self.generate_llvm_ir_compile(target, src) + obj_list.append(o) + elif is_unity and self.get_target_source_can_unity(target, src): + abs_src = os.path.join(self.environment.get_build_dir(), + src.rel_to_builddir(self.build_to_src)) + unity_src.append(abs_src) + else: + o, s = self.generate_single_compile(target, src, False, [], + header_deps + d_generated_deps + fortran_order_deps, + fortran_inc_args) + obj_list.append(o) + compiled_sources.append(s) + source2object[s] = o if is_unity: for src in self.generate_unity_files(target, unity_src): @@ -1084,8 +1087,14 @@ class NinjaBackend(backends.Backend): final_obj_list = self.generate_prelink(target, obj_list) else: final_obj_list = obj_list - elem = self.generate_link(target, outname, final_obj_list, linker, pch_objects, stdlib_args=stdlib_args) + self.generate_dependency_scan_target(target, compiled_sources, source2object, fortran_order_deps) + + if target.uses_rust(): + self.generate_rust_target(target, outname, final_obj_list, fortran_order_deps) + return + + elem = self.generate_link(target, outname, final_obj_list, linker, pch_objects, stdlib_args=stdlib_args) self.add_build(elem) #In AIX, we archive shared libraries. If the instance is a shared library, we add a command to archive the shared library #object and create the build element. @@ -1556,7 +1565,6 @@ class NinjaBackend(backends.Backend): elem.add_item('ARGS', commands) self.add_build(elem) - self.generate_generator_list_rules(target) self.create_target_source_introspection(target, compiler, commands, rel_srcs, generated_rel_srcs) def determine_java_compile_args(self, target, compiler) -> T.List[str]: @@ -1972,6 +1980,7 @@ class NinjaBackend(backends.Backend): for s in f.get_outputs()]) self.all_structured_sources.update(_ods) orderdeps.extend(_ods) + return orderdeps, main_rust_file for i in target.get_sources(): if main_rust_file is None: @@ -2010,7 +2019,8 @@ class NinjaBackend(backends.Backend): args += target.get_extra_args('rust') return args - def get_rust_compiler_deps_and_args(self, target: build.BuildTarget, rustc: Compiler) -> T.Tuple[T.List[str], T.List[str], T.List[RustDep], T.List[str]]: + def get_rust_compiler_deps_and_args(self, target: build.BuildTarget, rustc: Compiler, + obj_list: T.List[str]) -> T.Tuple[T.List[str], T.List[RustDep], T.List[str]]: deps: T.List[str] = [] project_deps: T.List[RustDep] = [] args: T.List[str] = [] @@ -2042,11 +2052,9 @@ class NinjaBackend(backends.Backend): type_ += ':' + ','.join(modifiers) args.append(f'-l{type_}={libname}') - objs, od = self.flatten_object_list(target) - for o in objs: + for o in obj_list: args.append(f'-Clink-arg={o}') deps.append(o) - fortran_order_deps = self.get_fortran_order_deps(od) linkdirs = mesonlib.OrderedSet() external_deps = target.external_deps.copy() @@ -2151,31 +2159,21 @@ class NinjaBackend(backends.Backend): if has_shared_deps or has_rust_shared_deps: args += self.get_build_rpath_args(target, rustc) - return deps, fortran_order_deps, project_deps, args - - def generate_rust_target(self, target: build.BuildTarget) -> None: - rustc = T.cast('RustCompiler', target.compilers['rust']) - self.generate_generator_list_rules(target) - - for i in target.get_sources(): - if not rustc.can_compile(i): - raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') - for g in target.get_generated_sources(): - for i in g.get_outputs(): - if not rustc.can_compile(i): - raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') + return deps, project_deps, args + def generate_rust_target(self, target: build.BuildTarget, target_name: str, obj_list: T.List[str], + fortran_order_deps: T.List[str]) -> None: orderdeps, main_rust_file = self.generate_rust_sources(target) - target_name = self.get_target_filename(target) if main_rust_file is None: raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report') + rustc = T.cast('RustCompiler', target.compilers['rust']) args = rustc.compiler_args() depfile = os.path.join(self.get_target_private_dir(target), target.name + '.d') args += self.get_rust_compiler_args(target, rustc, target.rust_crate_type, depfile) - deps, fortran_order_deps, project_deps, deps_args = self.get_rust_compiler_deps_and_args(target, rustc) + deps, project_deps, deps_args = self.get_rust_compiler_deps_and_args(target, rustc, obj_list) args += deps_args proc_macro_dylib_path = None @@ -2210,7 +2208,10 @@ class NinjaBackend(backends.Backend): rustdoc = rustc.get_rustdoc(self.environment) args = rustdoc.get_exe_args() args += self.get_rust_compiler_args(target.doctests.target, rustdoc, target.rust_crate_type) - _, _, _, deps_args = self.get_rust_compiler_deps_and_args(target.doctests.target, rustdoc) + # There can be no non-Rust objects: the doctests are gathered from Rust + # sources and the tests are linked with the target (which is where the + # obj_list was linked into) + _, _, deps_args = self.get_rust_compiler_deps_and_args(target.doctests.target, rustdoc, []) args += deps_args target.doctests.cmd_args = args.to_native() + [main_rust_file] + target.doctests.cmd_args @@ -2232,10 +2233,7 @@ class NinjaBackend(backends.Backend): def swift_module_file_name(self, target): return os.path.join(self.get_target_private_dir(target), - self.target_swift_modulename(target) + '.swiftmodule') - - def target_swift_modulename(self, target): - return target.name + target.swift_module_name + '.swiftmodule') def determine_swift_dep_modules(self, target): result = [] @@ -2262,7 +2260,7 @@ class NinjaBackend(backends.Backend): return srcs, others def generate_swift_target(self, target) -> None: - module_name = self.target_swift_modulename(target) + module_name = target.swift_module_name swiftc = target.compilers['swift'] abssrc = [] relsrc = [] @@ -2288,6 +2286,13 @@ class NinjaBackend(backends.Backend): compile_args += swiftc.get_cxx_interoperability_args(target.compilers) compile_args += self.build.get_project_args(swiftc, target.subproject, target.for_machine) compile_args += self.build.get_global_args(swiftc, target.for_machine) + if isinstance(target, (build.StaticLibrary, build.SharedLibrary)): + # swiftc treats modules with a single source file, and the main.swift file in multi-source file modules + # as top-level code. This is undesirable in library targets since it emits a main function. Add the + # -parse-as-library option as necessary to prevent emitting the main function while keeping files explicitly + # named main.swift treated as the entrypoint of the module in case this is desired. + if len(abssrc) == 1 and os.path.basename(abssrc[0]) != 'main.swift': + compile_args += swiftc.get_library_args() for i in reversed(target.get_include_dirs()): basedir = i.get_curdir() for d in i.get_incdirs(): diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 72d376d..410b4d2 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -75,6 +75,7 @@ lang_arg_kwargs |= { vala_kwargs = {'vala_header', 'vala_gir', 'vala_vapi'} rust_kwargs = {'rust_crate_type', 'rust_dependency_map'} cs_kwargs = {'resources', 'cs_args'} +swift_kwargs = {'swift_module_name'} buildtarget_kwargs = { 'build_by_default', @@ -110,7 +111,8 @@ known_build_target_kwargs = ( pch_kwargs | vala_kwargs | rust_kwargs | - cs_kwargs) + cs_kwargs | + swift_kwargs) known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie', 'vs_module_defs', 'android_exe_type'} known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions', 'rust_abi'} @@ -769,14 +771,23 @@ class BuildTarget(Target): ''' Initialisations and checks requiring the final list of compilers to be known ''' self.validate_sources() - if self.structured_sources and any([self.sources, self.generated]): - raise MesonException('cannot mix structured sources and unstructured sources') - if self.structured_sources and 'rust' not in self.compilers: - raise MesonException('structured sources are only supported in Rust targets') if self.uses_rust(): + if self.link_language and self.link_language != 'rust': + raise MesonException('cannot build Rust sources with a different link_language') + if self.structured_sources: + # TODO: the interpreter should be able to generate a better error message? + if any((s.endswith('.rs') for s in self.sources)) or \ + any(any((s.endswith('.rs') for s in g.get_outputs())) for g in self.generated): + raise MesonException('cannot mix Rust structured sources and unstructured sources') + # relocation-model=pic is rustc's default and Meson does not # currently have a way to disable PIC. self.pic = True + self.pie = True + else: + if self.structured_sources: + raise MesonException('structured sources are only supported in Rust targets') + if 'vala' in self.compilers and self.is_linkable_target(): self.outputs += [self.vala_header, self.vala_vapi] self.install_tag += ['devel', 'devel'] @@ -878,6 +889,10 @@ class BuildTarget(Target): if isinstance(t, (CustomTarget, CustomTargetIndex)): continue # We can't know anything about these. for name, compiler in t.compilers.items(): + if name == 'rust': + # Rust is always linked through a C-ABI target, so do not add + # the compiler here + continue if name in link_langs and name not in self.compilers: self.compilers[name] = compiler @@ -963,7 +978,7 @@ class BuildTarget(Target): self.compilers[lang] = compiler break else: - if is_known_suffix(s): + if is_known_suffix(s) and not is_header(s): path = pathlib.Path(str(s)).as_posix() m = f'No {self.for_machine.get_lower_case_name()} machine compiler for {path!r}' raise MesonException(m) @@ -1260,6 +1275,10 @@ class BuildTarget(Target): raise InvalidArguments(f'Invalid rust_dependency_map "{rust_dependency_map}": must be a dictionary with string values.') self.rust_dependency_map = rust_dependency_map + self.swift_module_name = kwargs.get('swift_module_name') + if self.swift_module_name == '': + self.swift_module_name = self.name + def _extract_pic_pie(self, kwargs: T.Dict[str, T.Any], arg: str, option: str) -> bool: # Check if we have -fPIC, -fpic, -fPIE, or -fpie in cflags all_flags = self.extra_args['c'] + self.extra_args['cpp'] @@ -1589,6 +1608,9 @@ class BuildTarget(Target): if isinstance(link_target, (CustomTarget, CustomTargetIndex)): continue for language in link_target.compilers: + if language == 'rust' and not link_target.uses_rust_abi(): + # All Rust dependencies must go through a C-ABI dependency, so ignore it + continue if language not in langs: langs.append(language) diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index aab761a..f645090 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -18,6 +18,7 @@ __all__ = [ 'is_library', 'is_llvm_ir', 'is_object', + 'is_separate_compile', 'is_source', 'is_java', 'is_known_suffix', @@ -62,6 +63,7 @@ from .compilers import ( is_object, is_library, is_known_suffix, + is_separate_compile, lang_suffixes, LANGUAGES_USING_LDFLAGS, sort_clink, diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 76d8d72..3b7f066 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -84,7 +84,7 @@ clib_langs = ('objcpp', 'cpp', 'objc', 'c', 'nasm', 'fortran') # List of languages that can be linked with C code directly by the linker # used in build.py:process_compilers() and build.py:get_dynamic_linker() # This must be sorted, see sort_clink(). -clink_langs = ('d', 'cuda') + clib_langs +clink_langs = ('rust', 'd', 'cuda') + clib_langs SUFFIX_TO_LANG = dict(itertools.chain(*( [(suffix, lang) for suffix in v] for lang, v in lang_suffixes.items()))) @@ -154,6 +154,9 @@ def is_java(fname: mesonlib.FileOrString) -> bool: suffix = fname.split('.')[-1] return suffix in lang_suffixes['java'] +def is_separate_compile(fname: mesonlib.FileOrString) -> bool: + return not fname.endswith('.rs') + def is_llvm_ir(fname: 'mesonlib.FileOrString') -> bool: if isinstance(fname, mesonlib.File): fname = fname.fname diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index 1c875a3..d2eb611 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -1272,12 +1272,19 @@ class CLikeCompiler(Compiler): # check the equivalent enable flag too "-Wforgotten-towel". if arg.startswith('-Wno-'): # Make an exception for -Wno-attributes=x as -Wattributes=x is invalid - # for GCC at least. Also, the opposite of -Wno-vla-larger-than is - # -Wvla-larger-than=N + # for GCC at least. Also, the positive form of some flags require a + # value to be specified, i.e. we need to pass -Wfoo=N rather than just + # -Wfoo. if arg.startswith('-Wno-attributes='): pass - elif arg == '-Wno-vla-larger-than': - new_args.append('-Wvla-larger-than=1000') + elif arg in {'-Wno-alloc-size-larger-than', + '-Wno-alloca-larger-than', + '-Wno-frame-larger-than', + '-Wno-stack-usage', + '-Wno-vla-larger-than'}: + # Pass an arbitrary value to the enabling flag; since the test program + # is trivial, it is unlikely to provoke any of these warnings. + new_args.append('-W' + arg[5:] + '=1000') else: new_args.append('-W' + arg[5:]) if arg.startswith('-Wl,'): diff --git a/mesonbuild/compilers/swift.py b/mesonbuild/compilers/swift.py index 47d254b..3fff7a1 100644 --- a/mesonbuild/compilers/swift.py +++ b/mesonbuild/compilers/swift.py @@ -159,6 +159,9 @@ class SwiftCompiler(Compiler): else: return ['-cxx-interoperability-mode=off'] + def get_library_args(self) -> T.List[str]: + return ['-parse-as-library'] + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: for idx, i in enumerate(parameter_list): diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 19e8b4e..489ef50 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -35,6 +35,7 @@ from .compilers import ( is_library, is_llvm_ir, is_object, + is_separate_compile, is_source, ) @@ -937,6 +938,9 @@ class Environment: def is_assembly(self, fname: 'mesonlib.FileOrString') -> bool: return is_assembly(fname) + def is_separate_compile(self, fname: 'mesonlib.FileOrString') -> bool: + return is_separate_compile(fname) + def is_llvm_ir(self, fname: 'mesonlib.FileOrString') -> bool: return is_llvm_ir(fname) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index d741aab..62d855d 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -363,6 +363,7 @@ class _BuildTarget(_BaseBuildTarget): d_module_versions: T.List[T.Union[str, int]] d_unittest: bool rust_dependency_map: T.Dict[str, str] + swift_module_name: str sources: SourcesVarargsType c_args: T.List[str] cpp_args: T.List[str] diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index fbe3e3e..a94e26b 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -633,6 +633,7 @@ _BUILD_TARGET_KWS: T.List[KwargInfo] = [ default={}, since='1.2.0', ), + KwargInfo('swift_module_name', str, default='', since='1.9.0'), KwargInfo('build_rpath', str, default='', since='0.42.0'), KwargInfo( 'gnu_symbol_visibility', diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 57fa286..e19e528 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -125,14 +125,15 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]: res[basename] = os.path.join(installdata.prefix, s.install_path, basename) return res -def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]]: - plan: T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]] = { +def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[str, T.Dict[str, T.Union[str, T.List[str], None]]]]: + plan: T.Dict[str, T.Dict[str, T.Dict[str, T.Union[str, T.List[str], None]]]] = { 'targets': { Path(installdata.build_dir, target.fname).as_posix(): { 'destination': target.out_name, 'tag': target.tag or None, 'subproject': target.subproject or None, - 'install_rpath': target.install_rpath or None + 'install_rpath': target.install_rpath or None, + 'build_rpaths': sorted(x.decode('utf8') for x in target.rpath_dirs_to_remove), } for target in installdata.targets }, diff --git a/mesonbuild/modules/fs.py b/mesonbuild/modules/fs.py index 1fa368e..57a6b6d 100644 --- a/mesonbuild/modules/fs.py +++ b/mesonbuild/modules/fs.py @@ -2,7 +2,9 @@ # Copyright 2019 The Meson development team from __future__ import annotations -from pathlib import Path, PurePath, PureWindowsPath +from ntpath import sep as ntsep +from pathlib import Path +from posixpath import sep as posixsep import hashlib import os import typing as T @@ -12,7 +14,7 @@ from .. import mlog from ..build import BuildTarget, CustomTarget, CustomTargetIndex, InvalidArguments from ..interpreter.type_checking import INSTALL_KW, INSTALL_MODE_KW, INSTALL_TAG_KW, NoneType from ..interpreterbase import FeatureNew, KwargInfo, typed_kwargs, typed_pos_args, noKwargs -from ..mesonlib import File, MesonException, has_path_sep, path_is_in_root, relpath +from ..mesonlib import File, MesonException, has_path_sep, is_windows, path_is_in_root, relpath if T.TYPE_CHECKING: from . import ModuleState @@ -42,7 +44,7 @@ class FSModule(ExtensionModule): INFO = ModuleInfo('fs', '0.53.0') - def __init__(self, interpreter: 'Interpreter') -> None: + def __init__(self, interpreter: Interpreter) -> None: super().__init__(interpreter) self.methods.update({ 'as_posix': self.as_posix, @@ -62,29 +64,30 @@ class FSModule(ExtensionModule): 'replace_suffix': self.replace_suffix, 'size': self.size, 'stem': self.stem, + 'suffix': self.suffix, }) - def _absolute_dir(self, state: 'ModuleState', arg: 'FileOrString') -> Path: + def _absolute_dir(self, state: ModuleState, arg: FileOrString) -> str: """ make an absolute path from a relative path, WITHOUT resolving symlinks """ if isinstance(arg, File): - return Path(arg.absolute_path(state.source_root, state.environment.get_build_dir())) - return Path(state.source_root) / Path(state.subdir) / Path(arg).expanduser() + return arg.absolute_path(state.source_root, state.environment.get_build_dir()) + return os.path.join(state.source_root, state.subdir, os.path.expanduser(arg)) @staticmethod - def _obj_to_path(feature_new_prefix: str, obj: T.Union[FileOrString, BuildTargetTypes], state: ModuleState) -> PurePath: + def _obj_to_pathstr(feature_new_prefix: str, obj: T.Union[FileOrString, BuildTargetTypes], state: ModuleState) -> str: if isinstance(obj, str): - return PurePath(obj) + return obj if isinstance(obj, File): FeatureNew(f'{feature_new_prefix} with file', '0.59.0').use(state.subproject, location=state.current_node) - return PurePath(str(obj)) + return str(obj) FeatureNew(f'{feature_new_prefix} with build_tgt, custom_tgt, and custom_idx', '1.4.0').use(state.subproject, location=state.current_node) - return PurePath(state.backend.get_target_filename(obj)) + return state.backend.get_target_filename(obj) - def _resolve_dir(self, state: 'ModuleState', arg: 'FileOrString') -> Path: + def _resolve_dir(self, state: ModuleState, arg: FileOrString) -> str: """ resolves symlinks and makes absolute a directory relative to calling meson.build, if not already absolute @@ -92,7 +95,7 @@ class FSModule(ExtensionModule): path = self._absolute_dir(state, arg) try: # accommodate unresolvable paths e.g. symlink loops - path = path.resolve() + path = os.path.realpath(path) except Exception: # return the best we could do pass @@ -101,123 +104,139 @@ class FSModule(ExtensionModule): @noKwargs @FeatureNew('fs.expanduser', '0.54.0') @typed_pos_args('fs.expanduser', str) - def expanduser(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: - return str(Path(args[0]).expanduser()) + def expanduser(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: + return os.path.expanduser(args[0]) @noKwargs @FeatureNew('fs.is_absolute', '0.54.0') @typed_pos_args('fs.is_absolute', (str, File)) - def is_absolute(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: T.Dict[str, T.Any]) -> bool: - if isinstance(args[0], File): + def is_absolute(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: T.Dict[str, T.Any]) -> bool: + path = args[0] + if isinstance(path, File): FeatureNew('fs.is_absolute with file', '0.59.0').use(state.subproject, location=state.current_node) - return PurePath(str(args[0])).is_absolute() + path = str(path) + if is_windows(): + # os.path.isabs was broken for Windows before Python 3.13, so we implement it ourselves + path = path[:3].replace(posixsep, ntsep) + return path.startswith(ntsep * 2) or path.startswith(':' + ntsep, 1) + return path.startswith(posixsep) @noKwargs @FeatureNew('fs.as_posix', '0.54.0') @typed_pos_args('fs.as_posix', str) - def as_posix(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: + def as_posix(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: r""" this function assumes you are passing a Windows path, even if on a Unix-like system and so ALL '\' are turned to '/', even if you meant to escape a character """ - return PureWindowsPath(args[0]).as_posix() + return args[0].replace(ntsep, posixsep) @noKwargs @typed_pos_args('fs.exists', str) - def exists(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: - return self._resolve_dir(state, args[0]).exists() + def exists(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: + return os.path.exists(self._resolve_dir(state, args[0])) @noKwargs @typed_pos_args('fs.is_symlink', (str, File)) - def is_symlink(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: T.Dict[str, T.Any]) -> bool: + def is_symlink(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: T.Dict[str, T.Any]) -> bool: if isinstance(args[0], File): FeatureNew('fs.is_symlink with file', '0.59.0').use(state.subproject, location=state.current_node) - return self._absolute_dir(state, args[0]).is_symlink() + return os.path.islink(self._absolute_dir(state, args[0])) @noKwargs @typed_pos_args('fs.is_file', str) - def is_file(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: - return self._resolve_dir(state, args[0]).is_file() + def is_file(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: + return os.path.isfile(self._resolve_dir(state, args[0])) @noKwargs @typed_pos_args('fs.is_dir', str) - def is_dir(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: - return self._resolve_dir(state, args[0]).is_dir() + def is_dir(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: + return os.path.isdir(self._resolve_dir(state, args[0])) @noKwargs @typed_pos_args('fs.hash', (str, File), str) - def hash(self, state: 'ModuleState', args: T.Tuple['FileOrString', str], kwargs: T.Dict[str, T.Any]) -> str: + def hash(self, state: ModuleState, args: T.Tuple[FileOrString, str], kwargs: T.Dict[str, T.Any]) -> str: if isinstance(args[0], File): FeatureNew('fs.hash with file', '0.59.0').use(state.subproject, location=state.current_node) file = self._resolve_dir(state, args[0]) - if not file.is_file(): + if not os.path.isfile(file): raise MesonException(f'{file} is not a file and therefore cannot be hashed') try: h = hashlib.new(args[1]) except ValueError: raise MesonException('hash algorithm {} is not available'.format(args[1])) - mlog.debug('computing {} sum of {} size {} bytes'.format(args[1], file, file.stat().st_size)) - h.update(file.read_bytes()) + mlog.debug('computing {} sum of {} size {} bytes'.format(args[1], file, os.stat(file).st_size)) + with open(file, mode='rb', buffering=0) as f: + h.update(f.read()) return h.hexdigest() @noKwargs @typed_pos_args('fs.size', (str, File)) - def size(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: T.Dict[str, T.Any]) -> int: + def size(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: T.Dict[str, T.Any]) -> int: if isinstance(args[0], File): FeatureNew('fs.size with file', '0.59.0').use(state.subproject, location=state.current_node) file = self._resolve_dir(state, args[0]) - if not file.is_file(): + if not os.path.isfile(file): raise MesonException(f'{file} is not a file and therefore cannot be sized') try: - return file.stat().st_size + return os.stat(file).st_size except ValueError: raise MesonException('{} size could not be determined'.format(args[0])) @noKwargs @typed_pos_args('fs.is_samepath', (str, File), (str, File)) - def is_samepath(self, state: 'ModuleState', args: T.Tuple['FileOrString', 'FileOrString'], kwargs: T.Dict[str, T.Any]) -> bool: + def is_samepath(self, state: ModuleState, args: T.Tuple[FileOrString, FileOrString], kwargs: T.Dict[str, T.Any]) -> bool: if isinstance(args[0], File) or isinstance(args[1], File): FeatureNew('fs.is_samepath with file', '0.59.0').use(state.subproject, location=state.current_node) file1 = self._resolve_dir(state, args[0]) file2 = self._resolve_dir(state, args[1]) - if not file1.exists(): + if not os.path.exists(file1): return False - if not file2.exists(): + if not os.path.exists(file2): return False try: - return file1.samefile(file2) + return os.path.samefile(file1, file2) except OSError: return False @noKwargs @typed_pos_args('fs.replace_suffix', (str, File, CustomTarget, CustomTargetIndex, BuildTarget), str) - def replace_suffix(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes], str], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.replace_suffix', args[0], state) - return str(path.with_suffix(args[1])) + def replace_suffix(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes], str], kwargs: T.Dict[str, T.Any]) -> str: + if args[1] and not args[1].startswith('.'): + raise ValueError(f"Invalid suffix {args[1]!r}") + path = self._obj_to_pathstr('fs.replace_suffix', args[0], state) + return os.path.splitext(path)[0] + args[1] @noKwargs @typed_pos_args('fs.parent', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) - def parent(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.parent', args[0], state) - return str(path.parent) + def parent(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.parent', args[0], state) + return os.path.split(path)[0] or '.' @noKwargs @typed_pos_args('fs.name', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) - def name(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.name', args[0], state) - return str(path.name) + def name(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.name', args[0], state) + return os.path.basename(path) @noKwargs @typed_pos_args('fs.stem', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) @FeatureNew('fs.stem', '0.54.0') - def stem(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.stem', args[0], state) - return str(path.stem) + def stem(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.name', args[0], state) + return os.path.splitext(os.path.basename(path))[0] + + @noKwargs + @typed_pos_args('fs.suffix', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) + @FeatureNew('fs.suffix', '1.9.0') + def suffix(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.suffix', args[0], state) + return os.path.splitext(path)[1] @FeatureNew('fs.read', '0.57.0') @typed_pos_args('fs.read', (str, File)) @typed_kwargs('fs.read', KwargInfo('encoding', str, default='utf-8')) - def read(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: 'ReadKwArgs') -> str: + def read(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: ReadKwArgs) -> str: """Read a file from the source tree and return its value as a decoded string. diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py index c74283c..d4549c0 100755 --- a/mesonbuild/msubprojects.py +++ b/mesonbuild/msubprojects.py @@ -4,6 +4,7 @@ from dataclasses import dataclass, InitVar import os, subprocess import argparse import asyncio +import fnmatch import threading import copy import shutil @@ -640,9 +641,14 @@ def add_common_arguments(p: argparse.ArgumentParser) -> None: p.add_argument('--allow-insecure', default=False, action='store_true', help='Allow insecure server connections.') -def add_subprojects_argument(p: argparse.ArgumentParser) -> None: - p.add_argument('subprojects', nargs='*', - help='List of subprojects (default: all)') +def add_subprojects_argument(p: argparse.ArgumentParser, name: str = None) -> None: + helpstr = 'Patterns of subprojects to operate on (default: all)' + if name: + p.add_argument(name, dest='subprojects', metavar='pattern', nargs=1, action='append', + default=[], help=helpstr) + else: + p.add_argument('subprojects', metavar='pattern', nargs='*', default=[], + help=helpstr) def add_wrap_update_parser(subparsers: 'SubParsers') -> argparse.ArgumentParser: p = subparsers.add_parser('update', help='Update wrap files from WrapDB (Since 0.63.0)') @@ -692,7 +698,7 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: p.add_argument('args', nargs=argparse.REMAINDER, help=argparse.SUPPRESS) add_common_arguments(p) - p.set_defaults(subprojects=[]) + add_subprojects_argument(p, '--filter') p.set_defaults(subprojects_func=Runner.foreach) p = subparsers.add_parser('purge', help='Remove all wrap-based subproject artifacts') @@ -724,7 +730,8 @@ def run(options: 'Arguments') -> int: return 0 r = Resolver(source_dir, subproject_dir, wrap_frontend=True, allow_insecure=options.allow_insecure, silent=True) if options.subprojects: - wraps = [wrap for name, wrap in r.wraps.items() if name in options.subprojects] + wraps = [wrap for name, wrap in r.wraps.items() + if any(fnmatch.fnmatch(name, pat) for pat in options.subprojects)] else: wraps = list(r.wraps.values()) types = [t.strip() for t in options.types.split(',')] if options.types else [] |