researchgate / gradle-release

gradle-release is a plugin for providing a Maven-like release process for projects using Gradle
MIT License
868 stars 220 forks source link

Allow overriding of the 'build' start parameter to make integrating with lifecycle tasks easier. #28

Closed ghost closed 11 years ago

ghost commented 11 years ago

I would like to configure the plugin to use my own lifecycle task(s) instead of the default build task. I imagine something like this:

release {
    lifecycleTasks = [ 'releaseBuild', 'anotherTaskMaybeEmailSomeone' ]
}

I'm including part of my build script inline to show how I've set up a few lifecycle tasks and integrated them with the plugin. It seems to work quite well, but I just finished writing it, so it shouldn't be taken as an example of how to structure a build.

def majorJavaVersion = '1.7'
def exactJavaVersion = '1.7.0_17'
def runningJavaVersion = System.getProperty("java.version")

def buildTimestamp = new Date()
def buildLabelFormat = new SimpleDateFormat("yyMMddHHmmssZ")
def buildLabel = buildLabelFormat.format(buildTimestamp)

apply plugin: 'release'

release {
    // this needs to be changed if using a release branch
    requireBranch = 'master'
}

allprojects {
    ext.buildTime = buildTimestamp
}

// application updates are going to depend on the type of build being done.
// release builds belong to the stable update channel and preview builds
// belong to the preview update channel.  installers are customized based
// on the update channel set up here.
def updateChannels = [':previewBuild': 'preview', ':releaseBuild': 'stable']

gradle.taskGraph.whenReady { graph ->
    def updateChannel = 'dev' // catchall default

    updateChannels.keySet().each {
        if (graph.hasTask(it)) {
            updateChannel = updateChannels.get(it)
        }
    }

    allprojects { ext.updateChannel = updateChannel }
}

task build {
    // prevent the use of 'build' as a start parameter to give us more fine grained
    // control over the build
    gradle.taskGraph.whenReady { graph ->
        assert (!graph.hasTask(it)): "Cannot invoke 'build' directly.  Try 'devBuild'."
    }
}

task release(overwrite: true) {
    // override the release task so we can plug in the releaseBuild lifecycle task
    // instead of using 'build'
    gradle.taskGraph.whenReady { graph ->
        if(graph.hasTask(it)) {
            def releaseStartParam = gradle.startParameter.newInstance()
            releaseStartParam.setTaskNames([
                    'initScmPlugin',
                    'checkCommitNeeded',
                    'checkUpdateNeeded',
                    'unSnapshotVersion',
                    'confirmReleaseVersion',
                    'checkSnapshotDependencies',
                    'releaseBuild',
                    'preTagCommit',
                    'createReleaseTag',
                    'updateVersion',
                    'commitNewVersion'
            ])
            GradleLauncher.newInstance(releaseStartParam).run().rethrowFailure()
        }
    }
}

task updateBuildLabel << {
    allprojects {
        ext.buildTime = buildTimestamp.toString()
        ext.buildLabel =
            project.version.contains("SNAPSHOT") ? buildLabel : project.version
    }

    println "##teamcity[buildNumber '${project.ext.buildLabel}']"
}

task checkDevEnv << {
    // make sure the env is good enough to perform dev builds
    assert(runningJavaVersion.startsWith(majorJavaVersion))
}

task checkReleaseEnv << {
    // fail if the environment is wrong or if a release build was triggered without
    // using the 'release' task (the version will be unSnapshotted by the time this
    // task runs if the 'release' task was used to start the build).
    assert(runningJavaVersion.equals(exactJavaVersion))
    assert(!project.version.contains('SNAPSHOT'))
}

task devBuild {
    // some env checks, create a timestamp or release version based label, and
    // build the most dependent sub-project (an installer).  this does not run
    // any tests.
    dependsOn checkDevEnv, updateBuildLabel, ':itma-standalone-installer:i4jBuild'
}

task ciBuild {
    // an alias for the ci server
    dependsOn devBuild
}

task nightlyBuild {
    // it would be nicer to add a buildNeeded task to :itma-standalone-installer, but
    // i don't know how without using the java plugin.  this adds tests to all java
    // projects that :itma-standalone depends on.
    dependsOn ciBuild, ':itma-standalone:buildNeeded'
}

task previewBuild {
    // only start uploading installers automatically for builds that are preview
    // or better
    dependsOn nightlyBuild, ':itma-standalone-installer:i4jUpload'
}

task releaseBuild {
    // the same as preview, but with some extra env checks
    dependsOn checkReleaseEnv, previewBuild
}

// make sure no one accidentally adds a task named releaseBuild to any subprojects
subprojects {
    afterEvaluate { 
        assert(it.getTasksByName(releaseBuild.name, true).isEmpty())
    }
}
townsfolk commented 11 years ago

I tried to do something like this when I first created the plugin. I actually wanted to provide common tasks for dealing with SCM and doing releases, but allow users to be able to define their own release process by setting the tasks in the ReleaseConvention. I wasn't able to accomplish this because the ReleaseConvention wasn't configured before the ReleaseTask was, and thus the user's values are never seen. Do you know if there are any draw backs to using the GradleLauncher as you have? I seem to remember the Gradle folks discouraging it's use and using the GradleBuild Task type instead. Does kicking off a new GradleLauncher as you have forward the output (and input) to the current console?

ghost commented 11 years ago

I'm not sure about the GradleLauncher. Even the API page warns that it's half deprecated, so you're right that it can't be relied on. Regardless, what I posted is definitely NOT the way to do what I actually want. I didn't notice the GradleBuild type on the release task until just now. That whole ridiculous overridden task setup I posted can be replaced by this:

release.tasks.eachWithIndex { it, i ->
    if (it.equals('build')) {
        release.tasks[i] = 'releaseBuild'
    }
}

I wouldn't be surprised if there's some other shorter syntax, but that's the best I could come up with for now. I don't know enough about the structure of plugins to make any suggestions, but, as long as having build as the main lifecycle task in the plugin isn't going to change, the above works perfectly fine for me.

I also noticed that if a build fails between confirmReleaseVersion and preTagCommit the gradle.properties file will contain the unSnapshotted (release) version number and I have to fix it by hand.

townsfolk commented 11 years ago

Ok cool. I think I'll add your work-a-round to the readme file so others can use it. I don't think the build task will change - at least not anytime soon, since it's the default task for the standard plugins (java, groovy, etc..).

As for the other question, please create a separate ticket and describe the situation where the release fails between those two steps so I can recreate it. I've tried my best to revert the gradle.properties file should anything fail, but I'm sure there are holes.