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
409 lines
13 KiB
Python
Executable File
409 lines
13 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Copyright Advanced Micro Devices, Inc.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""Sets up a Python venv and optionally installs rocm packages into it.
|
|
|
|
* https://docs.python.org/3/library/venv.html
|
|
* https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#create-and-use-virtual-environments
|
|
|
|
There are a few modes this can be used in:
|
|
|
|
* Minimally, this is equivalent to `python -m venv .venv`:
|
|
|
|
```
|
|
python setup_venv.py .venv
|
|
```
|
|
|
|
* To install the latest nightly rocm packages into the venv:
|
|
|
|
```
|
|
python setup_venv.py .venv --packages rocm[libraries,devel,device-all] --index-name nightly
|
|
```
|
|
|
|
This is roughly equivalent to:
|
|
|
|
```
|
|
python -m venv .venv
|
|
source .venv/bin/activate
|
|
python -m pip install --upgrade pip
|
|
python -m pip install rocm[libraries,devel,device-all] --index-url=https://rocm.nightlies.amd.com/whl-multi-arch/
|
|
deactivate
|
|
```
|
|
|
|
Some automated workflows using this script to install packages can run shortly
|
|
after other workflows uploads those packages. In these cases server-side index
|
|
generation may not have completed yet and installs can fail for 1-3 minutes.
|
|
Package installs are retried by default to cover that transient window. This
|
|
behavior can be adjusted with the `--install-retry-timeout-seconds` and
|
|
`--install-retry-wait-between-seconds` arguments.
|
|
See https://github.com/ROCm/TheRock/issues/5455 for more details.
|
|
"""
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
import platform
|
|
import shlex
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
from github_actions.github_actions_api import *
|
|
|
|
is_windows = platform.system() == "Windows"
|
|
|
|
ROCM_INDEX_URLS_MAP = {
|
|
"stable": "https://repo.amd.com/rocm/whl-multi-arch/",
|
|
"prerelease": "https://rocm.prereleases.amd.com/whl-multi-arch/",
|
|
"nightly": "https://rocm.nightlies.amd.com/whl-multi-arch/",
|
|
"dev": "https://rocm.devreleases.amd.com/whl-multi-arch/",
|
|
}
|
|
|
|
|
|
def log(*args, **kwargs):
|
|
print(*args, **kwargs)
|
|
sys.stdout.flush()
|
|
|
|
|
|
def run_command(args: list[str | Path], cwd: Path = Path.cwd()):
|
|
args = [str(arg) for arg in args]
|
|
log(f"++ Exec [{cwd}]$ {shlex.join(args)}")
|
|
subprocess.check_call(args, cwd=str(cwd), stdin=subprocess.DEVNULL)
|
|
|
|
|
|
def run_command_with_retries(
|
|
args: list[str | Path],
|
|
cwd: Path = Path.cwd(),
|
|
retry_timeout_seconds: int = 0,
|
|
retry_wait_between_seconds: int = 0,
|
|
):
|
|
deadline = time.monotonic() + retry_timeout_seconds
|
|
attempt = 1
|
|
while True:
|
|
try:
|
|
run_command(args, cwd)
|
|
return
|
|
except subprocess.CalledProcessError as e:
|
|
if (
|
|
retry_timeout_seconds == 0
|
|
or time.monotonic() + retry_wait_between_seconds > deadline
|
|
):
|
|
log(
|
|
"Command failed after "
|
|
f"{attempt} attempt(s); no retry time remains"
|
|
)
|
|
raise
|
|
log(
|
|
"Command failed "
|
|
f"(attempt {attempt}, exit code {e.returncode}); "
|
|
f"retrying in {retry_wait_between_seconds}s..."
|
|
)
|
|
time.sleep(retry_wait_between_seconds)
|
|
attempt += 1
|
|
|
|
|
|
def find_venv_python_exe(venv_path: Path) -> Path | None:
|
|
"""Finds the python executable under |venv_path|, if one exists."""
|
|
paths = [venv_path / "bin" / "python", venv_path / "Scripts" / "python.exe"]
|
|
for p in paths:
|
|
if p.exists():
|
|
return p
|
|
return None
|
|
|
|
|
|
def create_venv(venv_dir: Path, use_uv: bool = False):
|
|
"""Creates a Python venv at |venv_dir|.
|
|
|
|
No-op if venv_dir is already an initialized venv (has a python executable).
|
|
"""
|
|
log(f"Creating venv at '{venv_dir}'")
|
|
|
|
# Log some other variations of the path too.
|
|
try:
|
|
venv_dir_relative = venv_dir.relative_to(Path.cwd())
|
|
except ValueError:
|
|
venv_dir_relative = venv_dir
|
|
venv_dir_resolved = venv_dir.resolve()
|
|
log(f" Dir relative to CWD: '{venv_dir_relative}'")
|
|
log(f" Dir fully resolved : '{venv_dir_resolved}'")
|
|
log("")
|
|
|
|
# Create with 'python -m venv' as needed.
|
|
python_exe = find_venv_python_exe(venv_dir_resolved)
|
|
if python_exe:
|
|
log(f" Found existing python executable at '{python_exe}', skipping creation")
|
|
log(" Run again with --clean to clear the existing directory instead")
|
|
return
|
|
|
|
if use_uv:
|
|
run_command(["uv", "venv", str(venv_dir_resolved)])
|
|
else:
|
|
run_command([sys.executable, "-m", "venv", str(venv_dir_resolved)])
|
|
|
|
|
|
def update_venv(venv_dir: Path, use_uv: bool = False):
|
|
if use_uv:
|
|
# No updates needed.
|
|
return
|
|
|
|
# pip logs warnings about wanting to update, so we'll do that for it.
|
|
# Keep setuptools/wheel available so artifact-only installs of ROCm sdists
|
|
# can use --no-build-isolation without resolving build dependencies from
|
|
# the package index being tested.
|
|
log("")
|
|
python_exe = find_venv_python_exe(venv_dir)
|
|
run_command(
|
|
[
|
|
str(python_exe),
|
|
"-m",
|
|
"pip",
|
|
"install",
|
|
"--upgrade",
|
|
"pip",
|
|
"setuptools",
|
|
"wheel",
|
|
]
|
|
)
|
|
|
|
|
|
def activate_venv_in_gha(venv_dir: Path):
|
|
"""Activates the venv in venv_dir for future GitHub Actions workflow steps.
|
|
|
|
This is a (useful) hack that modifies the PATH and VIRTUAL_ENV env vars
|
|
rather than call the platform-specific 'activate' scripts.
|
|
"""
|
|
|
|
log("")
|
|
log(f"Activating venv for future GitHub Actions workflow steps")
|
|
gha_warn_if_not_running_on_ci()
|
|
|
|
# See https://docs.python.org/3/library/venv.html#how-venvs-work.
|
|
#
|
|
# The usual way to activate a venv is to run the platform-specific command:
|
|
# POSIX bash : `source <venv>/bin/activate`
|
|
# Windows cmd.exe : `<venv>\Scripts\activate.bat`
|
|
# Windows powershell : `<venv>\Scripts\Activate.ps1`
|
|
# etc.
|
|
#
|
|
# What these scripts actually do is a combination of setting environment
|
|
# variables, which we can't normally do (persistently) from a Python script.
|
|
# However, in the context of a GitHub Actions workflow, we *can* set
|
|
# environment variables (and job outputs, and step summaries, etc.) using
|
|
# https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions.
|
|
|
|
if is_windows:
|
|
gha_add_to_path(venv_dir / "Scripts")
|
|
else:
|
|
gha_add_to_path(venv_dir / "bin")
|
|
gha_set_env({"VIRTUAL_ENV": venv_dir})
|
|
|
|
|
|
def install_packages_into_venv(
|
|
venv_dir: Path,
|
|
packages: list[str],
|
|
use_uv: bool = False,
|
|
index_url: str | None = None,
|
|
index_name: str | None = None,
|
|
find_links: str | None = None,
|
|
pre: bool = False,
|
|
disable_cache: bool = False,
|
|
install_retry_timeout_seconds: int = 0,
|
|
install_retry_wait_between_seconds: int = 0,
|
|
):
|
|
"""Installs packages into venv_dir using the provided options.
|
|
|
|
Args:
|
|
venv_dir: The venv to install into
|
|
packages: The list of packages to install
|
|
use_uv: True to use 'uv', uses 'pip' otherwise
|
|
index_url: URL for '--index-url' command argument
|
|
index_name: Shorthand for an index_url (e.g. 'nightly')
|
|
find_links: URL for '--find-links' command argument
|
|
pre: Allow pre-release packages (pip: --pre, uv: --prerelease=allow)
|
|
disable_cache: Disable package cache (pip: --no-cache-dir, uv: --no-cache)
|
|
install_retry_timeout_seconds: Maximum retry window for the install command
|
|
install_retry_wait_between_seconds: Delay between package install retries
|
|
"""
|
|
log("")
|
|
|
|
venv_python_exe = find_venv_python_exe(venv_dir)
|
|
assert venv_python_exe is not None, f"No python executable found in {venv_dir}"
|
|
pip_install_cmd = (
|
|
[str(venv_python_exe), "-m", "pip", "install"]
|
|
if not use_uv
|
|
else ["uv", "pip", "install", "--python", str(venv_python_exe)]
|
|
)
|
|
|
|
if index_url and index_name:
|
|
raise ValueError("Can't set both index_url and index_name")
|
|
|
|
if index_name:
|
|
# Look up known index name.
|
|
index_url = ROCM_INDEX_URLS_MAP[index_name]
|
|
|
|
if index_url == "":
|
|
pip_install_cmd.append("--no-index")
|
|
pip_install_cmd.append("--no-build-isolation")
|
|
elif index_url:
|
|
pip_install_cmd.append(f"--index-url={index_url}")
|
|
|
|
if find_links:
|
|
pip_install_cmd.append(f"--find-links={find_links}")
|
|
|
|
if pre:
|
|
pip_install_cmd.append("--prerelease=allow" if use_uv else "--pre")
|
|
|
|
if disable_cache:
|
|
pip_install_cmd.append("--no-cache" if use_uv else "--no-cache-dir")
|
|
|
|
pip_install_cmd.extend(packages)
|
|
|
|
run_command_with_retries(
|
|
pip_install_cmd,
|
|
retry_timeout_seconds=install_retry_timeout_seconds,
|
|
retry_wait_between_seconds=install_retry_wait_between_seconds,
|
|
)
|
|
|
|
|
|
def log_venv_activate_instructions(venv_dir: Path):
|
|
"""Logs platform-specific instructions for activating a venv."""
|
|
log("")
|
|
log(f"Setup complete at '{venv_dir}'! Activate the venv with:")
|
|
if is_windows:
|
|
log(f" {venv_dir}\\Scripts\\activate.bat")
|
|
else:
|
|
log(f" source {venv_dir}/bin/activate")
|
|
|
|
|
|
def run(args: argparse.Namespace):
|
|
venv_dir = args.venv_dir
|
|
use_uv = args.use_uv
|
|
|
|
if args.clean and venv_dir.exists():
|
|
log(f"Clearing existing venv_dir '{venv_dir}'")
|
|
shutil.rmtree(venv_dir)
|
|
|
|
create_venv(venv_dir, use_uv)
|
|
update_venv(venv_dir, use_uv)
|
|
|
|
if args.packages:
|
|
install_packages_into_venv(
|
|
venv_dir=venv_dir,
|
|
packages=args.packages.split(),
|
|
use_uv=use_uv,
|
|
index_url=args.index_url,
|
|
index_name=args.index_name,
|
|
find_links=args.find_links,
|
|
pre=args.pre,
|
|
disable_cache=args.disable_cache,
|
|
install_retry_timeout_seconds=args.install_retry_timeout_seconds,
|
|
install_retry_wait_between_seconds=args.install_retry_wait_between_seconds,
|
|
)
|
|
|
|
if args.activate_in_future_github_actions_steps:
|
|
activate_venv_in_gha(venv_dir)
|
|
else:
|
|
log_venv_activate_instructions(venv_dir)
|
|
|
|
|
|
def main(argv: list[str]):
|
|
p = argparse.ArgumentParser("setup_venv.py")
|
|
p.add_argument(
|
|
"venv_dir",
|
|
type=Path,
|
|
help="Directory in which to create the venv, such as '.venv'",
|
|
)
|
|
|
|
general_options = p.add_argument_group("General options")
|
|
general_options.add_argument(
|
|
"--clean",
|
|
action=argparse.BooleanOptionalAction,
|
|
help="If the venv directory already exists, clear it and start fresh",
|
|
)
|
|
general_options.add_argument(
|
|
"--pre",
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Allow installing pre-release packages",
|
|
)
|
|
general_options.add_argument(
|
|
"--disable-cache",
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Disable the pip/uv package cache",
|
|
)
|
|
general_options.add_argument(
|
|
"--activate-in-future-github-actions-steps",
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Attempts to activate the venv persistently when running in a GitHub Action. This is less reliable than running the official activate command",
|
|
)
|
|
general_options.add_argument(
|
|
"--use-uv",
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Uses uv instead of pip/venv, see more at: https://docs.astral.sh/uv/",
|
|
)
|
|
general_options.add_argument(
|
|
"--install-retry-timeout-seconds",
|
|
type=int,
|
|
default=180,
|
|
help=(
|
|
"Maximum wall-clock time to keep retrying failed package install "
|
|
"commands in seconds. This does not timeout an in-progress pip "
|
|
"command. (default: 180; 0 disables retries)"
|
|
),
|
|
)
|
|
general_options.add_argument(
|
|
"--install-retry-wait-between-seconds",
|
|
type=int,
|
|
default=15,
|
|
help="Seconds to wait between failed package install attempts (default: 15)",
|
|
)
|
|
|
|
install_options = p.add_argument_group("Install options")
|
|
|
|
# TODO(#1036): Other flags or helper scripts to help map between versions,
|
|
# git commits/refs, workflow runs, etc.
|
|
# I'd like a shorthand for "install packages from commit abcde"
|
|
# Maybe use find_artifacts_for_commit.py
|
|
install_options.add_argument(
|
|
"--packages",
|
|
type=str,
|
|
help="Packages to install, including any extras or explicit versions (e.g. 'rocm[libraries,devel]==1.0')",
|
|
)
|
|
# TODO(#1036): add "auto" mode here that infers the index from the version?
|
|
install_options.add_argument(
|
|
"--index-url",
|
|
type=str,
|
|
help="Package index URL for pip --index-url",
|
|
)
|
|
install_options.add_argument(
|
|
"--index-name",
|
|
type=str,
|
|
choices=["stable", "prerelease", "nightly", "dev"],
|
|
help="Shorthand for a named index",
|
|
)
|
|
install_options.add_argument(
|
|
"--find-links",
|
|
type=str,
|
|
help="Package location URL for pip --find-links (compatible with --index-url)",
|
|
)
|
|
|
|
args = p.parse_args(argv)
|
|
|
|
if args.venv_dir.exists() and not args.venv_dir.is_dir():
|
|
p.error(f"venv_dir '{args.venv_dir}' exists and is not a directory")
|
|
if args.install_retry_timeout_seconds < 0:
|
|
p.error("--install-retry-timeout-seconds must be non-negative")
|
|
if (
|
|
args.install_retry_timeout_seconds > 0
|
|
and args.install_retry_wait_between_seconds <= 0
|
|
):
|
|
p.error(
|
|
"--install-retry-wait-between-seconds must be positive when retries are enabled"
|
|
)
|
|
|
|
run(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1:])
|