This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch jenkins-wheel-test-artifacts
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/jenkins-wheel-test-artifacts 
by this push:
     new 716688edb8 [CI] Keep wheel build bootstrap in a writable dependency 
overlay
716688edb8 is described below

commit 716688edb896e6ff1e9012fc5504ddab1a422310
Author: Tianqi Chen <[email protected]>
AuthorDate: Thu Oct 15 01:30:15 2026 +0000

    [CI] Keep wheel build bootstrap in a writable dependency overlay
---
 ci/jenkins/README.md                    |  8 ++++++--
 ci/jenkins/generated/common.groovy      |  7 ++++---
 tests/python/ci/test_wheel_artifacts.py | 32 +++++++++++++++++++++++++++-----
 tests/scripts/ci.py                     |  5 +++--
 4 files changed, 40 insertions(+), 12 deletions(-)

diff --git a/ci/jenkins/README.md b/ci/jenkins/README.md
index 0bf17757e2..4391f32ff1 100644
--- a/ci/jenkins/README.md
+++ b/ci/jenkins/README.md
@@ -106,8 +106,12 @@ the separate runtime-only compile check and Emscripten 
runtime build remain.
 
 Immediately before building, the same container and Python install the declared
 `pyproject.toml` build requirements (scikit-build-core, setuptools-scm and
-mlc-z3-static) without requesting upgrades to already-satisfying versions. A
-failed bootstrap stops the build. `uv build` then runs offline without 
isolation.
+mlc-z3-static) and their build dependencies. A
+failed bootstrap stops the build. Build requirements install into a writable 
local
+directory, which is added to `PYTHONPATH` only for the build command. Both 
commands
+select the original image Python, preserving its existing dependencies without
+modifying the image environment or exposing the build overlay to test 
consumers.
+`uv build` then runs offline without isolation.
 
 The selected images must already provide compatible runtime and test 
dependencies,
 including apache-tvm-ffi. Consumer wheel installation remains `--no-deps 
--offline`;
diff --git a/ci/jenkins/generated/common.groovy 
b/ci/jenkins/generated/common.groovy
index 422d806c09..68b7be3f92 100644
--- a/ci/jenkins/generated/common.groovy
+++ b/ci/jenkins/generated/common.groovy
@@ -315,11 +315,12 @@ def build_docker() {
 }
 
 def wheel_build(image, cpp_tests = false) {
-  // Bootstrap only the declared build requirements in the selected 
interpreter.
+  // Overlay build requirements without modifying the image Python environment.
   // The build itself stays offline; runtime dependencies belong to the image.
   def commands = build_environment() + '''
-    rm -rf build/wheels
-    uv pip install --python python3 "scikit-build-core>=0.11" 
"setuptools-scm>=8" "mlc-z3-static>=4.16.0"
+    rm -rf build/wheels build/wheel-build-deps
+    uv pip install --python python3 --target build/wheel-build-deps 
"scikit-build-core>=0.11" "setuptools-scm>=8" "mlc-z3-static>=4.16.0"
+    PYTHONPATH="$PWD/build/wheel-build-deps${PYTHONPATH:+:$PYTHONPATH}" \
     uv build --wheel --no-build-isolation --offline --python python3 --out-dir 
build/wheels . \
       -Cbuild-dir=build -Ccmake.build-type=RelWithDebInfo 
-Cbuild.verbose="${VERBOSE:-true}"
   '''.stripIndent().trim()
diff --git a/tests/python/ci/test_wheel_artifacts.py 
b/tests/python/ci/test_wheel_artifacts.py
index 3551bfab25..fa707f15d9 100644
--- a/tests/python/ci/test_wheel_artifacts.py
+++ b/tests/python/ci/test_wheel_artifacts.py
@@ -258,30 +258,52 @@ def test_build_bootstrap_precedes_offline_build(tmp_path, 
monkeypatch, bootstrap
     monkeypatch.setattr(ci, "docker", lambda **kwargs: calls.append(kwargs))
     command, _, _ = ci.generate_command("cpu", {}, "wheel build")
     command()
-    commands = [script for script in calls[0]["scripts"] if 
script.startswith("uv ")]
+    commands = [
+        script
+        for script in calls[0]["scripts"]
+        if "uv pip install" in script or "uv build" in script
+    ]
     bootstrap, build = commands
     arguments = shlex.split(bootstrap)
     # The declared build requirements are a flat string list; keep this test 
usable on Python 3.10.
     requires = re.search(r"(?ms)^requires = (\[.*?\])", (ROOT / 
"pyproject.toml").read_text())
     expected = ast.literal_eval(requires[1])
     assert arguments[:5] == ["uv", "pip", "install", "--python", "python3"]
-    assert arguments[5:] == expected
+    assert arguments[5] == "--target"
+    assert arguments[7:] == expected
     assert "--no-build-isolation --offline --python python3" in build
+    assert 
build.startswith(f'PYTHONPATH="$PWD/{arguments[6]}${{PYTHONPATH:+:$PYTHONPATH}}"
 ')
     binary = tmp_path / "uv"
     binary.write_text(
         f"#!{sys.executable}\n"
-        "import json, pathlib, sys\n"
+        "import json, os, pathlib, sys\n"
         "with pathlib.Path('calls').open('a') as out: 
out.write(json.dumps(sys.argv[1:]) + '\\n')\n"
+        "with pathlib.Path('paths').open('a') as out: 
out.write(os.environ['PYTHONPATH'] + '\\n')\n"
         f"sys.exit(17 if {bootstrap_fails!r} and sys.argv[1] == 'pip' else 
0)\n"
     )
     binary.chmod(0o755)
     result = subprocess.run(
-        [shutil.which("bash"), "-ec", "\n".join(commands)],
+        [
+            shutil.which("bash"),
+            "-ec",
+            "\n".join([*commands, 'printf "%s" "$PYTHONPATH" > after-path']),
+        ],
         cwd=tmp_path,
-        env={**os.environ, "PATH": f"{tmp_path}:{os.environ['PATH']}"},
+        env={
+            **os.environ,
+            "PATH": f"{tmp_path}:{os.environ['PATH']}",
+            "PYTHONPATH": "image-marker",
+        },
         capture_output=True,
         text=True,
     )
     assert result.returncode == (17 if bootstrap_fails else 0)
     recorded = [json.loads(line) for line in (tmp_path / 
"calls").read_text().splitlines()]
     assert [call[0] for call in recorded] == (["pip"] if bootstrap_fails else 
["pip", "build"])
+    paths = (tmp_path / "paths").read_text().splitlines()
+    assert paths[0] == "image-marker"
+    if not bootstrap_fails:
+        assert paths[1] == f"{tmp_path}/{arguments[6]}:image-marker"
+        assert (tmp_path / "after-path").read_text() == "image-marker"
+    else:
+        assert not (tmp_path / "after-path").exists()
diff --git a/tests/scripts/ci.py b/tests/scripts/ci.py
index 9d6423f9ff..5c49251f4f 100755
--- a/tests/scripts/ci.py
+++ b/tests/scripts/ci.py
@@ -402,8 +402,9 @@ def generate_command(
         else:
             scripts = [
                 f"./tests/scripts/task_config_build_{name}.sh {build_dir}",
-                f"rm -rf {build_dir}/wheels",
-                'uv pip install --python python3 "scikit-build-core>=0.11" 
"setuptools-scm>=8" "mlc-z3-static>=4.16.0"',
+                f"rm -rf {build_dir}/wheels {build_dir}/wheel-build-deps",
+                f'uv pip install --python python3 --target 
{build_dir}/wheel-build-deps "scikit-build-core>=0.11" "setuptools-scm>=8" 
"mlc-z3-static>=4.16.0"',
+                
f'PYTHONPATH="$PWD/{build_dir}/wheel-build-deps${{PYTHONPATH:+:$PYTHONPATH}}" '
                 f"uv build --wheel --no-build-isolation --offline --python 
python3 --out-dir {build_dir}/wheels . "
                 f"-Cbuild-dir={build_dir} -Ccmake.build-type=RelWithDebInfo "
                 f"-Cbuild.verbose={'true' if verbose else 'false'}"

Reply via email to