| ################################################################################ | |
| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| ################################################################################ | |
| name: Release / Java packages | |
| on: | |
| workflow_call: | |
| workflow_dispatch: | |
| concurrency: | |
| group: release-java-${{ github.ref }} | |
| cancel-in-progress: false | |
| permissions: | |
| actions: read | |
| contents: read | |
| jobs: | |
| package: | |
| name: Package ${{ matrix.lane }} | |
| if: >- | |
| github.repository == 'apache/paimon' && | |
| startsWith(github.ref, 'refs/tags/') && | |
| contains(github.ref_name, '-rc') | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - lane: jdk8 | |
| java_version: 8 | |
| - lane: jdk11 | |
| java_version: 11 | |
| - lane: jdk17 | |
| java_version: 17 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up JDK ${{ matrix.java_version }} | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: ${{ matrix.java_version }} | |
| distribution: temurin | |
| cache: maven | |
| - name: Set up Maven | |
| uses: stCarolas/setup-maven@12eb41b233df95d49b0c11fc1b5bc8312e5d4ce0 # v5.1 | |
| with: | |
| maven-version: 3.8.8 | |
| - name: Validate release tag and version | |
| id: release | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| release_version="$( | |
| mvn -q -DforceStdout help:evaluate \ | |
| -Dexpression=project.version | |
| )" | |
| rc_number="${GITHUB_REF_NAME##*-rc}" | |
| expected_tag="release-${release_version}-rc${rc_number}" | |
| if [[ ! "${rc_number}" =~ ^[0-9]+$ ]]; then | |
| echo "RC number must be numeric: ${rc_number}" >&2 | |
| exit 1 | |
| fi | |
| if [[ "${GITHUB_REF_NAME}" != "${expected_tag}" ]]; then | |
| echo "Tag ${GITHUB_REF_NAME} does not match Maven version ${release_version}" >&2 | |
| exit 1 | |
| fi | |
| if [[ "${release_version}" == *-SNAPSHOT ]]; then | |
| echo "Release version must not contain -SNAPSHOT" >&2 | |
| exit 1 | |
| fi | |
| echo "version=${release_version}" >> "${GITHUB_OUTPUT}" | |
| echo "rc_number=${rc_number}" >> "${GITHUB_OUTPUT}" | |
| java -version | |
| mvn -version | |
| - name: Reject SNAPSHOT project versions and dependencies | |
| shell: bash | |
| env: | |
| LANE: ${{ matrix.lane }} | |
| run: | | |
| set -euo pipefail | |
| enforcer_goal="org.apache.maven.plugins:maven-enforcer-plugin:3.6.3:enforce" | |
| enforcer_rules="requireReleaseVersion,requireReleaseDeps" | |
| case "${LANE}" in | |
| jdk8) | |
| mvn -ntp -B "${enforcer_goal}" \ | |
| -Denforcer.rules="${enforcer_rules}" \ | |
| -Papache-release,docs-and-source,spark3,flink1 | |
| ;; | |
| jdk11) | |
| mvn -ntp -B "${enforcer_goal}" \ | |
| -Denforcer.rules="${enforcer_rules}" \ | |
| -Papache-release,docs-and-source,flink2 \ | |
| -pl org.apache.paimon:paimon-flink-2.0,org.apache.paimon:paimon-flink-2.1,org.apache.paimon:paimon-flink-2.2,org.apache.paimon:paimon-flink2-common,org.apache.paimon:paimon-iceberg \ | |
| -am | |
| ;; | |
| jdk17) | |
| mvn -ntp -B "${enforcer_goal}" \ | |
| -Denforcer.rules="${enforcer_rules}" \ | |
| -Papache-release,docs-and-source,spark4 \ | |
| -pl org.apache.paimon:paimon-spark-common_2.13,org.apache.paimon:paimon-spark4-common_2.13,org.apache.paimon:paimon-spark-4.0_2.13,org.apache.paimon:paimon-spark-4.1_2.13 \ | |
| -am | |
| ;; | |
| *) | |
| echo "Unknown Java release lane: ${LANE}" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| - name: Package Java release artifacts | |
| shell: bash | |
| env: | |
| LANE: ${{ matrix.lane }} | |
| MAVEN_OPTS: -Xmx4096m | |
| RELEASE_VERSION: ${{ steps.release.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p release-java | |
| repository="${RUNNER_TEMP}/paimon-${LANE}-repository" | |
| alt_repository="local::default::file://${repository}" | |
| effective_pom="${RUNNER_TEMP}/paimon-${LANE}-effective-pom.xml" | |
| expected_projects="release-java/${LANE}-expected-projects.txt" | |
| log="release-java/${LANE}.log" | |
| capture_expected_projects() { | |
| mvn -q -ntp -B help:effective-pom \ | |
| -Doutput="${effective_pom}" "$@" | |
| } | |
| case "${LANE}" in | |
| jdk8) | |
| capture_expected_projects \ | |
| -Papache-release,docs-and-source,spark3,flink1 | |
| mvn clean deploy -ntp -B \ | |
| -Papache-release,docs-and-source,spark3,flink1 \ | |
| -DskipTests -Dgpg.skip=true -Dstyle.color=never \ | |
| -DdeployAtEnd=true \ | |
| -DaltDeploymentRepository="${alt_repository}" \ | |
| 2>&1 | tee "${log}" | |
| ;; | |
| jdk11) | |
| capture_expected_projects \ | |
| -Papache-release,docs-and-source,flink2 \ | |
| -pl org.apache.paimon:paimon-flink-2.0,org.apache.paimon:paimon-flink-2.1,org.apache.paimon:paimon-flink-2.2,org.apache.paimon:paimon-flink2-common,org.apache.paimon:paimon-iceberg | |
| # This install only supplies reactor dependencies for the deploy | |
| # below. Their Javadocs are built by the owning release lane. | |
| mvn clean install -ntp -B \ | |
| -Pdocs-and-source,flink2 \ | |
| -DskipTests -Dmaven.javadoc.skip=true \ | |
| -Dstyle.color=never \ | |
| -pl org.apache.paimon:paimon-flink-2.0,org.apache.paimon:paimon-flink-2.1,org.apache.paimon:paimon-flink-2.2,org.apache.paimon:paimon-iceberg \ | |
| -am \ | |
| 2>&1 | tee "${log}" | |
| mvn deploy -ntp -B \ | |
| -Papache-release,docs-and-source,flink2 \ | |
| -DskipTests -Dgpg.skip=true -Dstyle.color=never \ | |
| -DdeployAtEnd=true \ | |
| -DaltDeploymentRepository="${alt_repository}" \ | |
| -pl org.apache.paimon:paimon-flink-2.0,org.apache.paimon:paimon-flink-2.1,org.apache.paimon:paimon-flink-2.2,org.apache.paimon:paimon-flink2-common,org.apache.paimon:paimon-iceberg \ | |
| 2>&1 | tee -a "${log}" | |
| ;; | |
| jdk17) | |
| capture_expected_projects \ | |
| -Papache-release,docs-and-source,spark4 \ | |
| -pl org.apache.paimon:paimon-spark-common_2.13,org.apache.paimon:paimon-spark4-common_2.13,org.apache.paimon:paimon-spark-4.0_2.13,org.apache.paimon:paimon-spark-4.1_2.13 | |
| # This install only supplies reactor dependencies for the deploy | |
| # below. Their Javadocs are built by the owning release lane. | |
| mvn clean install -ntp -B \ | |
| -Pdocs-and-source,spark4 \ | |
| -DskipTests -Dmaven.javadoc.skip=true \ | |
| -Dstyle.color=never \ | |
| -pl paimon-spark/paimon-spark-4.0,paimon-spark/paimon-spark-4.1 \ | |
| -am \ | |
| 2>&1 | tee "${log}" | |
| mvn deploy -ntp -B \ | |
| -Papache-release,docs-and-source,spark4 \ | |
| -DskipTests -Dgpg.skip=true -Dstyle.color=never \ | |
| -DdeployAtEnd=true \ | |
| -DaltDeploymentRepository="${alt_repository}" \ | |
| -pl org.apache.paimon:paimon-spark-common_2.13,org.apache.paimon:paimon-spark4-common_2.13,org.apache.paimon:paimon-spark-4.0_2.13,org.apache.paimon:paimon-spark-4.1_2.13 \ | |
| 2>&1 | tee -a "${log}" | |
| ;; | |
| *) | |
| echo "Unknown Java release lane: ${LANE}" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| python3 - \ | |
| "${effective_pom}" "${expected_projects}" \ | |
| "${RELEASE_VERSION}" <<'PY' | |
| import sys | |
| import xml.etree.ElementTree as ET | |
| effective_pom, output, release_version = sys.argv[1:] | |
| root = ET.parse(effective_pom).getroot() | |
| projects = list(root) if root.tag == "projects" else [root] | |
| namespace = "{http://maven.apache.org/POM/4.0.0}" | |
| expected = [] | |
| for project in projects: | |
| def required(name): | |
| element = project.find(namespace + name) | |
| if element is None or not element.text: | |
| raise ValueError("Effective POM is missing " + name) | |
| return element.text.strip() | |
| deploy_skipped = False | |
| plugins = project.findall( | |
| "./%sbuild/%splugins/%splugin" | |
| % (namespace, namespace, namespace) | |
| ) | |
| for plugin in plugins: | |
| artifact = plugin.find(namespace + "artifactId") | |
| if artifact is None or artifact.text != "maven-deploy-plugin": | |
| continue | |
| skip = plugin.find( | |
| "./%sconfiguration/%sskip" % (namespace, namespace) | |
| ) | |
| if ( | |
| skip is not None | |
| and skip.text | |
| and skip.text.strip().lower() == "true" | |
| ): | |
| deploy_skipped = True | |
| break | |
| if deploy_skipped: | |
| continue | |
| group_id = required("groupId") | |
| artifact_id = required("artifactId") | |
| version = required("version") | |
| packaging_element = project.find(namespace + "packaging") | |
| packaging = ( | |
| packaging_element.text.strip() | |
| if packaging_element is not None and packaging_element.text | |
| else "jar" | |
| ) | |
| if group_id != "org.apache.paimon": | |
| raise ValueError("Unexpected release groupId: " + group_id) | |
| if version != release_version: | |
| raise ValueError( | |
| "Unexpected project version for %s: %s" | |
| % (artifact_id, version) | |
| ) | |
| if packaging not in ("jar", "pom"): | |
| raise ValueError( | |
| "Unsupported release packaging for %s: %s" | |
| % (artifact_id, packaging) | |
| ) | |
| expected.append((group_id, artifact_id, version, packaging)) | |
| if not expected: | |
| raise ValueError("Effective POM contains no release projects") | |
| if len(expected) != len(set(expected)): | |
| raise ValueError("Effective POM contains duplicate projects") | |
| with open(output, "w", encoding="utf-8") as result: | |
| for project in sorted(expected): | |
| result.write("\t".join(project) + "\n") | |
| PY | |
| while IFS=$'\t' read -r group_id artifact_id version packaging; do | |
| artifact_directory="${repository}/${group_id//.//}/${artifact_id}/${version}" | |
| artifact_base="${artifact_directory}/${artifact_id}-${version}" | |
| if [[ ! -f "${artifact_base}.pom" ]]; then | |
| echo "Missing release POM: ${artifact_base}.pom" >&2 | |
| exit 1 | |
| fi | |
| if [[ "${packaging}" == "jar" ]]; then | |
| for classifier in '' '-sources'; do | |
| if [[ ! -f "${artifact_base}${classifier}.jar" ]]; then | |
| echo "Missing release JAR: ${artifact_base}${classifier}.jar" >&2 | |
| exit 1 | |
| fi | |
| done | |
| if [[ ! -f "${artifact_base}-javadoc.jar" ]]; then | |
| echo "No Javadoc JAR was produced for ${group_id}:${artifact_id}" | |
| fi | |
| fi | |
| done < "${expected_projects}" | |
| unexpected_artifact="$( | |
| find "${repository}" -type f \ | |
| \( -name '*.jar' -o -name '*.pom' \) \ | |
| ! -path "*/${RELEASE_VERSION}/*" -print -quit | |
| )" | |
| if [[ -n "${unexpected_artifact}" ]]; then | |
| echo "Artifact is outside the release version: ${unexpected_artifact}" >&2 | |
| exit 1 | |
| fi | |
| files="${GITHUB_WORKSPACE}/release-java/${LANE}-files.txt" | |
| ( | |
| cd "${repository}" | |
| find . -type f | LC_ALL=C sort > "${files}" | |
| ) | |
| if [[ ! -s "${files}" ]] || | |
| ! grep -q '\.pom$' "${files}" || | |
| ! grep -q '\.jar$' "${files}"; then | |
| echo "Incomplete Maven repository image for ${LANE}" >&2 | |
| exit 1 | |
| fi | |
| if grep -q '\.asc$' "${files}"; then | |
| echo "Unsigned repository image unexpectedly contains signatures" >&2 | |
| exit 1 | |
| fi | |
| ( | |
| cd "${repository}" | |
| while IFS= read -r artifact; do | |
| sha512sum "${artifact}" | |
| done < "${files}" | |
| ) > "release-java/${LANE}-sha512.txt" | |
| tar -czf "release-java/${LANE}-repository.tar.gz" \ | |
| -C "${repository}" . | |
| ( | |
| cd release-java | |
| sha512sum "${LANE}-repository.tar.gz" \ | |
| > "${LANE}-repository.tar.gz.sha512" | |
| ) | |
| { | |
| echo "lane=${LANE}" | |
| echo "jdk=${{ matrix.java_version }}" | |
| echo "version=${{ steps.release.outputs.version }}" | |
| echo "rc=${{ steps.release.outputs.rc_number }}" | |
| echo "commit=${GITHUB_SHA}" | |
| echo "signed=false" | |
| echo "staged=false" | |
| echo "artifact_source=maven-file-repository" | |
| } > "release-java/${LANE}-manifest.txt" | |
| - name: Upload Java repository lane | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: java-repository-${{ matrix.lane }} | |
| path: release-java/ | |
| if-no-files-found: error | |
| retention-days: 1 | |
| combine: | |
| name: Combine Java repository | |
| needs: package | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/download-artifact@v6 | |
| with: | |
| pattern: java-repository-* | |
| path: release-java/lanes | |
| merge-multiple: true | |
| - name: Merge repository lanes | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p release-java/final/evidence | |
| combined="${RUNNER_TEMP}/paimon-maven-repository" | |
| mkdir -p "${combined}" | |
| for lane in jdk8 jdk11 jdk17; do | |
| archive="release-java/lanes/${lane}-repository.tar.gz" | |
| checksum="release-java/lanes/${lane}-repository.tar.gz.sha512" | |
| test -f "${archive}" | |
| test -f "${checksum}" | |
| ( | |
| cd release-java/lanes | |
| sha512sum -c "${lane}-repository.tar.gz.sha512" | |
| ) | |
| candidate="${RUNNER_TEMP}/paimon-${lane}-candidate" | |
| mkdir -p "${candidate}" | |
| tar -xzf "${archive}" -C "${candidate}" | |
| while IFS= read -r source; do | |
| relative="${source#"${candidate}/"}" | |
| target="${combined}/${relative}" | |
| if [[ -f "${target}" ]]; then | |
| if ! cmp -s "${source}" "${target}"; then | |
| echo "Conflicting repository file from ${lane}: ${relative}" >&2 | |
| exit 1 | |
| fi | |
| else | |
| mkdir -p "$(dirname "${target}")" | |
| cp -p "${source}" "${target}" | |
| fi | |
| done < <(find "${candidate}" -type f | LC_ALL=C sort) | |
| cp "release-java/lanes/${lane}-manifest.txt" \ | |
| "release-java/final/evidence/" | |
| cp "release-java/lanes/${lane}.log" \ | |
| "release-java/final/evidence/" | |
| cp "release-java/lanes/${lane}-expected-projects.txt" \ | |
| "release-java/final/evidence/" | |
| done | |
| version="$( | |
| sed -n 's/^version=//p' \ | |
| release-java/lanes/jdk8-manifest.txt | |
| )" | |
| rc_number="$( | |
| sed -n 's/^rc=//p' \ | |
| release-java/lanes/jdk8-manifest.txt | |
| )" | |
| if [[ -z "${version}" ]]; then | |
| echo "Missing release version from JDK 8 manifest" >&2 | |
| exit 1 | |
| fi | |
| if [[ -z "${rc_number}" ]]; then | |
| echo "Missing RC number from JDK 8 manifest" >&2 | |
| exit 1 | |
| fi | |
| for lane in jdk8 jdk11 jdk17; do | |
| expected_jdk="${lane#jdk}" | |
| grep -qx "lane=${lane}" \ | |
| "release-java/lanes/${lane}-manifest.txt" | |
| grep -qx "jdk=${expected_jdk}" \ | |
| "release-java/lanes/${lane}-manifest.txt" | |
| grep -qx "version=${version}" \ | |
| "release-java/lanes/${lane}-manifest.txt" | |
| grep -qx "rc=${rc_number}" \ | |
| "release-java/lanes/${lane}-manifest.txt" | |
| grep -qx "commit=${GITHUB_SHA}" \ | |
| "release-java/lanes/${lane}-manifest.txt" | |
| grep -qx 'signed=false' \ | |
| "release-java/lanes/${lane}-manifest.txt" | |
| grep -qx 'staged=false' \ | |
| "release-java/lanes/${lane}-manifest.txt" | |
| done | |
| files="${GITHUB_WORKSPACE}/release-java/final/paimon-maven-repository-files.txt" | |
| ( | |
| cd "${combined}" | |
| find . -type f | LC_ALL=C sort > "${files}" | |
| ) | |
| if [[ ! -s "${files}" ]] || | |
| ! grep -q '\.pom$' "${files}" || | |
| ! grep -q '\.jar$' "${files}"; then | |
| echo "Combined Maven repository image is incomplete" >&2 | |
| exit 1 | |
| fi | |
| if grep -q '\.asc$' "${files}"; then | |
| echo "Combined repository image is unexpectedly signed" >&2 | |
| exit 1 | |
| fi | |
| ( | |
| cd "${combined}" | |
| while IFS= read -r artifact; do | |
| sha512sum "${artifact}" | |
| done < "${files}" | |
| ) > release-java/final/paimon-maven-repository-sha512.txt | |
| archive="paimon-${version}-maven-repository.tar.gz" | |
| tar -czf "release-java/final/${archive}" \ | |
| -C "${combined}" . | |
| ( | |
| cd release-java/final | |
| sha512sum "${archive}" > "${archive}.sha512" | |
| ) | |
| { | |
| echo "version=${version}" | |
| echo "rc=${rc_number}" | |
| echo "tag=${GITHUB_REF_NAME}" | |
| echo "commit=${GITHUB_SHA}" | |
| echo "lanes=jdk8,jdk11,jdk17" | |
| echo "signed=false" | |
| echo "staged=false" | |
| echo "artifact_source=combined-maven-file-repository" | |
| } > release-java/final/paimon-maven-repository-manifest.txt | |
| - name: Upload combined Java repository | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: java-release-repository | |
| path: release-java/final/ | |
| if-no-files-found: error |