Skip to content

Commit dfee37c

Browse files
committed
Simplify Gradle build logic for making releases. (#684)
Release and release candidate builds require a clean working directory and a matching tag: Create a tag for the release version first, then execute whatever build target you need with the corresponding version specified as project property (`-PpublishVersion=A.B.C` or `-PpublishVersion=A.B.C-rcN`). Release builds additionally enforce signing of the generated artifacts. Ad hoc builds from branches (including `master`) or arbitrary "version" designators always result in SNAPSHOT builds and don't require a clean working directory or a matching tag: Specify the desired "version" as project property (`-PpublishVersion=A-B-C-SNAPSHOT`) or simply omit it (will then use the current branch name).
1 parent 5d3f4e8 commit dfee37c

File tree

3 files changed

+90
-153
lines changed

3 files changed

+90
-153
lines changed

.github/workflows/publish.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
name: Publish package to GitHub Packages
22
on:
33
push:
4-
branches:
4+
tags:
55
- '*-rc*'
66
jobs:
77
publish:
88
runs-on: ubuntu-latest
99
steps:
10-
- uses: actions/checkout@v2
10+
- uses: actions/checkout@v3
11+
with:
12+
fetch-tags: true
1113
- uses: actions/setup-java@v1
1214
with:
1315
java-version: 11
1416
- name: Publish package
15-
run: ./gradlew publishAllPublicationsToGitHubPackagesRepository
17+
run: ./gradlew publishAllPublicationsToGitHubPackagesRepository -PpublishVersion=${{ github.ref_name }}
1618
env:
1719
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1820

MAINTAINING.md

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -54,67 +54,59 @@ sonatypePassword=$token
5454

5555
These are done more often, in irregular intervals. They are not considered stable and may break your application, so be cautious when using them.
5656

57-
The process is equal to the making of a release candidate, but without making any tags:
57+
The process is identical to making a release candidate, but without making any tags:
5858

59-
1. build and upload the `master-SNAPSHOT`:
59+
1. Switch to the `master` branch:
6060
```
61-
git pull; git checkout master;
61+
git switch master
6262
```
63-
1. proceed as described in [Release candidate - Upload to Sonatype](#upload-to-sonatype)
63+
1. Proceed as described in [Release candidate - Upload to Sonatype](#upload-to-sonatype), but omit the `publishVersion` parameter in order to build and upload the `master-SNAPSHOT`.
6464
6565
## Release candidate
6666
6767
*Release candidates should be tested by different people before releasing!*
6868
6969
### Prepare your release candidate
7070
71-
1. Make an rc-branch (necessary for Gradle to pick up the proper name):
71+
1. Make an annotated signed tag for the release candidate (necessary for Gradle to pick up the proper name):
7272
```
73-
git checkout -b A.B.C-rcN
73+
git tag -s metafacture-core-A.B.C-rcN
74+
```
75+
1. When prompted, add a sensible commit message. For instance, something like:
76+
```
77+
Release candidate 5.7.0
7478
```
75-
(leave out the ` metafacture-core-` to avoid later the git "error: src refspec ... matches more than one" when you push the annotated git tag for having a tag named the same as the branch is not possible)
7679
1. Optionally, you can now test the build locally by invoking a Gradle target:
7780
```
78-
./gradlew assemble
81+
./gradlew assemble -PpublishVersion=A.B.C-rcN
7982
```
8083
8184
### Upload to Sonatype
8285
83-
1. Now you can build and upload the release candidate to Sonatype (note that `./gradlew` should inform you to make a "snapshot build". If the version doesn't end with `-SNAPSHOT` the artifacts will not be uploaded to Sonatype's snapshot repository!):
84-
```
85-
./gradlew clean; ./gradlew publishToMavenLocal; ./gradlew publishToSonatype
86-
```
87-
1. Go to [Sonatype's snapshot repository](https://oss.sonatype.org/index.html#nexus-search;gav~org.metafacture) and type in the correct `Version` to see if it is already available there (can take some minutes). [Example for `5.5.1-rc1-SNAPSHOT`](https://oss.sonatype.org/index.html#nexus-search;gav~org.metafacture~~5.5.1*~~)(if you don't see a `5.5.1-rc1-SNAPSHOT.jar` there check it at https://oss.sonatype.org/content/repositories/snapshots/org/metafacture/metafacture-biblio/5.5.1-rc1-SNAPSHOT/).
88-
1. Make an annotated signed tag (it's important to do that _after_ uploading to Sonatype's snapshot repository because otherwise the `-SNAPSHOT` will not be appended to the release candidate thus will not land in `snapshot repository`):
86+
1. Make sure to have a *clean* Git directory (otherwise the build will fail with the error message `Working copy has modifications`):
8987
```
90-
git tag -s metafacture-core-A.B.C-rcN
88+
git status
9189
```
92-
1. Push the annotated signed tag to GitHub:
90+
1. Now you can build and upload the release candidate to Sonatype (note that `./gradlew` should inform you to make a "snapshot build". If the version doesn't end with `-SNAPSHOT` the artifacts will not be uploaded to Sonatype's snapshot repository!):
9391
```
94-
git push origin tag metafacture-core-A.B.C-rcN
92+
./gradlew publishToSonatype -PpublishVersion=A.B.C-rcN
9593
```
94+
1. Go to [Sonatype's snapshot repository](https://oss.sonatype.org/index.html#nexus-search;gav~org.metafacture) and type in the correct `Version` to see if it is already available there (can take some minutes). [Example for `5.5.1-rc1-SNAPSHOT`](https://oss.sonatype.org/index.html#nexus-search;gav~org.metafacture~~5.5.1*~~)(if you don't see a `5.5.1-rc1-SNAPSHOT.jar` there check it at https://oss.sonatype.org/content/repositories/snapshots/org/metafacture/metafacture-biblio/5.5.1-rc1-SNAPSHOT/).
9695
9796
### Publish to [GitHub Packages](https://github.com/orgs/metafacture/packages?repo_name=metafacture-core)
9897
99-
1. Push your properly named branch to GitHub. Notice the `-rc` part of the branch's name:
98+
1. Push the annotated signed tag to GitHub:
10099
```
101-
git push origin A.B.C-rcN
100+
git push origin tag metafacture-core-A.B.C-rcN
102101
```
103-
Because there is `fetch --no-tags` in `actions/checkout@v2` the `-SNAPSHOT` suffix will always be appended (in comparison to doing `./gradlew publishAllPublicationsToGitHubPackagesRepository` locally, which will find the `SCM tag`). The publishing to GitHub packages is triggered then.
104-
105-
If we don't want `-SNAPSHOT` we may want to remove the `-SNAPSHOT` in `build.gradle`:
106-
```
107-
if (grgit.branch.current().name.contains('-rc')) { ...
108-
return "${grgit.branch.current().name}-SNAPSHOT"
109-
}
110-
```
102+
The publishing to GitHub packages is triggered then via the GitHub Actions workflow.
111103
112104
Note that `Packages` is not the same as [`Releases`](https://github.com/metafacture/metafacture-core/releases).
113105
114106
### Consume the SNAPSHOT
115107
116-
1. See e.g. [Example for 5.5.1-rc1-SNAPSHOT](https://oss.sonatype.org/index.html#nexus-search;gav~org.metafacture~~5.5.1*~~) how to configure the dependency.
117-
1. Configure your build system to use Sonatype's Snapshot Repository to be able to load the dependencies of the release candidate (or master-SNAPSHOT).
108+
1. See e.g. [5.5.1-rc1-SNAPSHOT](https://oss.sonatype.org/index.html#nexus-search;gav~org.metafacture~~5.5.1*~~) for how to configure the dependency.
109+
1. Configure your build system to use Sonatype's Snapshot Repository to be able to load the dependencies of the release candidate (or `master-SNAPSHOT`).
118110
For Maven update your `pom.xml` (after `</dependencies>`):
119111
```xml
120112
<repositories>
@@ -149,43 +141,33 @@ Note that `Packages` is not the same as [`Releases`](https://github.com/metafact
149141
150142
a) It's going from your local Git repository to Sonatype to Maven Central. Each station requires some manual actions so you can double check that everything is ok. b) A release should also be published to GitHub.
151143
152-
1. Switch to `master` branch. Merge the approved `rc` into master:
153-
```
154-
git switch master; pull --no-ff origin A.B.C-rcN; git push origin master
155-
```
156-
157-
1. Make sure you have a signed tag locally:
144+
1. Ensure that the approved release candidate tag exactly matches `master` (should output `metafacture-core-A.B.C-rcN`):
158145
```
159-
git show metafacture-core-A.B.C
146+
git switch master; git describe --tags --exact-match
160147
```
161-
If it doesn't exist yet, create it:
148+
1. Make an annotated signed tag for the release:
162149
```
163150
git tag -s metafacture-core-A.B.C
164151
```
165152
1. When prompted, add a sensible commit message. For instance, something like:
166153
```
167154
Release 5.7.0
168155
```
169-
1. Make sure you have that signed tag pushed to GitHub:
170-
```
171-
git ls-remote --tags origin
172-
```
173-
If it is missing, push it with:
156+
1. Push the annotated signed tag to GitHub:
174157
```
175-
git push origin metafacture-core-A.B.C
158+
git push origin metafacture-core-A.B.C
176159
```
177-
1. Now the tag is available at GitHub. You can manually choose to [draft a new release on GitHub](https://github.com/metafacture/metafacture-core/releases/new). The signed `*dist*` files must be uploaded manually. They are produced like this:
160+
1. Make sure to have a *clean* Git directory (otherwise the build will fail with the error message `Working copy has modifications`):
178161
```
179-
./gradlew metafacture-runner:signArchive
162+
git status
180163
```
181-
and can be found in `metafacture-core/metafacture-runner/build/distributions/` (don't mind the `Source code` for that is created by GitHub automatically).
182-
1. Make sure to have a *clean* Git directory (otherwise only a SNAPSHOT will be built):
164+
1. Now the tag is available on GitHub. You can manually choose to [draft a new release on GitHub](https://github.com/metafacture/metafacture-core/releases/new). The signed `*-dist.*` files must be uploaded manually. They are produced like this:
183165
```
184-
git status
166+
./gradlew metafacture-runner:signArchive -PpublishVersion=A.B.C
185167
```
186-
1. Let the release be built and uploaded (the SCM tag will be detected and the release be built):
168+
and can be found in `metafacture-runner/build/distributions/` (don't mind the `Source code` for that is created by GitHub automatically).
169+
1. Now you can build and upload the release to Sonatype:
187170
```
188-
./gradlew clean; ./gradlew publishToMavenLocal; ./gradlew publishToSonatype
171+
./gradlew publishToSonatype -PpublishVersion=A.B.C
189172
```
190-
1. Finally, go to [oss.sonatype.org](https://oss.sonatype.org), log in, check the [Staging Repositories](https://oss.sonatype.org/#stagingRepositories) and when it's finished, click on `Close`. If everything is good publish with clicking on `Release` - attention, because once published it can't be removed. The artifacts are uploaded to Maven Central (which may take some time. Have a look e.g. [metafacture-biblio](https://repo1.maven.org/maven2/org/metafacture/metafacture-biblio/) ). You can check that it's actually in the publishing pipeline by clicking on `Views/Repositories->Releases`, then type in the `Path lookup` field `org/metafacture/` and click on version.
191-
173+
1. Finally, go to [oss.sonatype.org](https://oss.sonatype.org), log in, check the [Staging Repositories](https://oss.sonatype.org/#stagingRepositories) and when it's finished, click on `Close`. If everything is good, publish with clicking on `Release` - attention, because once published it can't be removed. The artifacts are uploaded to Maven Central (which may take some time; have a look at e.g. [metafacture-biblio](https://repo1.maven.org/maven2/org/metafacture/metafacture-biblio/)). You can check that it's actually in the publishing pipeline by clicking on `Views/Repositories->Releases`, then type in the `Path lookup` field `org/metafacture/` and click on version.

build.gradle

Lines changed: 51 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ subprojects {
270270
password = System.getenv("GITHUB_TOKEN")
271271
}
272272
}
273-
if (scmInfo.isRelease() && project.hasProperty('releaseRepositoryUrl')) {
273+
if (scmInfo.isRelease && project.hasProperty('releaseRepositoryUrl')) {
274274
maven {
275275
url = releaseRepositoryUrl
276276
credentials {
@@ -284,7 +284,7 @@ subprojects {
284284

285285
signing {
286286
required {
287-
scmInfo.isRelease()
287+
scmInfo.isRelease
288288
}
289289
sign publishing.publications.mavenArtifacts
290290
}
@@ -310,114 +310,67 @@ nexusPublishing {
310310
class ScmInfo {
311311
def version
312312
def tag
313+
def isRelease
313314

314-
ScmInfo(version, tag) {
315+
ScmInfo(version, tag, isRelease) {
315316
this.version = version
316317
this.tag = tag
317-
}
318-
319-
def isRelease() {
320-
return tag != null
318+
this.isRelease = isRelease
321319
}
322320
}
323321

324322
def getScmInfo() {
325-
def tag = getGitTag()
326-
if (tag != null) {
327-
logger.lifecycle('SCM tag found. Making a release build')
328-
version = extractVersionFromTag(tag)
329-
} else {
330-
logger.lifecycle('No SCM tag found. Making a snapshot build')
331-
version = getSnapshotVersion()
332-
}
333-
logger.lifecycle("Version is $version")
334-
return new ScmInfo(version, tag)
335-
}
323+
def version = null
324+
def versionTag = null
325+
def isRelease = false
336326

337-
def getSnapshotVersion() {
338-
if (grgit == null) {
339-
logger.warn('No Git repository found')
340-
return 'non-scm-build-SNAPSHOT'
341-
}
342-
if (grgit.branch.current().fullName == 'HEAD') {
343-
logger.lifecycle('Detached HEAD found')
344-
return "commit-${grgit.head().id}-SNAPSHOT"
345-
}
346-
if (grgit.branch.current().name == 'master') {
347-
logger.lifecycle('On master branch')
348-
return 'master-SNAPSHOT'
349-
}
350-
if (grgit.branch.current().name.startsWith('releases/')) {
351-
logger.lifecycle('Release branch found')
352-
return "${extractVersionFromBranch(grgit.branch.current().name)}-SNAPSHOT"
353-
}
354-
if (grgit.branch.current().name.contains('-rc')) {
355-
logger.lifecycle('Release candidate branch found')
356-
return "${grgit.branch.current().name}-SNAPSHOT"
357-
}
358-
logger.lifecycle('Feature branch found')
359-
return "feature-${grgit.branch.current().name}-SNAPSHOT"
360-
}
327+
if (project.hasProperty('publishVersion')) {
328+
version = publishVersion
361329

362-
def getGitTag() {
363-
if (grgit == null) {
364-
logger.warn('No Git repository found')
365-
return null
366-
}
367-
if (!grgit.status().isClean()) {
368-
logger.warn('Working copy has modifications. Will not look for tags')
369-
return null
370-
}
371-
def tags = getAnnotatedTags()
372-
if (tags.isEmpty()) {
373-
logger.lifecycle('HEAD has no annotated tags')
374-
return null
375-
}
376-
if (tags.size() > 1) {
377-
logger.warn("HEAD has ${tags.size()} annotated tags")
378-
return null
379-
}
380-
def tag = tags[0]
381-
logger.lifecycle("Found annotated tag $tag.name")
382-
return tag.name
383-
}
330+
def matcher = version =~ /\d+(?:\.\d+)+(-rc\d+)?/
331+
if (matcher.matches()) {
332+
if (grgit == null) {
333+
throw new GradleException('No Git repository found')
334+
}
335+
if (!grgit.status().isClean()) {
336+
throw new GradleException('Working copy has modifications')
337+
}
384338

385-
def getAnnotatedTags() {
386-
def tags = []
387-
for (tag in grgit.tag.list()) {
388-
if (tag.commit == grgit.head()
389-
&& tag.tagger != null
390-
&& tag.dateTime != null) {
391-
tags.add tag
339+
def tagName = "metafacture-core-$version"
340+
for (tag in grgit.tag.list()) {
341+
if (
342+
tag.name == tagName && // matching name
343+
tag.commit == grgit.head() && // pointing at HEAD
344+
tag.tagger != null && tag.dateTime != null // annotated
345+
) {
346+
versionTag = tagName
347+
break
348+
}
349+
}
350+
if (versionTag == null) {
351+
throw new GradleException("HEAD has no matching annotated tag: $tagName")
352+
}
353+
354+
if (matcher[0][1] != null) {
355+
version += '-SNAPSHOT'
356+
logger.lifecycle("Making release candidate build: $version")
357+
}
358+
else {
359+
logger.lifecycle("Making release build: $version")
360+
isRelease = true
361+
}
362+
}
363+
else if (version.matches(~/[\w-]+-SNAPSHOT/)) {
364+
logger.lifecycle("Making snapshot build: $version")
365+
}
366+
else {
367+
throw new GradleException("Invalid version: $version")
392368
}
393369
}
394-
return tags
395-
}
396-
397-
def static extractVersionFromTag(tag) {
398-
Matcher matcher =
399-
tag =~ /metafacture-core-(\d+\.\d+\.\d+(-[-A-Za-z0-9]+)?)/
400-
if (!matcher.matches()) {
401-
throw new GradleException("""\
402-
Unsupported tag format: $tag
403-
Could not extract version from tag. Supported tag formats are
404-
metafacture-core-X.Y.Z and
405-
metafacture-core-X.Y.Z-QUALIFIER
406-
""".stripIndent())
370+
else {
371+
version = "${grgit.branch.current().name}-SNAPSHOT"
372+
logger.lifecycle("Making snapshot build: $version")
407373
}
408-
return matcher.group(1)
409-
}
410374

411-
def static extractVersionFromBranch(branch) {
412-
Matcher matcher =
413-
branch =~ /releases\/metafacture-core-(\d+\.\d+\.\d+(-[-A-Za-z0-9]+)?)/
414-
if (!matcher.matches()) {
415-
throw new GradleException("""\
416-
Unsupported branch format: $branch
417-
Could not extract version from branch. Supported branch formats are
418-
releases/metafacture-core-X.Y.Z and
419-
releases/metafacture-core-X.Y.Z-QUALIFIER
420-
""".stripIndent())
421-
}
422-
return matcher.group(1)
375+
return new ScmInfo(version, versionTag, isRelease)
423376
}

0 commit comments

Comments
 (0)