Skip to content

Commit 7453fa0

Browse files
ci: publish releases to the Stream Maven repository
Gradle stages a signed tree under build/staged-repo when streamPublishTargets includes streamRepo; a separate upload job pushes it with the build-conventions action. The GitHub release is created only after the upload, with the Stream repository card. Central stays available by changing PUBLISH_TARGETS.
1 parent bbaa0db commit 7453fa0

3 files changed

Lines changed: 179 additions & 19 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 142 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,52 +6,177 @@ on:
66
branches:
77
- main
88

9+
permissions:
10+
contents: read
11+
pull-requests: read
12+
13+
env:
14+
# Where releases go: streamRepo, central, or streamRepo,central. Central is a manual
15+
# escape hatch for a release that has to go out when the Stream repository cannot take it.
16+
PUBLISH_TARGETS: streamRepo
17+
918
jobs:
1019
Release:
1120
name: 🚀 Release
1221
if: github.event.pull_request.merged && startsWith(github.head_ref, 'release-')
1322
runs-on: ubuntu-latest
23+
outputs:
24+
version: ${{ steps.version.outputs.version }}
25+
stream_repo: ${{ steps.version.outputs.stream_repo }}
1426
steps:
15-
- uses: actions/checkout@v3
27+
- uses: actions/checkout@v4
1628
with:
1729
fetch-depth: 0
18-
- uses: actions/setup-java@v3
30+
- uses: actions/setup-java@v4
1931
with:
2032
distribution: 'temurin'
2133
java-version: '17'
22-
- uses: actions/github-script@v5
23-
with:
24-
script: |
25-
const get_change_log_diff = require('./scripts/get_changelog_diff.js')
26-
core.exportVariable('CHANGELOG', get_change_log_diff())
2734

28-
// Getting the release version from the PR source branch
29-
// Source branch looks like this: release-1.0.0
30-
const version = context.payload.pull_request.head.ref.split('-')[1]
31-
core.exportVariable('VERSION', version)
35+
# Getting the release version (the tag) from the PR source branch
36+
# Source branch looks like this: release-v1.0.0
37+
- name: Read the version
38+
id: version
39+
env:
40+
HEAD_REF: ${{ github.head_ref }}
41+
run: |
42+
echo "version=${HEAD_REF#release-}" >> "$GITHUB_OUTPUT"
43+
echo "stream_repo=${{ contains(env.PUBLISH_TARGETS, 'streamRepo') }}" >> "$GITHUB_OUTPUT"
44+
45+
- name: Write the signing keyring
46+
run: |
47+
sudo bash -c "echo '$GPG_KEY_CONTENTS' | base64 -d > '$SIGNING_SECRET_KEY_RING_FILE'"
48+
env:
49+
GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }}
50+
SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }}
3251

3352
- name: Build artifacts
3453
run: ./gradlew jar javadocJar sourcesJar
3554

55+
# An env var rather than -P, so a value with a space cannot split into a second argument.
56+
- name: Select publish targets
57+
run: echo "ORG_GRADLE_PROJECT_streamPublishTargets=$PUBLISH_TARGETS" >> "$GITHUB_ENV"
58+
59+
# Stages a signed Maven-2 tree under build/staged-repo for the upload job.
60+
- name: Stage for the Stream repository
61+
if: ${{ contains(env.PUBLISH_TARGETS, 'streamRepo') }}
62+
run: ./gradlew publishReleasePublicationToStreamRepoStagingRepository --max-workers 1
63+
env:
64+
STREAM_KEY: ${{ secrets.STREAM_KEY }}
65+
STREAM_SECRET: ${{ secrets.STREAM_SECRET }}
66+
STREAM_APP_ID: ${{ secrets.STREAM_APP_ID }}
67+
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
68+
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
69+
SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }}
70+
3671
- name: Publish to MavenCentral
37-
run: |
38-
sudo bash -c "echo '$GPG_KEY_CONTENTS' | base64 -d > '$SIGNING_SECRET_KEY_RING_FILE'"
39-
./gradlew publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository
72+
if: ${{ contains(env.PUBLISH_TARGETS, 'central') }}
73+
run: ./gradlew publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository
4074
env:
4175
STREAM_KEY: ${{ secrets.STREAM_KEY }}
4276
STREAM_SECRET: ${{ secrets.STREAM_SECRET }}
4377
STREAM_APP_ID: ${{ secrets.STREAM_APP_ID }}
44-
GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }}
4578
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
4679
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
4780
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
4881
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
4982
SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }}
5083
SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
5184

85+
# Handed to a job that has never seen the signing key -- the reason Gradle stages to a
86+
# directory instead of writing to the bucket itself.
87+
- name: Hand the staged tree to the upload job
88+
if: ${{ contains(env.PUBLISH_TARGETS, 'streamRepo') }}
89+
uses: actions/upload-artifact@v4
90+
with:
91+
name: stream-repo-staged
92+
path: build/staged-repo
93+
retention-days: 1
94+
if-no-files-found: error
95+
96+
# Separate job: secrets are injected per job, so this one holds the R2 credential and never the
97+
# signing key, and the Release job holds the signing key and never the R2 credential.
98+
Upload:
99+
name: ☁️ Upload to the Stream repository
100+
needs: Release
101+
if: ${{ needs.Release.outputs.stream_repo == 'true' }}
102+
runs-on: ubuntu-latest
103+
steps:
104+
- name: Download the staged tree
105+
uses: actions/download-artifact@v4
106+
with:
107+
name: stream-repo-staged
108+
path: build/staged-repo
109+
110+
- name: Publish to the Stream repository
111+
uses: GetStream/stream-build-conventions-android/.github/actions/publish-to-stream-repo@v1.0.1
112+
with:
113+
staged-directory: build/staged-repo
114+
channel: releases
115+
endpoint: ${{ secrets.STREAM_REPO_ENDPOINT }}
116+
bucket: ${{ secrets.STREAM_REPO_BUCKET }}
117+
access-key-id: ${{ secrets.STREAM_REPO_ACCESS_KEY_ID }}
118+
secret-access-key: ${{ secrets.STREAM_REPO_SECRET_ACCESS_KEY }}
119+
120+
# The tag and the GitHub release only once the artifact is actually resolvable: after the
121+
# upload when the Stream repository is a target, straight after publishing when it is not.
122+
GitHub-Release:
123+
name: 🏷️ Create the GitHub release
124+
needs: [Release, Upload]
125+
if: >-
126+
${{ always()
127+
&& needs.Release.result == 'success'
128+
&& (needs.Upload.result == 'success' || needs.Upload.result == 'skipped') }}
129+
runs-on: ubuntu-latest
130+
permissions:
131+
# write is needed to create the tag and the GitHub release
132+
contents: write
133+
steps:
134+
- uses: actions/checkout@v4
135+
136+
- uses: actions/github-script@v7
137+
with:
138+
script: |
139+
const get_change_log_diff = require('./scripts/get_changelog_diff.js')
140+
require('fs').writeFileSync('changes.md', get_change_log_diff())
141+
142+
# Customers need a repository line to see a version that is not on Central.
143+
- name: Write the release notes
144+
env:
145+
TAG: ${{ needs.Release.outputs.version }}
146+
run: |
147+
VERSION="${TAG#v}"
148+
: > notes.md
149+
if [ "$PUBLISH_TARGETS" == "streamRepo" ]; then
150+
cat > notes.md <<EOF
151+
> [!IMPORTANT]
152+
> **\`io.getstream.client:stream-java:$VERSION\` is published to the Stream Maven repository, not Maven Central.**
153+
>
154+
> Gradle (\`build.gradle.kts\`):
155+
> \`\`\`kotlin
156+
> repositories {
157+
> mavenCentral()
158+
> maven("https://stream-io-repo.com")
159+
> }
160+
> \`\`\`
161+
>
162+
> Maven (\`pom.xml\`):
163+
> \`\`\`xml
164+
> <repositories>
165+
> <repository>
166+
> <id>stream</id>
167+
> <url>https://stream-io-repo.com</url>
168+
> </repository>
169+
> </repositories>
170+
> \`\`\`
171+
172+
EOF
173+
fi
174+
cat changes.md >> notes.md
175+
52176
- name: Create release on GitHub
53177
uses: ncipollo/release-action@v1
54178
with:
55-
body: ${{ env.CHANGELOG }}
56-
tag: ${{ env.VERSION }}
179+
bodyFile: notes.md
180+
tag: ${{ needs.Release.outputs.version }}
181+
commit: ${{ github.event.pull_request.merge_commit_sha }}
57182
token: ${{ secrets.GITHUB_TOKEN }}

‎README.md‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,18 @@ For the client-side integrations (web and mobile) have a look at the JavaScript,
3030
3131
## ⚙️ Installation
3232

33-
Add the following dependency to your `pom.xml`:
33+
Releases are published to the Stream Maven repository. Add it next to Maven Central, then add the dependency.
34+
35+
In your `pom.xml`:
36+
37+
```xml
38+
<repositories>
39+
<repository>
40+
<id>stream</id>
41+
<url>https://stream-io-repo.com</url>
42+
</repository>
43+
</repositories>
44+
```
3445

3546
```xml
3647
<dependency>
@@ -43,7 +54,14 @@ Add the following dependency to your `pom.xml`:
4354
or in your `build.gradle`:
4455

4556
```gradle
46-
implementation 'io.getstream.client:stream-java:$stream_version'
57+
repositories {
58+
mavenCentral()
59+
maven { url 'https://stream-io-repo.com' }
60+
}
61+
62+
dependencies {
63+
implementation 'io.getstream.client:stream-java:$stream_version'
64+
}
4765
```
4866

4967
In case you want to download the artifact and put it manually into your project,

‎publish.gradle‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ if (secretPropsFile.exists()) {
2525
ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID')
2626
}
2727

28+
// Where to publish: "streamRepo", "central", or both, comma-separated. CI always passes it;
29+
// with nothing passed this stays on Central so a local publishToMavenLocal behaves as before.
30+
// streamRepo stages a Maven-2 tree under build/staged-repo for a separate upload job.
31+
def publishTargets = (findProperty("streamPublishTargets") ?: "central").toString()
32+
.split(",").collect { it.trim() }.findAll { !it.isEmpty() }.toSet()
33+
if (publishTargets.isEmpty() || !(publishTargets - ["central", "streamRepo"]).isEmpty()) {
34+
throw new GradleException("'streamPublishTargets' must be a comma-separated subset of central, streamRepo but was '$publishTargets'")
35+
}
36+
2837
nexusPublishing {
2938
repositories {
3039
sonatype {
@@ -87,6 +96,14 @@ afterEvaluate {
8796
}
8897
}
8998
}
99+
if ("streamRepo" in publishTargets) {
100+
repositories {
101+
maven {
102+
name = "streamRepoStaging"
103+
url = layout.buildDirectory.dir("staged-repo").get().asFile.toURI()
104+
}
105+
}
106+
}
90107
}
91108
}
92109

0 commit comments

Comments
 (0)