my latest implementation of this is based on a ‘projectsEvaluated’ block which does the following
project.gradle.projectsEvaluated {
TreeSet<Project> projects = accumulateProjects(project.rootProject, [] as TreeSet<Project>)
if (projects.last() == project) {
projects.findAll{ it.extensions.findByName('internals') != null }.each { it.internals.resolve(it) }
}
}
protected TreeSet<Project> accumulateProjects(Project project, TreeSet<Project> container) {
container << project
project.subprojects.each { accumulateProjects(it, container) }
container
}
I couldn’t find a simpler way to get the entire set of projects in the build.
The ‘internals’ extension referenced above is an extension that is modelled on ‘DefaultDependencyHandler’, it has a ‘methodMissing’ impl that just records the args that come through so it can replay them through the ‘DependencyHandler’ later.
def calls = []
Object methodMissing(String name, Object args) {
calls << [name, args]
null
}
The ‘resolve’ method is pretty much a cut and paste of how ‘DefaultDependencyHandler’ works. It unpacks the args into either a call to ‘DependencyHandler.add(config, notation, closure)’ or ‘DependencyHandler.add(config, notation)’ while still supporting the existing use case of n individual dependencies declared against a single configuration.
/**
* Resolves each recorded call into either a module dependency or a project dependency based on whether the
* dependency corresponds to a project declared in this build.
* @param project the project to apply the deps to.
*/
void resolve(Project project) {
calls.each {
resolve(valueOf(it[0]), it[1], project)
}
}
/**
* Resolves a single recorded call into either a project dependency or an internal dependency.
* @param confName the conf.
* @param args the args.
* @param project the project to add dependency to.
*/
void resolve(String confName, def args, Project project) {
def conf = project.configurations.findByName(confName)
if (conf == null) {
throw new InvalidProjectException("Unable to resolve dependency [${args}], " +
"configuration ${confName} does not exist")
} else {
Object[] normalizedArgs = GUtil.collectionize(args)
if (normalizedArgs.length == 2 && normalizedArgs[1] instanceof Closure) {
resolveSingle(project, confName, normalizedArgs[0], (Closure) normalizedArgs[1])
} else if (normalizedArgs.length == 1) {
resolveSingle(project, confName, normalizedArgs[0], null)
} else {
normalizedArgs.each { notation ->
resolveSingle(project, confName, notation, null)
}
}
}
}
/**
* Resolves the args down to something to call {@link org.gradle.api.artifacts.dsl.DependencyHandler#add(java.lang.String, java.lang.Object)}
* or {@link org.gradle.api.artifacts.dsl.DependencyHandler#add(java.lang.String, java.lang.Object, groovy.lang.Closure)}.
* @param project the project.
* @param confName the configuration name.
* @param arg descriptor for a module dependency.
* @param configurator an optional closure.
*/
protected void resolveSingle(Project project, String confName, Object arg, Closure configurator) {
final dep = project.dependencies.create(arg)
Project depAsProject = asProject(dep, project.rootProject)
if (depAsProject != null) {
project.dependencies.add(confName, depAsProject, configurator)
} else {
project.dependencies.add(confName, arg, configurator)
}
}
/**
* Resolves the arg into a project dependency.
* @param dependency the external dependency.
* @param project the owning project.
* @return a dependency that corresponds to the supplied arg.
*/
Project asProject(Dependency dependency, Project project) {
if (project.group == dependency.group && project.name == dependency.name) {
project
} else {
def matches = project.subprojects.collect { asProject(dependency, it) }.flatten().findAll { it != null }
if (matches.isEmpty()) {
null
} else {
matches[0]
}
}
}
I think most of the time was spent working out how that ‘dependencies{}’ actually works, plenty of black magic there!