Skip to content

Build the Java bindings dynamically against the pip-installed libcuopt in the official Docker images - #1959

Merged
rapids-bot[bot] merged 6 commits into
mainfrom
docker-java-support
Sep 22, 2026
Merged

rapids-bot[bot] merged 6 commits into
mainfrom
docker-java-support

Conversation

@ramakrishnap-nv

@ramakrishnap-nv ramakrishnap-nv commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Builds libcuopt_jni.so in-image against the pip-installed libcuopt.so, instead of embedding a statically-linked classifier JAR (~600MB duplicated). CUOPT_RUNTIME_LIBRARY_DIR gives cuopt_jni.so the same RPATH-based resolution libcuopt.so already uses, so no LD_LIBRARY_PATH changes are needed.

  • New java-build stage in Dockerfile/Dockerfile.ubi: JDK + build toolchain, reuses cuda-headers for nvcc, runs ci/docker/build_java_dynamic*.sh (builds the .so, javac-compiles the classes, no Maven).
  • cuopt-final installs a headless JRE and copies in cuopt.jar + libcuopt_jni.so.
  • UBI10 has no java-17-openjdk; uses java-21-openjdk.

~300KB per image instead of ~600MB.

Verification

Built both Dockerfiles locally, ran a Java program against cuopt.jar in each. Native loading succeeded fully (RPATH-only, confirmed via ldd); reached cuOptSolve, which failed only with CUOPT_VALIDATION_ERROR (no GPU on this host).

Test plan

  • CI: build_images.yaml matrix
  • Local: both Dockerfiles verified as above
  • GPU-enabled run for an actual solve

…images

Neither ci/docker/Dockerfile nor Dockerfile.ubi installed any Java support
at all. The existing dynamic-link Java build (ci/build_java.sh) can't help
here either: it needs a conda environment with libcuopt's headers, and
these images are pip-based (no headers, just the compiled libcuopt.so).

The self-contained classifier JAR from java-static-build (#1817) is the
right fit instead: it embeds libcuopt statically, so nothing has to link
against this image's own pip-installed libcuopt at all. build.yaml already
builds one per ARCH x CUDA-major combination on every branch/nightly run,
in the same overall workflow run that (via build_test_publish_images.yaml)
builds these images, so build_images.yaml downloads it as a same-run
artifact rather than needing any new publish channel.

Verified against a real build: built the actual Dockerfile with a real
classifier JAR pulled from a recent successful main run, then compiled
and ran a small Java program against the resulting image's cuopt.jar.
Found and fixed a real gap doing that -- the JNI library failed with
UnsatisfiedLinkError: libcublas.so.13, because this image's CUDA runtime
comes from pip-installed nvidia-cublas-cu13 (under
.../dist-packages/nvidia/cu13/lib), not /usr/local/cuda/targets/.../lib
the way the RAPIDS CI image the JAR was built and tested against has it.
libcuopt.so itself doesn't hit this (it carries its own $ORIGIN-relative
RPATH into that same pip directory), but the JNI library has no such
RPATH, so LD_LIBRARY_PATH now includes it explicitly. Confirmed the fix:
the program got past native library loading and all the way to an actual
cuOptSolve call (which then hit CUOPT_VALIDATION_ERROR only because this
local test had no GPU passed through -- a test-environment limitation,
not a packaging bug).
@ramakrishnap-nv
ramakrishnap-nv requested a review from a team as a code owner September 21, 2026 15:36
@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: NVIDIA/cuopt/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: bca6b447-35f0-4354-9bc4-2d5c7e7eda78

📥 Commits

Reviewing files that changed from the base of the PR and between 9cc97db and a92e14c.

📒 Files selected for processing (4)
  • .github/workflows/build_images.yaml
  • ci/docker/Dockerfile
  • ci/docker/Dockerfile.ubi
  • ci/docker/build_java_dynamic.sh
💤 Files with no reviewable changes (1)
  • .github/workflows/build_images.yaml

Included review availability: Your plan provides up to 12 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

The workflow stages Java and native sources for Docker builds. Standard and UBI images compile Java bindings against the installed libcuopt package and copy the generated artifacts into the final images.

Changes

Dynamic Java image builds

Layer / File(s) Summary
Source staging and binding builds
.github/workflows/build_images.yaml, ci/docker/build_java_dynamic.sh
The workflow stages Java, C++, and build-script inputs. The script validates libcuopt.so, builds JNI bindings, compiles Java sources, and packages the Java artifacts.
Standard Docker image integration
ci/docker/Dockerfile
The image adds an OpenJDK 17 build stage, installs the headless JRE, and copies generated Java artifacts into /opt/cuopt/java.
UBI Docker image integration
ci/docker/Dockerfile.ubi
The image adds a Java 21 build stage, installs the headless runtime, and copies generated Java artifacts into /opt/cuopt/java.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Refactor

Suggested reviewers: tmckayus, trxcllnt

Merge Risk: ⚪ Minimal · up to a92e1

The images now build Java bindings against the installed native package and include the required runtime artifacts for both supported image variants. No actionable production or deployment risk is evidenced.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: dynamic Java binding builds against the pip-installed libcuopt in official Docker images.
Description check ✅ Passed The description directly explains the dynamic Java binding build, Dockerfile changes, runtime packaging, library resolution, size reduction, and verification results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
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 docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

@github-actions

Copy link
Copy Markdown

CI Test Summary

⏭️ All 5 test job(s) skipped.

The self-contained classifier JAR approach (reverted in the prior commit)
statically links libcuopt and its dependencies (TBB, NCCL, cuDSS, rmm,
rapids_logger) into the JNI library, duplicating ~600MB of libraries the
image already carries for the Python side via pip. Instead, compile
libcuopt_jni.so inside each image against the libcuopt.so already
pip-installed there: CUOPT_RUNTIME_LIBRARY_DIR bakes libcuopt's location
into cuopt_jni's own RPATH, so no LD_LIBRARY_PATH changes are needed --
libcuopt.so resolves the rest of its dependency chain itself via its
existing $ORIGIN-relative RPATH. The result is a 55KB jar + ~250KB .so per
image instead of a ~600MB embedded copy.

Verified locally end-to-end on both Dockerfile (Ubuntu) and Dockerfile.ubi
(RHEL/UBI10): built each image, compiled and ran a small Java program
against cuopt.jar with -Dcuopt.native.dir, and confirmed native library
loading succeeds fully (no UnsatisfiedLinkError) and the call chain reaches
cuOptSolve, which reports CUOPT_VALIDATION_ERROR only because this host's
driver doesn't support the images' CUDA 13.3 requirement -- the same
signature seen when verifying the (now-abandoned) static approach.

UBI10's AppStream repo has no java-17-openjdk package; use java-21-openjdk
(an LTS release satisfying build_native.sh's JDK 17+ requirement) instead.
@ramakrishnap-nv ramakrishnap-nv changed the title Bundle the self-contained Java classifier JAR in the official Docker images Build the Java bindings dynamically against the pip-installed libcuopt in the official Docker images Sep 21, 2026
@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

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.

@ramakrishnap-nv
ramakrishnap-nv marked this pull request as draft September 21, 2026 20:32
@copy-pr-bot

copy-pr-bot Bot commented Sep 21, 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 ramakrishnap-nv self-assigned this Sep 21, 2026
@ramakrishnap-nv ramakrishnap-nv added non-breaking Introduces a non-breaking change improvement Improves an existing functionality labels Sep 21, 2026
Keep only the load-bearing why, not the full rationale already covered
in the PR description.
@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 21, 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.

@ramakrishnap-nv
ramakrishnap-nv marked this pull request as ready for review September 21, 2026 20:47

@gforsyth gforsyth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks reasonable.

My only non-blocking comment is that the two new build_*.sh scripts are effectively identical except for the CUOPT_SITE_PACKAGES line. It might be worth only maintaining one script with a switch instead of having to make changes to both.

Comment thread ci/docker/build_java_dynamic_ubi.sh Outdated
REPO_ROOT="${1:?missing repo root}"
OUT_DIR="${2:?missing output directory}"

CUOPT_SITE_PACKAGES="/usr/local/lib64/python3.14/site-packages/libcuopt"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How are we certain that this is always in python3.14 here but we require the Python short-version in the debian version?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Dockerfile.ubi hardcodes python3.14 everywhere already (no ARG PYTHON_SHORT_VER — it doesn't matrix Python versions like Dockerfile does, just dnf install python3.14 directly). This script matches that existing convention rather than introducing a new one.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed: UBI10's AppStream repo only offers python3.14 as an installable alternate version (plus the un-removable system python3.12 dnf itself needs) — no deadsnakes-style range like the Debian image has. build_images.yaml also never passes PYTHON_SHORT_VER to the UBI build (only CUDA_VER/CUOPT_VER), and the image tag has no -py suffix. So it's a support gap, not a preference for 3.14.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks, I don't need the massive LLM overexplanation

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Apologies, just wanted to make sure I figured out main root requirement for this. I will try to keep it short next time.

build_java_dynamic.sh and build_java_dynamic_ubi.sh differed only in how
they located libcuopt's pip site-packages dir. Take that path as a direct
argument instead, and have each Dockerfile pass its own layout, so there's
one script to maintain.
@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator Author

Merged the two scripts into one (build_java_dynamic.sh now takes the libcuopt site-packages path as an argument instead of deriving it from a Python version), pushed in a92e14c. Verified both Dockerfile and Dockerfile.ubi still build and run correctly with the consolidated script.

@gforsyth gforsyth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, thanks for consolidating!

@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator Author

/merge

@rapids-bot
rapids-bot Bot merged commit 4aef51a into main Sep 22, 2026
33 checks passed
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.

2 participants