diff --git a/pycheribuild/projects/docker_adduser.py b/pycheribuild/projects/docker_adduser.py index f8423d7b7..947465c01 100644 --- a/pycheribuild/projects/docker_adduser.py +++ b/pycheribuild/projects/docker_adduser.py @@ -42,7 +42,7 @@ class DockerAdduser(SimpleProject): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.build_dir = self.config.build_root / (self.target + "-build") + self._initial_build_dir = self.config.build_root / (self.target + "-build") def process(self): if not self.build_dir.is_dir(): diff --git a/pycheribuild/projects/project.py b/pycheribuild/projects/project.py index aa3fe42d3..15f2f1d2e 100644 --- a/pycheribuild/projects/project.py +++ b/pycheribuild/projects/project.py @@ -53,7 +53,7 @@ SubversionRepository, TargetBranchInfo, ) -from .simple_project import SimpleProject, _default_stdout_filter +from .simple_project import ReuseOtherProjectBuildDir, SimpleProject, _default_stdout_filter from ..config.chericonfig import BuildType, CheriConfig, ComputedDefaultValue, Linkage, supported_build_type_strings from ..config.config_loader_base import ConfigOptionHandle from ..config.target_info import ( @@ -101,6 +101,7 @@ "MakefileProject", "MercurialRepository", "Project", + "ReuseOtherProjectBuildDir", "ReuseOtherProjectDefaultTargetRepository", "ReuseOtherProjectRepository", "SubversionRepository", @@ -117,6 +118,9 @@ def install_dir_not_specified(_: CheriConfig, project: "Project"): def _default_build_dir(_: CheriConfig, project: "SimpleProject"): assert isinstance(project, Project) + if project._build_dir is not None and isinstance(project._build_dir, ReuseOtherProjectBuildDir): + # For projects that reuse other source directories, we return None to use the default for the source project. + return None return project.build_dir_for_target(project.crosscompile_target) @@ -475,10 +479,6 @@ def generate_cmakelists(self): def get_source_dir(cls, caller: AbstractProject, cross_target: "Optional[CrossCompileTarget]" = None) -> Path: return cls._get_instance_no_setup(caller, cross_target).source_dir - @classmethod - def get_build_dir(cls, caller: AbstractProject, cross_target: "Optional[CrossCompileTarget]" = None) -> Path: - return cls._get_instance_no_setup(caller, cross_target).build_dir - @classmethod def get_install_dir(cls, caller: AbstractProject, cross_target: "Optional[CrossCompileTarget]" = None) -> Path: return cls._get_instance_no_setup(caller, cross_target).real_install_root_dir @@ -641,7 +641,7 @@ def setup_config_options(cls, install_directory_help="", **kwargs) -> None: # supported target). default_xtarget = cls.default_architecture if cls._xtarget is not None or default_xtarget is not None: - cls.build_dir = cls.add_path_option( + cls._initial_build_dir = cls.add_path_option( "build-directory", metavar="DIR", default=cls.default_build_dir, @@ -940,7 +940,7 @@ def __init__(self, *args, **kwargs) -> None: assert not self.build_via_symlink_farm, "Using a symlink farm only makes sense with a separate build dir" if self.config.debug_output: self.info("Cannot build", self.target, "in a separate build dir, will build in", self.source_dir) - self.build_dir = self.source_dir + self._initial_build_dir = self.source_dir self.configure_command = None # non-assignable variables: diff --git a/pycheribuild/projects/simple_project.py b/pycheribuild/projects/simple_project.py index 6a9aac7f7..507896d9c 100644 --- a/pycheribuild/projects/simple_project.py +++ b/pycheribuild/projects/simple_project.py @@ -254,6 +254,26 @@ def __get__(self, instance: "SimpleProject", owner: "type[SimpleProject]"): return ValueError("Should have been replaced!") +class ReuseOtherProjectBuildDir: + def __init__( + self, + build_project: "type[SimpleProject]", + *, + subdirectory=".", + dir_for_target: "Optional[CrossCompileTarget]" = None, + do_update=False, + ): + self.build_project = build_project + self.subdirectory = subdirectory + self.dir_for_target = dir_for_target + self.do_update = do_update + + def get_real_build_dir(self, caller: "type[SimpleProject]", base_project_build_dir: Optional[Path]) -> Path: + if base_project_build_dir is not None: + return base_project_build_dir + return self.build_project.get_build_dir(caller, cross_target=self.dir_for_target) / self.subdirectory + + if typing.TYPE_CHECKING: def BoolConfigOption( # noqa: N802 @@ -374,7 +394,6 @@ class SimpleProject(AbstractProject, metaclass=ABCMeta if typing.TYPE_CHECKING e # freebsd-freebsd-amd64, etc.) include_os_in_target_suffix: bool = True source_dir: Optional[Path] = None - build_dir: Optional[Path] = None install_dir: Optional[Path] = None build_dir_suffix: str = "" # add a suffix to the build dir (e.g. for freebsd-with-bootstrap-clang) use_asan: bool = False @@ -406,6 +425,12 @@ class SimpleProject(AbstractProject, metaclass=ABCMeta if typing.TYPE_CHECKING e __checked_pkg_config: "dict[str, InstallInstructions]" = {} custom_target_name: "Optional[typing.Callable[[str, CrossCompileTarget], str]]" = None + _build_dir: Optional[Path] = None + _initial_build_dir: Optional[Path] = None + + @classmethod + def get_build_dir(cls, caller: AbstractProject, cross_target: "Optional[CrossCompileTarget]" = None) -> Path: + return cls._get_instance_no_setup(caller, cross_target).build_dir @classmethod def is_toolchain_target(cls) -> bool: @@ -1064,6 +1089,19 @@ def __init__(self, config: CheriConfig, *, crosscompile_target: CrossCompileTarg self._setup_late_called = False self._last_stdout_line_can_be_overwritten = False assert not hasattr(self, "gitBranch"), "gitBranch must not be used: " + self.__class__.__name__ + if self._build_dir is not None: + assert isinstance(self._build_dir, ReuseOtherProjectBuildDir) + initial_build_dir = inspect.getattr_static(self, "_initial_build_dir") + assert isinstance(initial_build_dir, ConfigOptionHandle) + # noinspection PyProtectedMember + assert ( + initial_build_dir._get_default_value(self.config, self) is None + ), "initial build dir != None for ReuseOtherProjectBuildDir" + self._initial_build_dir = self._build_dir.get_real_build_dir(self, self._initial_build_dir) + + @property + def build_dir(self) -> Path: + return self._initial_build_dir def setup(self) -> None: """ diff --git a/tests/test_argument_parsing.py b/tests/test_argument_parsing.py index a3dcab3b5..d669d9b5a 100644 --- a/tests/test_argument_parsing.py +++ b/tests/test_argument_parsing.py @@ -1249,13 +1249,13 @@ def test_mfs_root_kernel_config_options(): ] config_options.sort() assert config_options == [ + "_initial_build_dir", "_initial_source_dir", "_install_dir", "_linkage", "auto_var_init", "build_alternate_abi_kernels", "build_bench_kernels", - "build_dir", "build_fett_kernels", "build_fpga_kernels", "build_nocaprevoke_kernels", diff --git a/tests/test_async_delete.py b/tests/test_async_delete.py index ecaea061d..24b3c96a4 100644 --- a/tests/test_async_delete.py +++ b/tests/test_async_delete.py @@ -28,7 +28,7 @@ def __init__(self, config: MockConfig, name: str): self.expected_install = config.source_root / "install" / name self._install_dir = self.expected_install self.expected_build = Path(config.source_root, "build", name + "-build") - self.build_dir = self.expected_build + self._initial_build_dir = self.expected_build super().__init__(config, crosscompile_target=CompilationTargets.NATIVE) def setup(self):