-
Notifications
You must be signed in to change notification settings - Fork 0
145 lines (133 loc) · 7.48 KB
/
Copy pathrelease.yml
File metadata and controls
145 lines (133 loc) · 7.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
name: Release
# Triggered by pushing a semver tag: v1.2.3
#
# Steps:
# 1. Validate the tag matches the POM version and that the build timestamp was
# bumped for this release (prevents accidental mismatches).
# 2. Stage all artifacts to a local directory via mvn deploy.
# 3. Attest build provenance for the staged jars, before anything is published.
# 4. JReleaser signs the artifacts (GPG), publishes to Maven Central via the
# Sonatype Central Portal, and creates a GitHub Release with changelog.
#
# Release workflow for maintainers:
# mvn versions:set -DnewVersion=1.0.0 -DgenerateBackupPoms=false
# mvn versions:set-property -Dproperty=project.build.outputTimestamp \
# -DnewVersion="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -DgenerateBackupPoms=false
# git commit -am "Release 1.0.0" && git tag v1.0.0 && git push --tags
# mvn versions:set -DnewVersion=1.1.0-SNAPSHOT -DgenerateBackupPoms=false
# git commit -am "Prepare 1.1.0-SNAPSHOT" && git push
#
# Required repository secrets:
# GPG_SECRET_KEY — armored GPG private key (-----BEGIN PGP PRIVATE KEY BLOCK-----)
# GPG_PUBLIC_KEY — armored GPG public key (-----BEGIN PGP PUBLIC KEY BLOCK-----)
# GPG_PASSPHRASE — passphrase protecting the GPG key
# MAVENCENTRAL_USERNAME — Sonatype Central Portal username (or token username)
# MAVENCENTRAL_PASSWORD — Sonatype Central Portal password (or token password)
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
permissions:
contents: read
jobs:
release:
name: Release (Java 21)
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: write # create the GitHub release and upload assets
id-token: write # mint the short-lived OIDC token the attestation is signed with
attestations: write # record the provenance attestation against this repository
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0 # full history required for JReleaser changelog
# The one job holding a contents:write token, and the only one running
# `mvn deploy` — so the one where a credential left in .git/config
# would expose the most to the most code. Nothing here needs it: the
# staging deploy writes to a local directory, the validation steps
# read git locally, and JReleaser authenticates with
# JRELEASER_GITHUB_TOKEN rather than through git.
persist-credentials: false
- name: Set up JDK 21
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: '21'
cache: maven
- name: Validate tag matches POM version
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
POM_VERSION=$(./mvnw -q --no-transfer-progress help:evaluate -Dexpression=project.version -q -DforceStdout)
if [ "$POM_VERSION" != "$TAG_VERSION" ]; then
echo "::error::Tag $GITHUB_REF_NAME expects version $TAG_VERSION but POM has $POM_VERSION"
exit 1
fi
echo "RELEASE_VERSION=$TAG_VERSION" >> "$GITHUB_ENV"
echo "Releasing version $POM_VERSION"
# project.build.outputTimestamp is what makes the published jars
# reproducible, and like the version it is bumped by hand — so, like the
# version, it is verified here rather than trusted. A release that reuses
# the previous release's timestamp dates its artifacts to the wrong
# release; one in the future dates them to a release that has not happened.
- name: Validate the build timestamp was bumped for this release
run: |
TIMESTAMP=$(./mvnw -q --no-transfer-progress help:evaluate -Dexpression=project.build.outputTimestamp -DforceStdout)
if ! printf '%s' "$TIMESTAMP" | grep -Eq '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$'; then
echo "::error::project.build.outputTimestamp is '$TIMESTAMP'; expected an ISO-8601 UTC instant such as 2026-08-14T09:30:00Z"
exit 1
fi
if [ "$(date -u -d "$TIMESTAMP" +%s)" -gt "$(date -u +%s)" ]; then
echo "::error::project.build.outputTimestamp ($TIMESTAMP) is in the future"
exit 1
fi
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${GITHUB_REF_NAME}^" 2>/dev/null || true)
if [ -n "$PREVIOUS_TAG" ]; then
PREVIOUS_TIMESTAMP=$(git show "$PREVIOUS_TAG:pom.xml" |
sed -n 's:.*<project\.build\.outputTimestamp>\(.*\)</project\.build\.outputTimestamp>.*:\1:p')
if [ -n "$PREVIOUS_TIMESTAMP" ] && [ "$PREVIOUS_TIMESTAMP" = "$TIMESTAMP" ]; then
echo "::error::project.build.outputTimestamp is still $TIMESTAMP, unchanged since $PREVIOUS_TAG. Bump it, amend the release commit and re-tag: ./mvnw versions:set-property -Dproperty=project.build.outputTimestamp -DnewVersion=\"\$(date -u +%Y-%m-%dT%H:%M:%SZ)\" -DgenerateBackupPoms=false"
exit 1
fi
fi
echo "Build timestamp $TIMESTAMP (previous release: ${PREVIOUS_TAG:-none} ${PREVIOUS_TIMESTAMP:-n/a})"
# jacoco.skip because the coverage gate has no business on the publishing
# path: CI already enforces it on every push and pull request, and here it
# only passes because a fresh checkout has no coverage data for it to read.
# That makes its verdict an accident of whether target/ happens to be
# empty — it fails outright anywhere it is not, such as a maintainer
# running these same steps locally.
- name: Stage artifacts to local directory
run: ./mvnw -B --no-transfer-progress -Dmaven.test.skip=true -Djacoco.skip=true deploy -DaltDeploymentRepository="local::file://${GITHUB_WORKSPACE}/target/staging-deploy"
# Deliberately before JReleaser publishes rather than after. An attestation
# that fails here costs a re-run; one that fails after the upload leaves a
# version on Maven Central that can never be attested and never withdrawn.
#
# The GPG signature says the release came from whoever holds the key. This
# says which workflow, at which commit, built these exact bytes — which is
# what lets a consumer verify a jar against this repository rather than
# against a keyholder.
- name: Attest build provenance for the staged artifacts
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: ${{ github.workspace }}/target/staging-deploy/**/*.jar
- name: Run JReleaser
uses: jreleaser/release-action@90ac653bb9c79d11179e65d81499f3f34527dcd5 # 2.5.0
with:
arguments: full-release
env:
JRELEASER_PROJECT_VERSION: ${{ env.RELEASE_VERSION }}
JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JRELEASER_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }}
JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME: ${{ secrets.MAVENCENTRAL_USERNAME }}
JRELEASER_MAVENCENTRAL_SONATYPE_PASSWORD: ${{ secrets.MAVENCENTRAL_PASSWORD }}
- name: Upload JReleaser output
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: jreleaser-release-output
path: |
out/jreleaser/trace.log
out/jreleaser/output.properties