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

313 lines
11 KiB
Python

#!/usr/bin/env python
# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""fileset_tool.py
Helper tool for manipulating filesets by listing matching files, copying,
archiving, etc. This is ultimately inspired by the fileset manipulation behavior
of Ant, which uses recursive glob include/exclude patterns rooted on some
base directory to manage artifact moving and packaging.
This is based on a limited form of the pathlib.Path pattern language introduced
in Python 3.13 (https://docs.python.org/3/library/pathlib.html#pattern-language)
with the following changes:
* It does not support character classes.
"""
from typing import Callable
import argparse
from pathlib import Path
import sys
from _therock_utils.archive_util import open_archive_for_write
from _therock_utils.artifacts import ArtifactPopulator
import _therock_utils.artifact_builder as artifact_builder
from _therock_utils.hash_util import calculate_hash, write_hash
from _therock_utils.os_util import rmtree_with_retry
from _therock_utils.pattern_match import PatternMatcher
def do_list(args: argparse.Namespace, pm: PatternMatcher):
for relpath, direntry in pm.matches():
print(relpath)
def do_copy(args: argparse.Namespace, pm: PatternMatcher):
verbose = args.verbose
destdir: Path = args.dest_dir
pm.copy_to(
destdir=destdir,
verbose=verbose,
always_copy=args.always_copy,
remove_dest=args.remove_dest,
)
def do_artifact(args):
"""Produces an 'artifact directory', which is a slice of installed stage/
directories, split into components (i.e. run, dev, dbg, doc, test).
"""
descriptor = artifact_builder.ArtifactDescriptor.load_toml_file(
args.descriptor, artifact_name=args.artifact_name
)
scanner = artifact_builder.ComponentScanner(args.root_dir, descriptor)
# Disable strict verification temporarily until debug builds are tested/fixed.
# scanner.verify()
component_dirs = args.component_dirs
# It is an alternating list of <component> <dir> so must be divisible by 2.
if len(component_dirs) % 2:
raise SystemExit(
"Expected component dirs to be alternating list of component names and directories"
)
for i in range(len(component_dirs) // 2):
component_name = component_dirs[i * 2]
output_dir = Path(component_dirs[i * 2 + 1])
# Setup output dir.
if output_dir.exists():
rmtree_with_retry(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
try:
contents = scanner.components[component_name]
except KeyError:
return
contents.write_artifact(output_dir)
def do_artifact_archive(args):
output_path: Path = args.o
if output_path.exists():
output_path.unlink()
output_path.parent.mkdir(parents=True, exist_ok=True)
with open_archive_for_write(
output_path, args.compression_type, args.compression_level
) as arc:
for artifact_path in args.artifact:
manifest_path: Path = artifact_path / "artifact_manifest.txt"
relpaths = manifest_path.read_text().splitlines()
# Important: The manifest must be stored first.
arc.add(manifest_path, arcname=manifest_path.name, recursive=False)
for relpath in relpaths:
if not relpath:
continue
source_dir = artifact_path / relpath
if not source_dir.exists():
continue
pm = PatternMatcher()
pm.add_basedir(source_dir)
for subpath, dir_entry in pm.all.items():
fullpath = f"{relpath}/{subpath}"
arc.add(dir_entry.path, arcname=fullpath, recursive=False)
if args.hash_file:
digest = calculate_hash(output_path, args.hash_algorithm)
write_hash(args.hash_file, digest)
def _do_artifact_flatten(args):
flattener = ArtifactPopulator(
output_path=args.o, verbose=args.verbose, flatten=True
)
flattener(*args.artifact)
relpaths = list(flattener.relpaths)
relpaths.sort()
if args.verbose:
for relpath in relpaths:
print(relpath)
def _do_artifact_flatten_split(args):
"""Flatten split artifacts discovered by globbing at build time.
Split artifacts produce directories like:
artifacts/miopen_lib_generic/
artifacts/miopen_lib_gfx1201/
The exact per-target names can't be predicted at CMake configure time
(xnack suffixes, clang defaults), so we discover them by globbing.
"""
artifacts_dir: Path = args.artifacts_dir
discovered_dirs: list[Path] = []
for prefix in args.prefixes:
for candidate in sorted(artifacts_dir.glob(f"{prefix}_*")):
if candidate.is_dir() and (candidate / "artifact_manifest.txt").exists():
discovered_dirs.append(candidate)
if not discovered_dirs:
print(
f"Warning: no split artifact dirs found in {artifacts_dir} "
f"for prefixes: {args.prefixes}"
)
return
if args.verbose:
print(f"Discovered {len(discovered_dirs)} split artifact dirs:")
for d in discovered_dirs:
print(f" {d.name}")
flattener = ArtifactPopulator(
output_path=args.o, verbose=args.verbose, flatten=True
)
flattener(*discovered_dirs)
relpaths = list(flattener.relpaths)
relpaths.sort()
if args.verbose:
for relpath in relpaths:
print(relpath)
def main(cl_args: list[str]):
def add_pattern_matcher_args(p: argparse.ArgumentParser):
p.add_argument("basedir", type=Path, nargs="*", help="Base directories to scan")
p.add_argument("--include", nargs="+", help="Recursive glob pattern to include")
p.add_argument("--exclude", nargs="+", help="Recursive glob pattern to exclude")
p.add_argument("--verbose", action="store_true", help="Print verbose status")
def pattern_matcher_action(
action: Callable[[argparse.Namespace, PatternMatcher], None],
):
def run_action(args: argparse.Namespace):
if not args.basedir:
# base dir is CWD
args.basedir = [Path.cwd()]
pm = PatternMatcher(args.include or [], args.exclude or [])
for basedir in args.basedir:
pm.add_basedir(basedir)
action(args, pm)
return run_action
p = argparse.ArgumentParser(
"fileset_tool.py", usage="fileset_tool.py {command} ..."
)
sub_p = p.add_subparsers(required=True)
# 'copy' command
copy_p = sub_p.add_parser("copy", help="Copy matching files to a destination dir")
copy_p.add_argument("dest_dir", type=Path, help="Destination directory")
copy_p.add_argument(
"--always-copy", action="store_true", help="Always copy vs attempting to link"
)
copy_p.add_argument(
"--remove-dest",
default=True,
action=argparse.BooleanOptionalAction,
help="Remove the destination directory before copying",
)
add_pattern_matcher_args(copy_p)
copy_p.set_defaults(func=pattern_matcher_action(do_copy))
# 'list' command
list_p = sub_p.add_parser("list", help="List matching files to stdout")
add_pattern_matcher_args(list_p)
list_p.set_defaults(func=pattern_matcher_action(do_list))
# 'artifact' command
artifact_p = sub_p.add_parser(
"artifact", help="Merge artifacts based on a descriptor"
)
artifact_p.add_argument(
"--root-dir",
type=Path,
required=True,
help="Source directory to which all descriptor directories are relative",
)
artifact_p.add_argument(
"--descriptor",
type=Path,
required=True,
help="TOML file describing the artifact",
)
artifact_p.add_argument(
"--artifact-name",
type=str,
required=True,
help="Name of the artifact (e.g., rccl, blas) for kpack pattern matching",
)
artifact_p.add_argument(
"component_dirs",
nargs="+",
help="Alternating list of component name and directory to write it to",
)
artifact_p.set_defaults(func=do_artifact)
# 'artifact-archive' command
artifact_archive_p = sub_p.add_parser(
"artifact-archive",
help="Creates an archive file from one or more artifact directories",
)
artifact_archive_p.add_argument(
"artifact", nargs="+", type=Path, help="Artifact directory"
)
artifact_archive_p.add_argument(
"-o", type=Path, required=True, help="Output archive name"
)
artifact_archive_p.add_argument(
"--compression-type",
choices=["zstd", "xz"],
default="xz",
help="Compression algorithm (default: xz)",
)
artifact_archive_p.add_argument(
"--compression-level",
type=int,
default=None,
help="Compression level (default: 3 for zstd, 6 for xz)",
)
artifact_archive_p.add_argument(
"--hash-file",
type=Path,
help="Hash file to write representing the archive contents",
)
artifact_archive_p.add_argument(
"--hash-algorithm", default="sha256", help="Hash algorithm"
)
artifact_archive_p.set_defaults(func=do_artifact_archive)
# 'artifact-flatten' command
artifact_flatten_p = sub_p.add_parser(
"artifact-flatten",
help="Flattens one or more artifact directories into one output directory",
)
artifact_flatten_p.add_argument(
"artifact", nargs="+", type=Path, help="Artifact directory"
)
artifact_flatten_p.add_argument(
"-o", type=Path, required=True, help="Output archive name"
)
artifact_flatten_p.add_argument(
"--verbose", action="store_true", help="Print verbose status"
)
artifact_flatten_p.set_defaults(func=_do_artifact_flatten)
# 'artifact-flatten-split' command
artifact_flatten_split_p = sub_p.add_parser(
"artifact-flatten-split",
help="Flatten split artifact directories discovered by globbing",
)
artifact_flatten_split_p.add_argument(
"prefixes",
nargs="+",
help="Artifact prefixes to match (e.g., miopen_lib miopen_dev)",
)
artifact_flatten_split_p.add_argument(
"-o", type=Path, required=True, help="Output directory"
)
artifact_flatten_split_p.add_argument(
"--artifacts-dir",
type=Path,
required=True,
help="Base directory containing split artifact dirs",
)
artifact_flatten_split_p.add_argument(
"--verbose", action="store_true", help="Print verbose status"
)
artifact_flatten_split_p.set_defaults(func=_do_artifact_flatten_split)
args = p.parse_args(cl_args)
args.func(args)
if __name__ == "__main__":
main(sys.argv[1:])