Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
c156526
Make cuopt_static buildable for packaging, and add a scoped static build
ramakrishnap-nv Aug 27, 2026
bdb8905
Load the native library from the JAR, and package classifier JARs
ramakrishnap-nv Aug 27, 2026
e521ce7
Produce a working self-contained classifier JAR
ramakrishnap-nv Aug 27, 2026
2f626af
Build and verify the classifier JAR in CI
ramakrishnap-nv Aug 27, 2026
a0816cb
Run java-static-build on pull requests
ramakrishnap-nv Aug 27, 2026
87b17bb
Upload the classifier JARs as one Maven-repository-layout artifact
ramakrishnap-nv Aug 27, 2026
6df31e9
Build every classifier and gather them, following cuDF's layout
ramakrishnap-nv Aug 27, 2026
a4219ed
Run the whole Java suite against each classifier JAR
ramakrishnap-nv Aug 27, 2026
a623cc4
Add java-static-build-matrix to the pr-builder aggregator
ramakrishnap-nv Aug 27, 2026
78822d7
Route the new Maven calls through the retry wrapper
ramakrishnap-nv Aug 28, 2026
0c0fceb
Route Java console logging through System.out instead of native stdout
ramakrishnap-nv Aug 28, 2026
74325ec
Fix pre-commit findings: copyright year and clang-format alignment
ramakrishnap-nv Aug 28, 2026
9722612
Merge branch 'main' into java-static-classifiers
ramakrishnap-nv Aug 28, 2026
0bfbf4d
Patch vendored PSLP to respect verbose=false for its infeasible message
ramakrishnap-nv Aug 28, 2026
9558f83
Strip trailing whitespace from the PSLP patch file (pre-commit)
ramakrishnap-nv Aug 28, 2026
1ee37cb
Drop the trailing blank context line from the PSLP patch (pre-commit)
ramakrishnap-nv Aug 28, 2026
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
92 changes: 92 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,98 @@ jobs:
date: ${{ inputs.date }}
sha: ${{ inputs.sha }}
script: ci/build_cpp.sh
# Exploratory (#1817): one self-contained classifier JAR per CUDA major and architecture,
# gathered into a single Maven-repository artifact for publishing.
java-static-build-matrix:
permissions:
contents: read
uses: rapidsai/shared-workflows/.github/workflows/compute-matrix.yaml@main
with:
build_type: ${{ inputs.build_type || 'branch' }}
matrix_name: conda-cpp-build
matrix_filter: 'map(. + {CUDA_MAJOR: (.CUDA_VER | split(".") | .[0])}) | unique_by([.ARCH, .CUDA_MAJOR])'

java-static-build:
needs: [build-details, java-static-build-matrix]
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/custom-job.yaml@main
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.java-static-build-matrix.outputs.matrix) }}
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
date: ${{ inputs.date }}
sha: ${{ inputs.sha }}
node_type: "cpu16"
arch: ${{ matrix.ARCH }}
container_image: "rapidsai/ci-conda:26.10-latest"
script: "ci/build_java_static.sh"
artifact-name: "cuopt_java_${{ matrix.ARCH }}_cu${{ matrix.CUDA_MAJOR }}"
file_to_upload: "java/cuopt/classifier-jars/"

# Combines every classifier into one Maven-repository-layout artifact, which is the form a
# publishing workflow consumes. See rapidsai/build-infra#379.
# Runs the full Java suite against each classifier JAR on a GPU, with no libcuopt installed,
# so a JAR that loads but computes wrong answers fails here rather than at a user.
java-static-test:
needs: [java-static-build, java-static-build-matrix]
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/custom-job.yaml@main
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.java-static-build-matrix.outputs.matrix) }}
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
date: ${{ inputs.date }}
sha: ${{ inputs.sha }}
node_type: "gpu-l4-latest-1"
arch: ${{ matrix.ARCH }}
container_image: "rapidsai/ci-conda:26.10-latest"
script: "ci/test_java_static.sh"

java-static-gather:
needs: [java-static-build]
runs-on: linux-amd64-cpu4
permissions:
contents: read
steps:
- name: Checkout code repo
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ inputs.sha }}
persist-credentials: false
- name: Download per-classifier JAR artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: cuopt_java_*
path: ${{ runner.temp }}/jars
merge-multiple: true
- name: Assemble Maven repository layout
run: |
./java/cuopt/ci/assemble_maven_repo.sh \
--jars-dir "${RUNNER_TEMP}/jars" \
--output-dir "${RUNNER_TEMP}/maven-repo"
- name: Upload combined Maven repository artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: cuopt_java_maven_repo
path: ${{ runner.temp }}/maven-repo
if-no-files-found: error

java-build:
needs: cpp-build
permissions:
Expand Down
100 changes: 100 additions & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ jobs:
- conda-cpp-build
- conda-cpp-tests
- java-build
- java-static-build-matrix
- java-static-build
- java-static-test
- java-static-gather
- conda-python-build
- conda-python-tests
- docs-build
Expand Down Expand Up @@ -113,6 +117,7 @@ jobs:
- '!SECURITY.md'
- '!ci/build_wheel*.sh'
- '!ci/build_java.sh'
- '!ci/build_java_static.sh'
- '!ci/check_style.sh'
- '!ci/docker/**'
- '!ci/release/**'
Expand Down Expand Up @@ -178,6 +183,7 @@ jobs:
- '!agents/**'
- '!ci/build_docs.sh'
- '!ci/build_java.sh'
- '!ci/build_java_static.sh'
- '!ci/build_python.sh'
- '!ci/build_wheel*.sh'
- '!ci/check_style.sh'
Expand Down Expand Up @@ -213,6 +219,7 @@ jobs:
test_java:
- 'java/**'
- 'ci/build_java.sh'
- 'ci/build_java_static.sh'
- 'ci/test_java.sh'
- 'dependencies.yaml'
- '.github/workflows/pr.yaml'
Expand Down Expand Up @@ -262,6 +269,7 @@ jobs:
- '!agents/**'
- '!ci/build_docs.sh'
- '!ci/build_java.sh'
- '!ci/build_java_static.sh'
- '!ci/build_wheel*.sh'
- '!ci/check_style.sh'
- '!ci/docker/**'
Expand Down Expand Up @@ -337,6 +345,7 @@ jobs:
- '!ci/build_docs.sh'
- '!ci/build_python.sh'
- '!ci/build_java.sh'
- '!ci/build_java_static.sh'
- '!ci/check_style.sh'
- '!ci/docker/**'
- '!ci/release/**'
Expand Down Expand Up @@ -477,6 +486,97 @@ jobs:
artifact-name: "cuopt_docs"
container_image: "rapidsai/ci-conda:26.10-latest"
script: "ci/build_docs.sh"
# Exploratory (#1817): one self-contained classifier JAR per CUDA major and architecture,
# gathered into a single Maven-repository artifact. Compiles libcuopt from source, so it does
# not need conda-cpp-build.
java-static-build-matrix:
needs: changed-files
permissions:
contents: read
uses: rapidsai/shared-workflows/.github/workflows/compute-matrix.yaml@main
if: >-
fromJSON(needs.changed-files.outputs.changed_file_groups).test_java ||
fromJSON(needs.changed-files.outputs.changed_file_groups).test_cpp
with:
build_type: pull-request
matrix_name: conda-cpp-build
matrix_filter: 'map(. + {CUDA_MAJOR: (.CUDA_VER | split(".") | .[0])}) | unique_by([.ARCH, .CUDA_MAJOR])'

java-static-build:
needs: [java-static-build-matrix, changed-files]
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/custom-job.yaml@main
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.java-static-build-matrix.outputs.matrix) }}
if: >-
fromJSON(needs.changed-files.outputs.changed_file_groups).test_java ||
fromJSON(needs.changed-files.outputs.changed_file_groups).test_cpp
with:
build_type: pull-request
node_type: "cpu16"
arch: ${{ matrix.ARCH }}
container_image: "rapidsai/ci-conda:26.10-latest"
script: "ci/build_java_static.sh"
artifact-name: "cuopt_java_${{ matrix.ARCH }}_cu${{ matrix.CUDA_MAJOR }}"
file_to_upload: "java/cuopt/classifier-jars/"

# Runs the full Java suite against each classifier JAR on a GPU, with no libcuopt installed,
# so a JAR that loads but computes wrong answers fails here rather than at a user.
java-static-test:
needs: [java-static-build, java-static-build-matrix]
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/custom-job.yaml@main
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.java-static-build-matrix.outputs.matrix) }}
with:
build_type: pull-request
node_type: "gpu-l4-latest-1"
arch: ${{ matrix.ARCH }}
container_image: "rapidsai/ci-conda:26.10-latest"
script: "ci/test_java_static.sh"

java-static-gather:
needs: [java-static-build]
runs-on: linux-amd64-cpu4
permissions:
contents: read
steps:
- name: Checkout code repo
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- name: Download per-classifier JAR artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: cuopt_java_*
path: ${{ runner.temp }}/jars
merge-multiple: true
- name: Assemble Maven repository layout
run: |
./java/cuopt/ci/assemble_maven_repo.sh \
--jars-dir "${RUNNER_TEMP}/jars" \
--output-dir "${RUNNER_TEMP}/maven-repo"
- name: Upload combined Maven repository artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: cuopt_java_maven_repo
path: ${{ runner.temp }}/maven-repo
if-no-files-found: error

java-build:
needs: [conda-cpp-build, changed-files]
permissions:
Expand Down
70 changes: 70 additions & 0 deletions ci/build_java_static.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/bin/bash

# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Builds a self-contained Java classifier JAR and checks that it is actually self-contained.
#
# Unlike ci/build_java.sh, which installs a prebuilt libcuopt and links it as a shared library,
# this compiles libcuopt from source as a static archive and embeds it, so the JAR is the only
# thing a consumer installs. See #1817.

set -euo pipefail

if [[ -e /opt/conda/etc/profile.d/conda.sh ]]; then
. /opt/conda/etc/profile.d/conda.sh
fi

rapids-logger "Configuring conda strict channel priority"
conda config --set channel_priority strict

rapids-logger "Generating Java static build dependencies"
ENV_YAML_DIR=$(mktemp -d)
rapids-dependency-file-generator \
--output conda \
--file-key java_static \
--matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch)" | tee "${ENV_YAML_DIR}/env.yaml"

rapids-mamba-retry env create --yes -f "${ENV_YAML_DIR}/env.yaml" -n java_static

# Temporarily allow unbound variables for conda activation.
set +u
conda activate java_static
set -u

rapids-print-env

export CUOPT_PREFIX="${CONDA_PREFIX}"
STATIC_BUILD_DIR="${PWD}/cpp/build-static"
JNI_BUILD_DIR="${PWD}/java/cuopt/build/native-static"
JAR_OUTPUT_DIR="${PWD}/java/cuopt/classifier-jars"

rapids-logger "Building the scoped static libcuopt"
BUILD_DIR="${STATIC_BUILD_DIR}" bash java/cuopt/ci/build_static_libcuopt.sh

rapids-logger "Linking libcuopt into cuopt_jni"
cmake -S java/cuopt -B "${JNI_BUILD_DIR}" -GNinja \
-DCMAKE_BUILD_TYPE=Release \
-DCUOPT_PREFIX="${CUOPT_PREFIX}" \
-DCUOPT_STATIC_BUILD_DIR="${STATIC_BUILD_DIR}" \
-DCUOPT_EXTRA_INCLUDE_DIRS="${PWD}/cpp/include;${STATIC_BUILD_DIR}/include"
cmake --build "${JNI_BUILD_DIR}" --parallel "${PARALLEL_LEVEL:-$(nproc)}"

rapids-logger "Packaging the classifier JAR"
bash java/cuopt/ci/build_cuopt_java_jar.sh \
--native-lib "${JNI_BUILD_DIR}/libcuopt_jni.so" \
--cuda-version "${RAPIDS_CUDA_VERSION}" \
--output-dir "${JAR_OUTPUT_DIR}"

# The JAR looking fine on this machine proves nothing: the build environment supplies every
# dependency by construction. This resolves them the way a consumer's machine would.
rapids-logger "Verifying the JAR is self-contained"
CLASSIFIER_JAR=$(find "${JAR_OUTPUT_DIR}" -name 'cuopt-*.jar' \
! -name '*-sources.jar' ! -name '*-javadoc.jar' -print -quit)
bash java/cuopt/ci/verify_jar_dependencies.sh --jar "${CLASSIFIER_JAR}"

# The gather job combines the classifier directories from every matrix entry into one Maven
# repository layout; this job uploads its own directory as-is.
rapids-logger "Result"
du -h "${CLASSIFIER_JAR}" | sed 's/^/ /'
find "${JAR_OUTPUT_DIR}" -type f | sed "s|^${JAR_OUTPUT_DIR}/| |" | sort
79 changes: 79 additions & 0 deletions ci/test_java_static.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/bin/bash

# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Runs the Java test suite against an already-packaged classifier JAR, on a GPU, with no
# libcuopt installed. See #1817.
#
# ci/build_java_static.sh checks the JAR's dependencies statically; this is the other half,
# that the libraries it carries actually load and produce correct answers.
#
# Activates -Ppackaged-jar-tests so main compilation is skipped and the JAR supplies the classes
# and the native libraries. PackagedJarOriginCheck then asserts that is genuinely where they came
# from, so a stray target/classes cannot make this pass while testing the wrong thing.
#
# CUOPT_JAVA_JAR may be set to a classifier JAR to skip the download and test it directly.

set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=java/cuopt/ci/java_classifier.sh
. "${REPO_ROOT}/java/cuopt/ci/java_classifier.sh"
# shellcheck source=java/cuopt/scripts/maven.sh
. "${REPO_ROOT}/java/cuopt/scripts/maven.sh"
cuopt_maven_args

if [[ -e /opt/conda/etc/profile.d/conda.sh ]]; then
. /opt/conda/etc/profile.d/conda.sh
fi

if [[ -z "${CUOPT_JAVA_JAR:-}" ]]; then
case "$(arch)" in
x86_64) JOB_ARCH=amd64 ;;
aarch64) JOB_ARCH=arm64 ;;
*) echo "unsupported architecture $(arch)" >&2; exit 1 ;;
esac
ARTIFACT="cuopt_java_${JOB_ARCH}_cu${RAPIDS_CUDA_VERSION%%.*}"
rapids-logger "Downloading ${ARTIFACT}"
JAVA_PKG="$(rapids-download-from-github "${ARTIFACT}")"
CUOPT_JAVA_JAR="$(cuopt_java_resolve_artifact_jar "${JAVA_PKG}")"
fi
rapids-logger "Testing $(basename "${CUOPT_JAVA_JAR}")"

# A JDK, Maven and the CUDA runtime only. Installing libcuopt would defeat the test, since the
# JAR is supposed to carry its own copy.
rapids-logger "Creating a consumer-like environment"
ENV_YAML_DIR=$(mktemp -d)
cat > "${ENV_YAML_DIR}/env.yaml" << EOF
name: java_static_test
channels:
- conda-forge
dependencies:
- openjdk=11.*
- maven
- cuda-version=${RAPIDS_CUDA_VERSION%.*}
- libcublas
- libcusparse
EOF
rapids-mamba-retry env create --yes -f "${ENV_YAML_DIR}/env.yaml" -n java_static_test

set +u
conda activate java_static_test
set -u

if [[ -e "${CONDA_PREFIX}/lib/libcuopt.so" ]]; then
echo "ERROR: libcuopt.so is present in the test environment, so passing here would not show" >&2
echo " that the JAR is self-contained." >&2
exit 1
fi

rapids-print-env
nvidia-smi

rapids-logger "Running the suite against the packaged JAR"
cuopt_mvn -B -f "${REPO_ROOT}/java/cuopt/pom.xml" test \
-Ppackaged-jar-tests \
"-Dcuopt.jar.path=${CUOPT_JAVA_JAR}"

rapids-logger "Classifier JAR verified end to end"
Loading