Principal Software Engineer at Gradle
All things Dependency Management
API and implementation separation
Dependency constraints and platforms
Publishing and the Gradle Module Metadata format
Test fixtures and feature variants
Enrich existing Metadata with Rules
Java, Groovy, and Scala improvements
New features for plugin authors
Use java-library
or application
plugin
API and implementation separation (doc)
Deprecated: compile
and runtime
Declare dependencies: api
, implementation
, compileOnly
, runtimeOnly
Resolved dependencies: compileClasspath
or runtimeClasspath
"of each dependency, give me the variant required for compilation"
"of each dependency, give me the variant required to run the application"
Use dependency constraints block
Use rich versions, including strict versions, if required (doc)
dependencies {
constraints {
api("com.google.guava:guava:24.1.1-jre!!")
api("com.google.inject:guice") {
version {
strictly("[4.0, 5.0[")
// require("[4.0, 5.0[")
prefer("4.2.0")
reject("4.2.1")
// rejectAll()
}
because("Only version 4 of Guice has all DI features we need.")
}
}
}
Local project with java-platform
plugin
dependencies {
api(platform(project(":my-platform")))
}
Published platform (BOM can serve as platform)
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.9.8"))
}
Create Javadoc and/or sources jars for a java-library
java {
withJavadocJar()
withSourcesJar()
}
sourcesJar
and javadocJar
run as part of assemble
If maven-publish
or ivy-publish
are applied, jars are published with publish
Publication of the data-0.1
component of the demo project
data/0.1/
├── data-0.1.pom
<!-- do_not_remove: published-with-gradle-metadata -->
├── data-0.1.module
variants [
{ "name": "apiElements", "files": [...], ... }
{ "name": "runtimeElements", "files": [...], ... }
{ "name": "javadocElements", "files": [...], ... }
{ "name": "sourcesElements", "files": [...], ... }
]
├── data-0.1.jar
├── data-0.1-javadoc.jar
├── data-0.1-sources.jar
Publication of the platform-0.1
component of the demo project
platform/0.1/
├── platform-0.1.pom
<!-- do_not_remove: published-with-gradle-metadata -->
├── platform-0.1.module
variants [
{
"dependencyConstraints": [
{ "group": "com.google.guava", "module": "guava",
"version": { "strictly": "24.1.1-jre" }
},
{ "group": "com.google.inject", "module": "guice",
"version": { "requires": "4.2.2" }
},
...
Depend on the test fixture variant of another project/component (doc)
dependencies {
// Use test fixtures of local project
testImplementation(testFixtures(project(":data")))
// Use test fixture of Guava (if it would publish test fixtures)
testImplementation(testFixtures("com.google.guava:guava")))
}
Functionality of java-library
plugin (doc)
val loud: SourceSet by sourceSets.creating
java {
registerFeature("loud") {
// code isolated: separate implementation and dependencies
usingSourceSet(loud)
// code not isolated: use for "optional dependencies"
usingSourceSet(sourceSets.main)
}
}
dependencies {
"loudApi"(project(":data"))
"loudImplementation"("org.apache.commons:commons-lang3")
}
Depend on a feature variant of another project/component (doc)
dependencies {
implementation(project(":hello-java-service")) {
capabilities {
// Use feature variant by requesting the corresponding capability
requireCapability("org.gradle.hello6:hello-java-service-loud")
}
}
implementation("com.google.inject:guice") {
capabilities {
// Select no_aop feature of Guice (not published, added by rule)
requireCapability("com.google.inject:guice-no_aop")
}
}
}
Add additional jdk${version}Api and jdk${version}Runtime variants and
set org.gradle.jvm.version
for different variants (6 or 8)
Gradle uses targetCompatibility
to select the best variant (best jar)
subprojects {
dependencies {
components {
withModule<GuavaRule>("com.google.guava:guava")
}
}
}
Use in one project
java {
sourceCompatibility = JavaVersion.VERSION_13
targetCompatibility = JavaVersion.VERSION_13
}
Or use in all subprojects
subprojects {
plugins.withType<JavaPlugin> {
extensions.configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_13
targetCompatibility = JavaVersion.VERSION_13
}
}
}
Turn on support for Java 13 preview features like text blocks
// for compilation
tasks.withType<JavaCompile> {
options.compilerArgs.add("--enable-preview")
}
// to run tests
tasks.withType<Test> {
jvmArgs = listOf("--enable-preview")
}
// to run an application
application {
applicationDefaultJvmArgs = listOf("--enable-preview")
}
// to generate Javadoc
tasks.withType<Javadoc> {
val javadocOptions = options as CoreJavadocOptions
javadocOptions.addStringOption("source", "13")
javadocOptions.addBooleanOption("-enable-preview", true)
}
Turn on incremental compilation for Groovy compile tasks (doc)
tasks.withType<GroovyCompile> { options.isIncremental = true }
Turn on compilation avoidance between projects in settings.gradle(.kts)
enableFeaturePreview("GROOVY_COMPILATION_AVOIDANCE")
(doc)
Combine groovy
and java-library
plugins
For example to make Groovy an implementation
detail
Gradle now uses org.scala-sbt:zinc
(doc)
Configure Zinc compiler version scala { zincVersion.set("1.3.1") }
Combine scala
and java-library
plugins
For example to make Scala an implementation
detail
$ gradle init
Select type of project to generate:
1: basic
2: application
3: library
4: Gradle plugin
Enter selection (default: basic) [1..4]
Select implementation language:
1: Groovy
2: Java
3: Kotlin
Enter selection (default: Java) [1..3]
Select build script DSL:
1: Groovy
2: Kotlin
Enter selection (default: Groovy) [1..2]
├── build.gradle.kts
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
├── functionalTest
│ └── java
│ └── my
│ └── MyPluginFunctionalTest.java
├── main
│ ├── java
│ │ └── my
│ │ └── MyPlugin.java
│ └── resources
└── test
├── java
│ └── my
│ └── MyPluginTest.java
└── resources
Managed properties: Lazy properties with less boilerplate (doc)
buildSrc/src/main/java/ATask.java
abstract class ATask extends DefaultTask {
abstract Property<String> getInput();
@TaskAction
void doAction() {
System.out.println(input.get());
}
}
build.gradle.kts
tasks.create<ATask>("a") {
input.set("something")
// input.set(provider { ... })
}
Worker API: Execute work in parallel and/or isolation (separate JVM) (doc)
New in Gradle 6: FileSystemOperations
(work with files)
New in Gradle 6: ExecOperations
(execute extra processes)
private final FileSystemOperations fileSystemOperations
@Inject
public ReverseFile(FileSystemOperations fileSystemOperations) {
this.fileSystemOperations = fileSystemOperations
}
@Override
public void execute() {
fileSystemOperations.copy {
from parameters.fileToReverse
into parameters.destinationDir
filter { String line -> line.reverse() }
}
}
The previously shown variant-aware features can be leveraged in plugins
Examples of plugins that make heavy use of it already
Android (will add publishing in 3.6.0
)
Kotlin Native (already publishes Gradle Module Metadata)
Gradle User Manual with more content on dependency management
Recorded Dependency Management webcasts
Regular online intro and advanced trainings - gradle.com/training
Declared Dependencies
Resolved Dependencies
junit-jupiter-api-5.6.0.module (excerpt)
"variants": [
{ "name": "apiElements" ... },
{ "name": "runtimeElements" ... },
{ "name": "javadocElements",
"attributes": {
"org.gradle.category": "documentation",
"org.gradle.docstype": "javadoc",
},
"files": [
{ "url": "junit-jupiter-api-5.6.0-javadoc.jar" ... }
]},
{ "name": "sourcesElements" ... },
build.gradle.kts (consumer example)
val javadoc by configurations.creating {
attributes.attribute(CATEGORY_ATTRIBUTE, objects.named(DOCUMENTATION))
attributes.attribute(DOCS_TYPE_ATTRIBUTE, objects.named(JAVADOC))
}
dependencies { javadoc("org.junit.jupiter:junit-jupiter-api:5.6.0") }
tasks.create("doSomethingWithJavadocs") { doLast { javadoc.files } }
junit-jupiter-api-5.6.0.module (excerpt)
"variants": [
{
"name": "apiElements",
"attributes": { "org.gradle.category": "library", ... },
"dependencies": [
{
"group": "org.junit",
"module": "junit-bom",
"version": { "requires": "5.6.0" },
"attributes": { "org.gradle.category": "platform" },
}
]
},
junit-bom-5.6.0.module (excerpt)
"variants": [
{
"name": "apiElements",
"attributes": { "org.gradle.category": "platform", ... },
"dependencyConstraints": [
{
"group": "org.junit.jupiter",
"module": "junit-jupiter-api",
"version": { "requires": "5.6.0" }
},
{
"group": "org.junit.jupiter",
"module": "junit-jupiter-engine",
"version": { "requires": "5.6.0" }
}
...
val variantVersion = ctx.details.id.version
val version = variantVersion.substring(0, variantVersion.indexOf("-"))
val artifactName = variantVersion.substring(variantVersion.indexOf("-") + 1)
val otherArtifactName = if (artifactName == "android") "jre" else "android"
val jdkVersion = if (artifactName == "android") 6 else 8
val otherJdkVersion = if (artifactName == "android") 8 else 6
ctx.details.allVariants {
attributes {
if (!contains(TARGET_JVM_VERSION_ATTRIBUTE)) {
attribute(TARGET_JVM_VERSION_ATTRIBUTE, jdkVersion)
}
}
withCapabilities {
if (!capabilities.any { it.name == "google-collections" }) {
addCapability("com.google.collections",
"google-collections", variantVersion)
}
}
}
listOf("compile", "runtime").forEach { base ->
ctx.details.addVariant("jdk${otherJdkVersion}${base.capitalize()}", base) {
attributes {
attribute(TARGET_JVM_VERSION_ATTRIBUTE, otherJdkVersion)
}
withFiles {
removeAllFiles()
addFile("guava-$version-$otherArtifactName.jar",
"../$version-$otherArtifactName/guava-$version-$otherArtifactName.jar")
}
}
}
hello-java-service/gradle.kts
defines com.google.guava:guava:24.1.1-android!!
> Cannot find version of 'guava' that satisfies version constraints:
Dependency path 'app' --> 'guice:4.2.2' --> 'guava:25.1-android'
Dependency path 'app' --> 'hello-java-service' --> 'guava:{strictly 24.1.1-android}'
app/gradle.kts
defines com.google.guava:guava:24.1.1-android!!
\--- guice:4.2.2
\--- guava:25.1-android -> 24.1.1-android