The Java Software Model

by Cédric Champeau (@CedricChampeau)

Who am I

speaker {
    name 'Cédric Champeau'
    company 'Gradle Inc'
    oss 'Apache Groovy committer',
    successes (['Static type checker',
                    'Static compilation',
                    'Traits',
                    'Markup template engine',
                    'DSLs'])
        failures Stream.of(bugs),
        twitter '@CedricChampeau',
        github 'melix',
        extraDescription '''Groovy in Action 2 co-author
Misc OSS contribs (Gradle plugins, deck2pdf, jlangdetect, ...)'''
}
GradleLogoReg

Groovy in Action 2

koenig2

Coupon ctwgr8conftw

Why Gradle

Disclaimer

Current model

  • Convention based

apply plugin: 'application'
allprojects {
   apply plugin: 'java'
}
  • Coupled

    • Determining which tasks need to be executed is easy

    • Determining which tasks need to be configured is hard

    • Configuring properly is hard (afterEvaluate…​)

Why a new model

  • Polyglot programming

  • Multiple variants

  • Scalability

  • Continuous delivery

  • Speed

Example: modelling a Java library

Apply the Java software model plugins

plugins {
    id 'jvm-component'
    id 'java-lang'
}

Declare a library

model {
    components {
        main(JvmLibrarySpec)
    }
}

The rule engine

  • New way to write plugins

  • New way to configure builds

  • But likely to change soon

  • More in the variant-aware dependency management talk

Modelling a Java application

Application with a dependent library

model {
    components {
        app(JvmLibrarySpec)
	myLib(JvmLibrarySpec)
    }
}

Project layout

src
 |-- app
 |    |-- java
 |-- myLib
      |-- java

Declare the dependency onto the library

model {
    components {
        app(JvmLibrarySpec) {
            sources {
                java {
                    dependencies {
                        library 'myLib'
                    }
                }
            }
        }
	myLib(JvmLibrarySpec)
    }
}

Add a dependency onto an external library

model {
    components {
        app(JvmLibrarySpec) {
            sources {
                java {
                    dependencies {
                        library 'myLib'
                        module 'org.ow2.asm:asm:5.0.4'
                    }
                }
            }
        }
	myLib(JvmLibrarySpec)
    }
}

Component level dependencies

model {
    components {
        app(JvmLibrarySpec) {
            dependencies {
                library 'myLib'
                module 'org.ow2.asm:asm:5.0.4'
            }
        }
	myLib(JvmLibrarySpec)
    }
}

API vs implementation

model {
   components {
       myLib(JvmLibrarySpec) {
           api {
              exports 'com.acme.mylib' // not recursive!
           }
       }
   }
}

In action

API vs implementation benefits

  • Strong encapsulation

    • No more private API leakage

    • Prepare for Jigsaw today!

  • Compile avoidance

    • If private API changes

    • If public API changes in ABI compatible way

API dependencies

model {
   components {
       myLib(JvmLibrarySpec) {
           api {
              dependencies {
                 library 'com.google.guava:guava:17.0'
              }
           }
       }
   }
}

Target platforms

Declaring target platforms

model {
   components {
       myLib(JvmLibrarySpec) {
           targetPlatform 'java7'
           targetPlatform 'java8'
       }
   }
}

Binaries

  • myLib.jar for Java 7

  • myLib.jar for Java 8

  • Each one can have its own source sets/dependencies.

Variant aware

Testing

JUnit

  • So far only junit is supported

  • Adding more frameworks should be easy

plugins {
    id 'jvm-component'
    id 'java-lang'
    id 'junit-test-suite'
}

Declaring a test suite

model {
    components {
        main(JvmLibrarySpec)
    }
    testSuites {
        test(JUnitTestSuiteSpec) {
            jUnitVersion '4.12'
            testing $.components.main
        }
    }
}

Test suites

  • Have JUnit version specified as a first-class model element

  • Could support multiple target versions of JUnit

  • Have their own target platforms

  • Target a specific component

Future work

Toolchain support

Declaring JDKs

model {
    javaInstallations {
        openJdk6(LocalJava) {
            path '/usr/lib/jvm/jdk1.6.0-amd64'
        }
        oracleJre7(LocalJava) {
            path '/usr/lib/jvm/jre1.7.0'
        }
        ibmJdk8(LocalJava) {
            path '/usr/lib/jvm/jdk1.8.0'
        }
    }
}

Using JDKs

  • Automatic detection

  • Automatic selection of toolchain

  • Test on various platforms

  • …​

More models!

model {
    myPlugin(GradlePlugin) {
        targetGradleVersions '2.14', '3.0'
        // ...
    }
}

More models!

model {
    microservice(SpringBootApplication) {
        springBootVersion '1.3.5'
        // ...
    }
}

More models!

model {
    dockerImage(DockerImage) {
        from 'alpine:3.2'
        // ...
    }
}

But…​

We’re hiring!

GradleLogoLarge

Thank you!

Attributions

Extra time: The rule engine

Managed types

@Managed
interface ImageComponent extends LibrarySpec {
    String getTitle()
    void setTitle(String title)
    List<String> getSizes()
    void setSizes(List<String> sizes)
}

Rules

class MyImageRenderingPlugin extends RuleSource {
    @ComponentType
    void registerComponent(TypeBuilder<ImageComponent> builder) {
    }

    @ComponentType
    void registerBinary(TypeBuilder<ImageBinary> builder) {
    }
...

Binaries

@Managed
interface ImageBinary extends BinarySpec {
    String getTitle()
    void setTitle(String title)
    String getSize()
    void setSize(String size)
}

Creating binaries

    @ComponentBinaries
    void createBinariesForBinaryComponent(ModelMap<ImageBinary> binaries, ImageComponent library) {
        library.sizes.each { fontSize ->
            binaries.create(fontSize) {
                size = fontSize;
                title = library.title
            }
        }
    }

    @BinaryTasks
    void createRenderingTasks(ModelMap<Task> tasks, ImageBinary binary) {
        tasks.create(binary.tasks.taskName("render", "svg"), RenderSvg){
            it.content = binary.title;
            it.fontSize = binary.size;
            it.outputFile = new File(it.project.buildDir, "renderedSvg/${binary.title}_${binary.size}.svg")
        }
    }

Generic rules

        @Mutate
        public void registerPlatformResolver(PlatformResolvers platformResolvers) {
            platformResolvers.register(new JavaPlatformResolver());
        }

        @Model
        JavaInstallationProbe javaInstallationProbe(ServiceRegistry serviceRegistry) {
            return serviceRegistry.get(JavaInstallationProbe.class);
        }