-
Notifications
You must be signed in to change notification settings - Fork 61
[Buildbot] Add script for setting up environment for debug/reproduction #2278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: aomp-dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,6 +48,85 @@ A manual start of the container should look similar to | |
| sudo docker run --rm -it --network=host --device=/dev/kfd --device=/dev/dri --group-add video --cpuset-cpus 0-31 --user botworker <container-image> bash | ||
| ``` | ||
|
|
||
| ## Quick setup with `run.py` | ||
|
|
||
| `run.py` is a helper that lets a developer quickly set up an environment from one | ||
| of the manylinux image directories (e.g. `manylinux-build-only`, `manylinux-hip-tpl`) | ||
| for local debugging and reproduction. It downloads the base build context from | ||
| [TheRock](https://github.com/ROCm/TheRock/tree/main/dockerfiles), builds the | ||
| images, and creates a container ready to `docker exec` into. | ||
|
|
||
| ### Usage | ||
|
|
||
| ``` | ||
| python run.py <target> [--pull] [--build] [--clean] [options] | ||
| ``` | ||
|
|
||
| `<target>` is one of `manylinux-build-only` or `manylinux-hip-tpl`. | ||
|
|
||
| When no operation flag is given, the default flow is **pull then build** (download | ||
| the base files, build the images, and start the container). The operations run in | ||
| a fixed order (`clean` -> `pull` -> `build`) and abort if any step fails. | ||
|
|
||
| | Operation | Meaning | | ||
| | --------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | `--pull` | Download `build_manylinux_x86_64.Dockerfile` and the helper scripts it needs into `<dest>/manylinux-base`. | | ||
| | `--build` | Build the base image `localhost/manylinux:base` (only if missing), build the target image (tagged `<target>`), then create and run a container. | | ||
| | `--clean` | Remove the container, the target image, and the base image. | | ||
|
|
||
| | Option | Default | Meaning | | ||
| | ---------- | -------------- | -------------------------------------------------------------------- | | ||
| | `--dest` | current dir | Where the base build context (`manylinux-base/`) is downloaded/read. | | ||
| | `--name` | `test-<target>` | Container name. | | ||
| | `--no-gpu` | (off) | Skip the GPU device/group run flags when starting the container. | | ||
| | `--llvm-src` | (none) | Bind-mount a local LLVM source tree at `/home/botworker/bbot/llvm-project`. | | ||
|
|
||
| `--dest` is not remembered between runs. If you split `--pull` and `--build` into | ||
| two separate invocations and pulled to a non-default location, you must pass the | ||
| same `--dest` to `--build` so it can find the downloaded base files. For example, | ||
| after `python run.py manylinux-build-only --pull --dest ~/test`, build with: | ||
|
|
||
| ``` | ||
| python run.py manylinux-build-only --build --dest ~/test | ||
| ``` | ||
|
|
||
| Omitting `--dest` on the build step would look in `./manylinux-base` instead and | ||
| fail with a "Run a pull first" error (the base image is only built by `--build`, | ||
| not by `--pull`). | ||
|
|
||
| Use `--llvm-src` to mount an existing local LLVM checkout instead of cloning | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we comment on which / how UID and GID is used here? Whether local user UID / GID is mapped or what happens? |
||
| inside the container; it appears at `/home/botworker/bbot/llvm-project`. | ||
|
|
||
| The container is started detached and kept alive, so you can open a shell with: | ||
|
|
||
| ``` | ||
| docker exec -it test-<target> bash | ||
| ``` | ||
|
|
||
| ### Examples | ||
|
|
||
| ``` | ||
| # Default: pull base files, build the images, and start the container | ||
| python run.py manylinux-build-only | ||
|
|
||
| # Only download the base Dockerfile + helper scripts | ||
| python run.py manylinux-build-only --pull | ||
|
|
||
| # Split pull and build into two steps with a custom location | ||
| # (pass the same --dest to both so build can find the pulled files) | ||
| python run.py manylinux-build-only --pull --dest ~/test | ||
| python run.py manylinux-build-only --build --dest ~/test | ||
|
|
||
| # Build and run without GPU device flags | ||
| python run.py manylinux-build-only --build --no-gpu | ||
|
|
||
| # Mount a local LLVM source tree instead of cloning inside the container | ||
| python run.py manylinux-hip-tpl --build --llvm-src ~/git/llvm-project | ||
|
|
||
| # Remove the container and images | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this also stop the container? Thought: Should we allow a user to add a |
||
| python run.py manylinux-build-only --clean | ||
| ``` | ||
|
|
||
| ## Assumptions / Requirements | ||
|
|
||
| - The images require a working AMDGPU dkms / KFD to be installed in order to test work on the GPU. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,254 @@ | ||||||
| #!/usr/bin/env python3 | ||||||
|
|
||||||
| """ | ||||||
| run.py - Developer helper for the manylinux buildbot docker images. | ||||||
|
|
||||||
| Quickly set up an environment from one of the provided image directories | ||||||
| (manylinux-build-only, manylinux-hip-tpl, etc) for local debugging / reproduction. | ||||||
|
|
||||||
| Examples: | ||||||
| # Download the base Dockerfile + helper scripts | ||||||
| python run.py manylinux-build-only --pull | ||||||
|
|
||||||
| # Build the base image, the target image, and run a container | ||||||
| python run.py manylinux-build-only --build | ||||||
|
|
||||||
| # Remove the container and image | ||||||
| python run.py manylinux-build-only --clean | ||||||
| """ | ||||||
|
|
||||||
| import argparse | ||||||
| import shutil | ||||||
| import subprocess | ||||||
| import sys | ||||||
| import urllib.request | ||||||
| from pathlib import Path | ||||||
|
|
||||||
| SCRIPT_DIR = Path(__file__).resolve().parent | ||||||
|
|
||||||
| BASE_IMAGE = "localhost/manylinux:base" | ||||||
| BASE_DOCKERFILE = "build_manylinux_x86_64.Dockerfile" | ||||||
| THEROCK_LINK = "https://raw.githubusercontent.com/ROCm/TheRock/main/dockerfiles" | ||||||
| # Update this when onboarding a new buildbot image. | ||||||
| TARGETS = ["manylinux-build-only", "manylinux-hip-tpl"] | ||||||
|
|
||||||
| # Host LLVM source tree is mounted here in the container. | ||||||
| LLVM_MOUNT_TARGET = "/home/botworker/bbot/llvm-project" | ||||||
|
|
||||||
| # Necessary files. | ||||||
| BASE_FILES = [ | ||||||
| BASE_DOCKERFILE, | ||||||
| "install_ccache.sh", | ||||||
| "install_sccache.sh", | ||||||
| "install_cmake.sh", | ||||||
| "install_ninja.sh", | ||||||
| "install_awscli.sh", | ||||||
| "install_googletest.sh", | ||||||
| "install_rust.sh", | ||||||
| "install_patchelf.sh", | ||||||
| "install_shared_pythons.sh", | ||||||
| ] | ||||||
|
|
||||||
| GPU_RUN_FLAGS = [ | ||||||
| "--device=/dev/kfd", | ||||||
| "--device=/dev/dri", | ||||||
| "--group-add", "video", | ||||||
| "--group-add", "render", | ||||||
| ] | ||||||
|
|
||||||
|
|
||||||
| # Helper functions. | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment seems unnecessary IMHO |
||||||
| def log(msg): | ||||||
| print(f"[run.py] {msg}", flush=True) | ||||||
|
|
||||||
|
|
||||||
| def run_cmd(cmd, check=True, capture=False): | ||||||
| """Echo and run a command, streaming output. Returns CompletedProcess.""" | ||||||
| log("$ " + " ".join(cmd)) | ||||||
| return subprocess.run(cmd, check=check, text=True, capture_output=capture) | ||||||
|
|
||||||
|
|
||||||
| def require_docker(): | ||||||
| if shutil.which("docker") is None: | ||||||
| sys.exit("error: 'docker' not found on PATH.") | ||||||
|
|
||||||
|
|
||||||
| def image_exists(tag): | ||||||
| res = run_cmd(["docker", "images", "-q", tag], check=False, capture=True) | ||||||
| return bool(res.stdout.strip()) | ||||||
|
|
||||||
|
|
||||||
| def container_exists(name): | ||||||
| res = run_cmd( | ||||||
| ["docker", "ps", "-aq", "-f", f"name=^{name}$"], | ||||||
| check=False, | ||||||
| capture=True, | ||||||
| ) | ||||||
| return bool(res.stdout.strip()) | ||||||
|
|
||||||
|
|
||||||
| def get_target_dir(target): | ||||||
| return SCRIPT_DIR / target | ||||||
|
|
||||||
|
|
||||||
| def base_context_dir(dest): | ||||||
| return Path(dest).resolve() / "manylinux-base" | ||||||
|
|
||||||
|
|
||||||
| def container_name(args, target): | ||||||
| return args.name or f"test-{target}" | ||||||
|
|
||||||
|
|
||||||
| def download(url, out, retries=3): | ||||||
| last_err = None | ||||||
| for attempt in range(1, retries + 1): | ||||||
| try: | ||||||
| with urllib.request.urlopen(url, timeout=60) as resp: | ||||||
| out.write_bytes(resp.read()) | ||||||
| return | ||||||
| except Exception as err: | ||||||
| last_err = err | ||||||
| log(f"attempt {attempt}/{retries} failed: {err}") | ||||||
| sys.exit(f"error: failed to download {url}: {last_err}") | ||||||
|
|
||||||
|
|
||||||
| # Operations that this script can perform. | ||||||
| def cmd_pull(args): | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
How about these things are called with a more active voice? |
||||||
| ctx = base_context_dir(args.dest) | ||||||
| ctx.mkdir(parents=True, exist_ok=True) | ||||||
|
|
||||||
| log(f"Downloading base build context into {ctx}") | ||||||
| for name in BASE_FILES: | ||||||
| out = ctx / name | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the |
||||||
| url = f"{THEROCK_LINK}/{name}" | ||||||
|
|
||||||
| log(f"fetch: {url}") | ||||||
| download(url, out) | ||||||
|
|
||||||
| if name.endswith(".sh"): | ||||||
| out.chmod(0o755) | ||||||
|
|
||||||
| log("Pull complete.") | ||||||
|
|
||||||
|
|
||||||
| def cmd_build(args): | ||||||
| require_docker() | ||||||
| target_dir = get_target_dir(args.target) | ||||||
| ctx = base_context_dir(args.dest) | ||||||
|
|
||||||
| if not image_exists(BASE_IMAGE): | ||||||
| dockerfile = ctx / BASE_DOCKERFILE | ||||||
| if not dockerfile.is_file(): | ||||||
| sys.exit( | ||||||
| f"error: {BASE_IMAGE} is missing and base files were not found " | ||||||
| f"in {ctx}.\n Run a pull first, e.g. " | ||||||
| f"python run.py {args.target} --pull --dest {args.dest}" | ||||||
| ) | ||||||
| log(f"Building base image {BASE_IMAGE}") | ||||||
|
|
||||||
| run_cmd([ | ||||||
| "docker", "build", | ||||||
| "-t", BASE_IMAGE, | ||||||
| "-f", str(dockerfile), | ||||||
| str(ctx), | ||||||
| ]) | ||||||
| else: | ||||||
| log(f"Base image {BASE_IMAGE} already present.") | ||||||
|
|
||||||
| image_tag = args.target | ||||||
| log(f"Building target image {image_tag}") | ||||||
| run_cmd([ | ||||||
| "docker", "build", | ||||||
| "-t", image_tag, | ||||||
| "-f", str(target_dir / "Dockerfile"), | ||||||
| str(target_dir), | ||||||
| ]) | ||||||
|
|
||||||
| name = container_name(args, args.target) | ||||||
| if container_exists(name): | ||||||
| log(f"Removing pre-existing container {name}") | ||||||
| run_cmd(["docker", "rm", "-f", name], check=False) | ||||||
| run_args = ["docker", "run", "-dit", "--network=host", "--name", name] | ||||||
|
|
||||||
| if not args.no_gpu: | ||||||
| run_args += GPU_RUN_FLAGS | ||||||
|
|
||||||
| if args.llvm_src: | ||||||
| src = Path(args.llvm_src).expanduser().resolve() | ||||||
| if not src.is_dir(): | ||||||
| sys.exit(f"error: --llvm-src path not found or not a directory: {src}") | ||||||
| run_args += ["-v", f"{src}:{LLVM_MOUNT_TARGET}"] | ||||||
|
|
||||||
| run_args += [image_tag, "sleep", "infinity"] | ||||||
|
|
||||||
| log(f"Starting container {name}") | ||||||
| run_cmd(run_args) | ||||||
| log("Build complete.") | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO this should be printed after build is complete and before attempting to start the container. If we want, we can printer another |
||||||
| log(f"Open a shell with: docker exec -it {name} bash") | ||||||
|
|
||||||
|
|
||||||
| def cmd_clean(args): | ||||||
| require_docker() | ||||||
| name = container_name(args, args.target) | ||||||
|
|
||||||
| log(f"Removing container {name}") | ||||||
| run_cmd(["docker", "rm", "-f", name], check=False) | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we check if the docker exists first? |
||||||
|
|
||||||
| log(f"Removing image {args.target}") | ||||||
| run_cmd(["docker", "rmi", args.target], check=False) | ||||||
|
|
||||||
| log(f"Removing base image {BASE_IMAGE}") | ||||||
| run_cmd(["docker", "rmi", BASE_IMAGE], check=False) | ||||||
|
|
||||||
| log("Clean complete.") | ||||||
|
|
||||||
|
|
||||||
| # CLI | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unnecessary |
||||||
| def build_parser(): | ||||||
| parser = argparse.ArgumentParser( | ||||||
| description="Helper to set up manylinux buildbot docker images.", | ||||||
| formatter_class=argparse.RawDescriptionHelpFormatter, | ||||||
| epilog=__doc__, | ||||||
| ) | ||||||
|
|
||||||
| parser.add_argument("target", choices=TARGETS, help="image type, e.g. manylinux-build-only") | ||||||
| parser.add_argument("--pull", action="store_true", | ||||||
| help="download base Dockerfile + helper scripts") | ||||||
| parser.add_argument("--build", action="store_true", | ||||||
| help="build images and create/run the container") | ||||||
| parser.add_argument("--clean", action="store_true", | ||||||
| help="remove the container and image(s)") | ||||||
| parser.add_argument("--dest", default=".", | ||||||
| help="base build-context location (default: current dir)") | ||||||
| parser.add_argument("--name", help="container name (default: test-<target>)") | ||||||
| parser.add_argument("--no-gpu", action="store_true", | ||||||
| help="skip GPU device/group run flags (build)") | ||||||
| parser.add_argument("--llvm-src", metavar="PATH", | ||||||
| help="mount a local LLVM source tree at " | ||||||
| "/home/botworker/bbot/llvm-project in the container") | ||||||
|
|
||||||
|
|
||||||
| return parser | ||||||
|
|
||||||
|
|
||||||
| def main(argv=None): | ||||||
| args = build_parser().parse_args(argv) | ||||||
|
|
||||||
| # By default, perform a pull + build. | ||||||
| if not (args.pull or args.build or args.clean): | ||||||
| args.pull = True | ||||||
| args.build = True | ||||||
|
|
||||||
| try: | ||||||
| if args.clean: | ||||||
| cmd_clean(args) | ||||||
| if args.pull: | ||||||
| cmd_pull(args) | ||||||
| if args.build: | ||||||
| cmd_build(args) | ||||||
| except subprocess.CalledProcessError as err: | ||||||
| sys.exit(f"error: step failed ({err}); aborting.") | ||||||
|
|
||||||
|
|
||||||
| if __name__ == "__main__": | ||||||
| main() | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it also rebuild the base image if the upstream base files changed?