diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6d49b8b1d9..6b6c2c9a1c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -208,7 +208,8 @@ jobs: id-token: write packages: read pull-requests: read - needs: [build-details] + # Links against the client/mathopt/routing wheels' installed .so's instead of recompiling them. + needs: [build-details, wheel-build-libcuopt-client, wheel-build-libcuopt-mathopt, wheel-build-libcuopt-routing] secrets: inherit # zizmor: ignore[secrets-inherit] uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@release/26.10 with: @@ -291,7 +292,8 @@ jobs: id-token: write packages: read pull-requests: read - needs: [build-details] + # Links against the client wheel's installed libcuopt_client.so instead of recompiling it. + needs: [build-details, wheel-build-libcuopt-client] secrets: inherit # zizmor: ignore[secrets-inherit] uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@release/26.10 with: @@ -334,7 +336,8 @@ jobs: id-token: write packages: read pull-requests: read - needs: [build-details] + # Links against the client wheel's installed libcuopt_client.so instead of recompiling it. + needs: [build-details, wheel-build-libcuopt-client] secrets: inherit # zizmor: ignore[secrets-inherit] uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@release/26.10 with: @@ -371,7 +374,12 @@ jobs: package-type: cpp publish-wheel-search-key: cuopt_wheel_cpp_libcuopt_routing wheel-build-cuopt: - needs: [build-details, wheel-build-libcuopt] + needs: + - build-details + - wheel-build-libcuopt + - wheel-build-libcuopt-client + - wheel-build-libcuopt-mathopt + - wheel-build-libcuopt-routing permissions: actions: read contents: read diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index ce7783e025..8d35f6c9cc 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -595,25 +595,6 @@ jobs: script: "ci/test_java.sh" artifact-name: "cuopt-java" file_to_upload: "java/cuopt/target/" - wheel-build-libcuopt: - needs: [build-details, compute-matrix-filters, changed-files] - if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_wheels - permissions: - actions: read - contents: read - id-token: write - packages: read - pull-requests: read - secrets: inherit # zizmor: ignore[secrets-inherit] - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@release/26.10 - with: - # build for every combination of arch and CUDA version, but only for the latest Python - matrix_filter: ${{ needs.compute-matrix-filters.outputs.libcuopt_filter }} - package-type: cpp - package-name: libcuopt - build_type: pull-request - build-datetime: ${{ needs.build-details.outputs.build-datetime }} - script: ci/build_wheel_libcuopt.sh wheel-build-libcuopt-client: needs: [build-details, compute-matrix-filters, changed-files] if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_wheels @@ -635,7 +616,7 @@ jobs: build-datetime: ${{ needs.build-details.outputs.build-datetime }} script: ci/build_wheel_libcuopt_client.sh wheel-build-libcuopt-mathopt: - needs: [build-details, compute-matrix-filters, changed-files] + needs: [build-details, compute-matrix-filters, changed-files, wheel-build-libcuopt-client] if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_wheels permissions: actions: read @@ -654,7 +635,7 @@ jobs: build-datetime: ${{ needs.build-details.outputs.build-datetime }} script: ci/build_wheel_libcuopt_mathopt.sh wheel-build-libcuopt-routing: - needs: [build-details, compute-matrix-filters, changed-files] + needs: [build-details, compute-matrix-filters, changed-files, wheel-build-libcuopt-client] if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_wheels permissions: actions: read @@ -672,8 +653,40 @@ jobs: build_type: pull-request build-datetime: ${{ needs.build-details.outputs.build-datetime }} script: ci/build_wheel_libcuopt_routing.sh + wheel-build-libcuopt: + needs: + - build-details + - compute-matrix-filters + - changed-files + - wheel-build-libcuopt-client + - wheel-build-libcuopt-mathopt + - wheel-build-libcuopt-routing + if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_wheels + permissions: + actions: read + contents: read + id-token: write + packages: read + pull-requests: read + secrets: inherit # zizmor: ignore[secrets-inherit] + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@release/26.10 + with: + # build for every combination of arch and CUDA version, but only for the latest Python + matrix_filter: ${{ needs.compute-matrix-filters.outputs.libcuopt_filter }} + package-type: cpp + package-name: libcuopt + build_type: pull-request + build-datetime: ${{ needs.build-details.outputs.build-datetime }} + script: ci/build_wheel_libcuopt.sh wheel-build-cuopt: - needs: [build-details, wheel-build-libcuopt, compute-matrix-filters, changed-files] + needs: + - build-details + - wheel-build-libcuopt + - wheel-build-libcuopt-client + - wheel-build-libcuopt-mathopt + - wheel-build-libcuopt-routing + - compute-matrix-filters + - changed-files if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_wheels permissions: actions: read diff --git a/ci/build_wheel_cuopt.sh b/ci/build_wheel_cuopt.sh index 230e40d7f8..f5ac65f51b 100755 --- a/ci/build_wheel_cuopt.sh +++ b/ci/build_wheel_cuopt.sh @@ -31,6 +31,15 @@ LIBCUOPT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_ echo "libcuopt-${RAPIDS_PY_CUDA_SUFFIX} @ file://$(echo ${LIBCUOPT_WHEELHOUSE}/libcuopt_*.whl)" >> "${PIP_CONSTRAINT}" +LIBCUOPT_CLIENT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_client cuopt)") +echo "libcuopt-client @ file://$(echo ${LIBCUOPT_CLIENT_WHEELHOUSE}/libcuopt_client-*.whl)" >> "${PIP_CONSTRAINT}" + +LIBCUOPT_MATHOPT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_mathopt cuopt --cuda "$RAPIDS_CUDA_VERSION")") +echo "libcuopt-mathopt-${RAPIDS_PY_CUDA_SUFFIX} @ file://$(echo ${LIBCUOPT_MATHOPT_WHEELHOUSE}/libcuopt_mathopt_*.whl)" >> "${PIP_CONSTRAINT}" + +LIBCUOPT_ROUTING_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_routing cuopt --cuda "$RAPIDS_CUDA_VERSION")") +echo "libcuopt-routing-${RAPIDS_PY_CUDA_SUFFIX} @ file://$(echo ${LIBCUOPT_ROUTING_WHEELHOUSE}/libcuopt_routing_*.whl)" >> "${PIP_CONSTRAINT}" + EXCLUDE_ARGS=( --exclude "libraft.so" --exclude "libcublas.so.*" diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh index 807cfb8f21..23175f93aa 100755 --- a/ci/build_wheel_libcuopt.sh +++ b/ci/build_wheel_libcuopt.sh @@ -9,6 +9,17 @@ source rapids-init-pip package_name="libcuopt" package_dir="python/libcuopt" +RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")" + +LIBCUOPT_CLIENT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_client cuopt)") +echo "libcuopt-client @ file://$(echo ${LIBCUOPT_CLIENT_WHEELHOUSE}/libcuopt_client-*.whl)" >> "${PIP_CONSTRAINT}" + +LIBCUOPT_MATHOPT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_mathopt cuopt --cuda "$RAPIDS_CUDA_VERSION")") +echo "libcuopt-mathopt-${RAPIDS_PY_CUDA_SUFFIX} @ file://$(echo ${LIBCUOPT_MATHOPT_WHEELHOUSE}/libcuopt_mathopt_*.whl)" >> "${PIP_CONSTRAINT}" + +LIBCUOPT_ROUTING_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_routing cuopt --cuda "$RAPIDS_CUDA_VERSION")") +echo "libcuopt-routing-${RAPIDS_PY_CUDA_SUFFIX} @ file://$(echo ${LIBCUOPT_ROUTING_WHEELHOUSE}/libcuopt_routing_*.whl)" >> "${PIP_CONSTRAINT}" + # Install rockylinux repo if command -v dnf &> /dev/null; then bash ci/utils/update_rockylinux_repo.sh @@ -25,6 +36,8 @@ elif command -v apt-get &> /dev/null; then apt-get install -y uuid-dev fi +source rapids-configure-sccache + # Install Protobuf + gRPC (protoc + grpc_cpp_plugin) bash ci/utils/install_protobuf_grpc.sh @@ -37,7 +50,7 @@ python ci/utils/install_modern_libgomp.py "${MODERN_LIBGOMP_DIR}" # Also build our own cuDSS threading layer against this same libgomp (see cpp/CMakeLists.txt, # cpp/src/barrier/cudss_mtlayer_cuopt.cpp), instead of cuDSS's prebuilt one, so both actually # share one instance, not just the same flavor. Conda keeps cuDSS's default (#1219 discussion). -export SKBUILD_CMAKE_ARGS="-DOpenMP_gomp_LIBRARY:FILEPATH=${MODERN_LIBGOMP_DIR}/libgomp.so.1.0.0;-DCUOPT_BUILD_CUSTOM_CUDSS_MTLAYER=ON" +export SKBUILD_CMAKE_ARGS="-DOpenMP_gomp_LIBRARY:FILEPATH=${MODERN_LIBGOMP_DIR}/libgomp.so.1.0.0;-DCUOPT_BUILD_CUSTOM_CUDSS_MTLAYER=ON;-DCUOPT_FIND_CLIENT_EXTERNALLY=ON;-DCUOPT_FIND_MATHOPT_EXTERNALLY=ON;-DCUOPT_FIND_ROUTING_EXTERNALLY=ON" # auditwheel repair does its own dependency resolution separately from the compiler; without # this it can't see our fetched copy and silently vendors the old Rocky 8 system one instead. @@ -99,6 +112,9 @@ EXCLUDE_ARGS=( --exclude "libcurand.so.*" --exclude "libcusolver.so.*" --exclude "libcusparse.so.*" + --exclude "libcuopt_client.so" + --exclude "libcuopt_mathopt.so" + --exclude "libcuopt_routing.so" --exclude "libnccl.so.*" --exclude "libnvJitLink.so*" --exclude "librapids_logger.so" diff --git a/ci/build_wheel_libcuopt_client.sh b/ci/build_wheel_libcuopt_client.sh index e561fd271b..fd42997708 100755 --- a/ci/build_wheel_libcuopt_client.sh +++ b/ci/build_wheel_libcuopt_client.sh @@ -29,6 +29,8 @@ elif command -v apt-get &> /dev/null; then apt-get install -y uuid-dev fi +source rapids-configure-sccache + # Install Protobuf + gRPC (protoc + grpc_cpp_plugin) bash ci/utils/install_protobuf_grpc.sh diff --git a/ci/build_wheel_libcuopt_mathopt.sh b/ci/build_wheel_libcuopt_mathopt.sh index 8a86a4b50a..0a7308906c 100755 --- a/ci/build_wheel_libcuopt_mathopt.sh +++ b/ci/build_wheel_libcuopt_mathopt.sh @@ -13,6 +13,9 @@ source rapids-init-pip package_name="libcuopt_mathopt" package_dir="python/libcuopt_mathopt" +LIBCUOPT_CLIENT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_client cuopt)") +echo "libcuopt-client @ file://$(echo ${LIBCUOPT_CLIENT_WHEELHOUSE}/libcuopt_client-*.whl)" >> "${PIP_CONSTRAINT}" + # Install rockylinux repo if command -v dnf &> /dev/null; then bash ci/utils/update_rockylinux_repo.sh @@ -29,6 +32,8 @@ elif command -v apt-get &> /dev/null; then apt-get install -y uuid-dev fi +source rapids-configure-sccache + # Install Protobuf + gRPC (protoc + grpc_cpp_plugin) bash ci/utils/install_protobuf_grpc.sh @@ -41,7 +46,7 @@ python ci/utils/install_modern_libgomp.py "${MODERN_LIBGOMP_DIR}" # Also build our own cuDSS threading layer against this same libgomp (see cpp/CMakeLists.txt, # cpp/src/barrier/cudss_mtlayer_cuopt.cpp), instead of cuDSS's prebuilt one, so both actually # share one instance, not just the same flavor. Conda keeps cuDSS's default (#1219 discussion). -export SKBUILD_CMAKE_ARGS="-DOpenMP_gomp_LIBRARY:FILEPATH=${MODERN_LIBGOMP_DIR}/libgomp.so.1.0.0;-DCUOPT_BUILD_CUSTOM_CUDSS_MTLAYER=ON" +export SKBUILD_CMAKE_ARGS="-DOpenMP_gomp_LIBRARY:FILEPATH=${MODERN_LIBGOMP_DIR}/libgomp.so.1.0.0;-DCUOPT_BUILD_CUSTOM_CUDSS_MTLAYER=ON;-DCUOPT_FIND_CLIENT_EXTERNALLY=ON;-DSKIP_ROUTING_BUILD=ON" # auditwheel repair does its own dependency resolution separately from the compiler; without # this it can't see our fetched copy and silently vendors the old Rocky 8 system one instead. diff --git a/ci/build_wheel_libcuopt_routing.sh b/ci/build_wheel_libcuopt_routing.sh index a5ec02ce09..3388809077 100755 --- a/ci/build_wheel_libcuopt_routing.sh +++ b/ci/build_wheel_libcuopt_routing.sh @@ -13,6 +13,9 @@ source rapids-init-pip package_name="libcuopt_routing" package_dir="python/libcuopt_routing" +LIBCUOPT_CLIENT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_client cuopt)") +echo "libcuopt-client @ file://$(echo ${LIBCUOPT_CLIENT_WHEELHOUSE}/libcuopt_client-*.whl)" >> "${PIP_CONSTRAINT}" + # Install rockylinux repo if command -v dnf &> /dev/null; then bash ci/utils/update_rockylinux_repo.sh @@ -29,6 +32,8 @@ elif command -v apt-get &> /dev/null; then apt-get install -y uuid-dev fi +source rapids-configure-sccache + # Install Protobuf + gRPC (protoc + grpc_cpp_plugin) bash ci/utils/install_protobuf_grpc.sh @@ -41,7 +46,7 @@ python ci/utils/install_modern_libgomp.py "${MODERN_LIBGOMP_DIR}" # Also build our own cuDSS threading layer against this same libgomp (see cpp/CMakeLists.txt, # cpp/src/barrier/cudss_mtlayer_cuopt.cpp), instead of cuDSS's prebuilt one, so both actually # share one instance, not just the same flavor. Conda keeps cuDSS's default (#1219 discussion). -export SKBUILD_CMAKE_ARGS="-DOpenMP_gomp_LIBRARY:FILEPATH=${MODERN_LIBGOMP_DIR}/libgomp.so.1.0.0;-DCUOPT_BUILD_CUSTOM_CUDSS_MTLAYER=ON" +export SKBUILD_CMAKE_ARGS="-DOpenMP_gomp_LIBRARY:FILEPATH=${MODERN_LIBGOMP_DIR}/libgomp.so.1.0.0;-DCUOPT_BUILD_CUSTOM_CUDSS_MTLAYER=ON;-DCUOPT_FIND_CLIENT_EXTERNALLY=ON;-DSKIP_MATHOPT_BUILD=ON;-DSKIP_GRPC_BUILD=ON" # auditwheel repair does its own dependency resolution separately from the compiler; without # this it can't see our fetched copy and silently vendors the old Rocky 8 system one instead. diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index dbe706a4c7..607371396c 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -103,6 +103,9 @@ DEPENDENCIES=( cuopt-server cuopt-sh-client libcuopt + libcuopt-client + libcuopt-mathopt + libcuopt-routing libcuopt-tests libraft-headers librmm diff --git a/ci/test_doc_examples.sh b/ci/test_doc_examples.sh index 5de8526a05..2b27783067 100755 --- a/ci/test_doc_examples.sh +++ b/ci/test_doc_examples.sh @@ -33,6 +33,8 @@ GRPC_PID="" # C library paths (set by find_cuopt_libraries) include_path="" lib_path="" +client_include_path="" +client_lib_path="" # Create results directory mkdir -p "${RESULTS_DIR}" @@ -361,12 +363,14 @@ test_c_examples() { # Clean and build if [ -f "Makefile" ]; then log_info " Building C examples..." - local make_vars="" - [ -n "${include_path}" ] && make_vars="${make_vars} INCLUDE_PATH=${include_path}" - [ -n "${lib_path}" ] && make_vars="${make_vars} LIBCUOPT_LIBRARY_PATH=${lib_path}" + local make_vars=() + [ -n "${include_path}" ] && make_vars+=("INCLUDE_PATH=${include_path}") + [ -n "${lib_path}" ] && make_vars+=("LIBCUOPT_LIBRARY_PATH=${lib_path}") + make_vars+=("CLIENT_INCLUDE_PATH=${client_include_path:-${include_path}}") + make_vars+=("CLIENT_LIBRARY_PATH=${client_lib_path:-${lib_path}}") if make clean > "${RESULTS_DIR}/c-clean-${relative_path//\//_}.log" 2>&1 && \ - make ${make_vars} all > "${RESULTS_DIR}/c-build-${relative_path//\//_}.log" 2>&1; then + make "${make_vars[@]}" all > "${RESULTS_DIR}/c-build-${relative_path//\//_}.log" 2>&1; then log_success " C examples built successfully" else log_failure " Failed to build C examples" @@ -461,6 +465,8 @@ find_cuopt_libraries() { # Reset global variables include_path="" lib_path="" + client_include_path="" + client_lib_path="" # Get Python site-packages directory local site_packages="" @@ -485,9 +491,10 @@ find_cuopt_libraries() { found_header=$(find "${search_dir}" -name "cuopt_c.h" -path "*/mathematical_optimization/*" 2>/dev/null | head -1) if [ -n "${found_header}" ]; then - # Check if this is a Python package installation (contains libcuopt/include) - if echo "${found_header}" | grep -q "/libcuopt/include/"; then - # Python package structure: /path/to/libcuopt/include/cuopt/mathematical_optimization/cuopt_c.h + # Check if this is a Python package installation (contains libcuopt*/include, e.g. + # libcuopt/ or the per-component libcuopt_client/, libcuopt_mathopt/, libcuopt_routing/) + if echo "${found_header}" | grep -qE "/libcuopt[a-z_]*/include/"; then + # Python package structure: /path/to/libcuopt*/include/cuopt/mathematical_optimization/cuopt_c.h # Extract the include directory by going up 3 directories from the header file include_path=$(dirname "$(dirname "$(dirname "${found_header}")")") else @@ -499,32 +506,71 @@ find_cuopt_libraries() { fi if [ -z "${lib_path}" ]; then - # Search for libcuopt.so in both lib and lib64 directories + # The C examples link -lcuopt_mathopt directly, not the libcuopt umbrella (which + # no longer bundles it -- it's in libcuopt-mathopt's own wheel since the split). local found_lib - found_lib=$(find "${search_dir}" -name "libcuopt.so" \( -path "*/lib/*" -o -path "*/lib64/*" \) 2>/dev/null | head -1) + found_lib=$(find "${search_dir}" -name "libcuopt_mathopt.so" \( -path "*/lib/*" -o -path "*/lib64/*" \) 2>/dev/null | head -1) if [ -n "${found_lib}" ]; then lib_path=$(dirname "${found_lib}") fi fi - # Break early if both found - if [ -n "${include_path}" ] && [ -n "${lib_path}" ]; then + if [ -z "${client_include_path}" ] && [ -n "${include_path}" ] && \ + [ ! -f "${include_path}/cuopt/status_codes.h" ]; then + local found_status_codes + found_status_codes=$(find "${search_dir}" -path "*/cuopt/status_codes.h" 2>/dev/null | head -1) + if [ -n "${found_status_codes}" ]; then + client_include_path=$(dirname "$(dirname "${found_status_codes}")") + fi + fi + + if [ -z "${client_lib_path}" ] && [ -n "${lib_path}" ] && \ + [ ! -f "${lib_path}/libcuopt_client.so" ]; then + local found_client_lib + found_client_lib=$(find "${search_dir}" -name "libcuopt_client.so" \( -path "*/lib/*" -o -path "*/lib64/*" \) 2>/dev/null | head -1) + if [ -n "${found_client_lib}" ]; then + client_lib_path=$(dirname "${found_client_lib}") + fi + fi + + # Break early if everything needed is found + if [ -n "${include_path}" ] && [ -n "${lib_path}" ] && \ + { [ -n "${client_include_path}" ] || [ -f "${include_path}/cuopt/status_codes.h" ]; } && \ + { [ -n "${client_lib_path}" ] || [ -f "${lib_path}/libcuopt_client.so" ]; }; then break fi done - if [ -z "${include_path}" ] || [ -z "${lib_path}" ]; then + local client_include_found=false + if [ -n "${client_include_path}" ] || [ -f "${include_path}/cuopt/status_codes.h" ]; then + client_include_found=true + fi + local client_lib_found=false + if [ -n "${client_lib_path}" ] || [ -f "${lib_path}/libcuopt_client.so" ]; then + client_lib_found=true + fi + + if [ -z "${include_path}" ] || [ -z "${lib_path}" ] || \ + [ "${client_include_found}" = false ] || [ "${client_lib_found}" = false ]; then log_failure "Could not find cuOpt headers or libraries" if [ -z "${include_path}" ]; then log_failure " Missing: INCLUDE_PATH (searched for cuopt_c.h)" fi if [ -z "${lib_path}" ]; then - log_failure " Missing: LIBCUOPT_LIBRARY_PATH (searched for libcuopt.so)" + log_failure " Missing: LIBCUOPT_LIBRARY_PATH (searched for libcuopt_mathopt.so)" + fi + if [ "${client_include_found}" = false ]; then + log_failure " Missing: client include path (searched for status_codes.h)" + fi + if [ "${client_lib_found}" = false ]; then + log_failure " Missing: client library path (searched for libcuopt_client.so)" fi return 1 else log_info "Found: INCLUDE_PATH=${include_path}" log_info "Found: LIBCUOPT_LIBRARY_PATH=${lib_path}" + [ -n "${client_include_path}" ] && log_info "Found: client_include_path=${client_include_path}" + [ -n "${client_lib_path}" ] && log_info "Found: client_lib_path=${client_lib_path}" return 0 fi } diff --git a/ci/test_self_hosted_service.sh b/ci/test_self_hosted_service.sh index 803b55be46..ea9839378d 100755 --- a/ci/test_self_hosted_service.sh +++ b/ci/test_self_hosted_service.sh @@ -8,13 +8,24 @@ set -euo pipefail source rapids-init-pip # Download the cuopt built in the previous step +RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")" LIBCUOPT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt cuopt --cuda "$RAPIDS_CUDA_VERSION")") CUOPT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_python cuopt cuopt --stable --cuda "$RAPIDS_CUDA_VERSION")") CUOPT_SERVER_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_python cuopt-server cuopt --pure --arch any --cuda "$RAPIDS_CUDA_VERSION")") +LIBCUOPT_CLIENT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_client cuopt)") +LIBCUOPT_MATHOPT_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_mathopt cuopt --cuda "$RAPIDS_CUDA_VERSION")") +LIBCUOPT_ROUTING_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuopt_routing cuopt --cuda "$RAPIDS_CUDA_VERSION")") + # generate constraints (possibly pinning to oldest support versions of dependencies) rapids-generate-pip-constraints test_python "${PIP_CONSTRAINT}" +cat >> "${PIP_CONSTRAINT}" < "${PIP_CONSTRAINT}" <> "${PIP_CONSTRAINT}" <:-fvisibility=default>") + # cuopt_grpc_server only needs the generated headers (it links the generated symbols from + # cuopt_client.so rather than compiling the protos itself), but those headers are otherwise + # only generated as a side effect of compiling cuopt_client_objs, which is skipped when + # CUOPT_FIND_CLIENT_EXTERNALLY resolves cuopt::client externally. + add_custom_target(cuopt_grpc_proto_headers DEPENDS ${GRPC_PROTO_GENERATED_FILES}) + endif (NOT SKIP_GRPC_BUILD) # ################################################################################################## @@ -741,6 +749,26 @@ set(CUOPT_PRIVATE_CUDA_LIBS get_filename_component(CUDSS_MT_LIB_FILE_NAME "${CUDSS_MT_LIB_FILE}" NAME) +option(CUOPT_FIND_CLIENT_EXTERNALLY "Resolve cuopt::client via find_package(cuopt) instead of building it" OFF) +option(CUOPT_FIND_MATHOPT_EXTERNALLY "Resolve cuopt::mathopt via find_package(cuopt) instead of building it" OFF) +option(CUOPT_FIND_ROUTING_EXTERNALLY "Resolve cuopt::routing via find_package(cuopt) instead of building it" OFF) +if((CUOPT_FIND_CLIENT_EXTERNALLY AND NOT TARGET cuopt::client) OR + (CUOPT_FIND_MATHOPT_EXTERNALLY AND NOT TARGET cuopt::mathopt) OR + (CUOPT_FIND_ROUTING_EXTERNALLY AND NOT TARGET cuopt::routing)) + find_package(cuopt "${RAPIDS_VERSION}" QUIET) +endif() + +# A requested external component must not silently fall back to a local build. +if(CUOPT_FIND_CLIENT_EXTERNALLY AND NOT TARGET cuopt::client) + message(FATAL_ERROR "CUOPT_FIND_CLIENT_EXTERNALLY=ON but find_package(cuopt) did not provide cuopt::client") +endif() +if(CUOPT_FIND_MATHOPT_EXTERNALLY AND NOT TARGET cuopt::mathopt) + message(FATAL_ERROR "CUOPT_FIND_MATHOPT_EXTERNALLY=ON but find_package(cuopt) did not provide cuopt::mathopt") +endif() +if(CUOPT_FIND_ROUTING_EXTERNALLY AND NOT TARGET cuopt::routing) + message(FATAL_ERROR "CUOPT_FIND_ROUTING_EXTERNALLY=ON but find_package(cuopt) did not provide cuopt::routing") +endif() + # - cuopt_client - CPU-only support library ---------------------------------------------------------- # # Host-side problem representation (parsers, data_model_view, mps_data_model, writers), the @@ -750,6 +778,7 @@ get_filename_component(CUDSS_MT_LIB_FILE_NAME "${CUDSS_MT_LIB_FILE}" NAME) # LANGUAGES is deliberately not CUDA: adding a .cu file here should fail loudly rather than # quietly reintroduce a CUDA dependency. Built as an OBJECT library first (mirrored below by # cuopt_routing_objs / cuopt_mathopt_objs) so cuopt_static can link the objects directly. +if(NOT (CUOPT_FIND_CLIENT_EXTERNALLY AND TARGET cuopt::client)) add_library(cuopt_client_objs OBJECT ${CUOPT_CLIENT_SRC_FILES}) # Default visibility, deliberately unlike the other *_objs: carved out of the internals rather # than designed as an export surface, so libcuopt resolves ~239 symbols from it. @@ -767,6 +796,7 @@ set_target_properties(cuopt_client_objs add_library(cuopt_client SHARED $) add_library(cuopt::cuopt_client ALIAS cuopt_client) +add_library(cuopt::client ALIAS cuopt_client) target_include_directories(cuopt_client PUBLIC @@ -857,6 +887,7 @@ target_link_libraries(cuopt_client $<$:protobuf::libprotobuf> $<$:gRPC::grpc++> ) +endif() # ################################################################################################## @@ -887,7 +918,7 @@ endif () # # OBJECT library first, mirroring cuopt_client_objs, so cuopt_static links the same compiled # objects instead of recompiling routing a second time. -if(NOT SKIP_ROUTING_BUILD) +if(NOT SKIP_ROUTING_BUILD AND NOT (CUOPT_FIND_ROUTING_EXTERNALLY AND TARGET cuopt::routing)) add_library(cuopt_routing_objs OBJECT ${CUOPT_ROUTING_SRC_FILES}) set_target_properties(cuopt_routing_objs PROPERTIES POSITION_INDEPENDENT_CODE ON @@ -917,7 +948,7 @@ if(NOT SKIP_ROUTING_BUILD) # library's PUBLIC link interface. target_link_libraries(cuopt_routing_objs PUBLIC - cuopt_client + cuopt::client rmm::rmm rapids_logger::rapids_logger CCCL::CCCL @@ -935,7 +966,7 @@ if(NOT SKIP_ROUTING_BUILD) # and those are host-only so they build into cuopt_client. Without this the # symbols resolve only because something else pulled cuopt_client in, which a # routing-only install would not do. - cuopt_client + cuopt::client rmm::rmm rapids_logger::rapids_logger CCCL::CCCL @@ -947,9 +978,10 @@ if(NOT SKIP_ROUTING_BUILD) add_library(cuopt::routing ALIAS cuopt_routing) endif() -# cuopt_mathopt: LP / MIP / numerical optimization engine +# cuopt_mathopt: LP / MIP / numerical optimization engine (omitted when SKIP_MATHOPT_BUILD=ON) # # OBJECT library first, same reason as cuopt_routing_objs above. +if(NOT SKIP_MATHOPT_BUILD AND NOT (CUOPT_FIND_MATHOPT_EXTERNALLY AND TARGET cuopt::mathopt)) add_library(cuopt_mathopt_objs OBJECT ${CUOPT_MATHOPT_SRC_FILES}) set_target_properties(cuopt_mathopt_objs PROPERTIES POSITION_INDEPENDENT_CODE ON @@ -1004,7 +1036,7 @@ target_compile_definitions(cuopt_mathopt_objs # Repeated on cuopt_mathopt below -- see the note on cuopt_routing_objs above. target_link_libraries(cuopt_mathopt_objs PUBLIC - cuopt_client + cuopt::client rmm::rmm rapids_logger::rapids_logger CCCL::CCCL @@ -1047,7 +1079,7 @@ target_compile_definitions(cuopt_mathopt target_link_libraries(cuopt_mathopt PUBLIC # The parsers and the host-side problem representation live in cuopt_client. - cuopt_client + cuopt::client rmm::rmm rapids_logger::rapids_logger CCCL::CCCL @@ -1073,6 +1105,7 @@ if (TARGET KaMinPar) add_dependencies(cuopt_mathopt KaMinPar) endif () add_library(cuopt::mathopt ALIAS cuopt_mathopt) +endif() # ################################################################################################## @@ -1086,15 +1119,24 @@ endif () # Built for the internal tests, the MIP benchmarks, and for consumers that embed cuOpt into a # single self-contained shared object rather than linking the shared libcuopt (see the Java # classifier JARs). +if ((BUILD_TESTS OR (BUILD_MIP_BENCHMARKS AND NOT BUILD_LP_ONLY) OR CUOPT_BUILD_STATIC_LIB) + AND (CUOPT_FIND_CLIENT_EXTERNALLY OR CUOPT_FIND_MATHOPT_EXTERNALLY OR CUOPT_FIND_ROUTING_EXTERNALLY)) + message(FATAL_ERROR "cuopt_static embeds *_objs directly, which an externally-resolved " + "component doesn't have; it is incompatible with CUOPT_FIND_*_EXTERNALLY") +endif() + if (BUILD_TESTS OR (BUILD_MIP_BENCHMARKS AND NOT BUILD_LP_ONLY) OR CUOPT_BUILD_STATIC_LIB) # Embeds the *_objs directly rather than linking the shared component libraries: internal # tests and the Java classifier JAR reach internals the shared libraries don't export. if(NOT SKIP_ROUTING_BUILD) set(_cuopt_static_routing_objs $) endif() + if(NOT SKIP_MATHOPT_BUILD) + set(_cuopt_static_mathopt_objs $) + endif() add_library(cuopt_static STATIC $ - $ + ${_cuopt_static_mathopt_objs} ${_cuopt_static_routing_objs} ) target_link_libraries(cuopt_static @@ -1155,7 +1197,10 @@ endif (BUILD_TESTS) # forbids, so without it a -lcuopt consumer calling anything the client owns -- the # parsers, the host problem representation, the gRPC client -- fails to link with # "DSO missing from command line". -set(CUOPT_LINKER_SCRIPT_INPUTS "libcuopt_mathopt.so libcuopt_client.so") +set(CUOPT_LINKER_SCRIPT_INPUTS "libcuopt_client.so") +if(NOT SKIP_MATHOPT_BUILD) + string(APPEND CUOPT_LINKER_SCRIPT_INPUTS " libcuopt_mathopt.so") +endif() if(NOT SKIP_ROUTING_BUILD) string(APPEND CUOPT_LINKER_SCRIPT_INPUTS " libcuopt_routing.so") endif() @@ -1167,6 +1212,7 @@ configure_file( @ONLY ) +if(NOT TARGET cuopt::cuopt) add_library(cuopt INTERFACE) add_library(cuopt::cuopt ALIAS cuopt) @@ -1176,6 +1222,7 @@ target_include_directories(cuopt "$" "$" ) +endif() # Deliberately no component links. CMake will not export a target that references # targets outside its own export set, and each component exports separately now so its @@ -1194,8 +1241,11 @@ if (WRITE_FATBIN) ]=]) # Applies to the components that actually carry CUDA fatbins. Before the split this # was set on libcuopt.so, which held all the device code; it now holds none. - set(_CUOPT_FATBIN_TARGETS cuopt_mathopt) - if(NOT SKIP_ROUTING_BUILD) + set(_CUOPT_FATBIN_TARGETS "") + if(TARGET cuopt_mathopt) + list(APPEND _CUOPT_FATBIN_TARGETS cuopt_mathopt) + endif() + if(NOT SKIP_ROUTING_BUILD AND TARGET cuopt_routing) list(APPEND _CUOPT_FATBIN_TARGETS cuopt_routing) endif() foreach(_target ${_CUOPT_FATBIN_TARGETS}) @@ -1213,7 +1263,10 @@ set(CPACK_DEB_COMPONENT_INSTALL ON) # runtime .deb empty now that nothing installs under `runtime`. # `cuopt` is rapids_export's own component, holding cuopt-config.cmake and the export # set. It is easy to miss because it is named after the project rather than a library. -set(CPACK_COMPONENTS_ALL runtime dev cuopt client mathopt client-dev mathopt-dev) +set(CPACK_COMPONENTS_ALL runtime dev cuopt client client-dev) +if(NOT SKIP_MATHOPT_BUILD) + list(APPEND CPACK_COMPONENTS_ALL mathopt mathopt-dev) +endif() if(NOT SKIP_ROUTING_BUILD) list(APPEND CPACK_COMPONENTS_ALL routing routing-dev) endif() @@ -1235,8 +1288,13 @@ else () set(_INCLUDE_DEST include/cuopt/) endif () -set(CUOPT_COMPONENT_TARGETS cuopt_mathopt cuopt_client) -if(NOT SKIP_ROUTING_BUILD) +set(CUOPT_COMPONENT_TARGETS "") +foreach(_c cuopt_mathopt cuopt_client) + if(TARGET ${_c}) + list(APPEND CUOPT_COMPONENT_TARGETS ${_c}) + endif() +endforeach() +if(NOT SKIP_ROUTING_BUILD AND TARGET cuopt_routing) list(APPEND CUOPT_COMPONENT_TARGETS cuopt_routing) endif() @@ -1282,9 +1340,11 @@ endforeach() # only -- CMake requires everything an exported target references to share its export set, # which would defeat the split -- so the installed config synthesises them instead, from # whichever components that installation actually has. See the FINAL_CODE_BLOCK below. +if(TARGET cuopt) install(TARGETS cuopt EXPORT cuopt-exports ) +endif() # libcuopt.so is the ld script that keeps -lcuopt working. It is a link-time artifact, # so it belongs to the dev component; nothing loads it at runtime. @@ -1304,7 +1364,7 @@ endif () # Headers follow their component. Shared ones go to client-dev, the leaf every # other component links. -install(FILES include/cuopt/error.hpp include/cuopt/export.hpp +install(FILES include/cuopt/error.hpp include/cuopt/export.hpp include/cuopt/status_codes.h DESTINATION ${_INCLUDE_DEST} COMPONENT client-dev ) @@ -1423,6 +1483,7 @@ set_property(TARGET cuopt::cuopt APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${_cuopt_umbrella_links}") ]=]) +if(TARGET cuopt) rapids_export(INSTALL cuopt EXPORT_SET cuopt-exports GLOBAL_TARGETS ${CUOPT_COMPONENT_EXPORT_NAMES} @@ -1440,6 +1501,7 @@ rapids_export(BUILD cuopt DOCUMENTATION doc_string FINAL_CODE_BLOCK cuopt_code_string ) +endif() # ################################################################################################## # - make documentation ---------------------------------------------------------------------------- @@ -1468,7 +1530,7 @@ rapids_cpm_find( GIT_SHALLOW TRUE ) -if (NOT BUILD_LP_ONLY) +if (NOT BUILD_LP_ONLY AND NOT SKIP_MATHOPT_BUILD) add_executable(cuopt_cli cuopt_cli.cpp) # PIE executable: auditwheel/patchelf expands .dynstr/RPATH when repairing wheels; non-PIE @@ -1501,8 +1563,8 @@ if (NOT BUILD_LP_ONLY) # because --no-copy-dt-needed-entries will not let it reach client through mathopt. target_link_libraries(cuopt_cli PUBLIC - cuopt_mathopt - cuopt_client + cuopt::mathopt + cuopt::client OpenMP::OpenMP_CXX ${CUDSS_LIBRARIES} TBB::tbb @@ -1623,6 +1685,7 @@ if (NOT SKIP_GRPC_BUILD) src/grpc/server/grpc_job_management.cpp src/grpc/server/grpc_service_impl.cpp ) + add_dependencies(cuopt_grpc_server cuopt_grpc_proto_headers) set_target_properties(cuopt_grpc_server PROPERTIES @@ -1657,13 +1720,19 @@ if (NOT SKIP_GRPC_BUILD) find_library(UUID_LIBRARY uuid REQUIRED) + # grpc_worker.cpp calls into the mathopt engine unconditionally, unlike routing which has + # a SKIP_ROUTING_BUILD opt-out. A routing-only wheel should set SKIP_GRPC_BUILD=ON instead. + if (SKIP_MATHOPT_BUILD) + message(FATAL_ERROR "cuopt_grpc_server requires mathopt; set SKIP_GRPC_BUILD=ON too") + endif () + # Links both engines directly. It used to reach cuopt_routing transitively through # cuopt_grpc; that component is gone, and the server is the one artifact that solves # both VRP and LP/MIP, so the dependency is now explicit. target_link_libraries(cuopt_grpc_server PUBLIC - cuopt_mathopt - $<$>:cuopt_routing> + cuopt::mathopt + $<$>:cuopt::routing> OpenMP::OpenMP_CXX PRIVATE protobuf::libprotobuf diff --git a/cpp/doxygen/Doxyfile b/cpp/doxygen/Doxyfile index 8bd2dfa906..5df5653fb9 100644 --- a/cpp/doxygen/Doxyfile +++ b/cpp/doxygen/Doxyfile @@ -773,7 +773,8 @@ WARN_LOGFILE = INPUT = main_page.md \ ../include/cuopt/mathematical_optimization/cuopt_c.h \ - ../include/cuopt/mathematical_optimization/constants.h + ../include/cuopt/mathematical_optimization/constants.h \ + ../include/cuopt/status_codes.h # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/cpp/include/cuopt/error.hpp b/cpp/include/cuopt/error.hpp index 40be060c5b..119fe7dfab 100644 --- a/cpp/include/cuopt/error.hpp +++ b/cpp/include/cuopt/error.hpp @@ -7,7 +7,7 @@ #pragma once #include -#include "cuopt/mathematical_optimization/constants.h" +#include "cuopt/status_codes.h" #include diff --git a/cpp/include/cuopt/mathematical_optimization/constants.h b/cpp/include/cuopt/mathematical_optimization/constants.h index 3b80fa4a67..9ea2f052e6 100644 --- a/cpp/include/cuopt/mathematical_optimization/constants.h +++ b/cpp/include/cuopt/mathematical_optimization/constants.h @@ -243,14 +243,8 @@ /* @brief File format constants for problem I/O */ #define CUOPT_FILE_FORMAT_MPS 0 -/* @brief Status codes constants */ -#define CUOPT_SUCCESS 0 -#define CUOPT_INVALID_ARGUMENT 1 -#define CUOPT_MPS_FILE_ERROR 2 -#define CUOPT_MPS_PARSE_ERROR 3 -#define CUOPT_VALIDATION_ERROR 4 -#define CUOPT_OUT_OF_MEMORY 5 -#define CUOPT_RUNTIME_ERROR 6 +/* @brief Status codes constants -- shared with cuopt::client, defined in status_codes.h */ +#include "cuopt/status_codes.h" #define CUOPT_PRESOLVE_DEFAULT -1 #define CUOPT_PRESOLVE_OFF 0 diff --git a/cpp/include/cuopt/status_codes.h b/cpp/include/cuopt/status_codes.h new file mode 100644 index 0000000000..dc9fa006e8 --- /dev/null +++ b/cpp/include/cuopt/status_codes.h @@ -0,0 +1,20 @@ +/* clang-format off */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* clang-format on */ + +#ifndef CUOPT_STATUS_CODES_H +#define CUOPT_STATUS_CODES_H + +/* @brief Status codes constants */ +#define CUOPT_SUCCESS 0 +#define CUOPT_INVALID_ARGUMENT 1 +#define CUOPT_MPS_FILE_ERROR 2 +#define CUOPT_MPS_PARSE_ERROR 3 +#define CUOPT_VALIDATION_ERROR 4 +#define CUOPT_OUT_OF_MEMORY 5 +#define CUOPT_RUNTIME_ERROR 6 + +#endif // CUOPT_STATUS_CODES_H diff --git a/dependencies.yaml b/dependencies.yaml index 04a4263147..f9a41ea05f 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -136,9 +136,13 @@ files: key: requires includes: - build_common + - depends_on_libcuopt_client + - depends_on_libcuopt_mathopt + - depends_on_libcuopt_routing - depends_on_libraft_headers - depends_on_librmm - depends_on_rapids_logger + # Thin metapackage: depends on the three component wheels instead of bundling them. py_run_libcuopt: output: pyproject pyproject_dir: python/libcuopt @@ -148,8 +152,9 @@ files: extras: table: project includes: - - cuda_wheels - - depends_on_libraft_headers + - depends_on_libcuopt_client + - depends_on_libcuopt_mathopt + - depends_on_libcuopt_routing - depends_on_librmm - depends_on_rapids_logger # One set of keys per component wheel. The point of the split is in py_run_*: @@ -204,6 +209,7 @@ files: key: requires includes: - build_common + - depends_on_libcuopt_client - depends_on_libraft_headers - depends_on_librmm - depends_on_rapids_logger @@ -239,6 +245,7 @@ files: key: requires includes: - build_common + - depends_on_libcuopt_client - depends_on_libraft_headers - depends_on_librmm - depends_on_rapids_logger @@ -276,6 +283,10 @@ files: - build_common - build_python_common - depends_on_libcuopt + # needed for find_package(cuopt) to resolve cuopt::client/mathopt/routing + - depends_on_libcuopt_client + - depends_on_libcuopt_mathopt + - depends_on_libcuopt_routing - depends_on_rapids_logger - depends_on_cupy - depends_on_pylibraft diff --git a/docs/cuopt/source/cuopt-c/convex/examples/Makefile b/docs/cuopt/source/cuopt-c/convex/examples/Makefile index f89430bf47..6078af69c3 100644 --- a/docs/cuopt/source/cuopt-c/convex/examples/Makefile +++ b/docs/cuopt/source/cuopt-c/convex/examples/Makefile @@ -20,7 +20,8 @@ # # Prerequisites: # - cuOpt C library installed or built locally -# - Set INCLUDE_PATH and LIBCUOPT_LIBRARY_PATH (see below) +# - Set INCLUDE_PATH/LIBCUOPT_LIBRARY_PATH (libcuopt-mathopt) and +# CLIENT_INCLUDE_PATH/CLIENT_LIBRARY_PATH (libcuopt-client) (see below) # Compiler CC = gcc @@ -28,25 +29,32 @@ CC = gcc # Set include and library paths # REQUIRED: Set these paths either: # 1. As environment variables -# 2. On command line: make INCLUDE_PATH=... LIBCUOPT_LIBRARY_PATH=... +# 2. On command line: make INCLUDE_PATH=... LIBCUOPT_LIBRARY_PATH=... CLIENT_INCLUDE_PATH=... CLIENT_LIBRARY_PATH=... # 3. Uncomment and edit below for your system: # -# INCLUDE_PATH = /path/to/cuopt/include -# LIBCUOPT_LIBRARY_PATH = /path/to/cuopt/lib +# INCLUDE_PATH = /path/to/libcuopt_mathopt/include +# LIBCUOPT_LIBRARY_PATH = /path/to/libcuopt_mathopt/lib +# CLIENT_INCLUDE_PATH = /path/to/libcuopt_client/include +# CLIENT_LIBRARY_PATH = /path/to/libcuopt_client/lib # # Example for local build: # INCLUDE_PATH = ../../../cpp/include # LIBCUOPT_LIBRARY_PATH = ../../../cpp/build +# CLIENT_INCLUDE_PATH = ../../../cpp/include +# CLIENT_LIBRARY_PATH = ../../../cpp/build # # Example for conda install: # INCLUDE_PATH = $(CONDA_PREFIX)/include # LIBCUOPT_LIBRARY_PATH = $(CONDA_PREFIX)/lib +# CLIENT_INCLUDE_PATH = $(CONDA_PREFIX)/include +# CLIENT_LIBRARY_PATH = $(CONDA_PREFIX)/lib # # Note: The test script automatically finds and passes these paths # Compiler flags -CFLAGS = -I$(INCLUDE_PATH) -Wall -Wextra -LDFLAGS = -L$(LIBCUOPT_LIBRARY_PATH) -lcuopt_mathopt -Wl,-rpath,$(LIBCUOPT_LIBRARY_PATH) +CFLAGS = -I$(INCLUDE_PATH) -I$(CLIENT_INCLUDE_PATH) -Wall -Wextra +LDFLAGS = -L$(LIBCUOPT_LIBRARY_PATH) -L$(CLIENT_LIBRARY_PATH) -Wl,--no-as-needed -lcuopt_mathopt -lcuopt_client \ + -Wl,-rpath,$(LIBCUOPT_LIBRARY_PATH) -Wl,-rpath,$(CLIENT_LIBRARY_PATH) # Automatically discover all C source files in current directory SOURCES = $(wildcard *.c) @@ -82,22 +90,13 @@ clean: # Check if required paths are set check-paths: - @if [ -z "$(INCLUDE_PATH)" ]; then \ - echo "ERROR: INCLUDE_PATH is not set"; \ + @if [ -z "$(INCLUDE_PATH)" ] || [ -z "$(LIBCUOPT_LIBRARY_PATH)" ] || \ + [ -z "$(CLIENT_INCLUDE_PATH)" ] || [ -z "$(CLIENT_LIBRARY_PATH)" ]; then \ + echo "ERROR: one or more required paths are not set"; \ echo ""; \ - echo "Please set it using one of these methods:"; \ - echo " 1. Environment: export INCLUDE_PATH=/path/to/cuopt/include"; \ - echo " 2. Command line: make INCLUDE_PATH=/path/to/cuopt/include LIBCUOPT_LIBRARY_PATH=/path/to/lib"; \ - echo " 3. Edit Makefile and uncomment/set the path variables"; \ - echo ""; \ - exit 1; \ - fi - @if [ -z "$(LIBCUOPT_LIBRARY_PATH)" ]; then \ - echo "ERROR: LIBCUOPT_LIBRARY_PATH is not set"; \ - echo ""; \ - echo "Please set it using one of these methods:"; \ - echo " 1. Environment: export LIBCUOPT_LIBRARY_PATH=/path/to/cuopt/lib"; \ - echo " 2. Command line: make LIBCUOPT_LIBRARY_PATH=/path/to/lib"; \ + echo "Please set them using one of these methods:"; \ + echo " 1. Environment: export INCLUDE_PATH=... LIBCUOPT_LIBRARY_PATH=... CLIENT_INCLUDE_PATH=... CLIENT_LIBRARY_PATH=..."; \ + echo " 2. Command line: make INCLUDE_PATH=... LIBCUOPT_LIBRARY_PATH=... CLIENT_INCLUDE_PATH=... CLIENT_LIBRARY_PATH=..."; \ echo " 3. Edit Makefile and uncomment/set the path variables"; \ echo ""; \ exit 1; \ @@ -130,6 +129,8 @@ help: @echo "Current Configuration:" @echo " INCLUDE_PATH: $(INCLUDE_PATH)" @echo " LIBCUOPT_LIBRARY_PATH: $(LIBCUOPT_LIBRARY_PATH)" + @echo " CLIENT_INCLUDE_PATH: $(CLIENT_INCLUDE_PATH)" + @echo " CLIENT_LIBRARY_PATH: $(CLIENT_LIBRARY_PATH)" @echo "" @echo "Discovered Files:" @echo " C sources: $(words $(SOURCES))" diff --git a/docs/cuopt/source/cuopt-c/mip/examples/Makefile b/docs/cuopt/source/cuopt-c/mip/examples/Makefile index 737614b955..edf0535154 100644 --- a/docs/cuopt/source/cuopt-c/mip/examples/Makefile +++ b/docs/cuopt/source/cuopt-c/mip/examples/Makefile @@ -15,14 +15,16 @@ # make clean - Remove all built executables # # Required variables (pass on command line): -# make INCLUDE_PATH=/path/to/cuopt/include LIBCUOPT_LIBRARY_PATH=/path/to/cuopt/lib +# make INCLUDE_PATH=/path/to/libcuopt_mathopt/include LIBCUOPT_LIBRARY_PATH=/path/to/libcuopt_mathopt/lib \ +# CLIENT_INCLUDE_PATH=/path/to/libcuopt_client/include CLIENT_LIBRARY_PATH=/path/to/libcuopt_client/lib # Compiler CC = gcc # Compiler flags -CFLAGS = -I$(INCLUDE_PATH) -Wall -Wextra -LDFLAGS = -L$(LIBCUOPT_LIBRARY_PATH) -lcuopt_mathopt -Wl,-rpath,$(LIBCUOPT_LIBRARY_PATH) +CFLAGS = -I$(INCLUDE_PATH) -I$(CLIENT_INCLUDE_PATH) -Wall -Wextra +LDFLAGS = -L$(LIBCUOPT_LIBRARY_PATH) -L$(CLIENT_LIBRARY_PATH) -Wl,--no-as-needed -lcuopt_mathopt -lcuopt_client \ + -Wl,-rpath,$(LIBCUOPT_LIBRARY_PATH) -Wl,-rpath,$(CLIENT_LIBRARY_PATH) # Automatically discover all C source files in current directory SOURCES = $(wildcard *.c) @@ -39,13 +41,10 @@ clean: rm -f $(TARGETS) check-paths: - @if [ -z "$(INCLUDE_PATH)" ]; then \ - echo "ERROR: INCLUDE_PATH is not set"; \ - echo "Usage: make INCLUDE_PATH=/path/to/cuopt/include LIBCUOPT_LIBRARY_PATH=/path/to/cuopt/lib"; \ - exit 1; \ - fi - @if [ -z "$(LIBCUOPT_LIBRARY_PATH)" ]; then \ - echo "ERROR: LIBCUOPT_LIBRARY_PATH is not set"; \ - echo "Usage: make INCLUDE_PATH=/path/to/cuopt/include LIBCUOPT_LIBRARY_PATH=/path/to/cuopt/lib"; \ + @if [ -z "$(INCLUDE_PATH)" ] || [ -z "$(LIBCUOPT_LIBRARY_PATH)" ] || \ + [ -z "$(CLIENT_INCLUDE_PATH)" ] || [ -z "$(CLIENT_LIBRARY_PATH)" ]; then \ + echo "ERROR: one or more required paths are not set"; \ + echo "Usage: make INCLUDE_PATH=/path/to/libcuopt_mathopt/include LIBCUOPT_LIBRARY_PATH=/path/to/libcuopt_mathopt/lib \\"; \ + echo " CLIENT_INCLUDE_PATH=/path/to/libcuopt_client/include CLIENT_LIBRARY_PATH=/path/to/libcuopt_client/lib"; \ exit 1; \ fi diff --git a/java/cuopt/pom.xml b/java/cuopt/pom.xml index 161909c2b8..e6bd9c2888 100644 --- a/java/cuopt/pom.xml +++ b/java/cuopt/pom.xml @@ -158,6 +158,7 @@ SPDX-License-Identifier: Apache-2.0 ${project.basedir}/scripts/generate_constants.sh ${project.basedir}/../../cpp/include/cuopt/mathematical_optimization/constants.h + ${project.basedir}/../../cpp/include/cuopt/status_codes.h ${project.build.directory}/generated-sources/cuopt diff --git a/java/cuopt/scripts/generate_constants.sh b/java/cuopt/scripts/generate_constants.sh index 85ae723688..efb768e6af 100755 --- a/java/cuopt/scripts/generate_constants.sh +++ b/java/cuopt/scripts/generate_constants.sh @@ -4,8 +4,13 @@ set -euo pipefail -HEADER=${1:?missing constants.h path} -OUT_DIR=${2:?missing output directory} +if [ "$#" -lt 2 ]; then + echo "usage: $0 [header2.h ...] " >&2 + exit 1 +fi + +OUT_DIR=${*: -1} +HEADERS=("${@:1:$#-1}") PACKAGE_DIR="${OUT_DIR}/com/nvidia/cuopt/mathematicaloptimization" OUT_FILE="${PACKAGE_DIR}/CuOptConstants.java" @@ -17,7 +22,8 @@ mkdir -p "${PACKAGE_DIR}" echo " * SPDX-License-Identifier: Apache-2.0" echo " *" echo " * AUTO-GENERATED FILE. DO NOT EDIT." - echo " * Generated from cpp/include/cuopt/mathematical_optimization/constants.h." + echo " * Generated from cpp/include/cuopt/mathematical_optimization/constants.h and" + echo " * cpp/include/cuopt/status_codes.h." echo " * Run 'mvn generate-sources' from java/cuopt to regenerate." echo " */" echo "package com.nvidia.cuopt.mathematicaloptimization;" @@ -38,6 +44,6 @@ mkdir -p "${PACKAGE_DIR}" printf(" public static final String %s = %s;%s", name, value, "\n") } } - ' "${HEADER}" + ' "${HEADERS[@]}" echo "}" } > "${OUT_FILE}" diff --git a/python/cmake/cuopt_wheel_build.cmake b/python/cmake/cuopt_wheel_build.cmake index 4c1e681ebb..067aee148d 100644 --- a/python/cmake/cuopt_wheel_build.cmake +++ b/python/cmake/cuopt_wheel_build.cmake @@ -41,10 +41,12 @@ add_subdirectory(../../cpp cuopt-cpp) # cuopt is an INTERFACE target (libcuopt.so is a linker script) and compiles nothing, # so it has no use for argparse. cuopt_cli and cuopt_grpc_server, which do, already link -# argparse::argparse in cpp/CMakeLists.txt. -target_link_libraries(cuopt_cli PRIVATE - argparse -) +# argparse::argparse in cpp/CMakeLists.txt. cuopt_cli doesn't exist when SKIP_MATHOPT_BUILD=ON. +if (TARGET cuopt_cli) + target_link_libraries(cuopt_cli PRIVATE + argparse + ) +endif () set(rpaths "$ORIGIN/../lib64" @@ -82,16 +84,15 @@ foreach(_target cuopt_routing cuopt_mathopt cuopt_client cuopt_cli cuopt_grpc_se endif() endforeach() -# Executables also need the sibling component wheels. The libraries do not: load_library() -# loads them in dependency order, so glibc satisfies each DT_NEEDED from an already-loaded -# object. An executable has no Python in the process, so cuopt_cli shipped in -# libcuopt-mathopt would not find libcuopt_client.so, which is never vendored. +# cuopt_mathopt and cuopt_routing both have a real NEEDED on libcuopt_client.so, never +# vendored into their own wheels, so they need their own rpath to it too -- not just +# cuopt_cli/cuopt_grpc_server. set(component_rpaths "$ORIGIN/../../libcuopt_client/lib64" "$ORIGIN/../../libcuopt_mathopt/lib64" "$ORIGIN/../../libcuopt_routing/lib64" ) -foreach(_target cuopt_cli cuopt_grpc_server) +foreach(_target cuopt_mathopt cuopt_routing cuopt_cli cuopt_grpc_server) if(TARGET ${_target}) set_property(TARGET ${_target} APPEND PROPERTY INSTALL_RPATH ${component_rpaths}) endif() diff --git a/python/cuopt/cuopt/grpc/client/CMakeLists.txt b/python/cuopt/cuopt/grpc/client/CMakeLists.txt index ff05235d86..7385b90548 100644 --- a/python/cuopt/cuopt/grpc/client/CMakeLists.txt +++ b/python/cuopt/cuopt/grpc/client/CMakeLists.txt @@ -18,3 +18,10 @@ rapids_cython_create_modules( MODULE_PREFIX grpc_client_ ASSOCIATED_TARGETS cuopt ) + +# grpc_client.cxx cimports routing and mathopt headers; both are now separate wheels, +# so add their include dirs without linking the libraries. +target_include_directories(grpc_client_grpc_client PRIVATE + $ + $ +) diff --git a/python/cuopt/cuopt/linear_programming/internals/CMakeLists.txt b/python/cuopt/cuopt/linear_programming/internals/CMakeLists.txt index d818df4d21..56e1ffb0fb 100644 --- a/python/cuopt/cuopt/linear_programming/internals/CMakeLists.txt +++ b/python/cuopt/cuopt/linear_programming/internals/CMakeLists.txt @@ -16,3 +16,9 @@ rapids_cython_create_modules( ) target_include_directories(internals_internals PRIVATE "${CMAKE_CURRENT_LIST_DIR}") + +# internals.pyx also cimports a mathopt header (callbacks_implems.hpp); mathopt-dev is +# now a separate wheel, so add its include dir without linking the library. +target_include_directories(internals_internals PRIVATE + $ +) diff --git a/python/cuopt/cuopt/linear_programming/io/CMakeLists.txt b/python/cuopt/cuopt/linear_programming/io/CMakeLists.txt index a2c5f35fe4..b06f87097b 100644 --- a/python/cuopt/cuopt/linear_programming/io/CMakeLists.txt +++ b/python/cuopt/cuopt/linear_programming/io/CMakeLists.txt @@ -8,3 +8,9 @@ set(linked_libraries cuopt::client) rapids_cython_create_modules(SOURCE_FILES "${cython_sources}" LINKED_LIBRARIES "${linked_libraries}" ASSOCIATED_TARGETS cuopt CXX) + +# parser_wrapper.pyx cimports mathopt headers; mathopt-dev is now a separate wheel, so +# add its include dir without linking the library. +target_include_directories(parser_wrapper PRIVATE + $ +) diff --git a/python/cuopt/pyproject.toml b/python/cuopt/pyproject.toml index 06eb1c9998..9efcb388b7 100644 --- a/python/cuopt/pyproject.toml +++ b/python/cuopt/pyproject.toml @@ -104,6 +104,9 @@ requires = [ "cmake>=4.0", "cupy-cuda13x[ctk]>=14.0.1,!=14.1.0", "cython>=3.2.2,<3.3.0a0", + "libcuopt-client==26.10.*,>=0.0.0a0", + "libcuopt-mathopt==26.10.*,>=0.0.0a0", + "libcuopt-routing==26.10.*,>=0.0.0a0", "libcuopt==26.10.*,>=0.0.0a0", "ninja", "pylibraft==26.10.*,>=0.0.0a0", diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt index 4c9a2b47ce..bbbbe41908 100644 --- a/python/libcuopt/CMakeLists.txt +++ b/python/libcuopt/CMakeLists.txt @@ -15,17 +15,19 @@ project( LANGUAGES CXX CUDA ) -# Debug: Print CUDA compiler version early -message(STATUS "=============================================================") -message(STATUS "libcuopt: CMAKE_CUDA_COMPILER_VERSION = ${CMAKE_CUDA_COMPILER_VERSION}") -message(STATUS "libcuopt: CUDAToolkit_VERSION = ${CUDAToolkit_VERSION}") -message(STATUS "=============================================================") - -# Check if cuopt is already available. If so, it is the user's responsibility to ensure that the -# CMake package is also available at build time of the Python cuopt package. +# Already installed: nothing to build, the wheel just stages the component. Checking for +# the cuopt_grpc_server binary itself, not cuopt_FOUND, matters now that this build installs +# the client/mathopt/routing sibling wheels as dependencies -- those alone resolve cuopt_FOUND +# but not a prebuilt cuopt_grpc_server. PATHS+NO_DEFAULT_PATH restricts the search to the same +# install trees find_package(cuopt) resolved, not an unrelated binary elsewhere on PATH. find_package(cuopt "${RAPIDS_VERSION}") +find_program(CUOPT_GRPC_SERVER_PREBUILT cuopt_grpc_server + PATHS ${CMAKE_PREFIX_PATH} + PATH_SUFFIXES bin + NO_DEFAULT_PATH +) -if(cuopt_FOUND) +if(cuopt_FOUND AND CUOPT_GRPC_SERVER_PREBUILT) return() endif() diff --git a/python/libcuopt/libcuopt/_cli_wrapper.py b/python/libcuopt/libcuopt/_cli_wrapper.py deleted file mode 100644 index 78e95280b7..0000000000 --- a/python/libcuopt/libcuopt/_cli_wrapper.py +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -import os -import sys - - -def main(): - """ - This connects to cli binary which situated under libcuopt/bin folder - - execv replaces this process rather than spawning a child, so signals sent - to the console script's pid reach the solver directly instead of stopping - at a Python parent that forwards nothing. - """ - cli_path = os.path.join(os.path.dirname(__file__), "bin", "cuopt_cli") - os.execv(cli_path, [cli_path] + sys.argv[1:]) diff --git a/python/libcuopt/libcuopt/load.py b/python/libcuopt/libcuopt/load.py index 86f5798f70..28817fe3a5 100644 --- a/python/libcuopt/libcuopt/load.py +++ b/python/libcuopt/libcuopt/load.py @@ -2,123 +2,29 @@ # SPDX-License-Identifier: Apache-2.0 -import ctypes -import os - -# Loading with RTLD_LOCAL adds the library itself to the loader's -# loaded library cache without loading any symbols into the global -# namespace. This allows libraries that express a dependency on -# this library to be loaded later and successfully satisfy this dependency -# without polluting the global symbol table with symbols from -# libcudf that could conflict with symbols from other DSOs. -PREFERRED_LOAD_FLAG = ctypes.RTLD_LOCAL - - -def _load_system_installation(soname: str): - """Try to dlopen() the library indicated by ``soname`` - Raises ``OSError`` if library cannot be loaded. +def load_library(): + """Dynamically load libcuopt.so and its dependencies. + + libcuopt is now a thin metapackage: it carries no engine library itself + (libcuopt.so is a linker script, not an ELF object, so it cannot be + dlopen()ed anyway). The actual libraries ship in the libcuopt-client, + libcuopt-mathopt and libcuopt-routing wheels, so loading delegates to + their own load_library(), which already handles the client-before-engine + DT_NEEDED ordering and the system/wheel installation fallback. """ - return ctypes.CDLL(soname, PREFERRED_LOAD_FLAG) - - -def _load_wheel_installation(soname: str): - """Try to dlopen() the library indicated by ``soname`` + loaded = [] - Returns ``None`` if the library cannot be loaded. - """ - if os.path.isfile( - lib := os.path.join(os.path.dirname(__file__), "lib64", soname) - ): - return ctypes.CDLL(lib, PREFERRED_LOAD_FLAG) - return None + # mathopt is required; routing is optional (SKIP_ROUTING_BUILD) and may + # not be installed at all. + import libcuopt_mathopt + loaded.extend(libcuopt_mathopt.load_library()) -def load_library(): - """Dynamically load libcuopt.so and its dependencies""" try: - # librmm and libraft must be loaded before libcuopt - # because libcuopt references them. - import libraft - import librmm - import rapids_logger - - rapids_logger.load_library() - librmm.load_library() - libraft.load_library() + import libcuopt_routing except ModuleNotFoundError: pass + else: + loaded.extend(libcuopt_routing.load_library()) - prefer_system_installation = ( - os.getenv("RAPIDS_LIBCUOPT_PREFER_SYSTEM_LIBRARY", "false").lower() - != "false" - ) - - # cuOpt ships as component libraries. libcuopt.so is a linker script, - # not an ELF object, so it cannot be dlopen()ed -- load the components - # instead. Each pulls its own dependencies in through DT_NEEDED, so this - # order only needs to be valid, not exhaustive. routing and grpc are - # optional (SKIP_ROUTING_BUILD, SKIP_GRPC_BUILD) and may be absent. - components = [ - ("libcuopt_routing.so", False), - ("libcuopt_mathopt.so", True), - ] - loaded = [] - for soname, required in components: - lib = _load_component(soname, prefer_system_installation, required) - if lib is not None: - loaded.append(lib) return loaded - - -def _load_component( - soname: str, prefer_system_installation: bool, required: bool -): - """Load one cuOpt component. - - Returns the handle, or ``None`` if it could not be loaded. Failing to - load an optional component is silent; failing a required one warns. - """ - libcuopt_lib = None - try: - if prefer_system_installation: - # Prefer a system library if one is present to - # avoid clobbering symbols that other packages might expect, but if no - # other library is present use the one in the wheel. - try: - libcuopt_lib = _load_system_installation(soname) - except OSError: - # The fallback needs to sit inside the outer handler too. An - # optional component that is present but fails to load would - # otherwise raise out of here and stop the remaining components - # from being loaded at all. - libcuopt_lib = _load_wheel_installation(soname) - else: - # Prefer the libraries bundled in this package. If they aren't found - # (which might be the case in builds where the library - # was prebuilt before packaging the wheel), look for a - # system installation. - libcuopt_lib = _load_wheel_installation(soname) - if libcuopt_lib is None: - libcuopt_lib = _load_system_installation(soname) - except OSError as e: - # If none of the searches above succeed, just silently return None - # and rely on other mechanisms (like RPATHs on other DSOs) to - # help the loader find the library. - if not required: - return None - - import warnings - - warnings.warn( - f"Failed to load libcuopt library: {soname}. " - f"Error: {str(e)}. " - "Falling back to relying on system loader. " - "cuOpt functionality may be unavailable. " - f"This might lead to a generic error such as " - f"'{soname} missing' if the library cannot be found.", - RuntimeWarning, - ) - # The caller almost never needs to do anything with this library, but no - # harm in offering the option since this object at least provides a handle - # to inspect where libcuopt was loaded from. - return libcuopt_lib diff --git a/python/libcuopt/pyproject.toml b/python/libcuopt/pyproject.toml index 6e656a7518..29135bfd3d 100644 --- a/python/libcuopt/pyproject.toml +++ b/python/libcuopt/pyproject.toml @@ -30,11 +30,10 @@ classifiers = [ "Programming Language :: Python :: 3.14", ] dependencies = [ - "cuda-toolkit[cublas,cudart,curand,cusolver,cusparse,nvtx]==13.*", + "libcuopt-client==26.10.*,>=0.0.0a0", + "libcuopt-mathopt==26.10.*,>=0.0.0a0", + "libcuopt-routing==26.10.*,>=0.0.0a0", "librmm==26.10.*,>=0.0.0a0", - "nvidia-cudss-cu13>=0.7,<0.8", - "nvidia-nccl-cu13>=2.19", - "nvidia-nvjitlink>=13.3,<14", "rapids-logger==0.3.*", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. @@ -46,7 +45,6 @@ Source = "https://github.com/nvidia/cuopt" libcuopt = "libcuopt" [project.scripts] -cuopt_cli = "libcuopt._cli_wrapper:main" cuopt_grpc_server = "libcuopt._grpc_server_wrapper:main" [tool.pydistcheck] @@ -65,9 +63,12 @@ sdist.reproducible = true wheel.packages = ["libcuopt"] wheel.install-dir = "libcuopt" wheel.py-api = "py3" -# install.components is deliberately unset: this wheel still ships every component. -# Trimming it needs per-component CMake packages, so find_package(cuopt) can resolve each -# from its own prefix, and care with files rapids-cmake installs without a COMPONENT. +# Thin metapackage: engine libraries come from their own wheels now (#1635). +# "dev" = linker script, "cuopt" = rapids_export's config/export set, "grpc-server" = +# cuopt_grpc_server. "Unspecified" carries rapids-cmake's CCCL redirect files, needed +# for find_dependency(rmm) to resolve cleanly instead of hitting librmm's own +# (order-dependent) copy -- see #2013. +install.components = ["dev", "cuopt", "grpc-server", "Unspecified"] [tool.scikit-build.metadata.version] provider = "scikit_build_core.metadata.regex" @@ -80,6 +81,9 @@ dependencies-file = "../../dependencies.yaml" matrix-entry = "cuda_suffixed=true;use_cuda_wheels=true" requires = [ "cmake>=4.0", + "libcuopt-client==26.10.*,>=0.0.0a0", + "libcuopt-mathopt==26.10.*,>=0.0.0a0", + "libcuopt-routing==26.10.*,>=0.0.0a0", "librmm==26.10.*,>=0.0.0a0", "ninja", "rapids-logger==0.3.*", diff --git a/python/libcuopt_client/CMakeLists.txt b/python/libcuopt_client/CMakeLists.txt index e5fde2401b..f9fedc5e74 100644 --- a/python/libcuopt_client/CMakeLists.txt +++ b/python/libcuopt_client/CMakeLists.txt @@ -15,9 +15,11 @@ project( LANGUAGES CXX CUDA ) -# Already installed: nothing to build, the wheel just stages the component. +# Already installed: nothing to build, the wheel just stages the component. Checking +# cuopt::client specifically, not cuopt_FOUND, matters now that libcuopt-client ships +# cuopt-config.cmake: a sibling wheel resolving it must not short-circuit this build. find_package(cuopt "${RAPIDS_VERSION}") -if(cuopt_FOUND) +if(TARGET cuopt::client) return() endif() unset(cuopt_FOUND) diff --git a/python/libcuopt_client/pyproject.toml b/python/libcuopt_client/pyproject.toml index 05803e0e59..51a630f291 100644 --- a/python/libcuopt_client/pyproject.toml +++ b/python/libcuopt_client/pyproject.toml @@ -57,7 +57,7 @@ wheel.install-dir = "libcuopt_client" wheel.py-api = "py3" # The whole point of this package: stage only this component's install rules, # so the wheel carries one library instead of all three. -install.components = ["client", "client-dev"] +install.components = ["client", "client-dev", "cuopt", "Unspecified"] [tool.scikit-build.metadata.version] provider = "scikit_build_core.metadata.regex" diff --git a/python/libcuopt_mathopt/CMakeLists.txt b/python/libcuopt_mathopt/CMakeLists.txt index 0ebf0ad09d..9c35df545d 100644 --- a/python/libcuopt_mathopt/CMakeLists.txt +++ b/python/libcuopt_mathopt/CMakeLists.txt @@ -15,9 +15,11 @@ project( LANGUAGES CXX CUDA ) -# Already installed: nothing to build, the wheel just stages the component. +# Already installed: nothing to build, the wheel just stages the component. Checking +# cuopt::mathopt specifically, not cuopt_FOUND, matters now that this build installs the +# client sibling wheel as a dependency -- that alone resolves cuopt_FOUND but not mathopt. find_package(cuopt "${RAPIDS_VERSION}") -if(cuopt_FOUND) +if(TARGET cuopt::mathopt) return() endif() unset(cuopt_FOUND) diff --git a/python/libcuopt_mathopt/pyproject.toml b/python/libcuopt_mathopt/pyproject.toml index 481b2864df..23b64eb67b 100644 --- a/python/libcuopt_mathopt/pyproject.toml +++ b/python/libcuopt_mathopt/pyproject.toml @@ -80,6 +80,7 @@ dependencies-file = "../../dependencies.yaml" matrix-entry = "cuda_suffixed=true;use_cuda_wheels=true" requires = [ "cmake>=4.0", + "libcuopt-client==26.10.*,>=0.0.0a0", "librmm==26.10.*,>=0.0.0a0", "ninja", "rapids-logger==0.3.*", diff --git a/python/libcuopt_routing/CMakeLists.txt b/python/libcuopt_routing/CMakeLists.txt index 03afc376da..953573b8d3 100644 --- a/python/libcuopt_routing/CMakeLists.txt +++ b/python/libcuopt_routing/CMakeLists.txt @@ -15,9 +15,11 @@ project( LANGUAGES CXX CUDA ) -# Already installed: nothing to build, the wheel just stages the component. +# Already installed: nothing to build, the wheel just stages the component. Checking +# cuopt::routing specifically, not cuopt_FOUND, matters now that this build installs the +# client sibling wheel as a dependency -- that alone resolves cuopt_FOUND but not routing. find_package(cuopt "${RAPIDS_VERSION}") -if(cuopt_FOUND) +if(TARGET cuopt::routing) return() endif() unset(cuopt_FOUND) diff --git a/python/libcuopt_routing/pyproject.toml b/python/libcuopt_routing/pyproject.toml index b4e82e6fc4..e306beef43 100644 --- a/python/libcuopt_routing/pyproject.toml +++ b/python/libcuopt_routing/pyproject.toml @@ -75,6 +75,7 @@ dependencies-file = "../../dependencies.yaml" matrix-entry = "cuda_suffixed=true;use_cuda_wheels=true" requires = [ "cmake>=4.0", + "libcuopt-client==26.10.*,>=0.0.0a0", "librmm==26.10.*,>=0.0.0a0", "ninja", "rapids-logger==0.3.*",