Gradle
(Jos teet jo harjoitustyötä) Lue ensin moodle
Kurssin Moodle sivujen sisältämä tieto kannattaa lukea aina kurssin aluksi! Siellä selitetään kurssin tavoitteista, arvostelusta ja rakenteesta tarkemmin. Moodlesta löytyy myös suorat linkit näiden sivujen olennaisimpiin osiin. Nämä sivut olettavat, että tunnet moodlessa olevan materiaalin ja voivat tuntua sekavilta jos et.
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.