📜 ⬆️ ⬇️

Multimodule Java-project with Gradle. Step by step

A lot of articles about Gradle 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 not familiar with Gradle to “try out” and continue to independently study this tool.

This article will not describe in detail such topics as the gradle (plugin) plug-ins, tasks (tasks), dependencies (dependencies), automatic testing and other delights of this project builder. First, each topic deserves a separate article or even a series of articles, and secondly, there are already articles on these topics on these topics, for example: Gradle: Tasks Are Code , Gradle: Better Way To Build . And on the Gradle official site there is a beautifully written Gradle User Guide . I will focus on the direct solution of the problem, and all related topics will be described within the framework of this very task.
First we decide on the goal, what do we want to get at the output? And the goal is indicated in the title of the article. We want to get a project with several modules that 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 get other people's sources with the magic file gradlew (gradlew.bat) in the root of the project, then installing gradle is not necessary.
')
Gradle can be delivered by downloading the latest version from the Gradle downloads page or using the package manager in your favorite OS (note I put on Mac OS via brew and on Debian via apt-get from standard sources)

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, plug-ins (plugin), wrapper (wrapper)

Create a project folder and in its root make the 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 java and application plugins. The java plugin contains such useful tasks as jar — compile a jar archive, compileJava — compile source codes, etc. You can read more about the plugin here . The application plugin contains the following tasks: run - launch the application; installApp - installing an application on a computer, this task creates executable files for * nix and for windows (bat file); distZip - assembles the application in a zip archive, placing there all the jar files, as well as operating system-specific scripts. Read more about the plugin in the documentation .
Now let's dwell on the task wrapper . This very useful task is probably the most ingenious solution, designed to make life easier for programmers. After completing the $ gradle wrapper , we get 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 us gradlew executable files for * nix, gradlew.bat for Windows, as well as the gradle and .gradle folders. Hidden folder .gradle can not be included in the repository, it contains libraries of dependencies. All the core lies in the gradle and in the gradlew file itself. Now we can safely give our project to anyone with jdk of the required version, and he can independently compile, build, install the project using ./gradlew . Note that my version of gradle (see the result of the $ gradle -version above) is different from the one I specified in the build.gradle file, but this is not terrible, 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, instead of gradle you can safely use gradlew . By the way, executing the $ ./gradlew without parameters will create a .gralde folder and download all dependent libraries there (about dependencies below). But the execution of this command is not necessary, since at any launch of gradle (gradlew) dependencies will be checked and the missing files will be downloaded. Therefore, having received a project in which the gradlew files are located, you can immediately start the necessary task, the list of which can be obtained using the command ./gradlew tasks

Results of the second step (withdrawal reduced):
 $ ./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 gradle tasks. We can even compile a jar file, but nothing but an empty manifest will be there. It's time to write the code. Gradle uses by default the same directory structure as Maven , namely
 src -main -java -resources -test -java -resources 

main/java are java-files of our program, main/resources are other files (* .properties, * .xml, * .img and others). The test contains the files necessary for testing.
Since the testing in this article will not be considered, we will manage to create the src/main folder with all the nested ones and start creating our application. And the application is Hello World, in which we will use the Log4j library. Let's just figure out how dependencies work in gradle. build.gradle 's make changes to the build.gradle file, create a com/example/Main.java with the main application class in the src/main/java folder, as well as the Log4j 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
 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <param name="Target" value="System.out"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%p %c: %m%n"/> </layout> </appender> <root> <priority value ="debug" /> <appender-ref ref="console" /> </root> </log4j:configuration> 

Consider the changes in the build.gradle file. We added the mainClassName variable. It indicates the main class of our application and is used by the application plugin in the run task. This class will be launched. We also added the variables sourceCompatibility and targetCompatibility , assigning them the value JavaVersion.VERSION_1_7 . These are variables from the java plugin that show which version of jdk we need when building. The next block is repositories . In this block we connect Maven repository. Gradle is friends with him. The dependencies block contains the dependencies of our application. Subtleties settings look in the documentation . Here we indicate that the compile task requires log4j. In the example, the abbreviated syntax is specified. 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:
 <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> 

You can also configure dependencies on the compile files('libs/a.jar', 'libs/b.jar') and on the compile project(':library_project') subprojects.
The last addition to build.gradle is the jar block. It also refers to the java plugin. Contains additional information for assembling jar-file. In this case, we add the main class to the manifest, using the variable declared above, mainClassName .
Next is the optional gradle.properties file. This file is scattered throughout the documentation, a bit found here and here . In this case, we actually override the JAVA_HOME variable. This is true when you have several versions of jdk, as in my case, you could notice at the beginning of the article, $ gradle -version shows that my version of JVM: 1.8.0_05 (Oracle Corporation 25.5-b02) .
I think it’s pointless to dwell on the files src/main/java/Main.java and src/main/resources/log4j.xml , since everything is extremely simple. We send two messages to the Logger, the message “I'm the main project” is output to the console. In the log4j settings file, it is written that our logger will display messages also to the console.

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 is seen that the missing library is downloaded, and its use is demonstrated.

Step 4. Reaching the goal

We already have a project that works, builds and runs through gradle. It remains to finish a little: implement multi-modularity, stated in the title of the article, or a multi-project , if you use the terminology of gradle. Create two directories in the project root: main_project and library_project . Now move the src folder and the build.gradle file to the newly created main_project directory, and create a new settings.gradle file with this content in the root (more about 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 which folders to connect (in fact, independent gradle projects). At this stage, we need one main_project folder. After such changes, we can execute the $ ./gradlew run or with the specific $ ./gradlew main_project:run subproject, and 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, and so on. Gradle, if you do not specify a specific subproject, will run 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 $ ./gradlew run will run only this subproject )
Now create the code in our library_project . Create build.gradle and src/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 much easier for this subproject. We use java plugin and set variables with JDK version. This article is sufficient for this article. Now we want gradle to know about the library_project library_project , we will describe it in the file settings.gradle :

{project_path} /settings.gradle
 rootProject.name = 'Gradle_Multiproject' include 'main_project', 'library_project' 

Now we can build the jar file containing our library with the $ ./gradlew library_project:jar command.
 $ ./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 the use of the Simple class in main_project . To do this, add the line compile project(":library_project") to the {project_path}/main_project/build.gradle file in the dependencies block, which reports that the library_project project is needed for the execution of the compile task in this module.

Addition from MiniM : In graddle, the symbol ":" is used instead of "/" and for a more branchy structure, references to the project may look like this: "loaders: xml-loader"

{project_path} /main_project/build.gradle ( dependencies block)
 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). Remove trash

The main goal has been achieved, but at this stage quite legitimate questions could arise about the duplication of information in the build files, a deeper gradle setting, and also what to study further. For self-study, I advise you to read the contents of the links at the end of the article. For now, let's tidy up our build files by creating build.gradle in the project root 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, you can store all the settings at all, but I prefer to split large files) and what is not needed in the subprojects, for example, we need only one wrapper, fundamentally.
In the subprojects block we place the settings of the subprojects, namely: we connect the java plugin - everyone needs it; we expose the jdk version; we connect a maven repository. Also in this file we connect the plugins idea and eclipse . These plugins contain tasks for generating project files for the respective IDEs. And here we transfer the task wrapper. It is needed only in the root to create common gradlew files for all.
In the subprojects, we removed all unnecessary and added the variable version . The value of this variable will be added to jar files, for example, instead of library_project.jar will now be library_project-1.1.beta.jar.
In addition to the subprojects block, you can use allprojects or project(':project_name') . Read more here .

On this I will finish. I hope this article has aroused interest among people who are not familiar with Gradle, and prompted a more detailed study and subsequent use of this tool in their projects.

Thanks for attention.

Additions:
MiniM : In the gradle, the symbol ":" is used instead of "/" and for a more branched structure, the links to the project may look like this ": loaders: xml-loader"
leventov : idea with the idea plugin. issues.gradle.org/browse/GRADLE-1041 , there is a solution in the last comment.

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

Source: https://habr.com/ru/post/225189/


All Articles