Creative Commons -lisenssi

Gradle on ns. build automation työkalu jonka tarkoituksena on automatisoida projektinhallintaan liittyviä tehtäviä, kuten ohjelman kääntaminen ja testaus.

Gradlea käytetään pääsääntöisesti java projektien kehitykseen. Sille löytyy kuitenkin myös (epävirallisia) Python plugineja, jotka mahdollistavat käytön myös Python projektien kehitykseen. Python plugineja saa käyttää omassa harjoitustyössä, mutta niiden toimivuutta ei ole kurssinhenkilökunnan puolelta testattu. Toisaalta, koska Python koodia ei tarvitse (eikä helposti voikkaan) kääntää, gradle on tavallaan turhan monimutkainen Pythonin kanssa käytettäväksi. Suosittelemme Pythonille ennemmin Poetrya.

Johdatus Gradlen konfigurointiin (Javalle)

Nämä ohjeet ovat melkein suora kopio kurssin Ohjelmistotuotanto kurssin sivuilta

Tehdään gradle-projekti alusta asti itse. Tee projektirepoositooriossi uusi hakemisto ja mene hakemistoon.

Kokeile toimiiko koneessasi komento gradle. Huomaa, että esimerkiksi fuksiläppäreissä on asennettuna erittäin vanha gradlen versio. Komennon suorittaminen näyttää mikä versio on kyseessä

mluukkai@melkki:~$ gradle
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :help

Welcome to Gradle 4.4.1.

Jos komento ei toimi tai versio on vanhempi kuin 6.7, asenna uusin versio Gradlesta täällä olevien ohjeiden mukaan.

Aloita antamalla komento gradle:

> Task :help

Welcome to Gradle 8.6.

Directory '/Users/jezberg/Documents/teaching/tiralabra/gradletest' does not contain a Gradle build.

To create a new build in this directory, run gradle init

For more detail on the 'init' task, see https://docs.gradle.org/8.6/userguide/build_init_plugin.html

For more detail on creating a Gradle build, see https://docs.gradle.org/8.6/userguide/tutorial_using_tasks.html

To see a list of command-line options, run gradle --help

For more detail on using Gradle, see https://docs.gradle.org/8.6/userguide/command_line_interface.html

For troubleshooting, visit https://help.gradle.org
...

Ohje kertoo, että nykyisessä kansiossa ei ole Gradle buildia, ts. että emme ole alustaneet gradle projektiamme vielä. Gradle-projekti määritellään projektihakemiston juureen sijoitettavan tiedoston build.gradle avulla.

Saat luotua tiedoston suorittamalla taskin init (eli antamalla komennon gradle init). Ajetaan siis seuraavaksi init käsky.

jezberg@LM2-500-27156 gradletest % gradle init 

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 

Valitse basic (type of project), Groovy (build script DSL) ja anna projektille nimi. Valitse myös “no” kysymykseen uusista API toiminnoista.

Huomaat että operaation jälkeen hakemistoon on tullut tiedoston build.gradle lisäksi muutakin:

$ ls -la
drwxr-xr-x  9 jezberg  ATKK\hyad-all   288 Feb  6 17:06 .
drwxr-xr-x  8 jezberg  ATKK\hyad-all   256 Feb  6 16:55 ..
-rw-r--r--  1 jezberg  ATKK\hyad-all   214 Feb  6 17:06 .gitattributes
-rw-r--r--  1 jezberg  ATKK\hyad-all   103 Feb  6 17:06 .gitignore
-rw-r--r--  1 jezberg  ATKK\hyad-all   201 Feb  6 17:06 build.gradle
drwxr-xr-x  4 jezberg  ATKK\hyad-all   128 Feb  6 17:06 gradle
-rwxr-xr-x  1 jezberg  ATKK\hyad-all  8692 Feb  6 17:06 gradlew
-rw-r--r--  1 jezberg  ATKK\hyad-all  2918 Feb  6 17:06 gradlew.bat
-rw-r--r--  1 jezberg  ATKK\hyad-all   345 Feb  6 17:06 settings.gradle

Näistä hakemisto .gradle kannattaa gitignoroida. Gradle-projekteissa tulee gitignoroida aina myös hakemisto build mihin kaikki gradle-taskien generoimat tiedostot sijoitetaan. Gradle luokin valmiiksi tilanteeseen sopivan gitignore-tiedoston.

Tavoitteenamme on lisätä projektiin Java-koodia ja JUnit-testejä. Oletusarvoisesti gradle ei ymmärrä Javasta mitään, mutta ottamalla käyttöön java-pluginin, se lisää projektille uusia, Javan kääntämiseen liittyviä taskeja.

Aloita antamalla komento gradle:

> Task :help

Welcome to Gradle 6.7.

To run a build, run gradle <task> ...

To see a list of available tasks, run gradle tasks

To see a list of command-line options, run gradle --help

To see more detail about a task, run gradle help --task <task>

For troubleshooting, visit https://help.gradle.org
...

Ohje neuvoo meitä seuraavasti: “To see a list of available tasks, run gradle tasks”, eli kokeillaan komentoa gradle tasks:

> Task :tasks

------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'intro_gradle'.
components - Displays the components produced by root project 'intro_gradle'. [incubating]
dependencies - Displays all dependencies declared in root project 'intro_gradle'.
dependencyInsight - Displays the insight into a specific dependency in root project 'intro_gradle'.
dependentComponents - Displays the dependent components of components in root project 'intro_gradle'. [incubating]
help - Displays a help message.
model - Displays the configuration model of root project 'intro_gradle'. [incubating]
outgoingVariants - Displays the outgoing variants of root project 'intro_gradle'.
projects - Displays the sub-projects of root project 'intro_gradle'.
properties - Displays the properties of root project 'intro_gradle'.
tasks - Displays the tasks runnable from root project 'intro_gradle'.

Komento listaa käytettävissä olevat taskit. Gradlen dokumentaatio kuvaa taskeja seuraavasti:

Each project is made up of one or more tasks. A task represents some atomic piece of work which a build performs. This might be compiling some classes, creating a JAR, generating Javadoc, or publishing some archives to a repository.

Taskit ovat siis “komentoja”, joita voimme suorittaa gradle-projekteille.

Otetaan nyt käyttöön java-plugin lisäämällä tiedostoon build.gradle rivi:

plugins {
    id 'java'
}

Kun nyt suoritetaan komento gradle tasks huomataan että listalla on uusia, java-pluginin lisäämiä taskeja:

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

...

Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.

Jos suoritamme esimerkiksi taskin build eli komennon gradle build, on tulostus seuraava

BUILD SUCCESSFUL in 885ms

Tämä ei vielä oikein kerro mitään. Java-pluginin dokumentaatio selventää asiaa:

Build siis riippuu kahdesta pluginista check ja assemble:

Myös nämä riippuvat muutamasta taskista. Lopulta käy niin, että gradle build suorittaa kaikki seuraavat taskit

compileJava
processResources 
classes
jar
assemble
compileTestJava
processTestResources
testClasses
test
check

Eli build suorittaa koodin käännöksen, paketoinnin jar-tiedostoksi sekä projektiin liittyvät testit.

Ennen kun siirryt eteenpäin, suorita gradle clean, joka poistaa kaikki edellisen komennon luomat tiedostot.

Järkevä editori

Älä käytä tällä kertaa NetBeansia tai muutakaan IDE:ä vaan tee kaikki koodi ja konfiguraatiot tekstieditorilla.

Hyvä vaihtoehto editoriksi on laitoksen koneilta ja fuksikannettavista löytyvä Visual Studio Code

Koodin lisääminen projektiin

Gradle olettaa, että ohjelman koodi sijaitsee projektin juuren alla olevassa hakemistossa src/main/java. Luo hakemisto(t) ja tiedosto src/main/java/Main.java ja sille esim. seuraava sisältö:

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello gradle!");
    }
}

Suorita sitten task compileJava ja tarkastele projektihakemiston sisältöä komennolla tree:

$ tree
.
├── build
│   ├── classes
│   │   └── java
│   │       └── main
│   │           └── Main.class
...

Muista että gradle kommennot täytyy suorittaa projektin juuresta, , eli hakemistossa missä tiedosto build.gradle sijaitsee. Task compileJava on siis luonut hakemiston build ja sen sisälle käännöksen tuloksena olevan class-tiedoston.

Suorita käännetty koodi menemällä hakemistoon ja antamalla komento java Main:

$ cd build/classes/java/main/
$ java Main
Hello gradle!

Yleensä Java-koodia ei suoriteta käyttämällä suoraan class-tiedostoja. Parempi tapa on pakata koodi jar-tiedostoksi.

Jar-tiedosto muodostetaan gradlen taskilla jar. Help kertoo seuraavaa:

% gradle help --task jar

> Task :help
Detailed task information for jar

Path
     :jar

Type
     Jar (org.gradle.api.tasks.bundling.Jar)

Options
     --rerun     Causes the task to be re-run even if up-to-date.

Description
     Assembles a jar archive containing the classes of the 'main' feature.

Group
     build

BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed

Määritellään taskia varten pääohjelman sijainti lisäämällä seuraava tiedoston build.gradle loppuun:

jar {
    manifest {
        attributes 'Main-Class': 'Main'
    }
}

Huomaa, että tiedoston build.gradle pitää alkaa plugins-määrittelyllä. Jos se ei ole alussa, törmäät seuraavaan virheilmoitukseen:

FAILURE: Build failed with an exception.

* Where:
Build file '/Users/mluukkai/dev/intro_gradle/build.gradle' line: 7

...

@ line 7, column 1.
  plugins {

Palaa nyt projektihakemistoon ja suorita jar-tiedoston generoiva task jar (eli anna komento gradle jar).

Voit suorittaa syntyneen jar-tiedoston seuraavasti (huomaa että tiedoston nimi riippuu projektisi nimestä ja todennäköisesti ei ole gradle-test.jar):

$ java -jar build/libs/gradle-test.jar
Hello gradle!

application-plugin

Koodin voi (periaattessa) suorittaa myös komennolla gradle run.

Komento aiheuttaa kuitenkin nyt virheilmoituksen Task ‘run’ not found in root project.

Syynä tälle on se, että task run ei ole java-pluginin vaan application-pluginin määrittelemä. Otetaan tämä käyttöön muuttamalla tiedoston build.gradle alku muotoon

plugins {
    id 'java'
    id 'application'
}

Itseasiassa java-pluginin määrittelevää riviä ei nyt edes tarvita, sillä application sisältää myös sen määrittelevät taskit.

Komento gradle run aiheuttaa nyt virheen No main class specified.

Pluginin dokumentaatio kertoo, että pääohjelman sisältävä luokka, eli main class tulee määritellä lisäämällä tiedostoon build.gradle seuraava rivi:

application {
    mainClass = 'Main'
}

Nyt ohjelman suorittaminen taskin avulla toimii:

$ gradle run
> Task :compileJava UP-TO-DATE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE

> Task :run
Hello gradle!

BUILD SUCCESSFUL in 1s

Suorittaminen kannattanee tehdä optiota -q käyttäen, jolloin gradle jättää omat tulosteensa tekemättä:

$ gradle run -q
Hello gradle!

Laajennetaan ohjelmaa siten, että se kysyy Scanner-olion avulla käyttäjän nimeä:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("kuka olet: ");
        String nimi = scanner.nextLine();

        System.out.println("Hello " + nimi);
    }
}

Jos ohjelmasta tehdään jar-tiedosto (suorittamalla komento gradle jar), toimii se odotetulla tavalla:

$ java -jar build/libs/gradle-test.jar
kuka olet:
mluukkai
Hello mluukkai

Jos ohjelma suoritetaan gradlen run-taskin avulla, seurauksena on virhe:

$ gradle run
kuka olet:
Exception in thread "main" java.util.NoSuchElementException: No line found
        at java.util.Scanner.nextLine(Scanner.java:1540)
        at Main.main(Main.java:7)

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':run'.
> Process 'command '/Library/Java/JavaVirtualMachines/jdk1.11.0_101.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1

Syynä tälle on se, että oletusarvoisesti gradlen task run ei liitä terminaalia syötevirtaan System.in. Asia saadaan korjautumaan lisäämällä tiedostoon build.gradle seuraava:

run {
    standardInput = System.in
}

Nyt komento gradle run toimii.

Toinen luokka

Lisätään ohjelmalle luokka, jonka avulla on mahdollista laskea kertolaskuja. Sijoitetaan luokka pakkaukseen ohtu eli tiedostoon src/main/java/ohtu/Multiplier.java

package ohtu;

public class Multiplier {
    private int value;
    public Multiplier(int value) {
        this.value = value;
    }

    public int multipliedBy(int other) {
        return value * other;
    }

}

Käytetään luokkaa pääohjelmasta käsin. Huomaa, että koska luokka on eri pakkauksessa kuin pääohjelma, tulee pakkaus importata:

import java.util.*;
import ohtu.Multiplier;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Multiplier kolme = new Multiplier(3);
        System.out.println("anna luku ");
        int luku = scanner.nextInt();

        System.out.println("luku kertaa kolme on " + kolme.multipliedBy(luku) );
    }
}

Testit

Tehdään nyt luokalle JUnit-testi. Gradle olettaa, että JUnit-testit sijoitetaan hakemiston src/test/java alle. Sijoitamme testin samaan pakkaukseen kuin testattavan luokan, eli tiedostoon src/test/java/ohtu/MultiplierTest.java

package ohtu;

import static org.junit.Assert.*;
import org.junit.Test;

public class MultiplierTest {

    @Test
    public void kertominenToimii() {
        Multiplier viisi = new Multiplier(5);

        assertEquals(5, viisi.multipliedBy(1));
        assertEquals(35, viisi.multipliedBy(7));
    }

}

Yritetään suorittaa testit komennolla gradle test. Seurauksena on suuri määrä virheilmoituksia. Virheet tapahtuvat taskin compileTestJava eli testien kääntämisen aikana:

$ gradle test

> Task :compileTestJava FAILED
/Users/mluukkai/dev/intro_gradle/src/test/java/MultiplierTest.java:3: error: package org.junit does not exist
import static org.junit.Assert.*;
                       ^
/Users/mluukkai/dev/intro_gradle/src/test/java/MultiplierTest.java:4: error: package org.junit does not exist
import org.junit.Test;
                ^
/Users/mluukkai/dev/intro_gradle/src/test/java/MultiplierTest.java:8: error: cannot find symbol
    @Test
     ^
  symbol:   class Test
  location: class MultiplierTest
/Users/mluukkai/dev/intro_gradle/src/test/java/MultiplierTest.java:12: error: cannot find symbol
        assertEquals(5, viisi.multipliedBy(1));
        ^
  symbol:   method assertEquals(int,int)
  location: class MultiplierTest
/Users/mluukkai/dev/intro_gradle/src/test/java/MultiplierTest.java:13: error: cannot find symbol
        assertEquals(35, viisi.multipliedBy(7));
        ^
  symbol:   method assertEquals(int,int)
  location: class MultiplierTest
5 errors

Syynä virheille on se, että projektimme ei tunne testien importtaamaa koodia:

import static org.junit.Assert.*;
import org.junit.Test;

JUnit-kirjasto on siis ohjelmamme testien käännöksen aikainen riippuvuus.

Riippuvuudet

Käytännössä riippuvuudet ovat jar-tiedostoja, jotka sisältävät käytettävien apukirjastojen, eli tässä tapauksessa JUnitin koodin. Gradlen samoin kuin Mavenin hyvä puoli on se, että ohjelmoijan ei tarvitse itse latailla riippuvuuksia, riittää kun projektin riippuvuudet määritellään tiedostossa build.gradle ja gradle hoitaa automaattisesti riippuvuuksien lataamisen, jos niitä ei koneelta löydy.

Tarvittava määrittely on seuraava:

repositories {
    mavenCentral()
}

dependencies {
    testImplementation group: 'junit', name: 'junit', version: 'latest-release'
}

Ensimmäinen osa repositories kertoo gradlelle mistä sen tulee etsiä riippuvuuksia. mavenCentral on eräs niistä paikoista, johon on talletettu suuri määrä gradlen ja mavenin käyttämiä kirjastoja. repositories-osassa voidaan määritellä myös useita paikkoja joista gradle käy etsimässä projektiin määriteltyjä riippuvuuksia.

Toinen osa määrittelee, että testImplementation-vaiheeseen otetaan käyttöön JUnit-kirjaston uusin versio. Käytännössä tämä tarkoittaa, että kääntäessään testien koodia gradle liittää JUnitin classpathiin.

Lisättävän riippuvuuden erittelevälle riville voidaan käyttää myös vaihtoehtoista syntaksia, missä riippuvuus, ja sen versio ilmaistaan yhtenä merkkijonona:

dependencies {
    testImplementation 'junit:junit:latest-release'
}

Kun suoritamme uudelleen komennon gradle test kaikki toimii.

Muista vielä kokeilla rikkoa testi ja varmistaa että testit huomaavat virheen.