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
998 lines
33 KiB
Python
Executable File
998 lines
33 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Copyright Advanced Micro Devices, Inc.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
# Fetches sources from a specified branch/set of projects.
|
|
# This script is available for users, but it is primarily the mechanism
|
|
# the CI uses to get to a clean state.
|
|
#
|
|
# Stage-aware fetching:
|
|
# Use --stage <stage_name> to fetch only submodules needed for a build stage.
|
|
# This uses BUILD_TOPOLOGY.toml to determine which submodules are required.
|
|
#
|
|
# Legacy flag-based fetching:
|
|
# Use --include-* flags to control which project groups to fetch.
|
|
# This is the original behavior and is still supported.
|
|
|
|
import argparse
|
|
import concurrent.futures
|
|
import hashlib
|
|
from pathlib import Path
|
|
import platform
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
from typing import List
|
|
import os
|
|
|
|
import fetch_dvc_artifacts
|
|
from _therock_utils.git_mirrors import MIRROR_DIR_ENV, url_to_mirror_relpath
|
|
from _therock_utils.branch_config import (
|
|
get_source_sets_for_artifact_groups,
|
|
load_branch_config,
|
|
)
|
|
from _therock_utils.build_topology import BuildTopology, ExternalGitSource
|
|
|
|
THIS_SCRIPT_DIR = Path(__file__).resolve().parent
|
|
THEROCK_DIR = THIS_SCRIPT_DIR.parent
|
|
PATCHES_DIR = THEROCK_DIR / "patches"
|
|
TOPOLOGY_PATH = THEROCK_DIR / "BUILD_TOPOLOGY.toml"
|
|
BRANCH_CONFIG_PATH = THEROCK_DIR / "BRANCH_CONFIG.json"
|
|
ALWAYS_SUBMODULE_PATHS: list[str] = []
|
|
|
|
|
|
def is_windows() -> bool:
|
|
return platform.system() == "Windows"
|
|
|
|
|
|
def log(*args, **kwargs):
|
|
print(*args, **kwargs)
|
|
sys.stdout.flush()
|
|
|
|
|
|
def run_command(args: list[str | Path], cwd: Path, env: dict[str, str] | None = None):
|
|
args = [str(arg) for arg in args]
|
|
log(f"++ Exec [{cwd}]$ {shlex.join(args)}")
|
|
sys.stdout.flush()
|
|
|
|
full_env = {**os.environ, **(env or {})}
|
|
subprocess.check_call(args, cwd=str(cwd), env=full_env, stdin=subprocess.DEVNULL)
|
|
|
|
|
|
def resolve_reference_dir(args: argparse.Namespace) -> Path | None:
|
|
"""Resolve the git mirror/reference directory from args or environment.
|
|
|
|
Returns None if no reference directory is configured, which means
|
|
submodule updates proceed with normal network fetches (unchanged behavior).
|
|
"""
|
|
ref_dir = args.reference_dir
|
|
if ref_dir is None:
|
|
env_val = os.environ.get(MIRROR_DIR_ENV)
|
|
if env_val:
|
|
ref_dir = Path(env_val)
|
|
if ref_dir is None:
|
|
return None
|
|
ref_dir = Path(ref_dir).resolve()
|
|
if not ref_dir.is_dir():
|
|
log(
|
|
f"WARNING: Reference directory {ref_dir} does not exist. "
|
|
f"Proceeding without reference repos."
|
|
)
|
|
return None
|
|
return ref_dir
|
|
|
|
|
|
def _resolve_mirror_path(reference_dir: Path, url: str) -> Path | None:
|
|
"""Find the local mirror for a submodule URL, or None if not available."""
|
|
mirror = reference_dir / url_to_mirror_relpath(url)
|
|
if mirror.is_dir():
|
|
return mirror
|
|
return None
|
|
|
|
|
|
def _get_submodule_url_map() -> dict[str, str]:
|
|
"""Build a mapping from submodule path to remote URL from .gitmodules."""
|
|
result = subprocess.run(
|
|
[
|
|
"git",
|
|
"config",
|
|
"--file",
|
|
".gitmodules",
|
|
"--get-regexp",
|
|
r"submodule\..*\.url",
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
cwd=str(THEROCK_DIR),
|
|
)
|
|
if result.returncode != 0:
|
|
return {}
|
|
|
|
path_to_url: dict[str, str] = {}
|
|
for line in result.stdout.strip().split("\n"):
|
|
if not line.strip():
|
|
continue
|
|
key, url = line.split(None, 1)
|
|
name = key.split(".")[1]
|
|
try:
|
|
path = get_submodule_path(name)
|
|
path_to_url[path] = url
|
|
except subprocess.CalledProcessError:
|
|
continue
|
|
return path_to_url
|
|
|
|
|
|
def _submodule_is_initialized(submodule_path: str) -> bool:
|
|
"""Check whether a submodule directory has been cloned/initialized."""
|
|
git_marker = THEROCK_DIR / submodule_path / ".git"
|
|
return git_marker.exists()
|
|
|
|
|
|
def _update_one_submodule(
|
|
submodule_path: str,
|
|
update_args: list[str],
|
|
mirror: Path | None,
|
|
) -> None:
|
|
"""Fetch/clone a single already-registered submodule.
|
|
|
|
Callers must run ``git submodule init`` before invoking this function
|
|
so that the submodule URL is already recorded in ``.git/config``.
|
|
Separating init (serial) from update (parallel) avoids lock contention
|
|
on ``.git/config`` when multiple updates run concurrently.
|
|
|
|
If the --reference clone fails, retries automatically without --reference.
|
|
"""
|
|
cmd: list[str | Path] = ["git", "submodule", "update"]
|
|
if mirror:
|
|
cmd += ["--reference", str(mirror)]
|
|
log(f" {submodule_path}: using reference {mirror}")
|
|
else:
|
|
log(f" {submodule_path}: no mirror found, fetching from network")
|
|
cmd += update_args + ["--", submodule_path]
|
|
|
|
try:
|
|
run_command(cmd, cwd=THEROCK_DIR)
|
|
except subprocess.CalledProcessError:
|
|
if mirror:
|
|
log(
|
|
f" WARNING: --reference clone failed for {submodule_path}, "
|
|
f"retrying without reference..."
|
|
)
|
|
fallback_cmd: list[str | Path] = (
|
|
[
|
|
"git",
|
|
"submodule",
|
|
"update",
|
|
]
|
|
+ update_args
|
|
+ ["--", submodule_path]
|
|
)
|
|
run_command(fallback_cmd, cwd=THEROCK_DIR)
|
|
else:
|
|
raise
|
|
|
|
|
|
def _update_submodules_with_reference(
|
|
submodule_paths: list[str],
|
|
update_args: list[str],
|
|
reference_dir: Path,
|
|
jobs: int,
|
|
) -> None:
|
|
"""Update submodules using local mirror repos as git reference clones.
|
|
|
|
For uninitialized submodules the work is split into two phases to avoid
|
|
Git lock contention when running in parallel:
|
|
|
|
Phase 1 (serial): ``git submodule init`` registers all submodule URLs
|
|
in ``.git/config`` in a single command -- this is the step that takes
|
|
the ``.git/config`` lock.
|
|
|
|
Phase 2 (parallel): ``git submodule update --reference <mirror>`` is
|
|
run per-submodule, bounded by *jobs*. Each invocation clones into a
|
|
separate directory so there is no shared-lock contention.
|
|
|
|
Already-initialized submodules are batch-updated in a single git command
|
|
to preserve --jobs parallelism for the (typically fast) delta fetch.
|
|
|
|
If a --reference clone fails, the submodule is retried without --reference
|
|
as an automatic fallback.
|
|
"""
|
|
path_to_url = _get_submodule_url_map()
|
|
|
|
needs_init: list[str] = []
|
|
already_init: list[str] = []
|
|
for sp in submodule_paths:
|
|
if _submodule_is_initialized(sp):
|
|
already_init.append(sp)
|
|
else:
|
|
needs_init.append(sp)
|
|
|
|
if needs_init:
|
|
log(
|
|
f"Initializing {len(needs_init)} submodule(s) with reference repos "
|
|
f"(jobs={jobs})..."
|
|
)
|
|
|
|
# Phase 1: Register submodule URLs in .git/config (single serial
|
|
# command so there is no lock contention on .git/config).
|
|
run_command(
|
|
["git", "submodule", "init", "--"] + needs_init,
|
|
cwd=THEROCK_DIR,
|
|
)
|
|
|
|
# Phase 2: Clone/fetch each submodule in parallel. Each targets a
|
|
# separate directory so concurrent git processes don't contend.
|
|
update_tasks: list[tuple[str, Path | None]] = []
|
|
for sp in needs_init:
|
|
url = path_to_url.get(sp)
|
|
mirror = _resolve_mirror_path(reference_dir, url) if url else None
|
|
update_tasks.append((sp, mirror))
|
|
|
|
if jobs <= 1:
|
|
for sp, mirror in update_tasks:
|
|
_update_one_submodule(sp, update_args, mirror)
|
|
else:
|
|
errors: list[Exception] = []
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=jobs) as pool:
|
|
futures = {
|
|
pool.submit(_update_one_submodule, sp, update_args, mirror): sp
|
|
for sp, mirror in update_tasks
|
|
}
|
|
for future in concurrent.futures.as_completed(futures):
|
|
sp = futures[future]
|
|
try:
|
|
future.result()
|
|
except (
|
|
subprocess.CalledProcessError,
|
|
OSError,
|
|
) as exc:
|
|
log(f" ERROR: submodule update failed for {sp}: {exc}")
|
|
errors.append(exc)
|
|
if errors:
|
|
raise errors[0]
|
|
|
|
if already_init:
|
|
log(f"Updating {len(already_init)} already-initialized submodule(s)...")
|
|
run_command(
|
|
["git", "submodule", "update", "--init"]
|
|
+ update_args
|
|
+ ["--"]
|
|
+ already_init,
|
|
cwd=THEROCK_DIR,
|
|
)
|
|
|
|
|
|
def get_projects_from_topology(stage: str) -> List[str]:
|
|
"""Get submodule names for a build stage from BUILD_TOPOLOGY.toml."""
|
|
if not TOPOLOGY_PATH.exists():
|
|
raise FileNotFoundError(f"BUILD_TOPOLOGY.toml not found at {TOPOLOGY_PATH}")
|
|
|
|
topology = BuildTopology(str(TOPOLOGY_PATH))
|
|
current_platform = platform.system().lower()
|
|
submodules = topology.get_submodules_for_stage(stage, platform=current_platform)
|
|
return [s.name for s in submodules]
|
|
|
|
|
|
def get_available_stages() -> List[str]:
|
|
"""Get list of available build stages from BUILD_TOPOLOGY.toml."""
|
|
if not TOPOLOGY_PATH.exists():
|
|
return []
|
|
|
|
topology = BuildTopology(str(TOPOLOGY_PATH))
|
|
return [s.name for s in topology.get_build_stages()]
|
|
|
|
|
|
def get_topology() -> BuildTopology:
|
|
"""Load BUILD_TOPOLOGY.toml."""
|
|
if not TOPOLOGY_PATH.exists():
|
|
raise FileNotFoundError(f"BUILD_TOPOLOGY.toml not found at {TOPOLOGY_PATH}")
|
|
return BuildTopology(str(TOPOLOGY_PATH))
|
|
|
|
|
|
def parse_source_set_args(source_sets: list[str] | None) -> list[str]:
|
|
"""Parse source set CLI args, accepting spaces and commas."""
|
|
if not source_sets:
|
|
return []
|
|
result: list[str] = []
|
|
seen: set[str] = set()
|
|
for value in source_sets:
|
|
for name in value.split(","):
|
|
name = name.strip()
|
|
if not name or name in seen:
|
|
continue
|
|
seen.add(name)
|
|
result.append(name)
|
|
return result
|
|
|
|
|
|
def _append_source_set_contents(
|
|
topology: BuildTopology,
|
|
source_set_names: list[str],
|
|
projects_by_name: dict[str, str],
|
|
external_sources_by_path: dict[str, ExternalGitSource],
|
|
*,
|
|
current_platform: str | None = None,
|
|
) -> None:
|
|
"""Append the submodules and external sources from source sets."""
|
|
for source_set_name in source_set_names:
|
|
if source_set_name not in topology.source_sets:
|
|
raise ValueError(f"Source set '{source_set_name}' not found")
|
|
source_set = topology.source_sets[source_set_name]
|
|
if current_platform and current_platform in source_set.disable_platforms:
|
|
continue
|
|
for submodule in source_set.submodules:
|
|
if submodule.name not in projects_by_name:
|
|
projects_by_name[submodule.name] = submodule.name
|
|
for external_source in source_set.external_git_sources:
|
|
if external_source.path not in external_sources_by_path:
|
|
external_sources_by_path[external_source.path] = external_source
|
|
|
|
|
|
def get_enabled_sources(args) -> tuple[List[str], list[ExternalGitSource]]:
|
|
"""Get submodule and external git sources to fetch.
|
|
|
|
If --stage is provided, uses BUILD_TOPOLOGY.toml to determine submodules.
|
|
Otherwise, uses the legacy --include-* flags.
|
|
"""
|
|
topology = get_topology()
|
|
branch_config = load_branch_config(BRANCH_CONFIG_PATH, topology)
|
|
current_platform = platform.system().lower()
|
|
explicit_source_sets = parse_source_set_args(args.source_sets)
|
|
projects_by_name: dict[str, str] = {}
|
|
external_sources_by_path: dict[str, ExternalGitSource] = {}
|
|
|
|
# Stage-aware mode: use topology
|
|
if args.stage:
|
|
stage_source_sets = [
|
|
source_set.name
|
|
for source_set in topology.get_source_sets_for_stage(
|
|
args.stage, platform=current_platform
|
|
)
|
|
]
|
|
stage = topology.build_stages[args.stage]
|
|
branch_source_sets = get_source_sets_for_artifact_groups(
|
|
branch_config, stage.artifact_groups
|
|
)
|
|
_append_source_set_contents(
|
|
topology,
|
|
stage_source_sets + branch_source_sets + explicit_source_sets,
|
|
projects_by_name,
|
|
external_sources_by_path,
|
|
current_platform=current_platform,
|
|
)
|
|
# Apply --skip-submodules filter
|
|
skip_set = set(args.skip_submodules or [])
|
|
if skip_set:
|
|
original_count = len(projects_by_name)
|
|
projects_by_name = {
|
|
k: v for k, v in projects_by_name.items() if k not in skip_set
|
|
}
|
|
skipped_count = original_count - len(projects_by_name)
|
|
if skipped_count > 0:
|
|
log(
|
|
f"Skipped {skipped_count} submodule(s) via --skip-submodules: "
|
|
f"{sorted(skip_set)}"
|
|
)
|
|
projects = list(projects_by_name)
|
|
log(f"Stage '{args.stage}' requires submodules: {projects}")
|
|
external_sources = list(external_sources_by_path.values())
|
|
if external_sources:
|
|
log(
|
|
f"Stage '{args.stage}' requires external git sources: "
|
|
f"{[source.name for source in external_sources]}"
|
|
)
|
|
return projects, external_sources
|
|
|
|
# Legacy flag-based mode
|
|
projects = []
|
|
if args.include_system_projects:
|
|
projects.extend(args.system_projects)
|
|
if args.include_compilers:
|
|
projects.extend(args.compiler_projects)
|
|
if args.include_debug_tools:
|
|
projects.extend(args.debug_tools)
|
|
if args.include_rocm_libraries:
|
|
projects.extend(["rocm-libraries"])
|
|
if args.include_rocm_systems:
|
|
projects.extend(["rocm-systems"])
|
|
if args.include_ml_frameworks:
|
|
projects.extend(args.ml_framework_projects)
|
|
if args.include_media_libs:
|
|
projects.extend(args.media_libs_projects)
|
|
if args.include_math_libraries:
|
|
projects.extend(args.math_library_projects)
|
|
|
|
for project in projects:
|
|
if project not in projects_by_name:
|
|
projects_by_name[project] = project
|
|
|
|
_append_source_set_contents(
|
|
topology,
|
|
branch_config.source_sets + explicit_source_sets,
|
|
projects_by_name,
|
|
external_sources_by_path,
|
|
current_platform=current_platform,
|
|
)
|
|
# Apply --skip-submodules filter
|
|
skip_set = set(args.skip_submodules or [])
|
|
if skip_set:
|
|
original_count = len(projects_by_name)
|
|
projects_by_name = {
|
|
k: v for k, v in projects_by_name.items() if k not in skip_set
|
|
}
|
|
skipped_count = original_count - len(projects_by_name)
|
|
if skipped_count > 0:
|
|
log(
|
|
f"Skipped {skipped_count} submodule(s) via --skip-submodules: "
|
|
f"{sorted(skip_set)}"
|
|
)
|
|
return list(projects_by_name), list(external_sources_by_path.values())
|
|
|
|
|
|
def fetch_external_git_sources(
|
|
args: argparse.Namespace, external_sources: list[ExternalGitSource]
|
|
) -> None:
|
|
"""Fetch external git sources and check them out at their pinned commits."""
|
|
if not external_sources:
|
|
return
|
|
|
|
reference_dir = resolve_reference_dir(args)
|
|
for source in external_sources:
|
|
_fetch_one_external_git_source(args, source, reference_dir)
|
|
|
|
|
|
def _fetch_one_external_git_source(
|
|
args: argparse.Namespace,
|
|
source: ExternalGitSource,
|
|
reference_dir: Path | None,
|
|
) -> None:
|
|
source_dir = THEROCK_DIR / source.path
|
|
git_dir = source_dir / ".git"
|
|
mirror = (
|
|
_resolve_mirror_path(reference_dir, source.origin) if reference_dir else None
|
|
)
|
|
|
|
if git_dir.exists():
|
|
log(f"Updating external git source {source.name} in {source.path}")
|
|
run_command(
|
|
["git", "remote", "set-url", "origin", source.origin], cwd=source_dir
|
|
)
|
|
fetch_cmd: list[str | Path] = ["git", "fetch"]
|
|
if args.depth:
|
|
fetch_cmd += ["--depth", str(args.depth)]
|
|
if args.progress:
|
|
fetch_cmd += ["--progress"]
|
|
fetch_cmd += ["origin"]
|
|
run_command(fetch_cmd, cwd=source_dir)
|
|
else:
|
|
if source_dir.exists() and any(source_dir.iterdir()):
|
|
raise RuntimeError(
|
|
f"External source path {source_dir} exists but is not a git checkout"
|
|
)
|
|
source_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
clone_cmd: list[str | Path] = ["git", "clone", "--no-checkout"]
|
|
if args.depth:
|
|
clone_cmd += ["--depth", str(args.depth)]
|
|
if args.progress:
|
|
clone_cmd += ["--progress"]
|
|
if mirror:
|
|
clone_cmd += ["--reference", mirror]
|
|
log(f" {source.name}: using reference {mirror}")
|
|
clone_cmd += [source.origin, source_dir]
|
|
try:
|
|
run_command(clone_cmd, cwd=THEROCK_DIR)
|
|
except subprocess.CalledProcessError:
|
|
if not mirror:
|
|
raise
|
|
log(
|
|
f" WARNING: --reference clone failed for {source.name}, "
|
|
f"retrying without reference..."
|
|
)
|
|
clone_cmd = [
|
|
arg for arg in clone_cmd if arg != "--reference" and arg != mirror
|
|
]
|
|
run_command(clone_cmd, cwd=THEROCK_DIR)
|
|
|
|
run_command(["git", "checkout", "--detach", source.commit], cwd=source_dir)
|
|
run_command(["git", "reset", "--hard", source.commit], cwd=source_dir)
|
|
|
|
|
|
def run(args):
|
|
projects, external_sources = get_enabled_sources(args)
|
|
submodule_paths = ALWAYS_SUBMODULE_PATHS + [
|
|
get_submodule_path(project) for project in projects
|
|
]
|
|
# TODO(scotttodd): Check for git lfs?
|
|
update_args = []
|
|
if args.depth:
|
|
update_args += ["--depth", str(args.depth)]
|
|
if args.progress:
|
|
update_args += ["--progress"]
|
|
if args.jobs:
|
|
update_args += ["--jobs", str(args.jobs)]
|
|
if args.remote:
|
|
update_args += ["--remote"]
|
|
if args.update_submodules:
|
|
if submodule_paths:
|
|
reference_dir = resolve_reference_dir(args)
|
|
if reference_dir:
|
|
log(f"Using reference directory: {reference_dir}")
|
|
_update_submodules_with_reference(
|
|
submodule_paths,
|
|
update_args,
|
|
reference_dir,
|
|
jobs=args.jobs if args.jobs is not None else 4,
|
|
)
|
|
else:
|
|
run_command(
|
|
["git", "submodule", "update", "--init"]
|
|
+ update_args
|
|
+ ["--"]
|
|
+ submodule_paths,
|
|
cwd=THEROCK_DIR,
|
|
)
|
|
fetch_external_git_sources(args, external_sources)
|
|
if args.dvc_projects:
|
|
pull_large_files(args.dvc_projects, projects, jobs=args.jobs)
|
|
|
|
# Because we allow local patches, if a submodule is in a patched state,
|
|
# we manually set it to skip-worktree since recording the commit is
|
|
# then meaningless. Here on each fetch, we reset the flag so that if
|
|
# patches are aged out, the tree is restored to normal.
|
|
submodule_paths = [get_submodule_path(name) for name in projects]
|
|
if submodule_paths:
|
|
run_command(
|
|
["git", "update-index", "--no-skip-worktree", "--"] + submodule_paths,
|
|
cwd=THEROCK_DIR,
|
|
)
|
|
|
|
# Remove any stale .smrev files.
|
|
remove_smrev_files(args, projects)
|
|
|
|
if args.apply_patches:
|
|
apply_patches(args, projects)
|
|
|
|
|
|
def pull_large_files(dvc_projects, projects, jobs=None):
|
|
if not dvc_projects:
|
|
print("No DVC projects specified, skipping large file pull.")
|
|
return
|
|
pull_jobs = jobs if jobs is not None else fetch_dvc_artifacts.DEFAULT_JOBS
|
|
for project in dvc_projects:
|
|
# For submodules in TheRock's topology, use get_submodule_path
|
|
if project in projects:
|
|
submodule_path = get_submodule_path(project)
|
|
project_dir = THEROCK_DIR / submodule_path
|
|
# For external repositories (e.g., external-rocm-systems),
|
|
# use the path directly since they're checked out relative to THEROCK_DIR
|
|
else:
|
|
project_dir = THEROCK_DIR / project
|
|
|
|
dvc_config_file = project_dir / ".dvc" / "config"
|
|
if not dvc_config_file.exists():
|
|
log(f"WARNING: dvc config not found in {project_dir}, when expected.")
|
|
continue
|
|
print(f"dvc config detected in {project_dir}, fetching large files")
|
|
result = fetch_dvc_artifacts.pull(project_dir, jobs=pull_jobs)
|
|
print(
|
|
f" done: fetched={result.fetched} "
|
|
f"cached={result.cached} skipped={result.skipped}"
|
|
)
|
|
|
|
|
|
def remove_smrev_files(args, projects):
|
|
for project in projects:
|
|
submodule_path = get_submodule_path(project)
|
|
project_dir = THEROCK_DIR / submodule_path
|
|
project_revision_file = project_dir.with_name(f".{project_dir.name}.smrev")
|
|
if project_revision_file.exists():
|
|
print(f"Remove stale project revision file: {project_revision_file}")
|
|
project_revision_file.unlink()
|
|
|
|
|
|
def apply_patches(args, projects):
|
|
if not args.patch_tag:
|
|
log("Not patching (no --patch-tag specified)")
|
|
return
|
|
patch_version_dir: Path = PATCHES_DIR / args.patch_tag
|
|
if not patch_version_dir.exists():
|
|
log(f"No patch directory {patch_version_dir} exists. Skipping patches.")
|
|
return
|
|
for patch_project_dir in patch_version_dir.iterdir():
|
|
log(f"* Processing project patch directory {patch_project_dir}:")
|
|
# Check that project patch directory was included
|
|
if not patch_project_dir.name in projects:
|
|
log(
|
|
f"* Project patch directory {patch_project_dir.name} was not included. Skipping."
|
|
)
|
|
continue
|
|
submodule_path = get_submodule_path(patch_project_dir.name)
|
|
submodule_url = get_submodule_url(patch_project_dir.name)
|
|
submodule_revision = get_submodule_revision(submodule_path)
|
|
project_dir = THEROCK_DIR / submodule_path
|
|
project_revision_file = project_dir.with_name(f".{project_dir.name}.smrev")
|
|
|
|
if not project_dir.exists():
|
|
log(f"WARNING: Source directory {project_dir} does not exist. Skipping.")
|
|
continue
|
|
patch_files = list(patch_project_dir.glob("*.patch"))
|
|
patch_files.sort()
|
|
log(f"Applying {len(patch_files)} patches")
|
|
run_command(
|
|
[
|
|
"git",
|
|
"-c",
|
|
"user.name=therockbot",
|
|
"-c",
|
|
"user.email=therockbot@amd.com",
|
|
"am",
|
|
"--whitespace=nowarn",
|
|
"--no-gpg-sign",
|
|
]
|
|
+ patch_files,
|
|
cwd=project_dir,
|
|
env={
|
|
"GIT_COMMITTER_DATE": "Thu, 1 Jan 2099 00:00:00 +0000",
|
|
},
|
|
)
|
|
# Since it is in a patched state, make it invisible to changes.
|
|
run_command(
|
|
["git", "update-index", "--skip-worktree", "--", submodule_path],
|
|
cwd=THEROCK_DIR,
|
|
)
|
|
|
|
# Generate the .smrev patch state file.
|
|
# This file consists of two lines: The git origin and a summary of the
|
|
# state of the source tree that was checked out. This can be consumed
|
|
# by individual build steps in lieu of heuristics for asking git. If
|
|
# the tree is in a patched state, the commit hashes of HEAD may be
|
|
# different from checkout-to-checkout, but the .smrev file will have
|
|
# stable contents so long as the submodule pin and contents of the
|
|
# hashes are the same.
|
|
# Note that this does not track the dirty state of the tree. If full
|
|
# fidelity hashes of the tree state are needed for development/dirty
|
|
# trees, then another mechanism must be used.
|
|
patches_hash = hashlib.sha1()
|
|
for patch_file in patch_files:
|
|
patch_contents = Path(patch_file).read_bytes()
|
|
patches_hash.update(patch_contents)
|
|
patches_digest = patches_hash.digest().hex()
|
|
project_revision_file.write_text(
|
|
f"{submodule_url}\n{submodule_revision}+PATCHED:{patches_digest}\n"
|
|
)
|
|
|
|
|
|
# Gets the relative path to a submodule given its name.
|
|
# Raises an exception on failure.
|
|
def get_submodule_path(name: str, cwd=THEROCK_DIR) -> str:
|
|
relpath = (
|
|
subprocess.check_output(
|
|
[
|
|
"git",
|
|
"config",
|
|
"--file",
|
|
".gitmodules",
|
|
"--get",
|
|
f"submodule.{name}.path",
|
|
],
|
|
cwd=cwd,
|
|
)
|
|
.decode()
|
|
.strip()
|
|
)
|
|
return relpath
|
|
|
|
|
|
# Gets the URL for a submodule given its name.
|
|
# Raises an exception on failure.
|
|
def get_submodule_url(name: str) -> str:
|
|
relpath = (
|
|
subprocess.check_output(
|
|
[
|
|
"git",
|
|
"config",
|
|
"--file",
|
|
".gitmodules",
|
|
"--get",
|
|
f"submodule.{name}.url",
|
|
],
|
|
cwd=str(THEROCK_DIR),
|
|
)
|
|
.decode()
|
|
.strip()
|
|
)
|
|
return relpath
|
|
|
|
|
|
def get_submodule_revision(submodule_path: str) -> str:
|
|
# Generates a line like:
|
|
# 160000 5e2093d23f7d34c372a788a6f2b7df8bc1c97947 0 compiler/amd-llvm
|
|
ls_line = (
|
|
subprocess.check_output(
|
|
["git", "ls-files", "--stage", submodule_path], cwd=str(THEROCK_DIR)
|
|
)
|
|
.decode()
|
|
.strip()
|
|
)
|
|
return ls_line.split()[1]
|
|
|
|
|
|
def main(argv):
|
|
parser = argparse.ArgumentParser(
|
|
prog="fetch_sources",
|
|
description="Fetch sources for TheRock build. Use --stage for stage-aware "
|
|
"fetching or --include-* flags for legacy mode.",
|
|
)
|
|
|
|
# Stage-aware fetching (preferred for CI)
|
|
available_stages = get_available_stages()
|
|
parser.add_argument(
|
|
"--stage",
|
|
type=str,
|
|
choices=available_stages if available_stages else None,
|
|
help=f"Build stage to fetch sources for. Uses BUILD_TOPOLOGY.toml. "
|
|
f"Available: {', '.join(available_stages) if available_stages else 'none'}",
|
|
)
|
|
parser.add_argument(
|
|
"--list-stages",
|
|
action="store_true",
|
|
help="List available build stages and their submodules, then exit",
|
|
)
|
|
parser.add_argument(
|
|
"--source-sets",
|
|
nargs="+",
|
|
default=[],
|
|
help=(
|
|
"Additional source sets to fetch. Accepts space-separated names or "
|
|
"comma-separated lists."
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--list-source-sets",
|
|
action="store_true",
|
|
help="List available source sets and their sources, then exit",
|
|
)
|
|
|
|
# Reference repos for faster submodule clones
|
|
parser.add_argument(
|
|
"--reference-dir",
|
|
type=Path,
|
|
default=None,
|
|
help=(
|
|
"Path to a directory of bare git mirrors (created by "
|
|
"setup_git_mirrors.py). Submodule clones will use --reference "
|
|
"to read objects locally instead of over the network. "
|
|
f"Also reads from ${MIRROR_DIR_ENV} environment variable."
|
|
),
|
|
)
|
|
|
|
# Legacy options
|
|
parser.add_argument(
|
|
"--patch-tag",
|
|
type=str,
|
|
default="amd-mainline",
|
|
help="Patch tag to apply to sources after sync",
|
|
)
|
|
parser.add_argument(
|
|
"--update-submodules",
|
|
default=True,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Updates submodules",
|
|
)
|
|
parser.add_argument(
|
|
"--remote",
|
|
default=False,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Updates submodules from remote vs current",
|
|
)
|
|
parser.add_argument(
|
|
"--apply-patches",
|
|
default=True,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Apply patches",
|
|
)
|
|
parser.add_argument(
|
|
"--depth", type=int, help="Git depth when updating submodules", default=None
|
|
)
|
|
parser.add_argument(
|
|
"--progress",
|
|
default=False,
|
|
action="store_true",
|
|
help="Git progress displayed when updating submodules",
|
|
)
|
|
parser.add_argument(
|
|
"--jobs",
|
|
type=int,
|
|
help="Number of jobs to use for updating submodules",
|
|
default=None,
|
|
)
|
|
parser.add_argument(
|
|
"--include-system-projects",
|
|
default=True,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Include systems projects",
|
|
)
|
|
parser.add_argument(
|
|
"--include-compilers",
|
|
default=True,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Include compilers",
|
|
)
|
|
parser.add_argument(
|
|
"--include-debug-tools",
|
|
default=True,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Include ROCm debugging tools",
|
|
)
|
|
parser.add_argument(
|
|
"--include-rocm-libraries",
|
|
default=True,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Include supported rocm-libraries projects",
|
|
)
|
|
parser.add_argument(
|
|
"--include-rocm-systems",
|
|
default=True,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Include supported rocm-systems projects",
|
|
)
|
|
parser.add_argument(
|
|
"--include-ml-frameworks",
|
|
default=True,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Include machine learning frameworks that are part of ROCM",
|
|
)
|
|
parser.add_argument(
|
|
"--include-media-libs",
|
|
default=True,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Include media projects that are part of ROCM",
|
|
)
|
|
parser.add_argument(
|
|
"--include-math-libraries",
|
|
default=True,
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Include math libraries that are part of ROCM",
|
|
)
|
|
parser.add_argument(
|
|
"--skip-submodules",
|
|
nargs="+",
|
|
default=[],
|
|
help=(
|
|
"Submodule names to skip (e.g., 'rocm-libraries rocm-systems'). "
|
|
"These will not be fetched regardless of stage or source set configuration. "
|
|
"Useful for external repo builds where the submodule is checked out separately."
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--system-projects",
|
|
nargs="+",
|
|
type=str,
|
|
default=[
|
|
"half",
|
|
"rocm-cmake",
|
|
],
|
|
)
|
|
parser.add_argument(
|
|
"--compiler-projects",
|
|
nargs="+",
|
|
type=str,
|
|
default=[
|
|
"HIPIFY",
|
|
"llvm-project",
|
|
"spirv-llvm-translator",
|
|
],
|
|
)
|
|
parser.add_argument(
|
|
"--ml-framework-projects",
|
|
nargs="+",
|
|
type=str,
|
|
default=[],
|
|
)
|
|
parser.add_argument(
|
|
"--media-libs-projects",
|
|
nargs="+",
|
|
type=str,
|
|
default=(
|
|
[]
|
|
if is_windows()
|
|
else [
|
|
# Linux only projects.
|
|
"amd-mesa",
|
|
]
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
# projects that use DVC to manage large files
|
|
"--dvc-projects",
|
|
nargs="+",
|
|
type=str,
|
|
default=(
|
|
[
|
|
"rocm-libraries",
|
|
"rocm-systems",
|
|
]
|
|
if is_windows()
|
|
else [
|
|
"rocm-libraries",
|
|
]
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--debug-tools",
|
|
nargs="+",
|
|
type=str,
|
|
default=(
|
|
[]
|
|
if is_windows()
|
|
else [
|
|
# Linux only projects.
|
|
"rocgdb",
|
|
]
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--math-library-projects",
|
|
nargs="+",
|
|
type=str,
|
|
default=[
|
|
"libhipcxx",
|
|
],
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
# Handle --list-stages
|
|
if args.list_stages:
|
|
if not TOPOLOGY_PATH.exists():
|
|
print(f"BUILD_TOPOLOGY.toml not found at {TOPOLOGY_PATH}")
|
|
sys.exit(1)
|
|
|
|
topology = BuildTopology(str(TOPOLOGY_PATH))
|
|
print("Available build stages and their submodules:\n")
|
|
for stage in topology.get_build_stages():
|
|
submodules = topology.get_submodules_for_stage(stage.name)
|
|
submodule_names = [s.name for s in submodules]
|
|
print(f" {stage.name} ({stage.type}):")
|
|
print(f" {stage.description}")
|
|
print(
|
|
f" Submodules: {', '.join(submodule_names) if submodule_names else '(none)'}"
|
|
)
|
|
print()
|
|
sys.exit(0)
|
|
|
|
# Handle --list-source-sets
|
|
if args.list_source_sets:
|
|
if not TOPOLOGY_PATH.exists():
|
|
print(f"BUILD_TOPOLOGY.toml not found at {TOPOLOGY_PATH}")
|
|
sys.exit(1)
|
|
|
|
topology = BuildTopology(str(TOPOLOGY_PATH))
|
|
print("Available source sets:\n")
|
|
for source_set in topology.get_source_sets():
|
|
submodule_names = [s.name for s in source_set.submodules]
|
|
external_sources = [
|
|
f"{s.name} ({s.origin} @ {s.commit} -> {s.path})"
|
|
for s in source_set.external_git_sources
|
|
]
|
|
print(f" {source_set.name}:")
|
|
print(f" {source_set.description}")
|
|
print(
|
|
f" Submodules: {', '.join(submodule_names) if submodule_names else '(none)'}"
|
|
)
|
|
print(
|
|
" External git sources: "
|
|
f"{', '.join(external_sources) if external_sources else '(none)'}"
|
|
)
|
|
if source_set.disable_platforms:
|
|
print(
|
|
f" Disabled platforms: {', '.join(source_set.disable_platforms)}"
|
|
)
|
|
print()
|
|
sys.exit(0)
|
|
|
|
run(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1:])
|