gradle build tool learning series – 012 task hook to execution diagram

Posted by expl0it on Tue, 28 Dec 2021 17:56:11 +0100

gradle build tool learning series

Sometimes you may want to execute code when a specific lifecycle event occurs. A lifecycle event may occur before, during, or after a construction phase. The lifecycle event that occurs after the execution phase is the completion of the build.

Internal representation of Task execution diagram

During configuration, Gradle determines the order of tasks to be run in the execution phase, and the internal structure representing the internal dependencies shall be constructed as a directed acyclic graph (DAG). Each task in the graph is called a node, and each node is connected through a directed edge.

In general, connections between these nodes are created by declaring dependsOn relationships or by using implicit task dependent intervention mechanisms. It is important that DAG does not have a closed loop, that is, a previously executed task will never be executed again.

Attaching a Task to a Task execution diagram

As shown in the figure below, the so-called task execution diagram actually refers to TaskExecutionGraph Class.


There are two ways to write callback lifecycle events (the whenReady method above)

  • closure
  • It is implemented through the listener interface provided by Gradle API

The build script is extended by calling the whenReady method to register a closure. After the task graph is generated, it will be executed immediately. As shown in the figure below

gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
    if (taskGraph.hasTask(release)) {
        if (!version.release) {
            version.release = true
            ant.propertyfile(file: versionFile) {
                entry(key: 'release', type: 'string', operation: '=', value: 'true')
            }
        }
    }
}

The life cycle hook registered in the above code is called after the task graph is generated. Check whether the branch graph contains the release d task, and then perform the corresponding operation of modifying the file. The following is the completed code

apply plugin: 'war'
ext.versionFile = file('version.properties')

task loadVersion {
    project.version = readVersion()
}

ProjectVersion readVersion() {
    logger.quiet 'Reading the version file.'

    if (!versionFile.exists()) {
        throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
    }

    Properties versionProps = new Properties()

    versionFile.withInputStream { stream ->
        versionProps.load(stream)
    }

    new ProjectVersion(versionProps.major.toInteger(), versionProps.minor.toInteger(), versionProps.release.toBoolean())
}

gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
    if (taskGraph.hasTask(release)) {
        if (!version.release) {
            version.release = true
            ant.propertyfile(file: versionFile) {
                entry(key: 'release', type: 'string', operation: '=', value: 'true')
            }
        }
    }
}

task createDistribution(type: Zip) {
    from war.outputs.files

    from(sourceSets*.allSource) {
        into 'src'
    }

    from(rootDir) {
        include versionFile.name
    }
}

task backupReleaseDistribution(type: Copy) {
    from createDistribution.outputs.files
    into "$buildDir/backup"
}

task release(dependsOn: backupReleaseDistribution) {
	doLast{
		logger.quiet 'Releasing the project...'
	}
}

tasks.addRule("Pattern: increment<Classifier>Version – Increments the project version classifier.") { String taskName ->
    if (taskName.startsWith('increment') && taskName.endsWith('Version')) {
        task(taskName){
			doLast {
				String classifier = (taskName - 'increment' - 'Version').toLowerCase()
				String currentVersion = version.toString()

				switch (classifier) {
					case 'major': ++version.major
						break
					case 'minor': ++version.minor
						break
					default: throw new GradleException("Invalid version type '$classifier. Allowed types: ['Major', 'Minor']")
				}

				String newVersion = version.toString()
				logger.info "Incrementing $classifier project version: $currentVersion -> $newVersion"

				ant.propertyfile(file: versionFile) {
					entry(key: classifier, type: 'int', operation: '+', value: 1)
				}
			}
        }
    }
}

class ProjectVersion {
    Integer major
    Integer minor
    Boolean release

    ProjectVersion(Integer major, Integer minor) {
        this.major = major
        this.minor = minor
        this.release = Boolean.FALSE
    }

    ProjectVersion(Integer major, Integer minor, Boolean release) {
        this(major, minor)
        this.release = release
    }

    @Override
    String toString() {
        "$major.$minor${release ? '' : '-SNAPSHOT'}"
    }
}

In the above code, the execution sequence is ensured through dependsOn, implicit task dependency and hook task execution diagram: createdistribution - > backupreleasedistribution - > release - > closure.

The execution effect is as follows

Topics: Java Apache Gradle