A multi-module Java project with Gradle. Step by step
- Tutorial
A lot of articles about Gradle have been written. And for my part, I would like to add such step-by-step instructions to the piggy bank, the reading of which, I hope, will allow those who are new to Gradle to “try out” and continue to study this tool on their own.
This article will not describe in detail such topics as gradle plugins, tasks, dependencies, automatic testing and other delights of this project builder. Firstly, each topic deserves a separate article or even a series of articles, and secondly, there are already articles on these topics on the hub, for example: Gradle: Tasks Are Code , Gradle: Better Way To Build . And on the official Gradle website there is a beautifully written Gradle User Guide. I will focus on the immediate solution of the task, and all related topics will be described in the framework of this task itself.
First we decide on the goal, what do we want to get at the exit? And the goal is indicated in the title of the article. We want to get a project with several modules, which is built using Gradle. And so, let's get started.
Note: If you just want to “play” with gradle by downloading the files for the article, or you got someone else's sources with the magic file gradlew (gradlew.bat) in the root of the project, then installing gradle is not necessary.
You can install Gradle by downloading the latest version from the Gradle download page or using the package manager in your favorite OS (note: I installed it on Mac OS via brew and on Debian via apt-get from standard sources)
The result of the first step:
Create a project folder and in its root create a file
{project_path} /build.gralde
Let's take a closer look at what we wrote in the file. It uses the dynamic language Groovy. Using a full-fledged programming language in gradle gives you more freedom compared to package builders using declarative languages.
In this file we connect plugins
Now we dwell in more detail on the problem
We see that the script created gradlew executables for * nix, gradlew.bat for Windows, as well as the gradle and .gradle folders. The hidden .gradle folder can not be included in the repository, it contains dependency libraries. All basic lies in gradle and in the gradlew file itself. Now we can safely give our project to any person who has jdk of the desired version, and he will be able to independently compile, assemble, install the project using
Now
Results of the second step command (output is shortened):
At this stage, we can already perform several tasks
Since testing will not be considered in this article, we will get by creating a folder
{project_path} /build.gradle
{project_path} /gradle.properties
{project_path} /src/main/java/com/example/Main.java
{project_path} /src/main/resources/log4j.xml
Consider the changes in the file
For comparison, a similar block in maven:
You can also configure file
The last addition to
Next is an optional file
I think that dwell on files in detail
The results of the third step:
It can be seen that the missing library is being downloaded, and its use has been demonstrated.
We already have a project that works, builds and runs through gradle. It remains to complete the smallest detail: implement the multi- modularity stated in the title of the article, or multi-project , using gradle terminology. Create two directories in the root of the project:
{project_path} /settings.gradle
In this file we say what our project is called and what folders to connect (actually independent gradle projects). At this stage, we need one folder
Now we will create the code in ours
{project_path} /library_project/build.gradle
{project_path} /library_project/src/main/java/com/example/library/Simple.java
{project_path} /settings.gradle
Now we can compile the jar file containing our library with the command
The resulting file can be found at:
Now let's add class usage
Addition from MiniM : In gradle, the symbol ":" is used instead of "/" and for a more branched structure, project links may look like this:: loaders: xml-loader "
{project_path} /main_project/build.gradle (block
{project_path} /main_project/src/main/java/com/example/Main.java
You can check.
The result of the fourth step:
The main goal was achieved, but at this stage quite logical questions could arise about duplication of information in build files, deeper gradle settings, and also what to study further. For independent study, I advise you to read the contents of the links at the end of the article. In the meantime, let's tidy up our build files by creating
{project_path} /build.gradle
{project_path} /main_project/build.gradle
{project_path} /build.gradle
In the root,
In the block
In subprojects we removed all unnecessary and added a variable
In addition to the block
On this I will end. I hope this article aroused interest among people not familiar with Gradle, and prompted a more detailed study and the subsequent use of this tool in their projects.
Thanks for attention.
Additions:
MiniM : In gradle, the symbol ":" is used instead of "/" and for a more branched structure, project links may look like this:: loaders: xml-loader "
leventov : with a plugin
Sources of the project created in the article on bitbucket ( zip archive )
Gradle
Gradle User Guide
Apache Logging Services
Apache Maven
Groovy Language
This article will not describe in detail such topics as gradle plugins, tasks, dependencies, automatic testing and other delights of this project builder. Firstly, each topic deserves a separate article or even a series of articles, and secondly, there are already articles on these topics on the hub, for example: Gradle: Tasks Are Code , Gradle: Better Way To Build . And on the official Gradle website there is a beautifully written Gradle User Guide. I will focus on the immediate solution of the task, and all related topics will be described in the framework of this task itself.
First we decide on the goal, what do we want to get at the exit? And the goal is indicated in the title of the article. We want to get a project with several modules, which is built using Gradle. And so, let's get started.
Step 1. Install gradle
Note: If you just want to “play” with gradle by downloading the files for the article, or you got someone else's sources with the magic file gradlew (gradlew.bat) in the root of the project, then installing gradle is not necessary.
You can install Gradle by downloading the latest version from the Gradle download page or using the package manager in your favorite OS (note: I installed it on Mac OS via brew and on Debian via apt-get from standard sources)
The result of the first step:
$ gradle -version
------------------------------------------------------------
Gradle 1.11
------------------------------------------------------------
Build time: 2014-02-11 11:34:39 UTC
Build number: none
Revision: a831fa866d46cbee94e61a09af15f9dd95987421
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.9.2 compiled on July 8 2013
Ivy: 2.2.0
JVM: 1.8.0_05 (Oracle Corporation 25.5-b02)
OS: Mac OS X 10.9.3 x86_64
Step 2. Empty project, plugins, wrapper
Create a project folder and in its root create a file
build.gradle
with the following contents: {project_path} /build.gralde
apply plugin: “java”
apply plugin: “application”
task wrapper(type: Wrapper) {
gradleVersion = '1.12'
}
Let's take a closer look at what we wrote in the file. It uses the dynamic language Groovy. Using a full-fledged programming language in gradle gives you more freedom compared to package builders using declarative languages.
In this file we connect plugins
java
and application
. The plugin java
contains such useful tasks as jar - to compile a jar archive, compileJava - to compile source codes, etc. You can read more about the plugin here . Pluginapplication
contains tasks: run - launch the application; installApp - install the application on the computer, this task creates executable files for * nix and for windows (bat file); distZip - collects the application in a zip archive, placing all jar files there, as well as operating system-specific scripts. Read more about the plugin in the documentation . Now we dwell in more detail on the problem
wrapper
. This very useful task is probably the most ingenious solution designed to make life easier for programmers. Having executed $ gradle wrapper
, we obtain the following result:$ gradle wrapper
:wrapper
BUILD SUCCESSFUL
Total time: 7.991 secs
$ ls -a
. .. .gradle build.gradle gradle gradlew gradlew.bat
We see that the script created gradlew executables for * nix, gradlew.bat for Windows, as well as the gradle and .gradle folders. The hidden .gradle folder can not be included in the repository, it contains dependency libraries. All basic lies in gradle and in the gradlew file itself. Now we can safely give our project to any person who has jdk of the desired version, and he will be able to independently compile, assemble, install the project using
./gradlew
. Note that my version of gradle (see the result of the command $ gradle -version
above) is different from the one I specified in the build.gradle file, but this is not scary, because after running the wrapper task, we will get the necessary version of gradle.$ ./gradlew -version
------------------------------------------------------------
Gradle 1.12
------------------------------------------------------------
Build time: 2014-04-29 09:24:31 UTC
Build number: none
Revision: a831fa866d46cbee94e61a09af15f9dd95987421
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.9.3 compiled on December 23 2013
Ivy: 2.2.0
JVM: 1.8.0_05 (Oracle Corporation 25.5-b02)
OS: Mac OS X 10.9.3 x86_64
Now
gradle
you can safely use instead gradlew
. By the way, executing a command $ ./gradlew
without parameters will create a folder .gralde
and download all the dependent libraries there (about the dependencies below). But the execution of this command is not necessary, since at any start of gradle (gradlew), dependencies will be checked and missing files will be downloaded. Therefore, having received the project in which the gradlew files are located, you can immediately start the desired task, a list of which can be obtained by the ./gradlew tasks
Results of the second step command (output is shortened):
$ ./gradlew tasks
:tasks
------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------
Application tasks
-----------------
distTar - Bundles the project as a JVM application with libs and OS specific scripts.
distZip - Bundles the project as a JVM application with libs and OS specific scripts.
installApp - Installs the project as a JVM application along with libs and OS specific scripts.
run - Runs this project as a JVM application
...
Other tasks
-----------
wrapper
...
To see all tasks and more detail, run with --all.
BUILD SUCCESSFUL
Total time: 7.808 secs
Step 3. Fill in the blanks
At this stage, we can already perform several tasks
gradle
. We can even compile a jar file, but nothing but an empty manifest will be there. It is time to write the code. Gradle uses the same directory structure as Maven by default , namelysrc
-main
-java
-resources
-test
-java
-resources
main/java
- these are java-files of our program, main/resources
- these are other files (* .properties, * .xml, * .img and others). There test
are files necessary for testing. Since testing will not be considered in this article, we will get by creating a folder
src/main
with all the subfolders and start creating our application. And the application is Hello World, in which we will use the Log4j library . We’ll just figure out how dependencies work in gradle. We build.gradle
will make changes to the file , create a file com/example/Main.java
with the main application class in the folder src/main/java
, as well as a file with Log4j settings src/main/resources/log4j.xml
. And the file gradle.properties
(optional, details below) {project_path} /build.gradle
apply plugin: "java"
apply plugin: "application"
mainClassName = "com.example.Main"
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
repositories {
mavenCentral()
}
dependencies {
compile "log4j:log4j:1.2.17"
}
jar {
manifest.attributes("Main-Class": mainClassName);
}
task wrapper(type: Wrapper) {
gradleVersion = "1.12"
}
{project_path} /gradle.properties
org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk1.7.0_55.jdk/Contents/Home/
{project_path} /src/main/java/com/example/Main.java
package com.example;
import org.apache.log4j.Logger;
public class Main {
private static final Logger LOG = Logger.getLogger(Main.class);
public static void main(String[] args) {
LOG.info("Application started");
System.out.println("I'm the main project");
LOG.info("Application finished");
}
}
{project_path} /src/main/resources/log4j.xml
Consider the changes in the file
build.gradle
. We have added a variable mainClassName
. It indicates the main class of our application and is used by the plugin application
in the task run
. It is this class that will be launched. We also added variables sourceCompatibility
and targetCompatibility
, assigning them a value JavaVersion.VERSION_1_7
. These are the variables from the plugin java
; they show which version of jdk we need when building. The next block is repositories
. In this block we connect the Maven repository . Gradle is very “friendly” with him. The block dependencies
contains the dependencies of our application. We look at the details of the settings in the documentation . Here we indicate that for the taskcompile
log4j required. The example has a shorthand syntax. You can write a detailed version and it will look like this:complie group: 'log4j', name: 'log4j', version: '1.2.17'
For comparison, a similar block in maven:
log4j log4j 1.2.17
You can also configure file
compile files('libs/a.jar', 'libs/b.jar')
and subproject dependencies compile project(':library_project')
. The last addition to
build.gradle
is a block jar
. It also applies to the plugin java
. It contains additional information for building a jar file. In this case, we add the main class to the manifest using the variable declared above mainClassName
. Next is an optional file
gradle.properties
. The description of this file is scattered throughout the documentation, a little is here and here . In this case, we are actually redefining the variable JAVA_HOME
. This is true when you have several versions of jdk, as in my case, you could pay attention at the beginning of the article, it $ gradle -version
shows that my version JVM: 1.8.0_05 (Oracle Corporation 25.5-b02)
.I think that dwell on files in detail
src/main/java/Main.java
and src/main/resources/log4j.xml
does not make sense, since everything is extremely simple. We send two messages to Logger, the message “I'm the main project” is displayed in the console. The log4j settings file says that our logger will also output messages to the console. The results of the third step:
$ ./gradlew run
:compileJava
Download http://repo1.maven.org/maven2/log4j/log4j/1.2.17/log4j-1.2.17.jar
:processResources
:classes
:run
INFO com.example.Main: Application started
I'm the main project
INFO com.example.Main: Application finished
BUILD SUCCESSFUL
Total time: 14.627 secs
It can be seen that the missing library is being downloaded, and its use has been demonstrated.
Step 4. Reaching the goal
We already have a project that works, builds and runs through gradle. It remains to complete the smallest detail: implement the multi- modularity stated in the title of the article, or multi-project , using gradle terminology. Create two directories in the root of the project:
main_project
and library_project
. Now move the folder src
and file build.gradle
to the directory you just created main_project
, and create a new file settings.gradle
with the following contents in the root (more on this file here ): {project_path} /settings.gradle
rootProject.name = 'Gradle_Multiproject'
include 'main_project'
In this file we say what our project is called and what folders to connect (actually independent gradle projects). At this stage, we need one folder
main_project
. After such changes, we can perform $ ./gradlew run
either with an indication of a specific subproject $ ./gradlew main_project:run
, and we will get the same result as at the end of step 3. That is, a working project. We can also execute all other jar, build, installApp commands and so on. Gradle, if you do not specify a specific subproject, will start the task in all connected subprojects that have this task (for example, if the application plugin is connected to only one subproject, we will have main_project, the command $ ./gradlew run
will run only that subproject) Now we will create the code in ours
library_project
. We create build.gradle
andsrc/main/java/com/example/library/Simple.java
{project_path} /library_project/build.gradle
apply plugin: "java"
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
{project_path} /library_project/src/main/java/com/example/library/Simple.java
package com.example.library;
public class Simple {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
build.gradle
for this subproject is much simpler. We use the java plugin and set the variables with the JDK version. This is sufficient for this article. Now we want gradle to know about the subproject library_project
, we will describe this in the file settings.gradle
: {project_path} /settings.gradle
rootProject.name = 'Gradle_Multiproject'
include 'main_project', 'library_project'
Now we can compile the jar file containing our library with the command
$ ./gradlew library_project:jar
.$ ./gradlew library_project:jar
:library_project:compileJava
:library_project:processResources UP-TO-DATE
:library_project:classes
:library_project:jar
BUILD SUCCESSFUL
Total time: 10.061 secs
The resulting file can be found at:
{project_path}/library_project/build/libs/library_project.jar
. Now let's add class usage
Simple
to main_project
. To do this, {project_path}/main_project/build.gradle
add a line compile project(":library_project")
to the block in the file dependencies
, which says that to perform the compile task in this module, you need a project library_project
. Addition from MiniM : In gradle, the symbol ":" is used instead of "/" and for a more branched structure, project links may look like this:: loaders: xml-loader "
{project_path} /main_project/build.gradle (block
dependencies
)dependencies {
compile "log4j:log4j:1.2.17"
compile project(":library_project")
}
{project_path} /main_project/src/main/java/com/example/Main.java
package com.example;
import org.apache.log4j.Logger;
import com.example.library.Simple;
public class Main {
private static final Logger LOG = Logger.getLogger(Main.class);
public static void main(String[] args) {
LOG.info("Application started");
System.out.println("I'm the main project");
Simple simple = new Simple();
simple.setValue(10);
System.out.println("Value from Simple: " + simple.getValue());
LOG.info("Application finished");
}
}
You can check.
The result of the fourth step:
$ ./gradlew run
:library_project:compileJava UP-TO-DATE
:library_project:processResources UP-TO-DATE
:library_project:classes UP-TO-DATE
:library_project:jar UP-TO-DATE
:main_project:compileJava
:main_project:processResources UP-TO-DATE
:main_project:classes
:main_project:run
INFO com.example.Main: Application started
I'm the main project
Value from Simple: 10
INFO com.example.Main: Application finished
BUILD SUCCESSFUL
Total time: 11.022 secs
Step 5 (final). We remove garbage
The main goal was achieved, but at this stage quite logical questions could arise about duplication of information in build files, deeper gradle settings, and also what to study further. For independent study, I advise you to read the contents of the links at the end of the article. In the meantime, let's tidy up our build files by creating
build.gradle
in the root of the project and changing the contents of the rest of the build files {project_path} /build.gradle
apply plugin: "idea"
apply plugin: "eclipse"
subprojects {
apply plugin: "java"
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
repositories {
mavenCentral()
}
}
task wrapper(type: Wrapper) {
gradleVersion = "1.12"
}
{project_path} /main_project/build.gradle
apply plugin: "application"
version = '1.0'
mainClassName = "com.example.Main"
dependencies {
compile "log4j:log4j:1.2.17"
compile project(":library_project")
}
jar {
manifest.attributes("Main-Class": mainClassName);
}
{project_path} /build.gradle
version = "1.1_beta"
In the root,
build.gradle
we will store what applies to all projects (in fact, all settings can be stored in general, but I prefer to separate large files) and what is not needed in subprojects, for example, we need only one wrapper, in the root. In the block
subprojects
we put the settings of subprojects, namely: we connect the java plugin - everyone needs it; set the jdk version; we connect the maven repository. Also in this file we include idea and eclipse plugins . These plugins contain tasks for generating project files for the corresponding IDEs. And here we transfer the wrapper task. It is needed only at the root to create gradlew files common to all. In subprojects we removed all unnecessary and added a variable
version
. The value of this variable will be added to jar files, for example, instead of library_project.jar now there will be library_project-1.1.beta.jar. In addition to the block
subprojects
, you can use allprojects
or project(':project_name')
. More details here . On this I will end. I hope this article aroused interest among people not familiar with Gradle, and prompted a more detailed study and the subsequent use of this tool in their projects.
Thanks for attention.
Additions:
MiniM : In gradle, the symbol ":" is used instead of "/" and for a more branched structure, project links may look like this:: loaders: xml-loader "
leventov : with a plugin
idea есть проблемы
. issues.gradle.org/browse/GRADLE-1041, in the last comment there is a solution.useful links
Sources of the project created in the article on bitbucket ( zip archive )
Gradle
Gradle User Guide
Apache Logging Services
Apache Maven
Groovy Language