Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/build_images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ jobs:
run: |
git rev-parse HEAD > ./ci/docker/context/COMMIT_SHA
git log -n1 --pretty='%ct' > ./ci/docker/context/COMMIT_TIME
# cpp/include and cpp/src aren't in the pip wheel; cuopt_jni.cpp needs both (the latter for
# internal headers like pdlp/cuopt_c_internal.hpp) to build the JNI library in-image.
- name: Stage Java sources for the in-image dynamic build
run: |
mkdir -p ./ci/docker/context/repo
cp -r ./java ./ci/docker/context/repo/java
mkdir -p ./ci/docker/context/repo/cpp
cp -r ./cpp/include ./ci/docker/context/repo/cpp/include
cp -r ./cpp/src ./ci/docker/context/repo/cpp/src
cp ./ci/docker/build_java_dynamic.sh ./ci/docker/context/build_java_dynamic.sh
- name: Login to NGC
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
Expand Down
29 changes: 29 additions & 0 deletions ci/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,37 @@ RUN \
&& apt-get purge -y --autoremove gcc \
&& rm -rf /var/lib/apt/lists/*

# Own stage so the build toolchain doesn't persist into cuopt-final. See build_java_dynamic.sh.
FROM install-env AS java-build

ARG PYTHON_SHORT_VER

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
openjdk-17-jdk \
build-essential \
cmake \
&& rm -rf /var/lib/apt/lists/*

# CMake's find_package(CUDAToolkit REQUIRED) needs nvcc on PATH; reuse cuda-headers instead of
# installing the toolkit again via apt.
COPY --from=cuda-headers /usr/local/cuda /usr/local/cuda
ENV PATH="/usr/local/cuda/bin:${PATH}"

COPY ./repo /build/repo
COPY ./build_java_dynamic.sh /build/build_java_dynamic.sh
RUN bash /build/build_java_dynamic.sh /build/repo \
"/usr/local/lib/python${PYTHON_SHORT_VER}/dist-packages/libcuopt" /opt/cuopt/java

FROM install-env AS cuopt-final

ARG PYTHON_SHORT_VER

# Headless JRE to run the Java bindings; version matches java/cuopt/pom.xml's maven.compiler.release.
RUN apt-get update \
&& apt-get install -y --no-install-recommends openjdk-17-jre-headless \
&& rm -rf /var/lib/apt/lists/*

# Make cuopt_grpc_server, cuopt_cli, and shared libraries available to all processes
# (profile.d scripts are only sourced by login shells; ENV works for all containers)
ENV PATH="/usr/local/cuda/bin:/usr/bin:/usr/local/bin:/usr/local/nvidia/bin/:/usr/local/lib/python${PYTHON_SHORT_VER}/dist-packages/libcuopt/bin:${PATH}"
Expand All @@ -119,6 +146,8 @@ COPY --from=cuda-libs /usr/local/cuda/lib64/libnvJitLink* /usr/local/cuda/lib64/
# Copy CUDA headers needed for runtime compilation (e.g., CuPy NVRTC).
COPY --from=cuda-headers /usr/local/cuda/include/ /usr/local/cuda/include/

COPY --from=java-build /opt/cuopt/java /opt/cuopt/java

# Entrypoint supports server selection:
# Default: Python REST server
# CUOPT_SERVER_TYPE=grpc: gRPC server (uses CUOPT_SERVER_PORT, CUOPT_GPU_COUNT)
Expand Down
21 changes: 21 additions & 0 deletions ci/docker/Dockerfile.ubi
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,27 @@ RUN \
dnf clean all && \
ln -sf /usr/bin/python3.14 /usr/bin/python

# Own stage so the build toolchain doesn't persist into cuopt-final. See build_java_dynamic.sh.
FROM install-env AS java-build

# No java-17-openjdk on UBI10 AppStream (only 21, 25); 21 satisfies the JDK 17+ requirement.
RUN dnf install -y java-21-openjdk-devel cmake gcc-c++ make && dnf clean all

# CMake's find_package(CUDAToolkit REQUIRED) needs nvcc on PATH; reuse cuda-headers instead of
# installing the toolkit again via dnf.
COPY --from=cuda-headers /usr/local/cuda /usr/local/cuda
ENV PATH="/usr/local/cuda/bin:${PATH}"

COPY ./repo /build/repo
COPY ./build_java_dynamic.sh /build/build_java_dynamic.sh
RUN bash /build/build_java_dynamic.sh /build/repo \
/usr/local/lib64/python3.14/site-packages/libcuopt /opt/cuopt/java

FROM install-env AS cuopt-final

# Headless JRE to run the Java bindings; version matches java/cuopt/pom.xml's maven.compiler.release.
RUN dnf install -y java-21-openjdk-headless && dnf clean all

# Register python via alternatives so the system alternatives database is
# consistent with the /usr/bin/python symlink created in install-env.
# NOTE: do NOT register python3 → python3.14 here: /usr/bin/dnf uses
Expand Down Expand Up @@ -110,6 +129,8 @@ COPY --from=cuda-libs /usr/local/cuda/lib64/libnvJitLink* /usr/local/cuda/lib64/
# Copy CUDA headers needed for runtime compilation (e.g., CuPy NVRTC)
COPY --from=cuda-headers /usr/local/cuda/include/ /usr/local/cuda/include/

COPY --from=java-build /opt/cuopt/java /opt/cuopt/java

COPY ./entrypoint.sh /opt/cuopt/entrypoint.sh
ENTRYPOINT ["/opt/cuopt/entrypoint.sh"]
CMD ["python", "-m", "cuopt_server.cuopt_service"]
49 changes: 49 additions & 0 deletions ci/docker/build_java_dynamic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Builds libcuopt_jni.so against the pip-installed libcuopt.so and compiles the Java classes with
# javac (java/cuopt/pom.xml has no non-test dependency, so Maven isn't needed). Writes cuopt.jar
# and libcuopt_jni.so to OUT_DIR.

set -euo pipefail

REPO_ROOT="${1:?missing repo root}"
CUOPT_SITE_PACKAGES="${2:?missing path to the pip-installed libcuopt package, e.g. /usr/local/lib/python3.14/dist-packages/libcuopt}"
OUT_DIR="${3:?missing output directory}"

if [[ ! -f "${CUOPT_SITE_PACKAGES}/lib64/libcuopt.so" ]]; then
echo "libcuopt.so not found under ${CUOPT_SITE_PACKAGES}/lib64; is libcuopt pip-installed yet?" >&2
exit 1
fi

# libcuopt.so resolves its own dependencies (TBB, NCCL, cuDSS, rmm, rapids_logger, cublas, ...)
# via RPATH, so cuopt_jni.so only needs to find libcuopt.so itself -- CUOPT_RUNTIME_LIBRARY_DIR
# bakes that into its RPATH too, no LD_LIBRARY_PATH needed.
export CUOPT_PREFIX="${CUOPT_SITE_PACKAGES}"
export CUOPT_LIBRARY="${CUOPT_SITE_PACKAGES}/lib64/libcuopt.so"
export CUOPT_RUNTIME_LIBRARY_DIR="${CUOPT_SITE_PACKAGES}/lib64"
# rmm and rapids_logger are separate pip packages with their own include dirs (unlike conda,
# where CUOPT_PREFIX/include/rapids covers all three).
PIP_SITE_PACKAGES="$(dirname "${CUOPT_SITE_PACKAGES}")"
export CUOPT_EXTRA_INCLUDE_DIRS="${REPO_ROOT}/cpp/include;${REPO_ROOT}/cpp/src;${PIP_SITE_PACKAGES}/librmm/include;${PIP_SITE_PACKAGES}/rapids_logger/include"
export CUOPT_JAVA_NATIVE_BUILD_DIR="${REPO_ROOT}/java/cuopt/build/native"

cd "${REPO_ROOT}"
bash java/cuopt/scripts/build_native.sh

GEN_SRC_DIR="${REPO_ROOT}/java/cuopt/target/generated-sources/cuopt"
bash java/cuopt/scripts/generate_constants.sh \
"${REPO_ROOT}/cpp/include/cuopt/mathematical_optimization/constants.h" \
"${GEN_SRC_DIR}"

CLASSES_DIR="$(mktemp -d)"
mapfile -t JAVA_SOURCES < <(find "${REPO_ROOT}/java/cuopt/src/main/java" "${GEN_SRC_DIR}" -name '*.java')
javac -d "${CLASSES_DIR}" --release 17 "${JAVA_SOURCES[@]}"

mkdir -p "${OUT_DIR}"
jar cf "${OUT_DIR}/cuopt.jar" -C "${CLASSES_DIR}" .
cp "${CUOPT_JAVA_NATIVE_BUILD_DIR}/libcuopt_jni.so" "${OUT_DIR}/"
rm -rf "${CLASSES_DIR}"

echo "Wrote ${OUT_DIR}/cuopt.jar and ${OUT_DIR}/libcuopt_jni.so"
Loading