From 477f21b3fea1b5d87c610b7c0c946492f18b5b77 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 21 Oct 2026 10:11:05 -0500 Subject: [PATCH 1/8] ci: nightly smoke tests for individual split packages Install each of libcuopt-client, libcuopt-mathopt and libcuopt-routing alone (wheel and conda) and build and run a C++ consumer against it. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Ramakrishna Prabhu --- .github/workflows/test.yaml | 35 +++++++++++ ci/split_package_smoke/CMakeLists.txt | 27 +++++++++ ci/split_package_smoke/build_and_run.sh | 26 +++++++++ ci/split_package_smoke/client.cpp | 14 +++++ ci/split_package_smoke/mathopt.cpp | 56 ++++++++++++++++++ ci/split_package_smoke/routing.cpp | 36 ++++++++++++ ci/test_conda_split_packages.sh | 66 +++++++++++++++++++++ ci/test_wheel_split_packages.sh | 77 +++++++++++++++++++++++++ 8 files changed, 337 insertions(+) create mode 100644 ci/split_package_smoke/CMakeLists.txt create mode 100755 ci/split_package_smoke/build_and_run.sh create mode 100644 ci/split_package_smoke/client.cpp create mode 100644 ci/split_package_smoke/mathopt.cpp create mode 100644 ci/split_package_smoke/routing.cpp create mode 100755 ci/test_conda_split_packages.sh create mode 100755 ci/test_wheel_split_packages.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9b39784d6a..4533b97885 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -178,6 +178,39 @@ jobs: script-env-secret-3-key: CUOPT_AWS_SECRET_ACCESS_KEY script-env-secret-3-value: ${{ secrets.CUOPT_AWS_SECRET_ACCESS_KEY }} + # Installs each libcuopt-* package alone and builds and runs a C++ binary against it. + conda-split-package-tests: + if: ${{ inputs.build_type == 'nightly' }} + permissions: + actions: read + contents: read + id-token: write + packages: read + pull-requests: read + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@main + with: + build_type: ${{ inputs.build_type }} + branch: ${{ inputs.branch }} + date: ${{ inputs.date }} + sha: ${{ inputs.sha }} + script: ci/test_conda_split_packages.sh + + wheel-split-package-tests: + if: ${{ inputs.build_type == 'nightly' }} + permissions: + actions: read + contents: read + id-token: write + packages: read + pull-requests: read + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@main + with: + build_type: ${{ inputs.build_type }} + branch: ${{ inputs.branch }} + date: ${{ inputs.date }} + sha: ${{ inputs.sha }} + script: ci/test_wheel_split_packages.sh + conda-notebook-tests: permissions: actions: read @@ -222,6 +255,8 @@ jobs: - java-static-test - wheel-tests-cuopt - wheel-tests-cuopt-server + - conda-split-package-tests + - wheel-split-package-tests - conda-notebook-tests - multi-gpu-cpp-tests uses: ./.github/workflows/nightly-summary.yaml diff --git a/ci/split_package_smoke/CMakeLists.txt b/ci/split_package_smoke/CMakeLists.txt new file mode 100644 index 0000000000..f950da8c0b --- /dev/null +++ b/ci/split_package_smoke/CMakeLists.txt @@ -0,0 +1,27 @@ +# cmake-format: off +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# cmake-format: on + +cmake_minimum_required(VERSION 3.30) +project(cuopt_split_package_smoke LANGUAGES CXX) + +set(COMPONENT "" CACHE STRING "cuOpt component to link: client, mathopt or routing") +if(NOT COMPONENT MATCHES "^(client|mathopt|routing)$") + message(FATAL_ERROR "COMPONENT must be client, mathopt or routing, got '${COMPONENT}'") +endif() + +# Wheels install into lib64, which CMake skips on Debian-derived distros. +set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ON) + +find_package(cuopt REQUIRED) +if(NOT TARGET cuopt::${COMPONENT}) + message(FATAL_ERROR "cuopt::${COMPONENT} was not found in this installation") +endif() + +add_executable(smoke_${COMPONENT} ${COMPONENT}.cpp) +target_compile_features(smoke_${COMPONENT} PRIVATE cxx_std_20) +# Keep the component in the link even if the program uses none of it, so a library that cannot +# load fails at startup. +target_link_options(smoke_${COMPONENT} PRIVATE LINKER:--no-as-needed) +target_link_libraries(smoke_${COMPONENT} PRIVATE cuopt::${COMPONENT}) diff --git a/ci/split_package_smoke/build_and_run.sh b/ci/split_package_smoke/build_and_run.sh new file mode 100755 index 0000000000..3262287e49 --- /dev/null +++ b/ci/split_package_smoke/build_and_run.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Usage: build_and_run.sh +# +# Builds the consumer for one component against whatever is on CMAKE_PREFIX_PATH, then runs it. +# Extra CMake arguments can be passed in CUOPT_SMOKE_CMAKE_ARGS. + +set -euo pipefail + +component=$1 +prefix_path=$2 +src="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +build="$(mktemp -d)/${component}" + +# shellcheck disable=SC2086 +cmake -S "${src}" -B "${build}" -GNinja \ + -DCOMPONENT="${component}" \ + -DCMAKE_PREFIX_PATH="${prefix_path}" \ + ${CUOPT_SMOKE_CMAKE_ARGS:-} +cmake --build "${build}" + +cd "${build}" +"./smoke_${component}" diff --git a/ci/split_package_smoke/client.cpp b/ci/split_package_smoke/client.cpp new file mode 100644 index 0000000000..a43c57b00a --- /dev/null +++ b/ci/split_package_smoke/client.cpp @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include + +// The client's installed headers expose almost nothing to call, so this checks that the library +// and every shared library it links resolve when the binary starts. +int main() +{ + std::printf("CUOPT_SUCCESS=%d\n", CUOPT_SUCCESS); + return CUOPT_SUCCESS; +} diff --git a/ci/split_package_smoke/mathopt.cpp b/ci/split_package_smoke/mathopt.cpp new file mode 100644 index 0000000000..d0555912a8 --- /dev/null +++ b/ci/split_package_smoke/mathopt.cpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include + +// minimize -0.2*x1 + 0.1*x2 s.t. 3*x1 + 4*x2 <= 5.4, 2.7*x1 + 10.1*x2 <= 4.9, x >= 0 +int main() +{ + cuopt_int_t row_offsets[] = {0, 2, 4}; + cuopt_int_t column_indices[] = {0, 1, 0, 1}; + cuopt_float_t values[] = {3.0, 4.0, 2.7, 10.1}; + cuopt_float_t objective[] = {-0.2, 0.1}; + cuopt_float_t con_lower[] = {-CUOPT_INFINITY, -CUOPT_INFINITY}; + cuopt_float_t con_upper[] = {5.4, 4.9}; + cuopt_float_t var_lower[] = {0.0, 0.0}; + cuopt_float_t var_upper[] = {CUOPT_INFINITY, CUOPT_INFINITY}; + char var_types[] = {CUOPT_CONTINUOUS, CUOPT_CONTINUOUS}; + + cuOptOptimizationProblem problem = nullptr; + cuOptSolverSettings settings = nullptr; + cuOptSolution solution = nullptr; + cuopt_int_t termination = -1; + cuopt_float_t objective_value = 0.0; + + cuopt_int_t status = cuOptCreateRangedProblem(2, + 2, + CUOPT_MINIMIZE, + 0.0, + objective, + row_offsets, + column_indices, + values, + con_lower, + con_upper, + var_lower, + var_upper, + var_types, + &problem); + if (status == CUOPT_SUCCESS) status = cuOptCreateSolverSettings(&settings); + if (status == CUOPT_SUCCESS) status = cuOptSolve(problem, settings, &solution); + if (status == CUOPT_SUCCESS) status = cuOptGetTerminationStatus(solution, &termination); + if (status == CUOPT_SUCCESS) status = cuOptGetObjectiveValue(solution, &objective_value); + + cuOptDestroyProblem(&problem); + cuOptDestroySolverSettings(&settings); + cuOptDestroySolution(&solution); + + std::printf("status=%d termination=%d objective=%f\n", status, termination, objective_value); + return status == CUOPT_SUCCESS && termination == CUOPT_TERMINATION_STATUS_OPTIMAL && + std::fabs(objective_value + 0.36) < 1e-3 + ? 0 + : 1; +} diff --git a/ci/split_package_smoke/routing.cpp b/ci/split_package_smoke/routing.cpp new file mode 100644 index 0000000000..d6bfcd9b4c --- /dev/null +++ b/ci/split_package_smoke/routing.cpp @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include +#include + +#include +#include +#include + +// Four locations on a line, one vehicle: the best tour costs 1 + 1 + 1 + 3. +int main() +{ + raft::handle_t handle; + auto stream = handle.get_stream(); + std::vector cost = {0, 1, 2, 3, 1, 0, 1, 2, 2, 1, 0, 1, 3, 2, 1, 0}; + rmm::device_uvector d_cost(cost.size(), stream); + raft::update_device(d_cost.data(), cost.data(), cost.size(), stream); + handle.sync_stream(); + + cuopt::routing::data_model_view_t model(&handle, 4, 1); + model.add_cost_matrix(d_cost.data()); + cuopt::routing::solver_settings_t settings; + settings.set_time_limit(1); + + auto solution = cuopt::routing::solve(model, settings); + std::printf( + "%s objective=%f\n", solution.get_status_string().c_str(), solution.get_total_objective()); + return solution.get_status() == cuopt::routing::solution_status_t::SUCCESS && + std::fabs(solution.get_total_objective() - 6.0) < 1e-3 + ? 0 + : 1; +} diff --git a/ci/test_conda_split_packages.sh b/ci/test_conda_split_packages.sh new file mode 100755 index 0000000000..ceea8ccd1b --- /dev/null +++ b/ci/test_conda_split_packages.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Installs each libcuopt-* conda package on its own, in a fresh environment, and builds and runs a +# C++ binary against it, to catch a package that only works because another one happened to be +# installed. +# +# CUOPT_SMOKE_CHANNELS replaces the downloaded channel, e.g. to test the published nightlies. +# CUOPT_SMOKE_CUDA_VERSION is the CUDA major.minor to use when RAPIDS_CUDA_VERSION is not set. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +COMPONENTS=${COMPONENTS:-"client mathopt routing"} + +# shellcheck disable=SC1091 +. "$(conda info --base)/etc/profile.d/conda.sh" + +if [[ -n "${CUOPT_SMOKE_CHANNELS:-}" ]]; then + read -ra CHANNEL_ARGS <<< "${CUOPT_SMOKE_CHANNELS}" + CUDA_VERSION=${CUOPT_SMOKE_CUDA_VERSION:?set CUOPT_SMOKE_CUDA_VERSION, e.g. 13.3} + create_env() { mamba create --yes "$@"; } +else + conda config --set channel_priority strict + CPP_CHANNEL=$(rapids-download-from-github "$(rapids-artifact-name conda_cpp libcuopt cuopt --cuda "$RAPIDS_CUDA_VERSION")") + RAPIDS_CHANNEL=rapidsai-nightly + rapids-is-release-build && RAPIDS_CHANNEL=rapidsai + CHANNEL_ARGS=(--channel "${CPP_CHANNEL}" --channel "${RAPIDS_CHANNEL}" --channel conda-forge --channel nvidia) + CUDA_VERSION=${RAPIDS_CUDA_VERSION%.*} + create_env() { rapids-mamba-retry create --yes "$@"; } +fi + +FAILED=() +for component in ${COMPONENTS}; do + echo "=== conda: libcuopt-${component} alone ===" + package="libcuopt-${component}" + expected="libcuopt-client ${package}" + [[ "${component}" == "client" ]] && expected="libcuopt-client" + + env_dir="$(mktemp -d)/env" + ( + create_env --prefix "${env_dir}" "${CHANNEL_ARGS[@]}" \ + "${package}" "cuda-version=${CUDA_VERSION}" \ + cmake ninja cxx-compiler cuda-nvcc cuda-cudart-dev + + installed=$(conda list --prefix "${env_dir}" --json | python -c \ + "import json,sys; print(' '.join(sorted(p['name'] for p in json.load(sys.stdin) if p['name'].startswith(('libcuopt','cuopt')))))") + wanted=$(echo "${expected}" | tr ' ' '\n' | sort | tr '\n' ' ' | sed 's/ $//') + if [[ "${installed}" != "${wanted}" ]]; then + echo "installing ${package} pulled in: ${installed} (expected: ${wanted})" >&2 + exit 1 + fi + + set +u + conda activate "${env_dir}" + set -u + bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${env_dir}" + ) || FAILED+=("${component}") +done + +if [[ ${#FAILED[@]} -gt 0 ]]; then + echo "FAILED conda components: ${FAILED[*]}" >&2 + exit 1 +fi diff --git a/ci/test_wheel_split_packages.sh b/ci/test_wheel_split_packages.sh new file mode 100755 index 0000000000..ca7608bb04 --- /dev/null +++ b/ci/test_wheel_split_packages.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Installs each libcuopt-* wheel on its own, in a fresh venv, and builds and runs a C++ binary +# against it, to catch a wheel that only works because another one happened to be installed. +# +# CUOPT_SMOKE_PIP_ARGS replaces the downloaded wheelhouses, e.g. to test the published nightlies. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +COMPONENTS=${COMPONENTS:-"client mathopt routing"} + +if [[ -n "${CUOPT_SMOKE_PIP_ARGS:-}" ]]; then + read -ra PIP_ARGS <<< "${CUOPT_SMOKE_PIP_ARGS}" + SUFFIX=${RAPIDS_PY_CUDA_SUFFIX:?set RAPIDS_PY_CUDA_SUFFIX, e.g. cu13} + pip_install() { pip install "${PIP_ARGS[@]}" "$@"; } +else + source rapids-init-pip + bash "${REPO_ROOT}/ci/utils/install_openssl3_runtime.sh" + + SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")" + CLIENT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_client cuopt)") + MATHOPT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_mathopt cuopt --cuda "$RAPIDS_CUDA_VERSION")") + ROUTING_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_routing cuopt --cuda "$RAPIDS_CUDA_VERSION")") + + cat > "${PIP_CONSTRAINT}" <&2 + exit 1 + fi + + prefixes=$(python - <<'PY' +import importlib, importlib.metadata as md, os +print(";".join(os.path.dirname(importlib.import_module(ep.value.split(":")[0]).__file__) + for ep in md.entry_points(group="cmake.prefix"))) +PY +) + bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${prefixes}" + ) || FAILED+=("${component}") +done + +if [[ ${#FAILED[@]} -gt 0 ]]; then + echo "FAILED wheel components: ${FAILED[*]}" >&2 + exit 1 +fi From 64f5d0ef69429da72edc228726d98f827d5b7cf3 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Fri, 23 Oct 2026 11:43:23 -0500 Subject: [PATCH 2/8] ci: simplify split-package smoke tests Build the mathopt smoke test from the existing C API docs example instead of a duplicate source file, and drop the local-testing override variables. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Ramakrishna Prabhu --- ci/split_package_smoke/CMakeLists.txt | 10 ++++- ci/split_package_smoke/build_and_run.sh | 5 +-- ci/split_package_smoke/mathopt.cpp | 56 ------------------------- ci/test_conda_split_packages.sh | 26 ++++-------- ci/test_wheel_split_packages.sh | 27 ++++-------- 5 files changed, 26 insertions(+), 98 deletions(-) delete mode 100644 ci/split_package_smoke/mathopt.cpp diff --git a/ci/split_package_smoke/CMakeLists.txt b/ci/split_package_smoke/CMakeLists.txt index f950da8c0b..db6249e70b 100644 --- a/ci/split_package_smoke/CMakeLists.txt +++ b/ci/split_package_smoke/CMakeLists.txt @@ -4,7 +4,7 @@ # cmake-format: on cmake_minimum_required(VERSION 3.30) -project(cuopt_split_package_smoke LANGUAGES CXX) +project(cuopt_split_package_smoke LANGUAGES C CXX) set(COMPONENT "" CACHE STRING "cuOpt component to link: client, mathopt or routing") if(NOT COMPONENT MATCHES "^(client|mathopt|routing)$") @@ -19,7 +19,13 @@ if(NOT TARGET cuopt::${COMPONENT}) message(FATAL_ERROR "cuopt::${COMPONENT} was not found in this installation") endif() -add_executable(smoke_${COMPONENT} ${COMPONENT}.cpp) +if(COMPONENT STREQUAL "mathopt") + set(SMOKE_SOURCE ${CMAKE_CURRENT_LIST_DIR}/../../docs/cuopt/source/cuopt-c/convex/examples/simple_lp_example.c) +else() + set(SMOKE_SOURCE ${COMPONENT}.cpp) +endif() + +add_executable(smoke_${COMPONENT} ${SMOKE_SOURCE}) target_compile_features(smoke_${COMPONENT} PRIVATE cxx_std_20) # Keep the component in the link even if the program uses none of it, so a library that cannot # load fails at startup. diff --git a/ci/split_package_smoke/build_and_run.sh b/ci/split_package_smoke/build_and_run.sh index 3262287e49..c1e745bea8 100755 --- a/ci/split_package_smoke/build_and_run.sh +++ b/ci/split_package_smoke/build_and_run.sh @@ -6,7 +6,6 @@ # Usage: build_and_run.sh # # Builds the consumer for one component against whatever is on CMAKE_PREFIX_PATH, then runs it. -# Extra CMake arguments can be passed in CUOPT_SMOKE_CMAKE_ARGS. set -euo pipefail @@ -15,11 +14,9 @@ prefix_path=$2 src="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" build="$(mktemp -d)/${component}" -# shellcheck disable=SC2086 cmake -S "${src}" -B "${build}" -GNinja \ -DCOMPONENT="${component}" \ - -DCMAKE_PREFIX_PATH="${prefix_path}" \ - ${CUOPT_SMOKE_CMAKE_ARGS:-} + -DCMAKE_PREFIX_PATH="${prefix_path}" cmake --build "${build}" cd "${build}" diff --git a/ci/split_package_smoke/mathopt.cpp b/ci/split_package_smoke/mathopt.cpp deleted file mode 100644 index d0555912a8..0000000000 --- a/ci/split_package_smoke/mathopt.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -#include - -#include -#include - -// minimize -0.2*x1 + 0.1*x2 s.t. 3*x1 + 4*x2 <= 5.4, 2.7*x1 + 10.1*x2 <= 4.9, x >= 0 -int main() -{ - cuopt_int_t row_offsets[] = {0, 2, 4}; - cuopt_int_t column_indices[] = {0, 1, 0, 1}; - cuopt_float_t values[] = {3.0, 4.0, 2.7, 10.1}; - cuopt_float_t objective[] = {-0.2, 0.1}; - cuopt_float_t con_lower[] = {-CUOPT_INFINITY, -CUOPT_INFINITY}; - cuopt_float_t con_upper[] = {5.4, 4.9}; - cuopt_float_t var_lower[] = {0.0, 0.0}; - cuopt_float_t var_upper[] = {CUOPT_INFINITY, CUOPT_INFINITY}; - char var_types[] = {CUOPT_CONTINUOUS, CUOPT_CONTINUOUS}; - - cuOptOptimizationProblem problem = nullptr; - cuOptSolverSettings settings = nullptr; - cuOptSolution solution = nullptr; - cuopt_int_t termination = -1; - cuopt_float_t objective_value = 0.0; - - cuopt_int_t status = cuOptCreateRangedProblem(2, - 2, - CUOPT_MINIMIZE, - 0.0, - objective, - row_offsets, - column_indices, - values, - con_lower, - con_upper, - var_lower, - var_upper, - var_types, - &problem); - if (status == CUOPT_SUCCESS) status = cuOptCreateSolverSettings(&settings); - if (status == CUOPT_SUCCESS) status = cuOptSolve(problem, settings, &solution); - if (status == CUOPT_SUCCESS) status = cuOptGetTerminationStatus(solution, &termination); - if (status == CUOPT_SUCCESS) status = cuOptGetObjectiveValue(solution, &objective_value); - - cuOptDestroyProblem(&problem); - cuOptDestroySolverSettings(&settings); - cuOptDestroySolution(&solution); - - std::printf("status=%d termination=%d objective=%f\n", status, termination, objective_value); - return status == CUOPT_SUCCESS && termination == CUOPT_TERMINATION_STATUS_OPTIMAL && - std::fabs(objective_value + 0.36) < 1e-3 - ? 0 - : 1; -} diff --git a/ci/test_conda_split_packages.sh b/ci/test_conda_split_packages.sh index ceea8ccd1b..7a96dfb222 100755 --- a/ci/test_conda_split_packages.sh +++ b/ci/test_conda_split_packages.sh @@ -6,31 +6,20 @@ # Installs each libcuopt-* conda package on its own, in a fresh environment, and builds and runs a # C++ binary against it, to catch a package that only works because another one happened to be # installed. -# -# CUOPT_SMOKE_CHANNELS replaces the downloaded channel, e.g. to test the published nightlies. -# CUOPT_SMOKE_CUDA_VERSION is the CUDA major.minor to use when RAPIDS_CUDA_VERSION is not set. set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -COMPONENTS=${COMPONENTS:-"client mathopt routing"} +COMPONENTS="client mathopt routing" # shellcheck disable=SC1091 . "$(conda info --base)/etc/profile.d/conda.sh" -if [[ -n "${CUOPT_SMOKE_CHANNELS:-}" ]]; then - read -ra CHANNEL_ARGS <<< "${CUOPT_SMOKE_CHANNELS}" - CUDA_VERSION=${CUOPT_SMOKE_CUDA_VERSION:?set CUOPT_SMOKE_CUDA_VERSION, e.g. 13.3} - create_env() { mamba create --yes "$@"; } -else - conda config --set channel_priority strict - CPP_CHANNEL=$(rapids-download-from-github "$(rapids-artifact-name conda_cpp libcuopt cuopt --cuda "$RAPIDS_CUDA_VERSION")") - RAPIDS_CHANNEL=rapidsai-nightly - rapids-is-release-build && RAPIDS_CHANNEL=rapidsai - CHANNEL_ARGS=(--channel "${CPP_CHANNEL}" --channel "${RAPIDS_CHANNEL}" --channel conda-forge --channel nvidia) - CUDA_VERSION=${RAPIDS_CUDA_VERSION%.*} - create_env() { rapids-mamba-retry create --yes "$@"; } -fi +conda config --set channel_priority strict +CPP_CHANNEL=$(rapids-download-from-github "$(rapids-artifact-name conda_cpp libcuopt cuopt --cuda "$RAPIDS_CUDA_VERSION")") +RAPIDS_CHANNEL=rapidsai-nightly +rapids-is-release-build && RAPIDS_CHANNEL=rapidsai +CUDA_VERSION=${RAPIDS_CUDA_VERSION%.*} FAILED=() for component in ${COMPONENTS}; do @@ -41,7 +30,8 @@ for component in ${COMPONENTS}; do env_dir="$(mktemp -d)/env" ( - create_env --prefix "${env_dir}" "${CHANNEL_ARGS[@]}" \ + rapids-mamba-retry create --yes --prefix "${env_dir}" \ + --channel "${CPP_CHANNEL}" --channel "${RAPIDS_CHANNEL}" --channel conda-forge --channel nvidia \ "${package}" "cuda-version=${CUDA_VERSION}" \ cmake ninja cxx-compiler cuda-nvcc cuda-cudart-dev diff --git a/ci/test_wheel_split_packages.sh b/ci/test_wheel_split_packages.sh index ca7608bb04..f126e59d2d 100755 --- a/ci/test_wheel_split_packages.sh +++ b/ci/test_wheel_split_packages.sh @@ -5,34 +5,25 @@ # Installs each libcuopt-* wheel on its own, in a fresh venv, and builds and runs a C++ binary # against it, to catch a wheel that only works because another one happened to be installed. -# -# CUOPT_SMOKE_PIP_ARGS replaces the downloaded wheelhouses, e.g. to test the published nightlies. set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -COMPONENTS=${COMPONENTS:-"client mathopt routing"} +COMPONENTS="client mathopt routing" -if [[ -n "${CUOPT_SMOKE_PIP_ARGS:-}" ]]; then - read -ra PIP_ARGS <<< "${CUOPT_SMOKE_PIP_ARGS}" - SUFFIX=${RAPIDS_PY_CUDA_SUFFIX:?set RAPIDS_PY_CUDA_SUFFIX, e.g. cu13} - pip_install() { pip install "${PIP_ARGS[@]}" "$@"; } -else - source rapids-init-pip - bash "${REPO_ROOT}/ci/utils/install_openssl3_runtime.sh" +source rapids-init-pip +bash "${REPO_ROOT}/ci/utils/install_openssl3_runtime.sh" - SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")" - CLIENT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_client cuopt)") - MATHOPT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_mathopt cuopt --cuda "$RAPIDS_CUDA_VERSION")") - ROUTING_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_routing cuopt --cuda "$RAPIDS_CUDA_VERSION")") +SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")" +CLIENT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_client cuopt)") +MATHOPT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_mathopt cuopt --cuda "$RAPIDS_CUDA_VERSION")") +ROUTING_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_routing cuopt --cuda "$RAPIDS_CUDA_VERSION")") - cat > "${PIP_CONSTRAINT}" < "${PIP_CONSTRAINT}" < Date: Fri, 23 Oct 2026 11:59:49 -0500 Subject: [PATCH 3/8] ci: move split-package smoke test Python into a helper script Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Ramakrishna Prabhu --- ci/split_package_smoke/env_check.py | 81 +++++++++++++++++++++++++++++ ci/test_conda_split_packages.sh | 9 +--- ci/test_wheel_split_packages.sh | 16 ++---- 3 files changed, 86 insertions(+), 20 deletions(-) create mode 100755 ci/split_package_smoke/env_check.py diff --git a/ci/split_package_smoke/env_check.py b/ci/split_package_smoke/env_check.py new file mode 100755 index 0000000000..7363650192 --- /dev/null +++ b/ci/split_package_smoke/env_check.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Helpers for the split-package smoke tests, called from the ci/test_*_split_packages.sh scripts. + +Commands: + conda-packages ... Fail unless exactly these cuOpt conda packages are installed + wheel-packages ... Fail unless exactly these libcuopt wheels are installed + cmake-prefixes Print the CMAKE_PREFIX_PATH of the installed wheels +""" + +import importlib +import importlib.metadata as metadata +import json +import os +import subprocess +import sys + + +def installed_conda_packages(prefix): + out = subprocess.run( + [ + os.environ.get("CONDA_EXE", "conda"), + "list", + "--prefix", + prefix, + "--json", + ], + check=True, + capture_output=True, + text=True, + ).stdout + return { + p["name"] + for p in json.loads(out) + if p["name"].startswith(("libcuopt", "cuopt")) + } + + +def installed_wheels(): + names = ( + d.metadata["Name"].lower().replace("_", "-") + for d in metadata.distributions() + ) + return {n for n in names if n.startswith("libcuopt")} + + +def check(installed, expected): + if installed != set(expected): + print( + f"installed: {sorted(installed)}, expected: {sorted(expected)}", + file=sys.stderr, + ) + sys.exit(1) + + +def cmake_prefixes(): + return ";".join( + os.path.dirname( + importlib.import_module(ep.value.split(":")[0]).__file__ + ) + for ep in metadata.entry_points(group="cmake.prefix") + ) + + +def main(): + command, args = sys.argv[1], sys.argv[2:] + if command == "conda-packages": + check(installed_conda_packages(args[0]), args[1:]) + elif command == "wheel-packages": + check(installed_wheels(), args) + elif command == "cmake-prefixes": + print(cmake_prefixes()) + else: + sys.exit(__doc__) + + +if __name__ == "__main__": + main() diff --git a/ci/test_conda_split_packages.sh b/ci/test_conda_split_packages.sh index 7a96dfb222..a5af1cadc6 100755 --- a/ci/test_conda_split_packages.sh +++ b/ci/test_conda_split_packages.sh @@ -35,13 +35,8 @@ for component in ${COMPONENTS}; do "${package}" "cuda-version=${CUDA_VERSION}" \ cmake ninja cxx-compiler cuda-nvcc cuda-cudart-dev - installed=$(conda list --prefix "${env_dir}" --json | python -c \ - "import json,sys; print(' '.join(sorted(p['name'] for p in json.load(sys.stdin) if p['name'].startswith(('libcuopt','cuopt')))))") - wanted=$(echo "${expected}" | tr ' ' '\n' | sort | tr '\n' ' ' | sed 's/ $//') - if [[ "${installed}" != "${wanted}" ]]; then - echo "installing ${package} pulled in: ${installed} (expected: ${wanted})" >&2 - exit 1 - fi + # shellcheck disable=SC2086 + python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" conda-packages "${env_dir}" ${expected} set +u conda activate "${env_dir}" diff --git a/ci/test_wheel_split_packages.sh b/ci/test_wheel_split_packages.sh index f126e59d2d..355a2f2246 100755 --- a/ci/test_wheel_split_packages.sh +++ b/ci/test_wheel_split_packages.sh @@ -45,19 +45,9 @@ for component in ${COMPONENTS}; do pip install --upgrade pip cmake ninja rapids-pip-retry install --prefer-binary --constraint "${PIP_CONSTRAINT}" "${package}" - installed=$(pip list --format=freeze | sed -E 's/==.*//' | grep -E '^libcuopt' | sort | tr '\n' ' ') - wanted=$(echo "${expected}" | tr ' ' '\n' | sort | tr '\n' ' ') - if [[ "${installed}" != "${wanted}" ]]; then - echo "installing ${package} pulled in: ${installed} (expected: ${wanted})" >&2 - exit 1 - fi - - prefixes=$(python - <<'PY' -import importlib, importlib.metadata as md, os -print(";".join(os.path.dirname(importlib.import_module(ep.value.split(":")[0]).__file__) - for ep in md.entry_points(group="cmake.prefix"))) -PY -) + # shellcheck disable=SC2086 + python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" wheel-packages ${expected} + prefixes=$(python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" cmake-prefixes) bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${prefixes}" ) || FAILED+=("${component}") done From 4e528d487bc4ad8b08d1d966a5290353d869aa62 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Fri, 23 Oct 2026 13:45:40 -0500 Subject: [PATCH 4/8] ci: pass expected packages as arrays in split-package smoke tests Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Ramakrishna Prabhu --- ci/test_conda_split_packages.sh | 8 +++----- ci/test_wheel_split_packages.sh | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/ci/test_conda_split_packages.sh b/ci/test_conda_split_packages.sh index a5af1cadc6..16314462b6 100755 --- a/ci/test_conda_split_packages.sh +++ b/ci/test_conda_split_packages.sh @@ -12,7 +12,6 @@ set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" COMPONENTS="client mathopt routing" -# shellcheck disable=SC1091 . "$(conda info --base)/etc/profile.d/conda.sh" conda config --set channel_priority strict @@ -25,8 +24,8 @@ FAILED=() for component in ${COMPONENTS}; do echo "=== conda: libcuopt-${component} alone ===" package="libcuopt-${component}" - expected="libcuopt-client ${package}" - [[ "${component}" == "client" ]] && expected="libcuopt-client" + expected=(libcuopt-client "${package}") + [[ "${component}" == "client" ]] && expected=(libcuopt-client) env_dir="$(mktemp -d)/env" ( @@ -35,8 +34,7 @@ for component in ${COMPONENTS}; do "${package}" "cuda-version=${CUDA_VERSION}" \ cmake ninja cxx-compiler cuda-nvcc cuda-cudart-dev - # shellcheck disable=SC2086 - python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" conda-packages "${env_dir}" ${expected} + python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" conda-packages "${env_dir}" "${expected[@]}" set +u conda activate "${env_dir}" diff --git a/ci/test_wheel_split_packages.sh b/ci/test_wheel_split_packages.sh index 355a2f2246..ecc8c80a9d 100755 --- a/ci/test_wheel_split_packages.sh +++ b/ci/test_wheel_split_packages.sh @@ -31,22 +31,20 @@ for component in ${COMPONENTS}; do if [[ "${component}" == "client" ]]; then package=libcuopt-client - expected="libcuopt-client" + expected=(libcuopt-client) else package="libcuopt-${component}-${SUFFIX}" - expected="libcuopt-client ${package}" + expected=(libcuopt-client "${package}") fi venv="$(mktemp -d)/venv" python -m venv "${venv}" ( - # shellcheck disable=SC1091 . "${venv}/bin/activate" pip install --upgrade pip cmake ninja rapids-pip-retry install --prefer-binary --constraint "${PIP_CONSTRAINT}" "${package}" - # shellcheck disable=SC2086 - python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" wheel-packages ${expected} + python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" wheel-packages "${expected[@]}" prefixes=$(python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" cmake-prefixes) bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${prefixes}" ) || FAILED+=("${component}") From dd2b2715bf5ebc1eb78edf4fd045d00f0b2f944d Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Fri, 23 Oct 2026 14:06:15 -0500 Subject: [PATCH 5/8] ci: run split-package smoke tests on one matrix entry Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Ramakrishna Prabhu --- .github/workflows/test.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 91cfd6a457..1e640721e7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -178,7 +178,8 @@ jobs: script-env-secret-3-key: CUOPT_AWS_SECRET_ACCESS_KEY script-env-secret-3-value: ${{ secrets.CUOPT_AWS_SECRET_ACCESS_KEY }} - # Installs each libcuopt-* package alone and builds and runs a C++ binary against it. + # Installs each libcuopt-* package alone and builds and runs a C++ binary against it, on the + # latest amd64 CUDA and Python only. conda-split-package-tests: if: ${{ inputs.build_type == 'nightly' }} permissions: @@ -194,6 +195,7 @@ jobs: date: ${{ inputs.date }} sha: ${{ inputs.sha }} script: ci/test_conda_split_packages.sh + matrix_filter: 'map(select(.ARCH == "amd64")) | sort_by(.CUDA_VER, .PY_VER) | [last]' wheel-split-package-tests: if: ${{ inputs.build_type == 'nightly' }} @@ -210,6 +212,7 @@ jobs: date: ${{ inputs.date }} sha: ${{ inputs.sha }} script: ci/test_wheel_split_packages.sh + matrix_filter: 'map(select(.ARCH == "amd64")) | sort_by(.CUDA_VER, .PY_VER) | [last]' conda-notebook-tests: permissions: From a9d7039a2b481a2a1f1d95d765bcad4bac317bca Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Fri, 23 Oct 2026 14:44:19 -0500 Subject: [PATCH 6/8] ci: stop split-package failures being masked, use bash arrays Run each component in a subshell with errexit on and read its status instead of chaining it with ||, which ignored errexit and kept only the last command's result. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Ramakrishna Prabhu --- ci/test_conda_split_packages.sh | 18 ++++++++++++++---- ci/test_wheel_split_packages.sh | 14 +++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ci/test_conda_split_packages.sh b/ci/test_conda_split_packages.sh index 16314462b6..8525e9292a 100755 --- a/ci/test_conda_split_packages.sh +++ b/ci/test_conda_split_packages.sh @@ -10,7 +10,7 @@ set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -COMPONENTS="client mathopt routing" +COMPONENTS=(client mathopt routing) . "$(conda info --base)/etc/profile.d/conda.sh" @@ -21,14 +21,19 @@ rapids-is-release-build && RAPIDS_CHANNEL=rapidsai CUDA_VERSION=${RAPIDS_CUDA_VERSION%.*} FAILED=() -for component in ${COMPONENTS}; do +for component in "${COMPONENTS[@]}"; do echo "=== conda: libcuopt-${component} alone ===" package="libcuopt-${component}" expected=(libcuopt-client "${package}") - [[ "${component}" == "client" ]] && expected=(libcuopt-client) + if [[ "${component}" == "client" ]]; then + expected=(libcuopt-client) + fi env_dir="$(mktemp -d)/env" + # Run the component with errexit on in a subshell and read its status, so an early failure is not masked. + set +e ( + set -e rapids-mamba-retry create --yes --prefix "${env_dir}" \ --channel "${CPP_CHANNEL}" --channel "${RAPIDS_CHANNEL}" --channel conda-forge --channel nvidia \ "${package}" "cuda-version=${CUDA_VERSION}" \ @@ -40,7 +45,12 @@ for component in ${COMPONENTS}; do conda activate "${env_dir}" set -u bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${env_dir}" - ) || FAILED+=("${component}") + ) + status=$? + set -e + if [[ ${status} -ne 0 ]]; then + FAILED+=("${component}") + fi done if [[ ${#FAILED[@]} -gt 0 ]]; then diff --git a/ci/test_wheel_split_packages.sh b/ci/test_wheel_split_packages.sh index ecc8c80a9d..520f1b48b9 100755 --- a/ci/test_wheel_split_packages.sh +++ b/ci/test_wheel_split_packages.sh @@ -9,7 +9,7 @@ set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -COMPONENTS="client mathopt routing" +COMPONENTS=(client mathopt routing) source rapids-init-pip bash "${REPO_ROOT}/ci/utils/install_openssl3_runtime.sh" @@ -26,7 +26,7 @@ libcuopt-routing-${SUFFIX} @ file://$(echo "${ROUTING_WHEELHOUSE}"/libcuopt_rout CONSTRAINTS FAILED=() -for component in ${COMPONENTS}; do +for component in "${COMPONENTS[@]}"; do echo "=== wheel: libcuopt-${component} alone ===" if [[ "${component}" == "client" ]]; then @@ -39,7 +39,10 @@ for component in ${COMPONENTS}; do venv="$(mktemp -d)/venv" python -m venv "${venv}" + # Run the component with errexit on in a subshell and read its status, so an early failure is not masked. + set +e ( + set -e . "${venv}/bin/activate" pip install --upgrade pip cmake ninja rapids-pip-retry install --prefer-binary --constraint "${PIP_CONSTRAINT}" "${package}" @@ -47,7 +50,12 @@ for component in ${COMPONENTS}; do python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" wheel-packages "${expected[@]}" prefixes=$(python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" cmake-prefixes) bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${prefixes}" - ) || FAILED+=("${component}") + ) + status=$? + set -e + if [[ ${status} -ne 0 ]]; then + FAILED+=("${component}") + fi done if [[ ${#FAILED[@]} -gt 0 ]]; then From 57b75998efd02cd10f048bed6cd4e2de26910ddf Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Fri, 23 Oct 2026 14:49:30 -0500 Subject: [PATCH 7/8] ci: use the ERR trap exit-code pattern in split-package tests Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Ramakrishna Prabhu --- ci/test_conda_split_packages.sh | 44 ++++++++++++++------------------- ci/test_wheel_split_packages.sh | 36 +++++++++++---------------- 2 files changed, 32 insertions(+), 48 deletions(-) diff --git a/ci/test_conda_split_packages.sh b/ci/test_conda_split_packages.sh index 8525e9292a..8463de9045 100755 --- a/ci/test_conda_split_packages.sh +++ b/ci/test_conda_split_packages.sh @@ -20,7 +20,10 @@ RAPIDS_CHANNEL=rapidsai-nightly rapids-is-release-build && RAPIDS_CHANNEL=rapidsai CUDA_VERSION=${RAPIDS_CUDA_VERSION%.*} -FAILED=() +EXITCODE=0 +trap "EXITCODE=1" ERR +set +e + for component in "${COMPONENTS[@]}"; do echo "=== conda: libcuopt-${component} alone ===" package="libcuopt-${component}" @@ -30,30 +33,19 @@ for component in "${COMPONENTS[@]}"; do fi env_dir="$(mktemp -d)/env" - # Run the component with errexit on in a subshell and read its status, so an early failure is not masked. - set +e - ( - set -e - rapids-mamba-retry create --yes --prefix "${env_dir}" \ - --channel "${CPP_CHANNEL}" --channel "${RAPIDS_CHANNEL}" --channel conda-forge --channel nvidia \ - "${package}" "cuda-version=${CUDA_VERSION}" \ - cmake ninja cxx-compiler cuda-nvcc cuda-cudart-dev - - python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" conda-packages "${env_dir}" "${expected[@]}" - - set +u - conda activate "${env_dir}" - set -u - bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${env_dir}" - ) - status=$? - set -e - if [[ ${status} -ne 0 ]]; then - FAILED+=("${component}") - fi + rapids-mamba-retry create --yes --prefix "${env_dir}" \ + --channel "${CPP_CHANNEL}" --channel "${RAPIDS_CHANNEL}" --channel conda-forge --channel nvidia \ + "${package}" "cuda-version=${CUDA_VERSION}" \ + cmake ninja cxx-compiler cuda-nvcc cuda-cudart-dev + + python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" conda-packages "${env_dir}" "${expected[@]}" + + set +u + conda activate "${env_dir}" + set -u + bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${env_dir}" + conda deactivate done -if [[ ${#FAILED[@]} -gt 0 ]]; then - echo "FAILED conda components: ${FAILED[*]}" >&2 - exit 1 -fi +rapids-logger "Test script exiting with value: ${EXITCODE}" +exit "${EXITCODE}" diff --git a/ci/test_wheel_split_packages.sh b/ci/test_wheel_split_packages.sh index 520f1b48b9..50c67320a5 100755 --- a/ci/test_wheel_split_packages.sh +++ b/ci/test_wheel_split_packages.sh @@ -25,7 +25,10 @@ libcuopt-mathopt-${SUFFIX} @ file://$(echo "${MATHOPT_WHEELHOUSE}"/libcuopt_math libcuopt-routing-${SUFFIX} @ file://$(echo "${ROUTING_WHEELHOUSE}"/libcuopt_routing_*.whl) CONSTRAINTS -FAILED=() +EXITCODE=0 +trap "EXITCODE=1" ERR +set +e + for component in "${COMPONENTS[@]}"; do echo "=== wheel: libcuopt-${component} alone ===" @@ -39,26 +42,15 @@ for component in "${COMPONENTS[@]}"; do venv="$(mktemp -d)/venv" python -m venv "${venv}" - # Run the component with errexit on in a subshell and read its status, so an early failure is not masked. - set +e - ( - set -e - . "${venv}/bin/activate" - pip install --upgrade pip cmake ninja - rapids-pip-retry install --prefer-binary --constraint "${PIP_CONSTRAINT}" "${package}" - - python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" wheel-packages "${expected[@]}" - prefixes=$(python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" cmake-prefixes) - bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${prefixes}" - ) - status=$? - set -e - if [[ ${status} -ne 0 ]]; then - FAILED+=("${component}") - fi + . "${venv}/bin/activate" + pip install --upgrade pip cmake ninja + rapids-pip-retry install --prefer-binary --constraint "${PIP_CONSTRAINT}" "${package}" + + python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" wheel-packages "${expected[@]}" + prefixes=$(python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" cmake-prefixes) + bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${prefixes}" + deactivate done -if [[ ${#FAILED[@]} -gt 0 ]]; then - echo "FAILED wheel components: ${FAILED[*]}" >&2 - exit 1 -fi +rapids-logger "Test script exiting with value: ${EXITCODE}" +exit "${EXITCODE}" From 26b6bb4dded66815ccd5d38a0378493c439d5a6c Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Fri, 23 Oct 2026 16:40:09 -0500 Subject: [PATCH 8/8] ci: scope set +e in split-package smoke tests and add docstrings Co-Authored-By: Claude Sonnet 5.5 --- ci/split_package_smoke/env_check.py | 5 +++++ ci/test_conda_split_packages.sh | 3 ++- ci/test_wheel_split_packages.sh | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ci/split_package_smoke/env_check.py b/ci/split_package_smoke/env_check.py index 7363650192..5470bbb93f 100755 --- a/ci/split_package_smoke/env_check.py +++ b/ci/split_package_smoke/env_check.py @@ -20,6 +20,7 @@ def installed_conda_packages(prefix): + """Return the names of the cuOpt packages installed in the conda environment at prefix.""" out = subprocess.run( [ os.environ.get("CONDA_EXE", "conda"), @@ -40,6 +41,7 @@ def installed_conda_packages(prefix): def installed_wheels(): + """Return the names of the libcuopt wheels installed in the current environment.""" names = ( d.metadata["Name"].lower().replace("_", "-") for d in metadata.distributions() @@ -48,6 +50,7 @@ def installed_wheels(): def check(installed, expected): + """Exit with an error unless the installed package names are exactly the expected ones.""" if installed != set(expected): print( f"installed: {sorted(installed)}, expected: {sorted(expected)}", @@ -57,6 +60,7 @@ def check(installed, expected): def cmake_prefixes(): + """Return the CMake prefix paths of the installed wheels, joined with ";".""" return ";".join( os.path.dirname( importlib.import_module(ep.value.split(":")[0]).__file__ @@ -66,6 +70,7 @@ def cmake_prefixes(): def main(): + """Run the command named in argv.""" command, args = sys.argv[1], sys.argv[2:] if command == "conda-packages": check(installed_conda_packages(args[0]), args[1:]) diff --git a/ci/test_conda_split_packages.sh b/ci/test_conda_split_packages.sh index 8463de9045..63b345d826 100755 --- a/ci/test_conda_split_packages.sh +++ b/ci/test_conda_split_packages.sh @@ -22,7 +22,6 @@ CUDA_VERSION=${RAPIDS_CUDA_VERSION%.*} EXITCODE=0 trap "EXITCODE=1" ERR -set +e for component in "${COMPONENTS[@]}"; do echo "=== conda: libcuopt-${component} alone ===" @@ -33,6 +32,7 @@ for component in "${COMPONENTS[@]}"; do fi env_dir="$(mktemp -d)/env" + set +e rapids-mamba-retry create --yes --prefix "${env_dir}" \ --channel "${CPP_CHANNEL}" --channel "${RAPIDS_CHANNEL}" --channel conda-forge --channel nvidia \ "${package}" "cuda-version=${CUDA_VERSION}" \ @@ -45,6 +45,7 @@ for component in "${COMPONENTS[@]}"; do set -u bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${env_dir}" conda deactivate + set -e done rapids-logger "Test script exiting with value: ${EXITCODE}" diff --git a/ci/test_wheel_split_packages.sh b/ci/test_wheel_split_packages.sh index 50c67320a5..fdc912a0b3 100755 --- a/ci/test_wheel_split_packages.sh +++ b/ci/test_wheel_split_packages.sh @@ -27,7 +27,6 @@ CONSTRAINTS EXITCODE=0 trap "EXITCODE=1" ERR -set +e for component in "${COMPONENTS[@]}"; do echo "=== wheel: libcuopt-${component} alone ===" @@ -41,6 +40,7 @@ for component in "${COMPONENTS[@]}"; do fi venv="$(mktemp -d)/venv" + set +e python -m venv "${venv}" . "${venv}/bin/activate" pip install --upgrade pip cmake ninja @@ -50,6 +50,7 @@ for component in "${COMPONENTS[@]}"; do prefixes=$(python "${REPO_ROOT}/ci/split_package_smoke/env_check.py" cmake-prefixes) bash "${REPO_ROOT}/ci/split_package_smoke/build_and_run.sh" "${component}" "${prefixes}" deactivate + set -e done rapids-logger "Test script exiting with value: ${EXITCODE}"