Skip to content

Commit

Permalink
feat: write metadata to file
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
  • Loading branch information
henryiii committed Aug 11, 2023
1 parent 196c8ce commit c255732
Show file tree
Hide file tree
Showing 24 changed files with 516 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ repos:
- id: prettier
types_or: [yaml, markdown, html, css, scss, javascript, json]
args: [--prose-wrap=always]
exclude: "^tests"
exclude: "^tests|src/scikit_build_core/resources/scikit-build.schema.json"

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.282
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,22 @@ install.components = []
# Whether to strip the binaries. True for scikit-build-core 0.5+.
install.strip = false

# The path (relative to platlib) for the file to generate.
generate[].path = ""

# The template to use for the file. This includes string.Template style
# placeholders for all the metadata. If empty, a template-path must be set.
generate[].template = ""

# The path to the template file. If empty, a template must be set.
generate[].template-path = ""

# The place to put the generated file. The "build" directory is useful for CMake
# files, and the "install" directory is useful for Python files, usually. You
# can also write directly to the "source" directory, will overwrite existing
# files & remember to gitignore the file.
generate[].location = "install"

# List dynamic metadata fields and hook locations in this table.
metadata = {}

Expand Down
8 changes: 8 additions & 0 deletions docs/api/scikit_build_core.build.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ scikit\_build\_core.build package
Submodules
----------

scikit\_build\_core.build.generate module
-----------------------------------------

.. automodule:: scikit_build_core.build.generate
:members:
:undoc-members:
:show-inheritance:

scikit\_build\_core.build.sdist module
--------------------------------------

Expand Down
26 changes: 25 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ default targets):

## Dynamic metadata

Scikit-build-core 0.3.0 supports dynamic metadata with two built-in plugins.
Scikit-build-core 0.3.0+ supports dynamic metadata with two built-in plugins.

:::{warning}

Expand Down Expand Up @@ -445,6 +445,30 @@ metadata.readme.provider = "scikit_build_core.metadata.fancy_pypi_readme"

:::

### Writing metadata

You can write out metadata to file(s) as well. Other info might become available
here in the future, but currently it supports anything available as strings in
metadata. (Note that arrays like this are only supported in TOML configuration.)

```toml
[[tool.scikit-build.generate]]
path = "package/_version.py"
template = '''
version = "${version}"
'''
```

`template` or `template-file` is required; this uses {class}`string.Template`
formatting. There are three options for output location; `location = "install"`
(the default) will go to the wheel, `location = "build"` will go to the CMake
build directory, and `location = "source"` will write out to the source
directory (be sure to .gitignore this file. It will automatically be added to
your SDist includes. It will overwrite existing files).

The path is generally relative to the base of the wheel / build dir / source
dir, depending on which location you pick.

## Editable installs

Experimental support for editable installs is provided, with some caveats and
Expand Down
32 changes: 32 additions & 0 deletions src/scikit_build_core/build/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations

__all__ = ["generate_file_contents"]

import dataclasses
import string

from pyproject_metadata import StandardMetadata

from ..settings.skbuild_model import GenerateSettings


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


def generate_file_contents(gen: GenerateSettings, metadata: StandardMetadata) -> str:
"""
Generate a file contents from a template. Input GeneratorSettings and
metadata. Metadata is available inside the template.
"""

assert (
gen.template_path or gen.template
), f"One of template or template-path must be set for {gen.path}"

if gen.template_path:
template = gen.template_path.read_text(encoding="utf-8")
else:
template = gen.template

return string.Template(template).substitute(dataclasses.asdict(metadata))
30 changes: 24 additions & 6 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 .generate import generate_file_contents
from .wheel import _build_wheel_impl

__all__: list[str] = ["build_sdist"]
Expand Down Expand Up @@ -70,6 +71,22 @@ def normalize_tar_info(tar_info: tarfile.TarInfo) -> tarfile.TarInfo:
return tar_info


def add_bytes_to_tar(
tar: tarfile.TarFile, data: bytes, name: str, normalize: bool
) -> None:
"""
Write ``data`` bytes to ``name`` in a tarfile ``tar``. Normalize the info if
``normalize`` is true.
"""

tarinfo = tarfile.TarInfo(name)
if normalize:
tarinfo = normalize_tar_info(tarinfo)
with io.BytesIO(data) as bio:
tarinfo.size = bio.getbuffer().nbytes
tar.addfile(tarinfo, bio)


def build_sdist(
sdist_directory: str,
config_settings: dict[str, list[str] | str] | None = None,
Expand Down Expand Up @@ -116,6 +133,12 @@ def build_sdist(
None, config_settings, None, exit_after_config=True, editable=False
)

for gen in settings.generate:
if gen.location == "source":
contents = generate_file_contents(gen, metadata)
gen.path.write_text(contents)
settings.sdist.include.append(str(gen.path))

sdist_dir.mkdir(parents=True, exist_ok=True)
with contextlib.ExitStack() as stack:
gzip_container = stack.enter_context(
Expand All @@ -138,11 +161,6 @@ def build_sdist(
filter=normalize_tar_info if reproducible else lambda x: x,
)

tarinfo = tarfile.TarInfo(name=f"{srcdirname}/PKG-INFO")
tarinfo.size = len(pkg_info)
if reproducible:
tarinfo = normalize_tar_info(tarinfo)
with io.BytesIO(pkg_info) as fileobj:
tar.addfile(tarinfo, fileobj)
add_bytes_to_tar(tar, pkg_info, f"{srcdirname}/PKG-INFO", reproducible)

return filename
21 changes: 21 additions & 0 deletions src/scikit_build_core/build/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
)
from ._scripts import process_script_dir
from ._wheelfile import WheelWriter
from .generate import generate_file_contents

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

Expand Down Expand Up @@ -167,6 +168,12 @@ def _build_wheel_impl(
"No license files found, set wheel.license-files to [] to suppress this warning"
)

for gen in settings.generate:
if gen.location == "source":
contents = generate_file_contents(gen, metadata)
gen.path.write_text(contents)
settings.sdist.include.append(str(gen.path))

config = CMaker(
cmake,
source_dir=settings.cmake.source_dir,
Expand Down Expand Up @@ -199,6 +206,20 @@ def _build_wheel_impl(
path.write_bytes(data)
return WheelImplReturn(wheel_filename=dist_info.name)

for gen in settings.generate:
contents = generate_file_contents(gen, metadata)
if gen.location == "source":
continue
if gen.location == "build":
path = build_dir / gen.path
elif gen.location == "install":
path = install_dir / gen.path
else:
msg = f"Invalid location {gen.location!r}, must be 'build' or 'install'"
raise AssertionError(msg)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(contents, encoding="utf-8")

rich_print("[green]***[/green] [bold]Configuring CMake...")
defines: dict[str, str] = {}
cache_entries: dict[str, str | Path] = {
Expand Down
2 changes: 1 addition & 1 deletion src/scikit_build_core/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
get_soabi,
)

__all__: list[str] = ["Builder", "get_archs", "archs_to_tags"]
__all__ = ["Builder", "get_archs", "archs_to_tags"]

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

Expand Down
59 changes: 59 additions & 0 deletions src/scikit_build_core/resources/scikit-build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,65 @@
}
}
},
"generate": {
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": [
"path",
"template"
],
"properties": {
"path": {
"type": "string",
"description": "The path (relative to platlib) for the file to generate.",
"minLength": 1
},
"template": {
"type": "string",
"description": "The template to use for the file. This includes string.Template style placeholders for all the metadata. If empty, a template-path must be set.",
"minLength": 1
},
"location": {
"type": "string",
"default": "install",
"description": "The place to put the generated file. The \"build\" directory is useful for CMake files, and the \"install\" directory is useful for Python files, usually. You can also write directly to the \"source\" directory, will overwrite existing files & remember to gitignore the file.",
"minLength": 1
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": [
"path",
"template-path"
],
"properties": {
"path": {
"type": "string",
"description": "The path (relative to platlib) for the file to generate.",
"minLength": 1
},
"template-path": {
"type": "string",
"description": "The path to the template file. If empty, a template must be set.",
"minLength": 1
},
"location": {
"type": "string",
"default": "install",
"description": "The place to put the generated file. The \"build\" directory is useful for CMake files, and the \"install\" directory is useful for Python files, usually. You can also write directly to the \"source\" directory, will overwrite existing files & remember to gitignore the file.",
"minLength": 1
}
}
}
]
}
},
"metadata": {
"type": "object",
"patternProperties": {
Expand Down
12 changes: 10 additions & 2 deletions src/scikit_build_core/settings/documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

from packaging.version import Version

from .._compat.typing import get_args, get_origin

__all__ = ["pull_docs"]


Expand Down Expand Up @@ -64,7 +66,13 @@ def mk_docs(dc: type[object], prefix: str = "") -> Generator[DCDoc, None, None]:
yield from mk_docs(field.type, prefix=f"{prefix}{field.name}.")
continue

if field.default is not dataclasses.MISSING:
if get_origin(field.type) is list:
field_type = get_args(field.type)[0]
if dataclasses.is_dataclass(field_type):
yield from mk_docs(field_type, prefix=f"{prefix}{field.name}[].")
continue

if field.default is not dataclasses.MISSING and field.default is not None:
default = repr(
str(field.default)
if isinstance(field.default, (Path, Version))
Expand All @@ -73,7 +81,7 @@ def mk_docs(dc: type[object], prefix: str = "") -> Generator[DCDoc, None, None]:
elif field.default_factory is not dataclasses.MISSING:
default = repr(field.default_factory())
else:
default = ""
default = '""'

yield DCDoc(
f"{prefix}{field.name}".replace("_", "-"),
Expand Down
Loading

0 comments on commit c255732

Please sign in to comment.