How can I programmatically add transitive dependencies in Gradle?

Viewed 711

I'm pretty (very) new to Gradle and I am evaluating the potential benefits of switching from SBT to Gradle in a Scala project at my current employer. As such I'm not looking to convert an entire build to Gradle right away but it seems it should be possible to dynamically base the Gradle build (atm, primarily compiler flags and dependencies with global versions). This way I don't unnecessarily increase the cognitive load on my colleagues but at the same time I don't run the risk of my build lagging behind or conflicting with the "canonical" configuration in the pom-files.

This is my current build.gradle (so far I have only started to tackle dependencies):

def dependencyVersions = [:]
new XmlSlurper().parse('pom.xml').dependencyManagement.dependencies.dependency.each {
    dependencyVersions["${it.groupId}:${it.artifactId}"] = it.version.text()
}

allprojects {
    group = 'my.org'
    version = 'latest-SNAPSHOT'
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'scala'

    repositories {
        mavenLocal()
        maven {
            url = uri('https://repo.maven.apache.org/maven2')
        }
    }

    dependencies {
        implementation 'org.scala-lang:scala-library:2.13.3'

        // ... Global deps ...

        new XmlSlurper().parse("$projectDir/pom.xml").dependencies.dependency.each {
            if(it.groupId.text() == 'my.org') {
                add('implementation', project(":${it.artifactId}"))
            } else {
                def version = it.version.text() ? it.version.text() : dependencyVersions["${it.groupId}:${it.artifactId}"]
                def dep = "${it.groupId}:${it.artifactId}:${version}"
                def scope = it.scope.text() ? it.scope.text() : 'compile'
                if(scope == 'compile')
                    add('implementation', dep)
                else if(scope == 'test') {
                    add('testImplementation', dep)
                } else {
                    throw new Exception("Unrecognized dependency scope: $scope")
                }
            }
        }
    }

    sourceCompatibility = '1.8'

    tasks.withType(JavaCompile) {
        options.encoding = 'UTF-8'
    }
}

The above almost works. Direct dependencies are added as they should be but the problem is that transitive dependencies are not available at compile-time. How can I configure the subproject such that any transitive dependencies are resolved and added to the subprojects as well?

1 Answers

If you are intending for a module/subproject to be consumed as a dependency or library from another subproject/project, then you should use the Java Library plugin instead of the Java plugin

With the Java Library plugin, you will have access to the api configuration. You can read more about implementation vs api in API and implementation separation docs.

So your Gradle file could be:

dependencies {
    implementation 'org.scala-lang:scala-library:2.13.3'

    // ... Global deps ...

    new XmlSlurper().parse("$projectDir/pom.xml").dependencies.dependency.each {
        if(it.groupId.text() == 'my.org') {
            add('api', project(":${it.artifactId}"))
        } else {
            def version = it.version.text() ? it.version.text() : dependencyVersions["${it.groupId}:${it.artifactId}"]
            def dep = "${it.groupId}:${it.artifactId}:${version}"
            def scope = it.scope.text() ? it.scope.text() : 'compile'
            if(scope == 'compile')
                add('api', dep)
            else if(scope == 'test') {
                add('testImplementation', dep)
            } else {
                throw new Exception("Unrecognized dependency scope: $scope")
            }
        }
    }
}

The only thing different here is switching from implementation to api.

Related