You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tvm.apache.org by tq...@apache.org on 2026/10/15 20:07:59 UTC
(tvm) branch jenkins-wheel-test-artifacts updated (c78140df98 -> 61ec4c3bf1)
This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a change to branch jenkins-wheel-test-artifacts
in repository https://gitbox.apache.org/repos/asf/tvm.git
discard c78140df98 [CI] Build and consume source wheel artifacts across Jenkins platforms
new 61ec4c3bf1 [CI] Build and consume source wheel artifacts across Jenkins platforms
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (c78140df98)
\
N -- N -- N refs/heads/jenkins-wheel-test-artifacts (61ec4c3bf1)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
CMakeLists.txt | 9 +++++++++
1 file changed, 9 insertions(+)
(tvm) 01/01: [CI] Build and consume source wheel artifacts across Jenkins platforms
Posted by tq...@apache.org.
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
commit 61ec4c3bf1a3faea2a893d49ca90738147aed3fa
Author: Tianqi Chen <tq...@users.noreply.github.com>
AuthorDate: Thu Oct 15 18:15:51 2026 +0000
[CI] Build and consume source wheel artifacts across Jenkins platforms
---
CMakeLists.txt | 9 ++
ci/README.md | 10 ++
ci/jenkins/generated/arm_jenkinsfile.groovy | 31 ++--
ci/jenkins/generated/common.groovy | 23 ++-
ci/jenkins/generated/cpu_jenkinsfile.groovy | 35 ++---
ci/jenkins/generated/gpu_jenkinsfile.groovy | 23 ++-
ci/jenkins/generated/wasm_jenkinsfile.groovy | 32 +---
docker/bash.sh | 8 +
docs/conf.py | 5 +-
.../linux-x86_64/src/main/assembly/assembly.xml | 7 +-
.../osx-x86_64/src/main/assembly/assembly.xml | 7 +-
jvm/core/pom.xml | 2 +-
jvm/native/linux-x86_64/pom.xml | 6 +-
jvm/native/osx-x86_64/pom.xml | 6 +-
jvm/pom.xml | 4 +
pyproject.toml | 3 +
python/tvm/__init__.py | 32 ++--
python/tvm/config.py | 97 ++++++++++++
python/tvm/rpc/minrpc.py | 2 +
tests/python/runtime/rpc/test_minrpc_source.py | 51 +++++++
tests/python/runtime/test_config.py | 74 +++++++++
tests/scripts/ci.py | 85 ++++-------
tests/scripts/task_build.py | 143 -----------------
.../{task_web_wasm.sh => task_build_arm.sh} | 27 ++--
...{task_config_build_arm.sh => task_build_cpu.sh} | 28 ++--
...task_web_wasm.sh => task_build_dependencies.sh} | 24 +--
...config_build_gpu_other.sh => task_build_gpu.sh} | 32 ++--
...config_build_cpu.sh => task_build_gpu_other.sh} | 34 +++--
...onfig_build_gpu_other.sh => task_build_wasm.sh} | 23 ++-
tests/scripts/task_build_wheels.sh | 47 ++++++
tests/scripts/task_config_build_gpu.sh | 43 ------
...d_wasm.sh => task_install_docs_dependencies.sh} | 24 +--
tests/scripts/task_java_unittest.sh | 8 +-
tests/scripts/task_jvm_build.sh | 22 ++-
tests/scripts/task_python_docs.sh | 21 +--
tests/scripts/task_python_install_wheels.sh | 32 ++++
tests/scripts/task_python_unittest.sh | 26 ++--
tests/scripts/task_python_unittest_gpuonly.sh | 5 +-
tests/scripts/task_web_wasm.sh | 10 +-
tirx/pyproject.toml | 3 +
tirx/python/tvm_tirx/__init__.py | 169 ++++++++++++---------
tirx/python/tvm_tirx/config.py | 99 ++++++++++++
tirx/tests/python/runtime/test_tirx_config.py | 74 +++++++++
.../tests/scripts/task_build_arm.sh | 20 ++-
.../tests/scripts/task_build_cpu.sh | 26 ++--
.../tests/scripts/task_build_dependencies.sh | 27 ++--
.../tests/scripts/task_build_gpu.sh | 31 ++--
.../tests/scripts/task_build_wasm.sh | 21 ++-
tirx/tests/scripts/task_build_wheels.sh | 55 +++++++
49 files changed, 1012 insertions(+), 614 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4bd18b92a0..c24451877f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -294,6 +294,15 @@ install(TARGETS tvm_compiler tvm_runtime_extra
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR})
+foreach(_target fpA_intB_gemm flash_attn)
+ if(TARGET ${_target})
+ tvm_configure_target_library(${_target})
+ install(TARGETS ${_target}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR})
+ endif()
+endforeach()
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h")
# Parent minRPC compilation retains its own source and private header closure.
install(DIRECTORY src/runtime/rpc/ DESTINATION src/runtime/rpc FILES_MATCHING PATTERN "*.h" PATTERN "*.cc")
diff --git a/ci/README.md b/ci/README.md
index e617d7af91..a9e65f3622 100644
--- a/ci/README.md
+++ b/ci/README.md
@@ -93,3 +93,13 @@ Other jobs run in GitHub Actions for Windows and MacOS jobs.
The native Groovy entry points in `ci/jenkins/generated/` load shared helpers
from `common.groovy`. Edit these sources directly; no generation step is needed.
See comments in [common.groovy](jenkins/generated/common.groovy) for shared utility options and trust constraints.
+
+The native Jenkins pipelines call `tests/scripts/task_build_<platform>.sh`.
+Each root script invokes its counterpart under `tirx/tests/scripts/` to build
+pinned-source FFI and TIRx wheels, then builds TVM against that installed core. All three wheel artifacts share
+the checkout-root `dist/` directory. Platform options use direct CMake `-D` flags.
+C++ tests run in the native build that produced each wheel. Consumers install the
+three transferred wheels offline without dependencies into `.local/python` and
+use its absolute path as `PYTHONPATH`; `TVM_HOME` retains checkout resources.
+Groovy sets the cache, parallelism and C/C++ compiler-launcher environment values;
+`docker/bash.sh` forwards them to the build scripts.
diff --git a/ci/jenkins/generated/arm_jenkinsfile.groovy b/ci/jenkins/generated/arm_jenkinsfile.groovy
index 1429442a03..cdfdac1d79 100644
--- a/ci/jenkins/generated/arm_jenkinsfile.groovy
+++ b/ci/jenkins/generated/arm_jenkinsfile.groovy
@@ -27,30 +27,19 @@ try {
ci = evaluate(helper_source)
ci.start()
+docker_run += ' --env TVM_TEST_USE_WHEEL=1'
+
stage('Build') {
ci.with_node(instance: 'ARM-GRAVITON3-SPOT', retry_instance: 'ARM-GRAVITON3',
name: 'BUILD: arm', folder: 'tvm/build-arm', variables: ['PLATFORM=arm']) {
ci.docker_init(images.ci_arm)
- sh(
- script: "${docker_run} ${images.ci_arm} ./tests/scripts/task_config_build_arm.sh build",
- label: 'Create ARM CMake config',
- )
- sh (
- script: "${docker_run} --env CI_NUM_EXECUTORS ${images.ci_arm} ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --build-dir build",
- label: 'Run CMake build',
- )
- sh (
- script: """
- set -eux
- ${docker_run} ${images.ci_arm} python3 ./tests/scripts/task_build.py \
- --sccache-bucket tvm-sccache-prod \
- --sccache-region us-west-2 \
- --cmake-target cpptest \
- --cmake-target tirx_cpptest \
- --build-dir build
- """,
- label: 'Make C++ tests',
- )
- ci.upload_artifacts('arm', '--artifact tvm_lib --artifact cpptest')
+ withEnv(ci.build_environment()) {
+ sh(
+ script: "${docker_run} ${images.ci_arm} ./tests/scripts/task_build_arm.sh build 1",
+ label: 'Build arm wheels and C++ tests',
+ )
+ }
+ ci.upload_artifacts('arm', '--path "dist/*.whl"')
+
}
}
diff --git a/ci/jenkins/generated/common.groovy b/ci/jenkins/generated/common.groovy
index 1e9ce344be..e6be4ed05f 100644
--- a/ci/jenkins/generated/common.groovy
+++ b/ci/jenkins/generated/common.groovy
@@ -22,8 +22,19 @@ import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
image_platforms = [ci_arm: 'ARM', ci_cpu: 'CPU', ci_gpu: 'GPU', ci_wasm: 'CPU']
// Resolve Hub inputs through the caller account ECR cache; false keeps public names.
enable_docker_hub_cache = true
+// Preserve local caching when the container has no explicit AWS credentials.
+if (env.AWS_ACCESS_KEY_ID) {
+ env.SCCACHE_BUCKET = env.SCCACHE_BUCKET ?: 'tvm-sccache-prod'
+}
+env.SCCACHE_REGION = env.SCCACHE_REGION ?: 'us-west-2'
+// Images also expose sccache as cc/c++; keep the compiler separate from its launcher.
+env.CC = env.CC ?: 'gcc'
+env.CXX = env.CXX ?: 'g++'
+env.CMAKE_C_COMPILER_LAUNCHER = env.CMAKE_C_COMPILER_LAUNCHER ?: 'sccache'
+env.CMAKE_CXX_COMPILER_LAUNCHER = env.CMAKE_CXX_COMPILER_LAUNCHER ?: 'sccache'
max_time = 180
-docker_run = 'docker/bash.sh --env CI --env PLATFORM --env TEST_STEP_NAME'
+docker_run = 'docker/bash.sh --env CI --env PLATFORM --env TEST_STEP_NAME' +
+ ' --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY --env AWS_SESSION_TOKEN'
docker_build = 'docker/build.sh'
s3_bucket = 'tvm-jenkins-artifacts-prod'
jenkins_scripts_root = 'ci/jenkins/scripts'
@@ -388,4 +399,14 @@ def start() {
}
}
+// Preserve caller limits, otherwise share the node among its executors.
+def build_environment() {
+ def parallel_limit = env.CMAKE_BUILD_PARALLEL_LEVEL ?: sh(
+ script: 'n=$(( $(getconf _NPROCESSORS_ONLN) / ${CI_NUM_EXECUTORS:-1} )); if [ "$n" -lt 1 ]; then n=1; fi; echo "$n"',
+ returnStdout: true,
+ label: 'Determine build parallelism',
+ ).trim()
+ return ["CMAKE_BUILD_PARALLEL_LEVEL=${parallel_limit}"]
+}
+
return this
diff --git a/ci/jenkins/generated/cpu_jenkinsfile.groovy b/ci/jenkins/generated/cpu_jenkinsfile.groovy
index 44aceb116d..91a8510a63 100644
--- a/ci/jenkins/generated/cpu_jenkinsfile.groovy
+++ b/ci/jenkins/generated/cpu_jenkinsfile.groovy
@@ -27,31 +27,20 @@ try {
ci = evaluate(helper_source)
ci.start()
+docker_run += ' --env TVM_TEST_USE_WHEEL=1'
+
stage('Build') {
ci.with_node(instance: 'CPU-SPOT', retry_instance: 'CPU',
name: 'BUILD: CPU', folder: 'tvm/build-cpu', variables: ['PLATFORM=cpu']) {
ci.docker_init(images.ci_cpu)
- sh(
- script: "${docker_run} ${images.ci_cpu} ./tests/scripts/task_config_build_cpu.sh build",
- label: 'Create CPU CMake config',
- )
- sh (
- script: "${docker_run} --env CI_NUM_EXECUTORS ${images.ci_cpu} ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --build-dir build",
- label: 'Run CMake build',
- )
- sh (
- script: """
- set -eux
- ${docker_run} ${images.ci_cpu} python3 ./tests/scripts/task_build.py \
- --sccache-bucket tvm-sccache-prod \
- --sccache-region us-west-2 \
- --cmake-target cpptest \
- --cmake-target tirx_cpptest \
- --build-dir build
- """,
- label: 'Make C++ tests',
- )
- ci.upload_artifacts('cpu', '--artifact tvm_lib --artifact cpptest')
+ withEnv(ci.build_environment()) {
+ sh(
+ script: "${docker_run} ${images.ci_cpu} ./tests/scripts/task_build_cpu.sh build 1",
+ label: 'Build cpu wheels and C++ tests',
+ )
+ }
+ ci.upload_artifacts('cpu', '--path "dist/*.whl"')
+
}
}
@@ -62,10 +51,6 @@ stage('Test') {
variables: ['PLATFORM=cpu', 'TEST_STEP_NAME=unittest: CPU']) {
ci.docker_init(images.ci_cpu)
ci.download_artifacts('cpu')
- sh (
- script: "${docker_run} --env CI_NUM_EXECUTORS ${images.ci_cpu} ./tests/scripts/task_cpp_unittest.sh",
- label: 'Run C++ tests',
- )
sh (
script: "${docker_run} ${images.ci_cpu} ./tests/scripts/task_python_unittest.sh",
label: 'Run Python unit tests',
diff --git a/ci/jenkins/generated/gpu_jenkinsfile.groovy b/ci/jenkins/generated/gpu_jenkinsfile.groovy
index ac46fa4de1..391dd9384c 100644
--- a/ci/jenkins/generated/gpu_jenkinsfile.groovy
+++ b/ci/jenkins/generated/gpu_jenkinsfile.groovy
@@ -29,21 +29,28 @@ try {
ci = evaluate(helper_source)
ci.start()
+docker_run += ' --env TVM_TEST_USE_WHEEL=1'
+
stage('Build') {
ci.with_node(instance: 'CPU-SPOT', retry_instance: 'CPU',
name: 'BUILD: GPU', folder: 'tvm/build-gpu', variables: ['PLATFORM=gpu']) {
ci.docker_init(images.ci_gpu)
- sh "${docker_run} --no-gpu ${images.ci_gpu} ./tests/scripts/task_config_build_gpu.sh build"
- sh (
- script: "${docker_run} --env CI_NUM_EXECUTORS ${images.ci_gpu} --no-gpu ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --build-dir build",
- label: 'Run CMake build',
- )
- ci.upload_artifacts('gpu', '--artifact tvm_lib --artifact tvm_lib_gpu_extra')
+ withEnv(ci.build_environment()) {
+ sh(
+ script: "${docker_run} --no-gpu ${images.ci_gpu} ./tests/scripts/task_build_gpu.sh build 0",
+ label: 'Build gpu',
+ )
+ }
+ ci.upload_artifacts('gpu', '--path "dist/*.whl"')
// Check the compiler-only GPU configuration after publishing the test build.
sh 'rm -rf build'
- sh "${docker_run} --no-gpu ${images.ci_gpu} ./tests/scripts/task_config_build_gpu_other.sh build"
- sh "${docker_run} --env CI_NUM_EXECUTORS ${images.ci_gpu} --no-gpu ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --cmake-target tvm_runtime --build-dir build"
+ withEnv(ci.build_environment()) {
+ sh(
+ script: "${docker_run} --no-gpu ${images.ci_gpu} ./tests/scripts/task_build_gpu_other.sh build 0",
+ label: 'Build gpu_other',
+ )
+ }
}
}
diff --git a/ci/jenkins/generated/wasm_jenkinsfile.groovy b/ci/jenkins/generated/wasm_jenkinsfile.groovy
index 593da51094..681c92184d 100644
--- a/ci/jenkins/generated/wasm_jenkinsfile.groovy
+++ b/ci/jenkins/generated/wasm_jenkinsfile.groovy
@@ -27,34 +27,18 @@ try {
ci = evaluate(helper_source)
ci.start()
+docker_run += ' --env TVM_TEST_USE_WHEEL=1'
+
stage('Build') {
ci.with_node(instance: 'CPU-SPOT', retry_instance: 'CPU',
name: 'BUILD: WASM', folder: 'tvm/build-wasm', variables: ['PLATFORM=wasm']) {
ci.docker_init(images.ci_wasm)
- sh(
- script: "${docker_run} ${images.ci_wasm} ./tests/scripts/task_config_build_wasm.sh build",
- label: 'Create WASM CMake config',
- )
- sh (
- script: "${docker_run} --env CI_NUM_EXECUTORS ${images.ci_wasm} ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --build-dir build",
- label: 'Run CMake build',
- )
- sh (
- script: """
- set -eux
- ${docker_run} ${images.ci_wasm} python3 ./tests/scripts/task_build.py \
- --sccache-bucket tvm-sccache-prod \
- --sccache-region us-west-2 \
- --cmake-target cpptest \
- --cmake-target tirx_cpptest \
- --build-dir build
- """,
- label: 'Make C++ tests',
- )
- sh (
- script: "${docker_run} --env CI_NUM_EXECUTORS ${images.ci_wasm} ./tests/scripts/task_cpp_unittest.sh",
- label: 'Run C++ tests',
- )
+ withEnv(ci.build_environment()) {
+ sh(
+ script: "${docker_run} ${images.ci_wasm} ./tests/scripts/task_build_wasm.sh build 1",
+ label: 'Build wasm wheels and C++ tests',
+ )
+ }
sh(
script: "${docker_run} ${images.ci_wasm} ./tests/scripts/task_web_wasm.sh",
label: 'Run WASM lint and tests',
diff --git a/docker/bash.sh b/docker/bash.sh
index fe0b1eeb2b..639c002761 100755
--- a/docker/bash.sh
+++ b/docker/bash.sh
@@ -236,6 +236,14 @@ function validate_mount() {
break_joined_flag='if (( ${#1} == 2 )); then shift; else set -- -"${1#-i}" "${@:2}"; fi'
DOCKER_ENV=( )
+# Forward build settings selected by the caller without overriding explicit values.
+BUILD_ENV_VARS=(CC CXX SCCACHE_BUCKET SCCACHE_REGION CMAKE_BUILD_PARALLEL_LEVEL
+ CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER)
+for build_var in "${BUILD_ENV_VARS[@]}"; do
+ if [ "${!build_var+x}" = "x" ]; then
+ DOCKER_ENV+=( --env "$build_var=${!build_var}" )
+ fi
+done
DOCKER_FLAGS=( )
while (( $# )); do
diff --git a/docs/conf.py b/docs/conf.py
index 361356a95e..0031d0b501 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -50,8 +50,9 @@ if curr_path.name == "_staging":
else:
tvm_path = Path(os.pardir)
-sys.path.insert(0, str(tvm_path.resolve() / "python"))
-sys.path.insert(0, str(tvm_path.resolve() / "tirx" / "python"))
+if os.environ.get("TVM_DOCS_USE_WHEEL") != "1":
+ sys.path.insert(0, str(tvm_path.resolve() / "python"))
+ sys.path.insert(0, str(tvm_path.resolve() / "tirx" / "python"))
sys.path.insert(0, str(tvm_path.resolve() / "docs"))
# -- General configuration ------------------------------------------------
diff --git a/jvm/assembly/linux-x86_64/src/main/assembly/assembly.xml b/jvm/assembly/linux-x86_64/src/main/assembly/assembly.xml
index 7345c496e9..ea7396f8dc 100644
--- a/jvm/assembly/linux-x86_64/src/main/assembly/assembly.xml
+++ b/jvm/assembly/linux-x86_64/src/main/assembly/assembly.xml
@@ -26,7 +26,12 @@ under the License.
<includeBaseDirectory>false</includeBaseDirectory>
<files>
<file>
- <source>../../../build/lib/libtvm_runtime.so</source>
+ <source>${tvm.lib.dir}/libtvm_runtime.so</source>
+ <outputDirectory>lib/native</outputDirectory>
+ <fileMode>0644</fileMode>
+ </file>
+ <file>
+ <source>${tvm.ffi.lib.dir}/libtvm_ffi.so</source>
<outputDirectory>lib/native</outputDirectory>
<fileMode>0644</fileMode>
</file>
diff --git a/jvm/assembly/osx-x86_64/src/main/assembly/assembly.xml b/jvm/assembly/osx-x86_64/src/main/assembly/assembly.xml
index 739852e474..70f5754983 100644
--- a/jvm/assembly/osx-x86_64/src/main/assembly/assembly.xml
+++ b/jvm/assembly/osx-x86_64/src/main/assembly/assembly.xml
@@ -26,7 +26,12 @@ under the License.
<includeBaseDirectory>false</includeBaseDirectory>
<files>
<file>
- <source>../../../build/lib/libtvm_runtime.dylib</source>
+ <source>${tvm.lib.dir}/libtvm_runtime.dylib</source>
+ <outputDirectory>lib/native</outputDirectory>
+ <fileMode>0644</fileMode>
+ </file>
+ <file>
+ <source>${tvm.ffi.lib.dir}/libtvm_ffi.dylib</source>
<outputDirectory>lib/native</outputDirectory>
<fileMode>0644</fileMode>
</file>
diff --git a/jvm/core/pom.xml b/jvm/core/pom.xml
index eeb55f5cfd..26ba6dcd78 100644
--- a/jvm/core/pom.xml
+++ b/jvm/core/pom.xml
@@ -104,7 +104,7 @@ under the License.
<threadCount>1</threadCount>
<argLine>
-Djava.library.path=${project.parent.basedir}/native/${platform}/target
- -Dlibtvm.so.path=${project.parent.basedir}/../build/lib/${libtvm.so.filename}
+ -Dlibtvm.so.path=${tvm.lib.dir}/${libtvm.so.filename}
-Dlog4j.configuration=file://${project.parent.basedir}/conf/log4j.properties
</argLine>
</configuration>
diff --git a/jvm/native/linux-x86_64/pom.xml b/jvm/native/linux-x86_64/pom.xml
index 31e120bc58..dcda4c6ce1 100644
--- a/jvm/native/linux-x86_64/pom.xml
+++ b/jvm/native/linux-x86_64/pom.xml
@@ -117,8 +117,8 @@ under the License.
<compilerStartOption>-std=c++17</compilerStartOption>
</compilerStartOptions>
<compilerEndOptions>
- <compilerEndOption>-I../../../include</compilerEndOption>
- <compilerEndOption>-I../../../3rdparty/tvm-ffi/include</compilerEndOption>
+ <compilerEndOption>-I${tvm.include.dir}</compilerEndOption>
+ <compilerEndOption>-I${tvm.ffi.include.dir}</compilerEndOption>
<compilerEndOption>-I${JAVA_HOME}/include</compilerEndOption>
<compilerEndOption>-I${JAVA_HOME}/include/linux</compilerEndOption>
<compilerEndOption>${cflags}</compilerEndOption>
@@ -127,7 +127,7 @@ under the License.
<linkerStartOption>-shared</linkerStartOption>
</linkerStartOptions>
<linkerEndOptions>
- <linkerEndOption>-L${project.parent.basedir}/../../build/lib/</linkerEndOption>
+ <linkerEndOption>-L${tvm.lib.dir}</linkerEndOption>
<linkerEndOption>-ltvm_runtime</linkerEndOption>
<linkerEndOption>${ldflags}</linkerEndOption>
</linkerEndOptions>
diff --git a/jvm/native/osx-x86_64/pom.xml b/jvm/native/osx-x86_64/pom.xml
index 4f9d70d60d..ccb79f5bbb 100644
--- a/jvm/native/osx-x86_64/pom.xml
+++ b/jvm/native/osx-x86_64/pom.xml
@@ -118,8 +118,8 @@ under the License.
<compilerStartOption>-std=c++17</compilerStartOption>
</compilerStartOptions>
<compilerEndOptions>
- <compilerEndOption>-I../../../include</compilerEndOption>
- <compilerEndOption>-I../../../3rdparty/tvm-ffi/include</compilerEndOption>
+ <compilerEndOption>-I${tvm.include.dir}</compilerEndOption>
+ <compilerEndOption>-I${tvm.ffi.include.dir}</compilerEndOption>
<compilerEndOption>-I${JAVA_HOME}/include</compilerEndOption>
<compilerEndOption>-I${JAVA_HOME}/include/darwin</compilerEndOption>
<compilerEndOption>${cflags}</compilerEndOption>
@@ -134,7 +134,7 @@ under the License.
<linkerMiddleOption>-Wl,-x</linkerMiddleOption>
</linkerMiddleOptions>
<linkerEndOptions>
- <linkerEndOption>-L${project.parent.basedir}/../../build/lib/</linkerEndOption>
+ <linkerEndOption>-L${tvm.lib.dir}</linkerEndOption>
<linkerEndOption>-ltvm_runtime</linkerEndOption>
<linkerEndOption>${ldflags}</linkerEndOption>
</linkerEndOptions>
diff --git a/jvm/pom.xml b/jvm/pom.xml
index e5efce0bf5..2a2f53cd73 100644
--- a/jvm/pom.xml
+++ b/jvm/pom.xml
@@ -44,6 +44,10 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <tvm.include.dir>${maven.multiModuleProjectDirectory}/../include</tvm.include.dir>
+ <tvm.ffi.include.dir>${maven.multiModuleProjectDirectory}/../3rdparty/tvm-ffi/include</tvm.ffi.include.dir>
+ <tvm.lib.dir>${maven.multiModuleProjectDirectory}/../build/lib</tvm.lib.dir>
+ <tvm.ffi.lib.dir>${tvm.lib.dir}</tvm.ffi.lib.dir>
</properties>
<packaging>pom</packaging>
diff --git a/pyproject.toml b/pyproject.toml
index cd3a9547bf..3ff5135849 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -51,6 +51,9 @@ classifiers = [
]
dependencies = ["apache-tvm-tirx==0.1.0a0"]
+[project.scripts]
+tvm-config = "tvm.config:__main__"
+
[project.optional-dependencies]
torch = ["torch"]
cuda = ["cuda-bindings"]
diff --git a/python/tvm/__init__.py b/python/tvm/__init__.py
index 7320967ffb..8952ca68b3 100644
--- a/python/tvm/__init__.py
+++ b/python/tvm/__init__.py
@@ -18,13 +18,27 @@
# pylint: disable=redefined-builtin, wildcard-import
"""TVM compiler dialects and runtime extensions built on TIRx."""
-from .libinfo import __version__
-from .base import _RUNTIME_ONLY
-from . import runtime
-from . import rpc
+import sys
-if not _RUNTIME_ONLY:
- from . import te
- from . import s_tir
- from .driver import compile
- from . import relax
+from typing import TYPE_CHECKING
+
+
+def _is_config_mode() -> bool:
+ """Recognize the config CLI before loading Python or native TVM components."""
+ if sys.argv[0].endswith("tvm-config"):
+ return True
+ argv = getattr(sys, "orig_argv", [])
+ return any(arg == "-m" and argv[i + 1 : i + 2] == ["tvm.config"] for i, arg in enumerate(argv))
+
+
+if TYPE_CHECKING or not _is_config_mode():
+ from .libinfo import __version__
+ from .base import _RUNTIME_ONLY
+ from . import runtime
+ from . import rpc
+
+ if not _RUNTIME_ONLY:
+ from . import te
+ from . import s_tir
+ from .driver import compile
+ from . import relax
diff --git a/python/tvm/config.py b/python/tvm/config.py
new file mode 100644
index 0000000000..c76ef90006
--- /dev/null
+++ b/python/tvm/config.py
@@ -0,0 +1,97 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Lightweight compiler and linker configuration for the TVM runtime."""
+
+import argparse
+import os
+import sys
+from pathlib import Path
+
+
+def _runtime_root() -> Path:
+ """Locate this package without importing its native code."""
+ return Path(__file__).resolve().parent
+
+
+def _find_include() -> str:
+ package = _runtime_root()
+ for path in (package / "include", package.parent.parent / "include"):
+ if (path / "tvm" / "runtime").is_dir():
+ return str(path.resolve())
+ raise RuntimeError("Cannot find TVM runtime headers")
+
+
+def _library_paths() -> list[Path]:
+ package = _runtime_root()
+ source = package.parent.parent
+ paths = [package / "lib", source / "build" / "lib", source / "lib"]
+ if os.environ.get("TVM_LIBRARY_PATH"):
+ paths.insert(0, Path(os.environ["TVM_LIBRARY_PATH"]))
+ for name in ("LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", "PATH"):
+ paths.extend(Path(value) for value in os.environ.get(name, "").split(os.pathsep) if value)
+ return paths
+
+
+def _find_runtime() -> str:
+ filename = {"win32": "tvm_runtime_extra.dll", "darwin": "libtvm_runtime_extra.dylib"}.get(
+ sys.platform, "libtvm_runtime_extra.so"
+ )
+ for directory in _library_paths():
+ path = directory / filename
+ if path.is_file():
+ return str(path.resolve())
+ raise RuntimeError("Cannot find libtvm_runtime_extra")
+
+
+def __main__() -> None:
+ """Print paths and flags using the same conventions as tvm_ffi.config."""
+ parser = argparse.ArgumentParser(description="Get TVM runtime build configuration")
+ for name, help_text in (
+ ("includedir", "Print include directory"),
+ ("libdir", "Print library directory"),
+ ("libfiles", "Fully qualified library filenames"),
+ ("libs", "Libraries to be linked"),
+ ("cflags", "Print C flags"),
+ ("cxxflags", "Print C++ flags"),
+ ("ldflags", "Print linker flags"),
+ ):
+ parser.add_argument(f"--{name}", action="store_true", help=help_text)
+ args = parser.parse_args()
+ if len(sys.argv) == 1:
+ parser.print_help()
+ return
+ if args.includedir:
+ print(_find_include())
+ if args.libdir or args.libfiles or args.libs or args.ldflags:
+ library = Path(_find_runtime())
+ link_file = library.with_suffix(".lib") if sys.platform == "win32" else library
+ if args.libdir:
+ print(library.parent)
+ if args.libfiles:
+ print(link_file)
+ if args.cxxflags:
+ print(f"-I{_find_include()} -std=c++20")
+ if args.cflags:
+ print(f"-I{_find_include()}")
+ if args.libs:
+ print(link_file if sys.platform == "win32" else "-ltvm_runtime_extra")
+ if args.ldflags and sys.platform != "win32":
+ print(f"-L{library.parent}")
+
+
+if __name__ == "__main__":
+ __main__()
diff --git a/python/tvm/rpc/minrpc.py b/python/tvm/rpc/minrpc.py
index 5885f75005..2b5ba261a2 100644
--- a/python/tvm/rpc/minrpc.py
+++ b/python/tvm/rpc/minrpc.py
@@ -41,6 +41,8 @@ def find_minrpc_server_libpath(server="posix_popen_server"):
curr_dir = os.path.dirname(os.path.realpath(os.path.expanduser(__file__)))
source_dir = os.path.abspath(os.path.join(curr_dir, "..", "..", ".."))
roots = [os.path.join(curr_dir, ".."), source_dir]
+ if os.environ.get("TVM_HOME"):
+ roots.append(os.environ["TVM_HOME"])
candidates = []
for root in roots:
minrpc_dir = os.path.join(root, "src", "runtime", "rpc", "minrpc")
diff --git a/tests/python/runtime/rpc/test_minrpc_source.py b/tests/python/runtime/rpc/test_minrpc_source.py
new file mode 100644
index 0000000000..0512d1cfa5
--- /dev/null
+++ b/tests/python/runtime/rpc/test_minrpc_source.py
@@ -0,0 +1,51 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Find checkout-only minrpc sources while importing an installed TVM wheel."""
+
+from pathlib import Path
+
+import pytest
+
+from tvm import libinfo
+from tvm.rpc import minrpc
+
+
+@pytest.mark.parametrize("layout", ["source", "symlink", "wheel"])
+def test_minrpc_source_location(tmp_path, monkeypatch, layout):
+ checkout = tmp_path / "checkout"
+ minrpc_dir = checkout / "src/runtime/rpc/minrpc"
+ server = minrpc_dir / "posix_popen_server/posix_popen_server.cc"
+ server.parent.mkdir(parents=True)
+ server.write_text("// source fixture\n")
+ # A wheel's package location is unrelated to the source checkout.
+ package = tmp_path / "site-packages/tvm" if layout == "wheel" else checkout / "python/tvm"
+ if layout == "symlink":
+ package.mkdir(parents=True)
+ alias = tmp_path / "python-alias"
+ alias.symlink_to(checkout / "python", target_is_directory=True)
+ package = alias / "tvm"
+ monkeypatch.setattr(libinfo, "__file__", str(package / "libinfo.py"))
+ monkeypatch.setattr(minrpc, "__file__", str(package / "rpc/minrpc.py"))
+ monkeypatch.delenv("TVM_HOME", raising=False)
+ if layout == "wheel":
+ monkeypatch.setenv("TVM_HOME", str(checkout))
+ directory, path = minrpc.find_minrpc_server_libpath()
+ assert Path(directory) == minrpc_dir
+ assert Path(path) == server
+ server.unlink()
+ with pytest.raises(RuntimeError, match="Cannot find minserver"):
+ minrpc.find_minrpc_server_libpath()
diff --git a/tests/python/runtime/test_config.py b/tests/python/runtime/test_config.py
new file mode 100644
index 0000000000..c039e08bd2
--- /dev/null
+++ b/tests/python/runtime/test_config.py
@@ -0,0 +1,74 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""The config command must work without importing native runtime dependencies."""
+
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+import tvm
+from tvm import config
+
+
+def test_config_without_native_imports(tmp_path):
+ # A startup hook rejects native loads and dependencies even when they are installed.
+ (tmp_path / "sitecustomize.py").write_text(
+ "import sys\n"
+ "class BlockNative:\n"
+ " def find_spec(self, fullname, path=None, target=None):\n"
+ " if fullname.split('.')[0] in ('tvm_ffi', 'torch', 'numpy') or "
+ "fullname in ('tvm.base', 'tvm.runtime', 'tvm.backend', 'tvm.driver', 'tvm_tirx.base'):\n"
+ " raise AssertionError(fullname)\n"
+ "sys.meta_path.insert(0, BlockNative())\n"
+ "def audit(event, args):\n"
+ " if event == 'ctypes.dlopen': raise AssertionError(args)\n"
+ "sys.addaudithook(audit)\n"
+ )
+ env = dict(os.environ)
+ env["PYTHONPATH"] = os.pathsep.join(
+ (
+ str(tmp_path),
+ str(Path(tvm.__file__).parent.parent),
+ )
+ )
+ command = [sys.executable, "-m", "tvm.config"]
+ include = subprocess.check_output([*command, "--includedir"], env=env, text=True).strip()
+ assert (Path(include) / "tvm" / "runtime").is_dir()
+ library = subprocess.check_output([*command, "--libfiles"], env=env, text=True).strip()
+ assert Path(library).is_file()
+ flags = subprocess.check_output(
+ [*command, "--cflags", "--cxxflags", "--libs", "--ldflags"], env=env, text=True
+ )
+ assert f"-I{include}" in flags
+ assert "-std=c++20" in flags
+
+
+def test_runtime_library_path_fallback(tmp_path, monkeypatch):
+ library = tmp_path / Path(config._find_runtime()).name
+ library.touch()
+ monkeypatch.setattr(config, "_runtime_root", lambda: tmp_path / "tvm")
+ monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
+ monkeypatch.delenv("DYLD_LIBRARY_PATH", raising=False)
+ monkeypatch.setenv("PATH", str(tmp_path))
+ assert config._find_runtime() == str(library)
+
+
+if __name__ == "__main__":
+ import tvm_tirx.testing
+
+ tvm_tirx.testing.main()
diff --git a/tests/scripts/ci.py b/tests/scripts/ci.py
index fa027c150a..a9c71e05c1 100755
--- a/tests/scripts/ci.py
+++ b/tests/scripts/ci.py
@@ -165,27 +165,26 @@ def docker(
"""
check_docker()
- # As sccache is added to these images these can be uncommented
- sccache_images = {
- "ci_gpu",
- "ci_cpu",
- # "ci_wasm",
- "ci_cortexm",
- "ci_arm",
- "ci_riscv",
- "ci_adreno",
- }
-
- if image in sccache_images and os.getenv("USE_SCCACHE", "1") == "1":
- scripts = [
- "sccache --start-server",
- ] + scripts
- # Set the C/C++ compiler so CMake picks them up in the build
- env["CC"] = "/opt/sccache/cc"
- env["CXX"] = "/opt/sccache/c++"
+ env.setdefault("CC", os.getenv("CC", "gcc"))
+ env.setdefault("CXX", os.getenv("CXX", "g++"))
+ if os.getenv("USE_SCCACHE", "1") == "1":
+ env.setdefault(
+ "CMAKE_C_COMPILER_LAUNCHER", os.getenv("CMAKE_C_COMPILER_LAUNCHER", "sccache")
+ )
+ env.setdefault(
+ "CMAKE_CXX_COMPILER_LAUNCHER", os.getenv("CMAKE_CXX_COMPILER_LAUNCHER", "sccache")
+ )
env["SCCACHE_CACHE_SIZE"] = os.getenv("SCCACHE_CACHE_SIZE", "50G")
env["SCCACHE_SERVER_PORT"] = os.getenv("SCCACHE_SERVER_PORT", "4226")
+ # Pass the bound through Docker; CMake and scikit-build honor it directly.
+ env.setdefault(
+ "CMAKE_BUILD_PARALLEL_LEVEL",
+ os.environ.get(
+ "CMAKE_BUILD_PARALLEL_LEVEL",
+ str(max(NPROC // int(os.environ.get("CI_NUM_EXECUTORS", "1")), 1)),
+ ),
+ )
env["PLATFORM"] = name
docker_bash = REPO_ROOT / "docker" / "bash.sh"
@@ -258,42 +257,12 @@ def docs(
# TODO: Change this to tlcpack/docs once that is uploaded
image = "ci_cpu" if docker_image is None else docker_image
build_dir = get_build_dir("cpu")
- config_script = " && ".join(
- [
- f"mkdir -p {build_dir}",
- f"pushd {build_dir}",
- "cp ../cmake/config.cmake .",
- "popd",
- ]
- )
-
- # These are taken from the ci-gpu image via pip freeze, consult that
- # if there are any changes: https://github.com/apache/tvm/tree/main/docs#native
- requirements = [
- "Sphinx==8.1.3",
- "sphinx_autodoc_annotation~=1.0",
- "sphinx-gallery==0.20.0",
- "sphinx-book-theme==1.1.4",
- "pydata-sphinx-theme==0.15.4",
- "autodocsumm==0.2.14",
- "image==1.5.33",
- "matplotlib==3.10.8",
- "commonmark==0.9.1",
- "docutils==0.21.2",
- "Pillow==12.1.1",
- ]
-
- extra_setup = [
- "uv pip install " + " ".join(requirements),
- ]
+ extra_setup = ["./tests/scripts/task_install_docs_dependencies.sh"]
else:
check_gpu()
- config_script = f"./tests/scripts/task_config_build_gpu.sh {build_dir}"
- scripts = extra_setup + [
- config_script,
- f"./tests/scripts/task_build.py --build-dir {build_dir}",
- ]
+ platform_name = "cpu" if cpu else "gpu"
+ scripts = extra_setup + [f"./tests/scripts/task_build_{platform_name}.sh {build_dir}"]
if skip_build:
scripts = []
@@ -304,6 +273,7 @@ def docs(
tutorial_pattern = os.getenv("TVM_TUTORIAL_EXEC_PATTERN", ".py" if full else "none")
env = {
+ "TVM_TEST_USE_WHEEL": "1",
"TVM_TUTORIAL_EXEC_PATTERN": tutorial_pattern,
"PYTHON_DOCS_ONLY": "0" if full else "1",
"IS_LOCAL": "1",
@@ -401,10 +371,12 @@ def generate_command(
scripts = []
else:
scripts = [
- f"./tests/scripts/task_config_build_{name}.sh {build_dir}",
- f"./tests/scripts/task_build.py --build-dir {build_dir}",
+ f"./tests/scripts/task_build_{name}.sh {build_dir} {int(bool(kwargs.get('cpp')))}",
]
+ # Direct --tests and interactive Python use the same installed artifacts.
+ scripts.append("source ./tests/scripts/task_python_install_wheels.sh")
+
if post_build is not None:
scripts += post_build
@@ -422,10 +394,7 @@ def generate_command(
scripts.extend(script.format(build_dir=build_dir) for script in extra_scripts)
docker_env = {
- # Need to specify the library path manually or else TVM can't
- # determine which build directory to use (i.e. if there are
- # multiple copies of libtvm_compiler.so / libtvm_runtime.so laying around)
- "TVM_LIBRARY_PATH": str(REPO_ROOT / get_build_dir(name)),
+ "TVM_TEST_USE_WHEEL": "1",
"VERBOSE": "true" if verbose else "false",
}
@@ -578,7 +547,7 @@ def add_subparser(
return subparser
-CPP_UNITTEST = ("run c++ unitests", ["./tests/scripts/task_cpp_unittest.sh {build_dir}"])
+CPP_UNITTEST = ("run c++ unitests", ["{build_dir}/tirx-wheel/tirx_cpptest", "{build_dir}/cpptest"])
generated = [
generate_command(
diff --git a/tests/scripts/task_build.py b/tests/scripts/task_build.py
deleted file mode 100755
index f7bfd558e6..0000000000
--- a/tests/scripts/task_build.py
+++ /dev/null
@@ -1,143 +0,0 @@
-#!/usr/bin/env python3
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-import argparse
-import logging
-import multiprocessing
-import os
-import shutil
-import subprocess
-import sys
-from pathlib import Path
-
-REPO_ROOT = Path(__file__).resolve().parent.parent.parent
-
-if __name__ == "__main__":
- logging.basicConfig(
- format="[%(filename)s:%(lineno)d %(levelname)s] %(message)s", level=logging.INFO
- )
-
- parser = argparse.ArgumentParser(description="List pytest nodeids for a folder")
- parser.add_argument("--sccache-bucket", required=False, help="sccache bucket name")
- parser.add_argument("--sccache-region", required=False, help="sccache region")
- parser.add_argument("--build-dir", default="build", help="build folder")
- parser.add_argument(
- "--cmake-target", action="append", default=[], help="build target (repeatable)"
- )
- parser.add_argument(
- "--ffi-only", action="store_true", help="prepare the source-pinned FFI package"
- )
- parser.add_argument("--debug", required=False, action="store_true", help="build in debug mode")
- args = parser.parse_args()
- sccache_exe = shutil.which("sccache")
-
- if args.cmake_target and all(
- target in {"standalone_crt", "crttest"} for target in args.cmake_target
- ):
- logging.info("Skipping standalone_crt build")
- exit(0)
-
- use_sccache = sccache_exe is not None
- build_dir = Path(os.getcwd()) / args.build_dir
- build_dir = build_dir.relative_to(REPO_ROOT)
- env = os.environ.copy()
- env["PYTHONPATH"] = os.pathsep.join(
- [
- str(REPO_ROOT / "tirx" / "python"),
- str(REPO_ROOT / "python"),
- os.environ.get("PYTHONPATH", ""),
- ]
- )
-
- if use_sccache:
- if args.sccache_bucket and "AWS_ACCESS_KEY_ID" in os.environ:
- env["SCCACHE_BUCKET"] = args.sccache_bucket
- env["SCCACHE_REGION"] = args.sccache_region if args.sccache_region else "us-west-2"
- logging.info(f"Using sccache bucket: {args.sccache_bucket}")
- logging.info(f"Using sccache region: {env['SCCACHE_REGION']}")
- else:
- logging.info("No sccache bucket set, using local cache")
- if "SCCACHE_SERVER_PORT" in os.environ:
- env["SCCACHE_SERVER_PORT"] = os.getenv("SCCACHE_SERVER_PORT")
- env["CXX"] = "/opt/sccache/c++"
- env["CC"] = "/opt/sccache/cc"
-
- else:
- if sccache_exe is None:
- reason = "'sccache' executable not found"
- else:
- reason = "<unknown>"
- logging.info(f"Not using sccache, reason: {reason}")
-
- # Build from this checkout's pinned submodule once, then transfer the exact
- # Python/native package together with the TVM libraries to test workers.
- ffi_commit = subprocess.check_output(
- ["git", "-C", str(REPO_ROOT / "3rdparty" / "tvm-ffi"), "rev-parse", "HEAD"], text=True
- ).strip()
- ffi_identity = f"{ffi_commit} {sys.implementation.cache_tag}"
- ffi_marker = REPO_ROOT / "python" / "tvm_ffi" / ".source-commit"
- if not ffi_marker.is_file() or ffi_marker.read_text().strip() != ffi_identity:
- subprocess.run(
- [
- "uv",
- "pip",
- "install",
- "--python",
- sys.executable,
- "-v",
- "--no-deps",
- "--reinstall",
- "--target=python",
- "./3rdparty/tvm-ffi/",
- ],
- cwd=REPO_ROOT,
- env=env,
- check=True,
- )
- ffi_marker.write_text(ffi_identity + "\n")
- if args.ffi_only:
- sys.exit(0)
-
- if use_sccache:
- subprocess.run(["sccache", "--start-server"], env=env, check=False)
- logging.info("===== sccache stats =====")
- subprocess.run(["sccache", "--show-stats"], env=env, check=True)
-
- executors = int(os.environ.get("CI_NUM_EXECUTORS", 1))
- build_platform = os.environ.get("PLATFORM", None)
-
- nproc = multiprocessing.cpu_count()
-
- available_cpus = nproc // executors
- num_cpus = max(available_cpus, 1)
-
- build_type = "Debug" if args.debug else "RelWithDebInfo"
- subprocess.run(
- ["cmake", "-GNinja", f"-DCMAKE_BUILD_TYPE={build_type}", ".."],
- cwd=build_dir,
- env=env,
- check=True,
- )
-
- ninja_args = [*args.cmake_target, f"-j{num_cpus}"]
- if os.environ.get("VERBOSE", "true").lower() in {"1", "true", "yes"}:
- ninja_args.append("-v")
- subprocess.run(["cmake", "--build", ".", "--", *ninja_args], cwd=build_dir, env=env, check=True)
-
- if use_sccache:
- logging.info("===== sccache stats =====")
- subprocess.run(["sccache", "--show-stats"], env=env, check=True)
diff --git a/tests/scripts/task_web_wasm.sh b/tests/scripts/task_build_arm.sh
similarity index 69%
copy from tests/scripts/task_web_wasm.sh
copy to tests/scripts/task_build_arm.sh
index aed91a371b..c82dc36698 100755
--- a/tests/scripts/task_web_wasm.sh
+++ b/tests/scripts/task_build_arm.sh
@@ -18,19 +18,14 @@
set -euxo pipefail
-export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
-
-# setup tvm-ffi into python folder
-uv pip install -v --target=python ./3rdparty/tvm-ffi/
-
-rm -rf .emscripten_cache
-cd web
-export EM_CACHE=`pwd`/.emscripten_cache
-make clean
-npm install
-npm run lint
-npm run prepwasm
-npm run bundle
-npm run test
-npm run typedoc
-cd ..
+ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOTDIR"
+BUILD_DIR="${1:-build}"
+CPP_TESTS="${2:-0}"
+./tirx/tests/scripts/task_build_arm.sh "$BUILD_DIR" "$CPP_TESTS"
+exec ./tests/scripts/task_build_wheels.sh "$BUILD_DIR" "$CPP_TESTS" \
+ "-DUSE_SORT=ON" \
+ "-DUSE_RPC=ON" \
+ "-DCMAKE_CXX_FLAGS=-Werror" \
+ "-DUSE_CCACHE=OFF" \
+ "-DSUMMARIZE=ON"
diff --git a/tests/scripts/task_config_build_arm.sh b/tests/scripts/task_build_cpu.sh
similarity index 56%
copy from tests/scripts/task_config_build_arm.sh
copy to tests/scripts/task_build_cpu.sh
index b58b21db34..ec95812cb1 100755
--- a/tests/scripts/task_config_build_arm.sh
+++ b/tests/scripts/task_build_cpu.sh
@@ -18,14 +18,20 @@
set -euxo pipefail
-BUILD_DIR=$1
-mkdir -p "$BUILD_DIR"
-cd "$BUILD_DIR"
-cp ../cmake/config.cmake .
-
-echo set\(USE_SORT ON\) >> config.cmake
-echo set\(USE_RPC ON\) >> config.cmake
-echo set\(USE_LLVM llvm-config-17\) >> config.cmake
-echo set\(CMAKE_CXX_FLAGS -Werror\) >> config.cmake
-echo set\(USE_CCACHE OFF\) >> config.cmake
-echo set\(SUMMARIZE ON\) >> config.cmake
+ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOTDIR"
+BUILD_DIR="${1:-build}"
+CPP_TESTS="${2:-0}"
+./tirx/tests/scripts/task_build_cpu.sh "$BUILD_DIR" "$CPP_TESTS"
+exec ./tests/scripts/task_build_wheels.sh "$BUILD_DIR" "$CPP_TESTS" \
+ "-DUSE_SORT=ON" \
+ "-DUSE_DNNL=ON" \
+ "-DUSE_EXAMPLE_NPU_CODEGEN=ON" \
+ "-DUSE_EXAMPLE_NPU_RUNTIME=ON" \
+ "-DCMAKE_CXX_FLAGS=-Wno-error=range-loop-construct -Wno-error=comment" \
+ "-DHIDE_PRIVATE_SYMBOLS=ON" \
+ "-DUSE_TENSORFLOW_PATH=/tensorflow" \
+ "-DUSE_TFLITE=/opt/tflite" \
+ "-DUSE_FLATBUFFERS_PATH=/flatbuffers" \
+ "-DUSE_CCACHE=OFF" \
+ "-DSUMMARIZE=ON"
diff --git a/tests/scripts/task_web_wasm.sh b/tests/scripts/task_build_dependencies.sh
old mode 100755
new mode 100644
similarity index 70%
copy from tests/scripts/task_web_wasm.sh
copy to tests/scripts/task_build_dependencies.sh
index aed91a371b..08cdee6633
--- a/tests/scripts/task_web_wasm.sh
+++ b/tests/scripts/task_build_dependencies.sh
@@ -16,21 +16,9 @@
# specific language governing permissions and limitations
# under the License.
-set -euxo pipefail
-
-export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
-
-# setup tvm-ffi into python folder
-uv pip install -v --target=python ./3rdparty/tvm-ffi/
-
-rm -rf .emscripten_cache
-cd web
-export EM_CACHE=`pwd`/.emscripten_cache
-make clean
-npm install
-npm run lint
-npm run prepwasm
-npm run bundle
-npm run test
-npm run typedoc
-cd ..
+# Source this helper to use the writable build dependency target.
+BUILD_DEPS="$(realpath "$1")/wheel-build-deps"
+WHEEL_DIR="$PWD/dist"
+uv pip install --python python3 --target "$BUILD_DEPS" --no-deps --offline \
+ "$WHEEL_DIR"/apache_tvm_tirx-*.whl
+export PYTHONPATH="$BUILD_DEPS"
diff --git a/tests/scripts/task_config_build_gpu_other.sh b/tests/scripts/task_build_gpu.sh
similarity index 57%
copy from tests/scripts/task_config_build_gpu_other.sh
copy to tests/scripts/task_build_gpu.sh
index 0d5d2030c4..ab5ab5c968 100755
--- a/tests/scripts/task_config_build_gpu_other.sh
+++ b/tests/scripts/task_build_gpu.sh
@@ -16,17 +16,25 @@
# specific language governing permissions and limitations
# under the License.
-# This file is a compiler test to ensure that runtimes can compile
-# correctly, even if they aren't actively tested in the CI.
set -euxo pipefail
-BUILD_DIR=$1
-mkdir -p "$BUILD_DIR"
-cd "$BUILD_DIR"
-cp ../cmake/config.cmake .
-
-echo set\(USE_OPENCL ON\) >> config.cmake
-echo set\(USE_ROCM ON\) >> config.cmake
-echo set\(CMAKE_CXX_FLAGS -Werror\) >> config.cmake
-echo set\(USE_CCACHE OFF\) >> config.cmake
-echo set\(SUMMARIZE ON\) >> config.cmake
+ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOTDIR"
+BUILD_DIR="${1:-build}"
+CPP_TESTS="${2:-0}"
+./tirx/tests/scripts/task_build_gpu.sh "$BUILD_DIR" "$CPP_TESTS"
+exec ./tests/scripts/task_build_wheels.sh "$BUILD_DIR" "$CPP_TESTS" \
+ "-DUSE_CUBLAS=ON" \
+ "-DUSE_CUDNN=ON" \
+ "-DUSE_CUDA=ON" \
+ "-DUSE_RPC=ON" \
+ "-DUSE_SORT=ON" \
+ "-DUSE_BLAS=openblas" \
+ "-DCMAKE_CXX_FLAGS=-Werror" \
+ "-DUSE_TENSORRT_CODEGEN=ON" \
+ "-DHIDE_PRIVATE_SYMBOLS=ON" \
+ "-DUSE_CUTLASS=ON" \
+ "-DCMAKE_CUDA_ARCHITECTURES=75" \
+ "-DUSE_CLML=ON" \
+ "-DUSE_CCACHE=OFF" \
+ "-DSUMMARIZE=ON"
diff --git a/tests/scripts/task_config_build_cpu.sh b/tests/scripts/task_build_gpu_other.sh
similarity index 50%
rename from tests/scripts/task_config_build_cpu.sh
rename to tests/scripts/task_build_gpu_other.sh
index e9d528be11..e848172b4a 100755
--- a/tests/scripts/task_config_build_cpu.sh
+++ b/tests/scripts/task_build_gpu_other.sh
@@ -16,22 +16,26 @@
# specific language governing permissions and limitations
# under the License.
+# This file is a compiler test to ensure that runtimes can compile
+# correctly, even if they aren't actively tested in the CI.
set -euxo pipefail
-BUILD_DIR=$1
+BUILD_DIR="${1:-build}"
+CPP_TESTS="${2:-0}"
+ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
mkdir -p "$BUILD_DIR"
-cd "$BUILD_DIR"
-cp ../cmake/config.cmake .
+BUILD_DIR="$(realpath "$BUILD_DIR")"
+rm -f "$BUILD_DIR/config.cmake"
-echo set\(USE_SORT ON\) >> config.cmake
-echo set\(USE_DNNL ON\) >> config.cmake
-echo set\(USE_EXAMPLE_NPU_CODEGEN ON\) >> config.cmake
-echo set\(USE_EXAMPLE_NPU_RUNTIME ON\) >> config.cmake
-echo set\(USE_LLVM \"/usr/bin/llvm-config-17 --link-static\"\) >> config.cmake
-echo set\(CMAKE_CXX_FLAGS \"-Wno-error=range-loop-construct -Wno-error=comment\"\) >> config.cmake
-echo set\(HIDE_PRIVATE_SYMBOLS ON\) >> config.cmake
-echo set\(USE_TENSORFLOW_PATH \"/tensorflow\"\) >> config.cmake
-echo set\(USE_TFLITE \"/opt/tflite\"\) >> config.cmake
-echo set\(USE_FLATBUFFERS_PATH \"/flatbuffers\"\) >> config.cmake
-echo set\(USE_CCACHE OFF\) >> config.cmake
-echo set\(SUMMARIZE ON\) >> config.cmake
+cd "$ROOTDIR"
+# Preserve the separate runtime-only GPU compile check.
+uv pip install --python python3 --no-deps --target "$BUILD_DIR/python" ./3rdparty/tvm-ffi
+export PYTHONPATH="$BUILD_DIR/python"
+if command -v sccache >/dev/null; then
+ sccache --start-server || true
+ trap 'sccache --show-stats' EXIT
+fi
+cmake -S . -B "$BUILD_DIR" -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+ -DUSE_OPENCL=ON -DUSE_ROCM=ON -DCMAKE_CXX_FLAGS=-Werror \
+ -DUSE_CCACHE=OFF -DSUMMARIZE=ON
+cmake --build "$BUILD_DIR" --target tvm_runtime
diff --git a/tests/scripts/task_config_build_gpu_other.sh b/tests/scripts/task_build_wasm.sh
similarity index 66%
rename from tests/scripts/task_config_build_gpu_other.sh
rename to tests/scripts/task_build_wasm.sh
index 0d5d2030c4..541d58b444 100755
--- a/tests/scripts/task_config_build_gpu_other.sh
+++ b/tests/scripts/task_build_wasm.sh
@@ -16,17 +16,16 @@
# specific language governing permissions and limitations
# under the License.
-# This file is a compiler test to ensure that runtimes can compile
-# correctly, even if they aren't actively tested in the CI.
set -euxo pipefail
-BUILD_DIR=$1
-mkdir -p "$BUILD_DIR"
-cd "$BUILD_DIR"
-cp ../cmake/config.cmake .
-
-echo set\(USE_OPENCL ON\) >> config.cmake
-echo set\(USE_ROCM ON\) >> config.cmake
-echo set\(CMAKE_CXX_FLAGS -Werror\) >> config.cmake
-echo set\(USE_CCACHE OFF\) >> config.cmake
-echo set\(SUMMARIZE ON\) >> config.cmake
+ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOTDIR"
+BUILD_DIR="${1:-build}"
+CPP_TESTS="${2:-0}"
+./tirx/tests/scripts/task_build_wasm.sh "$BUILD_DIR" "$CPP_TESTS"
+exec ./tests/scripts/task_build_wheels.sh "$BUILD_DIR" "$CPP_TESTS" \
+ "-DUSE_SORT=ON" \
+ "-DCMAKE_CXX_FLAGS=-Werror" \
+ "-DHIDE_PRIVATE_SYMBOLS=ON" \
+ "-DUSE_CCACHE=OFF" \
+ "-DSUMMARIZE=ON"
diff --git a/tests/scripts/task_build_wheels.sh b/tests/scripts/task_build_wheels.sh
new file mode 100755
index 0000000000..f0012a575e
--- /dev/null
+++ b/tests/scripts/task_build_wheels.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -euxo pipefail
+
+mkdir -p "${1:-build}"
+BUILD_DIR="$(realpath "${1:-build}")"
+CPP_TESTS="${2:-0}"
+shift 2
+WHEEL_DIR="$PWD/dist"
+rm -f "$BUILD_DIR/config.cmake"
+if command -v sccache >/dev/null; then
+ trap 'sccache --show-stats' EXIT
+fi
+tvm_options=()
+if [ "$CPP_TESTS" = "1" ]; then
+ tvm_options=( '-Cbuild.targets=all;cpptest')
+fi
+source ./tests/scripts/task_build_dependencies.sh "$BUILD_DIR"
+cmake_options=("$@")
+if [ "$CPP_TESTS" = "1" ]; then
+ cmake_options+=(-DUSE_GTEST=ON -DBUILD_TESTING=ON)
+fi
+cmake_options+=(-DTVM_USE_INSTALLED_TIRX=ON)
+cmake_args=$(IFS=';'; echo "${cmake_options[*]}")
+uv build --wheel --no-build-isolation --offline --python python3 --out-dir "$WHEEL_DIR" . \
+ -Cbuild-dir="$BUILD_DIR" -Ccmake.build-type=RelWithDebInfo \
+ -Ccmake.args="$cmake_args" \
+ -Cbuild.verbose="${VERBOSE:-true}" "${tvm_options[@]}"
+if [ "$CPP_TESTS" = "1" ]; then
+ "$BUILD_DIR/cpptest"
+fi
diff --git a/tests/scripts/task_config_build_gpu.sh b/tests/scripts/task_config_build_gpu.sh
deleted file mode 100755
index a16847023a..0000000000
--- a/tests/scripts/task_config_build_gpu.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env bash
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-set -euxo pipefail
-
-BUILD_DIR=$1
-mkdir -p "$BUILD_DIR"
-cd "$BUILD_DIR"
-cp ../cmake/config.cmake .
-
-echo set\(USE_CUBLAS ON\) >> config.cmake
-echo set\(USE_CUDNN ON\) >> config.cmake
-echo set\(USE_CUDA ON\) >> config.cmake
-echo set\(USE_VULKAN ON\) >> config.cmake
-echo set\(USE_OPENCL ON\) >> config.cmake
-echo set\(USE_OPENCL_GTEST \"/googletest\"\) >> config.cmake
-echo set\(USE_LLVM \"/usr/bin/llvm-config-15 --link-static\"\) >> config.cmake
-echo set\(USE_RPC ON\) >> config.cmake
-echo set\(USE_SORT ON\) >> config.cmake
-echo set\(USE_BLAS openblas\) >> config.cmake
-echo set\(CMAKE_CXX_FLAGS -Werror\) >> config.cmake
-echo set\(USE_TENSORRT_CODEGEN ON\) >> config.cmake
-echo set\(USE_CCACHE OFF\) >> config.cmake
-echo set\(SUMMARIZE ON\) >> config.cmake
-echo set\(HIDE_PRIVATE_SYMBOLS ON\) >> config.cmake
-echo set\(USE_CUTLASS ON\) >> config.cmake
-echo set\(CMAKE_CUDA_ARCHITECTURES 75\) >> config.cmake
-echo set\(USE_CLML ON\) >> config.cmake
diff --git a/tests/scripts/task_config_build_wasm.sh b/tests/scripts/task_install_docs_dependencies.sh
similarity index 67%
copy from tests/scripts/task_config_build_wasm.sh
copy to tests/scripts/task_install_docs_dependencies.sh
index 11526082e4..2ec999876f 100755
--- a/tests/scripts/task_config_build_wasm.sh
+++ b/tests/scripts/task_install_docs_dependencies.sh
@@ -18,14 +18,16 @@
set -euxo pipefail
-BUILD_DIR=$1
-mkdir -p "$BUILD_DIR"
-cd "$BUILD_DIR"
-cp ../cmake/config.cmake .
-
-echo set\(USE_SORT ON\) >> config.cmake
-echo set\(USE_LLVM llvm-config-15\) >> config.cmake
-echo set\(CMAKE_CXX_FLAGS -Werror\) >> config.cmake
-echo set\(HIDE_PRIVATE_SYMBOLS ON\) >> config.cmake
-echo set\(USE_CCACHE OFF\) >> config.cmake
-echo set\(SUMMARIZE ON\) >> config.cmake
+# Additional documentation dependencies for the CPU CI image.
+uv pip install \
+ 'Sphinx==8.1.3' \
+ 'sphinx_autodoc_annotation~=1.0' \
+ 'sphinx-gallery==0.20.0' \
+ 'sphinx-book-theme==1.1.4' \
+ 'pydata-sphinx-theme==0.15.4' \
+ 'autodocsumm==0.2.14' \
+ 'image==1.5.33' \
+ 'matplotlib==3.10.8' \
+ 'commonmark==0.9.1' \
+ 'docutils==0.21.2' \
+ 'Pillow==12.1.1'
diff --git a/tests/scripts/task_java_unittest.sh b/tests/scripts/task_java_unittest.sh
index 29342c70c8..c072ef26e6 100755
--- a/tests/scripts/task_java_unittest.sh
+++ b/tests/scripts/task_java_unittest.sh
@@ -18,8 +18,12 @@
set -euxo pipefail
-export PYTHONPATH=tirx/python:python
-export LD_LIBRARY_PATH="lib:${LD_LIBRARY_PATH:-}"
+if [ "${TVM_TEST_USE_WHEEL:-0}" = "1" ]; then
+ source "$(dirname "$0")/task_python_install_wheels.sh"
+else
+ export PYTHONPATH=tirx/python:python
+ export LD_LIBRARY_PATH="lib:${LD_LIBRARY_PATH:-}"
+fi
# to avoid CI CPU thread throttling.
export TVM_BIND_THREADS=0
diff --git a/tests/scripts/task_jvm_build.sh b/tests/scripts/task_jvm_build.sh
index 468fea4ec6..fede6385f4 100755
--- a/tests/scripts/task_jvm_build.sh
+++ b/tests/scripts/task_jvm_build.sh
@@ -33,13 +33,32 @@ fi
ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
TVM_BUILD_PATH="${TVM_BUILD_PATH:-${ROOTDIR}/build}"
-TVM_BUILD_PATH="$(realpath "${TVM_BUILD_PATH}")"
+if [ "${TVM_TEST_USE_WHEEL:-0}" != "1" ]; then
+ TVM_BUILD_PATH="$(realpath "${TVM_BUILD_PATH}")"
+fi
DLPACK_PATH="${DLPACK_PATH:-${ROOTDIR}/3rdparty/tvm-ffi/3rdparty/dlpack}"
INCLUDE_FLAGS="-I${ROOTDIR}/include -I${DLPACK_PATH}/include"
PKG_CFLAGS="-Wall -O3 ${INCLUDE_FLAGS} -fPIC"
PKG_LDFLAGS=""
+NATIVE_ARGS=()
+if [ "${TVM_TEST_USE_WHEEL:-0}" = "1" ]; then
+ TVM_INCLUDE_DIR="$(python3 -m tvm_tirx.config --includedir)"
+ FFI_INCLUDE_DIR="$(python3 -m tvm_ffi.config --includedir)"
+ TVM_LIB_DIR="$(python3 -m tvm_tirx.config --libdir)"
+ FFI_LIB_DIR="$(python3 -m tvm_ffi.config --libdir)"
+ PKG_CFLAGS="-Wall -O3 -fPIC $(python3 -m tvm_tirx.config --cflags) $(python3 -m tvm_ffi.config --cflags)"
+ PKG_LDFLAGS="$(python3 -m tvm_tirx.config --ldflags --libs) $(python3 -m tvm_ffi.config --ldflags --libs)"
+ # Each config flag prints one line; Maven expects one compiler/linker argument list.
+ PKG_LDFLAGS="${PKG_LDFLAGS//$'\n'/ }"
+ NATIVE_ARGS=(
+ "-Dtvm.include.dir=${TVM_INCLUDE_DIR}"
+ "-Dtvm.ffi.include.dir=${FFI_INCLUDE_DIR}"
+ "-Dtvm.lib.dir=${TVM_LIB_DIR}"
+ "-Dtvm.ffi.lib.dir=${FFI_LIB_DIR}"
+ )
+fi
if [ "$(uname -s)" = "Darwin" ]; then
JVM_PKG_PROFILE="osx-x86_64"
@@ -64,5 +83,6 @@ mvn ${GOAL} \
"-Dcflags=${PKG_CFLAGS}" \
"-Dldflags=${PKG_LDFLAGS}" \
"-Dcurrent_libdir=${TVM_BUILD_PATH}" \
+ "${NATIVE_ARGS[@]}" \
${JVM_TEST_ARGS} \
"$@"
diff --git a/tests/scripts/task_python_docs.sh b/tests/scripts/task_python_docs.sh
index 0a4b88df96..bd9e69393a 100755
--- a/tests/scripts/task_python_docs.sh
+++ b/tests/scripts/task_python_docs.sh
@@ -18,7 +18,16 @@
set -euxo pipefail
-export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
+# The native pipeline opts in; old main-selected jobs retain source imports.
+if [ "${TVM_TEST_USE_WHEEL:-0}" = "1" ]; then
+ source "$(dirname "$0")/task_python_install_wheels.sh"
+ export TVM_DOCS_USE_WHEEL=1
+else
+ export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
+ unset TVM_DOCS_USE_WHEEL
+ uv pip install -v --target=python ./3rdparty/tvm-ffi/
+ uv pip install --no-deps apache-tvm-ffi-orcjit==0.1.2
+fi
# to avoid CI CPU thread throttling.
export TVM_BIND_THREADS=0
@@ -47,9 +56,6 @@ sphinx_precheck() {
clean_files
echo "PreCheck sphinx doc generation WARNINGS.."
- # setup tvm-ffi into python folder
- uv pip install -v --target=python ./3rdparty/tvm-ffi/
-
pushd docs
make clean
TVM_TUTORIAL_EXEC_PATTERN=none make html 2>&1 | tee /tmp/$$.log.txt
@@ -129,15 +135,10 @@ clean_files
find . -type f -path "*.log" | xargs rm -f
find . -type f -path "*.pyc" | xargs rm -f
-# setup tvm-ffi into python folder
-uv pip install -v --target=python ./3rdparty/tvm-ffi/
-
-# LLVMModule JIT execution, needed by the tutorials this build runs.
-uv pip install --no-deps apache-tvm-ffi-orcjit==0.1.2
cd docs
-PYTHONPATH=$(pwd)/../tirx/python:$(pwd)/../python make htmldepoly SPHINXOPTS='-j auto' |& tee /tmp/$$.log.txt
+make htmldepoly SPHINXOPTS='-j auto' |& tee /tmp/$$.log.txt
if grep -E "failed to execute|Segmentation fault" < /tmp/$$.log.txt; then
echo "Some of sphinx-gallery item example failed to execute."
exit 1
diff --git a/tests/scripts/task_python_install_wheels.sh b/tests/scripts/task_python_install_wheels.sh
new file mode 100644
index 0000000000..c435a95f44
--- /dev/null
+++ b/tests/scripts/task_python_install_wheels.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Source this file so subsequent Python invocations use the installed wheels.
+# Docker defaults PYTHONPATH to the checkout; replace it with the explicit target.
+unset PYTHONPATH TVM_LIBRARY_PATH TVM_FFI_LIBRARY_PATH TVM_TIRX_LIBRARY_PATH
+export PYTHONNOUSERSITE=1
+export TVM_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+TVM_PYTHON_TARGET="$TVM_HOME/.local/python"
+
+# Match the explicit prerequisite in the legacy Python test setup.
+uv pip install --python python3 --target "$TVM_PYTHON_TARGET" --no-deps apache-tvm-ffi-orcjit==0.1.2
+uv pip install --python python3 --target "$TVM_PYTHON_TARGET" --no-deps --offline --reinstall \
+ "$TVM_HOME"/dist/apache_tvm-*.whl \
+ "$TVM_HOME"/dist/apache_tvm_ffi-*.whl \
+ "$TVM_HOME"/dist/apache_tvm_tirx-*.whl
+export PYTHONPATH="$TVM_PYTHON_TARGET"
diff --git a/tests/scripts/task_python_unittest.sh b/tests/scripts/task_python_unittest.sh
index 9b319b7a67..5f12809894 100755
--- a/tests/scripts/task_python_unittest.sh
+++ b/tests/scripts/task_python_unittest.sh
@@ -18,15 +18,23 @@
set -euxo pipefail
-export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
-export PYTEST_ADDOPTS="${CI_PYTEST_ADD_OPTIONS:-} ${PYTEST_ADDOPTS:-}"
+if [ "${TVM_TEST_USE_WHEEL:-0}" = "1" ]; then
+ source "$(dirname "$0")/task_python_install_wheels.sh"
+else
+ export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
+
+ # Source-mode callers provision FFI from the pinned submodule.
+ uv pip install --python python3 --no-deps --target=python ./3rdparty/tvm-ffi/
-# Reuse the exact source-pinned FFI package transferred by the native build.
-python3 tests/scripts/task_build.py --ffi-only
+ # LLVMModule JIT execution. Installed here as well as in the CI image because a docker/
+ # change does not reach the test containers in the same run (they use the pinned tag).
+ # --no-deps keeps the submodule-built tvm-ffi above from being replaced by a PyPI build.
+ uv pip install --no-deps apache-tvm-ffi-orcjit==0.1.2
-# LLVMModule JIT execution. Installed here as well as in the CI image because a docker/
-# change does not reach the test containers in the same run (they use the pinned tag).
-# --no-deps keeps the submodule-built tvm-ffi above from being replaced by a PyPI build.
-uv pip install --no-deps apache-tvm-ffi-orcjit==0.1.2
+fi
+export PYTEST_ADDOPTS="${CI_PYTEST_ADD_OPTIONS:-} ${PYTEST_ADDOPTS:-}"
-python3 -m pytest -vvs -n auto -o faulthandler_timeout=120 -m "${TVM_TEST_MARKER:-not gpu}" tests/python tirx/tests/python
+if [ "$#" -eq 0 ]; then
+ set -- tests/python tirx/tests/python
+fi
+python3 -m pytest -vvs -n auto -o faulthandler_timeout=120 -m "${TVM_TEST_MARKER:-not gpu}" "$@"
diff --git a/tests/scripts/task_python_unittest_gpuonly.sh b/tests/scripts/task_python_unittest_gpuonly.sh
index c025bd6675..2e12d621f8 100755
--- a/tests/scripts/task_python_unittest_gpuonly.sh
+++ b/tests/scripts/task_python_unittest_gpuonly.sh
@@ -31,9 +31,6 @@ export TVM_TEST_TARGETS='cuda;metal;rocm;nvptx'
# only minimal functionality of Vulkan runtime.
export TVM_TEST_TARGETS='{"kind":"vulkan","from_device":0}'
-export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
-export PYTEST_ADDOPTS="${CI_PYTEST_ADD_OPTIONS:-} ${PYTEST_ADDOPTS:-}"
-
-python3 -m pytest -vvs -n auto -m "${TVM_TEST_MARKER}" \
+./tests/scripts/task_python_unittest.sh \
tirx/tests/python/backend/vulkan/codegen/test_vulkan.py \
tirx/tests/python/backend/vulkan/test_vulkan_module_export.py
diff --git a/tests/scripts/task_web_wasm.sh b/tests/scripts/task_web_wasm.sh
index aed91a371b..4573aab846 100755
--- a/tests/scripts/task_web_wasm.sh
+++ b/tests/scripts/task_web_wasm.sh
@@ -18,10 +18,14 @@
set -euxo pipefail
-export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
+if [ "${TVM_TEST_USE_WHEEL:-0}" = "1" ]; then
+ source "$(dirname "$0")/task_python_install_wheels.sh"
+else
+ export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
-# setup tvm-ffi into python folder
-uv pip install -v --target=python ./3rdparty/tvm-ffi/
+ # setup tvm-ffi into python folder
+ uv pip install -v --target=python ./3rdparty/tvm-ffi/
+fi
rm -rf .emscripten_cache
cd web
diff --git a/tirx/pyproject.toml b/tirx/pyproject.toml
index db8d5c071c..8c58b29d81 100644
--- a/tirx/pyproject.toml
+++ b/tirx/pyproject.toml
@@ -55,6 +55,9 @@ dependencies = [
"packaging",
]
+[project.scripts]
+tvm-tirx-config = "tvm_tirx.config:__main__"
+
[project.optional-dependencies]
torch = ["torch"]
cuda = ["cuda-bindings"]
diff --git a/tirx/python/tvm_tirx/__init__.py b/tirx/python/tvm_tirx/__init__.py
index efbdb05a5e..d583e2964f 100644
--- a/tirx/python/tvm_tirx/__init__.py
+++ b/tirx/python/tvm_tirx/__init__.py
@@ -18,108 +18,125 @@
# pylint: disable=redefined-builtin, wildcard-import
"""TVM: Open Deep Learning Compiler Stack."""
-import multiprocessing
-import os
import sys
-# ffi module must load first
-from tvm_ffi import register_object, register_global_func, get_global_func
+from typing import TYPE_CHECKING
-# top-level alias
-from .libinfo import __version__
-from .base import _RUNTIME_ONLY
-# tvm.runtime
-from .runtime import Object
-from .runtime._tensor import device, cpu, cuda, opencl, vulkan, metal
-from .runtime._tensor import vpi, rocm, ext_dev, hexagon
-from .runtime import DataType, DataTypeCode
+def _is_config_mode() -> bool:
+ """Recognize the config CLI before loading Python or native TVM components."""
+ if sys.argv[0].endswith("tvm-tirx-config"):
+ return True
+ argv = getattr(sys, "orig_argv", [])
+ return any(
+ arg == "-m" and argv[i + 1 : i + 2] == ["tvm_tirx.config"] for i, arg in enumerate(argv)
+ )
-# tvm.error
-from . import error
-# tvm.ir
-from .ir import IRModule, transform
-from . import ir
+if TYPE_CHECKING or not _is_config_mode():
+ import multiprocessing
+ import os
+ import sys
-# tvm.script — must be imported before any dialect package so that
-# tvm.script.register_dialect is reachable when dialect __init__.py files run.
-from . import script
+ # ffi module must load first
+ from tvm_ffi import register_object, register_global_func, get_global_func
-# tvm.tirx — registers itself via tvm.script.register_dialect in its __init__
-from . import tirx
-from .ir import prim
+ # top-level alias
+ from .libinfo import __version__
+ from .base import _RUNTIME_ONLY
-# tvm.backend — owns backend Python load hooks
-from . import backend
+ # tvm.runtime
+ from .runtime import Object
+ from .runtime._tensor import device, cpu, cuda, opencl, vulkan, metal
+ from .runtime._tensor import vpi, rocm, ext_dev, hexagon
+ from .runtime import DataType, DataTypeCode
-# tvm.target
-from . import target
-from .target import device_from_target
+ # tvm.error
+ from . import error
-# Shared code generation helpers
-from . import codegen
+ # tvm.ir
+ from .ir import IRModule, transform
+ from . import ir
-# others
-from . import sym
+ # tvm.script — must be imported before any dialect package so that
+ # tvm.script.register_dialect is reachable when dialect __init__.py files run.
+ from . import script
-# support infra
-from . import support
+ # tvm.tirx — registers itself via tvm.script.register_dialect in its __init__
+ from . import tirx
+ from .ir import prim
-# Side-effect imports: register CUDA/ROCm FFI callbacks at TVM startup
-from .support import rocm as _rocm, nvcc as _nvcc
+ # tvm.backend — owns backend Python load hooks
+ from . import backend
-# NOTE: This file should be python2 compatible so we can
-# raise proper error message when user run the package using
-# an older version of the python
+ # tvm.target
+ from . import target
+ from .target import device_from_target
+ # Shared code generation helpers
+ from . import codegen
-def _should_print_backtrace():
- in_pytest = "PYTEST_CURRENT_TEST" in os.environ
- tvm_backtrace = os.environ.get("TVM_BACKTRACE", "0")
+ # others
+ from . import sym
- try:
- tvm_backtrace = bool(int(tvm_backtrace))
- except ValueError:
- raise ValueError(f"invalid value for TVM_BACKTRACE {tvm_backtrace}, please set to 0 or 1.")
+ # support infra
+ from . import support
+
+ # Side-effect imports: register CUDA/ROCm FFI callbacks at TVM startup
+ from .support import rocm as _rocm, nvcc as _nvcc
- return in_pytest or tvm_backtrace
+ # NOTE: This file should be python2 compatible so we can
+ # raise proper error message when user run the package using
+ # an older version of the python
+ def _should_print_backtrace():
+ in_pytest = "PYTEST_CURRENT_TEST" in os.environ
+ tvm_backtrace = os.environ.get("TVM_BACKTRACE", "0")
-def tvm_wrap_excepthook(exception_hook):
- """Wrap given excepthook with TVM additional work."""
+ try:
+ tvm_backtrace = bool(int(tvm_backtrace))
+ except ValueError:
+ raise ValueError(
+ f"invalid value for TVM_BACKTRACE {tvm_backtrace}, please set to 0 or 1."
+ )
- def wrapper(exctype, value, trbk):
- """Clean subprocesses when TVM is interrupted."""
- if exctype is error.DiagnosticError and not _should_print_backtrace():
- # TODO(@jroesch): consider moving to C++?
- print("note: run with `TVM_BACKTRACE=1` environment variable to display a backtrace.")
- else:
- exception_hook(exctype, value, trbk)
+ return in_pytest or tvm_backtrace
- if hasattr(multiprocessing, "active_children"):
- # pylint: disable=not-callable
- for p in multiprocessing.active_children():
- p.terminate()
+ def tvm_wrap_excepthook(exception_hook):
+ """Wrap given excepthook with TVM additional work."""
- return wrapper
+ def wrapper(exctype, value, trbk):
+ """Clean subprocesses when TVM is interrupted."""
+ if exctype is error.DiagnosticError and not _should_print_backtrace():
+ # TODO(@jroesch): consider moving to C++?
+ print(
+ "note: run with `TVM_BACKTRACE=1` environment variable to display a backtrace."
+ )
+ else:
+ exception_hook(exctype, value, trbk)
+ if hasattr(multiprocessing, "active_children"):
+ # pylint: disable=not-callable
+ for p in multiprocessing.active_children():
+ p.terminate()
-sys.excepthook = tvm_wrap_excepthook(sys.excepthook)
+ return wrapper
-# Autoload loads built-in and out-of-tree backends. Out-of-tree extensions opt
-# into being loaded automatically at ``import tvm`` time by declaring an entry
-# point in the ``tvm.backends`` group:
-# [project.entry-points."tvm.backends"] tvm_foo = "tvm_foo:_autoload".
-# Autoload can be disabled via ``TVM_DEVICE_BACKEND_AUTOLOAD=0``.
-from .backend._autoload_backends import _autoload_backends
+ sys.excepthook = tvm_wrap_excepthook(sys.excepthook)
+
+ # Autoload loads built-in and out-of-tree backends. Out-of-tree extensions opt
+ # into being loaded automatically at ``import tvm`` time by declaring an entry
+ # point in the ``tvm.backends`` group:
+ # [project.entry-points."tvm.backends"] tvm_foo = "tvm_foo:_autoload".
+ # Autoload can be disabled via ``TVM_DEVICE_BACKEND_AUTOLOAD=0``.
+ from .backend._autoload_backends import _autoload_backends
-_autoload_backends()
+ _autoload_backends()
-# Load the optional ORC JIT extension for its global FFI registrations. LLVM
-# function lookup reports a targeted installation error if this import fails;
-# importing TVM itself remains silent for AOT-only workflows.
-try:
- import tvm_ffi_orcjit as _tvm_ffi_orcjit
-except Exception: # pylint: disable=broad-exception-caught
- pass
+ # Load the optional ORC JIT extension for its global FFI registrations. LLVM
+ # function lookup reports a targeted installation error if this import fails;
+ # importing TVM itself remains silent for AOT-only workflows.
+ try:
+ import tvm_ffi_orcjit as _tvm_ffi_orcjit
+ except Exception: # pylint: disable=broad-exception-caught
+ pass
diff --git a/tirx/python/tvm_tirx/config.py b/tirx/python/tvm_tirx/config.py
new file mode 100644
index 0000000000..ea69971fbc
--- /dev/null
+++ b/tirx/python/tvm_tirx/config.py
@@ -0,0 +1,99 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Lightweight compiler and linker configuration for the TVM runtime."""
+
+import argparse
+import os
+import sys
+from pathlib import Path
+
+
+def _runtime_root() -> Path:
+ """Locate this package without importing its native code."""
+ return Path(__file__).resolve().parent
+
+
+def _find_include() -> str:
+ package = _runtime_root()
+ for path in (package / "include", package.parent.parent / "include"):
+ if (path / "tvm" / "runtime").is_dir():
+ return str(path.resolve())
+ raise RuntimeError("Cannot find TVM runtime headers")
+
+
+def _library_paths() -> list[Path]:
+ package = _runtime_root()
+ source = package.parent.parent
+ paths = [package / "lib", source / "build" / "lib", source / "lib"]
+ if source.name == "tirx":
+ paths.append(source.parent / "build" / "lib")
+ if os.environ.get("TVM_TIRX_LIBRARY_PATH"):
+ paths.insert(0, Path(os.environ["TVM_TIRX_LIBRARY_PATH"]))
+ for name in ("LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", "PATH"):
+ paths.extend(Path(value) for value in os.environ.get(name, "").split(os.pathsep) if value)
+ return paths
+
+
+def _find_runtime() -> str:
+ filename = {"win32": "tvm_runtime.dll", "darwin": "libtvm_runtime.dylib"}.get(
+ sys.platform, "libtvm_runtime.so"
+ )
+ for directory in _library_paths():
+ path = directory / filename
+ if path.is_file():
+ return str(path.resolve())
+ raise RuntimeError("Cannot find libtvm_runtime")
+
+
+def __main__() -> None:
+ """Print paths and flags using the same conventions as tvm_ffi.config."""
+ parser = argparse.ArgumentParser(description="Get TIRx runtime build configuration")
+ for name, help_text in (
+ ("includedir", "Print include directory"),
+ ("libdir", "Print library directory"),
+ ("libfiles", "Fully qualified library filenames"),
+ ("libs", "Libraries to be linked"),
+ ("cflags", "Print C flags"),
+ ("cxxflags", "Print C++ flags"),
+ ("ldflags", "Print linker flags"),
+ ):
+ parser.add_argument(f"--{name}", action="store_true", help=help_text)
+ args = parser.parse_args()
+ if len(sys.argv) == 1:
+ parser.print_help()
+ return
+ if args.includedir:
+ print(_find_include())
+ if args.libdir or args.libfiles or args.libs or args.ldflags:
+ library = Path(_find_runtime())
+ link_file = library.with_suffix(".lib") if sys.platform == "win32" else library
+ if args.libdir:
+ print(library.parent)
+ if args.libfiles:
+ print(link_file)
+ if args.cxxflags:
+ print(f"-I{_find_include()} -std=c++20")
+ if args.cflags:
+ print(f"-I{_find_include()}")
+ if args.libs:
+ print(link_file if sys.platform == "win32" else "-ltvm_runtime")
+ if args.ldflags and sys.platform != "win32":
+ print(f"-L{library.parent}")
+
+
+if __name__ == "__main__":
+ __main__()
diff --git a/tirx/tests/python/runtime/test_tirx_config.py b/tirx/tests/python/runtime/test_tirx_config.py
new file mode 100644
index 0000000000..cab4876265
--- /dev/null
+++ b/tirx/tests/python/runtime/test_tirx_config.py
@@ -0,0 +1,74 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""The config command must work without importing native runtime dependencies."""
+
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+import tvm_tirx
+from tvm_tirx import config
+
+
+def test_config_without_native_imports(tmp_path):
+ # A startup hook rejects native loads and dependencies even when they are installed.
+ (tmp_path / "sitecustomize.py").write_text(
+ "import sys\n"
+ "class BlockNative:\n"
+ " def find_spec(self, fullname, path=None, target=None):\n"
+ " if fullname.split('.')[0] in ('tvm_ffi', 'torch', 'numpy') or "
+ "fullname in ('tvm.base', 'tvm.runtime', 'tvm.backend', 'tvm.driver', 'tvm_tirx.base'):\n"
+ " raise AssertionError(fullname)\n"
+ "sys.meta_path.insert(0, BlockNative())\n"
+ "def audit(event, args):\n"
+ " if event == 'ctypes.dlopen': raise AssertionError(args)\n"
+ "sys.addaudithook(audit)\n"
+ )
+ env = dict(os.environ)
+ env["PYTHONPATH"] = os.pathsep.join(
+ (
+ str(tmp_path),
+ str(Path(tvm_tirx.__file__).parent.parent),
+ )
+ )
+ command = [sys.executable, "-m", "tvm_tirx.config"]
+ include = subprocess.check_output([*command, "--includedir"], env=env, text=True).strip()
+ assert (Path(include) / "tvm" / "runtime").is_dir()
+ library = subprocess.check_output([*command, "--libfiles"], env=env, text=True).strip()
+ assert Path(library).is_file()
+ flags = subprocess.check_output(
+ [*command, "--cflags", "--cxxflags", "--libs", "--ldflags"], env=env, text=True
+ )
+ assert f"-I{include}" in flags
+ assert "-std=c++20" in flags
+
+
+def test_runtime_library_path_fallback(tmp_path, monkeypatch):
+ library = tmp_path / Path(config._find_runtime()).name
+ library.touch()
+ monkeypatch.setattr(config, "_runtime_root", lambda: tmp_path / "tvm_tirx")
+ monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
+ monkeypatch.delenv("DYLD_LIBRARY_PATH", raising=False)
+ monkeypatch.setenv("PATH", str(tmp_path))
+ assert config._find_runtime() == str(library)
+
+
+if __name__ == "__main__":
+ import tvm_tirx.testing
+
+ tvm_tirx.testing.main()
diff --git a/tests/scripts/task_config_build_arm.sh b/tirx/tests/scripts/task_build_arm.sh
similarity index 70%
rename from tests/scripts/task_config_build_arm.sh
rename to tirx/tests/scripts/task_build_arm.sh
index b58b21db34..cdbaf5b125 100755
--- a/tests/scripts/task_config_build_arm.sh
+++ b/tirx/tests/scripts/task_build_arm.sh
@@ -18,14 +18,12 @@
set -euxo pipefail
-BUILD_DIR=$1
-mkdir -p "$BUILD_DIR"
-cd "$BUILD_DIR"
-cp ../cmake/config.cmake .
-
-echo set\(USE_SORT ON\) >> config.cmake
-echo set\(USE_RPC ON\) >> config.cmake
-echo set\(USE_LLVM llvm-config-17\) >> config.cmake
-echo set\(CMAKE_CXX_FLAGS -Werror\) >> config.cmake
-echo set\(USE_CCACHE OFF\) >> config.cmake
-echo set\(SUMMARIZE ON\) >> config.cmake
+ROOTDIR="$(cd "$(dirname "$0")/../../.." && pwd)"
+cd "$ROOTDIR"
+BUILD_DIR="${1:-build}"
+CPP_TESTS="${2:-0}"
+exec ./tirx/tests/scripts/task_build_wheels.sh "$BUILD_DIR" "$CPP_TESTS" \
+ "-DUSE_LLVM=llvm-config-17" \
+ "-DCMAKE_CXX_FLAGS=-Werror" \
+ "-DUSE_CCACHE=OFF" \
+ "-DSUMMARIZE=ON"
diff --git a/tests/scripts/task_web_wasm.sh b/tirx/tests/scripts/task_build_cpu.sh
similarity index 67%
copy from tests/scripts/task_web_wasm.sh
copy to tirx/tests/scripts/task_build_cpu.sh
index aed91a371b..541b9e90af 100755
--- a/tests/scripts/task_web_wasm.sh
+++ b/tirx/tests/scripts/task_build_cpu.sh
@@ -18,19 +18,13 @@
set -euxo pipefail
-export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
-
-# setup tvm-ffi into python folder
-uv pip install -v --target=python ./3rdparty/tvm-ffi/
-
-rm -rf .emscripten_cache
-cd web
-export EM_CACHE=`pwd`/.emscripten_cache
-make clean
-npm install
-npm run lint
-npm run prepwasm
-npm run bundle
-npm run test
-npm run typedoc
-cd ..
+ROOTDIR="$(cd "$(dirname "$0")/../../.." && pwd)"
+cd "$ROOTDIR"
+BUILD_DIR="${1:-build}"
+CPP_TESTS="${2:-0}"
+exec ./tirx/tests/scripts/task_build_wheels.sh "$BUILD_DIR" "$CPP_TESTS" \
+ "-DUSE_LLVM=/usr/bin/llvm-config-17 --link-static" \
+ "-DCMAKE_CXX_FLAGS=-Wno-error=range-loop-construct -Wno-error=comment" \
+ "-DHIDE_PRIVATE_SYMBOLS=ON" \
+ "-DUSE_CCACHE=OFF" \
+ "-DSUMMARIZE=ON"
diff --git a/tests/scripts/task_web_wasm.sh b/tirx/tests/scripts/task_build_dependencies.sh
old mode 100755
new mode 100644
similarity index 60%
copy from tests/scripts/task_web_wasm.sh
copy to tirx/tests/scripts/task_build_dependencies.sh
index aed91a371b..b059a27ed4
--- a/tests/scripts/task_web_wasm.sh
+++ b/tirx/tests/scripts/task_build_dependencies.sh
@@ -16,21 +16,14 @@
# specific language governing permissions and limitations
# under the License.
-set -euxo pipefail
+# Source this helper to use the writable build dependency target.
+BUILD_DEPS="$(realpath "$1")/wheel-build-deps"
+WHEEL_DIR="$PWD/dist"
+rm -rf "$WHEEL_DIR" "$BUILD_DEPS"
+uv build --wheel --python python3 --out-dir "$WHEEL_DIR" ./3rdparty/tvm-ffi
+uv pip install --python python3 --target "$BUILD_DEPS" \
+ "scikit-build-core>=0.11" "setuptools-scm>=8" "mlc-z3-static>=4.16.0"
+uv pip install --python python3 --target "$BUILD_DEPS" --no-deps --offline \
+ "$WHEEL_DIR"/apache_tvm_ffi-*.whl
-export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
-
-# setup tvm-ffi into python folder
-uv pip install -v --target=python ./3rdparty/tvm-ffi/
-
-rm -rf .emscripten_cache
-cd web
-export EM_CACHE=`pwd`/.emscripten_cache
-make clean
-npm install
-npm run lint
-npm run prepwasm
-npm run bundle
-npm run test
-npm run typedoc
-cd ..
+export PYTHONPATH="$BUILD_DEPS"
diff --git a/tests/scripts/task_web_wasm.sh b/tirx/tests/scripts/task_build_gpu.sh
similarity index 62%
copy from tests/scripts/task_web_wasm.sh
copy to tirx/tests/scripts/task_build_gpu.sh
index aed91a371b..6cd3e7de54 100755
--- a/tests/scripts/task_web_wasm.sh
+++ b/tirx/tests/scripts/task_build_gpu.sh
@@ -18,19 +18,18 @@
set -euxo pipefail
-export PYTHONPATH="$(pwd)/tirx/python:$(pwd)/python"
-
-# setup tvm-ffi into python folder
-uv pip install -v --target=python ./3rdparty/tvm-ffi/
-
-rm -rf .emscripten_cache
-cd web
-export EM_CACHE=`pwd`/.emscripten_cache
-make clean
-npm install
-npm run lint
-npm run prepwasm
-npm run bundle
-npm run test
-npm run typedoc
-cd ..
+ROOTDIR="$(cd "$(dirname "$0")/../../.." && pwd)"
+cd "$ROOTDIR"
+BUILD_DIR="${1:-build}"
+CPP_TESTS="${2:-0}"
+exec ./tirx/tests/scripts/task_build_wheels.sh "$BUILD_DIR" "$CPP_TESTS" \
+ "-DUSE_CUDA=ON" \
+ "-DUSE_VULKAN=ON" \
+ "-DUSE_OPENCL=ON" \
+ "-DUSE_OPENCL_GTEST=/googletest" \
+ "-DUSE_LLVM=/usr/bin/llvm-config-15 --link-static" \
+ "-DCMAKE_CXX_FLAGS=-Werror" \
+ "-DHIDE_PRIVATE_SYMBOLS=ON" \
+ "-DCMAKE_CUDA_ARCHITECTURES=75" \
+ "-DUSE_CCACHE=OFF" \
+ "-DSUMMARIZE=ON"
diff --git a/tests/scripts/task_config_build_wasm.sh b/tirx/tests/scripts/task_build_wasm.sh
similarity index 69%
rename from tests/scripts/task_config_build_wasm.sh
rename to tirx/tests/scripts/task_build_wasm.sh
index 11526082e4..233bdd4ff6 100755
--- a/tests/scripts/task_config_build_wasm.sh
+++ b/tirx/tests/scripts/task_build_wasm.sh
@@ -18,14 +18,13 @@
set -euxo pipefail
-BUILD_DIR=$1
-mkdir -p "$BUILD_DIR"
-cd "$BUILD_DIR"
-cp ../cmake/config.cmake .
-
-echo set\(USE_SORT ON\) >> config.cmake
-echo set\(USE_LLVM llvm-config-15\) >> config.cmake
-echo set\(CMAKE_CXX_FLAGS -Werror\) >> config.cmake
-echo set\(HIDE_PRIVATE_SYMBOLS ON\) >> config.cmake
-echo set\(USE_CCACHE OFF\) >> config.cmake
-echo set\(SUMMARIZE ON\) >> config.cmake
+ROOTDIR="$(cd "$(dirname "$0")/../../.." && pwd)"
+cd "$ROOTDIR"
+BUILD_DIR="${1:-build}"
+CPP_TESTS="${2:-0}"
+exec ./tirx/tests/scripts/task_build_wheels.sh "$BUILD_DIR" "$CPP_TESTS" \
+ "-DUSE_LLVM=llvm-config-15" \
+ "-DCMAKE_CXX_FLAGS=-Werror" \
+ "-DHIDE_PRIVATE_SYMBOLS=ON" \
+ "-DUSE_CCACHE=OFF" \
+ "-DSUMMARIZE=ON"
diff --git a/tirx/tests/scripts/task_build_wheels.sh b/tirx/tests/scripts/task_build_wheels.sh
new file mode 100755
index 0000000000..e051a5d1b2
--- /dev/null
+++ b/tirx/tests/scripts/task_build_wheels.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -euxo pipefail
+
+mkdir -p "${1:-build}"
+BUILD_DIR="$(realpath "${1:-build}")"
+CPP_TESTS="${2:-0}"
+shift 2
+WHEEL_DIR="$PWD/dist"
+TIRX_BUILD="$BUILD_DIR/tirx-wheel"
+
+if command -v sccache >/dev/null; then
+ sccache --start-server || true
+ sccache --show-stats
+ trap 'sccache --show-stats' EXIT
+fi
+
+source ./tirx/tests/scripts/task_build_dependencies.sh "$BUILD_DIR"
+
+# TIRx owns the core native runtime. Build it with the same platform configuration.
+mkdir -p "$TIRX_BUILD"
+rm -f "$TIRX_BUILD/config.cmake"
+tirx_options=()
+if [ "$CPP_TESTS" = "1" ]; then
+ tirx_options=( '-Cbuild.targets=all;tirx_cpptest')
+fi
+cmake_options=("$@")
+if [ "$CPP_TESTS" = "1" ]; then
+ cmake_options+=(-DUSE_GTEST=ON -DBUILD_TESTING=ON)
+fi
+cmake_options+=("-DTVM_OPENCL_HEADERS_ROOT=$PWD/3rdparty/OpenCL-Headers")
+cmake_args=$(IFS=';'; echo "${cmake_options[*]}")
+uv build --wheel --no-build-isolation --offline --python python3 --out-dir "$WHEEL_DIR" ./tirx \
+ -Cbuild-dir="$TIRX_BUILD" -Ccmake.build-type=RelWithDebInfo \
+ -Ccmake.args="$cmake_args" \
+ -Cbuild.verbose="${VERBOSE:-true}" "${tirx_options[@]}"
+if [ "$CPP_TESTS" = "1" ]; then
+ "$TIRX_BUILD/tirx_cpptest"
+fi