📜 ⬆️ ⬇️

Gradle: Better Way To Build

No project using the Java platform (and more) can do without assembly tools (unless it is “Hello, world!”). Sooner or later, but it’s annoying to collect the distribution kit. Yes, and compile from the console would be nice if the project uses several different IDE. And before building the distribution kit, it would be great to put down the version number in its name. And the unit tests to get rid of - not for nothing that Kent Beck writes books. And there Continues Integration looms on the horizon. And it would be great to teach the CI server to do it all on their own. In short, there are a lot of tasks.

Once there are tasks, then there are solutions. I think most developers at least once, but have come across Ant. Many people use Maven. There are other, not so common tools: GAnt, Buildr, etc. Each of them has its own set of pros and cons, but today I want to introduce you to something new. Gradle .


Gradle tries to combine all the advantages of Ant, Maven and Ivy. And present what happened with the help of Groovy . Now, instead of crossing Batch scripts, java and xml configuration files, you can simply write a few lines of code in the Groovy dialect and enjoy life. Dialect is specifically designed to describe the assembly, testing, deployment, export, and any other actions on the project that can come to your mind.
')
Because Gradle runs on a running JVM, it successfully uses the Ant task libraries, Apache Ivy dependency management tools, and other existing tools (TestNG, JUnit, Surefire, Emma, ​​etc.). In principle, it is easy to integrate into the assembly any tool that works in jvm. In addition to everything, the Groovy dialect used in Gradle gives you complete freedom of action. Completely complete. Want conditional expressions? You are welcome! Want cycles? Welcome! Read and write files? Work with the network? Generate your own tasks on the fly? Anything! And in a normal, human programming language, not horrible xml constructs.

An interesting feature: a properly configured Gradle project can be assembled on a user's machine, on which Gradle is not installed. All that is required is to put 4 files into the source tree (which Gradle will generate for you): 2 executables for Win / * nix, 1 settings file and a small jar. Total ~ 20Kb. After that, the project can be assembled on any machine where there is access to the web. The script itself will take care of downloading the correct version of Gradle, about setting up and running the build.

Migrating to Gradle is very simple. For example, the maven2 assembly can be converted to a Gradle assembly automatically (while preserving the configured dependencies, artifacts, versions, and subprojects). And the migration has already begun. Now this tool is used by projects: Grails , Spring Security , Hibernate Core and even GAnt (honestly, GAnt is built using Gradle!).

Praised, now need to demonstrate in action.

First, create a template java project to demonstrate the use of build-by-convention. And then we will try to modify it a bit by adding a set of automated integration tests to the file structure to show how much freedom Gradle has in using the 'convention'. The example intentionally does not mention the source files, since not their meaning.

Suppose we have a project structure (you have already seen it a thousand times):

 / project
    / src
       / main
          / java
          / resources
       / test
          / java
          / resources


Create an empty build.gradle file in the project directory. Write one line there:

apply plugin:'java' 


Run the gradle build command and get:

>gradle build
:compileJava
:processResources
:classes
:jar
:assemble
:compileTestJava
:processTestResources
:testClasses
:test
:check
:build

BUILD SUCCESSFUL

Total time: 4.116 secs


In the console, we see the execution of a task sequence (Gradle Tasks), which are a close analogue of Ant Targets. In the / project / build directory, you can find compiled classes (including neatly packaged jar), test execution reports and other build results.

All this is so far no different from what many project participants have used to using Maven. The same directories, the same pom.xml (only called build.gradle). But do not rush to uncover rotten tomatoes and let me demonstrate one of the interesting features of Gradle.

Add integration tests. Let's create for them a separate branch of directories:

 / project
    / src
       / main
       / test
       / integTest
          / java
          / resources


and add the following code to build.gradle:
 sourceSets { integTest } 


In terms of the Gradle source set, a set of files and resources that must be compiled and run together. The above snippet defines a new source set named integTest. By default, sources and resources will be taken from /project/src/< source set>/java and /project/src/< source set>/resources respectively. The Java Plugin, which we connected at the beginning, sets two standard source sets: main and test .

Three new tasks will be automatically generated: compilation (compileIntegTestJava), resource processing (processIntegTestResource) and integrating their integTestClasses. Let's try to run:

>gradle integTestClasses
:compileIntegTestJava
:processIntegTestResources
:integTestClasses

BUILD SUCCESSFUL

Total time: 1.675 secs


At the cost of two lines, we got 3 new tasks and added a new catalog to the project build. But as soon as it comes to writing these tests, we find that we need all the dependencies of the main project, and also the compiled classes in addition.
Not a question, we write:

 configurations { integTestCompile { extendsFrom compile } } sourceSets { integTest{ compileClasspath = sourceSets.main.classes + configurations.integTestCompile } } 


The Congfigurations block describes dependency configurations . Each configuration can combine maven artifacts, sets of local files, etc. New configurations can be inherited from existing ones. In this case, we inherit the dependency configuration for compiling integration tests from the compile configuration. This configuration is standard (specified plugin) for compiling main

The string sourceSets.main.classes + configurations.integTestCompile denotes the union of the filesets. main.classes - the directory where the * .class files obtained during the main assembly will be located.

Probably, in the assembly, JUnit will be useful to us. And it would be nice to run these tests. We describe another configuration of dependencies.

 configurations { integTestCompile { extendsFrom compile } integTestRuntime { extendsFrom integTestCompile, runtime } } repositories { mavenCentral() } dependencies { integTestCompile "junit:junit:4.8.1" } sourceSets { integTest{ compileClasspath = sourceSets.main.classes + configurations.integTestCompile runtimeClasspath = classes + sourceSets.main.classes + configurations.integTestRuntime } } task integrationTest(type: Test) { testClassesDir = sourceSets.integTest.classesDir classpath = sourceSets.integTest.runtimeClasspath } 


The Repositories block will connect us to maven central (and other repositories of our choice). The dependencies block will add artifact dependency to our configuration. runtimeClasspath = classes + sourceSets.main.classes + configurations.integTestRuntime combines files from integTest.classes , main.classes and integTestRuntime .

The task to run the tests still had to write. However, it was easy: just specify where to get the tests and what classpath to run them with, i.e. "what". "How" Gradle will determine on its own.

Now we are ready:

>gradle clean integrationTest
:clean
:compileJava
:processResources
:classes
:compileIntegTestJava
:processIntegTestResources UP-TO-DATE
:integTestClasses
:integrationTest

BUILD SUCCESSFUL

Total time: 4.195 secs


Note that Gradle handled the integTest.compileClasspath dependency on main.classes and compiled source set main before collecting integration tests!

As a result, we managed to put together a standard configuration of the project, completely painlessly expand it and describe the dependency scheme between subtasks using inheritance. All this with a simple and readable language.

What hasn't a word been said yet: about working with tasks, about incremental build, about working with subprojects, about working with Maven repositories, about integrating with Ant, about standard plugins, about init-scripts and many other things . But about this, perhaps, in other articles.

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


All Articles