How to enable all optional dependencies for tests

I have a complex Gradle project with some sub-projects having optional dependencies which are normally not used; I guess this can be modeled by features/capabilities. Among this sub-projects there is a ‘main’ sub-project which has dependencies on other sub-projects. This ‘main’ sub-project has test code which needs optional dependencies to be present in runtime.

How can I enable optional dependencies for ALL sub-projects used in ‘main’, but only for test code? Preferably without copy/pasting all ‘main’ dependencies twice - once for main code and one more time for test code with enabled capability.

I’m not sure exactly what you’re doing but I think you can do this via an additional configuration in the subprojects.

Eg:

project(':sub') {
   configurations {
      optional
   }
   dependencies {
      compile 'foo:required:1.0' 
      optional 'foo:optional:1.0'
   }
} 
project(':main') {
   dependencies {
      compile project(':sub') 
      testRuntime project(path: ':sub', configuration: 'optional') 
   } 
} 

See DependencyHandler docs

By default, when you declare dependency to projectA, you actually declare dependency to the ‘default’ configuration of the projectA. If you need to depend on a specific configuration of projectA, use map notation for projects: configurationName project(path: ':projectA', configuration: 'someOtherConfiguration')

Yes, your code does what I want, but it does not scale well to larger project. For example:

subprojects {
   apply plugin: 'java-library'

   configurations {
      optional
   }
}
project(':sub1') {
   dependencies {
      optional 'org.postgresql:postgresql:42.2.9'
   }
} 
project(':sub2') {
   dependencies {
      implementation project(':sub1')
   }
} 
project(':sub3') {
   dependencies {
      optional 'commons-lang:commons-lang:2.4'
   }
} 
project(':sub4') {
   dependencies {
      implementation project(':sub3')
   }
} 
project(':main') {
   dependencies {
      implementation project(':sub2')
      implementation project(':sub4')
      // ???
   } 
} 

You’ll need to break it down for me what you want to achieve and what you mean by “it doesn’t scale”

I. Transitive dependencies do not work “out of the box” - in my example ‘:main’ depends on ‘:sub2’, which depends on ‘:sub1’, which has an optional dependency. If I simply add

testRuntime project(path: ':sub2', configuration: 'optional') 

then optional dependencies from ‘:sub1’ won’t be present.

II. In the example ‘:main’ depends on both ‘:sub2’ and ‘:sub4’, and I don’t want to repeat all these dependencies for testRuntime with optional configuration (and in a real project there are many dependencies). I want some simple switch to enable all optional dependencies at once.

You’d need to join the optional configurations too.
Eg

project(':sub1') {
   dependencies {
      optional 'org.postgresql:postgresql:42.2.9'
   }
} 
project(':sub2') {
   dependencies {
      implementation project(':sub1')
      optional project(path: ':sub1', configuration:'optional') 
   }
} 
project(':main') {
   dependencies {
      implementation project(':sub2')
      testRuntime project(path: ':sub2', configuration:'optional') 
   } 
} 

Unfortunately my project has more than 100 sub-projects, so it’s quite inconvenient to rewrite all these gradle files this way.

Since Gradle is highly programmable, there should be some trick to do it automatically.

You could probably do it via

subprojects {
   apply plugin: 'java-library'

   configurations {
      optional
   }
   configurations.implementation.incoming.dependencies.all {
      if (it instanceof ProjectDependency) {
          // wire the optional configurations together 
      } 
   } 
} 

I solved my problem using plugins, variants and dependency substitution:

It’s not the simplest solution, but leaves declarative parts (build.gradles) quite clear, and works transparently for any transitive dependency depth.