Skip to content

build(cmake): split libcuopt into cuopt_base / cuopt_routing / cuopt_lp component libs - #1622

Draft
ramakrishnap-nv wants to merge 10 commits into
mainfrom
feat/split-routing-lp-libs
Draft

build(cmake): split libcuopt into cuopt_base / cuopt_routing / cuopt_lp component libs#1622
ramakrishnap-nv wants to merge 10 commits into
mainfrom
feat/split-routing-lp-libs

Conversation

@ramakrishnap-nv

@ramakrishnap-nv ramakrishnap-nv commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Splits libcuopt.so into four component SHARED libraries (cuopt_base, cuopt_routing, cuopt_lp, cuopt_grpc) plus a thin umbrella libcuopt.so that links them all — existing -lcuopt consumers are unaffected.

🤖 Generated with Claude Code

ramakrishnap-nv and others added 3 commits July 24, 2026 13:48
…lp + umbrella

Introduce three STATIC component libraries that logically partition the
cuOpt sources by domain, then fold them into the existing libcuopt.so
umbrella via --whole-archive (LINK_LIBRARY:WHOLE_ARCHIVE).

Component libraries:
- cuopt_base   — utilities + linear algebra (logger, work scheduler)
- cuopt_routing — VRP / routing engine; links cuopt_base
- cuopt_lp      — LP / MIP / numerical optimization; links cuopt_base

Umbrella:
- cuopt SHARED  — re-exports all symbols from the three statics via
  --whole-archive; backward-compatible for GAMS (-lcuopt / libcuopt.so)

CMake aliases exposed: cuopt::base, cuopt::routing, cuopt::lp, cuopt::cuopt

No source files moved. External build output (libcuopt.so, headers,
install layout) is unchanged. SKIP_ROUTING_BUILD=ON continues to work
by omitting cuopt_routing from the build and umbrella link.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
Four issues found while validating the library split locally:

- cuopt_routing was missing OpenMP::OpenMP_CUDA, causing routing CUDA
  files to reject #pragma omp directives as unknown in CUDA compiler mode
- cuopt_lp was missing simde::simde, required by the fast MPS parser
  (io/experimental_mps_fast/) which uses SIMD intrinsics via simde headers
- The umbrella cuopt target was missing src/io in its private include
  dirs, causing gRPC mapper files (grpc_problem_mapper.cpp) that include
  mps_parser_internal.hpp to fail to compile
- WHOLE_ARCHIVE linkage on the umbrella was PUBLIC, propagating the static
  sub-libs as link dependencies to all consumers (test binaries). This
  caused double-definition errors when tests linked both libcuopt.so and
  the statics. Changed to PRIVATE and re-exposed the statics' transitive
  PUBLIC deps (rmm, raft, CCCL, CUDA libs) directly on the umbrella so
  that consumers receive the correct source-fetched include dirs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
Add libcuopt_base.a, libcuopt_routing.a, and libcuopt_lp.a to the
package_contents file check so CI fails fast if any of the three
component static libraries are missing from the installed package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

ramakrishnap-nv and others added 2 commits July 27, 2026 10:08
…to cuopt_lp

Component libs (cuopt_base, cuopt_routing, cuopt_lp) are now SHARED
instead of STATIC. The umbrella libcuopt.so becomes a thin stub (~15 KB)
carrying only DT_NEEDED entries for the three component libs; no code or
WHOLE_ARCHIVE baking.

The gRPC bridge (mapper + Cython client) moves from the umbrella into
cuopt_lp where it semantically belongs — LP/MIP remote solve is an LP
concern. The umbrella drops all gRPC sources, include dirs, and
protobuf/gRPC link deps.

Both RPATH settings use $ORIGIN so component libs find each other when
co-installed. Conda package_contents check updated from .a to .so.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
Moves all gRPC infrastructure (proto mappers, Cython client, solve_remote)
from cuopt_lp into a new cuopt_grpc SHARED component. cuopt_grpc links
cuopt_lp + cuopt_routing (when built), keeping both core solver libs free
of any gRPC/protobuf dependency.

The grpc_server binary now links cuopt_grpc directly. The umbrella links
cuopt_grpc when gRPC is built so -lcuopt continues to expose remote-solve
symbols to existing consumers.

When PR #1597 (VRP gRPC) lands, routing gRPC sources go into cuopt_grpc
alongside the LP ones — no cross-dependency between cuopt_lp and cuopt_routing
is needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
@ramakrishnap-nv ramakrishnap-nv self-assigned this Jul 27, 2026
@ramakrishnap-nv ramakrishnap-nv added non-breaking Introduces a non-breaking change improvement Improves an existing functionality labels Jul 27, 2026
@ramakrishnap-nv ramakrishnap-nv added this to the 26.10 milestone Jul 27, 2026
Resolve conflicts between:
- Our component library split (cuopt_base/routing/lp/grpc as SHARED + thin umbrella)
- main's cuopt_objs OBJECT library approach added in #1581

Both coexist: cuopt_objs + cuopt_static serve internal tests; the SHARED
component libs + umbrella serve all other consumers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator Author

/ok to test ec81ebc

@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

cuOpt componentized build

Layer / File(s) Summary
Component source aggregation
cpp/CMakeLists.txt, cpp/src/CMakeLists.txt, cpp/src/.../CMakeLists.txt
CMake now groups solver sources by base, routing, and LP components and propagates nested source lists through parent scopes.
Component shared-library targets
cpp/CMakeLists.txt
Shared component targets and centralized configuration are added, with gRPC sources built separately and object-library settings updated.
Umbrella runtime and packaging
cpp/CMakeLists.txt, conda/recipes/libcuopt/recipe.yaml
The umbrella library links component targets, installation and exports include them, the gRPC server links its dedicated target, and package tests expect the additional libraries.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: afender, iroy30, chris-maes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: splitting libcuopt into component libraries, though it omits the grpc component and thin umbrella detail.
Description check ✅ Passed The description is directly related to the change, describing the split into component shared libraries and preserving libcuopt consumers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/split-routing-lp-libs

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/CMakeLists.txt (1)

902-918: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Exported component names should match the documented API

cuopt::base / cuopt::lp are build-tree aliases only. The install/export set will expose the real targets (cuopt::cuopt_base, cuopt::cuopt_lp, etc.) unless those targets set EXPORT_NAME, so a consumer using find_package(cuopt) won’t be able to link against cuopt::base as documented.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/CMakeLists.txt` around lines 902 - 918, Update the component target
export configuration used by the rapids_export INSTALL and BUILD calls so
installed targets retain the documented names cuopt::base, cuopt::routing,
cuopt::lp, and cuopt::grpc. Set the appropriate EXPORT_NAME values on the
underlying cuopt_component targets, while preserving cuopt::cuopt as the
umbrella target and keeping build-tree aliases consistent.
🧹 Nitpick comments (2)
cpp/CMakeLists.txt (2)

549-556: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Give the git hash lookup a fallback for non-git source trees.

Without RESULT_VARIABLE/ERROR_QUIET, tarball builds leak git's error to the configure log and bake an empty hash into build_info.hpp.

♻️ Proposed fallback
 execute_process(
         COMMAND git rev-parse --short HEAD
         WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
         OUTPUT_VARIABLE GIT_COMMIT_HASH
         OUTPUT_STRIP_TRAILING_WHITESPACE
+        RESULT_VARIABLE _git_hash_result
+        ERROR_QUIET
 )
+if(NOT _git_hash_result EQUAL 0 OR GIT_COMMIT_HASH STREQUAL "")
+    set(GIT_COMMIT_HASH "unknown")
+endif()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/CMakeLists.txt` around lines 549 - 556, Update the git hash lookup in the
top-level CMake configuration to capture the execute_process result and suppress
stderr for source trees without Git metadata. When the lookup fails, assign a
stable non-empty fallback hash before the existing GIT_COMMIT_HASH message and
build_info.hpp generation; preserve the real short HEAD value for Git checkouts.

664-675: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

cuopt_objs duplicates the component include/definition setup.

The object library re-declares papilo/pslp/dejavu includes, CUDSS defines, and architecture defines that cuopt_configure_component (plus cuopt_lp) already establish. Since cuopt_objs backs cuopt_static for the test builds, drift here means tests compile under a different configuration than shipped libraries. Consider factoring the shared include/definition block into a helper both paths call.

Also applies to: 693-694, 720-724

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/CMakeLists.txt` around lines 664 - 675, Refactor the shared include
directories and compile definitions currently duplicated by cuopt_objs and the
cuopt_configure_component/cuopt_lp setup into a reusable CMake helper. Invoke
that helper for both cuopt_objs and the shipped-library path, including the
papilo/pslp/dejavu includes, CUDSS definitions, and architecture definitions,
while preserving target-specific settings such as POSITION_INDEPENDENT_CODE and
logging definitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cpp/CMakeLists.txt`:
- Around line 902-918: Update the component target export configuration used by
the rapids_export INSTALL and BUILD calls so installed targets retain the
documented names cuopt::base, cuopt::routing, cuopt::lp, and cuopt::grpc. Set
the appropriate EXPORT_NAME values on the underlying cuopt_component targets,
while preserving cuopt::cuopt as the umbrella target and keeping build-tree
aliases consistent.

---

Nitpick comments:
In `@cpp/CMakeLists.txt`:
- Around line 549-556: Update the git hash lookup in the top-level CMake
configuration to capture the execute_process result and suppress stderr for
source trees without Git metadata. When the lookup fails, assign a stable
non-empty fallback hash before the existing GIT_COMMIT_HASH message and
build_info.hpp generation; preserve the real short HEAD value for Git checkouts.
- Around line 664-675: Refactor the shared include directories and compile
definitions currently duplicated by cuopt_objs and the
cuopt_configure_component/cuopt_lp setup into a reusable CMake helper. Invoke
that helper for both cuopt_objs and the shipped-library path, including the
papilo/pslp/dejavu includes, CUDSS definitions, and architecture definitions,
while preserving target-specific settings such as POSITION_INDEPENDENT_CODE and
logging definitions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2d788dd1-855a-4038-900e-9a3eab8f07be

📥 Commits

Reviewing files that changed from the base of the PR and between 91134a2 and ec81ebc.

📒 Files selected for processing (13)
  • conda/recipes/libcuopt/recipe.yaml
  • cpp/CMakeLists.txt
  • cpp/src/CMakeLists.txt
  • cpp/src/barrier/CMakeLists.txt
  • cpp/src/branch_and_bound/CMakeLists.txt
  • cpp/src/cuts/CMakeLists.txt
  • cpp/src/dual_simplex/CMakeLists.txt
  • cpp/src/io/CMakeLists.txt
  • cpp/src/linear_algebra/CMakeLists.txt
  • cpp/src/math_optimization/CMakeLists.txt
  • cpp/src/mip_heuristics/CMakeLists.txt
  • cpp/src/pdlp/CMakeLists.txt
  • cpp/src/routing/CMakeLists.txt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator Author

/ok to test a54e62a

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

CI Test Summary

18 failed · 13 passed · 0 skipped

conda-cpp-tests / 13.3.0, 3.14, amd64, ubuntu26.04, h100, latest-driver, latest-deps — 3 failed tests
  • cli_test_t.missing_required_argument
  • cli_test_t.unrecognized_argument
  • cli_test_t.partial_solution_file
conda-cpp-tests / 12.9.2, 3.14, amd64, ubuntu22.04, h100, latest-driver, latest-deps — 3 failed tests
  • cli_test_t.missing_required_argument
  • cli_test_t.unrecognized_argument
  • cli_test_t.partial_solution_file
conda-cpp-tests / 12.2.2, 3.11, arm64, ubuntu22.04, a100, latest-driver, latest-deps — 4 failed tests
  • cli_test_t.missing_required_argument
  • cli_test_t.unrecognized_argument
  • cli_test_t.partial_solution_file
  • barrier.qplib_8515_ruiz_forced_off
conda-cpp-tests / 13.0.3, 3.14, arm64, rockylinux8, l4, latest-driver, latest-deps — 3 failed tests
  • cli_test_t.missing_required_argument
  • cli_test_t.unrecognized_argument
  • cli_test_t.partial_solution_file
conda-cpp-tests / 12.2.2, 3.11, amd64, rockylinux8, v100, earliest-driver, oldest-deps — 3 failed tests
  • cli_test_t.missing_required_argument
  • cli_test_t.unrecognized_argument
  • cli_test_t.partial_solution_file
conda-cpp-tests / 13.0.3, 3.12, amd64, ubuntu24.04, l4, latest-driver, latest-deps — 3 failed tests
  • cli_test_t.missing_required_argument
  • cli_test_t.unrecognized_argument
  • cli_test_t.partial_solution_file
conda-cpp-tests / 13.3.0, 3.13, amd64, ubuntu26.04, rtxpro6000, latest-driver, latest-deps — 3 failed tests
  • cli_test_t.missing_required_argument
  • cli_test_t.unrecognized_argument
  • cli_test_t.partial_solution_file
conda-cpp-tests / 13.3.0, 3.13, arm64, ubuntu26.04, l4, latest-driver, latest-deps — 3 failed tests
  • cli_test_t.missing_required_argument
  • cli_test_t.unrecognized_argument
  • cli_test_t.partial_solution_file
conda-python-tests / 12.2.2, 3.12, amd64, ubuntu22.04, l4, latest-driver, latest-deps — 8 failed tests
  • tests/linear_programming/test_cpu_only_execution.py
  • tests/test_incumbents.py::test_incumbent_callback_get_only
  • tests/test_incumbents.py::test_incumbent_callback_get_set
  • tests/test_job_abort.py::test_abort_on_complete
  • tests/test_job_abort.py::test_abort_of_running
  • tests/test_solver_logging.py::test_solver_logging
  • tests/test_lp.py
  • tests/test_pdlp_warmstart.py
conda-python-tests / 13.3.0, 3.13, amd64, ubuntu26.04, rtxpro6000, latest-driver, latest-deps — 8 failed tests
  • tests/linear_programming/test_cpu_only_execution.py
  • tests/test_incumbents.py::test_incumbent_callback_get_only
  • tests/test_incumbents.py::test_incumbent_callback_get_set
  • tests/test_job_abort.py::test_abort_on_complete
  • tests/test_job_abort.py::test_abort_of_running
  • tests/test_solver_logging.py::test_solver_logging
  • tests/test_lp.py
  • tests/test_pdlp_warmstart.py
conda-python-tests / 12.2.2, 3.11, amd64, rockylinux8, l4, earliest-driver, oldest-deps — 8 failed tests
  • tests/linear_programming/test_cpu_only_execution.py
  • tests/test_incumbents.py::test_incumbent_callback_get_only
  • tests/test_incumbents.py::test_incumbent_callback_get_set
  • tests/test_job_abort.py::test_abort_on_complete
  • tests/test_job_abort.py::test_abort_of_running
  • tests/test_solver_logging.py::test_solver_logging
  • tests/test_lp.py
  • tests/test_pdlp_warmstart.py
conda-python-tests / 13.0.3, 3.12, amd64, ubuntu24.04, rtxpro6000, latest-driver, latest-deps — 8 failed tests
  • tests/linear_programming/test_cpu_only_execution.py
  • tests/test_incumbents.py::test_incumbent_callback_get_only
  • tests/test_incumbents.py::test_incumbent_callback_get_set
  • tests/test_job_abort.py::test_abort_on_complete
  • tests/test_job_abort.py::test_abort_of_running
  • tests/test_solver_logging.py::test_solver_logging
  • tests/test_lp.py
  • tests/test_pdlp_warmstart.py
conda-python-tests / 13.3.0, 3.14, amd64, ubuntu26.04, h100, latest-driver, latest-deps — 8 failed tests
  • tests/linear_programming/test_cpu_only_execution.py
  • tests/test_incumbents.py::test_incumbent_callback_get_only
  • tests/test_incumbents.py::test_incumbent_callback_get_set
  • tests/test_job_abort.py::test_abort_on_complete
  • tests/test_job_abort.py::test_abort_of_running
  • tests/test_solver_logging.py::test_solver_logging
  • tests/test_lp.py
  • tests/test_pdlp_warmstart.py
conda-python-tests / 12.9.2, 3.14, amd64, ubuntu22.04, h100, latest-driver, latest-deps — 8 failed tests
  • tests/linear_programming/test_cpu_only_execution.py
  • tests/test_incumbents.py::test_incumbent_callback_get_only
  • tests/test_incumbents.py::test_incumbent_callback_get_set
  • tests/test_job_abort.py::test_abort_on_complete
  • tests/test_job_abort.py::test_abort_of_running
  • tests/test_solver_logging.py::test_solver_logging
  • tests/test_lp.py
  • tests/test_pdlp_warmstart.py
conda-python-tests / 13.0.3, 3.12, arm64, ubuntu22.04, l4, latest-driver, latest-deps — 8 failed tests
  • tests/linear_programming/test_cpu_only_execution.py
  • tests/test_incumbents.py::test_incumbent_callback_get_only
  • tests/test_incumbents.py::test_incumbent_callback_get_set
  • tests/test_job_abort.py::test_abort_on_complete
  • tests/test_job_abort.py::test_abort_of_running
  • tests/test_solver_logging.py::test_solver_logging
  • tests/test_lp.py
  • tests/test_pdlp_warmstart.py
conda-python-tests / 13.3.0, 3.14, arm64, ubuntu26.04, l4, latest-driver, latest-deps — 8 failed tests
  • tests/linear_programming/test_cpu_only_execution.py
  • tests/test_incumbents.py::test_incumbent_callback_get_only
  • tests/test_incumbents.py::test_incumbent_callback_get_set
  • tests/test_job_abort.py::test_abort_on_complete
  • tests/test_job_abort.py::test_abort_of_running
  • tests/test_solver_logging.py::test_solver_logging
  • tests/test_lp.py
  • tests/test_pdlp_warmstart.py

ramakrishnap-nv and others added 2 commits July 27, 2026 16:23
libcuopt.so is now a thin umbrella with DT_NEEDED on libcuopt_base.so,
libcuopt_routing.so, libcuopt_lp.so, and libcuopt_grpc.so. auditwheel
traverses DT_NEEDED transitively and failed when it couldn't locate
the component libs. Exclude them the same way libcuopt.so is excluded —
they ship with the libcuopt wheel and are available at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…builds

main appends GRPC_INFRA_FILES to CUOPT_SRC_FILES before creating cuopt_objs
so cuopt_static (used by NUMOPT_INTERNAL_TEST) gets solve_lp_remote /
solve_mip_remote. We dropped that line when we moved those files into
cuopt_grpc, causing undefined-reference link failures in tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator Author

/ok to test 705167f

…lution

cuopt_lp.so calls solve_lp/mip_remote (under CUOPT_ENABLE_GRPC) which are
defined in cuopt_grpc.so. With --as-needed the linker was dropping
libcuopt_grpc.so from executables that never directly referenced a grpc
symbol, leaving solve_lp_remote unresolved at runtime.

Route remote solves in cuopt_cli directly through solve_lp/mip_remote so
libcuopt_grpc.so is a genuine DT_NEEDED of the binary; --as-needed then
keeps it in the link and the symbol is in scope when libcuopt_lp.so needs it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improves an existing functionality non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant