Native Conditional Project Substitution

With the great changes for dependency substitution, I noticed the example code for conditionally replacing projects with specified modules when the project does not exist. This is a great feature, but I think that it could be supported natively to avoid having copy/pasted code across every project that wants to support such a feature.

configurations.main.resolutionStrategy.dependencySubstitution {
  // Substitute one project with a module _if_ the project does not exist
  substituteAbsentProject ':core' with module('com.mycompany:core:19.0.0')
}

Relatedly, I think it also makes sense to automate the more advanced catch-all example as well:

configurations.main.resolutionStrategy.dependencySubstitution {
  // Substitute any project using the project's group name and version number
  substituteAbsentProjects usingProjectDetails
  // Substitute any project using any group name and version number
  substituteAbsentProjects withGroup project.group version project.version
  // Substitute any project using the transformation given the project name
  substituteAbsentProjects withTransform {
    // this could get more creative, such as using maps for the version number
    project.group + it + ':' project.version
  }
}

Conceptually, all of these are very simple to apply. However, the implementation is not obvious to me because I do not know how to get ahold of a Project within the DependencySubstitutions code (it’s trivial in the examples because the Closures get to capture it, which is incredibly useful). If someone can point out how to get the Project object, then I can submit a PR with these changes this weekend.

Perhaps of interest, I could also see a different approach appearing where something like OptionalComponentSelector gets created to represent the process. Then it could exist more generically, but I expect it pushes a lot of complexity downstream for modules:

configurations.main.resolutionStrategy.dependencySubstitution {
  // first ComponentSelector gets wrapped with OptionalComponentSelector
  substituteIfAbsent project(':core') with module('com.mycompany:core:19.0.0')
  substituteIfAbsent module('com.mycompany:xyz:19.0.0') with project(':xyz')
}

The only valid reason that I can imagine a module not existing is because it has not been published yet (e.g., a specific version that the subproject represented as a fork with a needed fix). I wanted to avoid that kind of complexity with my first example format, but I do recognize that this is even more flexible; I do think it’s more flexibility than needed though.

1 Like

+1 We have the same problem (building multiple git repos) too and would like something like this in Gradle core too. What we’ve done is to go the other way around - we’ve created a ProjectResolverPlugin that replaces group-artifact-version dependencies with project dependencies if a matching project exists (and also fails the build if the dependency version doesn’t match the project version)