diff --git a/README.md b/README.md index ba1626045..cc60ef528 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,9 @@ editable.verbose = true # Rebuild the project when the package is imported. editable.rebuild = false +# Build directory to use when :confval:`editable.mode` is ``inplace``. +editable.build-dir = "" + # Extra args to pass directly to the builder in the build step. build.tool-args = [] @@ -281,7 +284,7 @@ strict-config = true experimental = false # If set, this will provide a method for backward compatibility. -minimum-version = "0.11" # current version +minimum-version = "0.1" # current version # The CMake build directory. Defaults to a unique temporary directory. build-dir = "" diff --git a/docs/reference/configs.md b/docs/reference/configs.md index 8c6e8b857..9038e261f 100644 --- a/docs/reference/configs.md +++ b/docs/reference/configs.md @@ -247,6 +247,15 @@ print(mk_skbuild_docs()) ## editable +```{eval-rst} +.. confval:: editable.build-dir + :type: ``str`` + + Build directory to use when :confval:`editable.mode` is ``inplace``. + + If empty, the project source directory is used (the historical behaviour). +``` + ```{eval-rst} .. confval:: editable.mode :type: ``"redirect" | "inplace"`` diff --git a/src/scikit_build_core/build/wheel.py b/src/scikit_build_core/build/wheel.py index 670419d33..6d5cde228 100644 --- a/src/scikit_build_core/build/wheel.py +++ b/src/scikit_build_core/build/wheel.py @@ -279,24 +279,27 @@ def _build_wheel_impl_impl( root_is_purelib=targetlib == "purelib", build_tag=settings.wheel.build_tag, ) + format_data = pyproject_format( + settings=settings, + tags=tags, + state=state, + ) # A build dir can be specified, otherwise use a temporary directory + log_build_dir = True if cmake is not None and editable and settings.editable.mode == "inplace": - build_dir = settings.cmake.source_dir + if settings.editable.build_dir: + build_dir = Path(settings.editable.build_dir.format(**format_data)) + else: + build_dir = settings.cmake.source_dir + log_build_dir = False else: build_dir = ( - Path( - settings.build_dir.format( - **pyproject_format( - settings=settings, - tags=tags, - state=state, - ) - ) - ) + Path(settings.build_dir.format(**format_data)) if settings.build_dir else build_tmp_folder / "build" ) + if log_build_dir: logger.info("Build directory: {}", build_dir.resolve()) wheel_dirs = { @@ -426,6 +429,12 @@ def _build_wheel_impl_impl( f"SKBUILD_{k.upper()}_DIR": v for k, v in wheel_dirs.items() } cache_entries["SKBUILD_STATE"] = state + if editable: + cache_entries["SKBUILD_EDITABLE_MODE"] = settings.editable.mode + if settings.editable.build_dir: + cache_entries["SKBUILD_EDITABLE_BUILD_DIR"] = os.fspath( + build_dir.resolve() + ) builder.configure( defines=defines, cache_entries=cache_entries, diff --git a/src/scikit_build_core/resources/scikit-build.schema.json b/src/scikit_build_core/resources/scikit-build.schema.json index 5065b4153..7eb9c594c 100644 --- a/src/scikit_build_core/resources/scikit-build.schema.json +++ b/src/scikit_build_core/resources/scikit-build.schema.json @@ -274,6 +274,11 @@ "type": "boolean", "default": false, "description": "Rebuild the project when the package is imported." + }, + "build-dir": { + "type": "string", + "default": "", + "description": "Build directory to use when :confval:`editable.mode` is ``inplace``." } } }, diff --git a/src/scikit_build_core/settings/skbuild_model.py b/src/scikit_build_core/settings/skbuild_model.py index a83abd2f8..bd7950a73 100644 --- a/src/scikit_build_core/settings/skbuild_model.py +++ b/src/scikit_build_core/settings/skbuild_model.py @@ -344,6 +344,13 @@ class EditableSettings: :confval:`build-dir` must be set. """ + build_dir: str = "" + """ + Build directory to use when :confval:`editable.mode` is ``inplace``. + + If empty, the project source directory is used (the historical behaviour). + """ + @dataclasses.dataclass class BuildSettings: diff --git a/tests/packages/simplest_c/CMakeLists.txt b/tests/packages/simplest_c/CMakeLists.txt index 9d6caab25..1ba593056 100644 --- a/tests/packages/simplest_c/CMakeLists.txt +++ b/tests/packages/simplest_c/CMakeLists.txt @@ -14,11 +14,16 @@ install( DESTINATION ${SKBUILD_PROJECT_NAME} COMPONENT PythonModule) -if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}" - AND DEFINED SKBUILD) - # Editable in-place builds. THe empty generator expression ensures - # multi-config enerators keeps us from having to set - # LIBRARY_OUTPUT_DIRECTORY_ too. +if(DEFINED SKBUILD_EDITABLE_MODE AND SKBUILD_EDITABLE_MODE STREQUAL "inplace") + # Editable in-place builds using a dedicated build directory. + set_target_properties( + _module PROPERTIES LIBRARY_OUTPUT_DIRECTORY + "${CMAKE_SOURCE_DIR}/src/${SKBUILD_PROJECT_NAME}$<0:>") +elseif("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}" + AND DEFINED SKBUILD) + # Editable in-place builds with an in-source build directory. The empty + # generator expression ensures multi-config generators keep us from having to + # set LIBRARY_OUTPUT_DIRECTORY_ too. set_target_properties( _module PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/src/${SKBUILD_PROJECT_NAME}$<0:>") diff --git a/tests/test_editable.py b/tests/test_editable.py index 9ff5b52dc..abe076334 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -64,11 +64,13 @@ def test_navigate_editable(isolated, isolate, package): def test_cython_pxd(monkeypatch, tmp_path, editable, editable_mode, isolated): editable_flag = ["-e"] if editable else [] - config_mode_flags = [] + config_mode_flags = ["--config-settings=build-dir=build/{wheel_tag}"] if editable: config_mode_flags.append(f"--config-settings=editable.mode={editable_mode}") - if editable_mode != "inplace": - config_mode_flags.append("--config-settings=build-dir=build/{wheel_tag}") + if editable_mode == "inplace": + config_mode_flags.append( + "--config-settings=editable.build-dir=build/{wheel_tag}/editable" + ) package1 = PackageInfo( "cython_pxd_editable/pkg1", @@ -170,11 +172,13 @@ def _setup_package_for_editable_layout_tests( ) -> None: editable_flag = ["-e"] if editable else [] - config_mode_flags = [] + config_mode_flags = ["--config-settings=build-dir=build/{wheel_tag}"] if editable: config_mode_flags.append(f"--config-settings=editable.mode={editable_mode}") - if editable_mode != "inplace": - config_mode_flags.append("--config-settings=build-dir=build/{wheel_tag}") + if editable_mode == "inplace": + config_mode_flags.append( + "--config-settings=editable.build-dir=build/{wheel_tag}/editable" + ) # Use a context so that we only change into the directory up until the point where # we run the editable install. We do not want to be in that directory when importing diff --git a/tests/test_pyproject_pep660.py b/tests/test_pyproject_pep660.py index 110bd6806..5f7757efe 100644 --- a/tests/test_pyproject_pep660.py +++ b/tests/test_pyproject_pep660.py @@ -25,7 +25,13 @@ def editable_mode(request: pytest.FixtureRequest) -> str: @pytest.mark.usefixtures("package_simplest_c") def test_pep660_wheel(editable_mode: str, tmp_path: Path): dist = tmp_path / "dist" - out = build_editable(str(dist), {"editable.mode": editable_mode}) + config_settings = { + "build-dir": "build/{wheel_tag}", + "editable.mode": editable_mode, + } + if editable_mode == "inplace": + config_settings["editable.build-dir"] = "build/{wheel_tag}/editable" + out = build_editable(str(dist), config_settings) (wheel,) = dist.glob("simplest-0.0.1-*.whl") assert wheel == dist / out @@ -61,12 +67,17 @@ def test_pep660_pip_isolated(isolated, isolate, editable_mode: str): if not isolate: isolated.install("scikit-build-core") - build_dir = "" if editable_mode == "inplace" else "build/{wheel_tag}" - + config_flags = [ + "--config-settings=build-dir=build/{wheel_tag}", + f"--config-settings=editable.mode={editable_mode}", + ] + if editable_mode == "inplace": + config_flags.append( + "--config-settings=editable.build-dir=build/{wheel_tag}/editable" + ) isolated.install( "-v", - f"--config-settings=build-dir={build_dir}", - f"--config-settings=editable.mode={editable_mode}", + *config_flags, *isolate_args, "-e", ".",