Files
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

292 lines
9.5 KiB
Python

#!/usr/bin/env python3
# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Usage:
post_build_upload.py [-h]
--artifact-group ARTIFACT_GROUP
[--build-dir BUILD_DIR]
[--upload | --no-upload] (default enabled if the `CI` env var is set)
[--run-id RUN_ID]
[--output-dir OUTPUT_DIR]
[--dry-run]
This script runs after building TheRock, where this script does:
1. Create log archives
2. (optional) upload artifacts
3. (optional) upload logs
4. (optional) add links to GitHub job summary
In the case that a CI build fails, this step will always upload available logs and artifacts.
For AWS credentials to upload, reach out to the #rocm-ci channel in the AMD Developer Community Discord
"""
import argparse
from datetime import datetime
import os
from pathlib import Path
import platform
import sys
import tarfile
from github_actions_api import gha_append_step_summary, str2bool
THEROCK_DIR = Path(__file__).resolve().parent.parent.parent
PLATFORM = platform.system().lower()
# Add build_tools to path for _therock_utils imports.
sys.path.insert(0, str(THEROCK_DIR / "build_tools"))
from _therock_utils.workflow_outputs import WorkflowOutputRoot
from _therock_utils.storage_backend import StorageBackend, create_storage_backend
def log(*args):
print(*args)
sys.stdout.flush()
def create_ninja_log_archive(build_dir: Path):
log_dir = build_dir / "logs"
# Python equivalent of `find ~/TheRock/build -iname .ninja_log``
found_files = []
log(f"[*] Create ninja log archive from: {build_dir}")
glob_pattern_ninja = f"**/.ninja_log"
log(f"[*] Path glob: {glob_pattern_ninja}")
found_files = list(build_dir.glob(glob_pattern_ninja))
if len(found_files) == 0:
print("No ninja log files found to archive... Skipping", file=sys.stderr)
return
files_to_archive = found_files
archive_name = log_dir / "ninja_logs.tar.gz"
if archive_name.exists():
print(f"NOTE: Archive exists: {archive_name}", file=sys.stderr)
added_count = 0
with tarfile.open(archive_name, "w:gz") as tar:
log(f"[+] Create archive: {archive_name}")
for file_path in files_to_archive:
tar.add(file_path)
added_count += 1
log(f"[+] Add: {file_path}")
log(f"[*] Files Added: {added_count}")
def _get_pyzstd():
"""Lazy import pyzstd with helpful error message."""
try:
import pyzstd
return pyzstd
except ModuleNotFoundError:
raise ModuleNotFoundError(
"pyzstd is required for zstd compression. "
"Install it with: pip install pyzstd"
)
def create_ccache_log_archive(build_dir: Path):
"""Archive the ccache log subdirectory into a zstd-compressed tarball.
ccache.log can be hundreds of MB (verbose per-invocation trace) but
compresses ~13x with zstd. The raw logs in logs/ccache/ are excluded from
upload (see upload_logs); this archive provides the compressed version.
"""
ccache_dir = build_dir / "logs" / "ccache"
if not ccache_dir.is_dir():
return
found_files = sorted(f for f in ccache_dir.iterdir() if f.is_file())
if not found_files:
return
pyzstd = _get_pyzstd()
archive_path = build_dir / "logs" / "ccache_logs.tar.zst"
with pyzstd.ZstdFile(archive_path, mode="wb") as zst:
with tarfile.open(mode="w|", fileobj=zst) as tar:
for file_path in found_files:
tar.add(file_path, arcname=file_path.name)
log(f"[+] Archived ccache log: {file_path.name}")
archive_size = archive_path.stat().st_size
log(
f"[INFO] Created {archive_path.name} "
f"({archive_size // 1024 // 1024}MB from {len(found_files)} files)"
)
def upload_logs(
artifact_group: str,
build_dir: Path,
output_root: WorkflowOutputRoot,
backend: StorageBackend,
):
"""Upload build logs, resource profiling summaries, and observability reports."""
log_dir = build_dir / "logs"
if not log_dir.is_dir():
log(f"[INFO] Log directory {log_dir} not found. Skipping upload.")
return
# Upload all log files recursively. This covers:
# - build.log, configure.log, etc.
# - ninja_logs.tar.gz
# - build_observability.html (when generated)
# - therock-build-prof/ subdirectory (resource profiling)
# index.html is generated server-side by build_tools/generate_s3_index.py after upload.
# Content-type is inferred per file by the backend.
# Exclude raw ccache logs — they're uploaded compressed as ccache_logs.tar.zst.
log("Uploading logs")
backend.upload_directory(
log_dir, output_root.log_dir(artifact_group), exclude=["ccache/**/*"]
)
# Resource profiling summaries (generated by resource_info.py --finalize)
# live in a subdirectory but are also expected at the log root for direct
# linking. Upload a flattened copy to preserve current S3 layout.
resource_prof_dir = log_dir / "therock-build-prof"
if resource_prof_dir.is_dir():
for filename in ["comp-summary.html", "comp-summary.md"]:
file_path = resource_prof_dir / filename
if file_path.is_file():
backend.upload_file(
file_path, output_root.log_file(artifact_group, filename)
)
log(f"[INFO] Uploaded {file_path} (flattened)")
def upload_manifest(
artifact_group: str,
build_dir: Path,
output_root: WorkflowOutputRoot,
backend: StorageBackend,
):
"""Upload therock_manifest.json."""
manifest_path = (
build_dir / "base" / "aux-overlay" / "build" / "therock_manifest.json"
)
if not manifest_path.is_file():
raise FileNotFoundError(f"therock_manifest.json not found at {manifest_path}")
log(f"[INFO] Uploading manifest {manifest_path}")
backend.upload_file(manifest_path, output_root.manifest(artifact_group))
def write_gha_build_summary(
artifact_group: str,
build_dir: Path,
output_root: WorkflowOutputRoot,
job_status: str,
):
log(f"Adding links to job summary for {output_root.prefix}")
log_index_url = output_root.log_index(artifact_group).https_url
gha_append_step_summary(f"[Build Logs]({log_index_url})")
observability_path = build_dir / "logs" / "build_observability.html"
if observability_path.is_file():
analysis_url = output_root.build_observability(artifact_group).https_url
gha_append_step_summary(f"[Build Observability]({analysis_url})")
else:
log("[INFO] Build Observability: Not generated")
# Only add artifact links if the job not failed
if not job_status or job_status == "success":
artifact_url = output_root.artifact_index().https_url
gha_append_step_summary(f"[Artifacts]({artifact_url})")
manifest_url = output_root.manifest(artifact_group).https_url
gha_append_step_summary(f"[TheRock Manifest]({manifest_url})")
def run(args):
log("Creating Ninja log archive")
log("--------------------------")
create_ninja_log_archive(args.build_dir)
log("Creating ccache log archive")
log("---------------------------")
create_ccache_log_archive(args.build_dir)
if not args.upload:
return
output_root = WorkflowOutputRoot.from_workflow_run(
run_id=args.run_id, platform=PLATFORM
)
backend = create_storage_backend(staging_dir=args.output_dir, dry_run=args.dry_run)
log("Upload log")
log("----------")
upload_logs(args.artifact_group, args.build_dir, output_root, backend)
log("Upload manifest")
log("----------------")
upload_manifest(args.artifact_group, args.build_dir, output_root, backend)
log("Write github actions build summary")
log("--------------------")
write_gha_build_summary(
args.artifact_group, args.build_dir, output_root, args.job_status
)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Post Build Upload steps")
parser.add_argument(
"--artifact-group",
type=str,
default=os.getenv("ARTIFACT_GROUP"),
required=True,
help="Artifact group to upload (default: $ARTIFACT_GROUP)",
)
parser.add_argument(
"--build-dir",
type=Path,
default=Path(os.getenv("BUILD_DIR", "build")),
help="Build directory containing logs, artifacts, etc. (default: 'build' or $BUILD_DIR)",
)
is_ci = str2bool(os.getenv("CI", "false"))
parser.add_argument(
"--upload",
default=is_ci,
help="Enable upload steps (default enabled if $CI is set)",
action=argparse.BooleanOptionalAction,
)
parser.add_argument("--run-id", type=str, help="GitHub run ID of this workflow run")
parser.add_argument(
"--job-status", type=str, help="Status of this Job ('success', 'failure')"
)
parser.add_argument(
"--output-dir",
type=Path,
default=None,
help="Output to local directory instead of S3 (for testing)",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Print what would be uploaded without actually uploading",
)
args = parser.parse_args()
# Check preconditions for provided arguments before proceeding.
if args.upload:
if not args.run_id:
parser.error("when --upload is true, --run_id must also be set")
if not args.build_dir.is_dir():
raise FileNotFoundError(
f"""
Build directory ({str(args.build_dir)}) not found. Skipping upload!
This can be due to the CI job being cancelled before the build was started.
"""
)
run(args)