| #!/usr/bin/env python3 |
| # Copyright 2023 The IREE Authors |
| # |
| # Licensed under the Apache License v2.0 with LLVM Exceptions. |
| # See https://llvm.org/LICENSE.txt for license information. |
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| """Sets up a Python venv with compiler/runtime from a workflow run. |
| |
| There are two modes in which to use this script: |
| |
| * Within a workflow, an artifact action will typically be used to fetch |
| relevant package artifacts. Specify the fetch location with |
| `--artifact-path=`. |
| |
| * Locally, the `--fetch-gh-workflow=WORKFLOW_ID` can be used instead in order |
| to download and setup the venv in one step. |
| |
| You must have the `gh` command line tool installed and authenticated if you |
| will be fetching artifacts. |
| """ |
| |
| from typing import Optional, Dict, Tuple |
| |
| import argparse |
| import functools |
| from glob import glob |
| import json |
| import os |
| import sys |
| from pathlib import Path |
| import platform |
| import subprocess |
| import sys |
| import tempfile |
| import zipfile |
| |
| |
| @functools.lru_cache |
| def list_gh_artifacts(run_id: str) -> Dict[str, str]: |
| print(f"Fetching artifacts for workflow run {run_id}") |
| base_path = f"/repos/openxla/iree" |
| output = subprocess.check_output( |
| [ |
| "gh", |
| "api", |
| "-H", |
| "Accept: application/vnd.github+json", |
| "-H", |
| "X-GitHub-Api-Version: 2022-11-28", |
| f"{base_path}/actions/runs/{run_id}/artifacts", |
| ] |
| ) |
| data = json.loads(output) |
| # Uncomment to debug: |
| # print(json.dumps(data, indent=2)) |
| artifacts = { |
| rec["name"]: f"{base_path}/actions/artifacts/{rec['id']}/zip" |
| for rec in data["artifacts"] |
| } |
| print("Found artifacts:") |
| for k, v in artifacts.items(): |
| print(f" {k}: {v}") |
| return artifacts |
| |
| |
| def fetch_gh_artifact(api_path: str, file: Path): |
| print(f"Downloading artifact {api_path}") |
| contents = subprocess.check_output( |
| [ |
| "gh", |
| "api", |
| "-H", |
| "Accept: application/vnd.github+json", |
| "-H", |
| "X-GitHub-Api-Version: 2022-11-28", |
| api_path, |
| ] |
| ) |
| file.write_bytes(contents) |
| |
| |
| def find_venv_python(venv_path: Path) -> Optional[Path]: |
| paths = [venv_path / "bin" / "python", venv_path / "Scripts" / "python.exe"] |
| for p in paths: |
| if p.exists(): |
| return p |
| return None |
| |
| |
| def parse_arguments(argv=None): |
| parser = argparse.ArgumentParser(description="Setup venv") |
| parser.add_argument("--artifact-path", help="Path in which to find/fetch artifacts") |
| parser.add_argument( |
| "--fetch-gh-workflow", help="Fetch artifacts from a GitHub workflow" |
| ) |
| parser.add_argument( |
| "--compiler-variant", |
| default="", |
| help="Package variant to install for the compiler ('', 'asserts')", |
| ) |
| parser.add_argument( |
| "--runtime-variant", |
| default="", |
| help="Package variant to install for the runtime ('', 'asserts')", |
| ) |
| parser.add_argument( |
| "venv_dir", type=Path, help="Directory in which to create the venv" |
| ) |
| args = parser.parse_args(argv) |
| return args |
| |
| |
| def main(args): |
| # Make sure we have an artifact path if fetching. |
| if not args.artifact_path and args.fetch_gh_workflow: |
| with tempfile.TemporaryDirectory() as td: |
| args.artifact_path = td |
| return main(args) |
| |
| # Find the regression suite project. |
| rs_dir = ( |
| (Path(__file__).resolve().parent.parent.parent) |
| / "experimental" |
| / "regression_suite" |
| ) |
| if not rs_dir.exists(): |
| print(f"Could not find regression_suite project: {rs_dir}") |
| return 1 |
| |
| artifact_prefix = f"{platform.system().lower()}_{platform.machine()}" |
| wheels = [] |
| for package_stem, variant in [ |
| ("iree-compiler", args.compiler_variant), |
| ("iree-runtime", args.runtime_variant), |
| ]: |
| wheels.append( |
| find_wheel_for_variants(args, artifact_prefix, package_stem, variant) |
| ) |
| print("Installing wheels:", wheels) |
| |
| # Set up venv. |
| venv_path = args.venv_dir |
| python_exe = find_venv_python(venv_path) |
| |
| if not python_exe: |
| print(f"Creating venv at {str(venv_path)}") |
| subprocess.check_call([sys.executable, "-m", "venv", str(venv_path)]) |
| python_exe = find_venv_python(venv_path) |
| if not python_exe: |
| raise RuntimeError("Error creating venv") |
| |
| # Install each of the built wheels without deps or consulting an index. |
| # This is because we absolutely don't want this falling back to anything |
| # but what we said. |
| for artifact_path, package_name in wheels: |
| cmd = [ |
| str(python_exe), |
| "-m", |
| "pip", |
| "install", |
| "--no-deps", |
| "--no-index", |
| "-f", |
| str(artifact_path), |
| "--force-reinstall", |
| package_name, |
| ] |
| print(f"Running command: {' '.join([str(c) for c in cmd])}") |
| subprocess.check_call(cmd) |
| |
| # Now install the regression suite project, which will bring in any |
| # deps. |
| cmd = [ |
| str(python_exe), |
| "-m", |
| "pip", |
| "install", |
| "--force-reinstall", |
| "-e", |
| str(rs_dir) + os.sep, |
| ] |
| print(f"Running command: {' '.join(cmd)}") |
| subprocess.check_call(cmd) |
| |
| return 0 |
| |
| |
| def find_wheel_for_variants( |
| args, artifact_prefix: str, package_stem: str, variant: str |
| ) -> Tuple[Path, str]: |
| artifact_path = Path(args.artifact_path) |
| package_suffix = "" if variant == "" else f"-{variant}" |
| package_name = f"{package_stem}{package_suffix}" |
| |
| def has_package(): |
| norm_package_name = package_name.replace("-", "_") |
| pattern = str(artifact_path / f"{norm_package_name}-*.whl") |
| files = glob(pattern) |
| return bool(files) |
| |
| if has_package(): |
| return (artifact_path, package_name) |
| |
| if not args.fetch_gh_workflow: |
| raise RuntimeError( |
| f"Could not find package {package_name} to install from {artifact_path}" |
| ) |
| |
| # Fetch. |
| artifact_path.mkdir(parents=True, exist_ok=True) |
| artifact_suffix = "" if variant == "" else f"_{variant}" |
| artifact_name = f"{artifact_prefix}_release{artifact_suffix}_packages" |
| artifact_file = artifact_path / f"{artifact_name}.zip" |
| if not artifact_file.exists(): |
| print(f"Package {package_name} not found. Fetching from {artifact_name}...") |
| artifacts = list_gh_artifacts(args.fetch_gh_workflow) |
| if artifact_name not in artifacts: |
| raise RuntimeError( |
| f"Could not find required artifact {artifact_name} in run {args.fetch_gh_workflow}" |
| ) |
| fetch_gh_artifact(artifacts[artifact_name], artifact_file) |
| print(f"Extracting {artifact_file}") |
| with zipfile.ZipFile(artifact_file) as zip_ref: |
| zip_ref.extractall(artifact_path) |
| |
| # Try again. |
| if not has_package(): |
| raise RuntimeError(f"Could not find {package_name} in {artifact_path}") |
| return (artifact_path, package_name) |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main(parse_arguments())) |