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
80 changes: 59 additions & 21 deletions .github/workflows/maven-publish.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Publishes a bundle of release-candidate Maven artifacts (jars, POMs,
# sources, javadoc) to Maven Central via the Sonatype Central Publisher
# Portal. Every file is GPG-signed before the repository is uploaded through
# Sonatype's OSSRH staging compatibility API for validation and human-gated
# publication.
#
# TODO: add nightly -> Sonatype snapshots
# 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).
name: Maven publish

on:
workflow_call:
inputs:
publication-type:
description: |
Only "rc" (release candidate) is currently supported. Nightly is a
future addition. The input is kept single-valued so callers do not
have to change their `with:` block when nightly is introduced.
required: true
type: string
artifact-name:
description: |
Name of the GitHub Actions artifact (uploaded by the caller job via
Expand All @@ -32,7 +25,8 @@ on:
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.
drop the bundle. Only applies to the Maven Central path (release
builds). Ignored on the snapshot path.
required: false
type: boolean
default: false
Expand Down Expand Up @@ -63,7 +57,7 @@ permissions:

jobs:
maven-publish:
name: maven publish (${{ inputs.publication-type }})
name: maven publish
runs-on: linux-amd64-cpu4
steps:
- name: Self-checkout shared-workflows
Expand All @@ -74,6 +68,25 @@ jobs:
path: shared-workflows
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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand All @@ -85,15 +98,14 @@ jobs:
run: |
shared-workflows/ci/maven-publish/prepare_maven_bundle.sh \
--input maven-repo \
--output signed-maven-repo \
--publication-type "${PUBLICATION_TYPE}"
--output signed-maven-repo
env:
PUBLICATION_TYPE: ${{ inputs.publication-type }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}

- name: Upload to Maven Central
id: publish
id: publish-central
if: ${{ steps.destination.outputs.is_release == 'true' }}
run: |
shared-workflows/ci/maven-publish/maven_central_publish.sh \
--input signed-maven-repo \
Expand All @@ -111,11 +123,37 @@ jobs:
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: |
shared-workflows/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() }}
if: ${{ !cancelled() && steps.destination.outputs.is_release == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
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
148 changes: 148 additions & 0 deletions ci/maven-publish/maven_snapshot_publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/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 <path> --group-id <g> \\
--artifact-id <a> --version <v> \\
--output-bundle <path>

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. 26.12.0-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}"
65 changes: 65 additions & 0 deletions ci/maven-publish/maven_snapshot_publish_in_container.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/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}/"
Loading