What’s New In Gradle 6.0?

Cédric Champeau - Gradle Inc.

Who am I?

cedric

Cédric Champeau (@CedricChampeau)

Principal Software Engineer at Gradle

Agenda

  1. All things Dependency Management

    1. API and implementation separation

    2. Dependency constraints and platforms

    3. Publishing and the Gradle Module Metadata format

    4. Test fixtures and feature variants

    5. Enrich existing Metadata with Rules

  2. Java, Groovy, and Scala improvements

  3. New features for plugin authors

Today’s Demo Project

overview

API and Implementation Separation

Java/JVM Gradle Core Plugins

Use java-library or application plugin

plugins1

Separating runtime/compile time dependencies

  • 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"

Dependency Constraints and Platforms

Share dependency versions with platforms

Create a separate project and apply java-platform plugin (doc)

plugins2

Use dependency constraints and rich versions

  • 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.")
    }
  }
}

Use Platform in all Projects

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"))
}

Publishing with Gradle Module Metadata

Today’s Demo Project

overview

Gradle Module Metadata

  • Published in addition to pom (or ivy.xml) file (doc)

  • Use maven-publish or ivy-publish (uploadArchives is deprecated) (doc)

plugins3

Javadoc and sources packaging and publishing

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

Gradle Module Metadata

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

Gradle Module Metadata

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" }
          },
          ...

Better project structures and reuse with test fixtures and feature variants

Write test fixtures to share code for testing

  • Apply java-test-fixtures plugin in addition to java-library (doc)

  • Use src/testFixtures/java (or src/testFixtures/<other-jvm-language>)

plugins4

Use test fixtures of other project/component

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")))
}

Split modules by using feature variants

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")
}

Use feature variants of other project/component

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")
    }
  }
}

Enrich existing Metadata with Component Metadata Rules

Component Metadata Rules

  • Works on all metadata (Gradle, pom, ivy) but allows to add everything that can be expressed in Gradle Module Metadata (doc)

  • Fix information that is wrong

    • E.g. remove unused dependency (doc)

  • Add information that is missing

    • Turning classified jars (like -javadoc.jar) into variants (doc)

    • Making variants encoded in versions explicit (like Guava) (doc)

    • Add capabilities to detect conflict (only one can be picked from log4j and log4j-over-slf4j) (doc)

    • …​

Guava Component Metadata Rule

  • 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")
    }
  }
}

Java, Groovy, and Scala toolchain improvements

Combine Core JVM Plugins

plugins5

Support for JDK 13

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
        }
    }
}

Support for JDK 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)
}

Incremental Groovy compilation

  • 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

Scala Zinc compiler update

  • 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

New features for plugin authors

Generate plugin projects with Gradle init task

$ gradle init

Generate plugin projects with Gradle init task

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4]

Generate plugin projects with Gradle init task

Select implementation language:
  1: Groovy
  2: Java
  3: Kotlin
Enter selection (default: Java) [1..3]

Generate plugin projects with Gradle init task

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 improvements

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 enhancements

  • 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() }
    }
}

Variant-aware plugins

  • 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)

Variant-aware plugins

  • Further documentation

    • Variant-aware matching and resolutions (doc)

    • Variant Attributes (doc)

    • Capabilities (doc)

    • Transforming artifacts (doc)

More Resources

Bonus: Where are the variants?

Variants of Components

Declared Dependencies

chapter 1 1

Variants of Components

Resolved Dependencies

chapter 1 2

Test fixture variant selection

chapter 2 1

Feature variant selection

chapter 2 2

Published variants with artifacts

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 } }

Published platform dependency

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" },
      }
    ]
  },

Published platform (BOM)

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" }
      }
      ...

Published variants and platform dependencies

chapter 4 1

Guava Component Metadata Rule

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")
    }
  }
}

Guava with added variants

chapter 4 2

Bonus: Strict version details

Strict version resolution

chapter 1 3

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