Gradle
- Basics
- Helpful Gradle commands
- Installation
- Gradle wrapper
- settings.gradle.kts
- Setting java.sourceCompatibility
- implementation vs api vs compileOnly
- Trouble with lombok annotations
- Gradle - Multi-Project Builds
- Declare the desired test framework directly on the test suite or explicitly declare the test framework implementation dependencies on the test’s runtime classpath.
- Issues with refreshing dependencies in Eclipse
- Working with EAR projects
- How to exclude a few tasks from the Gradle build process :
Basics
- https://docs.gradle.org/current/userguide/gradle_basics.html
- https://docs.gradle.org/current/userguide/gradle_basics.html#core_concepts
- https://docs.gradle.org/current/userguide/gradle_basics.html#project_structure
- https://docs.gradle.org/current/userguide/build_file_basics.html
- https://docs.gradle.org/current/userguide/dependency_management_basics.html
Helpful Gradle commands
List all tasks
To look at all the available tasks for a gradle project, use this:
gradlew tasks --all
Publish to maven local
gradlew publishToMavenLocal
Installation
Check if Gradle is installed
gradle -v
Installation
https://docs.gradle.org/current/userguide/installation.html
Use sdkman to install it.
sdk install gradle
Gradle wrapper
- https://docs.gradle.org/current/userguide/gradle_wrapper.html
- https://docs.gradle.org/current/userguide/gradle_wrapper_basics.html
- https://www.baeldung.com/gradle-wrapper
settings.gradle.kts
When to use gradle.properties vs. settings.gradle?
- https://docs.gradle.org/current/userguide/settings_file_basics.html
- https://stackoverflow.com/questions/45387971/when-to-use-gradle-properties-vs-settings-gradle
Setting java.sourceCompatibility
See https://docs.gradle.org/current/userguide/toolchains.html
In my-implementations/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts file
group = "com.my.company"
version = "1.0-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_16
implementation vs api vs compileOnly
What is the difference between these two?
api(libs.org.apache.commons.commons.collections4)
and
implementation(libs.org.apache.commons.commons.collections4)
- https://discuss.gradle.org/t/best-practice-for-api-vs-implementation-in-multi-module-project/30519
- The Java Library Plugin: https://docs.gradle.org/current/userguide/java_library_plugin.html
- https://stackoverflow.com/questions/44413952/gradle-implementation-vs-api-configuration
- https://www.baeldung.com/gradle-implementation-vs-compile
- https://abhiappmobiledeveloper.medium.com/difference-between-implementation-api-compile-and-runtimeonly-in-gradle-dependency-55b70215d245
- https://jeroenmols.com/blog/2017/06/14/androidstudio3/
compileOnly
When we configure a dependency as compileOnly, Gradle only adds it to the compile-time classpath. Gradle doesn’t add it to the build output. Accordingly, Gradle makes the dependency available during compilation, but not at runtime when the program is executed. The compileOnly dependency configuration helps reduce the size of the build output; and as a result, reduces the build time and memory use.
examples
You have a library that uses the SLF4J:
compileOnly org.slf4j:slf4j-api:1.7.30
slf4j, lombok, junit for testCompileOnly, etc.
Gradle adds the dependency to the compilation classpath only (it is not added to the build output). This is useful when you’re creating an Android library module and you need the dependency during compilation, but it’s optional to have present at runtime. That is, if you use this configuration, then your library module must include a runtime condition to check whether the dependency is available, and then gracefully change its behavior so it can still function if it’s not provided. This helps reduce the size of the final APK by not adding transient dependencies that aren’t critical. This configuration behaves just like provided (which is now deprecated).
implementation
When we configure a dependency as implementation, Gradle adds it to both the compile-time and runtime classpaths. This means Gradle makes the dependency available during compile-time and also at runtime when the program is executed. Gradle packages the dependency to the build output. However, the implementation dependency configuration increases the size of the build output, which in turn increases the build time and accompanying memory use, as compared to the compile-only configuration.
When your module configures an implementation dependency, it’s letting Gradle know that the module does not want to leak the dependency to other modules at compile time. That is, the dependency is available to other modules only at runtime. Using this dependency configuration instead of api or compile can result in significant build time improvements because it reduces the amount of projects that the build system needs to recompile. For example, if an implementation dependency changes its API, Gradle recompiles only that dependency and the modules that directly depend on it. Most app and test modules should use this configuration.
api
When we configure a dependency as api, Gradle adds it to the compile-time and runtime classpaths and includes it in the published API. This means Gradle makes the dependency available when the program compiles, when the program runs, and when dependent module programs compile. Gradle packages the dependency to the build output. Furthermore, Gradle includes the dependency in the published API. The api dependency configuration increases the build time and accompanying memory use, as compared to the implementation configuration.
When a module includes an api dependency, it’s letting Gradle know that the module wants to transitively export that dependency to other modules, so that it’s available to them at both runtime and compile time. This configuration behaves just like compile (which is now deprecated), and you should typically use this only in library modules. That’s because, if an api dependency changes its external API, Gradle recompiles all modules that have access to that dependency at compile time. So, having a large number of api dependencies can significantly increase build times. Unless you want to expose a dependency’s API to a separate test module, app modules should instead use implementation dependencies.
Difference Between implementation and api
The main difference between implementation and api in Gradle is that implementation doesn’t transitively export the dependency to other modules that depend on this module. In contrast, api transitively exports the dependency to other modules. This means that dependencies configured with api are available to other modules, which depend on this module, both at runtime and compile time. However, dependencies configured with implementation are available to other modules, which depend on this module only at runtime.
Therefore, we should use the api configuration with caution because it increases the build time significantly over the implementation configuration. As an example, if an api dependency changes its external API, Gradle recompiles all modules that have access to that dependency, even at compile time. In contrast, if an implementation dependency changes its external API, Gradle doesn’t recompile all modules even when the software in the modules is not running because the dependent modules don’t have access to that dependency at compile time.
runtimeonly
Gradle adds the dependency to the build output only, for use during runtime. That is, it is not added to the compile classpath. This configuration behaves just like apk (which is now deprecated).
When we configure a dependency as runtimeOnly, Gradle includes it in the build output for runtime, although it doesn’t use it at compile time.
Runtimeonly dependencies are available at runtime but not at compile-time.
For example, you don’t need the concrete SLF4J logger(e.g. logback) at compile time (as you use the SLF4J classes in order to access it) but you need it at runtime as you want to use it.
Let’s look at the following example:
You have a library that uses the SLF4J:
compileOnly org.slf4j:slf4j-api:1.7.30
and you could have a project using the library:
implementation project(":yourlibrary")
implementation org.slf4j:slf4j-api:2.0.0-alpha1
runtimeOnly ch.qos.logback:logback:0.5
compileOnlyApi
When we configure a dependency as compileOnlyApi, Gradle uses it for compilation only, just like compileOnly. Additionally, Gradle includes it in the published API.
Best Practices for Gradle Dependency Management
To ensure effective dependency management in Gradle, we should consider a few best practices:
- Use the implementation configuration by default
- Use the compileOnly configuration when you don’t want to package the dependency in the build output. An example use case is a software library that only includes compile-time annotations, which are typically used to generate code but are not needed at runtime
- Avoid using the api configuration, as it can lead to longer build times and increased memory usage
- Use specific versions of dependencies instead of dynamic versioning to ensure consistent behavior across builds
- Keep the dependency graph as small as possible to reduce complexity and improve build times
- Regularly check for updates to dependencies and update them as necessary to ensure that the project uses the latest and most secure versions
- Use dependency locking to ensure that builds are reproducible and consistent across different machines and environments
Example
understand Problem situation Suppose you have a library called MyLibrary that internally uses another library called InternalLibrary. Something like this:
‘InternalLibrary’ module
public class InternalLibrary {
public static String giveMeAString(){
return "hello";
}
}
‘MyLibrary’ module
public class MyLibrary {
public String myString(){
return InternalLibrary.giveMeAString();
}
}
Suppose the MyLibrary build.gradle uses api configuration in dependencies{} like this:
dependencies {
api project(':InternalLibrary')
}
You want to use MyLibrary in your code so in your app’s build.gradle you add this dependency:
dependencies {
implementation project(':MyLibrary')
}
Using the api configuration (or deprecated compile) you can access InternalLibrary in your application code:
// Access 'MyLibrary' (granted)
MyLibrary myLib = new MyLibrary();
System.out.println(myLib.myString());
// Can ALSO access the internal library too (but you shouldn't)
System.out.println(InternalLibrary.giveMeAString());
In this way the module MyLibrary is potentially “leaking” the internal implementation of something. You shouldn’t (be able to) use that because it’s not directly imported by you.
The implementation configuration was introduced to prevent this. So now if you use implementation instead of api in MyLibrary:
dependencies {
implementation project(':InternalLibrary')
}
you won’t be able to call InternalLibrary.giveMeAString() in your app code anymore.
This sort of boxing strategy allows Android Gradle plugin to know that if you edit something in InternalLibrary, it must only trigger the recompilation of MyLibrary and not the recompilation of your entire app, because you don’t have access to InternalLibrary.
When you have a lot of nested dependencies this mechanism can speed up the build a lot.
Trouble with lombok annotations
Example error
my-java-solutions/src/main/java/com/my/company/datastructures/maps/CombiningTwoMaps.java:19: error: invalid method reference
Map<String, List<Person>> existingMap = people.stream().collect(Collectors.groupingBy(Person::getGender));
^
cannot find symbol
symbol: method getGender()
location: class Person
https://stackoverflow.com/questions/35236104/gradle-build-fails-on-lombok-annotated-classes
For older versions of Gradle, annotationProcessor might help:
dependencies{
compileOnly 'org.projectlombok:lombok:1.18.8'
annotationProcessor 'org.projectlombok:lombok:1.18.8'
}
For newer versions
Use plugin https://plugins.gradle.org/plugin/io.freefair.lombok
Then we don’t need compileOnly
or annotationProcessor
In my-java-solutions/build.gradle.kts
plugins {
id("buildlogic.java-conventions")
id("io.freefair.lombok") version "8.14"
}
dependencies {
api(libs.org.apache.commons.commons.collections4)
...
Gradle - Multi-Project Builds
Declare the desired test framework directly on the test suite or explicitly declare the test framework implementation dependencies on the test’s runtime classpath.
Error
- no location
- [warn] The automatic loading of test framework implementation dependencies has been deprecated.
This is scheduled to be removed in Gradle 9.0.
The automatic loading of test framework implementation dependencies has been deprecated.
- Solutions
[enum] Declare the desired test framework directly on the test suite or explicitly declare the test framework implementation dependencies on the test's runtime classpath.
- Locations
- `:searching-and-sorting:test`
- [warn] No test executed. This behavior has been deprecated.
This will fail with an error in Gradle 9.0.
No test executed. This behavior has been deprecated.
- Solutions
[enum] There are test sources present but no test was executed. Please check your test configuration.
- Locations
- `<undefined>`
- `:searching-and-sorting:test`
Fix
dependencies {
// JUnit Jupiter API for writing tests
testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.0")
// JUnit Jupiter Engine for running tests
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.0")
// JUnit Platform Launcher (for running tests, often implicitly added by Gradle with test suites)
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.11.0")
}
tasks.test {
useJUnitPlatform {
version = "5.8.2"
}
}
Issues with refreshing dependencies in Eclipse
If there is trouble refreshing the dependencies in Eclipse after making changes to the build.gradle file, follow the steps below:
- check if you have included eclipse gradle plugin.
apply plugin : 'eclipse'
- Go to your project terminal
- Run
gradle tasks --all
to see the list of all available gradle tasks. - If the task
cleanEclipse
is available, run it. - If not, run
gradle cleanEclipseProject
andgradle cleanEclipseClasspath
separately. - After that, run
gradle eclipse
- Go to the project in eclipse and refresh the project.
This should bring all the latest dependencies down and you should see them in the Referenced Libraries
section.
Working with EAR projects
Go to the EAR folder in command prompt:
C:\XXXXXXXXXXXXXEAR
And use this command to build EAR.
gradle clean ear --info
gradle clean testall --info
gradle clean testAll ear –-info
From folder : C:\XXXXXXXXXXEAR\build\distributions
To folder : C:\WASLP_dev\tools\WASLP8559\wlp\usr\servers\default\dropins
Windows command to copy the EAR from a source folder to a destination folder:
xcopy C:\XXXXXXXXXXXXXXEAR\build\distributions C:\WASLP_dev\tools\WASLP8559\wlp\usr\servers\default\dropins
From folder : C:\Users\n0281526\Documents\services-property-insurance-partner-exchange\PiAcordSalesMediationServiceEAR
To folder : C:\WASLP_dev\tools\WASLP8559\wlp\usr\servers\default\dropins
Windows command to copy the EAR from a source folder to a destination folder:
xcopy C:\WASLP_dev\workspaces\git_repo\services-property-insurance-partner-exchange\PiAcordSalesMediationServiceEAR\build\distributions C:\WASLP_dev\tools\WASLP8559\wlp\usr\servers\default\dropins
How to exclude a few tasks from the Gradle build process :
./gradlew build -x checkstyleMain -x findbugsMain -x test -x jacocoTestCoverageVerification -x pmdMain