Slide 1

Slide 1 text

Modernizing the Groovy build Cédric Champeau, Gradle Inc. @CedricChampeau

Slide 2

Slide 2 text

Cédric Champeau Gradle, Inc. Principal Software Engineer JVM Team at Gradle Worked on dependency management, performance, improving Java ecosystem support Former Groovy committer (wrote the static compiler)

Slide 3

Slide 3 text

Me and Apache Groovy Started as a user, submitted some bugfixes Became committer in 2011 Contributed a number of things mostly around DSL and compile-time metaprogramming: - AST transformations - Static type checker and static compilation (@CompileStatic) - Traits - Type checking extensions - Template engine with static compilation of templates

Slide 4

Slide 4 text

Me and Apache Groovy Joined the Gradle team in 2015 Left the Apache Groovy PMC in march 2019 after disagreement on use of Kotlin Contributed build changes… and left around build changes Now back to the build!

Slide 5

Slide 5 text

History of the Groovy build Groovy is an old project (17 years old!) Started with Apache Ant Attempt to migrate to Apache Maven but never finished/too complicated Gradle introduced in 2008 as a parallel build Really usable in 2012 after the Gradle 1.0 release This is where I started to contribute to the build

Slide 6

Slide 6 text

State of the build as of 1st october 2020 A working Gradle build, including build cache, but: ⬢ Written by many different people ⬢ With “conventions” inherited from older build tools (Ant, Maven) ⬢ With very old releases of Gradle ○ Many things were harder to do back then ○ Many outdated patterns ○ Accidental dependencies ○ Lack of guidance ○ ...

Slide 7

Slide 7 text

Complicating factors Groovy is built with Gradle Gradle uses a Groovy DSL (or Kotlin ;)) Groovy provides different flavors of the same thing: - “Classic” Groovy - “Invokedynamic” Groovy - “Grooid” Groovy

Slide 8

Slide 8 text

Apache Groovy 4 is coming!

Slide 9

Slide 9 text

Groovy 4 and Gradle 7 Groovy 4 Changes to dependency (GAV) coordinates: - From org.codehaus.groovy to org.apache.groovy Removal of the classic flavor Gradle 7 Removes the old “maven” plugin in favor of “maven-publish” Ideal to leverage some of the dependency management features introduced in Gradle 6!

Slide 10

Slide 10 text

Let’s take a quick look at the “old” build

Slide 11

Slide 11 text

Idiomatic Gradle

Slide 12

Slide 12 text

Idiomatic Gradle goals Provide clear guidance on best practices Reduce the maintenance burden Improve the learning curve Improve performance Deliver better quality software

Slide 13

Slide 13 text

Declarative build scripts Most build scripts should only consist of: - Applying one or more plugins - Declarative configuration What matters is the model A Java library != a Java application != a Micronaut application != a Groovy library != ...

Slide 14

Slide 14 text

Types of modules in the Groovy build Groovy core The “main” project: it’s a library, but also provides the Groovy compiler itself Groovy modules JSON support, Date utils, Template engines, … Aggregating projects Groovy “all” (which isn’t Groovy all but a subset of Groovy + Groovy modules) The BOM The distribution Utilities (stress tests, …)

Slide 15

Slide 15 text

Example: groovy-dateutil

Slide 16

Slide 16 text

What’s wrong with the previous pattern? ⬢ No plugin applied - hard to tell what this script is about. Hard to figure out where to make modifications. “Magic” going on. ⬢ Explicit task creation - Duplicated code between modules. Hardcoded task name ⬢ Explicit task dependency - and wrong! ⬢ How do you know it’s part of groovy-all?

Slide 17

Slide 17 text

Example 2: stress tests ⬢ Explicit project type: the intent is clear. It’s not a Groovy module, it’s not a publication, it’s a special kind of module ⬢ Distinct test suite: avoids dirty configuration to disable tests when we mostly don’t want to run them

Slide 18

Slide 18 text

Example 3: the BOM ⬢ Explicit project type: it’s a platform ⬢ Dependency declaration still a bit convoluted (better pattern coming)

Slide 19

Slide 19 text

Major changes 01 allprojects { … } → explicit application of a convention plugin in each project 02 Inline tasks → explicit task implementations in buildSrc 03 apply from: “...” → plugins { id … }

Slide 20

Slide 20 text

Example: get rid of “inline tasks”

Slide 21

Slide 21 text

Does it mean I need to publish plugins?!

Slide 22

Slide 22 text

buildSrc to the rescue! The buildSrc directory should be used to put all custom logic: - Custom tasks - Models (extensions) of different module types - Expose logic via plugins And we have precompiled script plugins!

Slide 23

Slide 23 text

Plugins all the way! Gradle uses composition over inheritance Different aspects of a project can be applied by different plugins Plugins can react to the presence of another plugin

Slide 24

Slide 24 text

Precompiled script plugins Those are lightweight plugins It’s a plugin without all the ceremony Most of custom build logic should move to plugins Can be easily composed

Slide 25

Slide 25 text

Example: the Groovy platform plugin buildSrc/main/groovy/org.apache.groovy-platform.gradle

Slide 26

Slide 26 text

Example: the Groovy platform plugin buildSrc/main/groovy/org.apache.groovy-common.gradle

Slide 27

Slide 27 text

Shared configuration (only applied to the root)

Slide 28

Slide 28 text

Project specific configuration

Slide 29

Slide 29 text

Extensions are created by plugins ⬢ Extensions should make the build script readable ⬢ They talk about the what and they abstract the how ⬢ Creates tasks if needed A build script just applies the plugin and configures the extension The extension does the “hard work”

Slide 30

Slide 30 text

Avoid reaching out to other projects directly

Slide 31

Slide 31 text

Strong encapsulation Do not depend on another project task - E.g “dependsOn rootProject.task(“jar”) This is fragile, unsafe and it breaks encapsulation. Was challenging with the Groovy build which abused reaching out to other projects everywhere. https://docs.gradle.org/current/userguide/cross_project_publications.html

Slide 32

Slide 32 text

Task configuration avoidance

Slide 33

Slide 33 text

Configuration avoidance Before After Description tasks.create(“...”) tasks.register To define a new task tasks.findByName(...) tasks.named(...) { … } Configure an existing task by name tasks.withType(Foo).all { … } tasks.withType(Foo) .configureEach { … } Configures a task by type

Slide 34

Slide 34 text

Configuration avoidance: Groovy DSL gotchas

Slide 35

Slide 35 text

The lazy configuration API Use of Provider and Property: - Fixes most of ordering issues during configuration (get rid of afterEvaluate) - Can carry task dependency information (ideal to get rid of accidental dependencies)

Slide 36

Slide 36 text

The lazy configuration API: derived values

Slide 37

Slide 37 text

Gradle has evolved

Slide 38

Slide 38 text

Test fixtures Tests from different projects need common testing utilities Previously, Groovy did this: - Create utility classes in `src/test` (so they belonged to the test sources) - Add the test runtime classpath of the main project to the test compile classpath of modules

Slide 39

Slide 39 text

The java-test-fixtures plugin Creates a separate source set for test fixtures (src/testFixtures/java) Makes it easy to use from other projects

Slide 40

Slide 40 text

Optional dependencies There are no optional dependencies, only dependencies you need when you use a particular feature

Slide 41

Slide 41 text

Optional dependencies Each feature can have its own dependencies

Slide 42

Slide 42 text

Optional dependencies publication Published as optional dependencies for Maven (POM.xml) and feature variants for Gradle No ugly configuration like before!

Slide 43

Slide 43 text

Let’s talk about publishing Publishing was in a terrible shape - Based on the deprecated publication mechanisms (maven plugin) - Used a lot of internal APIs - Made lots of customizations to the generated POM files - Because of relocated dependencies - Because of the multiple flavors

Slide 44

Slide 44 text

Publishing now Uses the standard maven-publish plugin - Got rid of all internal APIs - Publishes Gradle Module Metadata - Running `./gradlew publish` generates a local repository in `build/repo` which allows seeing what is going to be published Gradle Module Metadata benefits: - Users will get an error if they use both the old Groovy (Codehaus) and new Groovy (Apache) - They can depend on optional features and get the dependencies they need automatically - They get automatic version alignment for free!

Slide 45

Slide 45 text

There’s more we can do

Slide 46

Slide 46 text

Future work Use of Gradle Java Toolchain API - Allows compiling with a JDK, running/testing with another in a declarative manner - Automatic download of JDK! - Removes the need for special tweaks in the build depending on what is used to run Gradle itself But… - Requires Gradle 6.8 for Groovy compilation support - Requires changes to the build pipeline (but it makes it simpler!)

Slide 47

Slide 47 text

Future work Make the root project a “real” root project - groovy-core should become a subproject itself - Allows moving some remaining shared configuration to where it belongs Remove old Ant tasks - Some tasks like `jarjar` still depend on very old Ant tasks - May require some extra work from Gradle

Slide 48

Slide 48 text

Thank you! Special thanks to Paul King for the review of all changes, including bugs in introduced in the process!