Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/api/scikit_build_core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ scikit\_build\_core.errors module
:show-inheritance:
:undoc-members:

scikit\_build\_core.format module
---------------------------------

.. automodule:: scikit_build_core.format
:members:
:show-inheritance:
:undoc-members:

scikit\_build\_core.program\_search module
------------------------------------------

Expand Down
27 changes: 27 additions & 0 deletions docs/configuration/formatted.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Formattable fields

The following configure keys are formatted as Python f-strings:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not good at naming these.


- `build-dir`
- `build.requires`

The available variables are documented in the members of
{py:class}`scikit_build_core.format.PyprojectFormatter` copied here for
visibility

```{eval-rst}
.. autoattribute:: scikit_build_core.format.PyprojectFormatter.build_type
:no-index:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to generate this section, but maybe in a later PR


.. autoattribute:: scikit_build_core.format.PyprojectFormatter.cache_tag
:no-index:

.. autoattribute:: scikit_build_core.format.PyprojectFormatter.root
:no-index:

.. autoattribute:: scikit_build_core.format.PyprojectFormatter.state
:no-index:

.. autoattribute:: scikit_build_core.format.PyprojectFormatter.wheel_tag
:no-index:
```
9 changes: 2 additions & 7 deletions docs/configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -643,13 +643,8 @@ speedups.

```

There are several values you can access through Python's formatting syntax:

- `cache_tag`: `sys.implementation.cache_tag`
- `wheel_tag`: The tags as computed for the wheel
- `build_type`: The current build type (`Release` by default)
- `state`: The current run state, `sdist`, `wheel`, `editable`,
`metadata_wheel`, and `metadata_editable`
There are several values you can access through Python's formatting syntax. See
[](./formatted.md).

Scikit-build-core also strictly validates configuration; if you need to disable
this, you can:
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ guide/faqs
configuration/index
configuration/overrides
configuration/dynamic
configuration/formatted
```

```{toctree}
Expand Down
3 changes: 0 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,6 @@ known-local-folder = ["pathutils"]
"typing.Self".msg = "Use scikit_build_core._compat.typing.Self instead."
"typing_extensions.Self".msg = "Use scikit_build_core._compat.typing.Self instead."
"typing.Final".msg = "Add scikit_build_core._compat.typing.Final instead."
"typing.NotRequired".msg = "Add scikit_build_core._compat.typing.NotRequired instead."
"typing.OrderedDict".msg = "Add scikit_build_core._compat.typing.OrderedDict instead."
"typing.TypedDict".msg = "Add scikit_build_core._compat.typing.TypedDict instead."
"typing.assert_never".msg = "Add scikit_build_core._compat.typing.assert_never instead."
"tomli".msg = "Use scikit_build_core._compat.tomllib instead."
"tomllib".msg = "Use scikit_build_core._compat.tomllib instead."
Expand Down
9 changes: 3 additions & 6 deletions src/scikit_build_core/build/_file_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import pathspec

from scikit_build_core.format import pyproject_format

if TYPE_CHECKING:
from collections.abc import Generator, Sequence

Expand Down Expand Up @@ -51,12 +53,7 @@ def each_unignored_file(
if p != Path(".gitignore")
}

exclude_build_dir = build_dir.format(
cache_tag="*",
wheel_tag="*",
build_type="*",
state="*",
)
exclude_build_dir = build_dir.format(**pyproject_format(dummy=True))

exclude_lines = (
[*EXCLUDE_LINES, exclude_build_dir] if exclude_build_dir else EXCLUDE_LINES
Expand Down
11 changes: 6 additions & 5 deletions src/scikit_build_core/build/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import dataclasses
import os
import shutil
import sys
import sysconfig
import tempfile
from collections.abc import Mapping
Expand All @@ -20,6 +19,7 @@
from ..builder.wheel_tag import WheelTag
from ..cmake import CMake, CMaker
from ..errors import FailedLiveProcessError
from ..format import pyproject_format
from ..settings.skbuild_read_settings import SettingsReader
from ._editable import editable_redirect, libdir_to_installed, mapping_to_modules
from ._init import setup_logging
Expand Down Expand Up @@ -279,10 +279,11 @@ def _build_wheel_impl_impl(
build_dir = (
Path(
settings.build_dir.format(
cache_tag=sys.implementation.cache_tag,
wheel_tag=str(tags),
build_type=settings.cmake.build_type,
state=state,
**pyproject_format(
settings=settings,
tags=tags,
state=state,
)
)
)
if settings.build_dir
Expand Down
30 changes: 6 additions & 24 deletions src/scikit_build_core/builder/get_requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import importlib.util
import os
import sysconfig
from pathlib import Path
from typing import TYPE_CHECKING, Literal

from packaging.tags import sys_tags

from .._compat import tomllib
from .._logging import logger
from ..format import pyproject_format
from ..program_search import (
best_program,
get_cmake_programs,
Expand Down Expand Up @@ -67,28 +67,6 @@ def _load_scikit_build_settings(
return SettingsReader.from_file("pyproject.toml", config_settings).settings


@dataclasses.dataclass()
class RootPathResolver:
"""Handle ``{root:uri}`` like formatting similar to ``hatchling``."""

path: Path = dataclasses.field(default_factory=Path)

def __post_init__(self) -> None:
self.path = self.path.resolve()

def __format__(self, fmt: str) -> str:
command, _, rest = fmt.partition(":")
if command == "parent":
parent = RootPathResolver(self.path.parent)
return parent.__format__(rest)
if command == "uri" and rest == "":
return self.path.as_uri()
if command == "" and rest == "":
return str(self)
msg = f"Could not handle format: {fmt}"
raise ValueError(msg)


@dataclasses.dataclass(frozen=True)
class GetRequires:
settings: ScikitBuildSettings = dataclasses.field(
Expand Down Expand Up @@ -164,7 +142,11 @@ def dynamic_metadata(self) -> Generator[str, None, None]:
return

for build_require in self.settings.build.requires:
yield build_require.format(root=RootPathResolver())
yield build_require.format(
**pyproject_format(
settings=self.settings,
)
)

for dynamic_metadata in self.settings.metadata.values():
if "provider" in dynamic_metadata:
Expand Down
113 changes: 113 additions & 0 deletions src/scikit_build_core/format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""Format variables available in the ``pyproject.toml`` evaluation"""

from __future__ import annotations

import dataclasses
import sys
import typing
from pathlib import Path
from typing import TYPE_CHECKING, TypedDict

if TYPE_CHECKING:
from typing import Literal

from scikit_build_core.builder.wheel_tag import WheelTag
from scikit_build_core.settings.skbuild_model import ScikitBuildSettings

__all__ = [
"PyprojectFormatter",
"RootPathResolver",
"pyproject_format",
]


def __dir__() -> list[str]:
return __all__


class PyprojectFormatter(TypedDict, total=False):
"""Format helper for pyproject.toml.

Stores all known variables that can be used for evaluating a formatted string
in the pyproject.toml config file.
"""

cache_tag: str
"""Tag used by the import machinery in the filenames of cached modules, i.e. ``sys.implementation.cache_tag``."""
wheel_tag: str
"""The tags as computed for the wheel."""
build_type: str
"""Build type passed as ``cmake.build_type``."""
state: Literal["sdist", "wheel", "editable", "metadata_wheel", "metadata_editable"]
"""The state of the build."""
root: RootPathResolver
"""Root path of the current project."""


@typing.overload
def pyproject_format(
*,
settings: ScikitBuildSettings,
state: Literal["sdist", "wheel", "editable", "metadata_wheel", "metadata_editable"]
| None = ...,
tags: WheelTag | None = ...,
) -> PyprojectFormatter: ...


@typing.overload
def pyproject_format(*, dummy: Literal[True]) -> dict[str, str]: ...


def pyproject_format(
*,
settings: ScikitBuildSettings | None = None,
state: (
Literal["sdist", "wheel", "editable", "metadata_wheel", "metadata_editable"]
| None
) = None,
tags: WheelTag | None = None,
dummy: bool = False,
) -> PyprojectFormatter | dict[str, str]:
"""Generate :py:class:`PyprojectFormatter` dictionary to use in f-string format."""
if dummy:
# Return a dict with all the known keys but with values replaced with dummy values
return {key: "*" for key in PyprojectFormatter.__annotations__}

assert settings is not None
# First set all known values
res = PyprojectFormatter(
cache_tag=sys.implementation.cache_tag,
# We are assuming the Path.cwd always evaluates to the folder containing pyproject.toml
# as part of PEP517 standard.
root=RootPathResolver(),
build_type=settings.cmake.build_type,
)
# Then compute all optional keys depending on the function input
if tags is not None:
res["wheel_tag"] = str(tags)

Check warning on line 87 in src/scikit_build_core/format.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/format.py#L87

Added line #L87 was not covered by tests
if state is not None:
res["state"] = state

Check warning on line 89 in src/scikit_build_core/format.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/format.py#L89

Added line #L89 was not covered by tests
# Construct the final dict including the always known keys
return res


@dataclasses.dataclass()
class RootPathResolver:
"""Handle ``{root:uri}`` like formatting similar to ``hatchling``."""

path: Path = dataclasses.field(default_factory=Path)

def __post_init__(self) -> None:
self.path = self.path.resolve()

def __format__(self, fmt: str) -> str:
command, _, rest = fmt.partition(":")
if command == "parent":
parent = RootPathResolver(self.path.parent)
return parent.__format__(rest)
if command == "uri" and rest == "":
return self.path.as_uri()
if command == "" and rest == "":
return str(self)
msg = f"Could not handle format: {fmt}"
raise ValueError(msg)

Check warning on line 113 in src/scikit_build_core/format.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/format.py#L110-L113

Added lines #L110 - L113 were not covered by tests
11 changes: 6 additions & 5 deletions src/scikit_build_core/hatch/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import importlib.metadata
import os
import shutil
import sys
import sysconfig
import tempfile
import typing
Expand All @@ -24,6 +23,7 @@
from ..builder.get_requires import GetRequires
from ..builder.wheel_tag import WheelTag
from ..cmake import CMake, CMaker
from ..format import pyproject_format
from ..settings.skbuild_read_settings import SettingsReader

__all__ = ["ScikitBuildHook"]
Expand Down Expand Up @@ -162,10 +162,11 @@ def _initialize(self, *, build_data: dict[str, Any]) -> None:
build_dir = (
Path(
settings.build_dir.format(
cache_tag=sys.implementation.cache_tag,
wheel_tag=str(tags),
build_type=settings.cmake.build_type,
state=state,
**pyproject_format(
settings=settings,
tags=tags,
state=state,
)
)
)
if settings.build_dir
Expand Down
Loading