Skip to content
Open
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
32 changes: 29 additions & 3 deletions backend/src/hatchling/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import os
from typing import Any

from hatchling.builders.variant_constants import VARIANT_DIST_INFO_FILENAME

__all__ = [
'build_editable',
'build_sdist',
Expand Down Expand Up @@ -46,15 +48,35 @@ def get_requires_for_build_wheel(config_settings: dict[str, Any] | None = None)

def build_wheel(
wheel_directory: str,
config_settings: dict[str, Any] | None = None, # noqa: ARG001
config_settings: dict[str, Any] | None = None,
metadata_directory: str | None = None, # noqa: ARG001
) -> str:
"""
https://peps.python.org/pep-0517/#build-wheel
"""
from hatchling.builders.wheel import WheelBuilder

builder = WheelBuilder(os.getcwd())
from hatchling.metadata.core import ProjectMetadata
from hatchling.plugin.manager import PluginManager

root_dir = os.getcwd()
plugin_manager = PluginManager()
metadata = ProjectMetadata(root_dir, plugin_manager)

if config_settings and 'variant-property' in config_settings:
variant_props = config_settings['variant-property']
else:
variant_props = None
if config_settings and 'variant-label' in config_settings:
variant_label = config_settings['variant-label']
else:
variant_label = None
builder = WheelBuilder(
root_dir,
plugin_manager=plugin_manager,
metadata=metadata,
variant_props=variant_props,
variant_label=variant_label,
)
return os.path.basename(next(builder.build(directory=wheel_directory, versions=['standard'])))


Expand Down Expand Up @@ -116,6 +138,10 @@ def prepare_metadata_for_build_wheel(
with open(os.path.join(directory, 'METADATA'), 'w', encoding='utf-8') as f:
f.write(builder.config.core_metadata_constructor(builder.metadata))

if builder.metadata.variant_label is not None:
with open(os.path.join(directory, VARIANT_DIST_INFO_FILENAME), 'w', encoding='utf-8') as f:
f.write(builder.config.variants_json_constructor(builder.metadata.variant_config))

return os.path.basename(directory)

def prepare_metadata_for_build_editable(
Expand Down
2 changes: 1 addition & 1 deletion backend/src/hatchling/builders/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def include_spec(self) -> pathspec.GitIgnoreSpec | None:

# Matching only at the root requires a forward slash, back slashes do not work. As such,
# normalize to forward slashes for consistency.
all_include_patterns.extend(f"/{relative_path.replace(os.sep, '/')}/" for relative_path in self.packages)
all_include_patterns.extend(f'/{relative_path.replace(os.sep, "/")}/' for relative_path in self.packages)

if all_include_patterns:
return pathspec.GitIgnoreSpec.from_lines(all_include_patterns)
Expand Down
2 changes: 2 additions & 0 deletions backend/src/hatchling/builders/plugin/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ def __init__(
config: dict[str, Any] | None = None,
metadata: ProjectMetadata | None = None,
app: Application | None = None,
variant_props: list[str] | None = None, # noqa: ARG002
variant_label: str | None = None, # noqa: ARG002
) -> None:
self.__root = root
self.__plugin_manager = cast(PluginManagerBound, plugin_manager)
Expand Down
12 changes: 6 additions & 6 deletions backend/src/hatchling/builders/sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,15 @@ def construct_setup_py_file(self, packages: list[str], extra_dependencies: tuple

authors_data = self.metadata.core.authors_data
if authors_data['name']:
contents += f" author={', '.join(authors_data['name'])!r},\n"
contents += f' author={", ".join(authors_data["name"])!r},\n'
if authors_data['email']:
contents += f" author_email={', '.join(authors_data['email'])!r},\n"
contents += f' author_email={", ".join(authors_data["email"])!r},\n'

maintainers_data = self.metadata.core.maintainers_data
if maintainers_data['name']:
contents += f" maintainer={', '.join(maintainers_data['name'])!r},\n"
contents += f' maintainer={", ".join(maintainers_data["name"])!r},\n'
if maintainers_data['email']:
contents += f" maintainer_email={', '.join(maintainers_data['email'])!r},\n"
contents += f' maintainer_email={", ".join(maintainers_data["email"])!r},\n'

if self.metadata.core.classifiers:
contents += ' classifiers=[\n'
Expand Down Expand Up @@ -313,9 +313,9 @@ def construct_setup_py_file(self, packages: list[str], extra_dependencies: tuple
for package in packages:
if package.startswith(f'src{os.sep}'):
src_layout = True
contents += f" {package.replace(os.sep, '.')[4:]!r},\n"
contents += f' {package.replace(os.sep, ".")[4:]!r},\n'
else:
contents += f" {package.replace(os.sep, '.')!r},\n"
contents += f' {package.replace(os.sep, ".")!r},\n'

contents += ' ],\n'

Expand Down
135 changes: 135 additions & 0 deletions backend/src/hatchling/builders/variant_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# This file is copied from variantlib/variantlib/constants.py
# Do not edit this file directly, instead edit variantlib/variantlib/constants.py
from __future__ import annotations

import re
from typing import Literal
from typing import TypedDict

VARIANT_LABEL_LENGTH = 16
NULL_VARIANT_LABEL = 'null'
CONFIG_FILENAME = 'variants.toml'
VARIANT_DIST_INFO_FILENAME = 'variant.json'

# Common variant info keys (used in pyproject.toml and variants.json)
VARIANT_INFO_DEFAULT_PRIO_KEY: Literal['default-priorities'] = 'default-priorities'
VARIANT_INFO_FEATURE_KEY: Literal['feature'] = 'feature'
VARIANT_INFO_NAMESPACE_KEY: Literal['namespace'] = 'namespace'
VARIANT_INFO_PROPERTY_KEY: Literal['property'] = 'property'
VARIANT_INFO_PROVIDER_DATA_KEY: Literal['providers'] = 'providers'
VARIANT_INFO_PROVIDER_ENABLE_IF_KEY: Literal['enable-if'] = 'enable-if'
VARIANT_INFO_PROVIDER_OPTIONAL_KEY: Literal['optional'] = 'optional'
VARIANT_INFO_PROVIDER_PLUGIN_API_KEY: Literal['plugin-api'] = 'plugin-api'
VARIANT_INFO_PROVIDER_PLUGIN_USE_KEY: Literal['plugin-use'] = 'plugin-use'
VARIANT_INFO_PROVIDER_REQUIRES_KEY: Literal['requires'] = 'requires'

PYPROJECT_TOML_TOP_KEY = 'variant'

VARIANTS_JSON_SCHEMA_KEY: Literal['$schema'] = '$schema'
VARIANTS_JSON_SCHEMA_URL = 'https://variants-schema.wheelnext.dev/v0.0.2.json'
VARIANTS_JSON_VARIANT_DATA_KEY: Literal['variants'] = 'variants'

VALIDATION_VARIANT_LABEL_REGEX = re.compile(rf'[0-9a-z._]{{1,{VARIANT_LABEL_LENGTH}}}')

VALIDATION_NAMESPACE_REGEX = re.compile(r'[a-z0-9_]+')
VALIDATION_FEATURE_NAME_REGEX = re.compile(r'[a-z0-9_]+')
VALIDATION_VALUE_REGEX = re.compile(r'[a-z0-9_.]+')

VALIDATION_FEATURE_REGEX = re.compile(
rf"""
(?P<namespace>{VALIDATION_NAMESPACE_REGEX.pattern})
\s* :: \s*
(?P<feature>{VALIDATION_FEATURE_NAME_REGEX.pattern})
""",
re.VERBOSE,
)

VALIDATION_PROPERTY_REGEX = re.compile(
rf"""
(?P<namespace>{VALIDATION_NAMESPACE_REGEX.pattern})
\s* :: \s*
(?P<feature>{VALIDATION_FEATURE_NAME_REGEX.pattern})
\s* :: \s*
(?P<value>{VALIDATION_VALUE_REGEX.pattern})
""",
re.VERBOSE,
)

VALIDATION_PROVIDER_ENABLE_IF_REGEX = re.compile(r'[\S ]+')
VALIDATION_PROVIDER_PLUGIN_API_REGEX = re.compile(
r"""
(?P<module> [\w.]+)
(?: \s* : \s*
(?P<attr> [\w.]+)
)?
""",
re.VERBOSE,
)
VALIDATION_PROVIDER_REQUIRES_REGEX = re.compile(r'[\S ]+')


# VALIDATION_PYTHON_PACKAGE_NAME_REGEX = re.compile(r"[^\s-]+?")
# Per PEP 508: https://peps.python.org/pep-0508/#names
VALIDATION_PYTHON_PACKAGE_NAME_REGEX = re.compile(r'[A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9]', re.IGNORECASE)
VALIDATION_WHEEL_NAME_REGEX = re.compile(
r'(?P<base_wheel_name> ' # <base_wheel_name> group (without variant)
r' (?P<namever> ' # "namever" group contains <name>-<ver>
r' (?P<name>[^\s-]+?) ' # <name>
r' - (?P<ver>[^\s-]*?) ' # "-" <ver>
r' ) ' # close "namever" group
r' (?: - (?P<build>\d[^-]*?) )? ' # optional "-" <build>
r' - (?P<pyver>[^\s-]+?) ' # "-" <pyver> tag
r' - (?P<abi>[^\s-]+?) ' # "-" <abi> tag
r' - (?P<plat>[^\s-]+?) ' # "-" <plat> tag
r') ' # end of <base_wheel_name> group
r'(?: - (?P<variant_label> ' # optional <variant_label>
rf' {VALIDATION_VARIANT_LABEL_REGEX.pattern}'
r' ) '
r')? '
r'\.whl ' # ".whl" suffix
r' ',
re.VERBOSE,
)


# ======================== Json TypedDict for the JSON format ======================== #

# NOTE: Unfortunately, it is not possible as of today to use variables in the definition
# of TypedDict. Similarly also impossible to use the normal "class format" if a
# key uses the characted `-`.
#
# For all these reasons and easier future maintenance - these classes have been
# added to this file instead of a more "format definition" file.


class PriorityJsonDict(TypedDict, total=False):
namespace: list[str]
feature: dict[str, list[str]]
property: dict[str, dict[str, list[str]]]


ProviderPluginJsonDict = TypedDict(
'ProviderPluginJsonDict',
{
'plugin-api': str,
'requires': list[str],
'enable-if': str,
'optional': bool,
'plugin-use': Literal['all', 'build', 'none'],
},
total=False,
)

VariantInfoJsonDict = dict[str, dict[str, list[str]]]


VariantsJsonDict = TypedDict(
'VariantsJsonDict',
{
'$schema': str,
'default-priorities': PriorityJsonDict,
'providers': dict[str, ProviderPluginJsonDict],
'variants': dict[str, VariantInfoJsonDict],
},
total=False,
)
Loading