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 c748eef52ef59d611076f5d6621c4f9cfc566ebf Author: Tianqi Chen <[email protected]> AuthorDate: Wed Oct 14 21:43:15 2026 +0000 [CI] Build and install wheel artifacts for Jenkins tests Build TVM and its pinned FFI dependency through pyproject, and install those artifacts for Python tests and documentation. Run C++ tests in the same native build before upload, retain the JVM runtime artifact and specialized runtime checks, and replace the legacy build wrapper with direct commands. --- ci/jenkins/data.py | 8 +- ci/jenkins/generated/arm_jenkinsfile.groovy | 59 +++++---- ci/jenkins/generated/cpu_jenkinsfile.groovy | 60 +++++---- ci/jenkins/generated/docker_jenkinsfile.groovy | 54 ++++---- ci/jenkins/generated/gpu_jenkinsfile.groovy | 64 +++++---- ci/jenkins/generated/wasm_jenkinsfile.groovy | 58 ++++---- ci/jenkins/templates/arm_jenkinsfile.groovy.j2 | 5 +- ci/jenkins/templates/cpu_jenkinsfile.groovy.j2 | 6 +- ci/jenkins/templates/gpu_jenkinsfile.groovy.j2 | 10 +- ci/jenkins/templates/utils/Build.groovy.j2 | 46 ++++--- ci/jenkins/templates/utils/Test.groovy.j2 | 6 - ci/jenkins/templates/utils/base.groovy.j2 | 1 - ci/jenkins/templates/wasm_jenkinsfile.groovy.j2 | 4 +- docs/conf.py | 3 +- tests/python/ci/test_wheel_artifacts.py | 167 ++++++++++++++++++++++++ tests/scripts/ci.py | 53 +++++--- tests/scripts/task_build.py | 106 --------------- tests/scripts/task_python_docs.sh | 17 +-- tests/scripts/task_python_install_wheels.sh | 54 ++++++++ tests/scripts/task_python_unittest.sh | 7 +- tests/scripts/task_python_unittest_gpuonly.sh | 7 +- 21 files changed, 486 insertions(+), 309 deletions(-) diff --git a/ci/jenkins/data.py b/ci/jenkins/data.py index 0811451b53..ebf29fc91b 100644 --- a/ci/jenkins/data.py +++ b/ci/jenkins/data.py @@ -31,10 +31,10 @@ data-driven artifact lists. import sys files_to_stash = { - # Executables and build files needed to run c++ tests - "cpptest": ["build/cpptest", "build/build.ninja", "build/CMakeFiles/rules.ninja"], - # runtime files - "tvm_runtime": ["build/lib/libtvm_runtime.so", "build/config.cmake"], + # Exact TVM and pinned FFI wheels, installed by Python test/docs consumers. + "wheels": ["build/wheels"], + # Maven's native assembly still consumes these paths directly. + "tvm_runtime": ["build/lib/libtvm_runtime.so", "build/lib/libtvm_ffi.so"], # compiler files "tvm_lib": [ "build/lib/libtvm_compiler.so", diff --git a/ci/jenkins/generated/arm_jenkinsfile.groovy b/ci/jenkins/generated/arm_jenkinsfile.groovy index 3442caded7..1670f37db7 100644 --- a/ci/jenkins/generated/arm_jenkinsfile.groovy +++ b/ci/jenkins/generated/arm_jenkinsfile.groovy @@ -60,7 +60,7 @@ // 'python3 jenkins/generate.py' // Note: This timestamp is here to ensure that updates to the Jenkinsfile are // always rebased on main before merging: -// Generated at 2026-07-05T01:05:57.719405 +// Generated at 2026-10-14T21:26:39.010162 import org.jenkinsci.plugins.pipeline.modeldefinition.Utils // These are set at runtime from data in ci/jenkins/docker-images.yml, update @@ -411,30 +411,40 @@ def python_unittest(image) { ) } -def make_cpp_tests(image, build_dir) { - sh ( - script: """ - set -eux - ${docker_run} ${image} python3 ./tests/scripts/task_build.py \ - --sccache-bucket tvm-sccache-prod \ - --sccache-region us-west-2 \ - --cmake-target cpptest \ - --build-dir ${build_dir} - """, - label: 'Make C++ tests', - ) +// Keep compiler caching and executor limits identical for wheels and the +// separate runtime-only compile check. Build commands remain at their callers. +def build_environment() { + return ''' + set -eux + export CMAKE_BUILD_PARALLEL_LEVEL=$(( $(getconf _NPROCESSORS_ONLN) / ${CI_NUM_EXECUTORS:-1} )) + if [ "$CMAKE_BUILD_PARALLEL_LEVEL" -lt 1 ]; then export CMAKE_BUILD_PARALLEL_LEVEL=1; fi + if command -v sccache >/dev/null; then + export CC=/opt/sccache/cc CXX=/opt/sccache/c++ + if [ -n "${AWS_ACCESS_KEY_ID:-}" ]; then + export SCCACHE_BUCKET=tvm-sccache-prod SCCACHE_REGION=us-west-2 + fi + sccache --start-server || true + sccache --show-stats + trap "sccache --show-stats" EXIT + fi + ''' } -def cmake_build(image, path) { - sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --build-dir ${path}", - label: 'Run CMake build', - ) -} -def cpp_unittest(image) { +def wheel_build(image, cpp_tests = false) { + def commands = build_environment() + ''' + rm -rf build/wheels + uv build --wheel --python python3 --out-dir build/wheels ./3rdparty/tvm-ffi + uv build --wheel --python python3 --out-dir build/wheels . \ + -Ccmake.build-type=RelWithDebInfo -Cbuild.verbose="${VERBOSE:-true}" + '''.stripIndent().trim() + if (cpp_tests) { + commands += " -Ccmake.define.USE_GTEST=ON -Cbuild.targets=all\\;cpptest" + // cpptest links the same native tree that scikit-build just packaged. + commands += "\n./tests/scripts/task_cpp_unittest.sh build" + } sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_cpp_unittest.sh", - label: 'Run C++ tests', + script: "${docker_run} --env CI_NUM_EXECUTORS ${image} bash -c '${commands}'", + label: 'Build wheel artifacts' + (cpp_tests ? ' and run C++ tests' : ''), ) } @@ -461,10 +471,9 @@ def run_build(node_type) { script: "${docker_run} ${ci_arm} ./tests/scripts/task_config_build_arm.sh build", label: 'Create ARM CMake config', ) - cmake_build(ci_arm, 'build') - make_cpp_tests(ci_arm, 'build') + wheel_build(ci_arm, true) sh( - script: "./${jenkins_scripts_root}/s3.py --action upload --bucket ${s3_bucket} --prefix ${s3_prefix}/arm --bundle tvm_lib --bundle cpptest", + script: "./${jenkins_scripts_root}/s3.py --action upload --bucket ${s3_bucket} --prefix ${s3_prefix}/arm --bundle wheels", label: 'Upload artifacts to S3', ) }) diff --git a/ci/jenkins/generated/cpu_jenkinsfile.groovy b/ci/jenkins/generated/cpu_jenkinsfile.groovy index 0d3e408235..56255dc5fb 100644 --- a/ci/jenkins/generated/cpu_jenkinsfile.groovy +++ b/ci/jenkins/generated/cpu_jenkinsfile.groovy @@ -60,7 +60,7 @@ // 'python3 jenkins/generate.py' // Note: This timestamp is here to ensure that updates to the Jenkinsfile are // always rebased on main before merging: -// Generated at 2026-07-05T01:05:57.741181 +// Generated at 2026-10-14T21:26:39.031061 import org.jenkinsci.plugins.pipeline.modeldefinition.Utils // These are set at runtime from data in ci/jenkins/docker-images.yml, update @@ -411,30 +411,40 @@ def python_unittest(image) { ) } -def make_cpp_tests(image, build_dir) { - sh ( - script: """ - set -eux - ${docker_run} ${image} python3 ./tests/scripts/task_build.py \ - --sccache-bucket tvm-sccache-prod \ - --sccache-region us-west-2 \ - --cmake-target cpptest \ - --build-dir ${build_dir} - """, - label: 'Make C++ tests', - ) +// Keep compiler caching and executor limits identical for wheels and the +// separate runtime-only compile check. Build commands remain at their callers. +def build_environment() { + return ''' + set -eux + export CMAKE_BUILD_PARALLEL_LEVEL=$(( $(getconf _NPROCESSORS_ONLN) / ${CI_NUM_EXECUTORS:-1} )) + if [ "$CMAKE_BUILD_PARALLEL_LEVEL" -lt 1 ]; then export CMAKE_BUILD_PARALLEL_LEVEL=1; fi + if command -v sccache >/dev/null; then + export CC=/opt/sccache/cc CXX=/opt/sccache/c++ + if [ -n "${AWS_ACCESS_KEY_ID:-}" ]; then + export SCCACHE_BUCKET=tvm-sccache-prod SCCACHE_REGION=us-west-2 + fi + sccache --start-server || true + sccache --show-stats + trap "sccache --show-stats" EXIT + fi + ''' } -def cmake_build(image, path) { - sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --build-dir ${path}", - label: 'Run CMake build', - ) -} -def cpp_unittest(image) { +def wheel_build(image, cpp_tests = false) { + def commands = build_environment() + ''' + rm -rf build/wheels + uv build --wheel --python python3 --out-dir build/wheels ./3rdparty/tvm-ffi + uv build --wheel --python python3 --out-dir build/wheels . \ + -Ccmake.build-type=RelWithDebInfo -Cbuild.verbose="${VERBOSE:-true}" + '''.stripIndent().trim() + if (cpp_tests) { + commands += " -Ccmake.define.USE_GTEST=ON -Cbuild.targets=all\\;cpptest" + // cpptest links the same native tree that scikit-build just packaged. + commands += "\n./tests/scripts/task_cpp_unittest.sh build" + } sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_cpp_unittest.sh", - label: 'Run C++ tests', + script: "${docker_run} --env CI_NUM_EXECUTORS ${image} bash -c '${commands}'", + label: 'Build wheel artifacts' + (cpp_tests ? ' and run C++ tests' : ''), ) } @@ -461,10 +471,9 @@ def run_build(node_type) { script: "${docker_run} ${ci_cpu} ./tests/scripts/task_config_build_cpu.sh build", label: 'Create CPU CMake config', ) - cmake_build(ci_cpu, 'build') - make_cpp_tests(ci_cpu, 'build') + wheel_build(ci_cpu, true) sh( - script: "./${jenkins_scripts_root}/s3.py --action upload --bucket ${s3_bucket} --prefix ${s3_prefix}/cpu --bundle tvm_lib --bundle cpptest", + script: "./${jenkins_scripts_root}/s3.py --action upload --bucket ${s3_bucket} --prefix ${s3_prefix}/cpu --bundle wheels", label: 'Upload artifacts to S3', ) }) @@ -521,7 +530,6 @@ def run_unittest_CPU(node_type) { label: 'Download artifacts from S3', ) - cpp_unittest(ci_cpu) python_unittest(ci_cpu) }) } diff --git a/ci/jenkins/generated/docker_jenkinsfile.groovy b/ci/jenkins/generated/docker_jenkinsfile.groovy index 8ffe78942a..3a691900a6 100644 --- a/ci/jenkins/generated/docker_jenkinsfile.groovy +++ b/ci/jenkins/generated/docker_jenkinsfile.groovy @@ -60,7 +60,7 @@ // 'python3 jenkins/generate.py' // Note: This timestamp is here to ensure that updates to the Jenkinsfile are // always rebased on main before merging: -// Generated at 2026-07-05T01:05:57.759905 +// Generated at 2026-10-14T21:26:39.048782 import org.jenkinsci.plugins.pipeline.modeldefinition.Utils // These are set at runtime from data in ci/jenkins/docker-images.yml, update @@ -411,30 +411,40 @@ def python_unittest(image) { ) } -def make_cpp_tests(image, build_dir) { - sh ( - script: """ - set -eux - ${docker_run} ${image} python3 ./tests/scripts/task_build.py \ - --sccache-bucket tvm-sccache-prod \ - --sccache-region us-west-2 \ - --cmake-target cpptest \ - --build-dir ${build_dir} - """, - label: 'Make C++ tests', - ) +// Keep compiler caching and executor limits identical for wheels and the +// separate runtime-only compile check. Build commands remain at their callers. +def build_environment() { + return ''' + set -eux + export CMAKE_BUILD_PARALLEL_LEVEL=$(( $(getconf _NPROCESSORS_ONLN) / ${CI_NUM_EXECUTORS:-1} )) + if [ "$CMAKE_BUILD_PARALLEL_LEVEL" -lt 1 ]; then export CMAKE_BUILD_PARALLEL_LEVEL=1; fi + if command -v sccache >/dev/null; then + export CC=/opt/sccache/cc CXX=/opt/sccache/c++ + if [ -n "${AWS_ACCESS_KEY_ID:-}" ]; then + export SCCACHE_BUCKET=tvm-sccache-prod SCCACHE_REGION=us-west-2 + fi + sccache --start-server || true + sccache --show-stats + trap "sccache --show-stats" EXIT + fi + ''' } -def cmake_build(image, path) { - sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --build-dir ${path}", - label: 'Run CMake build', - ) -} -def cpp_unittest(image) { +def wheel_build(image, cpp_tests = false) { + def commands = build_environment() + ''' + rm -rf build/wheels + uv build --wheel --python python3 --out-dir build/wheels ./3rdparty/tvm-ffi + uv build --wheel --python python3 --out-dir build/wheels . \ + -Ccmake.build-type=RelWithDebInfo -Cbuild.verbose="${VERBOSE:-true}" + '''.stripIndent().trim() + if (cpp_tests) { + commands += " -Ccmake.define.USE_GTEST=ON -Cbuild.targets=all\\;cpptest" + // cpptest links the same native tree that scikit-build just packaged. + commands += "\n./tests/scripts/task_cpp_unittest.sh build" + } sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_cpp_unittest.sh", - label: 'Run C++ tests', + script: "${docker_run} --env CI_NUM_EXECUTORS ${image} bash -c '${commands}'", + label: 'Build wheel artifacts' + (cpp_tests ? ' and run C++ tests' : ''), ) } diff --git a/ci/jenkins/generated/gpu_jenkinsfile.groovy b/ci/jenkins/generated/gpu_jenkinsfile.groovy index e7ce14c4ba..2421da1881 100644 --- a/ci/jenkins/generated/gpu_jenkinsfile.groovy +++ b/ci/jenkins/generated/gpu_jenkinsfile.groovy @@ -60,7 +60,7 @@ // 'python3 jenkins/generate.py' // Note: This timestamp is here to ensure that updates to the Jenkinsfile are // always rebased on main before merging: -// Generated at 2026-07-05T12:52:04.884879 +// Generated at 2026-10-14T21:26:39.068100 import org.jenkinsci.plugins.pipeline.modeldefinition.Utils // These are set at runtime from data in ci/jenkins/docker-images.yml, update @@ -411,30 +411,40 @@ def python_unittest(image) { ) } -def make_cpp_tests(image, build_dir) { - sh ( - script: """ - set -eux - ${docker_run} ${image} python3 ./tests/scripts/task_build.py \ - --sccache-bucket tvm-sccache-prod \ - --sccache-region us-west-2 \ - --cmake-target cpptest \ - --build-dir ${build_dir} - """, - label: 'Make C++ tests', - ) +// Keep compiler caching and executor limits identical for wheels and the +// separate runtime-only compile check. Build commands remain at their callers. +def build_environment() { + return ''' + set -eux + export CMAKE_BUILD_PARALLEL_LEVEL=$(( $(getconf _NPROCESSORS_ONLN) / ${CI_NUM_EXECUTORS:-1} )) + if [ "$CMAKE_BUILD_PARALLEL_LEVEL" -lt 1 ]; then export CMAKE_BUILD_PARALLEL_LEVEL=1; fi + if command -v sccache >/dev/null; then + export CC=/opt/sccache/cc CXX=/opt/sccache/c++ + if [ -n "${AWS_ACCESS_KEY_ID:-}" ]; then + export SCCACHE_BUCKET=tvm-sccache-prod SCCACHE_REGION=us-west-2 + fi + sccache --start-server || true + sccache --show-stats + trap "sccache --show-stats" EXIT + fi + ''' } -def cmake_build(image, path) { - sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --build-dir ${path}", - label: 'Run CMake build', - ) -} -def cpp_unittest(image) { +def wheel_build(image, cpp_tests = false) { + def commands = build_environment() + ''' + rm -rf build/wheels + uv build --wheel --python python3 --out-dir build/wheels ./3rdparty/tvm-ffi + uv build --wheel --python python3 --out-dir build/wheels . \ + -Ccmake.build-type=RelWithDebInfo -Cbuild.verbose="${VERBOSE:-true}" + '''.stripIndent().trim() + if (cpp_tests) { + commands += " -Ccmake.define.USE_GTEST=ON -Cbuild.targets=all\\;cpptest" + // cpptest links the same native tree that scikit-build just packaged. + commands += "\n./tests/scripts/task_cpp_unittest.sh build" + } sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_cpp_unittest.sh", - label: 'Run C++ tests', + script: "${docker_run} --env CI_NUM_EXECUTORS ${image} bash -c '${commands}'", + label: 'Build wheel artifacts' + (cpp_tests ? ' and run C++ tests' : ''), ) } @@ -458,9 +468,9 @@ def run_build(node_type) { 'PLATFORM=gpu', ], { sh "${docker_run} --no-gpu ${ci_gpu} ./tests/scripts/task_config_build_gpu.sh build" - cmake_build("${ci_gpu} --no-gpu", 'build') + wheel_build("${ci_gpu} --no-gpu") sh( - script: "./${jenkins_scripts_root}/s3.py --action upload --bucket ${s3_bucket} --prefix ${s3_prefix}/gpu --bundle tvm_lib --bundle tvm_lib_gpu_extra", + script: "./${jenkins_scripts_root}/s3.py --action upload --bucket ${s3_bucket} --prefix ${s3_prefix}/gpu --bundle wheels --bundle tvm_runtime", label: 'Upload artifacts to S3', ) @@ -468,7 +478,11 @@ def run_build(node_type) { // compiler test sh "rm -rf build" sh "${docker_run} --no-gpu ${ci_gpu} ./tests/scripts/task_config_build_gpu_other.sh build" - sh "${docker_run} --env CI_NUM_EXECUTORS ${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" + def runtime_commands = build_environment() + ''' + cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo + cmake --build build --target tvm_runtime --verbose + ''' + sh "${docker_run} --env CI_NUM_EXECUTORS ${ci_gpu} --no-gpu bash -c '${runtime_commands}'" }) } } diff --git a/ci/jenkins/generated/wasm_jenkinsfile.groovy b/ci/jenkins/generated/wasm_jenkinsfile.groovy index 7421fa86ca..cd9861e7f6 100644 --- a/ci/jenkins/generated/wasm_jenkinsfile.groovy +++ b/ci/jenkins/generated/wasm_jenkinsfile.groovy @@ -60,7 +60,7 @@ // 'python3 jenkins/generate.py' // Note: This timestamp is here to ensure that updates to the Jenkinsfile are // always rebased on main before merging: -// Generated at 2026-07-05T01:05:57.802104 +// Generated at 2026-10-14T21:26:39.087178 import org.jenkinsci.plugins.pipeline.modeldefinition.Utils // These are set at runtime from data in ci/jenkins/docker-images.yml, update @@ -411,30 +411,40 @@ def python_unittest(image) { ) } -def make_cpp_tests(image, build_dir) { - sh ( - script: """ - set -eux - ${docker_run} ${image} python3 ./tests/scripts/task_build.py \ - --sccache-bucket tvm-sccache-prod \ - --sccache-region us-west-2 \ - --cmake-target cpptest \ - --build-dir ${build_dir} - """, - label: 'Make C++ tests', - ) +// Keep compiler caching and executor limits identical for wheels and the +// separate runtime-only compile check. Build commands remain at their callers. +def build_environment() { + return ''' + set -eux + export CMAKE_BUILD_PARALLEL_LEVEL=$(( $(getconf _NPROCESSORS_ONLN) / ${CI_NUM_EXECUTORS:-1} )) + if [ "$CMAKE_BUILD_PARALLEL_LEVEL" -lt 1 ]; then export CMAKE_BUILD_PARALLEL_LEVEL=1; fi + if command -v sccache >/dev/null; then + export CC=/opt/sccache/cc CXX=/opt/sccache/c++ + if [ -n "${AWS_ACCESS_KEY_ID:-}" ]; then + export SCCACHE_BUCKET=tvm-sccache-prod SCCACHE_REGION=us-west-2 + fi + sccache --start-server || true + sccache --show-stats + trap "sccache --show-stats" EXIT + fi + ''' } -def cmake_build(image, path) { - sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --build-dir ${path}", - label: 'Run CMake build', - ) -} -def cpp_unittest(image) { +def wheel_build(image, cpp_tests = false) { + def commands = build_environment() + ''' + rm -rf build/wheels + uv build --wheel --python python3 --out-dir build/wheels ./3rdparty/tvm-ffi + uv build --wheel --python python3 --out-dir build/wheels . \ + -Ccmake.build-type=RelWithDebInfo -Cbuild.verbose="${VERBOSE:-true}" + '''.stripIndent().trim() + if (cpp_tests) { + commands += " -Ccmake.define.USE_GTEST=ON -Cbuild.targets=all\\;cpptest" + // cpptest links the same native tree that scikit-build just packaged. + commands += "\n./tests/scripts/task_cpp_unittest.sh build" + } sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_cpp_unittest.sh", - label: 'Run C++ tests', + script: "${docker_run} --env CI_NUM_EXECUTORS ${image} bash -c '${commands}'", + label: 'Build wheel artifacts' + (cpp_tests ? ' and run C++ tests' : ''), ) } @@ -461,9 +471,7 @@ def run_build(node_type) { script: "${docker_run} ${ci_wasm} ./tests/scripts/task_config_build_wasm.sh build", label: 'Create WASM CMake config', ) - cmake_build(ci_wasm, 'build') - make_cpp_tests(ci_wasm, 'build') - cpp_unittest(ci_wasm) + wheel_build(ci_wasm, true) sh ( script: "${docker_run} ${ci_wasm} ./tests/scripts/task_web_wasm.sh", label: 'Run WASM lint and tests', diff --git a/ci/jenkins/templates/arm_jenkinsfile.groovy.j2 b/ci/jenkins/templates/arm_jenkinsfile.groovy.j2 index 2bddbab4c7..bbc5a2e4d7 100644 --- a/ci/jenkins/templates/arm_jenkinsfile.groovy.j2 +++ b/ci/jenkins/templates/arm_jenkinsfile.groovy.j2 @@ -29,7 +29,6 @@ script: "${docker_run} ${ci_arm} ./tests/scripts/task_config_build_arm.sh build", label: 'Create ARM CMake config', ) - cmake_build(ci_arm, 'build') - make_cpp_tests(ci_arm, 'build') - {{ m.upload_artifacts(tag='arm', bundles=["tvm_lib", "cpptest"]) }} + wheel_build(ci_arm, true) + {{ m.upload_artifacts(tag='arm', bundles=["wheels"]) }} {% endcall %} diff --git a/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2 b/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2 index 62b7c3dfea..b7c7e9ded0 100644 --- a/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2 +++ b/ci/jenkins/templates/cpu_jenkinsfile.groovy.j2 @@ -29,9 +29,8 @@ script: "${docker_run} ${ci_cpu} ./tests/scripts/task_config_build_cpu.sh build", label: 'Create CPU CMake config', ) - cmake_build(ci_cpu, 'build') - make_cpp_tests(ci_cpu, 'build') - {{ m.upload_artifacts(tag='cpu', bundles=["tvm_lib", "cpptest"]) }} + wheel_build(ci_cpu, true) + {{ m.upload_artifacts(tag='cpu', bundles=["wheels"]) }} {% endcall %} {% set test_method_names = [] %} @@ -45,7 +44,6 @@ test_method_names=test_method_names, ) %} {{ m.download_artifacts(tag='cpu') }} - cpp_unittest(ci_cpu) python_unittest(ci_cpu) {% endcall %} diff --git a/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2 b/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2 index 4553fbefa2..d50e9278cb 100644 --- a/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2 +++ b/ci/jenkins/templates/gpu_jenkinsfile.groovy.j2 @@ -26,13 +26,17 @@ platform="gpu", ) %} sh "${docker_run} --no-gpu ${ci_gpu} ./tests/scripts/task_config_build_gpu.sh build" - cmake_build("${ci_gpu} --no-gpu", 'build') - {{ m.upload_artifacts(tag='gpu', bundles=["tvm_lib", "tvm_lib_gpu_extra"]) }} + wheel_build("${ci_gpu} --no-gpu") + {{ m.upload_artifacts(tag='gpu', bundles=["wheels", "tvm_runtime"]) }} // compiler test sh "rm -rf build" sh "${docker_run} --no-gpu ${ci_gpu} ./tests/scripts/task_config_build_gpu_other.sh build" - sh "${docker_run} --env CI_NUM_EXECUTORS ${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" + def runtime_commands = build_environment() + ''' + cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo + cmake --build build --target tvm_runtime --verbose + ''' + sh "${docker_run} --env CI_NUM_EXECUTORS ${ci_gpu} --no-gpu bash -c '${runtime_commands}'" {% endcall %} {% set test_method_names = [] %} diff --git a/ci/jenkins/templates/utils/Build.groovy.j2 b/ci/jenkins/templates/utils/Build.groovy.j2 index 42bd4a991f..fd9ad79ac5 100644 --- a/ci/jenkins/templates/utils/Build.groovy.j2 +++ b/ci/jenkins/templates/utils/Build.groovy.j2 @@ -5,23 +5,39 @@ def python_unittest(image) { ) } -def make_cpp_tests(image, build_dir) { - sh ( - script: """ - set -eux - ${docker_run} ${image} python3 ./tests/scripts/task_build.py \ - --sccache-bucket tvm-sccache-prod \ - --sccache-region us-west-2 \ - --cmake-target cpptest \ - --build-dir ${build_dir} - """, - label: 'Make C++ tests', - ) +// Keep compiler caching and executor limits identical for wheels and the +// separate runtime-only compile check. Build commands remain at their callers. +def build_environment() { + return ''' + set -eux + export CMAKE_BUILD_PARALLEL_LEVEL=$(( $(getconf _NPROCESSORS_ONLN) / ${CI_NUM_EXECUTORS:-1} )) + if [ "$CMAKE_BUILD_PARALLEL_LEVEL" -lt 1 ]; then export CMAKE_BUILD_PARALLEL_LEVEL=1; fi + if command -v sccache >/dev/null; then + export CC=/opt/sccache/cc CXX=/opt/sccache/c++ + if [ -n "${AWS_ACCESS_KEY_ID:-}" ]; then + export SCCACHE_BUCKET=tvm-sccache-prod SCCACHE_REGION=us-west-2 + fi + sccache --start-server || true + sccache --show-stats + trap "sccache --show-stats" EXIT + fi + ''' } -def cmake_build(image, path) { +def wheel_build(image, cpp_tests = false) { + def commands = build_environment() + ''' + rm -rf build/wheels + uv build --wheel --python python3 --out-dir build/wheels ./3rdparty/tvm-ffi + uv build --wheel --python python3 --out-dir build/wheels . \ + -Ccmake.build-type=RelWithDebInfo -Cbuild.verbose="${VERBOSE:-true}" + '''.stripIndent().trim() + if (cpp_tests) { + commands += " -Ccmake.define.USE_GTEST=ON -Cbuild.targets=all\\;cpptest" + // cpptest links the same native tree that scikit-build just packaged. + commands += "\n./tests/scripts/task_cpp_unittest.sh build" + } sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_build.py --sccache-bucket tvm-sccache-prod --sccache-region us-west-2 --build-dir ${path}", - label: 'Run CMake build', + script: "${docker_run} --env CI_NUM_EXECUTORS ${image} bash -c '${commands}'", + label: 'Build wheel artifacts' + (cpp_tests ? ' and run C++ tests' : ''), ) } diff --git a/ci/jenkins/templates/utils/Test.groovy.j2 b/ci/jenkins/templates/utils/Test.groovy.j2 deleted file mode 100644 index 48041983b9..0000000000 --- a/ci/jenkins/templates/utils/Test.groovy.j2 +++ /dev/null @@ -1,6 +0,0 @@ -def cpp_unittest(image) { - sh ( - script: "${docker_run} --env CI_NUM_EXECUTORS ${image} ./tests/scripts/task_cpp_unittest.sh", - label: 'Run C++ tests', - ) -} diff --git a/ci/jenkins/templates/utils/base.groovy.j2 b/ci/jenkins/templates/utils/base.groovy.j2 index a8fda460b7..031ed2ce3a 100644 --- a/ci/jenkins/templates/utils/base.groovy.j2 +++ b/ci/jenkins/templates/utils/base.groovy.j2 @@ -100,7 +100,6 @@ jenkins_scripts_root = "ci/scripts/jenkins" // are purely to satisfy the JVM {% include "utils/Prepare.groovy.j2" %} {% include "utils/Build.groovy.j2" %} -{% include "utils/Test.groovy.j2" %} cancel_previous_build() diff --git a/ci/jenkins/templates/wasm_jenkinsfile.groovy.j2 b/ci/jenkins/templates/wasm_jenkinsfile.groovy.j2 index 8e7d909bd1..80016d8361 100644 --- a/ci/jenkins/templates/wasm_jenkinsfile.groovy.j2 +++ b/ci/jenkins/templates/wasm_jenkinsfile.groovy.j2 @@ -29,9 +29,7 @@ script: "${docker_run} ${ci_wasm} ./tests/scripts/task_config_build_wasm.sh build", label: 'Create WASM CMake config', ) - cmake_build(ci_wasm, 'build') - make_cpp_tests(ci_wasm, 'build') - cpp_unittest(ci_wasm) + wheel_build(ci_wasm, true) sh ( script: "${docker_run} ${ci_wasm} ./tests/scripts/task_web_wasm.sh", label: 'Run WASM lint and tests', diff --git a/docs/conf.py b/docs/conf.py index 599e03ca38..6f40efe457 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,8 @@ if curr_path.name == "_staging": else: tvm_path = Path(os.pardir) -sys.path.insert(0, str(tvm_path.resolve() / "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() / "docs")) # -- General configuration ------------------------------------------------ diff --git a/tests/python/ci/test_wheel_artifacts.py b/tests/python/ci/test_wheel_artifacts.py new file mode 100644 index 0000000000..9eaa8613bc --- /dev/null +++ b/tests/python/ci/test_wheel_artifacts.py @@ -0,0 +1,167 @@ +# 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. +"""Wheel artifact transport and local CI command contracts, without a TVM import.""" + +import importlib.util +import json +import os +import shutil +import subprocess +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[3] + + +def load_local_ci(): + spec = importlib.util.spec_from_file_location("local_ci", ROOT / "tests/scripts/ci.py") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + [email protected]("platform", ["cpu", "gpu", "arm", "wasm"]) +def test_local_wheel_build(monkeypatch, platform): + ci = load_local_ci() + calls = [] + monkeypatch.setattr(ci, "docker", lambda **kwargs: calls.append(kwargs)) + command, _, _ = ci.generate_command(platform, {}, "wheel build") + command(cpp=True) + scripts = calls[0]["scripts"] + wheel = next(script for script in scripts if "-Cbuild-dir=" in script) + assert "-Ccmake.build-type=RelWithDebInfo" in wheel + assert "all;cpptest" in wheel + assert any("./3rdparty/tvm-ffi" in script for script in scripts) + assert any("task_python_install_wheels.sh" in script for script in scripts) == ( + platform != "wasm" + ) + calls.clear() + command(skip_build=True, tests=["tests/python/example.py"]) + assert not any("uv build" in script for script in calls[0]["scripts"]) + + [email protected]("succeed", [True, False]) +def test_wheel_directory_upload_retries_each_transfer(tmp_path, succeed): + # Exercise the existing S3 CLI/retry boundary with a directory bundle. + for relative in [ + "ci/scripts/jenkins/s3.py", + "ci/scripts/jenkins/cmd_utils.py", + "ci/scripts/jenkins/retry.sh", + "ci/jenkins/data.py", + ]: + target = tmp_path / relative + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy(ROOT / relative, target) + wheels = tmp_path / "build/wheels" + wheels.mkdir(parents=True) + (wheels / "apache_tvm.whl").write_text("tvm") + (wheels / "apache_tvm_ffi.whl").write_text("ffi") + binaries = tmp_path / "bin" + binaries.mkdir() + aws = binaries / "aws" + aws.write_text( + f"#!{sys.executable}\n" + "import json, pathlib, sys\n" + "path = pathlib.Path('attempts.json')\n" + "calls = json.loads(path.read_text()) if path.exists() else []\n" + "calls.append(sys.argv[1:])\n" + "path.write_text(json.dumps(calls))\n" + f"sys.exit(0 if {succeed!r} and len(calls) == 3 else 1)\n" + ) + aws.chmod(0o755) + sleep = binaries / "sleep" + sleep.write_text('#!/bin/sh\necho "$1" >> delays.txt\n') + sleep.chmod(0o755) + result = subprocess.run( + [ + sys.executable, + "ci/scripts/jenkins/s3.py", + "--action", + "upload", + "--bucket", + "test", + "--prefix", + "cpu", + "--bundle", + "wheels", + ], + cwd=tmp_path, + env={**os.environ, "PATH": f"{binaries}:{os.environ['PATH']}"}, + capture_output=True, + text=True, + ) + assert (result.returncode == 0) == succeed, result.stdout + result.stderr + attempts = json.loads((tmp_path / "attempts.json").read_text()) + assert len(attempts) == 3 + assert all( + call[-3:] == ["--recursive", "build/wheels", "s3://test/cpu/build/wheels"] + for call in attempts + ) + delays = [int(line) for line in (tmp_path / "delays.txt").read_text().splitlines()] + assert len(delays) == 2 and all(30 <= delay <= 200 for delay in delays) + + [email protected]("fail", [False, True]) +def test_local_interactive_keeps_cache_stats(tmp_path, monkeypatch, fail): + ci = load_local_ci() + monkeypatch.setattr(ci, "check_docker", lambda: None) + monkeypatch.setattr(ci, "cmd", lambda *args, **kwargs: None) + monkeypatch.setattr(ci, "SCRIPT_DIR", tmp_path / "scripts") + monkeypatch.setattr(ci, "REPO_ROOT", tmp_path) + monkeypatch.delenv("USE_SCCACHE", raising=False) + monkeypatch.setenv("DEBUG", "1") + binaries = tmp_path / "bin" + binaries.mkdir() + for name in ["sccache", "bash"]: + binary = binaries / name + binary.write_text(f'#!/bin/sh\necho "{name} $*" >> "$CALL_LOG"\n') + binary.chmod(0o755) + ci.docker("interactive", "ci_cpu", ["false" if fail else "true"], {}, True) + result = subprocess.run( + [shutil.which("bash"), str(ci.SCRIPT_DIR / "interactive.sh")], + env={ + **os.environ, + "PATH": f"{binaries}:{os.environ['PATH']}", + "CALL_LOG": str(tmp_path / "calls"), + }, + capture_output=True, + text=True, + ) + assert (result.returncode != 0) == fail + calls = (tmp_path / "calls").read_text().splitlines() + assert calls == [ + "sccache --start-server", + "sccache --show-stats", + "sccache --show-stats", + "bash ", + ] + + [email protected]("wheel_docs", [False, True]) +def test_docs_import_path(tmp_path, monkeypatch, wheel_docs): + # Run the real Sphinx path bootstrap without requiring Sphinx extensions. + # A stale --target=python FFI package must not precede Jenkins' wheel install. + monkeypatch.chdir(ROOT / "docs") + source_python = str(ROOT / "python") + monkeypatch.setattr(sys, "path", [path for path in sys.path if path != source_python]) + monkeypatch.setenv("TVM_DOCS_USE_WHEEL", "1" if wheel_docs else "0") + config = ROOT / "docs/conf.py" + bootstrap = config.read_text().split("# -- General configuration")[0] + exec(compile(bootstrap, str(config), "exec"), {"__file__": str(config)}) + assert (source_python in sys.path) != wheel_docs diff --git a/tests/scripts/ci.py b/tests/scripts/ci.py index fa027c150a..f242f110b5 100755 --- a/tests/scripts/ci.py +++ b/tests/scripts/ci.py @@ -165,27 +165,24 @@ 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": + if os.getenv("USE_SCCACHE", "1") == "1": scripts = [ - "sccache --start-server", + "if command -v sccache >/dev/null; then", + " export CC=/opt/sccache/cc CXX=/opt/sccache/c++", + " sccache --start-server || true", + " sccache --show-stats", + ' trap "sccache --show-stats' + ("; interact" if interactive else "") + '" EXIT', + "fi", ] + 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["SCCACHE_CACHE_SIZE"] = os.getenv("SCCACHE_CACHE_SIZE", "50G") env["SCCACHE_SERVER_PORT"] = os.getenv("SCCACHE_SERVER_PORT", "4226") + # Match Jenkins' per-executor native build budget, including wheel builds. + scripts = [ + "export CMAKE_BUILD_PARALLEL_LEVEL=$(( $(getconf _NPROCESSORS_ONLN) / " + "${CI_NUM_EXECUTORS:-1} ))", + 'if [ "$CMAKE_BUILD_PARALLEL_LEVEL" -lt 1 ]; then export CMAKE_BUILD_PARALLEL_LEVEL=1; fi', + ] + scripts env["PLATFORM"] = name docker_bash = REPO_ROOT / "docker" / "bash.sh" @@ -292,7 +289,8 @@ def docs( scripts = extra_setup + [ config_script, - f"./tests/scripts/task_build.py --build-dir {build_dir}", + f"cmake -S . -B {build_dir} -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo", + f"cmake --build {build_dir} --verbose", ] if skip_build: @@ -402,9 +400,22 @@ def generate_command( else: scripts = [ f"./tests/scripts/task_config_build_{name}.sh {build_dir}", - f"./tests/scripts/task_build.py --build-dir {build_dir}", + f"rm -rf {build_dir}/wheels", + f"uv build --wheel --python python3 --out-dir {build_dir}/wheels ./3rdparty/tvm-ffi", + f"uv build --wheel --python python3 --out-dir {build_dir}/wheels . " + f"-Cbuild-dir={build_dir} -Ccmake.build-type=RelWithDebInfo " + f"-Cbuild.verbose={'true' if verbose else 'false'}" + + ( + " -Ccmake.define.USE_GTEST=ON '-Cbuild.targets=all;cpptest'" + if kwargs.get("cpp") + else "" + ), ] + # Direct --tests and interactive Python use the same installed artifacts. + if name != "wasm": + scripts.append("source ./tests/scripts/task_python_install_wheels.sh") + if post_build is not None: scripts += post_build @@ -422,10 +433,10 @@ 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) + # The specialized Wasm source tests retain a native build path; + # the wheel installer clears it for Python wheel consumers. "TVM_LIBRARY_PATH": str(REPO_ROOT / get_build_dir(name)), + "TVM_WHEEL_DIR": str(REPO_ROOT / build_dir / "wheels"), "VERBOSE": "true" if verbose else "false", } diff --git a/tests/scripts/task_build.py b/tests/scripts/task_build.py deleted file mode 100755 index c6c30c9bbd..0000000000 --- a/tests/scripts/task_build.py +++ /dev/null @@ -1,106 +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. -# ruff: noqa: E402 -import argparse -import logging -import multiprocessing -import os -import shutil -import sys -from pathlib import Path - -# Hackery to enable importing of utils from ci/scripts/jenkins -REPO_ROOT = Path(__file__).resolve().parent.parent.parent -sys.path.append(str(REPO_ROOT / "ci" / "scripts" / "jenkins")) -from cmd_utils import REPO_ROOT, Sh, init_log - -if __name__ == "__main__": - init_log() - - 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", help="optional build target") - 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 in ["standalone_crt", "crttest"]: - 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 = {} - - 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}") - - sh = Sh(env) - - if use_sccache: - sh.run("sccache --start-server", check=False) - logging.info("===== sccache stats =====") - sh.run("sccache --show-stats") - - 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) - - if args.debug: - sh.run("cmake -GNinja -DCMAKE_BUILD_TYPE=Debug ..", cwd=build_dir) - else: - sh.run("cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo ..", cwd=build_dir) - - target = "" - if args.cmake_target: - target = args.cmake_target - - verbose = os.environ.get("VERBOSE", "true").lower() in {"1", "true", "yes"} - ninja_args = [target, f"-j{num_cpus}"] - if verbose: - ninja_args.append("-v") - - sh.run("cmake --build . -- " + " ".join(ninja_args), cwd=build_dir) - - if use_sccache: - logging.info("===== sccache stats =====") - sh.run("sccache --show-stats") diff --git a/tests/scripts/task_python_docs.sh b/tests/scripts/task_python_docs.sh index e45d439929..73d2662465 100755 --- a/tests/scripts/task_python_docs.sh +++ b/tests/scripts/task_python_docs.sh @@ -18,7 +18,14 @@ set -euxo pipefail -export PYTHONPATH="$(pwd)/python" +# Local docs retain source builds; Jenkins imports the exact build-node wheels. +if [ "${IS_LOCAL:-0}" != "1" ]; then + source "$(dirname "$0")/task_python_install_wheels.sh" + export TVM_DOCS_USE_WHEEL=1 +else + uv pip install -v --target=python ./3rdparty/tvm-ffi/ + export PYTHONPATH="$(pwd)/python" +fi # to avoid CI CPU thread throttling. export TVM_BIND_THREADS=0 @@ -47,9 +54,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 +133,12 @@ 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)/../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..f54c57c9c5 --- /dev/null +++ b/tests/scripts/task_python_install_wheels.sh @@ -0,0 +1,54 @@ +#!/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 wheel environment. +# Docker defaults PYTHONPATH to the checkout; never let it shadow the artifacts. +unset PYTHONPATH TVM_LIBRARY_PATH TVM_FFI_LIBRARY_PATH +export PYTHONNOUSERSITE=1 +TVM_WHEEL_DIR="$(realpath "${TVM_WHEEL_DIR:-$(pwd)/build/wheels}")" +# Preserve the image's active environment (often itself a venv), including when +# local ci.py sources this setup again for a named test suite. +export TVM_IMAGE_PYTHON="${TVM_IMAGE_PYTHON:-$(command -v python3)}" +export TVM_IMAGE_SITE_PACKAGES="${TVM_IMAGE_SITE_PACKAGES:-$("$TVM_IMAGE_PYTHON" -I -c 'import site; print("\n".join(site.getsitepackages()))')}" +export VIRTUAL_ENV="${TVM_WHEEL_DIR%/}/../wheel-venv" +uv venv --python "$TVM_IMAGE_PYTHON" --clear "$VIRTUAL_ENV" +# Append the image dependencies after our wheels without executing its .pth files: +# system-site-packages can install editable-import hooks ahead of the wheel. +TVM_WHEEL_SITE="$("$VIRTUAL_ENV/bin/python3" -c 'import sysconfig; print(sysconfig.get_path("purelib"))')" +printf '%s\n' "$TVM_IMAGE_SITE_PACKAGES" > "$TVM_WHEEL_SITE/image-dependencies.pth" +export PATH="$VIRTUAL_ENV/bin:$PATH" +uv pip install --python "$VIRTUAL_ENV/bin/python3" --no-deps --reinstall \ + "$TVM_WHEEL_DIR"/apache_tvm-*.whl "$TVM_WHEEL_DIR"/apache_tvm_ffi-*.whl + +# Fail before pytest if a source/editable install or native override leaked in. +python3 - <<'PYTHON' +from pathlib import Path +import sys +import tvm +import tvm_ffi + +prefix = Path(sys.prefix).resolve() +for module in (tvm, tvm_ffi): + path = Path(module.__file__).resolve() + assert path.is_relative_to(prefix), path + print(f"Installed {module.__name__}: {path}") +for name, library in {"tvm_ffi": tvm_ffi.LIB, **tvm.base._LOADED_LIBS}.items(): + path = Path(library._name).resolve() + assert path.is_relative_to(prefix), (name, path) + print(f"Installed {name}: {path}") +PYTHON diff --git a/tests/scripts/task_python_unittest.sh b/tests/scripts/task_python_unittest.sh index 511b3ec2a5..8472b142b8 100755 --- a/tests/scripts/task_python_unittest.sh +++ b/tests/scripts/task_python_unittest.sh @@ -18,15 +18,12 @@ set -euxo pipefail -export PYTHONPATH="$(pwd)/python" +source "$(dirname "$0")/task_python_install_wheels.sh" export PYTEST_ADDOPTS="${CI_PYTEST_ADD_OPTIONS:-} ${PYTEST_ADDOPTS:-}" -# setup tvm-ffi into python folder -uv pip install -v --target=python ./3rdparty/tvm-ffi/ - # 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 -python3 -m pytest -vvs -n auto -m "${TVM_TEST_MARKER:-not gpu}" tests/python +python3 -m pytest -vvs -n auto -m "${TVM_TEST_MARKER:-not gpu}" "${@:-tests/python}" diff --git a/tests/scripts/task_python_unittest_gpuonly.sh b/tests/scripts/task_python_unittest_gpuonly.sh index ac9e112b84..9e4b95ec3d 100755 --- a/tests/scripts/task_python_unittest_gpuonly.sh +++ b/tests/scripts/task_python_unittest_gpuonly.sh @@ -31,9 +31,4 @@ 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)/python" -export PYTEST_ADDOPTS="${CI_PYTEST_ADD_OPTIONS:-} ${PYTEST_ADDOPTS:-}" - -python3 -m pytest -vvs -n auto -m "${TVM_TEST_MARKER}" \ - tests/python/backend/vulkan/codegen/test_vulkan.py \ - tests/python/backend/vulkan/test_vulkan_module_export.py +./tests/scripts/task_python_unittest.sh tests/python/codegen/test_target_codegen_vulkan.py
