Index

Setup

Download the sources

Windows Portable

Let's start with an harmless setup. Create a directory and extract all the following "portable servers" (they can be founded, with others at http://kendar.org/?p=/system/portableservers). If you had already installed a JDK from the same source remember to choose the jdk-11.

Then you can run "pgradle.bat" directly from explorer and go into the directory in wihich you will create your projects

Windows standard

Or you can bloat your machine with a complete setup

000-Create a simple application

Preparation

Since now a file called "gradlew.bat" is created. You don't need to have gradle on the PATH variable anymore. This is a wrapper to the project-local gradle!

"The Gradle wrapper makes sure your build is always run with the same Gradle version, no matter who executes the build and where or that Gradle is installed or not, as long as the one uses the Gradle wrapper to build the project. This means you can design your build for that Gradle version and be sure that the build will not fail, just because someone is using a different version of Gradle and thus is also an important step in build reproducibility." (from stackoverflow )

File structure

001-Create an application with root project

The aim of this part is to create an application inside a project. Like with the maven "pom" project types

Preparation

*Create directory "demo001" and enter it * Run 'gradle init --type basic' and select the following to setup a simple stub * Script DSL "1: Groovy" * Project name, leave it blank (default)
* Now a new project without sources has been created * Add then inside the build.gradle the following lines

    plugins {
        id 'base'
    }

Remember the Gradle builds use (for our tutorial) groovy as a language. With the previous lines we are loading the plugins that are part of the current project build

Creating a 'main' supbroject

We will add to the main "settings.gradle" the following line. It's like adding child projects to the pom type pom.xml

    include 'startup'

To be fair, only one level of nesting is encouraged, because of some glitchy behaviour of gradle with nested projects (something that in maven you can't see...but think about the problem this carried)

The name of the project IS the name of the directory in which you will put the new build.gradle script

With a simple helper

I created a small script to initialize the directory "mksrc.bat". Supposing our demo001 project is in C:\Documents we will call the following to create a java project with Maven group org.kendar and version 1.0.0-SNAPSHOT

    mksrc C:\Documents\demo001\startup java org.kendar 1.0.0-SNAPSHOT

The source is

@echo off
set DEST_DIR=%1
set PRJ_TYPE=%2
set PRJ_GROUP=%3
set PRJ_VERSION=%4

mkdir %DEST_DIR%

echo plugins {>%DEST_DIR%\build.gradle
echo    id '%PRJ_TYPE%'>>%DEST_DIR%\build.gradle
echo }>>%DEST_DIR%\build.gradle
echo group '%PRJ_GROUP%'>>%DEST_DIR%\build.gradle
echo version '%PRJ_VERSION%'>>%DEST_DIR%\build.gradle


mkdir %DEST_DIR%\src\main\java
echo empty>%DEST_DIR%\src\main\java\.gitkeep
mkdir %DEST_DIR%\src\main\resources
echo empty>%DEST_DIR%\src\main\resources\.gitkeep

mkdir %DEST_DIR%\src\test\java
echo empty>%DEST_DIR%\src\test\java\.gitkeep
mkdir %DEST_DIR%\src\test\resources
echo empty>%DEST_DIR%\src\test\resources\.gitkeep

By hand

Create a directory under demo001, let's call it "startup" Add a new fle under it called "build.gradle". This is the equivalent of the maven pom We should then add

And add all directories (src test etc)

The group and version are now accessible through the "group" and "version" variables!

Finally

Now running the "gradle projects" command from demo001 directory you will find the demo001 project plus all its referenced projects!

gradle projects

Then you can add a class in demo001\startup\src\main\java\org\kendar\HelloWorld.java

Containing

package org.kendar;

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World\n");
    }
}

And finally run "gradle jar" and you will find the jar inside the directory "demo001\startup\build\libs"

Running the jar

You now want to run your project. Simply add in startaup\build.gradle the plugin 'application' and the class to start. This is how the file should look like.

Tells gradle to use Java, in form of application, and as an application contains a main class, defined in the relative section

    plugins {
       id 'java'
       id 'application'
    }

    group 'org.kendar'
    version '1.0.0-SNAPSHOT'

    application {
        mainClassName = 'org.kendar.HelloWorld'
    }

Now you can start the application with

    gradle run

gradle run

From now on you can find inside demo001\startup\build the directory "scripts" and "lib" that contains a batch to run the jar and the jar itself with its needed libraries

You can look here for further references on the application plugin

002-Adding internal dependencies

We add now a dependency. We will create a new application called demo002 identical to the demo001.

It is possible to directly copy the content of demo001 into the new demo002 directory and rename the project inside the demo002\settings.gradle to demo002

Then we create a new "services" subproject excatly as we previously did with the startup project calling (with the demo002 project inside the "C:\Documents" directory) and adding the services project to the main settins.gradle

    mksrc C:\Documents\demo001\services java org.kendar 1.0.0-SNAPSHOT

And adding the plugin 'java-library' to the services build.gradle to define that as...a library, resulting in the following:

plugins {
   id 'java'
   id 'java-library'
}
group 'org.kendar'
version '1.0.0-SNAPSHOT'

Now running the "gradle projects" a new project will appear

A new service

We then create a new service to extract the "Hello World" printing responsability adding demo002\services\src\main\java\org\kendar\HelloWorldService.java

Containing

package org.kendar;

public class HelloWorldService {
    public void printHello() {
        System.out.println("Hello World Service\n");
    }
}

Adding the dependency

We can now add the dependency from "services" into the "startup\build.gradle" adding the following at the end of the file. We use the keyword "implementation". This means that the dependency is considered always.

We have these different ways (with their Maven equivalent)

If a new dependency is needed, part of the main project. This can be added with the following syntax:

    dependencies {
        implementation project(":services")
    }

Eventually a couple of other syntaxes are allowed here if the dependency is on an artifact

All those ARE string, and can be replaced by variables (remember this when the refactor will start)

And change the HelloWorld class implementation to

package org.kendar;

public class HelloWorld {
    public static void main(String[] args) {
        HelloWorldService service = new HelloWorldService();
        service.printHello();
    }
}

Then running "gradle build run" we should see the "Hello World Service" message

gradle run build

003-Publishing on maven repository

First, just take the demo002 and copy as demo003

Publishin on local directory

To ease the development i choosed to use a local Maven repository to share the libraries. Again supposing our projects are in the C:\Documents directory, my new repo will be placed in C:\Documents\maven directory

This local repo will be used to overcame the need for the one level max nesting of gradle projects

The main build.gradle

In the main build.gradle i will add an external variable. This is a variable that will be seen by all build.gradle-s inside the project. This will contain a reference to the our maven directory. Please note that i assume that exists one "container" (kind of pom) project with only direct childrens! No nesting!

ext.repos = [
        'LocalMaven'   : "file://${projectDir}/../maven"
]

Then i have to tell all the subprojects to use this repository (as a source). I add this at the end of the main build.gradle. This means that all java-type subprojects will first search on the "LocalMaven" and then on the maven central.

All the subprojects will have access to the "repos" variable. The placement inside the main build.gradle is driven by the need to define a common directory for all subprojects. The internal variable will have the form of "repos.LocalMaven": notice the lack of commas!

The definition of the "jar source" repository can be placed in every java-type project, and can have the following form, the declaration order matters a lot! Here first is taken the local maven repository (just declared) and after the maven central repo.

repositories {
    maven {
        name = 'LocalMaven'
        url = uri(repos.LocalMaven)
    }
    mavenCentral()
}

The idea is to put all the repository informations in one single place to avoid littering the groovy code. We introduce some new constructs.

Inside the main build.gradle now can be added the declaration of the repos.

subprojects {
    afterEvaluate { project ->
        plugins.withType(JavaPlugin) {
            repositories {
                maven {
                    name = 'LocalMaven'
                    url = uri(repos.LocalMaven)
                }
                mavenCentral()
                ...

To demonstrate the previous statement you can add some line and see the differences (i always loved variable scoping...)

subprojects{
    println project.name
    println project.version
    afterEvaluate { project ->
        println project.name
        println project.version
        ...

gradle afterevaluate

At last i have to tell the projects that publishes artifacts to use the local maven repo. Adding the following to the previously created "subprojects" section.

subprojects {
    afterEvaluate { project ->
        plugins.withType(JavaPlugin) {
            ...
        }
        plugins.withType(MavenPublishPlugin) {
            publishing {
                publications {
                    mavenJava(MavenPublication) {
                        from project.components.java
                    }
                }
                repositories {
                    maven {
                        name = 'LocalMaven'
                        url = uri(repos.LocalMaven)
                    }
                ...

Remember to close the curly braces!!!!

Publishing the services

To publish the services on our local repository it would be enough to add the mavne-publish plugin to the build.gradle, resulting in the following. This because we already handled the publishing behaviour in the root project!

plugins {
   id 'java'
   id 'java-library'
   id 'maven-publish'
}
group 'org.kendar'
version '1.0.0-SNAPSHOT'

Now running "gradle build publish" the new directory C:\Documents\maven have been created containing the poms, md5 and jars for the services!

But this simple "publish" task... will not last long

Publishing on global repository

Setup a simple maven repository

We can now download a ready copy of Artifactory to simulate a real repo, from Artifactory Portable extract in the same dir previously used for Gradle and Java, then run partifactory.bat.

An UI will then open and the first things to do are

Integrate the main build.gradle

First must be added the variables containing the addresses of the remote repositories

ext.repos = [
        ...
        'RemoteMavenRelease'   : "http://localhost:8081/artifactory/libs-release",
        'RemoteMavenSnapshot'   : "http://localhost:8081/artifactory/libs-snapshot"
]

Now i will add the dependency on the "Remote Repo" to download files and to upload them. Here i have to add the credentials for my local artifactory repository. Of course you can work with any kind of repository following this approach.

subprojects {
    afterEvaluate { project ->
        plugins.withType(JavaPlugin) {
            repositories {
                ...
                maven {
                    name = 'RemoteMaven'
                    if (project.version.endsWith('-SNAPSHOT')) {
                        url = uri(repos.RemoteMavenSnapshot)
                    } else {
                        url = uri(repos.RemoteMavenRelease)
                    }
                    credentials {
                        username 'admin'
                        password '!Passw0rd'
                    }
                }
                ...
                mavenCentral()
            }
        }
        plugins.withType(MavenPublishPlugin) {
            publishing {
                ...
                repositories {
                    ...
                    maven {
                        name = 'RemoteMaven'
                        if (project.version.endsWith('-SNAPSHOT')) {
                            url = uri(repos.RemoteMavenSnapshot)
                        } else {
                            url = uri(repos.RemoteMavenRelease)
                        }
                        credentials {
                            username 'admin'
                            password '!Passw0rd'
                        }
                ...

A new task will then appear if we call gradle tasks: publishMavenJavaPublicationToRemoteMavenRepository (pretty long but this will be solved afterwards)

This when called will publish our data on the remote repo choosing the right path for snapshots and releases

004-Using "external" projects

Just take the demo003 and copy as demo004

Create then a new project demo004lib at the same level of the demo004 with a java-library type

Initialize the repos on the library

Remove eveything but the plugins and add the maven plugin and the package/version

plugins {
   id 'java'
   id 'java-library'
   id 'maven-publish'
}
group 'org.kendar'
version '1.0.0-SNAPSHOT'

Add the repository definition into build.gradle

ext.repos = [
        'LocalMaven'   : "file://${projectDir}/../maven"
]

And then the deployment part. Note that not using the "subprojects" we will add this part directly

publishing {
    publications {
        mavenJava(MavenPublication) {
            from project.components.java
        }
    }
    repositories {
        maven {
            name = 'LocalMaven'
            url = uri(repos.LocalMaven)
        }
    }
}

Remove the test class :)

Now we can "gradle build publish" the jar :) Remember that we added only the local maven dir repo!!!

Use the library inside demo004

Inside the service library we will add the dependency (do you remember about the various ways to define dependencies)

dependencies {
    implementation group: 'org.kendar', name: 'demo004lib', version: '1.0.0-SNAPSHOT'
}

Now with the gradle build everything will work :)

We can do a "gradle build publish" and we will see the dependency added on the disk repository pom:

...
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.kendar</groupId>
  <artifactId>services</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.kendar</groupId>
      <artifactId>demo004lib</artifactId>
      <version>1.0.0-SNAPSHOT</version>
      ...

Everything is working fine!

Add the dependency on the main project

We then would like to define the version of the dependency in the main project. In the main demo004 build.gradle we will add a new variable. The dependencies, if you remember, are in plain text

ext.dependenciesVersion = [
        'demo004lib'   : "1.0.0-SNAPSHOT"
]

And inside the "services" build gradle we will now write the following (note the version!)

dependencies {
    implementation group: 'org.kendar', name: 'demo004lib', 
        version: dependenciesVersion.demo004lib
}

We can now build with "gradle build publishMavenJavaPublicationToLocalMavenRepository"

And now we can see from the maven repo that everything have been published!

Simplify the publishing task names

You have notice how long are the task names generated by gradle. There is a way to reduce all this.

Let's start with the demo004lib, here there is only one publish task In the build.gradle add the following. We are telling gradle that a new task exists depending on the original task "publish"

task publishDir(dependsOn:'publish'){
}

When running "gradle tasks --all" into the demo004lib just modified appears a new task!

gradle init

And running it will publish the artifact to the local repository

It is now possible to do the same. Running "gradle tasks" the relevant lines are

Inside the demo004 build.gradle we can then add in afterEvaluate the needed tasks. Of course in the section of the subprojects containing the MavenPublishPlugin (maven)

Here we define a new item, the task. It can contain real java code :)

plugins.withType(MavenPublishPlugin) {
    ...
    task publishDir(dependsOn:'publishMavenJavaPublicationToLocalMavenRepository'){}

    task publishRemote(dependsOn:'publishMavenJavaPublicationToRemoteMavenRepository'){}

    task publishAll(){
        dependsOn 'publishDir'
        dependsOn 'publishRemote'
    }
    ...

Running "gradle publishAll" will spread our jars!!

005-Using Spring boot

Copy then the demo003 to demo005! I choose to transform the startup project in a Spring boot application!

Choose the version

First we should add the version of the plugins that are needed to build a spring boot app.

Two plugins will be used for this target with a new ext.version array in the build.gradle

ext.versions = [
        'SpringBoot'        : '2.2.2.RELEASE',
        'SpringDependencies': '1.0.8.RELEASE'
]

Adding the spring boot plugins!

Inside the startup project the following is needed. To build the project we need the following libs in classpath. The versions variable contains the dependency version. The rest of classpath is taken from Maven Central.

A simple way to write this would be the following.

plugin{
    id: 'org.springframework.boot:spring-boot-gradle-plugin:2.2.2.RELEASE'
    id: 'io.spring.gradle:dependency-management-plugin:1.0.8.RELEASE'
}

You can think... WTF!!! i can change the version with a variable!

plugin{
    id: 'org.springframework.boot:spring-boot-gradle-plugin:' + versions.SpringBoot
    id: 'io.spring.gradle:dependency-management-plugin:' + versions.SpringDependencies
}

Sorry, no, you can't. The only way i founded to do this is a combination of "buildscript" to add the plugins during the build, followed by "apply" to apply the plugins declared on the buildscript section

This section must be BEFORE the plugin section! These plugins are the first loaded!

buildscript {
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:'+versions.SpringBoot,
                'io.spring.gradle:dependency-management-plugin:'+versions.SpringDependencies
    }
}

plugins {
    ...

Right after the plugins we apply the plugins to the project

plugins {
    ...
}

apply plugin:'org.springframework.boot'
apply plugin:'io.spring.dependency-management'

And finally the plugin usage. Note the versions.SpringBoot in the first line!! The with dependency constraint we remove the optional dependency snakeyaml

Please notice the difference of declarations of the boot dependencies and boot starter web. Here there are "real strings" that can use the variables. All this stuff is normally inside a totally bloated pom.xml.

dependencies {
    ... //Other dependencies
    implementation group: 'org.springframework.boot', name: 'spring-boot-dependencies', version: versions.SpringBoot
    implementation 'org.springframework.boot:spring-boot-starter-web:'+versions.SpringBoot

    testImplementation 'org.springframework.boot:spring-boot-starter-test:'+versions.SpringBoot

    components {
        withModule('org.springframework:spring-beans') {
            allVariants {
                withDependencyConstraints {
                    it.findAll { it.name == 'snakeyaml' }
                        .each { it.version { strictly '1.19' } }
                }
            }
        }
    }
}

For me a definitve advantage is that i don't have to declare spring-boot as parent pom for my whole project (maven afaik does not support multiple inheritance)

Now you will see, runnign "gradle tasks" the bootRun task!

gradle init

Try everything

Add the a new source file: HelloGradleController.java

package org.kendar.controllers;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController("/")
public class HelloGradleController {

    @GetMapping
    public String helloGradle() {
        return "Hello Gradle!";
    }
}

And modify the main to the following

package org.kendar;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.Collections;

@SpringBootApplication
public class HelloWorld {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(HelloWorld.class);
        app.setDefaultProperties(Collections.singletonMap("server.port", "8094"));
        app.run(args);
    }
}

Now requiring "gradle bootRun" you can go to http://localhost:8094 and see a fantastic "Hello Gradle"

gradle init

006-Adding Junit and JaCoCo

Create a new project demo006, java application with JUnit 4 and package "org.kendar"

JUnit

Now you can see into the dependencies the following part, JUnit. You could then use the same idea from the previous parts if you have a multimodule project: adding the subprojects/afterEvaluate pattern, and a variable to declare the dependency version

dependencies {
    ...
    testImplementation 'junit:junit:4.12'
}

You can then run "gradle test" and...magic happens.

JaCoCo

Add then the jacoco plugin in the build.gradle

plugins {
    ...
    id 'jacoco'
}

And declare the JaCoCo version with the directory to use for the reports

jacoco {
    toolVersion = "0.8.5"
}

Then you can configure JaCoCo output

jacocoTestReport {
    reports {
        xml.enabled false
        csv.enabled false
        html.destination file("${buildDir}/jacocoHtml")
    }
}

And the rules to break the builds if coverage is not met. Whiting this a rule has been added to exclude gradle stuffs

jacocoTestCoverageVerification {
    violationRules {
        rule {
            limit {
                minimum = 0.5
            }
        }

        rule {
            enabled = false
            element = 'CLASS'
            includes = ['org.gradle.*']

            limit {
                counter = 'LINE'
                value = 'TOTALCOUNT'
                maximum = 0.3
            }
        }
    }
}

Then running "gradle clean check jacocoTestReport" You will generate the report into "demo006\build\jacocoHtml" dir

gradle init

007-Profiles

Clone the project demo002 into demo007

Profiles in gradle can be simulated in various way

First we should have a way to define a default profile. Here an environment variable is used. Simply add it to the main build.gradle. Remember that we are using a real programming language, not a bunch of xml files!

if (!hasProperty('buildProfile')) ext.buildProfile = 'default'

Properties for the build script

Now a new directory demo007/profiles is created containing two files

We can read the content of these into gradle's ext variable. Hem.. had you noticed some plain Java?? Here we are loading inside the project "ext" variable the properties that are needed by the build

Properties props = new Properties()
props.load(new FileInputStream(project.rootDir.path+'/profiles/'+ext.buildProfile+'.properties'))
props.each{prop->
    project.ext.set(prop.key,prop.value)
}

And of course use it inside all our scripts

println 'Deployment Target: ' + ext.deploymentServer

Java properties files

It is also possible to use specific property files to include in the various projects. To give an example you can create the directories demo007/profiles/default and demo007/profiles/prod containing the environment specific properties for the project

At first is needed a variable containing the demo007 path, this must be set inside the main build.gradle and used by all the subprojects

ext.rootDir='file://${projectDir}'

Then inside the subprojects we can add the specific files. For example in startup/build.gradle. Note that we are using the "ext" variables defined inside the root project!

sourceSets {
    main {
        java{
            resources {
                srcDir 'file://'+rootDir+'/profiles/'+buildProfile
                include 'environment.properties'
                output.resourcesDir='${buildDir}/resources/main'
            }
        }
    }
}

Then changing the main class to the following, it would be possible to print the content of the resource!

package org.kendar;

import java.io.IOException;
import java.util.Properties;

public class HelloWorld {
    public static void main(String[] args) {
        HelloWorldService service = new HelloWorldService();
        service.printHello();

        Properties props = new Properties();
        try 
        {
            props.load(HelloWorld.class.getClassLoader().getResourceAsStream("environment.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("I use maxThreads: " + props.getProperty("maxThreads"));
    }
}

Running "gradle run" will result in the following

gradle init

While adding a parameter

gradle run -PbuildProfile=prod

gradle init

Notice the "maxThreads" and Deplyoment target values!!

008-Default variables

${projectDir}: The project directory ${buildDir}: The target build directory


Last modified on: January 29, 2020