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
755 lines
23 KiB
Python
755 lines
23 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""TheRock developer bootstrap and build tool.
|
|
|
|
Downloads prebuilt CI artifacts and lets you selectively build
|
|
ml-libs components (hipdnn, hipkernelprovider, etc.) from source
|
|
while everything else uses prebuilt binaries.
|
|
|
|
Cross-platform replacement for rock_dev_bootstrap.sh.
|
|
|
|
Usage:
|
|
python rock_dev_bootstrap.py <command> [options] [components...]
|
|
|
|
Commands:
|
|
bootstrap [run-id] Download CI artifacts (auto-detects latest if omitted)
|
|
configure [components...] Remove .prebuilt markers + run cmake
|
|
build [components...] Build components with ninja
|
|
rebuild [components...] Expunge + rebuild components
|
|
|
|
Examples:
|
|
# One-time setup (auto-detect latest nightly):
|
|
python rock_dev_bootstrap.py bootstrap --therock-dir /path/to/TheRock
|
|
|
|
# One-time setup with specific run:
|
|
python rock_dev_bootstrap.py bootstrap --therock-dir /path/to/TheRock 22884930750
|
|
|
|
# Configure to build hipdnn + miopenprovider from source:
|
|
python rock_dev_bootstrap.py configure hipdnn miopenprovider
|
|
|
|
# Build everything that was configured:
|
|
python rock_dev_bootstrap.py build
|
|
|
|
# Clean rebuild of a component:
|
|
python rock_dev_bootstrap.py rebuild hipkernelprovider
|
|
|
|
Available components:
|
|
hipdnn, hipkernelprovider, miopenprovider, hipblasltprovider
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Constants
|
|
# ---------------------------------------------------------------------------
|
|
|
|
ALL_COMPONENTS = [
|
|
"hipdnn",
|
|
"hipkernelprovider",
|
|
"miopenprovider",
|
|
"hipblasltprovider",
|
|
"miopen",
|
|
]
|
|
|
|
NINJA_TARGETS = {
|
|
"hipdnn": "hipDNN",
|
|
"hipkernelprovider": "hipkernelprovider",
|
|
"miopenprovider": "miopenprovider",
|
|
"hipblasltprovider": "hipblasltprovider",
|
|
"miopen": "MIOpen",
|
|
}
|
|
|
|
# Platform-aware defaults
|
|
if sys.platform == "win32":
|
|
_DEFAULT_GPU = "gfx1151"
|
|
_DEFAULT_PLATFORM = "windows"
|
|
else:
|
|
_DEFAULT_GPU = "gfx94X-dcgpu"
|
|
_DEFAULT_PLATFORM = "linux"
|
|
|
|
# ci_nightly.yml is the top-level workflow that triggers both Linux and Windows
|
|
# builds. Reusable workflows (ci_windows.yml, ci_linux.yml) don't have their
|
|
# own runs in the GitHub API, so we must search the nightly workflow.
|
|
_DEFAULT_WORKFLOW = "ci_nightly.yml"
|
|
|
|
CONFIG_FILENAME = ".dev_bootstrap_config.json"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def component_to_flag(name: str) -> str:
|
|
"""Convert component name to cmake enable flag (e.g. hipdnn -> HIPDNN)."""
|
|
return name.upper().replace("-", "_")
|
|
|
|
|
|
def component_to_ninja_target(name: str) -> str:
|
|
"""Convert component name to ninja target name."""
|
|
return NINJA_TARGETS.get(name, name)
|
|
|
|
|
|
def validate_component(name: str) -> None:
|
|
"""Exit with error if component name is not recognized."""
|
|
if name not in ALL_COMPONENTS:
|
|
print(f"ERROR: Unknown component '{name}'")
|
|
print(f"Available components: {', '.join(ALL_COMPONENTS)}")
|
|
sys.exit(1)
|
|
|
|
|
|
def gpu_short(gpu_family: str) -> str:
|
|
"""Extract short GPU name (strip everything after first hyphen)."""
|
|
return gpu_family.split("-")[0]
|
|
|
|
|
|
def config_path(build_dir: Path) -> Path:
|
|
"""Path to the saved config file in the build directory."""
|
|
return build_dir / CONFIG_FILENAME
|
|
|
|
|
|
def save_config(
|
|
build_dir: Path, gpu: str, components: list[str], therock_dir: Path = None
|
|
) -> None:
|
|
"""Save current settings to the build directory for later commands."""
|
|
build_dir.mkdir(parents=True, exist_ok=True)
|
|
data = {"gpu": gpu, "components": components}
|
|
if therock_dir is not None:
|
|
data["therock_dir"] = str(therock_dir)
|
|
config_path(build_dir).write_text(json.dumps(data, indent=2))
|
|
|
|
|
|
def load_config(build_dir: Path) -> dict:
|
|
"""Load saved config from the build directory."""
|
|
cfg = config_path(build_dir)
|
|
if cfg.exists():
|
|
return json.loads(cfg.read_text())
|
|
return {}
|
|
|
|
|
|
def venv_python(therock_dir: Path) -> Path:
|
|
"""Return path to the venv's Python interpreter."""
|
|
venv_dir = therock_dir / ".venv"
|
|
if sys.platform == "win32":
|
|
return venv_dir / "Scripts" / "python.exe"
|
|
return venv_dir / "bin" / "python"
|
|
|
|
|
|
def run_cmd(
|
|
cmd: list, cwd: Path = None, check: bool = True, env: dict = None
|
|
) -> subprocess.CompletedProcess:
|
|
"""Run a command, printing it first. Exits on failure if check=True."""
|
|
print(f" $ {' '.join(str(c) for c in cmd)}")
|
|
result = subprocess.run(cmd, cwd=cwd, env=env)
|
|
if check and result.returncode != 0:
|
|
print(f"ERROR: Command failed with exit code {result.returncode}")
|
|
sys.exit(result.returncode)
|
|
return result
|
|
|
|
|
|
def run_cmd_capture(cmd: list, cwd: Path = None) -> subprocess.CompletedProcess:
|
|
"""Run a command and capture stdout. Exits on failure."""
|
|
print(f" $ {' '.join(str(c) for c in cmd)}")
|
|
result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
print(result.stdout)
|
|
print(result.stderr)
|
|
print(f"ERROR: Command failed with exit code {result.returncode}")
|
|
sys.exit(result.returncode)
|
|
return result
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# MSVC environment
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_VCVARS_SEARCH_ROOTS = [
|
|
Path("D:/develop"),
|
|
Path("C:/Program Files/Microsoft Visual Studio"),
|
|
Path("C:/Program Files (x86)/Microsoft Visual Studio"),
|
|
]
|
|
|
|
|
|
def find_vcvars64() -> Path:
|
|
"""Locate vcvars64.bat by searching known root directories."""
|
|
for root in _VCVARS_SEARCH_ROOTS:
|
|
if not root.is_dir():
|
|
continue
|
|
for bat in root.rglob("vcvars64.bat"):
|
|
return bat
|
|
return None
|
|
|
|
|
|
def get_msvc_env() -> dict:
|
|
"""Capture MSVC environment by running vcvars64.bat and reading env vars.
|
|
|
|
Returns an env dict with MSVC variables merged into the current environment,
|
|
or None on non-Windows or if vcvars64.bat cannot be found.
|
|
"""
|
|
if sys.platform != "win32":
|
|
return None
|
|
|
|
# If cl.exe is already available, no need to activate
|
|
if shutil.which("cl"):
|
|
return None
|
|
|
|
vcvars = find_vcvars64()
|
|
if vcvars is None:
|
|
print("WARNING: Cannot find vcvars64.bat — MSVC may not be available.")
|
|
print(" Run this script from a 'x64 Native Tools Command Prompt for")
|
|
print(" VS 2022', or install Visual Studio Build Tools.")
|
|
return None
|
|
|
|
print(f"Activating MSVC environment via {vcvars}...")
|
|
# Run vcvars64.bat in a cmd shell, then dump the resulting environment.
|
|
# Use a .bat wrapper written to a temp file to avoid quoting issues.
|
|
import tempfile
|
|
|
|
bat_content = f'@call "{vcvars}" >nul 2>&1\n@if errorlevel 1 exit /b 1\n@set\n'
|
|
bat_file = Path(tempfile.gettempdir()) / "_rock_dev_vcvars.bat"
|
|
bat_file.write_text(bat_content)
|
|
try:
|
|
result = subprocess.run(
|
|
["cmd.exe", "/c", str(bat_file)], capture_output=True, text=True
|
|
)
|
|
finally:
|
|
bat_file.unlink(missing_ok=True)
|
|
|
|
if result.returncode != 0:
|
|
print(f"WARNING: vcvars64.bat failed (exit {result.returncode})")
|
|
if result.stderr:
|
|
print(f" {result.stderr.strip()}")
|
|
return None
|
|
|
|
env = {}
|
|
for line in result.stdout.splitlines():
|
|
if "=" in line:
|
|
key, _, value = line.partition("=")
|
|
env[key] = value
|
|
|
|
# Ensure "." is on PATH so cmd.exe can find batch files in the current
|
|
# working directory. This is default cmd.exe behavior but breaks when
|
|
# processes are spawned from Git Bash.
|
|
path = env.get("Path", env.get("PATH", ""))
|
|
if not path.startswith(".;"):
|
|
env["Path"] = ".;" + path
|
|
|
|
return env
|
|
|
|
|
|
def get_build_env(therock_dir: Path) -> dict:
|
|
"""Build an environment dict for cmake/ninja with MSVC and venv Python.
|
|
|
|
Ensures the TheRock venv's Python (which has pyyaml and other deps from
|
|
requirements.txt) is on PATH so cmake's find_package(Python3) finds it
|
|
instead of the system Python.
|
|
"""
|
|
env = get_msvc_env()
|
|
if env is None:
|
|
env = dict(os.environ)
|
|
|
|
venv_dir = therock_dir / ".venv"
|
|
if sys.platform == "win32":
|
|
venv_bin = str(venv_dir / "Scripts")
|
|
else:
|
|
venv_bin = str(venv_dir / "bin")
|
|
|
|
path = env.get("Path", env.get("PATH", ""))
|
|
if venv_bin not in path:
|
|
env["Path"] = venv_bin + ";" + path
|
|
|
|
return env
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Python venv setup
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def setup_python(therock_dir: Path) -> Path:
|
|
"""Ensure a Python venv exists in TheRock dir with deps installed.
|
|
|
|
Returns the path to the venv's Python interpreter.
|
|
"""
|
|
venv_dir = therock_dir / ".venv"
|
|
python = venv_python(therock_dir)
|
|
requirements = therock_dir / "requirements.txt"
|
|
|
|
if not venv_dir.exists():
|
|
print("Creating Python virtual environment...")
|
|
run_cmd([sys.executable, "-m", "venv", str(venv_dir)])
|
|
|
|
print("Installing dependencies...")
|
|
run_cmd([str(python), "-m", "pip", "install", "-q", "--upgrade", "pip"])
|
|
if requirements.exists():
|
|
run_cmd([str(python), "-m", "pip", "install", "-q", "-r", str(requirements)])
|
|
|
|
return python
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Prebuilt marker removal
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def remove_prebuilt_markers(build_dir: Path, components: list[str]) -> None:
|
|
"""Remove .prebuilt marker files for the given components."""
|
|
print()
|
|
print("Removing .prebuilt markers for source-build components...")
|
|
print()
|
|
|
|
for comp in components:
|
|
found = False
|
|
for prebuilt in build_dir.rglob("stage.prebuilt"):
|
|
if comp.lower() in str(prebuilt).lower():
|
|
found = True
|
|
print(f" {comp}: removing .prebuilt marker ({prebuilt})")
|
|
prebuilt.unlink()
|
|
stage_dir = prebuilt.with_suffix("")
|
|
if stage_dir.is_dir():
|
|
shutil.rmtree(stage_dir)
|
|
if not found:
|
|
print(f" {comp}: no .prebuilt marker (will build from source)")
|
|
print()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Commands
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def do_bootstrap(args: argparse.Namespace) -> None:
|
|
"""Download CI artifacts and set up prebuilt markers."""
|
|
therock_dir = args.therock_dir
|
|
build_dir = args.build_dir
|
|
gpu = args.gpu
|
|
workflow = args.workflow
|
|
platform = args.platform
|
|
run_id = args.run_id
|
|
|
|
# Validate TheRock dir
|
|
artifact_manager = therock_dir / "build_tools" / "artifact_manager.py"
|
|
if not artifact_manager.exists():
|
|
print(f"ERROR: Cannot find {artifact_manager}")
|
|
print("Make sure --therock-dir points to a TheRock checkout.")
|
|
sys.exit(1)
|
|
|
|
python = setup_python(therock_dir)
|
|
|
|
# Always fetch sources to ensure they're up to date
|
|
print("Fetching submodule sources...")
|
|
fetch_cmd = [str(python), str(therock_dir / "build_tools" / "fetch_sources.py")]
|
|
if shutil.which("dvc") is None:
|
|
print()
|
|
print(" WARNING: 'dvc' not found on PATH — skipping large file pulls.")
|
|
print(" Some components may fail to build if they require DVC-managed files.")
|
|
print(" Install DVC from https://dvc.org/doc/install if needed.")
|
|
print()
|
|
fetch_cmd.extend(["--dvc-projects", "_skip"])
|
|
run_cmd(fetch_cmd, cwd=therock_dir)
|
|
print()
|
|
|
|
# Find latest run ID if not specified
|
|
if not run_id:
|
|
print("==========================================")
|
|
print("Finding latest CI run with artifacts")
|
|
print("==========================================")
|
|
print()
|
|
print(f"Searching {workflow} for {gpu} artifacts...")
|
|
print()
|
|
|
|
result = run_cmd_capture(
|
|
[
|
|
str(python),
|
|
str(therock_dir / "build_tools" / "find_latest_artifacts.py"),
|
|
"--artifact-group",
|
|
gpu,
|
|
"--workflow",
|
|
workflow,
|
|
"--platform",
|
|
platform,
|
|
"--verbose",
|
|
],
|
|
cwd=therock_dir,
|
|
)
|
|
|
|
output = result.stdout + result.stderr
|
|
print(output)
|
|
|
|
match = re.search(r"Workflow run ID:\s+(\d+)", output)
|
|
if not match:
|
|
print()
|
|
print(f"ERROR: Could not find a recent CI run with artifacts for {gpu}")
|
|
print()
|
|
print("Try specifying a run ID manually:")
|
|
print(f" python {sys.argv[0]} bootstrap <run-id>")
|
|
sys.exit(1)
|
|
|
|
run_id = match.group(1)
|
|
print(f"Using run ID: {run_id}")
|
|
|
|
print()
|
|
print("==========================================")
|
|
print(f"Fetching all artifacts from run {run_id}")
|
|
print("==========================================")
|
|
print()
|
|
print(f"GPU Family: {gpu}")
|
|
print(f"Build Dir: {build_dir}")
|
|
print()
|
|
|
|
run_cmd(
|
|
[
|
|
str(python),
|
|
str(therock_dir / "build_tools" / "artifact_manager.py"),
|
|
"fetch",
|
|
"--stage",
|
|
"all",
|
|
"--run-id",
|
|
run_id,
|
|
"--amdgpu-families",
|
|
gpu,
|
|
"--output-dir",
|
|
str(build_dir),
|
|
"--bootstrap",
|
|
"--platform",
|
|
platform,
|
|
],
|
|
cwd=therock_dir,
|
|
)
|
|
|
|
prebuilt_count = len(list(build_dir.rglob("*.prebuilt")))
|
|
print()
|
|
print("==========================================")
|
|
print("Bootstrap complete!")
|
|
print("==========================================")
|
|
print()
|
|
print(f" {prebuilt_count} components marked as prebuilt")
|
|
print()
|
|
print(f"Next: python {sys.argv[0]} configure [components...]")
|
|
print()
|
|
|
|
|
|
def do_configure(args: argparse.Namespace) -> None:
|
|
"""Remove .prebuilt markers and run cmake configure."""
|
|
therock_dir = args.therock_dir
|
|
build_dir = args.build_dir
|
|
gpu = args.gpu
|
|
components = args.components if args.components else list(ALL_COMPONENTS)
|
|
|
|
python = setup_python(therock_dir)
|
|
build_env = get_build_env(therock_dir)
|
|
|
|
print("==========================================")
|
|
print("Configuring TheRock build")
|
|
print("==========================================")
|
|
print()
|
|
print(f"GPU Family: {gpu}")
|
|
print(f"Build Dir: {build_dir}")
|
|
print()
|
|
print("Building from source:")
|
|
for comp in components:
|
|
print(f" - {comp}")
|
|
|
|
# Remove .prebuilt markers
|
|
remove_prebuilt_markers(build_dir, components)
|
|
|
|
# Save config
|
|
save_config(build_dir, gpu, components, therock_dir)
|
|
|
|
# Build cmake args
|
|
cmake_args = [
|
|
"cmake",
|
|
"-B",
|
|
str(build_dir),
|
|
"-GNinja",
|
|
"-DTHEROCK_ENABLE_ALL=OFF",
|
|
f"-DTHEROCK_AMDGPU_FAMILIES={gpu}",
|
|
"-DCMAKE_BUILD_TYPE=RelWithDebInfo",
|
|
]
|
|
for comp in components:
|
|
flag = component_to_flag(comp)
|
|
cmake_args.append(f"-DTHEROCK_ENABLE_{flag}=ON")
|
|
|
|
print("Running cmake...")
|
|
run_cmd(cmake_args, cwd=therock_dir, env=build_env)
|
|
|
|
print()
|
|
print("==========================================")
|
|
print("Configure complete!")
|
|
print("==========================================")
|
|
print()
|
|
print(f"Next: python {sys.argv[0]} build [components...]")
|
|
print()
|
|
|
|
|
|
def do_build(args: argparse.Namespace) -> None:
|
|
"""Build configured components with ninja."""
|
|
build_dir = args.build_dir
|
|
components = args.components
|
|
|
|
# Load saved config if no components specified
|
|
cfg = load_config(build_dir)
|
|
if not components:
|
|
components = cfg.get("components", [])
|
|
|
|
therock_dir = args.therock_dir or (
|
|
Path(cfg["therock_dir"]) if "therock_dir" in cfg else None
|
|
)
|
|
build_env = get_build_env(therock_dir) if therock_dir else get_msvc_env()
|
|
|
|
if not components:
|
|
print("ERROR: No components specified and no saved config found.")
|
|
print(f"Run 'python {sys.argv[0]} configure [components...]' first.")
|
|
sys.exit(1)
|
|
|
|
targets = [component_to_ninja_target(c) for c in components]
|
|
|
|
print("==========================================")
|
|
print("Building components")
|
|
print("==========================================")
|
|
print()
|
|
print(f"Build Dir: {build_dir}")
|
|
print(f"Targets: {' '.join(targets)}")
|
|
print()
|
|
|
|
ninja_cmd = ["ninja", "-C", str(build_dir)]
|
|
if args.jobs:
|
|
ninja_cmd.extend(["-j", str(args.jobs)])
|
|
run_cmd(ninja_cmd + targets, env=build_env)
|
|
|
|
print()
|
|
print("Build complete!")
|
|
print()
|
|
|
|
|
|
def do_rebuild(args: argparse.Namespace) -> None:
|
|
"""Expunge and rebuild components."""
|
|
build_dir = args.build_dir
|
|
components = args.components
|
|
|
|
# Load saved config if no components specified
|
|
cfg = load_config(build_dir)
|
|
if not components:
|
|
components = cfg.get("components", [])
|
|
|
|
therock_dir = args.therock_dir or (
|
|
Path(cfg["therock_dir"]) if "therock_dir" in cfg else None
|
|
)
|
|
build_env = get_build_env(therock_dir) if therock_dir else get_msvc_env()
|
|
|
|
if not components:
|
|
print("ERROR: No components specified and no saved config found.")
|
|
print(f"Run 'python {sys.argv[0]} configure [components...]' first.")
|
|
sys.exit(1)
|
|
|
|
targets = [component_to_ninja_target(c) for c in components]
|
|
expunge_targets = [f"{t}+expunge" for t in targets]
|
|
|
|
print("==========================================")
|
|
print("Rebuilding components (expunge + build)")
|
|
print("==========================================")
|
|
print()
|
|
print(f"Build Dir: {build_dir}")
|
|
print(f"Targets: {' '.join(targets)}")
|
|
print()
|
|
|
|
ninja_cmd = ["ninja", "-C", str(build_dir)]
|
|
if args.jobs:
|
|
ninja_cmd.extend(["-j", str(args.jobs)])
|
|
|
|
print(f"Expunging: {' '.join(expunge_targets)}")
|
|
run_cmd(ninja_cmd + expunge_targets, env=build_env)
|
|
|
|
print()
|
|
print(f"Building: {' '.join(targets)}")
|
|
run_cmd(ninja_cmd + targets, env=build_env)
|
|
|
|
print()
|
|
print("Rebuild complete!")
|
|
print()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Argument parsing
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def parse_args(argv: list[str] = None) -> argparse.Namespace:
|
|
"""Parse command line arguments."""
|
|
# Shared parent parser so global options work before or after the command
|
|
global_opts = argparse.ArgumentParser(add_help=False)
|
|
global_opts.add_argument(
|
|
"--gpu",
|
|
default=_DEFAULT_GPU,
|
|
help=f"GPU family (default: {_DEFAULT_GPU})",
|
|
)
|
|
global_opts.add_argument(
|
|
"--build-dir",
|
|
type=Path,
|
|
default=None,
|
|
help="Build directory (default: <cwd>/build/therock-<gpu-short>)",
|
|
)
|
|
global_opts.add_argument(
|
|
"--therock-dir",
|
|
type=Path,
|
|
default=None,
|
|
help="Path to TheRock checkout (required for bootstrap/configure)",
|
|
)
|
|
global_opts.add_argument(
|
|
"--workflow",
|
|
default=_DEFAULT_WORKFLOW,
|
|
help=f"CI workflow file for artifact lookup (default: {_DEFAULT_WORKFLOW})",
|
|
)
|
|
global_opts.add_argument(
|
|
"--platform",
|
|
default=_DEFAULT_PLATFORM,
|
|
help=f"Target platform (default: {_DEFAULT_PLATFORM})",
|
|
)
|
|
global_opts.add_argument(
|
|
"-j",
|
|
"--jobs",
|
|
type=int,
|
|
default=None,
|
|
help="Number of parallel build jobs (passed to ninja -j)",
|
|
)
|
|
|
|
parser = argparse.ArgumentParser(
|
|
prog="rock_dev_bootstrap.py",
|
|
description="TheRock developer bootstrap and build tool.",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
parents=[global_opts],
|
|
epilog="""
|
|
Available components:
|
|
hipdnn, hipkernelprovider, miopenprovider, hipblasltprovider
|
|
|
|
Per-component ninja targets (for manual use):
|
|
component+configure - Run cmake configure
|
|
component+build - Incremental build
|
|
component+stage - Install to stage dir
|
|
component+expunge - Full clean (build/, stage/, stamp/, dist/)
|
|
""",
|
|
)
|
|
|
|
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
|
|
# bootstrap
|
|
bp = subparsers.add_parser(
|
|
"bootstrap",
|
|
parents=[global_opts],
|
|
help="Download CI artifacts (auto-detects latest if run-id omitted)",
|
|
)
|
|
bp.add_argument(
|
|
"run_id",
|
|
nargs="?",
|
|
default=None,
|
|
help="GitHub Actions run ID (auto-detected if omitted)",
|
|
)
|
|
|
|
# configure
|
|
cp = subparsers.add_parser(
|
|
"configure",
|
|
parents=[global_opts],
|
|
help="Remove .prebuilt markers + run cmake",
|
|
)
|
|
cp.add_argument(
|
|
"components",
|
|
nargs="*",
|
|
default=[],
|
|
help="Components to build from source (default: all)",
|
|
)
|
|
|
|
# build
|
|
bp2 = subparsers.add_parser(
|
|
"build",
|
|
parents=[global_opts],
|
|
help="Build components with ninja",
|
|
)
|
|
bp2.add_argument(
|
|
"components",
|
|
nargs="*",
|
|
default=[],
|
|
help="Components to build (default: from saved config)",
|
|
)
|
|
|
|
# rebuild
|
|
rp = subparsers.add_parser(
|
|
"rebuild",
|
|
parents=[global_opts],
|
|
help="Expunge + rebuild components",
|
|
)
|
|
rp.add_argument(
|
|
"components",
|
|
nargs="*",
|
|
default=[],
|
|
help="Components to rebuild (default: from saved config)",
|
|
)
|
|
|
|
args = parser.parse_args(argv)
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
sys.exit(0)
|
|
|
|
# Derive build dir from GPU if not set.
|
|
# Default: <cwd>/build/therock-<gpu> — builds land relative to where
|
|
# the script is invoked. Use --build-dir to override.
|
|
if args.build_dir is None:
|
|
args.build_dir = Path.cwd() / "build" / f"therock-{gpu_short(args.gpu)}"
|
|
|
|
# Validate components
|
|
components = getattr(args, "components", None)
|
|
if components:
|
|
for comp in components:
|
|
validate_component(comp)
|
|
|
|
# Require therock-dir for commands that need it
|
|
if args.command in ("bootstrap", "configure"):
|
|
if args.therock_dir is None:
|
|
print(
|
|
"ERROR: --therock-dir is required for the '{}' command.".format(
|
|
args.command
|
|
)
|
|
)
|
|
sys.exit(1)
|
|
args.therock_dir = args.therock_dir.resolve()
|
|
if not args.therock_dir.is_dir():
|
|
print(f"ERROR: TheRock directory does not exist: {args.therock_dir}")
|
|
sys.exit(1)
|
|
|
|
return args
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
|
|
dispatch = {
|
|
"bootstrap": do_bootstrap,
|
|
"configure": do_configure,
|
|
"build": do_build,
|
|
"rebuild": do_rebuild,
|
|
}
|
|
|
|
handler = dispatch.get(args.command)
|
|
if handler:
|
|
handler(args)
|
|
else:
|
|
print(f"ERROR: Unknown command '{args.command}'")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|