blob: 12160e9808d6cb547734e00cfda914515baf095f [file]
################################################################################
# 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
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