SemVerPlugin.groovy
/*
* Copyright 2014-2015 David Fallah
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.tagc.semver
import groovy.transform.PackageScope
import org.ajoberstar.grgit.exception.GrgitException
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import com.github.tagc.semver.projectstrategy.VersionSelectionManager
import com.github.tagc.semver.tasks.BumpMajorTask
import com.github.tagc.semver.tasks.BumpMinorTask
import com.github.tagc.semver.tasks.BumpPatchTask
import com.github.tagc.semver.tasks.BumpTask
import com.github.tagc.semver.tasks.ChangeVersionTask
import com.github.tagc.semver.tasks.PrintVersionTask
import com.github.tagc.semver.version.BaseVersion
import com.github.tagc.semver.version.Version
/**
* An {@link org.gradle.api.Plugin} class that handles the application of semantic
* versioning logic to an {@link org.gradle.api.Project}.
*
* @author davidfallah
* @since v0.1.0
*/
class SemVerPlugin implements Plugin<Project> {
@PackageScope static final String EXTENSION_NAME = 'semver'
private static final String UTF_8_ENCODING = 'UTF-8'
private static final String PRINT_VERSION_TASK_NAME = 'printVersion'
private static final String BUMP_MAJOR_TASK_NAME = 'bumpMajor'
private static final String BUMP_MINOR_TASK_NAME = 'bumpMinor'
private static final String BUMP_PATCH_TASK_NAME = 'bumpPatch'
private static final String BUMP_TASK_NAME = 'bump'
private static final String CHANGE_VERSION_TASK_NAME = 'changeVersion'
private static final String MODIFIES_VERSION_INDICATOR_PROPERTY = 'modifiesVersion'
static String getPrintVersionTaskName() {
return PRINT_VERSION_TASK_NAME
}
static String getBumpMajorTaskName() {
return BUMP_MAJOR_TASK_NAME
}
static String getBumpMinorTaskName() {
return BUMP_MINOR_TASK_NAME
}
static String getBumpPatchTaskName() {
return BUMP_PATCH_TASK_NAME
}
static String getBumpTaskName() {
return BUMP_TASK_NAME
}
static String getChangeVersionTaskName() {
return CHANGE_VERSION_TASK_NAME
}
static String getModifiesVersionIndicatorProperty() {
return MODIFIES_VERSION_INDICATOR_PROPERTY
}
private Logger logger
@Override
void apply(Project project) {
if (!project) {
throw new GradleException('Plugin cannot be applied to null project')
}
logger = project.logger
project.extensions.create(EXTENSION_NAME, SemVerPluginExtension)
addTasks(project)
project.afterEvaluate { setVersionProjectNumber(project) }
/*
* Ensure that at most one task modifies the project version.
*/
project.gradle.taskGraph.whenReady { taskGraph ->
def numVersionModifierTasks = taskGraph.allTasks.findAll { task ->
task.hasProperty(getModifiesVersionIndicatorProperty()) \
&& task."${getModifiesVersionIndicatorProperty()}"
}.size()
if (numVersionModifierTasks > 1) {
throw new GradleException('Only one task may modify the project version in a single build.')
}
}
}
private void addTasks(Project project) {
def extension = project.extensions.findByName(EXTENSION_NAME)
def printVersionTask = project.task(getPrintVersionTaskName(), type:PrintVersionTask)
def changeVersionTask = project.task(getChangeVersionTaskName(), type:ChangeVersionTask)
def majorBumpTask = project.task(getBumpMajorTaskName(), type:BumpMajorTask)
def minorBumpTask = project.task(getBumpMinorTaskName(), type:BumpMinorTask)
def patchBumpTask = project.task(getBumpPatchTaskName(), type:BumpPatchTask)
def bumpTask = project.task(getBumpTaskName(), type:BumpTask)
[
majorBumpTask,
minorBumpTask,
patchBumpTask,
bumpTask
].each { task ->
task.conventionMapping.map('versionFileIn') {
project.file(URLDecoder.decode(extension.versionFilePath, UTF_8_ENCODING))
}
task.conventionMapping.map('versionFileOut') {
project.file(URLDecoder.decode(extension.versionFilePath, UTF_8_ENCODING))
}
task.conventionMapping.map('forceBump') { extension.forceBump }
task.ext."$MODIFIES_VERSION_INDICATOR_PROPERTY" = true
printVersionTask.shouldRunAfter task
task.dependsOn changeVersionTask
}
}
private void setVersionProjectNumber(Project project) {
assert project : 'Null project is illegal'
final GitBranchDetector branchDetector
try {
branchDetector = new GitBranchDetector(project)
} catch (GrgitException wrappedException) {
def exception = new GradleException('No Git repository can be found for this project.',
wrappedException)
throw exception
}
def rawVersion = readRawVersion(project)
def extension = project.extensions.findByName(EXTENSION_NAME)
Version.Category snapshotBump = extension.snapshotBump
def appliedVersion = VersionSelectionManager.instance.selectVersionForProject(
project, rawVersion, snapshotBump)
if (appliedVersion == null) {
throw new GradleException('No appropriate version could be applied for this project.')
}
project.version = appliedVersion
logger.info "Set project version to ${project.version}."
}
private Version readRawVersion(Project project) {
assert project : 'Null project is illegal'
def extension = project.extensions.findByName(EXTENSION_NAME)
final String versionFilePath = URLDecoder.decode(extension.versionFilePath, UTF_8_ENCODING)
if (!versionFilePath) {
throw new GradleException('Version file has not been specified.')
}
def versionFile = project.file(versionFilePath)
if (!versionFile.exists()) {
throw new GradleException("Missing version file: ${versionFile.canonicalPath}.")
}
BaseVersion.Parser.instance.parse(versionFile.text)
}
}