From 5d3f4e819e16f7e40c6de8be1c37aed6ce29fc51 Mon Sep 17 00:00:00 2001 From: Jens Wille Date: Tue, 24 Jun 2025 11:36:05 +0200 Subject: [PATCH 1/3] Fix internal link in MAINTAINING.md. (#591) --- MAINTAINING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINING.md b/MAINTAINING.md index f1a571457..ebb6a50e9 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -60,7 +60,7 @@ The process is equal to the making of a release candidate, but without making an ``` git pull; git checkout master; ``` -1. proceed as described in [Release candidate - Upload to Sonatype](https://github.com/metafacture/metafacture-core/wiki/Maintainer-Guidelines#upload-to-sonatype) +1. proceed as described in [Release candidate - Upload to Sonatype](#upload-to-sonatype) ## Release candidate From ab7f36896a90b4eac8829c7afe274da4d9006a9c Mon Sep 17 00:00:00 2001 From: Jens Wille Date: Tue, 24 Jun 2025 14:31:18 +0200 Subject: [PATCH 2/3] 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). --- .github/workflows/publish.yml | 8 ++- MAINTAINING.md | 86 +++++++++-------------- build.gradle | 125 ++++++++++------------------------ 3 files changed, 75 insertions(+), 144 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 147ac4aed..df47542cc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,18 +1,20 @@ name: Publish package to GitHub Packages on: push: - branches: + tags: - '*-rc*' jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + fetch-tags: true - uses: actions/setup-java@v1 with: java-version: 11 - name: Publish package - run: ./gradlew publishAllPublicationsToGitHubPackagesRepository + run: ./gradlew publishAllPublicationsToGitHubPackagesRepository -PpublishVersion=${{ github.ref_name }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/MAINTAINING.md b/MAINTAINING.md index ebb6a50e9..da88db4a5 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -54,13 +54,13 @@ sonatypePassword=$token These are done more often, in irregular intervals. They are not considered stable and may break your application, so be cautious when using them. -The process is equal to the making of a release candidate, but without making any tags: +The process is identical to making a release candidate, but without making any tags: -1. build and upload the `master-SNAPSHOT`: +1. Switch to the `master` branch: ``` - git pull; git checkout master; + git switch master ``` -1. proceed as described in [Release candidate - Upload to Sonatype](#upload-to-sonatype) +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`. ## Release candidate @@ -68,53 +68,45 @@ The process is equal to the making of a release candidate, but without making an ### Prepare your release candidate -1. Make an rc-branch (necessary for Gradle to pick up the proper name): +1. Make an annotated signed tag for the release candidate (necessary for Gradle to pick up the proper name): ``` - git checkout -b A.B.C-rcN + git tag -s metafacture-core-A.B.C-rcN + ``` +1. When prompted, add a sensible commit message. For instance, something like: + ``` + Release candidate 5.7.0 ``` - (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) 1. Optionally, you can now test the build locally by invoking a Gradle target: ``` - ./gradlew assemble + ./gradlew assemble -PpublishVersion=A.B.C-rcN ``` ### Upload to Sonatype -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!): - ``` - ./gradlew clean; ./gradlew publishToMavenLocal; ./gradlew publishToSonatype - ``` -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/). -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`): +1. Make sure to have a *clean* Git directory (otherwise the build will fail with the error message `Working copy has modifications`): ``` - git tag -s metafacture-core-A.B.C-rcN + git status ``` -1. Push the annotated signed tag to GitHub: +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!): ``` - git push origin tag metafacture-core-A.B.C-rcN + ./gradlew publishToSonatype -PpublishVersion=A.B.C-rcN ``` +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/). ### Publish to [GitHub Packages](https://github.com/orgs/metafacture/packages?repo_name=metafacture-core) -1. Push your properly named branch to GitHub. Notice the `-rc` part of the branch's name: +1. Push the annotated signed tag to GitHub: ``` - git push origin A.B.C-rcN + git push origin tag metafacture-core-A.B.C-rcN ``` -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. - -If we don't want `-SNAPSHOT` we may want to remove the `-SNAPSHOT` in `build.gradle`: -``` -if (grgit.branch.current().name.contains('-rc')) { ... - return "${grgit.branch.current().name}-SNAPSHOT" -} -``` +The publishing to GitHub packages is triggered then via the GitHub Actions workflow. Note that `Packages` is not the same as [`Releases`](https://github.com/metafacture/metafacture-core/releases). ### Consume the SNAPSHOT -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. -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). +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. +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`). For Maven update your `pom.xml` (after ``): ```xml @@ -149,16 +141,11 @@ Note that `Packages` is not the same as [`Releases`](https://github.com/metafact 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. -1. Switch to `master` branch. Merge the approved `rc` into master: - ``` - git switch master; pull --no-ff origin A.B.C-rcN; git push origin master - ``` - -1. Make sure you have a signed tag locally: +1. Ensure that the approved release candidate tag exactly matches `master` (should output `metafacture-core-A.B.C-rcN`): ``` - git show metafacture-core-A.B.C + git switch master; git describe --tags --exact-match ``` - If it doesn't exist yet, create it: +1. Make an annotated signed tag for the release: ``` git tag -s metafacture-core-A.B.C ``` @@ -166,26 +153,21 @@ a) It's going from your local Git repository to Sonatype to Maven Central. Each ``` Release 5.7.0 ``` -1. Make sure you have that signed tag pushed to GitHub: - ``` - git ls-remote --tags origin - ``` - If it is missing, push it with: +1. Push the annotated signed tag to GitHub: ``` - git push origin metafacture-core-A.B.C + git push origin metafacture-core-A.B.C ``` -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: +1. Make sure to have a *clean* Git directory (otherwise the build will fail with the error message `Working copy has modifications`): ``` - ./gradlew metafacture-runner:signArchive + git status ``` - and can be found in `metafacture-core/metafacture-runner/build/distributions/` (don't mind the `Source code` for that is created by GitHub automatically). -1. Make sure to have a *clean* Git directory (otherwise only a SNAPSHOT will be built): +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: ``` - git status + ./gradlew metafacture-runner:signArchive -PpublishVersion=A.B.C ``` -1. Let the release be built and uploaded (the SCM tag will be detected and the release be built): + and can be found in `metafacture-runner/build/distributions/` (don't mind the `Source code` for that is created by GitHub automatically). +1. Now you can build and upload the release to Sonatype: ``` - ./gradlew clean; ./gradlew publishToMavenLocal; ./gradlew publishToSonatype + ./gradlew publishToSonatype -PpublishVersion=A.B.C ``` -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. - +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. diff --git a/build.gradle b/build.gradle index 9d0977494..b9dcf80bc 100644 --- a/build.gradle +++ b/build.gradle @@ -63,6 +63,7 @@ subprojects { } ext.scmInfo = getScmInfo() +logger.lifecycle("Making ${scmInfo.type()} build: ${scmInfo.version}") allprojects { group = 'org.metafacture' @@ -270,7 +271,7 @@ subprojects { password = System.getenv("GITHUB_TOKEN") } } - if (scmInfo.isRelease() && project.hasProperty('releaseRepositoryUrl')) { + if (scmInfo.isRelease && project.hasProperty('releaseRepositoryUrl')) { maven { url = releaseRepositoryUrl credentials { @@ -284,7 +285,7 @@ subprojects { signing { required { - scmInfo.isRelease() + scmInfo.isRelease } sign publishing.publications.mavenArtifacts } @@ -310,114 +311,60 @@ nexusPublishing { class ScmInfo { def version def tag + def isRelease + + ScmInfo(version, tag, isRelease) { + if (!isRelease) version += '-SNAPSHOT' - ScmInfo(version, tag) { this.version = version this.tag = tag + this.isRelease = isRelease } - def isRelease() { - return tag != null + def type() { + return isRelease ? 'release' : tag != null ? 'release candidate' : 'snapshot' } } def getScmInfo() { - def tag = getGitTag() - if (tag != null) { - logger.lifecycle('SCM tag found. Making a release build') - version = extractVersionFromTag(tag) - } else { - logger.lifecycle('No SCM tag found. Making a snapshot build') - version = getSnapshotVersion() - } - logger.lifecycle("Version is $version") - return new ScmInfo(version, tag) -} + def version = null + def tag = null + def isRelease = false -def getSnapshotVersion() { - if (grgit == null) { - logger.warn('No Git repository found') - return 'non-scm-build-SNAPSHOT' - } - if (grgit.branch.current().fullName == 'HEAD') { - logger.lifecycle('Detached HEAD found') - return "commit-${grgit.head().id}-SNAPSHOT" - } - if (grgit.branch.current().name == 'master') { - logger.lifecycle('On master branch') - return 'master-SNAPSHOT' - } - if (grgit.branch.current().name.startsWith('releases/')) { - logger.lifecycle('Release branch found') - return "${extractVersionFromBranch(grgit.branch.current().name)}-SNAPSHOT" + if (project.hasProperty('publishVersion')) { + version = publishVersion + + def matcher = version =~ /\d+(?:\.\d+)+(-rc\d+)?/ + if (matcher.matches()) { + tag = getGitTag(version) + isRelease = matcher.group(1) == null + } } - if (grgit.branch.current().name.contains('-rc')) { - logger.lifecycle('Release candidate branch found') - return "${grgit.branch.current().name}-SNAPSHOT" + else { + version = grgit != null ? grgit.branch.current().name : rootProject.name } - logger.lifecycle('Feature branch found') - return "feature-${grgit.branch.current().name}-SNAPSHOT" + + return new ScmInfo(version, tag, isRelease) } -def getGitTag() { +def getGitTag(version) { if (grgit == null) { - logger.warn('No Git repository found') - return null + throw new GradleException('No Git repository found') } if (!grgit.status().isClean()) { - logger.warn('Working copy has modifications. Will not look for tags') - return null - } - def tags = getAnnotatedTags() - if (tags.isEmpty()) { - logger.lifecycle('HEAD has no annotated tags') - return null - } - if (tags.size() > 1) { - logger.warn("HEAD has ${tags.size()} annotated tags") - return null + throw new GradleException('Working copy has modifications') } - def tag = tags[0] - logger.lifecycle("Found annotated tag $tag.name") - return tag.name -} -def getAnnotatedTags() { - def tags = [] + def tagName = "${rootProject.name}-$version" for (tag in grgit.tag.list()) { - if (tag.commit == grgit.head() - && tag.tagger != null - && tag.dateTime != null) { - tags.add tag + if ( + tag.name == tagName && // matching name + tag.commit == grgit.head() && // pointing at HEAD + tag.tagger != null && tag.dateTime != null // annotated + ) { + return tagName } } - return tags -} -def static extractVersionFromTag(tag) { - Matcher matcher = - tag =~ /metafacture-core-(\d+\.\d+\.\d+(-[-A-Za-z0-9]+)?)/ - if (!matcher.matches()) { - throw new GradleException("""\ - Unsupported tag format: $tag - Could not extract version from tag. Supported tag formats are - metafacture-core-X.Y.Z and - metafacture-core-X.Y.Z-QUALIFIER - """.stripIndent()) - } - return matcher.group(1) -} - -def static extractVersionFromBranch(branch) { - Matcher matcher = - branch =~ /releases\/metafacture-core-(\d+\.\d+\.\d+(-[-A-Za-z0-9]+)?)/ - if (!matcher.matches()) { - throw new GradleException("""\ - Unsupported branch format: $branch - Could not extract version from branch. Supported branch formats are - releases/metafacture-core-X.Y.Z and - releases/metafacture-core-X.Y.Z-QUALIFIER - """.stripIndent()) - } - return matcher.group(1) + throw new GradleException("HEAD has no matching annotated tag: $tagName") } From 2debe2829656fa26feac85b59eceaa5209783adf Mon Sep 17 00:00:00 2001 From: Jens Wille Date: Thu, 17 Jul 2025 18:21:14 +0200 Subject: [PATCH 3/3] Use environment variable in GitHub workflow. (#700) In order to minimize risk of injection attacks. --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index df47542cc..4a29cd73b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: with: java-version: 11 - name: Publish package - run: ./gradlew publishAllPublicationsToGitHubPackagesRepository -PpublishVersion=${{ github.ref_name }} + run: ./gradlew publishAllPublicationsToGitHubPackagesRepository -PpublishVersion="$REFNAME" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + REFNAME: ${{ github.ref_name }}