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 a6d7f3b274 [CI] Bootstrap declared build requirements before wheel
creation
a6d7f3b274 is described below
commit a6d7f3b2747c0d21a0607d82bd05b6793304776a
Author: Tianqi Chen <[email protected]>
AuthorDate: Thu Oct 15 00:37:45 2026 +0000
[CI] Bootstrap declared build requirements before wheel creation
Install missing build requirements in the selected Python environment
before the offline no-isolation wheel build. Keep installed-wheel consumers
offline without dependency installation and stop immediately when build setup
fails.
---
ci/jenkins/README.md | 17 +++++++-------
ci/jenkins/generated/common.groovy | 5 +++--
tests/python/ci/test_wheel_artifacts.py | 40 +++++++++++++++++++++++++++++++++
tests/scripts/ci.py | 1 +
4 files changed, 53 insertions(+), 10 deletions(-)
diff --git a/ci/jenkins/README.md b/ci/jenkins/README.md
index e3292c13a8..0bf17757e2 100644
--- a/ci/jenkins/README.md
+++ b/ci/jenkins/README.md
@@ -104,13 +104,14 @@ path overrides, and verify the package and native-library
paths. FFI comes from
the image. GPU Java tests also receive their existing native runtime libraries;
the separate runtime-only compile check and Emscripten runtime build remain.
-The selected images must already provide the requirements in `pyproject.toml`
-(including scikit-build-core, setuptools-scm and compatible apache-tvm-ffi),
the
-platform runtime dependencies, and the test dependencies. The pinned image must
-be checked before rollout: the current image recipe does not provision all
these
-wheel prerequisites. Missing dependencies are errors, not permission for a test
-stage to fetch or build replacements. No image publication or Jenkins
deployment
-is performed by this change.
+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.
+
+The selected images must already provide compatible runtime and test
dependencies,
+including apache-tvm-ffi. Consumer wheel installation remains `--no-deps
--offline`;
+missing runtime dependencies are errors rather than permission to fetch
replacements.
`CMAKE_BUILD_PARALLEL_LEVEL` is passed through Docker as an environment
variable.
An explicit value wins; otherwise builds use at least one CPU and divide the
@@ -123,7 +124,7 @@ Jenkins may select the old trusted-main Jenkinsfile while
checking out a newer
PR revision. Retain `tests/scripts/task_build.py`, the original raw-artifact
bundles, and the source-mode test interfaces until trusted main has migrated.
The native pipelines and local wheel commands explicitly pass
-`TVM_TEST_USE_WHEEL=1`; only that mode installs wheel artifacts without
fetching
+`TVM_TEST_USE_WHEEL=1`; only that mode installs wheel artifacts without
fetching runtime
dependencies. Calls without the flag retain the old source setup and dependency
installation behavior. A missing wheel in wheel mode is an error, not a
fallback
to source mode.
diff --git a/ci/jenkins/generated/common.groovy
b/ci/jenkins/generated/common.groovy
index 57c8e7eb57..422d806c09 100644
--- a/ci/jenkins/generated/common.groovy
+++ b/ci/jenkins/generated/common.groovy
@@ -315,10 +315,11 @@ def build_docker() {
}
def wheel_build(image, cpp_tests = false) {
- // uv build never resolves runtime dependencies. No isolation plus offline
uses
- // only the backend and FFI already provisioned in the image.
+ // Bootstrap only the declared build requirements in the selected
interpreter.
+ // 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"
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 ae0c8c5141..3551bfab25 100644
--- a/tests/python/ci/test_wheel_artifacts.py
+++ b/tests/python/ci/test_wheel_artifacts.py
@@ -16,9 +16,11 @@
# under the License.
"""Wheel artifact transport and local CI command contracts, without a TVM
import."""
+import ast
import importlib.util
import json
import os
+import re
import shutil
import subprocess
import sys
@@ -245,3 +247,41 @@ def
test_wheel_mode_does_not_fall_back_when_install_fails(tmp_path):
)
assert result.returncode == 37
assert "uv pip install" not in result.stderr
+
+
[email protected]("bootstrap_fails", [False, True])
+def test_build_bootstrap_precedes_offline_build(tmp_path, monkeypatch,
bootstrap_fails):
+ import shlex
+
+ ci = load_local_ci()
+ calls = []
+ 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 ")]
+ 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 "--no-build-isolation --offline --python python3" in build
+ binary = tmp_path / "uv"
+ binary.write_text(
+ f"#!{sys.executable}\n"
+ "import json, pathlib, sys\n"
+ "with pathlib.Path('calls').open('a') as out:
out.write(json.dumps(sys.argv[1:]) + '\\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)],
+ cwd=tmp_path,
+ env={**os.environ, "PATH": f"{tmp_path}:{os.environ['PATH']}"},
+ 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"])
diff --git a/tests/scripts/ci.py b/tests/scripts/ci.py
index a39abaff49..9d6423f9ff 100755
--- a/tests/scripts/ci.py
+++ b/tests/scripts/ci.py
@@ -403,6 +403,7 @@ def generate_command(
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"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'}"