Skip to content

Commit

Permalink
feat: SDist CMake option
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Schreiner <[email protected]>
  • Loading branch information
henryiii committed Aug 9, 2023
1 parent 308dcde commit 95dd864
Show file tree
Hide file tree
Showing 18 changed files with 267 additions and 15 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ sdist.exclude = []
# value if not set.
sdist.reproducible = true

# If set to True, CMake will be run before building the SDist.
sdist.cmake = false

# A list of packages to auto-copy into the wheel. If this is not set, it will
# default to the first of ``src/<package>`` or ``<package>`` if they exist. The
# prefix(s) will be stripped from the package name inside the wheel.
Expand All @@ -195,9 +198,11 @@ wheel.py-api = ""
# (before 21.0.1) find the correct wheel.
wheel.expand-macos-universal-tags = false

# The install directory for the wheel. This is relative to the platlib root.
# EXPERIMENTAL: An absolute path will be one level higher than the platlib root,
# giving access to "/platlib", "/data", "/headers", and "/scripts".
# The install directory for the wheel. This is relative to the platlib root. You
# might set this to the package name. The original dir is still at
# SKBUILD_PLATLIB_DIR (also SKBUILD_DATA_DIR, etc. are available). EXPERIMENTAL:
# An absolute path will be one level higher than the platlib root, giving access
# to "/platlib", "/data", "/headers", and "/scripts".
wheel.install-dir = ""

# A list of license files to include in the wheel. Supports glob patterns.
Expand Down
46 changes: 46 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,52 @@ reproducible builds if you prefer, however:
sdist.reproducible = false
```

You can also request CMake to run during this step:

```toml
[tool.scikit-build]
sdist.cmake = true
```

:::{note}

If you do this, you'll want to have some artifact from the configure in your
source directory; for example:

```cmake
include(FetchContent)
if(NOT SKBUILD_STATE STREQUAL "sdist"
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/pybind11/CMakeLists.txt")
message(STATUS "Using integrated pybind11")
set(FETCHCONTENT_FULLY_DISCONNECTED ON)
endif()
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG v2.11.1
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/pybind11)
set(PYBIND11_FINDPYTHON ON)
FetchContent_MakeAvailable(pybind11)
```

The `/pybind11` directory is in the `.gitignore` and important parts are in
`sdist.include`:

```toml
[tool.scikit-build]
sdist.cmake = true
sdist.include = [
"pybind11/tools",
"pybind11/include",
"pybind11/CMakeLists.txt",
]
```

:::

## Customizing the built wheel

The wheel will automatically look for Python packages at `<package_name>` and
Expand Down
10 changes: 9 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@

DIR = Path(__file__).parent.resolve()

nox.options.sessions = ["lint", "pylint", "tests"]
nox.options.sessions = [
"lint",
"pylint",
"generate_schema",
"readme",
"build_api_docs",
"tests",
"test_doc_examples",
]


@nox.session(reuse_venv=True)
Expand Down
6 changes: 6 additions & 0 deletions src/scikit_build_core/build/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,15 @@ def get_requires_for_build_sdist(

requires = GetRequires(config_settings)

# These are only injected if cmake is required for the SDist step
cmake_requires = (
[*requires.cmake(), *requires.ninja()] if requires.settings.sdist.cmake else []
)

return [
"pathspec",
"pyproject_metadata",
*cmake_requires,
*requires.dynamic_metadata(),
]

Expand Down
6 changes: 6 additions & 0 deletions src/scikit_build_core/build/sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from ..settings.skbuild_read_settings import SettingsReader
from ._file_processor import each_unignored_file
from ._init import setup_logging
from .wheel import _build_wheel_impl

__all__: list[str] = ["build_sdist"]

Expand Down Expand Up @@ -110,6 +111,11 @@ def build_sdist(
srcdirname = f"{sdist_name}-{metadata.version}"
filename = f"{srcdirname}.tar.gz"

if settings.sdist.cmake:
_build_wheel_impl(
None, config_settings, None, exit_after_config=True, editable=False
)

sdist_dir.mkdir(parents=True, exist_ok=True)
with contextlib.ExitStack() as stack:
gzip_container = stack.enter_context(
Expand Down
10 changes: 9 additions & 1 deletion src/scikit_build_core/build/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def _build_wheel_impl(
config_settings: dict[str, list[str] | str] | None,
metadata_directory: str | None,
*,
exit_after_config: bool = False,
editable: bool,
) -> WheelImplReturn:
"""
Expand All @@ -97,6 +98,8 @@ def _build_wheel_impl(
action = "editable" if editable else "wheel"
if wheel_directory is None:
action = f"metadata_{action}"
if exit_after_config:
action = "sdist"

cmake = CMake.default_search(minimum_version=settings.cmake.minimum_version)

Expand Down Expand Up @@ -176,7 +179,7 @@ def _build_wheel_impl(
config=config,
)

if wheel_directory is None:
if wheel_directory is None and not exit_after_config:
if metadata_directory is None:
msg = "metadata_directory must be specified if wheel_directory is None"
raise AssertionError(msg)
Expand Down Expand Up @@ -209,6 +212,11 @@ def _build_wheel_impl(
version=metadata.version,
)

if exit_after_config:
return WheelImplReturn("")

assert wheel_directory is not None

generator = builder.config.env.get(
"CMAKE_GENERATOR",
"MSVC"
Expand Down
13 changes: 9 additions & 4 deletions src/scikit_build_core/builder/get_requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)
from ..resources import resources
from ..settings._load_provider import load_provider
from ..settings.skbuild_model import ScikitBuildSettings
from ..settings.skbuild_read_settings import SettingsReader

__all__ = ["GetRequires"]
Expand Down Expand Up @@ -48,8 +49,12 @@ def __post_init__(self) -> None:
"pyproject.toml", self.config_settings
).settings

@property
def settings(self) -> ScikitBuildSettings:
return self._settings

def cmake(self) -> Generator[str, None, None]:
cmake_min = self._settings.cmake.minimum_version
cmake_min = self.settings.cmake.minimum_version
cmake = best_program(
get_cmake_programs(module=False), minimum_version=cmake_min
)
Expand All @@ -73,7 +78,7 @@ def ninja(self) -> Generator[str, None, None]:
if os.environ.get("CMAKE_MAKE_PROGRAM", ""):
return

ninja_min = self._settings.ninja.minimum_version
ninja_min = self.settings.ninja.minimum_version
ninja = best_program(
get_ninja_programs(module=False), minimum_version=ninja_min
)
Expand All @@ -82,7 +87,7 @@ def ninja(self) -> Generator[str, None, None]:
return

if (
self._settings.ninja.make_fallback
self.settings.ninja.make_fallback
and not is_known_platform(known_wheels("ninja"))
and list(get_make_programs())
):
Expand All @@ -93,7 +98,7 @@ def ninja(self) -> Generator[str, None, None]:
yield f"ninja>={ninja_min}"

def dynamic_metadata(self) -> Generator[str, None, None]:
for dynamic_metadata in self._settings.metadata.values():
for dynamic_metadata in self.settings.metadata.values():
if "provider" in dynamic_metadata:
config = dynamic_metadata.copy()
provider = config.pop("provider")
Expand Down
7 changes: 6 additions & 1 deletion src/scikit_build_core/resources/scikit-build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@
"type": "boolean",
"default": true,
"description": "If set to True, try to build a reproducible distribution (Unix and Python 3.9+ recommended). ``SOURCE_DATE_EPOCH`` will be used for timestamps, or a fixed value if not set."
},
"cmake": {
"type": "boolean",
"default": false,
"description": "If set to True, CMake will be run before building the SDist."
}
}
},
Expand Down Expand Up @@ -137,7 +142,7 @@
"install-dir": {
"type": "string",
"default": "",
"description": "The install directory for the wheel. This is relative to the platlib root. EXPERIMENTAL: An absolute path will be one level higher than the platlib root, giving access to \"/platlib\", \"/data\", \"/headers\", and \"/scripts\"."
"description": "The install directory for the wheel. This is relative to the platlib root. You might set this to the package name. The original dir is still at SKBUILD_PLATLIB_DIR (also SKBUILD_DATA_DIR, etc. are available). EXPERIMENTAL: An absolute path will be one level higher than the platlib root, giving access to \"/platlib\", \"/data\", \"/headers\", and \"/scripts\"."
},
"license-files": {
"type": "array",
Expand Down
7 changes: 7 additions & 0 deletions src/scikit_build_core/settings/skbuild_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ class SDistSettings:
fixed value if not set.
"""

cmake: bool = False
"""
If set to True, CMake will be run before building the SDist.
"""


@dataclasses.dataclass
class WheelSettings:
Expand Down Expand Up @@ -142,6 +147,8 @@ class WheelSettings:
install_dir: str = ""
"""
The install directory for the wheel. This is relative to the platlib root.
You might set this to the package name. The original dir is still at
SKBUILD_PLATLIB_DIR (also SKBUILD_DATA_DIR, etc. are available).
EXPERIMENTAL: An absolute path will be one level higher than the platlib
root, giving access to "/platlib", "/data", "/headers", and "/scripts".
"""
Expand Down
14 changes: 9 additions & 5 deletions src/scikit_build_core/setuptools/build_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
prepare_metadata_for_build_wheel,
)

from ..builder.get_requires import GetRequires

if hasattr(setuptools.build_meta, "build_editable"):
from setuptools.build_meta import build_editable

Expand Down Expand Up @@ -38,14 +40,18 @@ def get_requires_for_build_sdist(
setuptools_reqs = setuptools.build_meta.get_requires_for_build_sdist(
config_settings
)
return [*setuptools_reqs]
requires = GetRequires(config_settings)

# These are only injected if cmake is required for the SDist step
cmake_requires = (
[*requires.cmake(), *requires.ninja()] if requires.settings.sdist.cmake else []
)
return [*setuptools_reqs, *cmake_requires]


def get_requires_for_build_wheel(
config_settings: dict[str, str | list[str]] | None = None
) -> list[str]:
from ..builder.get_requires import GetRequires

requires = GetRequires(config_settings)

setuptools_reqs = setuptools.build_meta.get_requires_for_build_wheel(
Expand All @@ -60,8 +66,6 @@ def get_requires_for_build_wheel(
def get_requires_for_build_editable(
config_settings: dict[str, str | list[str]] | None = None
) -> list[str]:
from ..builder.get_requires import GetRequires

requires = GetRequires(config_settings)
setuptools_reqs = setuptools.build_meta.get_requires_for_build_editable(
config_settings
Expand Down
11 changes: 11 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,17 @@ def package_simplest_c(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Packa
return package


@pytest.fixture()
def package_sdist_config(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> PackageInfo:
package = PackageInfo(
"sdist_config",
)
process_package(package, tmp_path, monkeypatch)
return package


def pytest_collection_modifyitems(items: list[pytest.Item]) -> None:
for item in items:
# Ensure all tests using virtualenv are marked as such
Expand Down
1 change: 1 addition & 0 deletions tests/packages/sdist_config/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/pybind11
22 changes: 22 additions & 0 deletions tests/packages/sdist_config/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.15...3.27)
project(sdist_config LANGUAGES CXX)

include(FetchContent)

if(NOT SKBUILD_STATE STREQUAL "sdist"
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/pybind11/CMakeLists.txt")
message(STATUS "Using integrated pybind11")
set(FETCHCONTENT_FULLY_DISCONNECTED ON)
endif()

FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG v2.11.1
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/pybind11)

set(PYBIND11_FINDPYTHON ON)
FetchContent_MakeAvailable(pybind11)

pybind11_add_module(sdist_config main.cpp)
install(TARGETS sdist_config DESTINATION .)
7 changes: 7 additions & 0 deletions tests/packages/sdist_config/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <pybind11/pybind11.h>

namespace py = pybind11;

PYBIND11_MODULE(sdist_config, m) {
m.def("life", []() { return 42; });
}
18 changes: 18 additions & 0 deletions tests/packages/sdist_config/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[build-system]
requires = ["scikit-build-core[pyproject]"]
build-backend = "scikit_build_core.build"

[project]
name = "sdist_config"
version = "0.1.0"

[tool.scikit-build]
sdist.cmake = true
sdist.include = [
"pybind11/tools",
"pybind11/include",
"pybind11/CMakeLists.txt",
]
wheel.license-files = []
wheel.packages = []
cmake.define.FETCHCONTENT_QUIET = false
6 changes: 6 additions & 0 deletions tests/test_get_requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ def test_get_requires_for_build_sdist(fp):
assert set(get_requires_for_build_sdist({})) == {"pathspec", "pyproject_metadata"}


def test_get_requires_for_build_sdist_cmake(fp):
expected = {"pathspec", "pyproject_metadata", "cmake>=3.15", *ninja}
fp.register([Path("cmake/path"), "--version"], stdout="3.14.0")
assert set(get_requires_for_build_sdist({"sdist.cmake": "True"})) == expected


def test_get_requires_for_build_wheel(fp):
expected = {"pathspec", "pyproject_metadata", "cmake>=3.15", *ninja}
fp.register([Path("cmake/path"), "--version"], stdout="3.14.0")
Expand Down
Loading

0 comments on commit 95dd864

Please sign in to comment.