All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.researchgate.release.ReleasePlugin.groovy Maven / Gradle / Ivy

Go to download

gradle-release is a plugin for providing a Maven-like release process to project using Gradle.

There is a newer version: 2.4.0
Show newest version
/*
 * This file is part of the gradle-release plugin.
 *
 * (c) Eric Berry
 * (c) ResearchGate GmbH
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

package net.researchgate.release

import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.tasks.GradleBuild
import org.gradle.api.tasks.TaskState

import java.util.regex.Matcher

class ReleasePlugin extends PluginHelper implements Plugin {

    static final String RELEASE_GROUP = 'Release'

    private BaseScmAdapter scmAdapter

    void apply(Project project) {
        this.project = project
        extension = project.extensions.create('release', ReleaseExtension, project, attributes)

        String preCommitText = findProperty('release.preCommitText', null, 'preCommitText')
        if (preCommitText) {
            extension.preCommitText = preCommitText
        }

        // name tasks with an absolute path so subprojects can be released independently
        String p = project.path
        p = !p.endsWith(Project.PATH_SEPARATOR) ? p + Project.PATH_SEPARATOR : p

        project.task('release', description: 'Verify project, release, and update version to next.', group: RELEASE_GROUP, type: GradleBuild) {
            startParameter = project.getGradle().startParameter.newInstance()

            tasks = [
                "${p}createScmAdapter" as String,
                "${p}initScmAdapter" as String,
                "${p}checkCommitNeeded" as String,
                "${p}checkUpdateNeeded" as String,
                "${p}unSnapshotVersion" as String,
                "${p}confirmReleaseVersion" as String,
                "${p}checkSnapshotDependencies" as String,
                "${p}runBuildTasks" as String,
                "${p}preTagCommit" as String,
                "${p}createReleaseTag" as String,
                "${p}updateVersion" as String,
                "${p}commitNewVersion" as String
            ]
        }

        project.task('createScmAdapter', group: RELEASE_GROUP,
            description: 'Finds the correct SCM plugin') << this.&createScmAdapter
        project.task('initScmAdapter', group: RELEASE_GROUP,
            description: 'Initializes the SCM plugin') << this.&initScmAdapter
        project.task('checkCommitNeeded', group: RELEASE_GROUP,
            description: 'Checks to see if there are any added, modified, removed, or un-versioned files.') << this.&checkCommitNeeded
        project.task('checkUpdateNeeded', group: RELEASE_GROUP,
            description: 'Checks to see if there are any incoming or outgoing changes that haven\'t been applied locally.') << this.&checkUpdateNeeded
        project.task('unSnapshotVersion', group: RELEASE_GROUP,
            description: 'Removes "-SNAPSHOT" from your project\'s current version.') << this.&unSnapshotVersion
        project.task('confirmReleaseVersion', group: RELEASE_GROUP,
            description: 'Prompts user for this release version. Allows for alpha or pre releases.') << this.&confirmReleaseVersion
        project.task('checkSnapshotDependencies', group: RELEASE_GROUP,
            description: 'Checks to see if your project has any SNAPSHOT dependencies.') << this.&checkSnapshotDependencies

        List buildTasks = []
        extension.buildTasks.each { String task ->
            buildTasks.add(p + task);
        }

        project.task('runBuildTasks', group: RELEASE_GROUP,
            description: 'Runs the build process in a separate gradle run.', type: GradleBuild) {
            startParameter = project.getGradle().startParameter.newInstance()

            project.afterEvaluate {
                tasks = [
                    "${p}beforeReleaseBuild" as String,
                    buildTasks,
                    "${p}afterReleaseBuild" as String
                ].flatten()
            }
        }
        project.task('preTagCommit', group: RELEASE_GROUP,
            description: 'Commits any changes made by the Release plugin - eg. If the unSnapshotVersion tas was executed') << this.&preTagCommit
        project.task('createReleaseTag', group: RELEASE_GROUP,
            description: 'Creates a tag in SCM for the current (un-snapshotted) version.') << this.&commitTag
        project.task('updateVersion', group: RELEASE_GROUP,
            description: 'Prompts user for the next version. Does it\'s best to supply a smart default.') << this.&updateVersion
        project.task('commitNewVersion', group: RELEASE_GROUP,
            description: 'Commits the version update to your SCM') << this.&commitNewVersion

        Boolean supportsMustRunAfter = project.tasks.initScmAdapter.respondsTo('mustRunAfter')

        if (supportsMustRunAfter) {
            project.tasks.initScmAdapter.mustRunAfter(project.tasks.createScmAdapter)
            project.tasks.checkCommitNeeded.mustRunAfter(project.tasks.initScmAdapter)
            project.tasks.checkUpdateNeeded.mustRunAfter(project.tasks.checkCommitNeeded)
            project.tasks.unSnapshotVersion.mustRunAfter(project.tasks.checkUpdateNeeded)
            project.tasks.confirmReleaseVersion.mustRunAfter(project.tasks.unSnapshotVersion)
            project.tasks.checkSnapshotDependencies.mustRunAfter(project.tasks.confirmReleaseVersion)
            project.tasks.runBuildTasks.mustRunAfter(project.tasks.checkSnapshotDependencies)
            project.tasks.preTagCommit.mustRunAfter(project.tasks.runBuildTasks)
            project.tasks.createReleaseTag.mustRunAfter(project.tasks.preTagCommit)
            project.tasks.updateVersion.mustRunAfter(project.tasks.createReleaseTag)
            project.tasks.commitNewVersion.mustRunAfter(project.tasks.updateVersion)
        }

        project.task('beforeReleaseBuild', group: RELEASE_GROUP,
            description: 'Runs immediately before the build when doing a release') {}
        project.task('afterReleaseBuild', group: RELEASE_GROUP,
            description: 'Runs immediately after the build when doing a release') {}

        if (supportsMustRunAfter) {
            project.task(buildTasks.first()).mustRunAfter(project.tasks.beforeReleaseBuild)
            project.tasks.afterReleaseBuild.mustRunAfter(buildTasks.last())
        }

        project.gradle.taskGraph.afterTask { Task task, TaskState state ->
            if (state.failure && task.name == "release") {
                try {
                    createScmAdapter()
                } catch (Exception e) {}
                if (scmAdapter && extension.revertOnFail && project.file(extension.versionPropertyFile)?.exists()) {
                    log.error('Release process failed, reverting back any changes made by Release Plugin.')
                    scmAdapter.revert()
                } else {
                    log.error('Release process failed, please remember to revert any uncommitted changes made by the Release Plugin.')
                }
            }
        }
    }

    void createScmAdapter() {
        scmAdapter = findScmAdapter()
    }

    void initScmAdapter() {
        scmAdapter.init()
    }

    void checkCommitNeeded() {
        scmAdapter.checkCommitNeeded()
    }

    void checkUpdateNeeded() {
        scmAdapter.checkUpdateNeeded()
    }

    void checkSnapshotDependencies() {
        def matcher = { Dependency d -> d.version?.contains('SNAPSHOT') }
        def collector = { Dependency d -> "${d.group ?: ''}:${d.name}:${d.version ?: ''}" }

        def message = ""

        project.allprojects.each { project ->
            def snapshotDependencies = [] as Set
            project.configurations.each { cfg ->
                snapshotDependencies += cfg.dependencies?.matching(matcher)?.collect(collector)
            }
            if (snapshotDependencies.size() > 0) {
                message += "\n\t${project.name}: ${snapshotDependencies}"
            }
        }

        if (message) {
            message = "Snapshot dependencies detected: ${message}"
            warnOrThrow(extension.failOnSnapshotDependencies, message)
        }
    }

    void commitTag() {
        def message = extension.tagCommitMessage + " '${tagName()}'."
        if (extension.preCommitText) {
            message = "${extension.preCommitText} ${message}"
        }
        scmAdapter.createReleaseTag(message)
    }

    void confirmReleaseVersion() {
        if (attributes.propertiesFileCreated) {
            return
        }
        updateVersionProperty(getReleaseVersion())
    }

    void unSnapshotVersion() {
        checkPropertiesFile()
        def version = project.version.toString()

        if (version.contains('-SNAPSHOT')) {
            attributes.usesSnapshot = true
            version -= '-SNAPSHOT'
            updateVersionProperty(version)
        }
    }

    void preTagCommit() {
        if (attributes.usesSnapshot || attributes.versionModified || attributes.propertiesFileCreated) {
            // should only be committed if the project was using a snapshot version.
            def message = extension.preTagCommitMessage + " '${tagName()}'."

            if (extension.preCommitText) {
                message = "${extension.preCommitText} ${message}"
            }

            if (attributes.propertiesFileCreated) {
                scmAdapter.add(findPropertiesFile());
            }
            scmAdapter.commit(message)
        }
    }

    void updateVersion() {
        def version = project.version.toString()
        Map patterns = extension.versionPatterns

        for (entry in patterns) {

            String pattern = entry.key
            Closure handler = entry.value
            Matcher matcher = version =~ pattern

            if (matcher.find()) {
                String nextVersion = handler(matcher, project)
                if (attributes.usesSnapshot) {
                    nextVersion += '-SNAPSHOT'
                }

                nextVersion = getNextVersion(nextVersion)
                updateVersionProperty(nextVersion)

                return
            }
        }

        throw new GradleException("Failed to increase version [$version] - unknown pattern")
    }

    String getNextVersion(String candidateVersion) {
        String nextVersion = findProperty('release.newVersion', null, 'newVersion')

        if (useAutomaticVersion()) {
            return nextVersion ?: candidateVersion
        }

        return readLine("Enter the next version (current one released as [${project.version}]):", nextVersion ?: candidateVersion)
    }

    def commitNewVersion() {
        def message = extension.newVersionCommitMessage + " '${tagName()}'."
        if (extension.preCommitText) {
            message = "${extension.preCommitText} ${message}"
        }
        scmAdapter.commit(message)
    }


    def checkPropertiesFile() {
        File propertiesFile = findPropertiesFile()

        if (!propertiesFile.canRead() || !propertiesFile.canWrite()) {
            throw new GradleException("Unable to update version property. Please check file permissions.")
        }

        Properties properties = new Properties()
        propertiesFile.withReader { properties.load(it) }

        assert properties.version, "[$propertiesFile.canonicalPath] contains no 'version' property"
        assert extension.versionPatterns.keySet().any { (properties.version =~ it).find() },
            "[$propertiesFile.canonicalPath] version [$properties.version] doesn't match any of known version patterns: " +
                extension.versionPatterns.keySet()

        // set the project version from the properties file if it was not otherwise specified
        if (!isVersionDefined()) {
            project.version = properties.version
        }
    }

    /**
     * Recursively look for the type of the SCM we are dealing with, if no match is found look in parent directory
     * @param directory the directory to start from
     */
    protected BaseScmAdapter findScmAdapter() {
        BaseScmAdapter adapter
        File projectPath = project.rootProject.projectDir.canonicalFile

        extension.scmAdapters.find {
            assert BaseScmAdapter.isAssignableFrom(it)

            BaseScmAdapter instance = it.getConstructor(Project.class, Map.class).newInstance(project, attributes)
            if (instance.isSupported(projectPath)) {
                adapter = instance
                return true
            }

            return false
        }

        if (adapter == null) {
            throw new GradleException(
                "No supported Adapter could be found. Are [${ projectPath }] or its parents are valid scm directories?")
        }

        adapter
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy