Files
TheRock/build_tools/packaging/upload_release_packages.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

548 lines
17 KiB
Python

#!/usr/bin/env python
# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""Upload promoted packages to S3 release bucket.
This script uploads promoted packages to S3 release buckets. It uploads wheel
files from architecture subdirectories and optionally tarball files. It expects
the same directory layout as download_prerelease_packages.py creates.
SAFETY FEATURES:
- DRY-RUN by default: Shows what would be uploaded without actually uploading
- Requires --execute flag to perform actual S3 uploads
- Testing buckets by default (you can use --use-release-buckets for production)
PREREQUISITES:
- pip install -r ./build_tools/packaging/requirements.txt
- AWS credentials configured (IAM role)
- Packages must be promoted first using promote_packages.py
TYPICAL USAGE (Command Line):
# Preview wheel upload (dry-run, no actual upload)
python ./build_tools/packaging/upload_release_packages.py \
--input-dir=./downloads
# Upload wheels only (to testing bucket)
python ./build_tools/packaging/upload_release_packages.py \
--input-dir=./downloads \
--execute
# Upload wheels and tarballs to production release buckets
python ./build_tools/packaging/upload_release_packages.py \
--input-dir=./downloads \
--upload-tarballs \
--execute \
--use-release-buckets
# Upload wheels only using multi-arch (flat layout)
python ./build_tools/packaging/upload_release_packages.py \
--input-dir=./downloads \
--multi-arch \
--execute
# Upload wheels and tarballs using multi-arch (flat layout, production buckets)
python ./build_tools/packaging/upload_release_packages.py \
--input-dir=./downloads \
--upload-tarballs \
--multi-arch \
--execute \
--use-release-buckets
# Upload single-arch wheels and tarballs to production release buckets
python ./build_tools/packaging/upload_release_packages.py \
--input-dir=./downloads \
--upload-tarballs \
--execute \
--use-release-buckets
DIRECTORY STRUCTURE:
Input directory structure (created by download_prerelease_packages.py):
<input-dir>/
<arch1>/
package1.whl
package2.whl
...
<arch2>/
...
tarball/ (optional, if --include-tarballs was used)
therock-dist-linux-<arch1>-<version>.tar.gz
therock-dist-windows-<arch2>-<version>.tar.gz
...
S3 bucket structure:
v3/whl/<arch>/
package1.whl
package2.whl
...
v3/tarball/
therock-dist-linux-<arch1>-<version>.tar.gz
therock-dist-windows-<arch2>-<version>.tar.gz
...
S3 bucket structure for mult-arch:
v4/whl/
package1.whl
package2.whl
package1.tar.gz
v4/tarball/
therock-dist-linux-<arch1>-<version>.tar.gz
therock-dist-windows-<arch2>-<version>.tar.gz
NOTE:
Index file generation is handled separately by manage.py.
This script only uploads package files.
"""
import argparse
import sys
from pathlib import Path
from typing import Tuple
try:
import boto3
from botocore.exceptions import ClientError, NoCredentialsError
except ImportError:
print("[ERROR]: boto3 not installed. Please run:")
print(" pip install boto3")
sys.exit(1)
def upload_python_files(
input_dir: Path,
bucket_name: str,
bucket_prefix: str,
execute: bool,
multi_arch: bool = False,
) -> int:
"""Upload Python package files to S3 bucket.
Args:
input_dir: Root directory containing architecture subdirectories
bucket_name: S3 bucket name
bucket_prefix: S3 bucket prefix (e.g., 'v3/whl/')
execute: If False, only show what would be uploaded (dry-run)
Returns:
Number of files uploaded (or would be uploaded)
"""
print("\nUploading Python packages to S3...")
print("=" * 80)
if not execute:
print("DRY-RUN MODE: No actual uploads will be performed")
print("=" * 80)
s3_client = boto3.client("s3") if execute else None
upload_count = 0
if multi_arch:
wheels_dir = input_dir / "wheels"
if not wheels_dir.exists():
print(f"[ERROR]: Multi-arch wheels directory not found: {wheels_dir}")
return 0
print("\nMulti-arch wheels")
files_to_upload = []
files_to_upload.extend(wheels_dir.glob("*.whl"))
files_to_upload.extend(wheels_dir.glob("*.tar.gz"))
upload_dirs = [(None, files_to_upload)]
else:
upload_dirs = []
for arch_dir in input_dir.iterdir():
if not arch_dir.is_dir():
continue
if arch_dir.name in ["tarball", "tarball-multi-arch", "wheels"]:
continue
arch = arch_dir.name
files_to_upload = []
files_to_upload.extend(arch_dir.glob("*.whl"))
files_to_upload.extend(arch_dir.glob("*.tar.gz"))
upload_dirs.append((arch, files_to_upload))
for arch, files_to_upload in upload_dirs:
if arch:
print(f"\nArchitecture: {arch}")
if not files_to_upload:
print(" No files to upload")
continue
print(f" Found {len(files_to_upload)} file(s) to upload:")
for file_path in sorted(files_to_upload):
if multi_arch:
s3_key = f"{bucket_prefix}{file_path.name}"
else:
s3_key = f"{bucket_prefix}{arch}/{file_path.name}"
if execute:
try:
print(f" Uploading {file_path.name} ...", end="", flush=True)
s3_client.upload_file(str(file_path), bucket_name, s3_key)
print(" done")
upload_count += 1
except ClientError as e:
print(
f"\n [ERROR]: Could not upload {file_path.name} to s3://{bucket_name}/{s3_key}: {e}"
)
else:
print(
f" [DRY-RUN] Would upload: {file_path.name} -> s3://{bucket_name}/{s3_key}"
)
upload_count += 1
return upload_count
def upload_tarball_files(
input_dir: Path,
bucket_name: str,
bucket_prefix: str,
execute: bool,
multi_arch: bool = False,
) -> int:
"""Upload tarball files to S3 bucket.
Args:
input_dir: Root directory containing tarball subdirectory
bucket_name: S3 bucket name
bucket_prefix: S3 bucket prefix (e.g., 'v3/tarball/')
execute: If False, only show what would be uploaded (dry-run)
Returns:
Number of tarball files uploaded (or would be uploaded)
"""
print("\nUploading tarballs to S3...")
print("=" * 80)
if not execute:
print("DRY-RUN MODE: No actual uploads will be performed")
print("=" * 80)
if multi_arch:
tarball_dir = input_dir / "tarball-multi-arch"
else:
tarball_dir = input_dir / "tarball"
if not tarball_dir.exists() or not tarball_dir.is_dir():
print(f" No tarball directory found at {tarball_dir}")
return 0
s3_client = boto3.client("s3") if execute else None
upload_count = 0
# Find all tar.gz files
tarball_files = list(tarball_dir.glob("*.tar.gz"))
if not tarball_files:
print(f" No tarball files found")
return 0
print(f" Found {len(tarball_files)} tarball file(s) to upload:")
for file_path in sorted(tarball_files):
s3_key = f"{bucket_prefix}{file_path.name}"
if execute:
try:
print(f" Uploading {file_path.name} ...", end="", flush=True)
s3_client.upload_file(str(file_path), bucket_name, s3_key)
print(" done")
upload_count += 1
except ClientError as e:
print(
f"\n [ERROR]: Could not upload {file_path.name} to s3://{bucket_name}/{s3_key}: {e}"
)
else:
print(
f" [DRY-RUN] Would upload: {file_path.name} -> s3://{bucket_name}/{s3_key}"
)
upload_count += 1
return upload_count
def parse_arguments(argv):
parser = argparse.ArgumentParser(
description="Upload promoted packages to S3 release bucket",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Dry-run: Preview wheel upload (DEFAULT - no actual uploads)
python upload_release_packages.py --input-dir=./downloads
# Upload wheels to test bucket
python upload_release_packages.py --input-dir=./downloads --execute
# Upload wheels and tarballs to production release buckets
python upload_release_packages.py --input-dir=./downloads --upload-tarballs --execute --use-release-buckets
# Upload only tarballs to production
python upload_release_packages.py --input-dir=./downloads --no-upload-python --upload-tarballs --execute --use-release-buckets
Safety Features:
- Dry-run is the DEFAULT mode (no --execute flag = no uploads)
- Testing buckets by DEFAULT (requires --use-release-buckets for production)
- Use --execute flag to perform actual uploads
""",
)
parser.add_argument(
"--input-dir",
type=Path,
required=True,
help="Input directory containing architecture subdirectories with promoted packages",
)
parser.add_argument(
"--upload-python",
action=argparse.BooleanOptionalAction,
default=True,
help="Upload Python packages (default: True)",
)
parser.add_argument(
"--multi-arch",
action=argparse.BooleanOptionalAction,
default=False,
help="Upload to multi-arch flat layout (no arch subdirectories)",
)
parser.add_argument(
"--bucket",
default="therock-testing-bucket",
help="S3 bucket name for Python packages (default: therock-testing-bucket). You probably want to use therock-release-python",
)
parser.add_argument(
"--bucket-prefix",
default="release-upload-testing/",
help="S3 bucket prefix for Python packages (default: release-upload-testing/)",
)
parser.add_argument(
"--upload-tarballs",
action=argparse.BooleanOptionalAction,
default=False,
help="Upload tarball packages (default: False)",
)
parser.add_argument(
"--tarball-bucket",
default="therock-testing-bucket",
help="S3 bucket name for tarball packages (default: therock-testing-bucket). You probably want to use therock-release-tarball",
)
parser.add_argument(
"--tarball-bucket-prefix",
default="release-upload-testing/tarball/",
help="S3 bucket prefix for tarball packages (default: release-upload-testing/tarball/)",
)
parser.add_argument(
"--execute",
action=argparse.BooleanOptionalAction,
default=False,
help="Actually perform uploads (default: dry-run mode)",
)
parser.add_argument(
"--use-release-buckets",
action=argparse.BooleanOptionalAction,
default=False,
help="Overwrite buckets to use production release buckets: therock-release-tarball:v3/tarball and therock-release-python:v3/whl",
)
args = parser.parse_args(argv)
# Adjust testing tarball prefix for multi-arch uploads
if args.multi_arch and not args.use_release_buckets:
args.tarball_bucket_prefix = "release-upload-testing/tarball-multi-arch/"
if args.use_release_buckets:
args.bucket = "therock-release-python"
if args.multi_arch:
args.bucket_prefix = "v4/rocm/whl/"
args.tarball_bucket = "therock-release-tarball"
args.tarball_bucket_prefix = "v4/rocm/tarball/"
else:
args.bucket_prefix = "v3/rocm/whl/"
args.tarball_bucket = "therock-release-tarball"
args.tarball_bucket_prefix = "v3/rocm/tarball/"
# Validate input directory
if not args.input_dir.exists():
parser.error(f"Input directory does not exist: {args.input_dir}")
if not args.input_dir.is_dir():
parser.error(f"Input path is not a directory: {args.input_dir}")
return args
def upload_release_packages(
input_dir: Path,
upload_python: bool = True,
bucket_name: str = "therock-testing-bucket",
bucket_prefix: str = "release-upload-testing/",
upload_tarballs: bool = False,
tarball_bucket_name: str = "therock-testing-bucket",
tarball_bucket_prefix: str = "release-upload-testing/tarball/",
execute: bool = False,
multi_arch: bool = False,
) -> Tuple[int, int]:
"""Upload promoted packages to S3 release bucket.
Args:
input_dir: Root directory containing architecture subdirectories with promoted packages
upload_python: Upload Python packages (default: True)
bucket_name: S3 bucket name for Python packages (default: therock-testing-bucket)
bucket_prefix: S3 bucket prefix for Python packages (default: release-upload-testing/)
upload_tarballs: Upload tarball packages (default: False)
tarball_bucket_name: S3 bucket name for tarball packages (default: therock-testing-bucket)
tarball_bucket_prefix: S3 bucket prefix for tarball packages (default: release-upload-testing/tarball/)
execute: If True, perform actual uploads; if False, dry-run mode (default: False)
Returns:
Tuple of (python_packages_uploaded, tarballs_uploaded)
Raises:
SystemExit: If AWS credentials are not configured or S3 operations fail
"""
print("=" * 80)
print("Upload Release Packages")
print("=" * 80)
print(f"Input directory: {input_dir.absolute()}")
print(f"Python bucket: {bucket_name}")
print(f"Python bucket prefix: {bucket_prefix}")
if upload_tarballs:
print(f"Tarball bucket: {tarball_bucket_name}")
print(f"Tarball bucket prefix: {tarball_bucket_prefix}")
print(
f"Execution mode: {'EXECUTE (will upload)' if execute else 'DRY-RUN (no uploads)'}"
)
print("=" * 80)
if not execute:
print("\n⚠️ SAFETY: Dry-run mode active - no actual uploads will be performed")
print("⚠️ Use --execute flag to perform actual uploads")
print(
"⚠️ Additionally use --use-release-buckets flag for production buckets (therock-release-python and therock-release-tarball)\n"
)
# Count architectures (exclude tarball directory)
architectures = [
d
for d in input_dir.iterdir()
if d.is_dir() and d.name not in ["tarball", "tarball-multi-arch", "wheels"]
]
print(f"\nFound {len(architectures)} architecture(s):")
for arch_dir in architectures:
print(f" - {arch_dir.name}")
if not architectures and not multi_arch:
print("\n[ERROR]: No architecture directories found in input directory")
sys.exit(1)
# Check if at least one upload type is enabled
if not upload_python and not upload_tarballs:
print(
"\n[ERROR]: No upload targets specified. Use --upload-python and/or --upload-tarballs"
)
sys.exit(1)
# Upload packages
wheels_uploaded = 0
tarballs_uploaded = 0
try:
if upload_python:
wheels_uploaded = upload_python_files(
input_dir,
bucket_name,
bucket_prefix,
execute,
multi_arch=multi_arch,
)
if upload_tarballs:
tarballs_uploaded = upload_tarball_files(
input_dir,
tarball_bucket_name,
tarball_bucket_prefix,
execute,
multi_arch=multi_arch,
)
except NoCredentialsError:
print("\n[ERROR]: AWS credentials not configured")
print("Please configure credentials via IAM role")
sys.exit(1)
except ClientError as e:
print(f"\n[ERROR]: S3 operation failed: {e}")
sys.exit(1)
# Final summary
print("\n" + "=" * 80)
print("SUMMARY")
print("=" * 80)
if execute:
print("Execution mode: EXECUTED")
if wheels_uploaded > 0:
print(
f"\nPython packages uploaded: {wheels_uploaded} (location: s3://{bucket_name}/{bucket_prefix})"
)
if tarballs_uploaded > 0:
print(
f"Tarballs uploaded: {tarballs_uploaded} (location: s3://{tarball_bucket_name}/{tarball_bucket_prefix})"
)
else:
print("Execution mode: DRY-RUN")
if wheels_uploaded > 0:
print(f" Python packages that would be uploaded: {wheels_uploaded}")
if tarballs_uploaded > 0:
print(f" Tarballs that would be uploaded: {tarballs_uploaded}")
print("\nNext steps:")
if execute:
print(" - Verify uploads in S3 bucket")
print(" - Use manage.py to generate and upload index files")
else:
print(" - Review the dry-run output above")
print(
" - Run again with --execute to perform actual uploads to testing bucket"
)
print(" - Add --use-release-buckets flag when ready for production upload")
print("=" * 80)
return wheels_uploaded, tarballs_uploaded
if __name__ == "__main__":
args = parse_arguments(sys.argv[1:])
upload_release_packages(
input_dir=args.input_dir,
upload_python=args.upload_python,
bucket_name=args.bucket,
bucket_prefix=args.bucket_prefix,
upload_tarballs=args.upload_tarballs,
tarball_bucket_name=args.tarball_bucket,
tarball_bucket_prefix=args.tarball_bucket_prefix,
execute=args.execute,
multi_arch=args.multi_arch,
)