Skip to content

Commit

Permalink
feat: cmake.version and ninja.version (#602)
Browse files Browse the repository at this point in the history
Close #583.

If `tool.scikit-build.minimum-version` is set, then this will require
the correct setting here. If it's unset, it will just produce a warning
if the old form is used (for now).

This is also an API change, but I think that's fine for a 0.x release
unless someone needs back-compat, which I can add if needed.

Signed-off-by: Henry Schreiner <[email protected]>
  • Loading branch information
henryiii authored Jan 5, 2024
1 parent ec41578 commit f8c5798
Show file tree
Hide file tree
Showing 22 changed files with 224 additions and 82 deletions.
6 changes: 3 additions & 3 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Included modules:
## Basic CMake usage

```python
cmake = CMake.default_search(minimum_version="3.15")
cmake = CMake.default_search(version=">=3.15")
config = CMaker(
cmake,
source_dir=source_dir,
Expand Down Expand Up @@ -208,8 +208,8 @@ from scikit_build_core.settings.skbuild_settings import SettingsReader

settings_reader = SettingsReader.from_file("pyproject.toml", config_settings)
setting = settings_reader.settings
assert settings.cmake.minimum_version == "3.15"
assert settings.ninja.minimum_version == "1.5"
assert str(settings.cmake.version) == ">=3.15"
assert str(settings.ninja.version) == ">=1.5"
```

## Builders
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ repos:
rev: v2.2.6
hooks:
- id: codespell
exclude: ^(LICENSE$|src/scikit_build_core/resources/find_python)
exclude: ^(LICENSE$|src/scikit_build_core/resources/find_python|tests/test_skbuild_settings.py$)

- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.9.0.6
Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,13 @@ print("```\n")

```toml
[tool.scikit-build]
# The minimum version of CMake to use. If CMake is not present on the system or
# is older than this, it will be downloaded via PyPI if possible. An empty
# DEPRECATED in 0.8; use version instead.
cmake.minimum-version = ""

# The versions of CMake to allow. If CMake is not present on the system or does
# not pass this specifier, it will be downloaded via PyPI if possible. An empty
# string will disable this check.
cmake.minimum-version = "3.15"
cmake.version = ">=3.15"

# A list of args to pass to CMake when configuring the project. Setting this in
# config or envvar will override toml. See also ``cmake.define``.
Expand All @@ -177,10 +180,13 @@ cmake.source-dir = "."
# target.
cmake.targets = []

# The minimum version of Ninja to use. If Ninja is not present on the system or
# is older than this, it will be downloaded via PyPI if possible. An empty
# DEPRECATED in 0.8; use version instead.
ninja.minimum-version = ""

# The versions of Ninja to allow. If Ninja is not present on the system or does
# not pass this specifier, it will be downloaded via PyPI if possible. An empty
# string will disable this check.
ninja.minimum-version = "1.5"
ninja.version = ">=1.5"

# If CMake is not present on the system or is older required, it will be
# downloaded via PyPI if possible. An empty string will disable this check.
Expand Down
14 changes: 11 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ The following behaviors are affected by `minimum-version`:
- `minimum-version` 0.5+ (or unset) provides the original name in metadata and
properly normalized SDist names.
- `minimum-version` 0.5+ (or unset) strips binaries by default.
- `minimum-version` 0.8+ (or unset) `cmake.minimum-version` and
`ninja.minimum-version` are replaced with `cmake.version` and `ninja.version`.

:::

Expand All @@ -117,8 +119,8 @@ For example, to require a recent CMake and Ninja:

```toml
[tool.scikit-build]
cmake.minimum-version = "3.26.1"
ninja.minimum-version = "1.11"
cmake.version = ">=3.26.1"
ninja.version = ">=1.11"
```

You can also enforce ninja to be required even if make is present on Unix:
Expand All @@ -138,6 +140,12 @@ would turn it off).
backport.find-python = "3.15"
```

```{versionadded} 0.8
These used to be called `cmake.minimum-version` and `ninja.minimum-version`, and
only took a single value. Now they are full specifier sets, allowing for more
complex version requirements, like `>=3.15,!=3.18.0`.
```

## Configuring source file inclusion

Scikit-build-core defaults to using your `.gitignore` to select what to exclude
Expand Down Expand Up @@ -647,7 +655,7 @@ will match top to bottom, overriding previous matches. For example:
```toml
[[tool.scikit-build.overrides]]
if.sys-platform = "darwin"
cmake.minimum-version = "3.18"
cmake.version = ">=3.18"
```

If you use `if.any` instead of `if`, then the override is true if any one of the
Expand Down
2 changes: 1 addition & 1 deletion src/scikit_build_core/build/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def _build_wheel_impl(
normalized_name = metadata.name.replace("-", "_").replace(".", "_")

if settings.wheel.cmake:
cmake = CMake.default_search(minimum_version=settings.cmake.minimum_version)
cmake = CMake.default_search(version=settings.cmake.version)
cmake_msg = [f"using [blue]CMake {cmake.version}[/blue]"]
else:
cmake = None
Expand Down
4 changes: 1 addition & 3 deletions src/scikit_build_core/builder/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,7 @@ def set_environment_for_gen(
return {}

if (generator or "Ninja") == "Ninja":
ninja = best_program(
get_ninja_programs(), minimum_version=ninja_settings.minimum_version
)
ninja = best_program(get_ninja_programs(), version=ninja_settings.version)

if ninja is not None:
env.setdefault("CMAKE_GENERATOR", "Ninja")
Expand Down
20 changes: 8 additions & 12 deletions src/scikit_build_core/builder/get_requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,17 @@ def settings(self) -> ScikitBuildSettings:
return self._settings

def cmake(self) -> Generator[str, None, None]:
cmake_min = self.settings.cmake.minimum_version
cmake_verset = self.settings.cmake.version

# If the module is already installed (via caching the build
# environment, for example), we will use that
if importlib.util.find_spec("cmake") is not None:
yield f"cmake>={cmake_min}"
yield f"cmake{cmake_verset}"
return

cmake = best_program(
get_cmake_programs(module=False), minimum_version=cmake_min
)
cmake = best_program(get_cmake_programs(module=False), version=cmake_verset)
if cmake is None:
yield f"cmake>={cmake_min}"
yield f"cmake{cmake_verset}"
return
logger.debug("Found system CMake: {} - not requiring PyPI package", cmake)

Expand All @@ -90,17 +88,15 @@ def ninja(self) -> Generator[str, None, None]:
if os.environ.get("CMAKE_MAKE_PROGRAM", ""):
return

ninja_min = self.settings.ninja.minimum_version
ninja_verset = self.settings.ninja.version

# If the module is already installed (via caching the build
# environment, for example), we will use that
if importlib.util.find_spec("ninja") is not None:
yield f"ninja>={ninja_min}"
yield f"ninja{ninja_verset}"
return

ninja = best_program(
get_ninja_programs(module=False), minimum_version=ninja_min
)
ninja = best_program(get_ninja_programs(module=False), version=ninja_verset)
if ninja is not None:
logger.debug("Found system Ninja: {} - not requiring PyPI package", ninja)
return
Expand All @@ -114,7 +110,7 @@ def ninja(self) -> Generator[str, None, None]:
"Found system Make & not on known platform - not requiring PyPI package for Ninja"
)
return
yield f"ninja>={ninja_min}"
yield f"ninja{ninja_verset}"

def dynamic_metadata(self) -> Generator[str, None, None]:
for dynamic_metadata in self.settings.metadata.values():
Expand Down
7 changes: 4 additions & 3 deletions src/scikit_build_core/cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence

from packaging.specifiers import SpecifierSet
from packaging.version import Version

from ._compat.typing import Self
Expand All @@ -42,13 +43,13 @@ class CMake:

@classmethod
def default_search(
cls, *, minimum_version: Version | None = None, module: bool = True
cls, *, version: SpecifierSet | None = None, module: bool = True
) -> Self:
candidates = get_cmake_programs(module=module)
cmake_program = best_program(candidates, minimum_version=minimum_version)
cmake_program = best_program(candidates, version=version)

if cmake_program is None:
msg = f"Could not find CMake with version >= {minimum_version}"
msg = f"Could not find CMake with version {version}"
raise CMakeNotFoundError(msg)
if cmake_program.version is None:
msg = "CMake version undetermined @ {program.path}"
Expand Down
8 changes: 5 additions & 3 deletions src/scikit_build_core/program_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
if TYPE_CHECKING:
from collections.abc import Generator, Iterable

from packaging.specifiers import SpecifierSet

__all__ = ["get_cmake_programs", "get_ninja_programs", "best_program", "Program"]


Expand Down Expand Up @@ -122,16 +124,16 @@ def get_make_programs() -> Generator[Path, None, None]:


def best_program(
programs: Iterable[Program], *, minimum_version: Version | None
programs: Iterable[Program], *, version: SpecifierSet | None
) -> Program | None:
"""
Select the first program entry that is of a supported version, or None if not found.
"""

for program in programs:
if minimum_version is None:
if version is None:
return program
if program.version is not None and program.version >= minimum_version:
if program.version is not None and version.contains(program.version):
return program

return None
18 changes: 14 additions & 4 deletions src/scikit_build_core/resources/scikit-build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@
"properties": {
"minimum-version": {
"type": "string",
"default": "3.15",
"description": "The minimum version of CMake to use. If CMake is not present on the system or is older than this, it will be downloaded via PyPI if possible. An empty string will disable this check."
"description": "DEPRECATED in 0.8; use version instead.",
"deprecated": true
},
"version": {
"type": "string",
"default": ">=3.15",
"description": "The versions of CMake to allow. If CMake is not present on the system or does not pass this specifier, it will be downloaded via PyPI if possible. An empty string will disable this check."
},
"args": {
"type": "array",
Expand Down Expand Up @@ -67,8 +72,13 @@
"properties": {
"minimum-version": {
"type": "string",
"default": "1.5",
"description": "The minimum version of Ninja to use. If Ninja is not present on the system or is older than this, it will be downloaded via PyPI if possible. An empty string will disable this check."
"description": "DEPRECATED in 0.8; use version instead.",
"deprecated": true
},
"version": {
"type": "string",
"default": ">=1.5",
"description": "The versions of Ninja to allow. If Ninja is not present on the system or does not pass this specifier, it will be downloaded via PyPI if possible. An empty string will disable this check."
},
"make-fallback": {
"type": "boolean",
Expand Down
3 changes: 2 additions & 1 deletion src/scikit_build_core/settings/documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pathlib import Path
from typing import TYPE_CHECKING

from packaging.specifiers import SpecifierSet
from packaging.version import Version

from .._compat.typing import get_args, get_origin
Expand Down Expand Up @@ -80,7 +81,7 @@ def mk_docs(dc: type[object], prefix: str = "") -> Generator[DCDoc, None, None]:
if field.default is not dataclasses.MISSING and field.default is not None:
default = repr(
str(field.default)
if isinstance(field.default, (Path, Version))
if isinstance(field.default, (Path, Version, SpecifierSet))
else field.default
)
elif field.default_factory is not dataclasses.MISSING:
Expand Down
7 changes: 5 additions & 2 deletions src/scikit_build_core/settings/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pathlib import Path
from typing import Any, Union

from packaging.specifiers import SpecifierSet
from packaging.version import Version

from .._compat.builtins import ExceptionGroup
Expand Down Expand Up @@ -49,7 +50,7 @@ def to_json_schema(dclass: type[Any], *, normalize_keys: bool) -> dict[str, Any]
if field.default is not dataclasses.MISSING and field.default is not None:
props[field.name]["default"] = (
str(field.default)
if isinstance(field.default, (Version, Path))
if isinstance(field.default, (Version, Path, SpecifierSet))
else field.default
)

Expand All @@ -66,6 +67,8 @@ def to_json_schema(dclass: type[Any], *, normalize_keys: bool) -> dict[str, Any]
docs = pull_docs(dclass)
for k, v in docs.items():
props[k]["description"] = v
if "DEPRECATED" in v:
props[k]["deprecated"] = True

if normalize_keys:
props = {k.replace("_", "-"): v for k, v in props.items()}
Expand All @@ -84,7 +87,7 @@ def to_json_schema(dclass: type[Any], *, normalize_keys: bool) -> dict[str, Any]
def convert_type(t: Any, *, normalize_keys: bool) -> dict[str, Any]:
if dataclasses.is_dataclass(t):
return to_json_schema(t, normalize_keys=normalize_keys)
if t is str or t is Path or t is Version:
if t is str or t is Path or t is Version or t is SpecifierSet:
return {"type": "string"}
if t is bool:
return {"type": "boolean"}
Expand Down
23 changes: 17 additions & 6 deletions src/scikit_build_core/settings/skbuild_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path
from typing import Any, Dict, List, Optional, Union

from packaging.specifiers import SpecifierSet
from packaging.version import Version

from .._compat.typing import Literal
Expand All @@ -26,10 +27,15 @@ def __dir__() -> List[str]:

@dataclasses.dataclass
class CMakeSettings:
minimum_version: Version = Version("3.15")
minimum_version: Optional[Version] = None
"""
DEPRECATED in 0.8; use version instead.
"""

version: SpecifierSet = SpecifierSet(">=3.15")
"""
The minimum version of CMake to use. If CMake is not present on the system
or is older than this, it will be downloaded via PyPI if possible. An empty
The versions of CMake to allow. If CMake is not present on the system or does
not pass this specifier, it will be downloaded via PyPI if possible. An empty
string will disable this check.
"""

Expand Down Expand Up @@ -71,10 +77,15 @@ class CMakeSettings:

@dataclasses.dataclass
class NinjaSettings:
minimum_version: Version = Version("1.5")
minimum_version: Optional[Version] = None
"""
DEPRECATED in 0.8; use version instead.
"""

version: SpecifierSet = SpecifierSet(">=1.5")
"""
The minimum version of Ninja to use. If Ninja is not present on the system
or is older than this, it will be downloaded via PyPI if possible. An empty
The versions of Ninja to allow. If Ninja is not present on the system or does
not pass this specifier, it will be downloaded via PyPI if possible. An empty
string will disable this check.
"""

Expand Down
Loading

0 comments on commit f8c5798

Please sign in to comment.