Files
TheRock/build_tools/_therock_utils/python_package_paths.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

181 lines
5.8 KiB
Python

# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""Path/key computation for structured product-local Python package publishing.
The stream-subdomain repository layout places Python artifacts under
product-local package directories:
<product>/<index>/<normalized-package>/<filename>
where <index> is ``whl`` or ``whl-next``. Release stream (dev/nightly/
prerelease) is selected by the target bucket, never encoded in the path. This
module computes the per-file destination keys the release publishers use when
run with ``--structured``; the generator in ``manage_structured.py`` later
discovers and indexes those directories.
pep503_normalize + package-name extraction here intentionally mirror
manage_structured.py so producer output round-trips through its
discover_packages(). They are duplicated (a few lines) rather than shared
because manage_structured.py runs server-side and is deployed on its own; a
shared import would drag this module into that deployment.
"""
import dataclasses
from pathlib import Path
from re import sub
from _therock_utils.storage_location import StorageLocation
from packaging.utils import (
InvalidSdistFilename,
InvalidWheelFilename,
parse_sdist_filename,
parse_wheel_filename,
)
# Distribution artifacts placed into package directories.
ACCEPTED_FILE_EXTENSIONS = (".whl", ".tar.gz", ".zip")
# Valid aggregate index names (the second path segment).
INDEX_NAMES = ("whl", "whl-next")
def pep503_normalize(name: str) -> str:
"""Normalize a package name per PEP 503.
Lowercase and collapse runs of "-", "_", and "." to a single "-".
"""
return sub(r"[-_.]+", "-", name.lower())
def package_name_from_filename(filename: str) -> str:
"""Return the PEP 503-normalized package name for a distribution artifact.
Uses spec-aware parsing (packaging.utils) so that sdist/zip filenames
with hyphens in the project name are handled correctly (e.g.
``llnl-hatchet-2024.1.tar.gz`` -> ``llnl-hatchet``).
Raises:
ValueError: if the filename cannot be parsed as a wheel or sdist.
"""
if filename.endswith(".whl"):
try:
name, _, _, _ = parse_wheel_filename(filename)
return name
except InvalidWheelFilename as e:
raise ValueError(f"Cannot parse wheel filename {filename!r}: {e}") from e
try:
name, _ = parse_sdist_filename(filename)
return name
except InvalidSdistFilename as e:
raise ValueError(f"Cannot parse sdist filename {filename!r}: {e}") from e
def is_accepted_artifact(filename: str) -> bool:
return filename.endswith(ACCEPTED_FILE_EXTENSIONS)
def structured_key(product: str, index: str, filename: str) -> str:
"""Compute the structured S3 key for an artifact.
Returns ``v5/rocm/<product>/<index>/<normalized-package>/<filename>``.
``v5`` is the layout schema version and the S3 origin prefix; ``rocm`` is
the served tree segment, so the object serves at
``<stream>.repo.amd.com/rocm/<product>/<index>/...`` with no rewrite.
Raises:
ValueError: if ``index`` is not a valid aggregate index name.
"""
if index not in INDEX_NAMES:
raise ValueError(f"index={index!r} is invalid, must be one of {INDEX_NAMES}")
package = package_name_from_filename(filename)
return f"v5/rocm/{product}/{index}/{package}/{filename}"
@dataclasses.dataclass
class PlannedUpload:
"""A local artifact to upload into a structured package directory.
Attributes:
source: Local path to the artifact.
dest: Destination location in the structured layout.
"""
source: Path
dest: StorageLocation
@dataclasses.dataclass
class PlannedCopy:
"""An S3 artifact to copy into a structured package directory.
Attributes:
source: Source location (an existing S3 object).
dest: Destination location in the structured layout.
"""
source: StorageLocation
dest: StorageLocation
def plan_local_uploads(
source_dir: Path,
dest_bucket: str,
product: str,
index: str,
) -> list[PlannedUpload]:
"""Plan structured uploads for accepted artifacts in a local directory.
Enumerates top-level artifacts in ``source_dir`` (not recursive: publish
sources are flat directories of wheels/sdists) and computes their structured
destinations. Sorted by filename for stable, reviewable output.
"""
return [
PlannedUpload(
source=path,
dest=StorageLocation(
dest_bucket, structured_key(product, index, path.name)
),
)
for path in sorted(source_dir.iterdir())
if path.is_file() and is_accepted_artifact(path.name)
]
def plan_key_copies(
source_keys: list[str],
source_bucket: str,
dest_bucket: str,
product: str,
index: str,
) -> list[PlannedCopy]:
"""Plan structured copies for accepted artifacts listed from S3.
Args:
source_keys: Full S3 keys returned by a listing (in ``source_bucket``).
source_bucket: Bucket the source keys live in.
dest_bucket: Destination bucket name.
product: Product segment (e.g. ``core``).
index: Index segment (``whl`` or ``whl-next``).
Returns:
Planned copies for accepted artifacts, sorted by source key. Only the
basename of each source key is used to place the artifact (the
structured layout is flat within each package directory).
"""
plans: list[PlannedCopy] = []
for key in sorted(source_keys):
filename = key.rsplit("/", 1)[-1]
if not is_accepted_artifact(filename):
continue
plans.append(
PlannedCopy(
source=StorageLocation(source_bucket, key),
dest=StorageLocation(
dest_bucket, structured_key(product, index, filename)
),
)
)
return plans