diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a62de35e22..b717129c72 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -144,16 +144,14 @@ jobs: path: ${{ runner.temp }}/maven-repo if-no-files-found: error - # Release path only (vYY.MM.PP tags). Calls a vendored copy of the workflow - # (TODO: drop java/cuopt/ci/maven-publish/ and this file once - # rapidsai/shared-workflows#652 merges and ships in a branch cuOpt tracks). + # Release path only (vYY.MM.PP tags). java-maven-publish: if: startsWith(github.ref, 'refs/tags/v') needs: [java-static-gather] permissions: actions: read contents: read - uses: ./.github/workflows/maven-publish.yaml + uses: rapidsai/shared-workflows/.github/workflows/maven-publish.yaml@main secrets: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} @@ -164,8 +162,6 @@ jobs: # 'inputs' context at all, so fall back to github.sha or the retained bundle name ends # in a trailing "-" with nothing after it. source-git-sha: ${{ inputs.sha || github.sha }} - # com.nvidia.cuopt isn't its own verified Sonatype namespace; com.nvidia is. - profile-id: com.nvidia # false = validate + drop (safe). true = stage PENDING for manual publish. stage-for-maven-central-publish: true diff --git a/.github/workflows/maven-publish.yaml b/.github/workflows/maven-publish.yaml deleted file mode 100644 index 52ca3230fd..0000000000 --- a/.github/workflows/maven-publish.yaml +++ /dev/null @@ -1,175 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Publishes a bundle of Maven artifacts (jars, POMs, sources, javadoc). Every -# file is GPG-signed. The destination is chosen inside this workflow with -# `rapids-is-release-build`: -# - release build (vYY.MM.PP tag) -> Maven Central via the Sonatype Central -# Publisher Portal (OSSRH staging + validation, human-gated publish). -# - non-release build (nightly, branch push) -> Sonatype snapshot repository -# (direct HTTP PUT, immediately available, no staging or portal step). -# -# Vendored from rapidsai/shared-workflows. TODO: drop this file and -# java/cuopt/ci/maven-publish/ once rapidsai/shared-workflows#652 ships in a -# branch cuOpt tracks, and call that workflow directly again. -name: Java Maven publish (vendored) - -on: - workflow_call: - inputs: - artifact-name: - description: | - Name of the GitHub Actions artifact (uploaded by the caller job via - actions/upload-artifact) to download and publish. Contents must be - a Maven repository directory. - required: true - type: string - stage-for-maven-central-publish: - description: | - When true, leave the bundle PENDING in the Sonatype Central - Publisher Portal for a human to release via the Portal UI - (https://central.sonatype.com). When false (default), validate then - drop the bundle. Only applies to the Maven Central path (release - builds). Ignored on the snapshot path. - required: false - type: boolean - default: false - source-git-sha: - description: | - Git SHA to include in the retained bundle name for provenance. - required: false - type: string - default: '' - profile-id: - description: | - Sonatype staging profile to search, if different from the Maven groupId read from - the POM (e.g. a verified parent namespace like com.nvidia for a com.nvidia.cuopt - groupId). Defaults to the groupId when not set. Only applies to the Maven Central - path (release builds); ignored on the snapshot path. - required: false - type: string - default: '' - secrets: - GPG_PRIVATE_KEY: - description: Armored GPG private key used to sign every artifact at upload. - required: true - GPG_PASSPHRASE: - description: Passphrase for GPG_PRIVATE_KEY. - required: true - MAVEN_DEPLOY_TOKEN: - description: Auth token paired with vars.MAVEN_DEPLOY_USERNAME. - required: true - -defaults: - run: - shell: bash - -permissions: - actions: read - contents: read - -jobs: - maven-publish: - name: maven publish - runs-on: linux-amd64-cpu4 - steps: - - name: Checkout code repo - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - persist-credentials: false - - - name: Install gha-tools - run: | - # rapids-is-release-build lives in rapidsai/gha-tools. Fetch the - # release tarball and add it to PATH so the release check below - # (and any downstream steps) can find it. - mkdir -p /tmp/gha-tools - wget -qO- https://github.com/rapidsai/gha-tools/releases/latest/download/tools.tar.gz \ - | tar -xz -C /tmp/gha-tools - echo "/tmp/gha-tools" >> "${GITHUB_PATH}" - - - name: Determine publish destination - id: destination - run: | - if rapids-is-release-build; then - echo "is_release=true" >> "${GITHUB_OUTPUT}" - else - echo "is_release=false" >> "${GITHUB_OUTPUT}" - fi - - - name: Download Maven repository artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - name: ${{ inputs.artifact-name }} - path: maven-repo - - - name: Prepare signed Maven bundle - id: prepare - run: | - java/cuopt/ci/maven-publish/prepare_maven_bundle.sh \ - --input maven-repo \ - --output signed-maven-repo - env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - - name: Upload to Maven Central - id: publish-central - if: ${{ steps.destination.outputs.is_release == 'true' }} - run: | - args=( - --input signed-maven-repo - --group-id "${GROUP_ID}" - --artifact-id "${ARTIFACT_ID}" - --version "${VERSION}" - --output-bundle central-bundle.zip - --auto-drop "${AUTO_DROP}" - ) - if [[ -n ${PROFILE_ID} ]]; then - args+=(--profile-id "${PROFILE_ID}") - fi - java/cuopt/ci/maven-publish/maven_central_publish.sh "${args[@]}" - env: - GROUP_ID: ${{ steps.prepare.outputs.GROUP_ID }} - PROFILE_ID: ${{ inputs.profile-id }} - ARTIFACT_ID: ${{ steps.prepare.outputs.ARTIFACT_ID }} - VERSION: ${{ steps.prepare.outputs.VERSION }} - # Invert: stage-for-...=true means don't auto-drop. - AUTO_DROP: ${{ !inputs.stage-for-maven-central-publish }} - MAVEN_DEPLOY_USERNAME: ${{ vars.MAVEN_DEPLOY_USERNAME }} - MAVEN_DEPLOY_TOKEN: ${{ secrets.MAVEN_DEPLOY_TOKEN }} - - - name: Upload to Sonatype snapshots - id: publish-snapshot - if: ${{ steps.destination.outputs.is_release == 'false' }} - run: | - java/cuopt/ci/maven-publish/maven_snapshot_publish.sh \ - --input signed-maven-repo \ - --group-id "${GROUP_ID}" \ - --artifact-id "${ARTIFACT_ID}" \ - --version "${VERSION}" \ - --output-bundle snapshot-bundle.zip - env: - GROUP_ID: ${{ steps.prepare.outputs.GROUP_ID }} - ARTIFACT_ID: ${{ steps.prepare.outputs.ARTIFACT_ID }} - VERSION: ${{ steps.prepare.outputs.VERSION }} - MAVEN_DEPLOY_USERNAME: ${{ vars.MAVEN_DEPLOY_USERNAME }} - MAVEN_DEPLOY_TOKEN: ${{ secrets.MAVEN_DEPLOY_TOKEN }} - - - name: Retain signed Maven Central bundle - if: ${{ !cancelled() && steps.destination.outputs.is_release == 'true' }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: maven-central-bundle-${{ steps.prepare.outputs.ARTIFACT_ID }}-${{ steps.prepare.outputs.VERSION }}-${{ inputs.source-git-sha }} - path: central-bundle.zip - if-no-files-found: ignore - retention-days: 90 - - - name: Retain signed Sonatype snapshot bundle - if: ${{ !cancelled() && steps.destination.outputs.is_release == 'false' }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: sonatype-snapshot-bundle-${{ steps.prepare.outputs.ARTIFACT_ID }}-${{ steps.prepare.outputs.VERSION }}-${{ inputs.source-git-sha }} - path: snapshot-bundle.zip - if-no-files-found: ignore - retention-days: 90 diff --git a/java/cuopt/ci/maven-publish/argparse.sh b/java/cuopt/ci/maven-publish/argparse.sh deleted file mode 100755 index 32486c6dba..0000000000 --- a/java/cuopt/ci/maven-publish/argparse.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Shared argparse helpers for the ci/maven-publish/ host orchestrator scripts. -# Meant to be sourced, not executed: -# . "${SCRIPT_DIR}/argparse.sh" - -# require_value : exit 1 if is empty. -require_value() { - local flag=$1 - local value=$2 - if [[ -z ${value} ]]; then - echo "Error: ${flag} requires a value" >&2 - exit 1 - fi -} - -# require_arg : exit 1 if empty, printing help if defined. -require_arg() { - local flag=$1 - local value=$2 - if [[ -z ${value} ]]; then - echo "Error: ${flag} is required." >&2 - if declare -F print_help > /dev/null; then - print_help - fi - exit 1 - fi -} diff --git a/java/cuopt/ci/maven-publish/maven_central_publish.sh b/java/cuopt/ci/maven-publish/maven_central_publish.sh deleted file mode 100755 index 39e79dfe66..0000000000 --- a/java/cuopt/ci/maven-publish/maven_central_publish.sh +++ /dev/null @@ -1,230 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Creates a deployment bundle from a signed Maven repository tree, uploads its -# files through Sonatype's OSSRH staging compatibility API, and polls the -# Publisher Portal until the deployment passes validation. -# Never calls the /publish endpoint: publication always requires a human click. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/argparse.sh" -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/maven_utils.sh" -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/maven_central_publish_steps.sh" - -CENTRAL_PORTAL_URL="https://central.sonatype.com" -OSSRH_STAGING_API_URL="https://ossrh-staging-api.central.sonatype.com" -# Used by wait_for_validated in the sourced step library. -# shellcheck disable=SC2034 -POLL_INTERVAL_SEC=15 -# shellcheck disable=SC2034 -POLL_TIMEOUT_SEC=900 - -INPUT_DIR="" -GROUP_ID="" -PROFILE_ID="" -ARTIFACT_ID="" -VERSION="" -OUTPUT_BUNDLE="" -AUTO_DROP="true" - -print_help() { - cat << EOF - -Usage: maven_central_publish.sh --input --group-id \\ - --artifact-id --version \\ - --output-bundle [OPTIONS] - -Copies a signed Maven repository tree into scratch space, generates .md5 and -.sha1 sidecars, and creates a retained ZIP. It uploads the repository files -individually through Sonatype's OSSRH staging compatibility API, then hands the -repository to the Central Publisher Portal without publishing it. - -REQUIRED: - -i, --input Signed Maven repository directory. - -g, --group-id Maven groupId, e.g. ai.rapids. - -a, --artifact-id Maven artifactId, e.g. cudf. - -v, --version Release version, e.g. 1.2.3. - -o, --output-bundle Path for the retained deployment ZIP. - -OPTIONS: - --profile-id

Sonatype staging profile to search, if different from - --group-id (e.g. a verified parent namespace like - com.nvidia for a com.nvidia.cuopt groupId). Defaults to - --group-id. - --auto-drop Drop after VALIDATED (default: true). - -h, --help Show this help message. - -ENVIRONMENT VARIABLES: - MAVEN_DEPLOY_USERNAME Publisher Portal user token username. - MAVEN_DEPLOY_TOKEN Publisher Portal user token password. - GITHUB_OUTPUT If set, receives STAGING_REPOSITORY_KEY and - DEPLOYMENT_ID. - -EOF -} - -parse_args() { - while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - print_help - exit 0 - ;; - -i|--input) - require_value "$1" "${2:-}" - INPUT_DIR=$2 - shift 2 - ;; - -g|--group-id) - require_value "$1" "${2:-}" - GROUP_ID=$2 - shift 2 - ;; - --profile-id) - require_value "$1" "${2:-}" - PROFILE_ID=$2 - shift 2 - ;; - -a|--artifact-id) - require_value "$1" "${2:-}" - ARTIFACT_ID=$2 - shift 2 - ;; - -v|--version) - require_value "$1" "${2:-}" - VERSION=$2 - shift 2 - ;; - -o|--output-bundle) - require_value "$1" "${2:-}" - OUTPUT_BUNDLE=$2 - shift 2 - ;; - --auto-drop) - require_value "$1" "${2:-}" - AUTO_DROP=$2 - shift 2 - ;; - *) - echo "Error: Unknown argument $1" - print_help - exit 1 - ;; - esac - done -} - -parse_args "$@" - -require_arg --input "${INPUT_DIR}" -require_arg --group-id "${GROUP_ID}" -require_arg --artifact-id "${ARTIFACT_ID}" -require_arg --version "${VERSION}" -require_arg --output-bundle "${OUTPUT_BUNDLE}" - -if [[ ${AUTO_DROP} != "true" && ${AUTO_DROP} != "false" ]]; then - fatal "--auto-drop must be 'true' or 'false' (got '${AUTO_DROP}')" -fi -require_maven_coordinates "${GROUP_ID}" "${ARTIFACT_ID}" "${VERSION}" -require_release_version "${VERSION}" -# A groupId nested under a verified parent namespace (e.g. com.nvidia.cuopt under the -# verified com.nvidia) still searches/creates its OSSRH staging repo under the parent's -# Sonatype profile, not the full groupId string -- so default to GROUP_ID, but let callers -# override it. -PROFILE_ID="${PROFILE_ID:-${GROUP_ID}}" -if [[ ! -d ${INPUT_DIR} ]]; then - fatal "--input '${INPUT_DIR}' does not exist or is not a directory" -fi -: "${MAVEN_DEPLOY_USERNAME:?must be set}" -: "${MAVEN_DEPLOY_TOKEN:?must be set}" - -require_cmds base64 curl jq zip md5sum sha1sum - -INPUT_DIR="$(cd "${INPUT_DIR}" && pwd)" -OUTPUT_BUNDLE_PARENT="$(dirname "${OUTPUT_BUNDLE}")" -mkdir -p "${OUTPUT_BUNDLE_PARENT}" -OUTPUT_BUNDLE_PARENT="$(cd "${OUTPUT_BUNDLE_PARENT}" && pwd)" -OUTPUT_BUNDLE="${OUTPUT_BUNDLE_PARENT}/$(basename "${OUTPUT_BUNDLE}")" -if [[ -e ${OUTPUT_BUNDLE} ]]; then - fatal "--output-bundle '${OUTPUT_BUNDLE}' already exists" -fi -echo "Maven Central upload" -echo " coordinates: ${GROUP_ID}:${ARTIFACT_ID}:${VERSION}" -echo " profile: ${PROFILE_ID}" -echo " auto-drop: ${AUTO_DROP}" -echo " input dir: ${INPUT_DIR}" -echo " output bundle: ${OUTPUT_BUNDLE}" -echo " staging API: ${OSSRH_STAGING_API_URL}" -echo " target portal: ${CENTRAL_PORTAL_URL}" - -WORK_DIR="$(mktemp -d)" -STAGING_REPOSITORY_KEY="" -STAGING_FILE_UPLOADED="false" -STAGING_HANDED_OFF="false" - -cleanup() { - local status=$? - if (( status != 0 )) \ - && [[ ${STAGING_FILE_UPLOADED} == "true" ]] \ - && [[ ${STAGING_HANDED_OFF} != "true" ]]; then - if [[ -z ${STAGING_REPOSITORY_KEY} ]]; then - STAGING_REPOSITORY_KEY=$(staging_search_repositories open 2>/dev/null \ - | jq -r 'if (.repositories | length) == 1 then .repositories[0].key else empty end' \ - 2>/dev/null) || true - fi - if [[ -n ${STAGING_REPOSITORY_KEY} ]]; then - echo "Cleaning up open OSSRH staging repository ${STAGING_REPOSITORY_KEY}" >&2 - staging_drop_repository "${STAGING_REPOSITORY_KEY}" \ - || echo "Warning: could not drop staging repository ${STAGING_REPOSITORY_KEY}; remove it before retrying" >&2 - fi - fi - rm -rf "${WORK_DIR}" - return "${status}" -} -trap cleanup EXIT - -BUNDLE_DIR="${WORK_DIR}/bundle" -BUNDLE_ARTIFACT_DIR="${BUNDLE_DIR}/$(maven_group_path "${GROUP_ID}")/${ARTIFACT_ID}/${VERSION}" -mkdir -p "${BUNDLE_DIR}" - -require_no_open_staging_repository -copy_bundle "${INPUT_DIR}" "${BUNDLE_DIR}" -require_bundle_contents "${BUNDLE_ARTIFACT_DIR}" -generate_bundle_checksums "${BUNDLE_ARTIFACT_DIR}" - -BUNDLE_ZIP="${WORK_DIR}/${ARTIFACT_ID}-${VERSION}.zip" -create_bundle_zip "${BUNDLE_ZIP}" "${BUNDLE_DIR}" -mv "${BUNDLE_ZIP}" "${OUTPUT_BUNDLE}" - -DEPLOYMENT_ID="" -upload_tree_to_staging "${BUNDLE_DIR}" -find_open_staging_repository -echo "Handing staging repository to the Publisher Portal" -staging_handoff_repository "${STAGING_REPOSITORY_KEY}" -wait_for_portal_handoff "${STAGING_REPOSITORY_KEY}" -STAGING_HANDED_OFF="true" -if [[ -n ${GITHUB_OUTPUT:-} ]]; then - echo "STAGING_REPOSITORY_KEY=${STAGING_REPOSITORY_KEY}" >> "${GITHUB_OUTPUT}" - echo "DEPLOYMENT_ID=${DEPLOYMENT_ID}" >> "${GITHUB_OUTPUT}" -fi - -wait_for_validated "${DEPLOYMENT_ID}" - -if [[ ${AUTO_DROP} == "true" ]]; then - drop_deployment "${DEPLOYMENT_ID}" -else - cat <&2 - rm -f "${body_file}" - return 1 - fi - cat "${body_file}" - rm -f "${body_file}" -} - -# _sonatype_upload FILE URL -# Basic auth, PUT FILE. Logs failure and returns 1 on non-2xx. -_sonatype_upload() { - local upload_file=$1 url=$2 - local body_file status - body_file=$(mktemp) - status=$(curl -sS --retry 3 --retry-delay 2 \ - --user "${MAVEN_DEPLOY_USERNAME}:${MAVEN_DEPLOY_TOKEN}" \ - --upload-file "${upload_file}" \ - -o "${body_file}" -w '%{http_code}' "${url}") \ - || status="transport-error" - if [[ ${status} != 2* ]]; then - _sonatype_error_log PUT "${url}" "${status}" "${body_file}" >&2 - rm -f "${body_file}" - return 1 - fi - rm -f "${body_file}" -} - -staging_search_repositories() { - local state=${1:-} - local url - url="${OSSRH_STAGING_API_URL}/manual/search/repositories?ip=client&profile_id=$(printf %s "${PROFILE_ID}" | jq -sRr @uri)" - if [[ -n ${state} ]]; then - url+="&state=$(printf %s "${state}" | jq -sRr @uri)" - fi - _sonatype_call GET "${url}" -} - -staging_upload_file() { - local file_path=$1 repository_path=$2 - local encoded_path - encoded_path=$(jq -rn --arg path "${repository_path}" \ - '$path | split("/") | map(@uri) | join("/")') - _sonatype_upload "${file_path}" \ - "${OSSRH_STAGING_API_URL}/service/local/staging/deploy/maven2/${encoded_path}" -} - -staging_handoff_repository() { - local repository_key=$1 - _sonatype_call POST \ - "${OSSRH_STAGING_API_URL}/manual/upload/repository/${repository_key}?publishing_type=portal_api" \ - >/dev/null -} - -staging_drop_repository() { - local repository_key=$1 - _sonatype_call DELETE \ - "${OSSRH_STAGING_API_URL}/manual/drop/repository/${repository_key}" \ - >/dev/null -} - -portal_get_status() { - local deployment_id=$1 - _sonatype_call POST \ - "${CENTRAL_PORTAL_URL}/api/v1/publisher/status?id=${deployment_id}" \ - || echo '{}' -} - -portal_drop_deployment() { - local deployment_id=$1 - _sonatype_call DELETE \ - "${CENTRAL_PORTAL_URL}/api/v1/publisher/deployment/${deployment_id}" \ - >/dev/null -} - -copy_bundle() { - local source_dir=$1 destination_dir=$2 - echo "Copying signed Maven tree into bundle scratch space" - cp -a "${source_dir}/." "${destination_dir}/" -} - -require_bundle_contents() { - local artifact_dir=$1 - local pattern desc entry - local expected=( - "${ARTIFACT_ID}-${VERSION}.pom|the POM" - "${ARTIFACT_ID}-${VERSION}.pom.asc|the POM signature" - "${ARTIFACT_ID}-${VERSION}.jar|the unclassified primary jar" - "${ARTIFACT_ID}-${VERSION}.jar.asc|the unclassified primary jar signature" - "${ARTIFACT_ID}-${VERSION}-sources.jar|the sources jar" - "${ARTIFACT_ID}-${VERSION}-sources.jar.asc|the sources jar signature" - "${ARTIFACT_ID}-${VERSION}-javadoc.jar|the javadoc jar" - "${ARTIFACT_ID}-${VERSION}-javadoc.jar.asc|the javadoc jar signature" - ) - for entry in "${expected[@]}"; do - IFS='|' read -r pattern desc <<< "${entry}" - if [[ ! -f ${artifact_dir}/${pattern} ]]; then - fatal "bundle is missing ${desc} (${pattern})" - fi - done -} - -generate_bundle_checksums() { - local root=$1 file - find "${root}" -type f \( \ - -name '*.md5' -o -name '*.sha1' -o -name '*.sha256' -o -name '*.sha512' \ - \) -delete - while IFS= read -r file; do - md5sum "${file}" | awk '{ print $1 }' > "${file}.md5" - sha1sum "${file}" | awk '{ print $1 }' > "${file}.sha1" - done < <(find "${root}" -type f ! -name '*.md5' ! -name '*.sha1' \ - ! -name '*.sha256' ! -name '*.sha512' | sort) -} - -create_bundle_zip() { - local zip_path=$1 bundle_dir=$2 - echo "Zipping bundle at ${zip_path}" - (cd "${bundle_dir}" && zip -qr "${zip_path}" .) -} - -require_no_open_staging_repository() { - local response count - response=$(staging_search_repositories open) - count=$(jq -er '.repositories | length' <<< "${response}") \ - || fatal "OSSRH staging search returned an invalid response" - if (( count != 0 )); then - jq . <<< "${response}" >&2 || true - fatal "found ${count} open OSSRH staging repository/repositories for ${PROFILE_ID} from this IP; drop or hand them off before retrying" - fi -} - -upload_tree_to_staging() { - local root=$1 file repository_path count=0 - echo "Uploading Maven repository files to the OSSRH staging service" - while IFS= read -r -d '' file; do - repository_path=${file#"${root}/"} - staging_upload_file "${file}" "${repository_path}" - # Consumed by the EXIT cleanup trap in maven_central_publish.sh. - # shellcheck disable=SC2034 - STAGING_FILE_UPLOADED="true" - count=$((count + 1)) - done < <(find "${root}" -type f -print0) - echo " uploaded files: ${count}" -} - -find_open_staging_repository() { - local response count - response=$(staging_search_repositories open) - count=$(jq -er '.repositories | length' <<< "${response}") \ - || fatal "OSSRH staging search returned an invalid response" - if (( count != 1 )); then - jq . <<< "${response}" >&2 || true - fatal "expected exactly one open OSSRH staging repository after upload, found ${count}" - fi - STAGING_REPOSITORY_KEY=$(jq -er '.repositories[0].key | select(type == "string" and length > 0)' \ - <<< "${response}") || fatal "OSSRH staging response did not contain a repository key" - # Keys are composite paths of the form //--default-repository, - # so slashes are expected. Just guard against characters that would break URL path use. - if [[ ! ${STAGING_REPOSITORY_KEY} =~ ^[A-Za-z0-9._/-]+$ ]]; then - fatal "OSSRH staging service returned an invalid repository key: ${STAGING_REPOSITORY_KEY}" - fi - echo " staging repository: ${STAGING_REPOSITORY_KEY}" -} - -wait_for_portal_handoff() { - local repository_key=$1 - local response state elapsed=0 - echo "Polling OSSRH staging handoff (interval=${POLL_INTERVAL_SEC}s, timeout=${POLL_TIMEOUT_SEC}s)" - while (( elapsed < POLL_TIMEOUT_SEC )); do - response=$(staging_search_repositories) - state=$(jq -r --arg key "${repository_key}" \ - '.repositories[]? | select(.key == $key) | .state // "UNKNOWN"' \ - <<< "${response}") - DEPLOYMENT_ID=$(jq -r --arg key "${repository_key}" \ - '.repositories[]? | select(.key == $key) | .portal_deployment_id // empty' \ - <<< "${response}") - echo " [${elapsed}s] staging state=${state:-UNKNOWN}" - if [[ -n ${DEPLOYMENT_ID} ]]; then - if [[ ! ${DEPLOYMENT_ID} =~ ^[A-Za-z0-9-]+$ ]]; then - fatal "OSSRH staging service returned an invalid Portal deployment id" - fi - echo " Portal deployment id: ${DEPLOYMENT_ID}" - return 0 - fi - sleep "${POLL_INTERVAL_SEC}" - elapsed=$((elapsed + POLL_INTERVAL_SEC)) - done - fatal "timed out waiting for OSSRH staging repository '${repository_key}' to reach the Portal" -} - -wait_for_validated() { - local deployment_id=$1 - local response state elapsed=0 - echo "Polling status (interval=${POLL_INTERVAL_SEC}s, timeout=${POLL_TIMEOUT_SEC}s)" - while (( elapsed < POLL_TIMEOUT_SEC )); do - response=$(portal_get_status "${deployment_id}") - state=$(jq -r '.deploymentState // "UNKNOWN"' <<< "${response}") - echo " [${elapsed}s] state=${state}" - case "${state}" in - VALIDATED) return 0 ;; - FAILED|VALIDATION_FAILED) - jq . <<< "${response}" >&2 || true - fatal "Publisher Portal reported terminal state ${state}" - ;; - PUBLISHED) - fatal "unexpected PUBLISHED state (USER_MANAGED deployments should never auto-publish)" - ;; - esac - sleep "${POLL_INTERVAL_SEC}" - elapsed=$((elapsed + POLL_INTERVAL_SEC)) - done - fatal "timed out waiting for VALIDATED, last state was '${state}'" -} - -drop_deployment() { - local deployment_id=$1 - echo "Dropping deployment ${deployment_id} (--auto-drop true)" - portal_drop_deployment "${deployment_id}" - echo "Deployment dropped. Nothing was published to Maven Central." -} diff --git a/java/cuopt/ci/maven-publish/maven_snapshot_publish.sh b/java/cuopt/ci/maven-publish/maven_snapshot_publish.sh deleted file mode 100755 index 34efcd1152..0000000000 --- a/java/cuopt/ci/maven-publish/maven_snapshot_publish.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Deploys the Maven repository tree under --input to the Sonatype snapshot -# repository via 'mvn deploy-file' inside a container, then downloads what -# Nexus stored into a retained ZIP at --output-bundle. VERSION must end with -# '-SNAPSHOT'. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/argparse.sh" -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/maven_utils.sh" - -INPUT_DIR="" -GROUP_ID="" -ARTIFACT_ID="" -VERSION="" -OUTPUT_BUNDLE="" -IMAGE="maven:3-eclipse-temurin-17" - -print_help() { - cat << EOF - -Usage: maven_snapshot_publish.sh --input --group-id \\ - --artifact-id --version \\ - --output-bundle - -Deploys the Maven repository tree under --input to the Sonatype snapshot -repository via 'mvn deploy-file' inside a container, then downloads what -Nexus stored into a retained ZIP at --output-bundle. VERSION must end with -'-SNAPSHOT'. - -REQUIRED: - -i, --input Maven repository directory to deploy. - -g, --group-id Maven groupId, e.g. ai.rapids. - -a, --artifact-id Maven artifactId, e.g. cudf. - -v, --version Snapshot version, e.g. 1.2.3-SNAPSHOT. - -o, --output-bundle Path for the retained snapshot ZIP. - -OPTIONS: - -h, --help Show this help message. - -ENVIRONMENT VARIABLES: - MAVEN_DEPLOY_USERNAME Publisher Portal user token username. - MAVEN_DEPLOY_TOKEN Publisher Portal user token password. - -EOF -} - -parse_args() { - while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - print_help - exit 0 - ;; - -i|--input) - require_value "$1" "${2:-}" - INPUT_DIR=$2 - shift 2 - ;; - -g|--group-id) - require_value "$1" "${2:-}" - GROUP_ID=$2 - shift 2 - ;; - -a|--artifact-id) - require_value "$1" "${2:-}" - ARTIFACT_ID=$2 - shift 2 - ;; - -v|--version) - require_value "$1" "${2:-}" - VERSION=$2 - shift 2 - ;; - -o|--output-bundle) - require_value "$1" "${2:-}" - OUTPUT_BUNDLE=$2 - shift 2 - ;; - *) - echo "Error: Unknown argument $1" - print_help - exit 1 - ;; - esac - done -} - -parse_args "$@" - -require_arg --input "${INPUT_DIR}" -require_arg --group-id "${GROUP_ID}" -require_arg --artifact-id "${ARTIFACT_ID}" -require_arg --version "${VERSION}" -require_arg --output-bundle "${OUTPUT_BUNDLE}" - -require_maven_coordinates "${GROUP_ID}" "${ARTIFACT_ID}" "${VERSION}" -require_snapshot_version "${VERSION}" - -if [[ ! -d ${INPUT_DIR} ]]; then - fatal "--input '${INPUT_DIR}' does not exist or is not a directory" -fi -: "${MAVEN_DEPLOY_USERNAME:?must be set}" -: "${MAVEN_DEPLOY_TOKEN:?must be set}" - -require_cmds docker - -INPUT_DIR="$(cd "${INPUT_DIR}" && pwd)" -OUTPUT_BUNDLE_PARENT="$(dirname "${OUTPUT_BUNDLE}")" -mkdir -p "${OUTPUT_BUNDLE_PARENT}" -OUTPUT_BUNDLE_PARENT="$(cd "${OUTPUT_BUNDLE_PARENT}" && pwd)" -OUTPUT_BUNDLE="${OUTPUT_BUNDLE_PARENT}/$(basename "${OUTPUT_BUNDLE}")" -if [[ -e ${OUTPUT_BUNDLE} ]]; then - fatal "--output-bundle '${OUTPUT_BUNDLE}' already exists" -fi - -BUNDLE_SCRATCH="$(mktemp -d)" -trap 'rm -rf "${BUNDLE_SCRATCH}"' EXIT - -echo "Sonatype snapshot deploy: ${GROUP_ID}:${ARTIFACT_ID}:${VERSION}" - -export MAVEN_DEPLOY_USERNAME MAVEN_DEPLOY_TOKEN \ - GROUP_ID ARTIFACT_ID VERSION - -docker run \ - --rm \ - --volume "${INPUT_DIR}:/input:ro" \ - --volume "${BUNDLE_SCRATCH}:/bundle" \ - --volume "${SCRIPT_DIR}:/scripts:ro" \ - --workdir /bundle \ - --env MAVEN_DEPLOY_USERNAME --env MAVEN_DEPLOY_TOKEN \ - --env GROUP_ID --env ARTIFACT_ID --env VERSION \ - --env HOST_UID="$(id -u)" --env HOST_GID="$(id -g)" \ - "${IMAGE}" \ - bash /scripts/maven_snapshot_publish_in_container.sh - -BUNDLE_IN_SCRATCH="${BUNDLE_SCRATCH}/${ARTIFACT_ID}-${VERSION}.zip" -if [[ ! -f ${BUNDLE_IN_SCRATCH} ]]; then - fatal "container did not produce ${BUNDLE_IN_SCRATCH}" -fi -mv "${BUNDLE_IN_SCRATCH}" "${OUTPUT_BUNDLE}" diff --git a/java/cuopt/ci/maven-publish/maven_snapshot_publish_in_container.sh b/java/cuopt/ci/maven-publish/maven_snapshot_publish_in_container.sh deleted file mode 100755 index 929924064a..0000000000 --- a/java/cuopt/ci/maven-publish/maven_snapshot_publish_in_container.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# In-container worker for maven_snapshot_publish.sh. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/maven_utils.sh" -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/prepare_maven_bundle_steps.sh" -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/maven_snapshot_publish_steps.sh" - -: "${MAVEN_DEPLOY_USERNAME:?must be set}" -: "${MAVEN_DEPLOY_TOKEN:?must be set}" -: "${GROUP_ID:?must be set}" -: "${ARTIFACT_ID:?must be set}" -: "${VERSION:?must be set}" -: "${HOST_UID:?must be set}" -: "${HOST_GID:?must be set}" - -INPUT_DIR=/input -BUNDLE_DIR=/bundle -SNAPSHOT_REPOSITORY_URL="https://central.sonatype.com/repository/maven-snapshots" - -trap 'chown -R "${HOST_UID}:${HOST_GID}" "${BUNDLE_DIR}" 2>/dev/null || true' EXIT - -install_container_deps -require_cmds curl mvn zip - -WORK_DIR="$(mktemp -d)" -DEPLOY_DIR="${WORK_DIR}/deploy" -DOWNLOAD_DIR="${WORK_DIR}/downloaded" -SETTINGS_FILE="${WORK_DIR}/settings.xml" -DEPLOY_LOG="${WORK_DIR}/deploy.log" -mkdir -p "${DEPLOY_DIR}" "${DOWNLOAD_DIR}" - -stage_snapshot_deploy_inputs "${INPUT_DIR}" "${DEPLOY_DIR}" - -GROUP_PATH="$(maven_group_path "${GROUP_ID}")" -ARTIFACT_DIR="${DEPLOY_DIR}/${GROUP_PATH}/${ARTIFACT_ID}/${VERSION}" -if [[ ! -d ${ARTIFACT_DIR} ]]; then - fatal "expected artifact dir ${ARTIFACT_DIR} not present in input" -fi - -write_maven_settings "${SETTINGS_FILE}" - -deploy_snapshot_with_mvn \ - "${ARTIFACT_DIR}" "${ARTIFACT_ID}" "${VERSION}" \ - "${SNAPSHOT_REPOSITORY_URL}" "${SETTINGS_FILE}" "${DEPLOY_LOG}" - -# Scope at the artifactId directory so the parent maven-metadata.xml is -# fetched alongside everything under the SNAPSHOT version directory. -download_deployed_tree \ - "${DEPLOY_LOG}" "${SNAPSHOT_REPOSITORY_URL}" \ - "${GROUP_PATH}/${ARTIFACT_ID}" "${DOWNLOAD_DIR}" - -BUNDLE_ZIP="${BUNDLE_DIR}/${ARTIFACT_ID}-${VERSION}.zip" -(cd "${DOWNLOAD_DIR}" && zip -qr "${BUNDLE_ZIP}" .) - -echo "Snapshot deploy complete: ${SNAPSHOT_REPOSITORY_URL}/${GROUP_PATH}/${ARTIFACT_ID}/${VERSION}/" diff --git a/java/cuopt/ci/maven-publish/maven_snapshot_publish_steps.sh b/java/cuopt/ci/maven-publish/maven_snapshot_publish_steps.sh deleted file mode 100755 index 850bd84e74..0000000000 --- a/java/cuopt/ci/maven-publish/maven_snapshot_publish_steps.sh +++ /dev/null @@ -1,156 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Step functions used by maven_snapshot_publish_in_container.sh. - -# stage_snapshot_deploy_inputs INPUT_DIR DEPLOY_DIR -# Copy INPUT_DIR into DEPLOY_DIR, stripping any sidecars that mvn regenerates. -stage_snapshot_deploy_inputs() { - local input_dir=$1 deploy_dir=$2 - cp -a "${input_dir}/." "${deploy_dir}/" - find "${deploy_dir}" -type f \( \ - -name '*.asc' -o -name '*.md5' -o \ - -name '*.sha1' -o -name '*.sha256' -o -name '*.sha512' -o \ - -name 'maven-metadata.xml*' \ - \) -delete -} - -# write_maven_settings OUT_PATH -# Wire the ossrh server-id to MAVEN_DEPLOY_USERNAME / MAVEN_DEPLOY_TOKEN. -write_maven_settings() { - local out=$1 - cat > "${out}" <<'EOF' - - - - - ossrh - ${env.MAVEN_DEPLOY_USERNAME} - ${env.MAVEN_DEPLOY_TOKEN} - - - -EOF -} - -# deploy_snapshot_with_mvn ARTIFACT_DIR ARTIFACT_ID VERSION \ -# REPO_URL SETTINGS_FILE LOG_PATH -# Deploys every jar under ARTIFACT_DIR via mvn deploy-file. -# The main jar is -.jar. sources.jar and javadoc.jar -# use mvn's -Dsources / -Djavadoc. Any other jar is attached with a -# classifier taken from its filename. mvn output goes to LOG_PATH so the -# caller can enumerate Nexus-assigned URLs. -deploy_snapshot_with_mvn() { - local artifact_dir=$1 artifact_id=$2 version=$3 - local repo_url=$4 settings_file=$5 log_path=$6 - - local main_jar="${artifact_dir}/${artifact_id}-${version}.jar" - local pom_file="${artifact_dir}/${artifact_id}-${version}.pom" - local sources_jar="${artifact_dir}/${artifact_id}-${version}-sources.jar" - local javadoc_jar="${artifact_dir}/${artifact_id}-${version}-javadoc.jar" - - if [[ ! -f ${main_jar} ]]; then - fatal "missing primary jar: ${main_jar}" - fi - if [[ ! -f ${pom_file} ]]; then - fatal "missing POM: ${pom_file}" - fi - - # Collect classifier jars: every jar under artifact_dir except main / - # sources / javadoc, which mvn takes separately below. - local -a files=() types=() classifiers=() - local jar classifier - for jar in "${artifact_dir}"/*.jar; do - case ${jar} in - "${main_jar}"|"${sources_jar}"|"${javadoc_jar}") continue ;; - esac - classifier=$(basename "${jar}" .jar) - classifier=${classifier#"${artifact_id}-${version}-"} - files+=("${jar}") - types+=(jar) - classifiers+=("${classifier}") - done - - local -a mvn_args=( - -B - -Dmaven.wagon.http.retryHandler.count=3 - -DretryFailedDeploymentCount=3 - -s "${settings_file}" - org.apache.maven.plugins:maven-deploy-plugin:3.1.4:deploy-file - -Durl="${repo_url}" - -DrepositoryId=ossrh - -Dfile="${main_jar}" - -DpomFile="${pom_file}" - ) - if [[ -f ${sources_jar} ]]; then - mvn_args+=(-Dsources="${sources_jar}") - fi - if [[ -f ${javadoc_jar} ]]; then - mvn_args+=(-Djavadoc="${javadoc_jar}") - fi - if (( ${#files[@]} > 0 )); then - mvn_args+=( - -Dfiles="$(IFS=,; echo "${files[*]}")" - -Dtypes="$(IFS=,; echo "${types[*]}")" - -Dclassifiers="$(IFS=,; echo "${classifiers[*]}")" - ) - fi - - # tee so parse_uploaded_urls can enumerate Nexus URLs from LOG_PATH. - set +e - mvn "${mvn_args[@]}" 2>&1 | tee "${log_path}" - local rc=${PIPESTATUS[0]} - set -e - if (( rc != 0 )); then - fatal "mvn deploy-file failed (exit ${rc})" - fi -} - -# parse_uploaded_urls LOG_PATH REPO_URL ARTIFACT_BASE_PATH -# Print every URL Nexus acknowledged via 'Uploaded to ossrh: ' whose path -# starts with //. Deduplicated. -parse_uploaded_urls() { - local log_path=$1 repo_url=$2 artifact_base_path=$3 - local prefix="${repo_url}/${artifact_base_path}/" - awk -v p="${prefix}" ' - /Uploaded to ossrh:/ { - for (i = 1; i <= NF; i++) if (index($i, p) == 1) print $i - } - ' "${log_path}" | sort -u -} - -# download_deployed_tree LOG_PATH REPO_URL ARTIFACT_BASE_PATH BUNDLE_DIR -download_deployed_tree() { - local log_path=$1 repo_url=$2 artifact_base_path=$3 bundle_dir=$4 - local -a urls - mapfile -t urls < <(parse_uploaded_urls "${log_path}" "${repo_url}" "${artifact_base_path}") - if (( ${#urls[@]} == 0 )); then - fatal "mvn deploy log has no 'Uploaded to ossrh:' URLs under ${repo_url}/${artifact_base_path}/" - fi - - echo "Downloading deployed tree (${#urls[@]} file(s))" - local url rel dest - for url in "${urls[@]}"; do - rel="${url#"${repo_url}/"}" - dest="${bundle_dir}/${rel}" - mkdir -p "$(dirname "${dest}")" - _download_with_retry "${url}" "${dest}" - done -} - -# _download_with_retry URL DEST -- covers the brief PUT->GET propagation lag. -_download_with_retry() { - local url=$1 dest=$2 - local attempt status="" - for attempt in 1 2 3 4 5; do - status=$(curl -sSL -o "${dest}" -w '%{http_code}' "${url}") \ - || status="transport-error" - if [[ ${status} == 2* ]]; then - return 0 - fi - echo " [attempt ${attempt}] ${url} -> ${status}, retrying" >&2 - sleep $((attempt * 2)) - done - fatal "failed to download ${url} after 5 attempts (last status: ${status})" -} diff --git a/java/cuopt/ci/maven-publish/maven_utils.sh b/java/cuopt/ci/maven-publish/maven_utils.sh deleted file mode 100755 index 19fe2a2cee..0000000000 --- a/java/cuopt/ci/maven-publish/maven_utils.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Shared helpers for the ci/maven-publish/ pipeline scripts (host and -# in-container). Sourced, not executed: -# . "${SCRIPT_DIR}/maven_utils.sh" - -fatal() { - echo "Error: $*" >&2 - exit 1 -} - -require_cmds() { - local cmd - for cmd in "$@"; do - if ! command -v "${cmd}" >/dev/null; then - fatal "'${cmd}' not on PATH" - fi - done -} - -require_release_version() { - local version=$1 - if [[ ${version} == *-SNAPSHOT ]]; then - fatal "release-shaped version required, got '${version}'" - fi -} - -require_snapshot_version() { - local version=$1 - if [[ ${version} != *-SNAPSHOT ]]; then - fatal "snapshot version (ending in '-SNAPSHOT') required, got '${version}'" - fi -} - -require_maven_coordinates() { - local group_id=$1 artifact_id=$2 version=$3 - local coordinate_pattern='^[A-Za-z0-9][A-Za-z0-9_.+-]*$' - if [[ ! ${group_id} =~ ${coordinate_pattern} ]]; then - fatal "invalid Maven groupId '${group_id}'" - fi - if [[ ! ${artifact_id} =~ ${coordinate_pattern} ]]; then - fatal "invalid Maven artifactId '${artifact_id}'" - fi - if [[ ! ${version} =~ ${coordinate_pattern} ]]; then - fatal "invalid Maven version '${version}'" - fi -} - -# Maven groupId (a.b.c) -> filesystem path (a/b/c). -maven_group_path() { - printf '%s\n' "$1" | tr '.' '/' -} diff --git a/java/cuopt/ci/maven-publish/prepare_maven_bundle.sh b/java/cuopt/ci/maven-publish/prepare_maven_bundle.sh deleted file mode 100755 index 944b739798..0000000000 --- a/java/cuopt/ci/maven-publish/prepare_maven_bundle.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Host wrapper that prepares a signed Maven repository tree in a container. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/argparse.sh" -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/maven_utils.sh" - -INPUT_DIR="" -OUTPUT_DIR="" -IMAGE="maven:3-eclipse-temurin-17" - -print_help() { - cat << EOF - -Usage: prepare_maven_bundle.sh --input --output - -Copies a Maven repository directory into a clean output tree and GPG-signs -every publishable file. The input tree is mounted read-only and is not changed. -Both release and -SNAPSHOT versions are accepted. The destination check -(Maven Central vs Sonatype snapshots) happens in the publish scripts. - -REQUIRED: - -i, --input Maven repository directory to sign. - -o, --output Empty directory for the signed Maven tree. - -ENVIRONMENT VARIABLES: - GPG_PRIVATE_KEY Armored GPG private key (required). - GPG_PASSPHRASE Passphrase for the GPG private key (required). - GITHUB_OUTPUT If set, receives GROUP_ID, ARTIFACT_ID, VERSION. - -EOF -} - -parse_args() { - while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - print_help - exit 0 - ;; - -i|--input) - require_value "$1" "${2:-}" - INPUT_DIR=$2 - shift 2 - ;; - -o|--output) - require_value "$1" "${2:-}" - OUTPUT_DIR=$2 - shift 2 - ;; - *) - echo "Error: Unknown argument $1" - print_help - exit 1 - ;; - esac - done -} - -parse_args "$@" - -require_arg --input "${INPUT_DIR}" -require_arg --output "${OUTPUT_DIR}" - -if [[ ! -d ${INPUT_DIR} ]]; then - fatal "--input '${INPUT_DIR}' does not exist or is not a directory" -fi - -: "${GPG_PRIVATE_KEY:?must be set}" -: "${GPG_PASSPHRASE:?must be set}" - -INPUT_DIR="$(cd "${INPUT_DIR}" && pwd)" -mkdir -p "${OUTPUT_DIR}" -OUTPUT_DIR="$(cd "${OUTPUT_DIR}" && pwd)" - -if [[ ${INPUT_DIR} == "${OUTPUT_DIR}" ]]; then - fatal "--input and --output must be different directories" -fi -if [[ -n $(find "${OUTPUT_DIR}" ! -path "${OUTPUT_DIR}" -print -quit) ]]; then - fatal "--output '${OUTPUT_DIR}' must be empty" -fi - -METADATA_SCRATCH="$(mktemp -d)" -trap 'rm -rf "${METADATA_SCRATCH}"' EXIT - -echo "Prepare signed Maven bundle" -echo " image: ${IMAGE}" -echo " input dir: ${INPUT_DIR}" -echo " output dir: ${OUTPUT_DIR}" - -DOCKER_ARGS=( - --rm - --volume "${INPUT_DIR}:/input:ro" - --volume "${OUTPUT_DIR}:/bundle" - --volume "${METADATA_SCRATCH}:/metadata" - --workdir /bundle - --env GPG_PRIVATE_KEY="${GPG_PRIVATE_KEY}" - --env GPG_PASSPHRASE="${GPG_PASSPHRASE}" - --env HOST_UID="$(id -u)" - --env HOST_GID="$(id -g)" - --volume "${SCRIPT_DIR}:/scripts:ro" -) - -docker run "${DOCKER_ARGS[@]}" "${IMAGE}" \ - bash /scripts/prepare_maven_bundle_in_container.sh - -METADATA_FILE="${METADATA_SCRATCH}/bundle_metadata.env" -if [[ ! -f ${METADATA_FILE} ]]; then - fatal "worker did not produce ${METADATA_FILE}" -fi - -GROUP_ID=$(sed -n 's/^GROUP_ID=//p' "${METADATA_FILE}") -ARTIFACT_ID=$(sed -n 's/^ARTIFACT_ID=//p' "${METADATA_FILE}") -VERSION=$(sed -n 's/^VERSION=//p' "${METADATA_FILE}") -require_maven_coordinates "${GROUP_ID}" "${ARTIFACT_ID}" "${VERSION}" - -{ - echo "GROUP_ID=${GROUP_ID}" - echo "ARTIFACT_ID=${ARTIFACT_ID}" - echo "VERSION=${VERSION}" -} | tee -a "${GITHUB_OUTPUT:-/dev/null}" - -echo "Signed bundle prepared for ${GROUP_ID}:${ARTIFACT_ID}:${VERSION}" diff --git a/java/cuopt/ci/maven-publish/prepare_maven_bundle_in_container.sh b/java/cuopt/ci/maven-publish/prepare_maven_bundle_in_container.sh deleted file mode 100755 index f499885911..0000000000 --- a/java/cuopt/ci/maven-publish/prepare_maven_bundle_in_container.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# In-container worker for prepare_maven_bundle.sh. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/maven_utils.sh" -# shellcheck disable=SC1091 -. "${SCRIPT_DIR}/prepare_maven_bundle_steps.sh" - -INPUT_DIR=/input -BUNDLE_DIR=/bundle -METADATA_DIR=/metadata - -: "${GPG_PRIVATE_KEY:?must be set}" -: "${GPG_PASSPHRASE:?must be set}" -: "${HOST_UID:?must be set}" -: "${HOST_GID:?must be set}" - -trap 'chown -R "${HOST_UID}:${HOST_GID}" "${BUNDLE_DIR}" "${METADATA_DIR}" 2>/dev/null || true' EXIT - -install_container_deps -cp -a "${INPUT_DIR}/." "${BUNDLE_DIR}/" -remove_generated_sidecars "${BUNDLE_DIR}" - -POM_FILE="" -discover_single_pom "${BUNDLE_DIR}" POM_FILE -ARTIFACT_DIR="$(dirname "${POM_FILE}")" - -GROUP_ID="" -ARTIFACT_ID="" -VERSION="" -read_pom_coordinates "${POM_FILE}" GROUP_ID ARTIFACT_ID VERSION -require_maven_coordinates "${GROUP_ID}" "${ARTIFACT_ID}" "${VERSION}" - -EXPECTED_ARTIFACT_DIR="${BUNDLE_DIR}/$(maven_group_path "${GROUP_ID}")/${ARTIFACT_ID}/${VERSION}" -if [[ ${ARTIFACT_DIR} != "${EXPECTED_ARTIFACT_DIR}" ]]; then - fatal "POM location ${ARTIFACT_DIR} does not match expected Maven repository layout ${EXPECTED_ARTIFACT_DIR}" -fi - -GPG_KEY_ID="" -import_gpg_signing_key GPG_KEY_ID -echo "GPG signing key: ${GPG_KEY_ID}" - -# Populated through a nameref in list_artifacts_to_sign. -# shellcheck disable=SC2034 -ARTIFACTS=() -echo "Signing artifacts under ${ARTIFACT_DIR}" -list_artifacts_to_sign "${ARTIFACT_DIR}" ARTIFACTS -sign_artifacts_in_place ARTIFACTS "${GPG_KEY_ID}" - -write_bundle_metadata "${METADATA_DIR}/bundle_metadata.env" \ - "${GROUP_ID}" "${ARTIFACT_ID}" "${VERSION}" - -echo "Signing complete for ${GROUP_ID}:${ARTIFACT_ID}:${VERSION}" diff --git a/java/cuopt/ci/maven-publish/prepare_maven_bundle_steps.sh b/java/cuopt/ci/maven-publish/prepare_maven_bundle_steps.sh deleted file mode 100755 index 130f4b1589..0000000000 --- a/java/cuopt/ci/maven-publish/prepare_maven_bundle_steps.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Step functions used by prepare_maven_bundle_in_container.sh. - -install_container_deps() { - if command -v gpg >/dev/null && command -v zip >/dev/null; then - return 0 - fi - export DEBIAN_FRONTEND=noninteractive - apt-get update -qq - apt-get install -qq -y --no-install-recommends gnupg zip -} - -remove_generated_sidecars() { - local root=$1 - find "${root}" -type f \( \ - -name '*.asc' -o -name '*.md5' -o -name '*.sha1' -o \ - -name '*.sha256' -o -name '*.sha512' \ - \) -delete -} - -discover_single_pom() { - local input_dir=$1 - local -n out_pom_var=$2 - local -a candidates - mapfile -t candidates < <(find "${input_dir}" -type f -name '*.pom' | sort) - # The single-candidate assignment is observed through the caller's nameref. - # shellcheck disable=SC2034 - case ${#candidates[@]} in - 0) fatal "no *.pom found anywhere under ${input_dir}" ;; - 1) out_pom_var=${candidates[0]} ;; - *) - { - echo "Error: expected exactly one POM under ${input_dir}, found:" - printf ' %s\n' "${candidates[@]}" - } >&2 - exit 1 - ;; - esac -} - -read_pom_coordinates() { - local pom_file=$1 - local -n out_group_var=$2 - local -n out_artifact_var=$3 - local -n out_version_var=$4 - out_group_var=$(mvn -q -B -f "${pom_file}" help:evaluate -Dexpression=project.groupId -DforceStdout) - out_artifact_var=$(mvn -q -B -f "${pom_file}" help:evaluate -Dexpression=project.artifactId -DforceStdout) - out_version_var=$(mvn -q -B -f "${pom_file}" help:evaluate -Dexpression=project.version -DforceStdout) - if [[ -z ${out_group_var} || -z ${out_artifact_var} || -z ${out_version_var} ]]; then - fatal "failed to read groupId/artifactId/version from ${pom_file}" - fi -} - -import_gpg_signing_key() { - local -n out_key_id=$1 - export GNUPGHOME - GNUPGHOME="$(mktemp -d)" - chmod 700 "${GNUPGHOME}" - echo "${GPG_PRIVATE_KEY}" | gpg --batch --yes --pinentry-mode loopback \ - --passphrase "${GPG_PASSPHRASE}" --import - out_key_id=$(gpg --list-secret-keys --keyid-format=long --with-colons \ - | awk -F: '$1=="sec" { print $5; exit }') - if [[ -z ${out_key_id} ]]; then - fatal "no GPG secret key was imported" - fi -} - -list_artifacts_to_sign() { - local artifact_dir=$1 - local -n out_files=$2 - mapfile -t out_files < <(find "${artifact_dir}" -maxdepth 1 -type f \ - ! -name '*.asc' ! -name '*.md5' ! -name '*.sha1' ! -name '*.sha256' \ - ! -name '*.sha512' | sort) - if (( ${#out_files[@]} == 0 )); then - fatal "no artifacts found under ${artifact_dir} to sign" - fi -} - -sign_artifacts_in_place() { - local -n files_ref=$1 - local key_id=$2 - local file - for file in "${files_ref[@]}"; do - gpg --batch --yes --pinentry-mode loopback \ - --passphrase "${GPG_PASSPHRASE}" \ - --local-user "${key_id}" \ - --armor --detach-sign \ - --output "${file}.asc" \ - "${file}" - done -} - -write_bundle_metadata() { - local out_path=$1 group_id=$2 artifact_id=$3 version=$4 - { - echo "GROUP_ID=${group_id}" - echo "ARTIFACT_ID=${artifact_id}" - echo "VERSION=${version}" - } > "${out_path}" -}