diff --git a/mesonbuild/build.py b/mesonbuild/build.py index cca83f321697..938b041c935a 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -440,18 +440,9 @@ def get_custom_targets(self) -> OrderedDict[str, CustomTarget]: return custom_targets def copy(self) -> Build: - def copy_value(v: _T) -> _T: - if isinstance(v, PerMachine): - build = copy_value(v.build) - host = build if v.build is v.host else copy_value(v.host) - return PerMachine(build=build, host=host) - if isinstance(v, (list, dict, set, OrderedDict)): - return v.copy() - return v - other = Build.__new__(Build) for k, v in self.__dict__.items(): - other.__dict__[k] = copy_value(v) + other.__dict__[k] = copy.copy(v) return other def merge(self, other: Build) -> None: @@ -822,7 +813,11 @@ def __init__( environment: Environment, compilers: CompilerDict, kwargs: BuildTargetKeywordArguments): - super().__init__(name, subdir, subproject, True, for_machine, environment, install=kwargs.get('install', False), build_subdir=kwargs.get('build_subdir', '')) + super().__init__(name, subdir, subproject, kwargs.get('build_by_default', True), + for_machine, environment, + install=kwargs.get('install', False), + build_subdir=kwargs.get('build_subdir', '')) + self.original_kwargs = kwargs # all_compilers is a reference to Interpreter.compilers, as such we # cannot mutate it inside build. Use a Mapping to get help from the # static type checker @@ -848,7 +843,7 @@ def __init__( # as Vala which generates .vapi and .h besides the compiled output. self.outputs = [self.filename] self.pch: T.Dict[Language, T.Optional[T.Tuple[str, T.Optional[str]]]] = {} - self.extra_args: T.DefaultDict[Language, T.List[str]] = kwargs.get('language_args', defaultdict(list)) + self.extra_args = kwargs.get('language_args', T.cast('T.DefaultDict[Language, list[str]]', defaultdict(list))) self.sources: T.List[File] = [] # If the same source is defined multiple times, use it only once. self.seen_sources: T.Set[File] = set() @@ -873,7 +868,62 @@ def __init__( # 1. Preexisting objects provided by the user with the `objects:` kwarg # 2. Compiled objects created by and extracted from another target self.process_objectlist(objects) - self.process_kwargs(kwargs) + + if not self.build_by_default and kwargs.get('install', False): + # For backward compatibility, if build_by_default is not explicitly + # set, use the value of 'install' if it's enabled. + self.build_by_default = True + + self.raw_overrides = kwargs.get('override_options', {}) + + self.pch['c'] = kwargs.get('c_pch') + self.pch['cpp'] = kwargs.get('cpp_pch') + + self.link_args = kwargs.get('link_args', []) + for l in self.link_args: + if '-Wl,-rpath' in l or l.startswith('-rpath'): + mlog.warning(textwrap.dedent('''\ + Please do not define rpath with a linker argument, use install_rpath + or build_rpath properties instead. + This will become a hard error in a future Meson release. + ''')) + self.link_early_args = kwargs.get('link_early_args', []) + self.process_link_depends(kwargs.get('link_depends', [])) + # Target-specific include dirs must be added BEFORE include dirs from + # internal deps (added inside self.add_deps()) to override them. + self.add_include_dirs(kwargs.get('include_directories', [])) + # Add dependencies (which also have include_directories) + self.add_deps(kwargs.get('dependencies', [])) + + self.has_custom_install_dir = False + i = kwargs.get('install_dir', []) + install_dir = i[0] if i else True + default_install_dir = self.get_default_install_dir()[0] + if install_dir is True: + install_dir = default_install_dir + elif install_dir != default_install_dir: + self.has_custom_install_dir = True + self.install_dir: T.List[T.Union[str, T.Literal[False]]] = [install_dir] + self.install_mode = kwargs.get('install_mode', None) + self.install_tag: T.List[T.Optional[str]] = kwargs.get('install_tag') or [None] + self.extra_files = kwargs.get('extra_files', []) + self.install_rpath: str = kwargs.get('install_rpath', '') + self.build_rpath = kwargs.get('build_rpath', '') + self.resources = kwargs.get('resources', []) + name_prefix = kwargs.get('name_prefix') + if name_prefix is not None: + self.prefix = name_prefix + self.name_prefix_set = True + name_suffix = kwargs.get('name_suffix') + if name_suffix is not None: + self.suffix = name_suffix + self.name_suffix_set = True + self.implicit_include_directories = kwargs.get('implicit_include_directories', True) + self.gnu_symbol_visibility = kwargs.get('gnu_symbol_visibility', '') + self.rust_dependency_map = kwargs.get('rust_dependency_map', {}) + + self.swift_interoperability_mode = kwargs.get('swift_interoperability_mode', 'c') + self.swift_module_name = kwargs.get('swift_module_name') or self.name self.missing_languages = self.process_compilers() self.single_compile_base_args: T.Dict[Compiler, ImmutableListProtocol[str]] = {} @@ -1002,7 +1052,7 @@ def process_objectlist(self, objects: T.List[ObjectTypes]) -> None: FeatureDeprecated.single_use(f'Source file {deprecated_non_objects[0]} in the \'objects\' kwarg is not an object.', '1.3.0', self.subproject) - def process_sourcelist(self, sources: T.List['SourceOutputs']) -> None: + def process_sourcelist(self, sources: T.Iterable['SourceOutputs']) -> None: """Split sources into generated and static sources. Sources can be: @@ -1323,8 +1373,9 @@ def get_transitive_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]: result: T.Dict[str, str] = {} for i in itertools.chain(self.link_targets, self.link_whole_targets): mapping = i.get_link_deps_mapping(prefix) - #we are merging two dictionaries, while keeping the earlier one dominant - result_tmp = mapping.copy() + # we are merging two dictionaries, while keeping the earlier one dominant + # use dict() both to copy and change type + result_tmp = dict(mapping) result_tmp.update(result) result = result_tmp return result @@ -1367,72 +1418,6 @@ def get_custom_install_mode(self) -> T.Optional['FileMode']: def get_override(self, name: str) -> T.Optional[ElementaryOptionValues]: return self.raw_overrides.get(name, None) - def process_kwargs(self, kwargs: BuildTargetKeywordArguments) -> None: - self.original_kwargs = kwargs - - if 'build_by_default' in kwargs: - self.build_by_default = kwargs['build_by_default'] - - if not self.build_by_default and kwargs.get('install', False): - # For backward compatibility, if build_by_default is not explicitly - # set, use the value of 'install' if it's enabled. - self.build_by_default = True - - self.raw_overrides = kwargs.get('override_options', {}) - - self.pch['c'] = kwargs.get('c_pch') - self.pch['cpp'] = kwargs.get('cpp_pch') - - self.link_args = kwargs.get('link_args', []) - for l in self.link_args: - if '-Wl,-rpath' in l or l.startswith('-rpath'): - mlog.warning(textwrap.dedent('''\ - Please do not define rpath with a linker argument, use install_rpath - or build_rpath properties instead. - This will become a hard error in a future Meson release. - ''')) - self.link_early_args = kwargs.get('link_early_args', []) - for i in self.link_early_args: - if not isinstance(i, str): - raise InvalidArguments('link_early_args values must be strings.') - self.process_link_depends(kwargs.get('link_depends', [])) - # Target-specific include dirs must be added BEFORE include dirs from - # internal deps (added inside self.add_deps()) to override them. - self.add_include_dirs(kwargs.get('include_directories', [])) - # Add dependencies (which also have include_directories) - self.add_deps(kwargs.get('dependencies', [])) - - self.has_custom_install_dir = False - i = kwargs.get('install_dir', []) - install_dir = i[0] if i else True - default_install_dir = self.get_default_install_dir()[0] - if install_dir is True: - install_dir = default_install_dir - elif install_dir != default_install_dir: - self.has_custom_install_dir = True - self.install_dir: T.List[T.Union[str, T.Literal[False]]] = [install_dir] - - self.install_mode = kwargs.get('install_mode', None) - self.install_tag: T.List[T.Optional[str]] = kwargs.get('install_tag') or [None] - self.extra_files = kwargs.get('extra_files', []) - self.install_rpath: str = kwargs.get('install_rpath', '') - self.build_rpath = kwargs.get('build_rpath', '') - self.resources = kwargs.get('resources', []) - name_prefix = kwargs.get('name_prefix') - if name_prefix is not None: - self.prefix = name_prefix - self.name_prefix_set = True - name_suffix = kwargs.get('name_suffix') - if name_suffix is not None: - self.suffix = name_suffix - self.name_suffix_set = True - self.implicit_include_directories = kwargs.get('implicit_include_directories', True) - self.gnu_symbol_visibility = kwargs.get('gnu_symbol_visibility', '') - self.rust_dependency_map = kwargs.get('rust_dependency_map', {}) - - self.swift_interoperability_mode = kwargs.get('swift_interoperability_mode', 'c') - self.swift_module_name = kwargs.get('swift_module_name') or self.name - @T.overload def _extract_pic_pie(self, kwargs: StaticLibraryKeywordArguments, arg: Literal['pic'], option: Literal['b_staticpic']) -> bool: ... @@ -2109,7 +2094,7 @@ def process_files(self, files: T.Iterable[T.Union[str, File, GeneratedTypes]], preserve_path_from, extra_args=extra_args if extra_args is not None else [], env=env if env is not None else EnvironmentVariables(), - extra_depends=extra_depends if extra_depends is not None else []) + extra_depends=list(extra_depends) if extra_depends is not None else []) for e in files: if isinstance(e, (CustomTarget, CustomTargetIndex)): @@ -2119,7 +2104,9 @@ def process_files(self, files: T.Iterable[T.Union[str, File, GeneratedTypes]], if preserve_path_from: raise InvalidArguments("generator.process: 'preserve_path_from' is not allowed if one input is a 'generated_list'.") output.depends.add(e) - fs = [FileInTargetPrivateDir(f) for f in e.get_outputs()] + output.add_files(FileMaybeInTargetPrivateDir(FileInTargetPrivateDir(f)) + for f in e.get_outputs()) + continue elif isinstance(e, str): fs = [File.from_source_file(self.environment.source_dir, subdir, e)] else: @@ -2186,6 +2173,10 @@ def get_preserved_path_segment(self, infile: FileMaybeInTargetPrivateDir) -> str rel = os.path.relpath(in_abs, self.preserve_path_from) return os.path.dirname(rel) + def add_files(self, newfiles: T.Iterable[FileMaybeInTargetPrivateDir]) -> None: + for f in newfiles: + self.add_file(f) + def add_file(self, newfile: FileMaybeInTargetPrivateDir) -> None: self.infilelist.append(newfile) outfiles = self.generator.get_base_outnames(newfile.fname) @@ -2213,6 +2204,9 @@ def get_extra_args(self) -> T.List[str]: def get_subdir(self) -> str: return self.subdir + def get_basename(self) -> str: + return self.generator.name + class Executable(BuildTarget): known_kwargs = known_exe_kwargs @@ -2542,21 +2536,44 @@ def __init__( environment: Environment, compilers: CompilerDict, kwargs: SharedLibraryKeywordArguments): - self.soversion: T.Optional[str] = None - self.ltversion: T.Optional[str] = None + super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, + environment, compilers, kwargs) # Max length 2, first element is compatibility_version, second is current_version self.darwin_versions: T.Optional[T.Tuple[str, str]] = None - self.vs_module_defs = None - self.shortname: T.Optional[str] = None + self.soversion: T.Optional[str] = None + self.ltversion: T.Optional[str] = None + if not self.environment.machines[self.for_machine].is_android(): + # Shared library version + self.ltversion = kwargs.get('version') + self.soversion = kwargs.get('soversion') + if self.soversion is None and self.ltversion is not None: + # library version is defined, get the soversion from that + # We replicate what Autotools does here and take the first + # number of the version by default. + self.soversion = self.ltversion.split('.')[0] + # macOS, iOS and tvOS dylib compatibility_version and current_version + self.darwin_versions = kwargs.get('darwin_versions') + if self.darwin_versions is None and self.soversion is not None: + # If unspecified, pick the soversion + self.darwin_versions = (self.soversion, self.soversion) + + self.vs_module_defs: File | None = None + # Visual Studio module-definitions file + self.process_vs_module_defs_kw(kwargs) + + # OS/2 uses a 8.3 name for a DLL + self.shortname = kwargs.get('shortname') + # The import library this target will generate self.import_filename: str | None = None + # The debugging information file this target will generate self.debug_filename: str | None = None + # Use by the pkgconfig module self.shared_library_only = False + self.rust_crate_type = kwargs.get('rust_crate_type', 'dylib') - super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, - environment, compilers, kwargs) def post_init(self) -> None: super().post_init() @@ -2576,7 +2593,7 @@ def post_init(self) -> None: def get_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]: result: T.Dict[str, str] = {} - mappings = self.get_transitive_link_deps_mapping(prefix) + mappings = dict(self.get_transitive_link_deps_mapping(prefix)) old = get_target_macos_dylib_install_name(self) if old not in mappings: fname = self.get_filename() @@ -2744,30 +2761,6 @@ def determine_filenames(self) -> None: if create_debug_file: self.debug_filename = os.path.splitext(self.filename)[0] + '.pdb' - def process_kwargs(self, kwargs: SharedLibraryKeywordArguments) -> None: - super().process_kwargs(kwargs) - - if not self.environment.machines[self.for_machine].is_android(): - # Shared library version - self.ltversion = kwargs.get('version') - self.soversion = kwargs.get('soversion') - if self.soversion is None and self.ltversion is not None: - # library version is defined, get the soversion from that - # We replicate what Autotools does here and take the first - # number of the version by default. - self.soversion = self.ltversion.split('.')[0] - # macOS, iOS and tvOS dylib compatibility_version and current_version - self.darwin_versions = kwargs.get('darwin_versions') - if self.darwin_versions is None and self.soversion is not None: - # If unspecified, pick the soversion - self.darwin_versions = (self.soversion, self.soversion) - - # Visual Studio module-definitions file - self.process_vs_module_defs_kw(kwargs) - - # OS/2 uses a 8.3 name for a DLL - self.shortname = kwargs.get('shortname') - def get_import_filename(self) -> T.Optional[str]: """ The name of the import library that will be outputted by the compiler @@ -2877,10 +2870,6 @@ def __init__( environment: Environment, compilers: CompilerDict, kwargs: SharedModuleKeywordArguments): - if 'version' in kwargs: - raise MesonException('Shared modules must not specify the version kwarg.') - if 'soversion' in kwargs: - raise MesonException('Shared modules must not specify the soversion kwarg.') super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, compilers, # SharedModuleKeywordArguments is a subclass, it's annoying mypy can't figure this out @@ -2923,46 +2912,53 @@ def is_linkable_target(self) -> bool: # For polymorphism with build targets return True -class CommandBase: - depend_files: T.List[File] - dependencies: T.List[T.Union[BuildTarget, 'CustomTarget']] - subproject: SubProject +def flatten_command(cmd: T.Sequence[str | File | programs.Program | BuildTargetTypes], + subproject: SubProject) -> tuple[list[str | File | BuildTarget | CustomTarget | programs.Program], + list[File], list[BuildTarget | CustomTarget]]: + final_cmd: list[str | File | programs.Program | BuildTarget | CustomTarget] = [] + depend_files: list[File] = [] + dependencies: list[BuildTarget | CustomTarget] = [] + for c in cmd: + if isinstance(c, LocalProgram): + c = c.program + if isinstance(c, str): + final_cmd.append(c) + elif isinstance(c, File): + depend_files.append(c) + final_cmd.append(c) + elif isinstance(c, programs.Program): + if not c.found(): + raise InvalidArguments('Tried to use not-found external program in "command"') + path = c.get_path() + # We know path is not non if c.found() + assert path is not None, 'for mypy' + if os.path.isabs(path): + # Can only add a dependency on an external program which we + # know the absolute path of + depend_files.append(File.from_absolute_file(path)) + # Do NOT flatten -- it is needed for later parsing + final_cmd.append(c) + elif isinstance(c, (BuildTarget, CustomTarget)): + dependencies.append(c) + final_cmd.append(c) + elif isinstance(c, CustomTargetIndex): + FeatureNew.single_use('CustomTargetIndex for command argument', '0.60', subproject) + dependencies.append(c.target) + c, df, d = flatten_command([File.from_built_file(c.get_subdir(), c.get_filename())], subproject) + final_cmd.extend(c) + depend_files.extend(df) + dependencies.extend(d) + elif isinstance(c, list): + # TODO: is this case even reachable? + c, df, d = flatten_command(c, subproject) + final_cmd.extend(c) + depend_files.extend(df) + dependencies.extend(d) + else: + raise InvalidArguments(f'Argument {c!r} in "command" is invalid') + return final_cmd, depend_files, dependencies - def flatten_command(self, cmd: T.Sequence[T.Union[str, File, programs.Program, BuildTargetTypes]]) -> \ - T.List[T.Union[str, File, BuildTarget, CustomTarget, programs.Program]]: - cmd = listify(cmd) - final_cmd: T.List[T.Union[str, File, BuildTarget, 'CustomTarget']] = [] - for c in cmd: - if isinstance(c, LocalProgram): - c = c.program - if isinstance(c, str): - final_cmd.append(c) - elif isinstance(c, File): - self.depend_files.append(c) - final_cmd.append(c) - elif isinstance(c, programs.Program): - if not c.found(): - raise InvalidArguments('Tried to use not-found external program in "command"') - path = c.get_path() - if os.path.isabs(path): - # Can only add a dependency on an external program which we - # know the absolute path of - self.depend_files.append(File.from_absolute_file(path)) - # Do NOT flatten -- it is needed for later parsing - final_cmd.append(c) - elif isinstance(c, (BuildTarget, CustomTarget)): - self.dependencies.append(c) - final_cmd.append(c) - elif isinstance(c, CustomTargetIndex): - FeatureNew.single_use('CustomTargetIndex for command argument', '0.60', self.subproject) - self.dependencies.append(c.target) - final_cmd += self.flatten_command(File.from_built_file(c.get_subdir(), c.get_filename())) - elif isinstance(c, list): - final_cmd += self.flatten_command(c) - else: - raise InvalidArguments(f'Argument {c!r} in "command" is invalid') - return final_cmd class CustomTargetBase: ''' Base class for CustomTarget and CustomTargetIndex @@ -2993,7 +2989,7 @@ def get(self, lib_type: T.Literal['static', 'shared'], recursive: bool = False) """Base case used by BothLibraries""" return self -class CustomTarget(Target, CustomTargetBase, CommandBase): +class CustomTarget(Target, CustomTargetBase): typename = 'custom' @@ -3012,7 +3008,7 @@ def __init__(self, build_by_default: T.Optional[bool] = None, capture: bool = False, console: bool = False, - depend_files: T.Optional[T.Sequence[FileOrString]] = None, + depend_files: list[File] | None = None, extra_depends: T.Optional[T.Sequence[TargetDepends]] = None, depfile: T.Optional[str] = None, depfile_type: T.Optional[Literal['gcc', 'msvc']] = None, @@ -3042,7 +3038,10 @@ def __init__(self, self.depend_files = list(depend_files or []) self.dependencies: T.List[T.Union[CustomTarget, BuildTarget]] = [] # must be after depend_files and dependencies - self.command = self.flatten_command(command) + c, df, d = flatten_command(command, self.subproject) + self.command = c + self.depend_files.extend(df) + self.dependencies.extend(d) self.depfile = depfile self.depfile_type = 'gcc' if depfile else depfile_type self.env = env or EnvironmentVariables() @@ -3135,7 +3134,7 @@ def get_outputs(self) -> T.List[str]: def get_filename(self) -> str: return self.outputs[0] - def get_sources(self) -> T.List[T.Union[str, File, BuildTarget, GeneratedTypes, ExtractedObjects, programs.Program]]: + def get_sources(self) -> T.List[CustomTargetSources]: return self.sources def get_generated_lists(self) -> T.List[GeneratedList]: @@ -3201,7 +3200,7 @@ def is_internal(self) -> bool: return CustomTargetIndex(self, self.outputs[0]).is_internal() def extract_all_objects(self) -> T.List[T.Union[str, 'ExtractedObjects']]: - return self.get_outputs() + return T.cast('list[str | ExtractedObjects]', self.get_outputs()) def type_suffix(self) -> str: return "@cus" @@ -3281,7 +3280,7 @@ def get_generated_headers(self) -> T.List[File]: gen_headers += [File(True, dep.subdir, o) for o in dep.get_outputs()] return gen_headers -class RunTarget(Target, CommandBase): +class RunTarget(Target): typename = 'run' @@ -3297,8 +3296,8 @@ def __init__(self, name: str, # These don't produce output artifacts super().__init__(name, subdir, subproject, False, MachineChoice.BUILD, environment) self.dependencies = list(dependencies) - self.depend_files = [] - self.command = self.flatten_command(command) + self.command, self.depend_files, d = flatten_command(command, subproject) + self.dependencies.extend(d) self.absolute_paths = False self.env = env self.default_env = default_env diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index e92d3f0e2bb7..56294495bcbd 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -555,7 +555,7 @@ def compile_resources(self, state: 'ModuleState', args: T.Tuple[str, CustomTarge def _get_gresource_dependencies( state: 'ModuleState', input_file: str, source_dirs: T.List[str], dependencies: T.Sequence[T.Union[mesonlib.File, CustomTarget, CustomTargetIndex]] - ) -> T.Tuple[T.List[mesonlib.FileOrString], T.List[T.Union[CustomTarget, CustomTargetIndex]], T.List[str]]: + ) -> T.Tuple[T.List[mesonlib.File], T.List[T.Union[CustomTarget, CustomTargetIndex]], T.List[str]]: cmd = ['glib-compile-resources', input_file, @@ -579,7 +579,7 @@ def _get_gresource_dependencies( depends: T.List[T.Union[CustomTarget, CustomTargetIndex]] = [] subdirs: T.List[str] = [] - dep_files: T.List[mesonlib.FileOrString] = [] + dep_files: T.List[mesonlib.File] = [] for resfile in raw_dep_files.copy(): resbasename = os.path.basename(resfile) for dep in dependencies: @@ -622,7 +622,7 @@ def _get_gresource_dependencies( 'keyword argument.') raw_dep_files.remove(resfile) dep_files.append(f) - dep_files.extend(raw_dep_files) + dep_files.extend(mesonlib.File.from_absolute_file(r) for r in raw_dep_files) return dep_files, depends, subdirs def _get_link_args(self, state: 'ModuleState', @@ -1296,7 +1296,7 @@ def compile_schemas(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs [], ['gschemas.compiled'], build_by_default=kwargs['build_by_default'], - depend_files=kwargs['depend_files'], + depend_files=state._interpreter.source_strings_to_files(kwargs['depend_files']), description='Compiling gschemas {}', ) self._devenv_prepend('GSETTINGS_SCHEMA_DIR', os.path.join(state.environment.get_build_dir(), state.subdir)) diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index 198cbebd828e..d53ebe4fa6b5 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -119,7 +119,7 @@ def compile_resources(self, state: 'ModuleState', args: T.Tuple[T.List[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]]], kwargs: 'CompileResources') -> ModuleReturnValue: extra_args = kwargs['args'].copy() - wrc_depend_files = kwargs['depend_files'] + wrc_depend_files = state._interpreter.source_strings_to_files(kwargs['depend_files']) wrc_depends = kwargs['depends'] for d in wrc_depends: if isinstance(d, build.CustomTarget): diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index 0c71c2033400..0b1ddc7d53c8 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -8,6 +8,7 @@ from pathlib import Path import argparse import ast +import copy import enum import sys import stat @@ -573,6 +574,14 @@ def assign(self, build: _T, host: _T) -> None: self.build = build self.host = host + def __copy__(self) -> PerMachine[_T]: + build = copy.copy(self.build) + if self.host is self.build: + host = build + else: + host = copy.copy(self.host) + return PerMachine(build, host) + @dataclasses.dataclass(eq=False, order=False) class PerThreeMachine(PerMachine[_T]): @@ -605,6 +614,20 @@ def miss_defaulting(self) -> "PerThreeMachineDefaultable[T.Optional[_T]]": def matches_build_machine(self, machine: MachineChoice) -> bool: return self.build == self[machine] + def __copy__(self) -> PerMachine[_T]: + build = copy.copy(self.build) + if self.host is self.build: + host = build + else: + host = copy.copy(self.host) + + if self.target is self.host: + target = host + else: + target = copy.copy(self.target) + + return PerThreeMachine(build, host, target) + @dataclasses.dataclass(eq=False, order=False) class PerMachineDefaultable(PerMachine[T.Optional[_T]]):