4

Background: I have a multi-project Gradle build, and I've defined a Gradle task which runs JavaScript unit tests in an Exec task. The inputs to this task are the JavaScript files in the project, so it's only re-run if one of the source files are modified. The task is added to all JavaScript projects from a master project.

Question: I want to extend this so that the tests are re-run if JavaScript files in the project, or in any of its project dependencies are changed. How is this best done?

The code below works if placed in each subproject build file (after the dependency declaration), but we have 20+ JavaScript subprojects and I'd like to stay DRY.

project.ext.jsSourceFiles = fileTree("src/").include("**/*.js*")

task testJavaScript(type: Exec, dependsOn: configurations.js) {
    inputs.files resolveJavascriptDependenciesFor(project)
    outputs.file "report.xml"

    // Run tests in JSTestDriver using command line call...    
}

def resolveJavascriptDependenciesFor(project) {
    def files = project.jsSourceFiles
    project.configurations.js.allDependencies.each {
        files = files + resolveJavascriptDependenciesFor(it.dependencyProject)
    }
    return files
}

Is there a better solution? Maybe where I don't have to resolve all file dependencies myself?

David Pärsson
  • 6,038
  • 2
  • 37
  • 52

3 Answers3

2

As written in the answer before, adding the jsTest task within a subprojects closure would make it very easy to add jstesting support for every subproject. I think you can ease your inputs setup by declaring source files as dependencies:

dependencies {
     js filetree("src/main").include("**/*.js")
}

and

subprojects { subproj ->
    task testJavaScript(type: Exec, dependsOn: configurations.js) {
        inputs.files subproj.configurations.js
        outputs.file "report.xml"

        commandLine  ...
    }    
}
David Pärsson
  • 6,038
  • 2
  • 37
  • 52
Rene Groeschke
  • 27,999
  • 10
  • 69
  • 78
  • Sorry, I only left out the outputs from my example for the sake of simplicity. The issue rather seems to be that the dependencies are not specified when the inputs are defined. – David Pärsson Oct 02 '12 at 07:52
  • By the way, what difference would it make to specify subproj.configurations.js as inputs? Is it possible to specify sources in a way so that the files from the subprojects dependencies are included? I tried this but didn't see any difference, since I haven't defined anything to tie the JavaScript source files to the js configuration. – David Pärsson Oct 02 '12 at 14:15
  • the inputs of a task are evaluated during the execution phase just before the task would be executed. Therefore i think all dependencies are defined at that evaluation time. You can add local js files to your js configuration by doing something like that: dependencies{ js filetree("src/main").matching{it.endsWith(".js")} } – Rene Groeschke Oct 03 '12 at 18:45
  • Thanks, that did the trick! I've updated your answer with the dependencies information and will accept it. – David Pärsson Oct 04 '12 at 09:26
0

Would it be possible to do something like this?

allprojects {project ->
   task testJavaScript(type: Exec, dependsOn: configurations.js) {
      inputs.files resolveJavascriptDependenciesFor(project)
     // Run tests in JSTestDriver using command line call...    
   }
}

def resolveJavascriptDependenciesFor(project) {
   def files = project.jsSourceFiles
   project.configurations.js.allDependencies.each {
      files = files + resolveJavascriptDependenciesFor(it.dependencyProject)
   }
   return files
}

Tha way the task is on all projects, and wil be called recursively. Not completely sure this works, but I think its the way to go

mathiasbn
  • 881
  • 11
  • 21
  • This might re-run all tests if the target is run from the master project, but it changes nothing regarding re-runs if files in dependencies are changed. – David Pärsson Oct 02 '12 at 12:51
  • The inputs property should contain the stuff that can make the task output change. If the dependencies can should trigger rerun, then they should also be in inputs. Inputs and outputs property is exactly for the incremental build – mathiasbn Oct 02 '12 at 17:51
0

I've found a solution that works but isn't great, using the same dependency specification as in the question. It's to load the gradle files in a slightly different order in the master project.

Master build.gradle:

subprojects {
    configurations {
        js
    }

    // Apply the subproject's build.gradle, but with another name (that isn't automatically loaded)
    if (project.file('depends.gradle').exists()) {
        apply from: project.file('depends.gradle')
    }

    apply from: project.parent.file('javaScriptProjectTasks.gradle')
}

Compare to the previous, non working master build.gradle

subprojects {
    configurations {
        js
    }

    apply from: project.parent.file('javaScriptProjectTasks.gradle')
    // The subproject's build.gradle is automatically loaded
}
David Pärsson
  • 6,038
  • 2
  • 37
  • 52