Gradle implementation of semantic release
At its core semantic-release
is a set of conventions that gives you entirely automated, semver-compliant package publishing. Coincidentally these conventions make sense on their own – like meaningful commit messages.
This removes the immediate connection between human emotions and version numbers, so strictly following the SemVer spec is not a problem anymore – and that’s ultimately semantic-release
’s goal.
This talk gives you a complete introduction to the underlying concepts of this module -- semantic release
Instead of writing meaningless commit messages, we can take our time to think about the changes in the codebase and write them down. Following formalized conventions it this then possible to generate a helpful changelog and to derive the next semantic version number from them.
When semantic-release
got setup it will do that after every successful continuous integration build of your master branch (or any other branch you specify) and publish the new version for you. That way no human is directly involved in the release process and your releases are guaranteed to be unromantic and unsentimental.
This module ships with the AngularJS Commit Message Conventions and changelog generator, but you can define your own style.
Each commit message consists of a header, a body and a footer. The header has a special format that includes a type, a scope and a subject:
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
Under the hood this plugin uses gradle-git's release-base plugin and configures it to automatically increment the major, minor or patch version depending on the commit messages since the last release. Releases are only performed on certain branches (/master/ and /(?:release[-/])?\d+(?:.\d+)?.x/ by default). On other branches only SNAPSHOT versions are built. On these branches the branch name is automatically appended to the version.
When doing a final release -- in addition to the default behavior of gradle-git to tag the release and push the tag -- if the origin repository is a github repository, the plugin generates a changelog and creates a release on GitHub.
plugins {
id 'de.gliderpilot.semantic-release' version '1.0.0'
}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'de.gliderpilot.gradle.semantic-release:gradle-semantic-release-plugin:1.0.0'
}
}
apply plugin: 'de.gliderpilot.semantic-release'
Don't configure the version property of the gradle project (no version in gradle.properties
, no version in build.gradle
)
Example for a java project deploying to maven
apply plugin: 'java'
apply plugin: 'maven-publish'
group = 'org.example'
publishing {
[...]
}
There is a new release
task, which automatically dependsOn the build
task and is finalizedBy the publish
task
(and the uploadArchives
task, if you use the old publishing mechanism).
Because the release
task does automatically execute the publish
task, you must take care of configuring only valid
repositories.
publishing {
repositories {
maven {
if (version.toString().endsWith("-SNAPSHOT") {
url "http://.../snapshots"
} else {
url "http://.../releases"
}
}
}
}
If you need other mechanisms to upload your artifacts, you need to manually configure this.
if (!version.toString().endsWith('-SNAPSHOT'))
publish.dependsOn publishPlugins, bintrayUpload
else if ((System.getenv('TRAVIS_PULL_REQUEST') ?: "false") == "false")
publish.dependsOn artifactoryPublish
In order to automatically upload the changelog to GitHub, you need a GitHub token and tell the plugin about it.
project.ext.ghToken = project.hasProperty('ghToken') ? project.getProperty('ghToken') : System.getenv('GH_TOKEN') ?: null
semanticRelease {
repo {
ghToken = project.ghToken
}
}
The ghToken is mandatory.
apply plugin: 'java'
task sourcesJar(type: Jar) {
classifier = 'sources'
from sourceSets.main.allSource
}
semanticRelease {
repo {
releaseAsset jar
// optionally set name, label and/or contentType
releaseAsset sourcesJar, name: "the-sources.jar", label: 'the sources jar', contentType: 'application/zip'
// or
// releaseAsset sourcesJar name "the-sources.jar" label "the sources jar" contentType "application/zip"
}
}
By specifying useGhEnterprise
this plugin can be used to publish releases along with the changelog to a GitHub Enterprise server.
semanticRelease {
repo {
ghToken = project.ext.ghToken
useGhEnterprise "https://github.enterprise" // GitHub Enterprise URL
}
}
First, you need to configure the environment variable GH_TOKEN in travis. Then you need a .travis.yml
sudo: false
addons:
apt:
packages:
- git
language: java
jdk: oraclejdk7
env: TERM=dumb
branches:
except:
- /^v\d+\.\d+\.\d+$/
before_install:
- git fetch --unshallow
- git config user.email "name@example.com"
- git config user.name "Travis-CI"
- git config url.https://.insteadOf git://
- git checkout -qf $TRAVIS_BRANCH
install:
- echo "skip default gradlew assemble"
script:
- ./gradlew release -Dorg.ajoberstar.grgit.auth.username=${GH_TOKEN} -Dorg.ajoberstar.grgit.auth.password
By default the plugin allows a couple of workflows (aka branching strategies).
Just commit on master
. As soon as you push to origin, travis will build a new version.
Develop on develop
and feature branches. Travis will build SNAPSHOT versions on these branches.
To create a release, just create a branch release/1.x
or release/1.0.x
, and travis will create the release.
Develop features on branches branched from master. Once a feature is merged back to master, a version is released.
You can tweak the default behavior of the plugin using two extensions.
semanticRelease {
changeLog {
changeScope = { org.ajoberstar.grgit.Commit ->
// return org.ajoberstar.gradle.git.release.semver.ChangeScope
// return MAJOR, MINOR or PATH to create a release
// return null, if this commit is not relevant (e.g. only doc)
[...]
}
changeLog = { List<Commit> commits, org.ajoberstar.gradle.git.release.base.ReleaseVersion version ->
"""\
Release of $version.version
[...]
""".stripIndent()
}
}
releaseBranches {
include 'stable'
}
branchNames {
// feature branches are dev/... instead of feature/...
// the version on branch dev/foo should be 1.0.0-foo-SNAPSHOT
// and not 1.0.0-dev-foo-SNAPSHOT
replace ~/^dev\/(.*)$/, '$1'
}
}
Since this plugin uses gradle-git under the cover, you can configure this plugin as described in their wiki.
The plugin does configure gradle-git with one versionStrategy and one defaultVersionStrategy. The defaultVersionStrategy is responsible for building SNAPSHOT versions. The semantic-release versionStrategy is only used, if there are
- relevant changes (features with or without BREAKING CHANGES, bugfixes or performance improvements)
- the branch is master or a release branch
- the task
release
was used and - the workspace is clean
The gradle-semantic-release-plugin
defines it's own extension semanticRelease
. When configuring gradle-git, you must not use the VersionStrategies defined by gradle-git directly, but you must use the releaseStrategy or snapshotStrategy of the semanticRelease extension as a base and use copyWith. Otherwise the version will not be inferred based on the commit messages.
import org.ajoberstar.gradle.git.release.semver.*
import org.ajoberstar.gradle.git.release.opinion.Strategies
release {
// replace the default strategy to add '-FINAL' to the version
versionStrategy semanticRelease.releaseStrategy.copyWith{
preReleaseStrategy: { it.copyWith(inferredPreRelease: 'FINAL') }
}
// add a second strategy to create release candidates from 'rc/.*' branches
versionStrategy semanticRelease.releaseStrategy.copyWith(
// the type is important, without type you would again replace the default strategy
type: 'rc',
selector: { SemVerStrategyState state ->
!state.repoDirty && state.currentBranch.name ==~ /rc\/.*/ &&
semanticRelease.semanticStrategy.canRelease(state) && project.gradle.startParameter.taskNames.find { it == 'release' }
},
preReleaseStrategy: StrategyUtil.all({ it.copyWith(inferredPreRelease: 'rc') } as PartialSemVerStrategy, Strategies.PreRelease.COUNT_INCREMENTED)
)
}
I think you might frequently ask questions like these
If you run ./gradlew
locally, the version that would be build gets logged (just remove -SNAPSHOT
).
Of course you can, but this doesn't necessarily mean you should. Running your tests on an independent machine before releasing software is a crucial part of this workflow. Also it is a pain to set this up locally, with tokens lying around and everything.
You can trigger a release by pushing to your GitHub repository. You deliberately cannot trigger a specific version release, because this is the whole point of semantic-release
. Start your packages with 1.0.0
and semver on. You can however prevent an accidental major version bump by using a branch pattern release/\d+\.x
. And you can prevent an accidental minor version bump by using a branch pattern release/\d+\.\d+\.x
. Using these two branchname patterns, you can also manually trigger a version bump without a correspondent commit message.
It is indeed a great idea because it forces you to follow best practices. If you don't feel comfortable making every passing feature or fix on your master branch addressable you might not treat your master right. Have a look at branch workflows. If you still think you should have control over the exact point in time of your release, e.g. because you are following a release schedule, you can release only on the release
branch and push your code there in certain intervals.
gradle-semantic-release-plugin
has a full unit- and integration-test-suite. Additionally we eat our own dogfood and release using our own plugin -- A new version won't get published if there is an error.
Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ 2015 © Tobias Schulte, based on the ideas of the semantic-release plugin of Stephan Bönnemann and contributors