Skip to content
Closed
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
6 changes: 3 additions & 3 deletions build_tools/_therock_utils/py_packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import tarfile

from .artifacts import ArtifactCatalog, ArtifactName
from .sdk_targets import package_owner
from .sdk_targets import package_owner, render_dist_info
from .exe_stub_gen import generate_exe_link_stub

is_windows = platform.system() == "Windows"
Expand Down Expand Up @@ -135,7 +135,7 @@ def __init__(
# Base: version and nonce only — no family lines. Used as the starting
# point for restrict_families packages so they can write clean family
# content without a .clear() dance.
dist_info_base = DIST_INFO_PATH.read_text()
dist_info_base = render_dist_info(DIST_INFO_PATH)
dist_info_base += f"__version__ = {repr(version)}\n"
dist_info_base += f"PY_PACKAGE_SUFFIX_NONCE = {repr(version_suffix)}\n"
self.dist_info_base_contents = dist_info_base
Expand Down Expand Up @@ -180,7 +180,7 @@ def __init__(
spec = importlib.util.spec_from_loader("rocm_sdk_dist_info", loader=None)
self.dist_info = importlib.util.module_from_spec(spec)
exec(
DIST_INFO_PATH.read_text(), self.dist_info.__dict__
render_dist_info(DIST_INFO_PATH), self.dist_info.__dict__
) # static template only, no user input
self.dist_info.__version__ = version
self.dist_info.PY_PACKAGE_SUFFIX_NONCE = version_suffix
Expand Down
31 changes: 24 additions & 7 deletions build_tools/_therock_utils/sdk_targets.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT

"""Build-time adapter to the SDK's self-contained ownership authority.

Keep ownership data in the metadata shipped by the SDK, never in this adapter.
"""
"""Build adapter and SDK embedding for the shared rocm-bootstrap leaf module."""

import importlib.util
import os
from pathlib import Path

_ROOT = Path(__file__).resolve().parents[2]
# TODO(#4865): Import rocm_bootstrap.package_metadata directly once bootstrap
# is available to TheRock packaging tools, replacing this source-path loader.
_METADATA_PATH = (
Path(__file__).resolve().parent.parent
/ "packaging/python/templates/rocm/src/rocm_sdk/_dist_info.py"
Path(os.environ.get("THEROCK_ROCM_SYSTEMS_SOURCE_DIR", _ROOT / "rocm-systems"))
/ "python/rocm-bootstrap/python/rocm_bootstrap/package_metadata.py"
)
_spec = importlib.util.spec_from_file_location("_sdk_target_metadata", _METADATA_PATH)
if _spec is None or _spec.loader is None:
raise ImportError(f"Cannot load SDK metadata from {_METADATA_PATH}")
raise ImportError(f"Cannot load target metadata from {_METADATA_PATH}")
_metadata = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_metadata)
if _metadata.ownership_data()["schema_version"] != 1:
raise ValueError("Unsupported target ownership schema")

canonical_target = _metadata.canonical_target
package_owner = _metadata.package_owner
group_package_targets = _metadata.group_package_targets
architectural_family = _metadata.architectural_family
ownership_data = _metadata.ownership_data


def render_dist_info(template: Path) -> str:
"""Embed the shared ownership source into standalone SDK metadata."""
contents = template.read_text()
start = "# BEGIN SHARED TARGET METADATA\n"
end = "# END SHARED TARGET METADATA\n"
if contents.count(start) != 1 or contents.count(end) != 1:
raise ValueError(f"Invalid target metadata embedding markers in {template}")
before, rest = contents.split(start)
_, after = rest.split(end)
return before + start + _METADATA_PATH.read_text() + "\n" + end + after
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
of bootstrapping, we are including it inline for the moment.
"""

from collections.abc import Iterable
import importlib.util
import re
import os
import subprocess
import sys
Expand All @@ -22,36 +20,18 @@

CACHED_TARGET_FAMILY: str | None = None

# Targets normally map 1:1 to packages with the same name.
# Overrides allow multiple targets to share a package owner.
_OVERRIDE_PACKAGE_OWNERS = {"gfx1250-strict": "gfx1250"}
_OVERRIDE_TARGET_ARCHITECTURAL_FAMILIES = {
"gfx1250": "gfx125X",
"gfx1250-strict": "gfx125X",
}
_TARGET_FEATURES = re.compile(r"(?:[:-](?:xnack|sramecc)[+-])+$")


def canonical_target(target: str) -> str:
"""Remove recognized target features, preserving canonical variant names."""
return _TARGET_FEATURES.sub("", target)


def package_owner(target: str) -> str:
"""Resolve a supplied target/family to its package owner."""
target = canonical_target(target)
owner = _OVERRIDE_PACKAGE_OWNERS.get(target)
return target if owner is None else owner

# BEGIN SHARED TARGET METADATA
# Build-time import only. render_dist_info() replaces this marked block with
# the shared metadata source before packaging; installed SDK packages do not
# depend on _therock_utils or rocm_bootstrap.
from _therock_utils.sdk_targets import (
canonical_target,
package_owner,
group_package_targets,
architectural_family,
)

def group_package_targets(targets: Iterable[str]) -> dict[str, list[str]]:
"""Group only supplied members, retaining their full input identities."""
groups: dict[str, list[str]] = {}
for target in targets:
members = groups.setdefault(package_owner(target), [])
if target not in members:
members.append(target)
return groups
# END SHARED TARGET METADATA


class LibraryEntry:
Expand Down Expand Up @@ -180,9 +160,7 @@ def discover_current_target_family() -> str | None:
# There may be multiple architecture supported on the system.
# This will select the first matching family.
canonical_arch = canonical_target(arch)
arch_family = _OVERRIDE_TARGET_ARCHITECTURAL_FAMILIES.get(
canonical_arch
)
arch_family = architectural_family(canonical_arch)
if arch_family is None:
arch_family = canonical_arch[:-1] + "X"
for suffix in suffixes:
Expand Down
23 changes: 23 additions & 0 deletions build_tools/tests/py_packaging_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,29 @@ def test_dist_info_object_matches_generated_file(self):
self.assertNotIn("Requires-Dist: rocm==7.0.0\n", pkg_info)
self.assertIn("Requires-Dist: rocm-sdk-core==7.0.0\n", pkg_info)

def test_generated_metadata_runs_without_build_dependencies(self):
params = self._make_two_family_params()
meta = PopulatedDistPackage(params, logical_name="meta")
metadata_path = meta.path / "src" / "rocm_sdk" / "_dist_info.py"
result = subprocess.run(
[
sys.executable,
"-I",
"-S",
"-c",
"import runpy, sys\n"
"metadata = runpy.run_path(sys.argv[1])\n"
"assert metadata['package_owner']('gfx1250-strict') == 'gfx1250'\n"
"assert metadata['canonical_target']('gfx1250-strict:xnack+') == 'gfx1250-strict'\n"
"assert 'rocm_bootstrap' not in sys.modules\n"
"assert '_therock_utils' not in sys.modules\n",
str(metadata_path),
],
capture_output=True,
text=True,
)
self.assertEqual(result.returncode, 0, result.stderr)


# ---------------------------------------------------------------------------
# Tests for cross-platform family awareness in the rocm sdist
Expand Down
30 changes: 30 additions & 0 deletions build_tools/tests/sdk_targets_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
from pathlib import Path
import sys
import tempfile
import unittest

sys.path.insert(0, os.fspath(Path(__file__).parent.parent))
Expand All @@ -14,6 +15,8 @@
canonical_target,
group_package_targets,
package_owner,
ownership_data,
render_dist_info,
)


Expand All @@ -40,5 +43,32 @@ def test_grouping_retains_supplied_members_and_order(self):
)


class RenderDistInfoTest(unittest.TestCase):
def test_embedded_ownership_matches_build_metadata(self):
template = (
Path(__file__).resolve().parents[1]
/ "packaging/python/templates/rocm/src/rocm_sdk/_dist_info.py"
)
source = render_dist_info(template)
self.assertNotIn("from _therock_utils", source)
namespace = {}
exec(source, namespace)
self.assertEqual(namespace["ownership_data"](), ownership_data())
self.assertEqual(namespace["package_owner"]("gfx1250-strict"), "gfx1250")

def test_missing_or_duplicate_markers_are_rejected(self):
start = "# BEGIN SHARED TARGET METADATA\n"
end = "# END SHARED TARGET METADATA\n"
with tempfile.TemporaryDirectory() as tmp:
template = Path(tmp) / "template.py"
for contents in ("", start, end, start + start + end, start + end + end):
with self.subTest(contents=contents):
template.write_text(contents)
with self.assertRaisesRegex(
ValueError, "Invalid target metadata embedding markers"
):
render_dist_info(template)


if __name__ == "__main__":
unittest.main()
Loading