Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
97 changes: 97 additions & 0 deletions src/scikit_build_core/format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Format variables available in the ``pyproject.toml`` evaluation"""

from __future__ import annotations

import dataclasses
import sys
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."""


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__}
# 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(),
)
# Then compute all optional keys depending on the function input
if settings is not None:
res["build_type"] = settings.cmake.build_type
if tags is not None:
res["wheel_tag"] = str(tags)

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

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/format.py#L71

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

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

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/format.py#L73

Added line #L73 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 97 in src/scikit_build_core/format.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/format.py#L94-L97

Added lines #L94 - L97 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