Migrating from scikit-build-core¶
If you’ve used scikit-build-core before the 1.0 release, it shipped an earlier,
provisional dynamic-metadata system under [tool.scikit-build.metadata]. This
page maps that system onto the one described here: the first section is for
users rewriting their configuration, the second is for plugin authors
updating a provider so it works under both loaders during the transition.
The two systems differ in three structural ways:
Configuration is an ordered array, not a field-keyed table. The old
[tool.scikit-build.metadata.<field>]becomes a[[tool.dynamic-metadata]]entry with the target given as afieldsetting. Entries run top to bottom instead of being resolved on demand, so ordering is explicit (see For users). This has much less magic and is easier for backends to adopt.provideris a registered name, not an import path. The old value was a bare importable module; now a string is a name in thedynamic_metadata.providerentry-point group. Because of this, a plugin can support both systems from a single release. A local plugin is an inline{path, module}table instead ofprovider+provider-path.The hook returns a fragment, not a single value.
dynamic_metadatano longer takes afieldargument and returns one value, instead it returns{field: value, ...}.
Updating tool.scikit-build.metadata¶
Each [tool.scikit-build.metadata.<field>] table becomes one
[[tool.dynamic-metadata]] array entry:
Old ( |
New ( |
|---|---|
|
|
|
|
|
|
any other keys |
unchanged (still passed as settings) |
A couple of the bundled providers are in this package as well:
scikit_build_core.metadata.regex is dynamic_metadata.regex, and
scikit_build_core.metadata.template is dynamic_metadata.template. Run
dynamic-metadata providers to list the non-local providers installed in your
environment.
A local provider¶
provider-path becomes the path of an inline-table provider, and the
importable module name becomes module:
# Before
[tool.scikit-build.metadata.version]
provider = "my_plugin"
provider-path = "scripts"
# After
[[tool.dynamic-metadata]]
provider = { path = "scripts", module = "my_plugin" }
field = "version"
See Providing a custom plugin for the
details of the inline form (including module = "my_plugin:MyClass").
Ordering and cross-references¶
The old system resolved fields lazily and detected cycles at runtime. Entries
now run in the order you list them, and a plugin reads an earlier entry’s output
with project[...]. If one field is computed from another, make sure the entry
that produces it comes first — a forward reference is just a KeyError. See
For users for the full ordering and merge rules.
Updating a plugin¶
A provider needs two changes to work with the new loader. Both can be made without breaking the old scikit-build-core system, so a single release can support both while consumers migrate.
1. Register an entry point¶
The old loader imported the provider module directly from its config string. The
new loader resolves a string provider through the dynamic_metadata.provider
entry-point group, so add a registration to your pyproject.toml:
[project.entry-points."dynamic_metadata.provider"]
"my_package.my_plugin" = "my_package.plugin"
Prefix the name with your package — the group is shared, and a name claimed
by two distributions is a hard error. Registering an entry point does not
require a runtime dependency on dynamic-metadata. See
Registering a name.
2. Wrap the hook for both signatures¶
The two loaders call dynamic_metadata differently:
Loader |
Call |
Returns |
|---|---|---|
scikit-build-core |
|
the field’s value |
dynamic-metadata |
|
|
get_requires_for_dynamic_metadata(settings) -> list[str] has the same
signature in both systems; leave it as is.
dynamic_wheel changed shape — the old dynamic_wheel(field, settings) -> bool
became dynamic_wheel(settings) -> dict[str, bool]. Wrap it the same way if you
implement it.
The new build_state hook has no old-system equivalent; add it only if you need
it (see Optional hooks). It is detected by
presence, so an old loader that does not know about it simply never calls it.
A wrapper class¶
The new system supports classes, so instead of branching on the argument types you can keep the existing module-level hooks untouched (scikit-build-core still imports the module and calls them directly) and add a small class that exposes the modern signatures, delegating to the old ones. Point the new entry point at the class:
[project.entry-points."dynamic_metadata.provider"]
"my_package.my_plugin" = "my_package.plugin:Provider"
# ... classic hooks here
class Provider:
"""Adapt the old module-level hooks to the dynamic-metadata protocol."""
def dynamic_metadata(self, settings, project):
field = settings["field"] # or hardcode it for a single-purpose plugin
# the bare name calls the module-level hook above, not this method
return {field: dynamic_metadata(field, settings)}
def dynamic_wheel(self, settings):
field = settings["field"]
return {field: dynamic_wheel(field, settings)}
def get_requires_for_dynamic_metadata(self, settings):
# unchanged signature — forward as-is
return get_requires_for_dynamic_metadata(settings)
Only add methods for the hooks your plugin actually implements; the loader
detects each by its presence. Because the new loader instantiates the class with
no arguments, Provider can also stash state on self across hooks (for
example from a build_state method) — something the old module-level functions
could not do.
(You do not need to use a class for the new system, but it’s available for things like this).