|
14 | 14 | from typing import TYPE_CHECKING, Any, Callable, Iterable, NamedTuple, Sequence, Tuple, cast |
15 | 15 |
|
16 | 16 | if TYPE_CHECKING: |
| 17 | + from hatchling.bridge.app import Application |
17 | 18 | from hatchling.metadata.core import ProjectMetadata |
| 19 | + from hatchling.plugin.manager import PluginManagerBound |
18 | 20 |
|
19 | 21 | from hatchling.__about__ import __version__ |
20 | 22 | from hatchling.builders.config import BuilderConfig |
|
47 | 49 | VARIANTS_JSON_SCHEMA_URL, |
48 | 50 | VARIANTS_JSON_VARIANT_DATA_KEY, |
49 | 51 | ) |
| 52 | +from hatchling.metadata.core import VariantConfig |
50 | 53 | from hatchling.metadata.spec import DEFAULT_METADATA_VERSION, get_core_metadata_constructors |
51 | 54 |
|
52 | 55 | if TYPE_CHECKING: |
@@ -307,75 +310,73 @@ def core_metadata_constructor(self) -> Callable[..., str]: |
307 | 310 | @property |
308 | 311 | def variants_json_constructor(self) -> Callable[..., str]: |
309 | 312 | if self.__variants_json_constructor is None: |
310 | | - def constructor(metadata: ProjectMetadata) -> str: |
311 | | - if metadata.variant_hash is not None: |
312 | | - data = { |
313 | | - VARIANTS_JSON_SCHEMA_KEY: VARIANTS_JSON_SCHEMA_URL, |
314 | | - VARIANT_INFO_DEFAULT_PRIO_KEY: {}, |
315 | | - VARIANT_INFO_PROVIDER_DATA_KEY: {}, |
316 | | - VARIANTS_JSON_VARIANT_DATA_KEY: {} |
317 | | - } |
318 | | - |
319 | | - # ==================== VARIANT_INFO_DEFAULT_PRIO_KEY ==================== # |
320 | | - |
321 | | - if (ns_prio := metadata.variant_default_priorities["namespace"]): |
322 | | - data[VARIANT_INFO_DEFAULT_PRIO_KEY][VARIANT_INFO_NAMESPACE_KEY] = ns_prio |
323 | | - |
324 | | - if (feat_prio := metadata.variant_default_priorities["feature"]): |
325 | | - data[VARIANT_INFO_DEFAULT_PRIO_KEY][VARIANT_INFO_FEATURE_KEY] = feat_prio |
326 | | - |
327 | | - if (prop_prio := metadata.variant_default_priorities["property"]): |
328 | | - data[VARIANT_INFO_DEFAULT_PRIO_KEY][VARIANT_INFO_PROPERTY_KEY] = prop_prio |
329 | | - |
330 | | - if not data[VARIANT_INFO_DEFAULT_PRIO_KEY]: |
331 | | - # If no default priorities are set, remove the key |
332 | | - del data[VARIANT_INFO_DEFAULT_PRIO_KEY] |
333 | | - |
334 | | - # ==================== VARIANT_INFO_PROVIDER_DATA_KEY ==================== # |
335 | | - |
336 | | - variant_providers = defaultdict(dict) |
337 | | - for ns, plugin_conf in metadata.variant_plugins.items(): |
338 | | - variant_providers[ns][VARIANT_INFO_PROVIDER_REQUIRES_KEY] = plugin_conf.get("requires", []) |
339 | | - |
340 | | - if (enable_if := plugin_conf.get("enable_if", None)) is not None: |
341 | | - variant_providers[ns][VARIANT_INFO_PROVIDER_ENABLE_IF_KEY] = enable_if |
342 | | - |
343 | | - if plugin_conf.get("optional", False): |
344 | | - variant_providers[ns][VARIANT_INFO_PROVIDER_OPTIONAL_KEY] = True |
345 | | - |
346 | | - if (plugin_api := plugin_conf.get("plugin_api", None)) is not None: |
347 | | - variant_providers[ns][VARIANT_INFO_PROVIDER_PLUGIN_API_KEY] = plugin_api |
348 | | - |
349 | | - data[VARIANT_INFO_PROVIDER_DATA_KEY] = variant_providers |
350 | | - |
351 | | - # ==================== VARIANTS_JSON_VARIANT_DATA_KEY ==================== # |
352 | | - |
353 | | - variant_data = defaultdict(lambda: defaultdict(set)) |
354 | | - for vprop_str in metadata.variant_properties: |
355 | | - match = VALIDATION_PROPERTY_REGEX.match(vprop_str) |
356 | | - if not match: |
357 | | - raise ValueError( |
358 | | - f"Invalid variant property '{vprop_str}' in variant {metadata.variant_hash}" |
359 | | - ) |
360 | | - namespace = match.group('namespace') |
361 | | - feature = match.group('feature') |
362 | | - value = match.group('value') |
363 | | - variant_data[namespace][feature].add(value) |
364 | | - data[VARIANTS_JSON_VARIANT_DATA_KEY][metadata.variant_hash] = variant_data |
365 | | - |
366 | | - def preprocess(data): |
367 | | - """Preprocess the data to ensure it is JSON serializable.""" |
368 | | - if isinstance(data, (defaultdict, dict)): |
369 | | - return {k: preprocess(v) for k, v in data.items()} |
370 | | - if isinstance(data, set): |
371 | | - return list(data) |
372 | | - return data |
373 | | - |
374 | | - return json.dumps( |
375 | | - preprocess(data), indent=4, sort_keys=True, ensure_ascii=False |
376 | | - ) |
377 | | - return '' |
378 | | - self.__variants_json_constructor = constructor |
| 313 | + def constructor(variant_config: VariantConfig) -> str: |
| 314 | + data = { |
| 315 | + VARIANTS_JSON_SCHEMA_KEY: VARIANTS_JSON_SCHEMA_URL, |
| 316 | + VARIANT_INFO_DEFAULT_PRIO_KEY: {}, |
| 317 | + VARIANT_INFO_PROVIDER_DATA_KEY: {}, |
| 318 | + VARIANTS_JSON_VARIANT_DATA_KEY: {} |
| 319 | + } |
| 320 | + |
| 321 | + # ==================== VARIANT_INFO_DEFAULT_PRIO_KEY ==================== # |
| 322 | + |
| 323 | + if (ns_prio := variant_config.default_priorities["namespace"]): |
| 324 | + data[VARIANT_INFO_DEFAULT_PRIO_KEY][VARIANT_INFO_NAMESPACE_KEY] = ns_prio |
| 325 | + |
| 326 | + if (feat_prio := variant_config.default_priorities["feature"]): |
| 327 | + data[VARIANT_INFO_DEFAULT_PRIO_KEY][VARIANT_INFO_FEATURE_KEY] = feat_prio |
| 328 | + |
| 329 | + if (prop_prio := variant_config.default_priorities["property"]): |
| 330 | + data[VARIANT_INFO_DEFAULT_PRIO_KEY][VARIANT_INFO_PROPERTY_KEY] = prop_prio |
| 331 | + |
| 332 | + if not data[VARIANT_INFO_DEFAULT_PRIO_KEY]: |
| 333 | + # If no default priorities are set, remove the key |
| 334 | + del data[VARIANT_INFO_DEFAULT_PRIO_KEY] |
| 335 | + |
| 336 | + # ==================== VARIANT_INFO_PROVIDER_DATA_KEY ==================== # |
| 337 | + |
| 338 | + variant_providers = defaultdict(dict) |
| 339 | + for ns, provider_cfg in variant_config.providers.items(): |
| 340 | + variant_providers[ns][VARIANT_INFO_PROVIDER_REQUIRES_KEY] = provider_cfg.requires |
| 341 | + |
| 342 | + if provider_cfg.enable_if is not None: |
| 343 | + variant_providers[ns][VARIANT_INFO_PROVIDER_ENABLE_IF_KEY] = provider_cfg.enable_if |
| 344 | + |
| 345 | + if provider_cfg.optional: |
| 346 | + variant_providers[ns][VARIANT_INFO_PROVIDER_OPTIONAL_KEY] = True |
| 347 | + |
| 348 | + if provider_cfg.plugin_api is not None: |
| 349 | + variant_providers[ns][VARIANT_INFO_PROVIDER_PLUGIN_API_KEY] = provider_cfg.plugin_api |
| 350 | + |
| 351 | + data[VARIANT_INFO_PROVIDER_DATA_KEY] = variant_providers |
| 352 | + |
| 353 | + # ==================== VARIANTS_JSON_VARIANT_DATA_KEY ==================== # |
| 354 | + |
| 355 | + variant_data = defaultdict(lambda: defaultdict(set)) |
| 356 | + for vprop_str in (variant_config.properties or []): |
| 357 | + match = VALIDATION_PROPERTY_REGEX.match(vprop_str) |
| 358 | + if not match: |
| 359 | + raise ValueError( |
| 360 | + f"Invalid variant property '{vprop_str}' in variant {variant_config.variant_hash}" |
| 361 | + ) |
| 362 | + namespace = match.group('namespace') |
| 363 | + feature = match.group('feature') |
| 364 | + value = match.group('value') |
| 365 | + variant_data[namespace][feature].add(value) |
| 366 | + data[VARIANTS_JSON_VARIANT_DATA_KEY][variant_config.vhash] = variant_data |
| 367 | + |
| 368 | + def preprocess(data): |
| 369 | + """Preprocess the data to ensure it is JSON serializable.""" |
| 370 | + if isinstance(data, (defaultdict, dict)): |
| 371 | + return {k: preprocess(v) for k, v in data.items()} |
| 372 | + if isinstance(data, set): |
| 373 | + return list(data) |
| 374 | + return data |
| 375 | + |
| 376 | + return json.dumps( |
| 377 | + preprocess(data), indent=4, sort_keys=True, ensure_ascii=False |
| 378 | + ) |
| 379 | + self.__variants_json_constructor = constructor |
379 | 380 | return self.__variants_json_constructor |
380 | 381 |
|
381 | 382 | @property |
@@ -545,6 +546,32 @@ class WheelBuilder(BuilderInterface): |
545 | 546 |
|
546 | 547 | PLUGIN_NAME = 'wheel' |
547 | 548 |
|
| 549 | + def __init__( |
| 550 | + self, |
| 551 | + root: str, |
| 552 | + plugin_manager: PluginManagerBound | None = None, |
| 553 | + config: dict[str, Any] | None = None, |
| 554 | + metadata: ProjectMetadata | None = None, |
| 555 | + app: Application | None = None, |
| 556 | + variant_props: list[str] | None = None, |
| 557 | + variant_label: str | None = None, |
| 558 | + ): |
| 559 | + metadata.variant_config = VariantConfig.from_dict( |
| 560 | + data=metadata.variant_config_data, |
| 561 | + vprops=variant_props, |
| 562 | + variant_label=variant_label, |
| 563 | + ) |
| 564 | + metadata.variant_config.validate() |
| 565 | + metadata.variant_hash = metadata.variant_config.vhash |
| 566 | + |
| 567 | + super().__init__( |
| 568 | + root=root, |
| 569 | + plugin_manager=plugin_manager, |
| 570 | + config=config, |
| 571 | + metadata=metadata, |
| 572 | + app=app, |
| 573 | + ) |
| 574 | + |
548 | 575 | def get_version_api(self) -> dict[str, Callable]: |
549 | 576 | return {'standard': self.build_standard, 'editable': self.build_editable} |
550 | 577 |
|
@@ -579,7 +606,11 @@ def build_standard(self, directory: str, **build_data: Any) -> str: |
579 | 606 | records.write((f'{archive.metadata_directory}/RECORD', '', '')) |
580 | 607 | archive.write_metadata('RECORD', records.construct()) |
581 | 608 |
|
582 | | - target = os.path.join(directory, f"{self.artifact_project_id}-{build_data['tag']}.whl") |
| 609 | + if self.metadata.variant_hash is not None: |
| 610 | + wheel_name = f"{self.artifact_project_id}-{build_data['tag']}-{self.metadata.variant_hash}.whl" |
| 611 | + else: |
| 612 | + wheel_name = f"{self.artifact_project_id}-{build_data['tag']}.whl" |
| 613 | + target = os.path.join(directory, wheel_name) |
583 | 614 |
|
584 | 615 | replace_file(archive.path, target) |
585 | 616 | normalize_artifact_permissions(target) |
@@ -806,11 +837,12 @@ def write_project_metadata( |
806 | 837 | 'METADATA', self.config.core_metadata_constructor(self.metadata, extra_dependencies=extra_dependencies) |
807 | 838 | ) |
808 | 839 | records.write(record) |
809 | | - record = archive.write_metadata( |
810 | | - VARIANT_DIST_INFO_FILENAME, |
811 | | - self.config.variants_json_constructor(self.metadata), |
812 | | - ) |
813 | | - records.write(record) |
| 840 | + if self.metadata.variant_hash is not None: |
| 841 | + record = archive.write_metadata( |
| 842 | + VARIANT_DIST_INFO_FILENAME, |
| 843 | + self.config.variants_json_constructor(self.metadata.variant_config), |
| 844 | + ) |
| 845 | + records.write(record) |
814 | 846 |
|
815 | 847 | def add_licenses(self, archive: WheelArchive, records: RecordFile) -> None: |
816 | 848 | for relative_path in self.metadata.core.license_files: |
|
0 commit comments