- Chapter 70. Building Java Libraries
- 70.1. Features
- 70.2. Java Software Model
- 70.3. Usage
- 70.4. Creating a library
- 70.5. Source Sets
- 70.6. Tasks
- 70.7. Finding out more about your project
- 70.8. Dependencies
- 70.8.1. Library usage
- 70.8.2. Dependency types
- 70.8.3. Declaring dependencies
- 70.9. Defining a Library API
- 70.9.1. Some terminology
Chapter 70. Building Java Libraries
Support for building Java libraries using the software model is currently incubating. Please be aware that the DSL, APIs and other configuration may change in later Gradle versions.
The Java software plugins are intended to replace the Java plugin, and leverage the Gradle software model to achieve the best performance, improved expressiveness and support for variant-aware dependency management.
70.1. Features
The Java software plugins provide:
- Support for building Java libraries and other components that run on the JVM.
- Support for several source languages.
- Support for building different variants of the same software, for different Java versions, or for any purpose.
- Build time definition and enforcement of Java library API.
- Compile avoidance.
- Dependency management between Java software components.
70.2. Java Software Model
The Java software plugins provide a software model that describes Java based software and how it should be built. This Java software model extends the base Gradle software model, to add support for building JVM libraries. A JVM library is a kind of library that is built for and runs on the JVM. It may be built from Java source, or from various other languages. All JVM libraries provide an API of some kind.
70.3. Usage
To use the Java software plugins, include the following in your build script:
Example 70.1. Using the Java software plugins
plugins < id 'jvm-component' id 'java-lang' >
70.4. Creating a library
A library is created by declaring a JvmLibrarySpec under the components element of the model :
Example 70.2. Creating a java library
Output of gradle build
> gradle build :compileMainJarMainJava :processMainJarMainResources :createMainJar :mainApiJar :mainJar :assemble :check UP-TO-DATE :build BUILD SUCCESSFUL
This example creates a library named main , which will implicitly create a JavaSourceSet named java . The conventions of the legacy Java plugin are observed, where Java sources are expected to be found in src/main/java , while resources are expected to be found in src/main/resources .
70.5. Source Sets
Source sets represent logical groupings of source files in a library. A library can define multiple source sets and all sources will be compiled and included in the resulting binaries. When a library is added to a build, the following source sets are added by default.
Table 70.1. Java plugin — default source sets
It is possible to configure an existing source set through the sources container:
Example 70.3. Configuring a source set
components < main < sources < java < // configure the "java" source set > > > >
It is also possible to create an additional source set, using the JavaSourceSet type:
Example 70.4. Creating a new source set
components < main < sources < mySourceSet(JavaSourceSet) < // configure the "mySourceSet" source set > > > >
70.6. Tasks
By default, when the plugins above are applied, no new tasks are added to the build. However, when libraries are defined, conventional tasks are added which build and package each binary of the library.
For each binary of a library, a single lifecycle task is created which executes all tasks associated with building the binary. To build all binaries, the standard build lifecycle task can be used.
Table 70.2. Java plugin — lifecycle tasks
For each source set added to a library, tasks are added to compile or process the source files for each binary.
Table 70.3. Java plugin — source set tasks
Source Set Type | Task name | Type | Description |
JavaSourceSet | compile$$$$ | PlatformJavaCompile | Compiles the sources of a given source set. |
JvmResourceSet | process$$$$ | ProcessResources | Copies the resources in the given source set to the classes output directory. |
For each binary in a library, a packaging task is added to create the jar for that binary.
Table 70.4. Java plugin — packaging tasks
Binary Type | Task name | Depends on | Type | Description |
JvmBinarySpec | create$$ | all PlatformJavaCompile and ProcessResources tasks associated with the binary | Jar | Packages the compiled classes and processed resources of the binary. |
70.7. Finding out more about your project
Gradle provides a report that you can run from the command-line that shows details about the components and binaries that your project produces. To use this report, just run gradle components . Below is an example of running this report for one of the sample projects:
Example 70.5. The components report
Output of gradle components
> gradle components :components ------------------------------------------------------------ Root project ------------------------------------------------------------ JVM library 'main' ------------------ Source sets Java source 'main:java' srcDir: src/main/java Java source 'main:mySourceSet' srcDir: src/main/mySourceSet JVM resources 'main:resources' srcDir: src/main/resources Binaries Jar 'main:jar' build using task: :mainJar target platform: java7 tool chain: JDK 7 (1.7) classes dir: build/classes/main/jar resources dir: build/resources/main/jar API Jar file: build/jars/main/jar/api/main.jar Jar file: build/jars/main/jar/main.jar Note: currently not all plugins register their components, so some components may not be visible here. BUILD SUCCESSFUL Total time: 1 secs
70.8. Dependencies
A component in the Java software model can declare dependencies on other Java libraries. If component main depends on library util , this means that the API of util is required when compiling the sources of main , and the runtime of util is required when running or testing main . The terms ‘API’ and ‘runtime’ are examples of usages of a Java library.
70.8.1. Library usage
The ‘API’ usage of a Java library consists of:
- Artifact(s): the Jar file(s) containing the public classes of that library
- Dependencies: the set of other libraries that are required to compile against that library
When library main is compiled with a dependency on util , the ‘API’ dependencies of ‘util’ are resolved transitively, resulting in the complete set of libraries required to compile. For each of these libraries (including ‘util’), the ‘API’ artifacts will be included in the compile classpath.
Similarly, the ‘runtime’ usage of a Java library consists of artifacts and dependencies. When a Java component is tested or bundled into an application, the runtime usage of any runtime dependencies will be resolved transitively into the set of libraries required at runtime. The runtime artifacts of these libraries will then be included in the testing or runtime classpath.
70.8.2. Dependency types
Two types of Java library dependencies can be declared:
- Dependencies on a library defined in a local Gradle project
- Dependencies on a library published to a Maven repository
Dependencies onto libraries published to an Ivy repository are not yet supported.
70.8.3. Declaring dependencies
Dependencies may be declared for a specific JavaSourceSet , for an entire JvmLibrarySpec or as part of the JvmApiSpec of a component:
Example 70.6. Declaring a dependency onto a library
model < components < server(JvmLibrarySpec) < sources < java < dependencies < library 'core' > > > > core(JvmLibrarySpec) < dependencies < library 'commons' > > commons(JvmLibrarySpec) < api < dependencies < library 'collections' > > > collections(JvmLibrarySpec) > >
Output of gradle serverJar
> gradle serverJar :compileCollectionsJarCollectionsJava :collectionsApiJar :compileCommonsJarCommonsJava :commonsApiJar :compileCoreJarCoreJava :processCoreJarCoreResources :coreApiJar :compileServerJarServerJava :createServerJar :serverApiJar :serverJar BUILD SUCCESSFUL
Dependencies declared for a source set will only be used for compiling that particular source set.
Dependencies declared for a component will be used when compiling all source sets for the component.
Dependencies declared for the component api are used for compiling all source sets for the component, and are also exported as part of the component’s API. See Enforcing API boundaries at compile time for more details.
The previous example declares a dependency for the java source set of the server library onto the core library of the same project. However, it is possible to create a dependency on a library in a different project as well:
Example 70.7. Declaring a dependency onto a project with an explicit library
client(JvmLibrarySpec) < sources < java < dependencies < project ':util' library 'main' > > > >
Output of gradle clientJar
> gradle clientJar :util:compileMainJarMainJava :util:mainApiJar :compileClientJarClientJava :clientApiJar :createClientJar :clientJar BUILD SUCCESSFUL
When the target project defines a single library, the library selector can be omitted altogether:
Example 70.8. Declaring a dependency onto a project with an implicit library
Dependencies onto libraries published to Maven repositories can be declared via module identifiers consisting of a group name , a module name plus an optional version selector :
Example 70.9. Declaring a dependency onto a library published to a Maven repository
verifier(JvmLibrarySpec) < dependencies < module 'asm' group 'org.ow2.asm' version '5.0.4' module 'asm-analysis' group 'org.ow2.asm' > >
Output of gradle verifierJar
> gradle verifierJar :compileVerifierJarVerifierJava :createVerifierJar :verifierApiJar :verifierJar BUILD SUCCESSFUL
A shorthand notation for module identifiers can also be used:
Example 70.10. Declaring a module dependency using shorthand notation
dependencies < module 'org.ow2.asm:asm:5.0.4' module 'org.ow2.asm:asm-analysis' >
Module dependencies will be resolved against the configured repositories as usual:
Example 70.11. Configuring repositories for dependency resolution
The DependencySpecContainer class provides a complete reference of the dependencies DSL.
70.9. Defining a Library API
Every library has an API, which consists of artifacts and dependencies that are required to compile against the library. The library may be explicitly declared for a component, or may be implied based on other component metadata.
By default, all public types of a library are considered to be part of its API. In many cases this is not ideal; a library will contain many public types that intended for internal use within that library. By explicitly declaring an API for a Java library, Gradle can provide compile-time encapsulation of these internal-but-public types. The types to include in a library API are declared at the package level. Packages containing API types are considered to be exported .
By default, dependencies of a library are not considered to be part of its API. By explicitly declaring a dependency as part of the library API, this dependency will then be made available to consumers when compiling. Dependencies declared this way are considered to be exported , and are known as ‘API dependencies’.
JDK 9 will introduce Jigsaw , the reference implementation of the Java Module System . Jigsaw will provide both compile-time and run-time enforcement of API encapsulation.
Gradle anticipates the arrival of JDK 9 and the Java Module System with an approach to specifying and enforcing API encapsulation at compile-time. This allows Gradle users to leverage the many benefits of strong encapsulation, and prepare their software projects for migration to JDK 9.
70.9.1. Some terminology
- An API is a set of classes, interfaces, methods that are exposed to a consumer.
- An API specification is the specification of classes, interfaces or methods that belong to an API, together with the set of dependencies that are part of the API. It can be found in various forms, like module-info.java in Jigsaw, or the api < . >block that Gradle defines as part of those stories. Usually, we can simplify this to a list of packages, called exported packages.
- A runtime jar consists of API classes and non-API classes used at execution time. There can be multiple runtime jars depending on combinations of the variant dimensions: target platform, hardware infrastructure, target application server, .
- API classes are classes of a variant which match the API specification
- Non-API classes are classes of a variant which do not match the API specification.
- A stubbed API class is an API class for which its implementation and non public members have been removed. It is meant to be used when a consumer is going to be compiled against an API.
- An API jar is a collection of API classes. There can be multiple API jars depending on the combinations of variant dimensions.
- A stubbed API jar is a collection of stubbed API classes. There can be multiple stubbed API jars depending on the combinations of variant dimensions.
- An ABI (application binary interface) corresponds to the public signature of an API, that is to say the set of stubbed API classes that it exposes (and their API visible members).
We avoid the use of the term implementation because it is too vague: both API classes and Non-API classes can have an implementation. For example, an API class can be an interface, but also a concrete class. Implementation is an overloaded term in the Java ecosystem, and often refers to a class implementing an interface. This is not the case here: a concrete class can be member of an API, but to compile against an API, you don’t need the implementation of the class: all you need is the signatures.