18

Confused by ordering in Gradle. I'm a novice, previously used Ant for builds, having a play with Gradle which may explain some of this

Brief background (in case anyone would be asking "why would you do that"). We ship a Java WebStart app in a war file. Java 7 (-40 onwards) is prompting users about incorrectly formatted jar files with missing elements. I would like to automate a process that gets the war; extracts the jars for JavaWS from 'bin' folder; adds elements to the Manifest Later I'll get to re-signing the jars and re-assembling the war, but for now I just want to get to adding the Manifest entries.

My problem is that I have defined tasks that have dependsOn elements but the tasks seem to run in the wrong order (see gradle file below). What I expect is the tasks to run in sequence: delete dir; get war; unwar the war; add elements to the jar. What I see (from log file) is: elements are added to jar, effectively this creates a new jar, the dir is deleted and the war is unwar'ed so I end up with the original war file contents.

I checked: Gradle Task To Call Other Tasks In Order however, I don't seem able to use mustRunAfter at all, could be related to version of gradle I have, but anyway I don't really want to control order of disparate tasks, I would prefer (in my Ant based thinking) that I can define the order I want by having tasks depend on each other.

Can anyone see the flaw in my build file? NOTE: if I remove the "dependsOn" from the resignclientjars task and run it manually after running the other tasks, it all works fine and I get my jars with the new elements in the Manifest, so I have a workable workaround, but would prefer to know what I'm doing wrong here.

task (deletework, type: Delete) {
    delete 'workYYY'
}

task (getlaganwar, type: Copy, dependsOn: deletework) {
    from "d:/dev/v8-0-5/wars"
    into "workYYY"
    include 'lagan.war'
}

task (unwar, type: Copy, dependsOn: getlaganwar) {
    from zipTree(file('workYYY/lagan.war'))
    into file("workYYY/lagan")
}

task (resignclientjars, type: Copy, dependsOn: unwar) {
//task (resignclientjars, type: Copy) {

//  mustRunAfter unwar

    def workDir = file("workYYY/lagan")
    def binDir = file(new File(workDir, "bin"))

    def collection = files { binDir.listFiles() }
    collection.each { 
        File jarFile = new File(binDir, it.name)
        ant.echo(message: "updating:${jarFile.absolutePath}")
        ant.jar(jarfile: jarFile, update: 'true') {
            manifest {
                attribute(name: 'Implementation-Title', value: 'Lagan Enterprise')
                attribute(name: 'Implementation-Vendor', value: 'Lagan Enterprise')
                attribute(name: 'Implementation-Version', value: 'Lagan Enterprise')
                attribute(name: 'Application-Name', value: 'Lagan Enterprise')
                attribute(name: 'Permissions', value: 'all-permissions')
            }
        }
    }
}

//unwar.mustRunAfter getlaganwar
//getlaganwar.mustRunAfter deletework
//resignclientjars.mustRunAfter getlaganwar

Debug Output contains the following:

[sts] -----------------------------------------------------
[sts] Starting Gradle build for the following tasks: 
[sts]      :resignclientjars
[sts] -----------------------------------------------------

12:06:19.658 [WARN] [org.gradle.api.internal.project.ant.AntLoggingAdapter] [ant:echo] updating:D:\dev\util\java7-clientjars\workYYY\lagan\bin\DebugWinIEBrowser.jar
12:06:19.666 [WARN] [org.gradle.api.internal.project.ant.AntLoggingAdapter] [ant:echo] updating:D:\dev\util\java7-clientjars\workYYY\lagan\bin\DebugWinMSWord.jar
12:06:19.670 [WARN] [org.gradle.api.internal.project.ant.AntLoggingAdapter] [ant:echo] updating:D:\dev\util\java7-clientjars\workYYY\lagan\bin\WinIEBrowser.jar
12:06:19.674 [WARN] [org.gradle.api.internal.project.ant.AntLoggingAdapter] [ant:echo] updating:D:\dev\util\java7-clientjars\workYYY\lagan\bin\WinMSWord.jar

12:06:19.690 [INFO] [org.gradle.execution.TaskNameResolvingBuildConfigurationAction] Selected primary task ':resignclientjars'
12:06:19.692 [DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Waiting to acquire exclusive lock on task artifact state cache (D:\dev\util\java7-clientjars\.gradle\1.5\taskArtifacts).
12:06:19.692 [DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Lock acquired.
12:06:19.692 [INFO] [org.gradle.BuildLogger] Tasks to be executed: [task ':deletework', task ':getlaganwar', task ':unwar', task ':resignclientjars']

12:06:19.693 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Starting to execute task ':deletework'
12:06:19.693 [DEBUG] [org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter] Determining if task ':deletework' is up-to-date
12:06:19.694 [INFO] [org.gradle.api.internal.changedetection.ShortCircuitTaskArtifactStateRepository] Task ':deletework' has not declared any outputs, assuming that it is out-of-date.
12:06:19.694 [DEBUG] [org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter] task ':deletework' is not up-to-date

12:06:19.695 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter] Executing actions for task ':deletework'.
12:06:19.695 [DEBUG] [org.gradle.api.internal.file.copy.DeleteActionImpl] Deleting D:\dev\util\java7-clientjars\workYYY

12:06:19.934 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Finished executing task ':deletework'
12:06:19.934 [LIFECYCLE] [org.gradle.TaskExecutionLogger] :getlaganwar
12:06:19.934 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Starting to execute task ':getlaganwar'
12:06:19.936 [DEBUG] [org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter] Determining if task ':getlaganwar' is up-to-date

12:06:19.942 [INFO] [org.gradle.api.internal.changedetection.DefaultTaskArtifactStateRepository] Executing task ':getlaganwar' due to:
Output file D:\dev\util\java7-clientjars\workYYY for task ':getlaganwar' has changed.
Output file D:\dev\util\java7-clientjars\workYYY\lagan.war has been removed for task ':getlaganwar'.
12:06:19.942 [DEBUG] [org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter] task ':getlaganwar' is not up-to-date

12:06:19.944 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter] Executing actions for task ':getlaganwar'.

12:06:20.564 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Finished executing task ':getlaganwar'
12:06:20.565 [LIFECYCLE] [org.gradle.TaskExecutionLogger] :unwar
12:06:20.565 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Starting to execute task ':unwar'
12:06:20.586 [DEBUG] [org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter] Determining if task ':unwar' is up-to-date
12:06:20.588 [DEBUG] [org.gradle.api.internal.changedetection.DefaultFileCacheListener] Can cache files for ZIP 'D:\dev\util\java7-clientjars\workYYY\lagan.war'
12:06:20.588 [DEBUG] [org.gradle.api.internal.changedetection.DefaultFileCacheListener] Can cache files for file 'D:\dev\util\java7-clientjars\workYYY\lagan'
12:06:24.096 [INFO] [org.gradle.api.internal.changedetection.DefaultTaskArtifactStateRepository] Executing task ':unwar' due to:
Output file D:\dev\util\java7-clientjars\workYYY\lagan for task ':unwar' has changed.

12:06:24.097 [DEBUG] [org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter] task ':unwar' is not up-to-date

12:06:24.100 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter] Executing actions for task ':unwar'.

12:06:27.863 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Finished executing task ':unwar'
12:06:27.863 [LIFECYCLE] [org.gradle.TaskExecutionLogger] :resignclientjars
12:06:27.864 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Starting to execute task ':resignclientjars'
12:06:27.864 [INFO] [org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter] Skipping task ':resignclientjars' as it has no source files.
12:06:27.864 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Finished executing task ':resignclientjars'
12:06:27.864 [LIFECYCLE] [org.gradle.TaskExecutionLogger] :resignclientjars UP-TO-DATE
12:06:27.865 [DEBUG] [org.gradle.execution.taskgraph.DefaultTaskGraphExecuter] Timing: Executing the DAG took 8.173 secs
12:06:27.865 [LIFECYCLE] [org.gradle.BuildResultLogger] 
12:06:27.865 [LIFECYCLE] [org.gradle.BuildResultLogger] BUILD SUCCESSFUL
12:06:27.865 [LIFECYCLE] [org.gradle.BuildResultLogger] 
12:06:27.866 [LIFECYCLE] [org.gradle.BuildResultLogger] Total time: 8.886 secs
Community
  • 1
  • 1
Gary McWilliams
  • 335
  • 3
  • 4
  • 9

6 Answers6

11

The unpredictable dependency ordering in Gradle is really annoying.

Here is workaround for executing the dependent tasks in order:

task dist(type: Zip) {
    def tasks = [clean, jar, test, docs]
    for (int i = 0; i < tasks.size() - 1; i++) {
        tasks[i + 1].mustRunAfter(tasks[i])
    }
    dependsOn(tasks)

    //...other stuff
}

Probably this workaround could be extracted in reusable manner as strictDependsOn()...

Dimitar II
  • 2,299
  • 32
  • 33
10

You're using Gradle 1.5, and mustRunAfter dates from 1.6. The current version is 1.8.

dependsOn doesn't fix any order for the tasks. This has been discussed ad nauseam in various bug reports. The workaround is to use depencencies between indifidual tasks, or mustRunAfter.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
7

The problem with your build script isn't the task dependencies, but that the task definition for resignclientjars is incorrect. It's doing its work in the configuration phase (i.e. for every build invocation whatsoever) instead of the execution phase. A correct task definition would look as follows:

task resignclientjars(dependsOn: unwar) {
    doLast {
       ...
    }
} 

You can read up on configuration phase vs. execution phase in the Gradle User Guide or the Gradle forums.

Peter Niederwieser
  • 121,412
  • 21
  • 324
  • 259
4

For whatever reason gradle does guarantee order for dependsOn, after time they added mustRunAfter, which however you have to chain yourself like a idiot, so here is a utility:

task buildAppRelease() {
    group = "build"
    dependsOn ordered(":allClean", ":allTestReleaseUnitTest", ":app:assembleRelease")
}

def ordered(String... dependencyPaths) {
    def dependencies = dependencyPaths.collect { tasks.getByPath(it) }
    for (int i = 0; i < dependencies.size() - 1; i++) {
        dependencies[i + 1].mustRunAfter(dependencies[i])
    }
    return dependencies
}
urSus
  • 12,492
  • 12
  • 69
  • 89
  • I fear this will cause every :allTestReleaseUnitTest will start :allClean first, even when started directly, not as part of buildAppRelease task... – Honza May 07 '20 at 09:54
  • 2
    I can't believe that Gradle does not respect the order of dependencies. That's completely annoying. – Carl Rosenberger Jun 17 '20 at 18:17
3

I found that it works if I use both dependsOn and mustRunAfter. Here is an example of running two tasks, one (custom registered "importUnicodeFiles" task) that is in "this" project and one (predefined "run" task) that is in a sibling project named ":unicode":

tasks.register("rebuildUnicodeFiles") {
    description = "Force the rebuild of the `./src/main/resources/text` data"
    val make = project(":unicode").tasks["run"]
    val copy = tasks["importUnicodeFiles"]
    dependsOn(make)
    dependsOn(copy)
    copy.mustRunAfter(make)
}
cpurdy
  • 1,177
  • 5
  • 12
1

I have been able to use dependsOn and mustRunAfter as below to define order for the tasks. Note, here I want to run ktlintFormat first then run ktlint.

ktlint.mustRunAfter ktlintFormat
compileKotlin.dependsOn ktlintFormat,ktlint
samaitra
  • 724
  • 7
  • 7