1

In a multiproject build, some projects depend on others, and the latter provide not only compile/runtime libraries, but also useful test libs (such as, for instance, "mock" implementations of the components they provide).

I know of a couple of ways to make the test sources of one project available to another. They are discussed, for instance as answers to this question.

What I am looking for is some magical way to make this happen automatically, so that if a subproject adds a dependency on another subproject, it automatically gets that projects test sources added to the testCompile config.

I tried the naive approach:

configure(rootProject.subprojects.findAll {
  it.configurations.getByName("compile")
    .dependencies.any {  
      it.name == project.name 
    } 
 }) {
  dependencies {
    testCompile project.sourceSets.test.output
  }
}

But this does not work ... presumably, because this code is evaluated "at the wrong stage" (or whatever the correct lingo is), and the other projects don't "know" yet that they depend on this one.

I also tried putting (an equivalent of) this at the end of root build file (hoping, that everything else would already be configured by then), but that did not work either.

Is there a way to do what I am looking for here?

Community
  • 1
  • 1
Dima
  • 39,570
  • 6
  • 44
  • 70
  • shouldn't you be doing `.getByName("test")` and/or `.getByName("testCompile")` ? – RaGe Dec 30 '16 at 19:49
  • Also maybe try sticking your snippet in an `afterEvaluate` block, example here: https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:project_evaluation – RaGe Dec 30 '16 at 19:50
  • No, I wanted `getByName('compile')` here. The idea is if "compile" depends on this project, then "testCompile" needs to depend on test sources. – Dima Dec 30 '16 at 20:47
  • @RaGe So, I tried using your suggestion about using `afterEvaluate`, and almost got it working! One problem I still have is that I defined a closure `exportTests` in main `build.gradle`, and it does what I want if I call it in the same file, like `exportTest(project(':notifications'))`, which is kinda clumsy. I would prefer, invoking it from within `notifications/build.gradle`, like `exportTests(project)`, but that says "export test is not defined", even though the docs seem to suggest that closures defined on the root project should be inherited by children. Any idea? – Dima Dec 31 '16 at 17:21
  • try accessing it in the subproject as `rootProject.closureVar` ? – RaGe Dec 31 '16 at 17:42
  • @RaGe nope :( Same thing: `Could not find method exportTests() for arguments [project ':notifications'] on root project 'server'` – Dima Dec 31 '16 at 18:07
  • are you defining your closure as an `ext.` param? This really is a separate question, would you mind raising a new question? – RaGe Dec 31 '16 at 20:54
  • @RaGe I tried defining it in a bunch of different places without much luck :( Here is my new question: http://stackoverflow.com/questions/41412975/closure-defined-in-root-not-visible-in-child – Dima Jan 01 '17 at 02:31
  • If A depends on B, you're trying to inject a test dependency into A from B? Why not add it from A? – RaGe Jan 01 '17 at 14:56
  • I (think that I ) explained in a comment to your answer: the idea is to reduce the amount of code that needs to be written by the user of B: if A depends on B, we _know_ it needs the test fixtures, so there is no point in having it spell it out explicitly. – Dima Jan 01 '17 at 15:36

1 Answers1

1

I also tried putting (an equivalent of) this at the end of root build file

The order of declaration in a build.gradle does not matter. Gradle build lifecycle has two phases, configuration and execution. In configuration, the various build.gradle files are read and a graph of execution order is created based on implicit and explicit dependencies among the various tasks.

Normally the root build.gradle is evaluated first, but it is possible to force the child projects to be evaluated first using evaluationDependsOnChildren()

Instead of position in the buildscripts, you can listen for various events of the buildcycle, to run something at certain points. In your case, you want to run your snippet once all projects are evaluated, using an afterEvaluate() block. See example here.


Some possible alternatives to your overall approach:

  • Add testCompile dependency from the downstream project instead of injecting from the upstream project. Add this to root build.gradle:

    subprojects {
        project.configurations.getByName("compile").dependencies.each { 
            if (it.name == foo){ // could be expanded to if it.name in collection
                //inherit testCompile
                project.configurations.dependencies.testCompile += it.sourceSets.test.output
            } 
        }
    }
    

    (pseudo-code, haven't tested)

  • Separate out the shareable test/mock components into a separate mock-project that you can add as testCompile dependency to both upstream and downstream projects

RaGe
  • 22,696
  • 11
  • 72
  • 104
  • `afterEvaluate` works, thanks! Hoever, please see my other question here: http://stackoverflow.com/questions/41412975/closure-defined-in-root-not-visible-in-child. – Dima Jan 01 '17 at 02:32
  • Well, yes, I considered the alternatives, but the whole point is to avoid having to write extra code in every dependent project. The idea is to provide a self-contained component, that other modules can use "declaratively" by simply adding a dependency line into their build files. If one wants to use this module, we _know_ he is going to need the test fixtures as well, so we could as well just give them to him without making him spell it out explicitly. – Dima Jan 01 '17 at 15:34
  • Hmm, how about the `gradle.projectsEvaluated` hook? – RaGe Jan 02 '17 at 01:46