This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new daefffcace [CI][REFACTOR] Decouple data.py from Jenkins script and
docker images (#19445)
daefffcace is described below
commit daefffcace85bdba605dce411663a39c798219d7
Author: Tianqi Chen <[email protected]>
AuthorDate: Sat Apr 25 19:22:27 2026 -0400
[CI][REFACTOR] Decouple data.py from Jenkins script and docker images
(#19445)
## Summary
`ci/jenkins/data.py` was carrying two unrelated concerns: artifact
bundle definitions for s3 staging, and the docker image tag registry.
This PR decouples both from the Jenkinsfile-generated code, making each
concern standalone and machine-readable without Python.
## Changes
### Part 1 — Bundle refactor (`10950ac192`)
- `ci/scripts/jenkins/s3.py` gains `--bundle <name>` (repeatable) that
resolves bundle names from `ci/jenkins/data.py::files_to_stash` at
runtime; `--items` preserved for back-compat
- `ci/jenkins/templates/utils/macros.j2` `upload_artifacts` macro emits
`--bundle <name>` flags instead of inlining the file list as `--items`
- Template callsites (cpu, gpu, arm) updated to pass `bundles=[...]`
names
- Generated `.groovy` files regenerated — no `build/` paths appear in
artifact upload blocks; only bundle names
### Part 2 — Docker image registry extraction (`6df22c09b9`)
- New `ci/docker-images.ini` (one `[ci_*]` section per image) is the
single source of truth for image tags
- `docker/dev_common.sh::lookup_image_spec` reads the ini directly with
`awk` — no Python invocation
- `ci/jenkins/data.py` drops the inline `docker_images` dict; loads it
via `configparser` from the ini, preserving the same nested-dict shape
so Jinja templates and the current `s3.py` module import keep working
unchanged
- `data.py __main__` is now the bundle-resolver CLI (`python3
ci/jenkins/data.py <bundle> [...]` → file paths, one per line) — no more
dual-purpose image-name lookup branch
- `ci/scripts/jenkins/open_docker_update_pr.py` updated to read/write
`ci/docker-images.ini` instead of `data.py`
## Acceptance
```
# No build/ artifact paths in generated Jenkinsfiles (bundle names only):
grep -rn "build/lib\|build/cpptest" ci/jenkins/generated/ # → nothing
# generate.py is byte-identical after the change:
python3 ci/jenkins/generate.py # → "no changes made" for all 5 groovy files
# dev_common.sh resolves same tag as before:
bash -c 'source docker/dev_common.sh && lookup_image_spec ci_cpu'
# → tlcpack/ci-cpu:20251130-061900-c429a2b1
```
---
ci/jenkins/data.py | 70 +++++++++-----------------
ci/jenkins/docker-images.ini | 2 +-
ci/jenkins/generate.py | 34 ++++++++++++-
ci/jenkins/generated/arm_jenkinsfile.groovy | 4 +-
ci/jenkins/generated/cpu_jenkinsfile.groovy | 4 +-
ci/jenkins/generated/gpu_jenkinsfile.groovy | 6 +--
ci/jenkins/templates/arm_jenkinsfile.groovy.j2 | 2 +-
ci/jenkins/templates/cpu_jenkinsfile.groovy.j2 | 2 +-
ci/jenkins/templates/gpu_jenkinsfile.groovy.j2 | 4 +-
ci/jenkins/templates/utils/macros.j2 | 9 +++-
ci/scripts/jenkins/determine_docker_images.py | 22 ++++++++
ci/scripts/jenkins/open_docker_update_pr.py | 49 ++++++++++--------
ci/scripts/jenkins/s3.py | 47 +++++++++++++++--
docker/dev_common.sh | 3 +-
tests/python/ci/test_ci.py | 4 +-
15 files changed, 175 insertions(+), 87 deletions(-)
diff --git a/ci/jenkins/data.py b/ci/jenkins/data.py
index 699676ecab..b277643e03 100644
--- a/ci/jenkins/data.py
+++ b/ci/jenkins/data.py
@@ -15,6 +15,19 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
+"""Bundle registry for CI artifact stashing.
+
+Single source of truth for the file lists uploaded to / downloaded from S3 by
+``ci/scripts/jenkins/s3.py``. This module deliberately carries nothing else —
+docker image tags live in ``ci/jenkins/docker-images.ini`` and Jinja-template
+metadata (image platforms, AWS endpoints) lives in ``ci/jenkins/generate.py``.
+
+CLI: ``python3 ci/jenkins/data.py <bundle> [<bundle> ...]`` resolves bundle
+names to their file paths (one per line; exit 1 on unknown name). Used by
+``s3.py`` at Jenkins runtime and by any external caller that needs
+data-driven artifact lists.
+"""
+
import sys
files_to_stash = {
@@ -24,15 +37,16 @@ files_to_stash = {
"hexagon_api": [
"build/hexagon_api_output",
],
- # This library is produced with HIDE_PRIVATE_SYMBOLS=ON
- "tvm_allvisible": ["build/libtvm_allvisible.so"],
# runtime files
"tvm_runtime": ["build/libtvm_runtime.so", "build/config.cmake"],
- # compiler files
+ # compiler files (libtvm_allvisible is the HIDE_PRIVATE_SYMBOLS=ON
+ # variant cpptest links against; bundled here so every consumer of
+ # tvm_lib gets it without having to remember a second bundle name).
"tvm_lib": [
"build/libtvm.so",
"build/libtvm_runtime.so",
"build/lib/libtvm_ffi.so",
+ "build/libtvm_allvisible.so",
"build/config.cmake",
],
# gpu related compiler files
@@ -43,46 +57,12 @@ files_to_stash = {
}
-# AWS info
-aws_default_region = "us-west-2"
-aws_ecr_url = "dkr.ecr." + aws_default_region + ".amazonaws.com"
-
-# Docker Images
-docker_images = {
- "ci_arm": {
- "tag": "tlcpack/ci-arm:20251130-061900-c429a2b1",
- "platform": "ARM",
- },
- "ci_cpu": {
- "tag": "tlcpack/ci-cpu:20251130-061900-c429a2b1",
- "platform": "CPU",
- },
- "ci_gpu": {
- "tag": "tlcpack/ci-gpu:20251130-061900-c429a2b1",
- "platform": "GPU",
- },
- "ci_lint": {
- "tag": "tlcpack/ci-lint:20251130-061900-c429a2b1",
- "platform": "CPU",
- },
- "ci_wasm": {
- "tag": "tlcpack/ci-wasm:20251130-061900-c429a2b1",
- "platform": "CPU",
- },
-}
-
-data = {
- "images": [{"name": k, "platform": v["platform"]} for k, v in
docker_images.items()],
- "aws_default_region": aws_default_region,
- "aws_ecr_url": aws_ecr_url,
- **{k: v["tag"] for k, v in docker_images.items()},
- **files_to_stash,
-}
-
if __name__ == "__main__":
- # This is used in docker/dev_common.sh to look up image tags
- name = sys.argv[1]
- if name in docker_images:
- print(docker_images[name]["tag"])
- else:
- exit(1)
+ paths = []
+ for name in sys.argv[1:]:
+ if name not in files_to_stash:
+ print(f"unknown bundle: {name}", file=sys.stderr)
+ sys.exit(1)
+ paths.extend(files_to_stash[name])
+ for p in paths:
+ print(p)
diff --git a/ci/jenkins/docker-images.ini b/ci/jenkins/docker-images.ini
index 9e795227cd..13f8d841cf 100644
--- a/ci/jenkins/docker-images.ini
+++ b/ci/jenkins/docker-images.ini
@@ -19,7 +19,7 @@
[jenkins]
ci_tag: 20260301-134651-63f099ad
ci_arm: tlcpack/ci-arm:%(ci_tag)s
-ci_cpu: tlcpack/ci_cpu:%(ci_tag)s
+ci_cpu: tlcpack/ci-cpu:%(ci_tag)s
ci_gpu: tlcpack/ci-gpu:%(ci_tag)s
ci_lint: tlcpack/ci-lint:%(ci_tag)s
ci_wasm: tlcpack/ci-wasm:%(ci_tag)s
diff --git a/ci/jenkins/generate.py b/ci/jenkins/generate.py
index af9dfbc92f..fc6e09ab80 100644
--- a/ci/jenkins/generate.py
+++ b/ci/jenkins/generate.py
@@ -16,6 +16,7 @@
# specific language governing permissions and limitations
# under the License.
import argparse
+import configparser
import datetime
import difflib
import re
@@ -24,12 +25,43 @@ from dataclasses import dataclass
from pathlib import Path
import jinja2
-from data import data
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
JENKINS_DIR = REPO_ROOT / "ci" / "jenkins"
TEMPLATES_DIR = JENKINS_DIR / "templates"
GENERATED_DIR = JENKINS_DIR / "generated"
+DOCKER_IMAGES_INI = JENKINS_DIR / "docker-images.ini"
+
+# Platform mapping for the CI image registry. The ini section names are the
+# image names; this dict pins their Jenkins agent label. Keep in sync with
+# the [jenkins] section of docker-images.ini when adding/removing images.
+_IMAGE_PLATFORMS = {
+ "ci_arm": "ARM",
+ "ci_cpu": "CPU",
+ "ci_gpu": "GPU",
+ "ci_lint": "CPU",
+ "ci_wasm": "CPU",
+}
+
+
+def _build_render_context() -> dict:
+ """Build the Jinja render context: image metadata + AWS endpoints."""
+ config = configparser.ConfigParser()
+ config.read(DOCKER_IMAGES_INI)
+ images = [
+ {"name": name, "platform": platform}
+ for name, platform in _IMAGE_PLATFORMS.items()
+ if config.has_option("jenkins", name)
+ ]
+ aws_default_region = "us-west-2"
+ return {
+ "images": images,
+ "aws_default_region": aws_default_region,
+ "aws_ecr_url": f"dkr.ecr.{aws_default_region}.amazonaws.com",
+ }
+
+
+data = _build_render_context()
class Change:
diff --git a/ci/jenkins/generated/arm_jenkinsfile.groovy
b/ci/jenkins/generated/arm_jenkinsfile.groovy
index 17bddc9b2c..16e56f0ea2 100644
--- a/ci/jenkins/generated/arm_jenkinsfile.groovy
+++ b/ci/jenkins/generated/arm_jenkinsfile.groovy
@@ -60,7 +60,7 @@
// 'python3 jenkins/generate.py'
// Note: This timestamp is here to ensure that updates to the Jenkinsfile are
// always rebased on main before merging:
-// Generated at 2026-02-09T16:32:44.108985
+// Generated at 2026-04-25T16:28:24.516990
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
// These are set at runtime from data in ci/jenkins/docker-images.yml, update
@@ -496,7 +496,7 @@ def run_build(node_type) {
cmake_build(ci_arm, 'build')
make_cpp_tests(ci_arm, 'build')
sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/arm --items build/libtvm.so
build/libtvm_runtime.so build/lib/libtvm_ffi.so build/config.cmake
build/cpptest build/build.ninja build/CMakeFiles/rules.ninja",
+ script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/arm --bundle tvm_lib --bundle cpptest",
label: 'Upload artifacts to S3',
)
})
diff --git a/ci/jenkins/generated/cpu_jenkinsfile.groovy
b/ci/jenkins/generated/cpu_jenkinsfile.groovy
index fb9edab77b..d98a24631d 100644
--- a/ci/jenkins/generated/cpu_jenkinsfile.groovy
+++ b/ci/jenkins/generated/cpu_jenkinsfile.groovy
@@ -60,7 +60,7 @@
// 'python3 jenkins/generate.py'
// Note: This timestamp is here to ensure that updates to the Jenkinsfile are
// always rebased on main before merging:
-// Generated at 2025-08-24T16:41:22.367054
+// Generated at 2026-04-25T16:52:16.785557
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
// These are set at runtime from data in ci/jenkins/docker-images.yml, update
@@ -496,7 +496,7 @@ def run_build(node_type) {
cmake_build(ci_cpu, 'build')
make_cpp_tests(ci_cpu, 'build')
sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/cpu --items build/libtvm.so
build/libtvm_runtime.so build/lib/libtvm_ffi.so build/config.cmake
build/libtvm_allvisible.so build/cpptest build/build.ninja
build/CMakeFiles/rules.ninja",
+ script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/cpu --bundle tvm_lib --bundle cpptest",
label: 'Upload artifacts to S3',
)
})
diff --git a/ci/jenkins/generated/gpu_jenkinsfile.groovy
b/ci/jenkins/generated/gpu_jenkinsfile.groovy
index 45f5604727..72c9c09081 100644
--- a/ci/jenkins/generated/gpu_jenkinsfile.groovy
+++ b/ci/jenkins/generated/gpu_jenkinsfile.groovy
@@ -60,7 +60,7 @@
// 'python3 jenkins/generate.py'
// Note: This timestamp is here to ensure that updates to the Jenkinsfile are
// always rebased on main before merging:
-// Generated at 2026-02-09T16:32:44.095534
+// Generated at 2026-04-25T16:52:16.819825
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
// These are set at runtime from data in ci/jenkins/docker-images.yml, update
@@ -492,7 +492,7 @@ def run_build(node_type) {
sh "${docker_run} --no-gpu ${ci_gpu}
./tests/scripts/task_config_build_gpu.sh build"
cmake_build("${ci_gpu} --no-gpu", 'build')
sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/gpu --items build/libtvm.so
build/libtvm_runtime.so build/lib/libtvm_ffi.so build/config.cmake
build/libtvm_allvisible.so build/3rdparty/libflash_attn/src/libflash_attn.so
build/3rdparty/cutlass_fpA_intB_gemm/cutlass_kernels/libfpA_intB_gemm.so",
+ script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/gpu --bundle tvm_lib --bundle
tvm_lib_gpu_extra",
label: 'Upload artifacts to S3',
)
@@ -502,7 +502,7 @@ def run_build(node_type) {
sh "${docker_run} --no-gpu ${ci_gpu}
./tests/scripts/task_config_build_gpu_other.sh build"
cmake_build("${ci_gpu} --no-gpu", 'build')
sh(
- script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/gpu2 --items build/libtvm.so
build/libtvm_runtime.so build/lib/libtvm_ffi.so build/config.cmake",
+ script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/gpu2 --bundle tvm_lib",
label: 'Upload artifacts to S3',
)
})
diff --git a/ci/jenkins/templates/arm_jenkinsfile.groovy.j2
b/ci/jenkins/templates/arm_jenkinsfile.groovy.j2
index 35aa9bf250..2bddbab4c7 100644
--- a/ci/jenkins/templates/arm_jenkinsfile.groovy.j2
+++ b/ci/jenkins/templates/arm_jenkinsfile.groovy.j2
@@ -31,5 +31,5 @@
)
cmake_build(ci_arm, 'build')
make_cpp_tests(ci_arm, 'build')
- {{ m.upload_artifacts(tag='arm', filenames=tvm_lib + cpptest) }}
+ {{ m.upload_artifacts(tag='arm', bundles=["tvm_lib", "cpptest"]) }}
{% endcall %}
diff --git a/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2
b/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2
index 367da73ebe..d2e479d5e8 100644
--- a/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2
+++ b/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2
@@ -31,7 +31,7 @@
)
cmake_build(ci_cpu, 'build')
make_cpp_tests(ci_cpu, 'build')
- {{ m.upload_artifacts(tag='cpu', filenames=tvm_lib + tvm_allvisible +
cpptest) }}
+ {{ m.upload_artifacts(tag='cpu', bundles=["tvm_lib", "cpptest"]) }}
{% endcall %}
{% set test_method_names = [] %}
diff --git a/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2
b/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2
index 2769ae2c5d..7ab5256419 100644
--- a/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2
+++ b/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2
@@ -27,13 +27,13 @@
) %}
sh "${docker_run} --no-gpu ${ci_gpu}
./tests/scripts/task_config_build_gpu.sh build"
cmake_build("${ci_gpu} --no-gpu", 'build')
- {{ m.upload_artifacts(tag='gpu', filenames=tvm_lib + tvm_allvisible +
tvm_lib_gpu_extra) }}
+ {{ m.upload_artifacts(tag='gpu', bundles=["tvm_lib", "tvm_lib_gpu_extra"]) }}
// compiler test
sh "rm -rf build"
sh "${docker_run} --no-gpu ${ci_gpu}
./tests/scripts/task_config_build_gpu_other.sh build"
cmake_build("${ci_gpu} --no-gpu", 'build')
- {{ m.upload_artifacts(tag='gpu2', filenames=tvm_lib) }}
+ {{ m.upload_artifacts(tag='gpu2', bundles=["tvm_lib"]) }}
{% endcall %}
{% set test_method_names = [] %}
diff --git a/ci/jenkins/templates/utils/macros.j2
b/ci/jenkins/templates/utils/macros.j2
index c96432840d..b1bd3679ac 100644
--- a/ci/jenkins/templates/utils/macros.j2
+++ b/ci/jenkins/templates/utils/macros.j2
@@ -166,12 +166,19 @@ test()
},
{% endmacro %}
-{% macro upload_artifacts(action, tag, filenames) %}
+{% macro upload_artifacts(tag, bundles=none, filenames=none) %}
+{% if bundles is not none %}
+sh(
+ script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/{{ tag }}{% for b in bundles %} --bundle {{
b }}{% endfor %}",
+ label: 'Upload artifacts to S3',
+ )
+{% elif filenames is not none %}
{% set items = ' '.join(filenames) %}
sh(
script: "./${jenkins_scripts_root}/s3.py --action upload --bucket
${s3_bucket} --prefix ${s3_prefix}/{{ tag }} --items {{ items }}",
label: 'Upload artifacts to S3',
)
+{% endif %}
{% endmacro %}
{% macro download_artifacts(tag) %}
diff --git a/ci/scripts/jenkins/determine_docker_images.py
b/ci/scripts/jenkins/determine_docker_images.py
index fed3ab79cc..3f3deb5c57 100755
--- a/ci/scripts/jenkins/determine_docker_images.py
+++ b/ci/scripts/jenkins/determine_docker_images.py
@@ -70,11 +70,28 @@ def image_exists(spec: str) -> bool:
return False
+def lookup_image_tag(name: str) -> str:
+ """Resolve image ``name`` (e.g. ``ci_cpu``) to its tag string from the ini.
+
+ Pure ini read — no Docker Hub query, no tlcpackstaging fallback. Used by
+ ``docker/dev_common.sh`` for local-dev image-name shortcuts where the
+ full Hub-existence check is unnecessary.
+ """
+ config = configparser.ConfigParser()
+ config.read(IMAGE_TAGS_FILE)
+ return config.get("jenkins", name)
+
+
if __name__ == "__main__":
init_log()
parser = argparse.ArgumentParser(
description="Writes out Docker images names to be used to
.docker-image-names/"
)
+ parser.add_argument(
+ "--lookup-only",
+ metavar="NAME",
+ help="Print the tag for NAME from the ini and exit (no Docker Hub
query, no fallback).",
+ )
parser.add_argument(
"--testing-docker-data",
help="(testing only) JSON data to mock response from Docker Hub API",
@@ -89,6 +106,11 @@ if __name__ == "__main__":
help="(testing only) Folder to write image names to",
)
args, other = parser.parse_known_args()
+
+ if args.lookup_only:
+ print(lookup_image_tag(args.lookup_only))
+ raise SystemExit(0)
+
name_dir = Path(args.base_dir)
if args.testing_images_data:
diff --git a/ci/scripts/jenkins/open_docker_update_pr.py
b/ci/scripts/jenkins/open_docker_update_pr.py
index a948750082..0172fe0221 100755
--- a/ci/scripts/jenkins/open_docker_update_pr.py
+++ b/ci/scripts/jenkins/open_docker_update_pr.py
@@ -17,6 +17,7 @@
# under the License.
import argparse
+import configparser
import datetime
import json
import logging
@@ -32,7 +33,7 @@ from git_utils import GitHubRepo, git, parse_remote
from should_rebuild_docker import docker_api
JENKINS_DIR = REPO_ROOT / "ci" / "jenkins"
-IMAGES_FILE = JENKINS_DIR / "data.py"
+IMAGES_FILE = JENKINS_DIR / "docker-images.ini"
GENERATE_SCRIPT = JENKINS_DIR / "generate.py"
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
BRANCH = "nightly-docker-update"
@@ -127,33 +128,41 @@ if __name__ == "__main__":
remote = git(["config", "--get", f"remote.{args.remote}.url"])
user, repo = parse_remote(remote)
- # Read the existing images from the Jenkinsfile
+ # Read the existing images from ci/jenkins/docker-images.ini.
+ # The ini has a single ``[jenkins]`` section with a shared ``ci_tag`` key
+ # and one ``ci_<name>: tlcpack/ci-<name>:%(ci_tag)s`` entry per image.
+ # Resolve each image to its full spec (with interpolation applied) and
+ # check against Docker Hub for newer tags.
logging.info(f"Reading {IMAGES_FILE}")
+ config = configparser.ConfigParser()
+ config.read(IMAGES_FILE)
with open(IMAGES_FILE) as f:
- content = f.readlines()
+ content = f.read()
- # Build a new Jenkinsfile with the latest images from tlcpack or
tlcpackstaging
replacements = {}
-
- for line in content:
- m = re.match(r'"tag": "(.*)",', line.strip())
- if m is not None:
- image_spec = m.groups()[0]
- logging.info(f"Found match on line {line.strip()}")
- new_image = latest_tlcpackstaging_image(image_spec)
- if new_image is None:
- logging.info("No new image found")
- else:
- logging.info(f"Using new image {new_image}")
- new_line = f' "tag": "{new_image}",\n'
- replacements[line] = new_line
+ for key in config.options("jenkins"):
+ if key == "ci_tag":
+ continue
+ image_spec = config.get("jenkins", key)
+ logging.info(f"Found {key} = {image_spec}")
+ new_image = latest_tlcpackstaging_image(image_spec)
+ if new_image is None:
+ logging.info("No new image found")
+ continue
+ logging.info(f"Using new image {new_image}")
+ # Rewrite the ``ci_<name>:`` line with the resolved tag (breaking
+ # the ``%(ci_tag)s`` interpolation for that single entry) so the
+ # update is unambiguous and doesn't disturb other images that share
+ # the old tag.
+ old_line_re = re.compile(rf"^{re.escape(key)}\s*[:=].*$", re.MULTILINE)
+ new_line = f"{key}: {new_image}"
+ replacements[old_line_re] = new_line
# Re-generate the Jenkinsfiles
command = f"python3 {shlex.quote(str(GENERATE_SCRIPT))}"
- content = "".join(content)
- for old_line, new_line in replacements.items():
- content = content.replace(old_line, new_line)
+ for old_line_re, new_line in replacements.items():
+ content = old_line_re.sub(new_line, content)
print(f"Updated to:\n{content}")
diff --git a/ci/scripts/jenkins/s3.py b/ci/scripts/jenkins/s3.py
index dd44490b1e..eb986dec99 100755
--- a/ci/scripts/jenkins/s3.py
+++ b/ci/scripts/jenkins/s3.py
@@ -17,6 +17,7 @@
# under the License.
import argparse
+import importlib.util
import logging
import re
from enum import Enum
@@ -24,6 +25,30 @@ from pathlib import Path
from cmd_utils import REPO_ROOT, Sh, init_log
+DATA_PY = REPO_ROOT / "ci" / "jenkins" / "data.py"
+
+
+def load_files_to_stash():
+ """Load the files_to_stash dict from ci/jenkins/data.py."""
+ spec = importlib.util.spec_from_file_location("data", DATA_PY)
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod)
+ return mod.files_to_stash
+
+
+def resolve_bundles(bundle_names: list[str]) -> list[str]:
+ """Resolve a list of bundle names to a flat list of file paths."""
+ files_to_stash = load_files_to_stash()
+ items = []
+ for name in bundle_names:
+ if name not in files_to_stash:
+ known = list(files_to_stash.keys())
+ logging.error(f"Unknown bundle '{name}'. Known bundles: {known}")
+ raise SystemExit(1)
+ items.extend(files_to_stash[name])
+ return items
+
+
RETRY_SCRIPT = REPO_ROOT / "ci" / "scripts" / "jenkins" / "retry.sh"
S3_DOWNLOAD_REGEX = re.compile(r"download: s3://.* to (.*)")
SH = Sh()
@@ -93,8 +118,19 @@ if __name__ == "__main__":
"--prefix", help="s3 bucket + tag (e.g. s3://tvm-ci-prod/PR-1234/cpu",
required=True
)
parser.add_argument("--items", help="files and folders to upload",
nargs="+")
+ parser.add_argument(
+ "--bundle",
+ help="bundle name(s) from ci/jenkins/data.py files_to_stash
(repeatable)",
+ action="append",
+ dest="bundles",
+ metavar="NAME",
+ )
args = parser.parse_args()
+
+ if args.items is not None and args.bundles is not None:
+ parser.error("--items and --bundle are mutually exclusive")
+
logging.info(args)
sh = Sh()
@@ -115,17 +151,18 @@ if __name__ == "__main__":
logging.error(f"Unsupported action: {args.action}")
exit(1)
- if args.items is None:
+ if args.bundles is not None:
+ items = resolve_bundles(args.bundles)
+ elif args.items is not None:
+ items = args.items
+ else:
if args.action == "upload":
- logging.error("Cannot upload without --items")
+ logging.error("Cannot upload without --items or --bundle")
exit(1)
else:
# Download the whole prefix
items = ["."]
- else:
- items = args.items
-
for item in items:
if action == Action.DOWNLOAD:
source = s3_path
diff --git a/docker/dev_common.sh b/docker/dev_common.sh
index fd5a8f91bd..68799676a3 100755
--- a/docker/dev_common.sh
+++ b/docker/dev_common.sh
@@ -30,7 +30,8 @@ GIT_TOPLEVEL=$(cd $(dirname ${BASH_SOURCE[0]}) && git
rev-parse --show-toplevel)
DOCKER_IS_ROOTLESS=$(docker info 2> /dev/null | grep 'Context: \+rootless' ||
true)
function lookup_image_spec() {
- img_spec=$(python3 "${GIT_TOPLEVEL}/ci/jenkins/data.py" "$1")
+ img_spec=$(python3
"${GIT_TOPLEVEL}/ci/scripts/jenkins/determine_docker_images.py" \
+ --lookup-only "$1" 2>/dev/null)
if [ -n "${img_spec}" ]; then
has_similar_docker_image=1
docker inspect "${1}" &>/dev/null || has_similar_docker_image=0
diff --git a/tests/python/ci/test_ci.py b/tests/python/ci/test_ci.py
index 41b3f5d275..251143c430 100644
--- a/tests/python/ci/test_ci.py
+++ b/tests/python/ci/test_ci.py
@@ -1198,7 +1198,7 @@ def test_github_tag_teams(tmpdir_factory, source_type,
data, check):
},
expected="Using tlcpackstaging tag on tlcpack",
expected_images=[
- '"tag": "tlcpack/ci-arm:456-456-abc"',
+ "ci_arm: tlcpack/ci-arm:456-456-abc",
],
),
tlcpack_update=dict(
@@ -1220,7 +1220,7 @@ def test_github_tag_teams(tmpdir_factory, source_type,
data, check):
},
expected="Found newer image, using: tlcpack",
expected_images=[
- '"tag": "tlcpack/ci-arm:234-234-abc",',
+ "ci_arm: tlcpack/ci-arm:234-234-abc",
],
),
)