Helps teams build, automate and deliver better software, faster.volgens de gradle.org website.
Wat is dat, een build tool?
Gradle is een build tool die de automatisatie van releasen, builden, testen, configureren, dependencies en libraries managen, … eenvoudiger maakt. Kort gezegd: het maakt het leven van een ontwikkelaar eenvoudiger. In een config bestand genaamd build.gradle
schrijft men met Groovy, een dynamische taal bovenop de JVM, op een descriptieve manier hoe Gradle de applicatie moet beheren.
Andere bekende build tools:
Maven
,Ant
(in XML) voor Java(C/Q)Make
(custom config) voor C/C++yarn
,grunt
,gulp
, (in JS) … voor JSnuget
(custom config, XML) voor .NET
Naast het beheren van compilaties, verzorgt Gradle ook libraries. Het is dus ook een dependency management systeem, zoals Composer voor PHP of Node Package Manager voor NodeJS. Sommige build tools doen dus meer dan builden en beheren en downloaden ook automatisch je libraries, maar dat is niet altijd het geval!
Ontleding van een Gradle config file
Een voorbeeld van een eenvoudige buildfile is hieronder terug te vinden:
plugins { id 'java' } group 'be.kuleuven.ses' version '1.0-SNAPSHOT' sourceCompatibility = 1.10 repositories { mavenCentral() } dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' testImplementation group: 'org.hamcrest', name: 'hamcrest-library', version: '2.2' } test { useJUnitPlatform() }
Hier onderscheiden we de volgende zaken:
- Het project is een java 10 project (er zijn ook nog andere talen op de JVM; zoals Kotlin—Gradle kan ook Kotlinprogramma’s compileren)
- Het project komt van
be.kuleuven.ses
, versie1.0-SNAPSHOT
. - Dependencies downloaden via de standaard maven central (ingebouwde URL).
- Hiervan moet Gradle
junit-jupiter-api 5.6.0
downloaden voor de testen - Hiervan moet Gradle
junit-jupiter-engine
(zelfde versie) gebruiken om testen te runnen - Hiervan moet Gradle
hamcrest-library 2.2
downloaden voor de testen
- Hiervan moet Gradle
Dependencies vallen (meestal) in twee categorieƫn:
implementation
(productie dependencies)testImplementation
(test dependencies)
Merk op dat een typisch gradle project geen jars mee zipt, zoals de oefeningen. Die worden dus automatisch door deze tool gedownload, en in de juiste map geplaatst.
Merk op dat bij Gradle 6.x, t.o.v. de vorige versies, de runtime dependencies nu implementation
in plaats van compile
heten, en de test dependencies testImplementation
en testRuntimeOnly
in plaats van testCompile
. Zie ook: Declaring dependencies in de Gradle docs.
Ontleding van een Gradle project mappenstructuur
Als we kijken naar de bestanden- en mappenstructuur van een voorbeeld Gradle project, vinden we dit terug:
build/ src/ main/ java/ be/ package1/ ClassMain test/ java/ be/ package1/ ClassMainTest resources/ css/ js/ gradle/ wrapper/ gradle-wrapper.jar gradle-wrapper.properties build.gradle gradlew.bat gradlew settings.gradle
Hier onderscheiden we de volgende zaken:
- Broncode (
.java
bestanden) insrc/main/java
ensrc/test/java
, met productie- en testcode gescheiden. - Gecompileerde code (
.class
bestanden) in debuild/
(of ook welout
) folder. - Eventueel
resources
voor webapps e.d. (images, css, …) gradle
map, en executable (gradlew.bat
win en shell script voor unix)gradle
settings en build file.
Wat gebeurt er nu precies als je gradlew.bat
uitvoert?
- Download de juiste versie van Gradle zelf (!! dus installatie is niet nodig), afhankelijk van de specificaties in de properties file.
- Download de juiste libraries om het project te kunnen runnen.
Aan deze wrapper kan je commando’s meegeven. Bijvoorbeeld, gradlew.bat test
:
Wouters-Air:singleton wgroeneveld$ ./gradlew test > Task :test FAILED ShoppingCartResourceTest > get_fromMultipleResources_shouldNotIncreaseDBHandleVarCount FAILED java.lang.AssertionError at ShoppingCartResourceTest.java:25 2 tests completed, 1 failed FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':test'. > There were failing tests. See the report at: file:///Users/jefklak/development/brainbaking/content/teaching/ses/singleton/build/reports/tests/test/index.html
Dit is exact hetzelfde als in IntelliJ de testen uitvoeren met de knop ‘Run’:
Waarom een build tool gebruiken?
De grootste voordelen hiervan zijn onder andere:
- Een kleine voetafdruk van de broncode (repository). Het is niet nodig om jars als libraries in een
lib/
folder zelf bij te houden: Gradle doet dit immers voor jou. - Een project bootstrappen in luttele seconden: download code, voer de Gradle wrapper uit, en alles wordt vanzelf klaargezet (de juiste Gradle versie, de juiste library versies, …)
- Platform-onafhankelijk processen besturen die altijd op dezelfde manier werken: een taak uitvoeren op mijn PC doet exact hetzelfde als bij jou, dankzij de beschrijving van de stappen in de config file.
Het is bijvoorbeeld bij de oefeningen eenvoudig om een test library als junit
mee te leveren, zonder de bestanden zelf aan te leveren, dankzij het toevoegen van twee regels in de dependencies block (zie hieronder: “Gradle en JUnit integratie”).
Een nieuw Gradle project maken
Dit kan op twee manieren:
- Via IntelliJ: File -> New Project, kies voor “Java”, en kies als build tool “Gradle”. Je moet dan nog kiezen voor de Groovy build files. Dit maakt automatisch de juiste bestanden aan (
src/main/java
,build.gradle(.kts)
,gradle
wrapper files) - Via commandline.
De tweede manier vereist de installatie van een lokale Gradle tool: zie Gradle Docs: installing manually (package managers zoals brew
en apt
voorzien meestal een gradle
entry). Zorg er net zoals bij De Java installatie ervoor dat de bin folder in je $PATH
is toegevoegd (zie documentatie).
Vanaf dan kan je met het gradle
commando in een lege map een nieuw project initialiseren. Zie Gradle Docs: Bootstrapping new projects
$ gradle init
Dit start een interactieve shell waarin je dezelfde keuzes als bij een nieuw IntelliJ project moet ingeven (Kotlin/Gradle, …). Dit maakt de folders gradle, .gradle, lib aan en de files settings.gradle, gradlew, en gradlew.bat. De broncode van je programma (Java code + build.gradle
) zit in de lib/
folder!
Het is ook via die gradle
commando dat je wrapper bestanden kan aanmaken (gradle wrapper
) of updaten.
Gradle en Maven integratie
Gradle voorziet een plugin genaamd ‘maven-publish’ die deze bestanden automatisch aanmaakt. Activeer de plugin en voeg een publishing
tag toe met de volgende properties:
plugins { id 'java' id 'maven-publish' // toevoegen! } publishing { publications { maven(MavenPublication) { groupId = project.group.toString() version = version artifactId = 'projectnaam' from components.java } } repositories { maven { url = "C:\\Users\\u0158802\\development\\java\\maven-repo" } } }
Deze uitbreiding voegt de target publish
toe aan Gradle. Dus: ./gradlew publish
publiceert de nodige bestanden in de aangegeven folder. Een Gradle project die daar gebruik van wenst te maken dient enkel een tweede Maven Repository plaats te definiƫren:
repositories { mavenCentral() maven { url = "C:\\Users\\u0158802\\development\\java\\maven-repo" } }
Gradle en JUnit integratie
JUnit 5 splitst de test library op in een aantal submodules, waarvan er twee belangrijke zijn die we nodig hebben om te testen:
junit-jupiter-api
- nodig om testen te SCHRIJVEN (de API waar@BeforeEach
e.a. in zitten)junit-jupiter-engine
- nodig om testen UIT TE VOEREN (cmdline interface)
Aangezien Gradle verschillende test bibliotheken ondersteund, zoals ook TestNG, dient men in de Gradle build file ondersteuning voor elk framework te activeren. Dit is enkel nodig bij cmdline uitvoeren van de testen. Als je beslist om enkel binnen IntelliJ testen uit te voeren, verzorgt IntelliJ dit zelf, en is de jupiter-engine ook niet nodig.
test { useJUnitPlatform() testLogging.showStandardStreams = true } dependencies { // for WRITING tests, this will suffice: testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' // for RUNNING tests (cmdline, without IntelliJ), this is also needed: testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' }
Optionele test libraries zoals Hamcrest en Selenium/WebDriver kunnen daarna ook worden toegevoegd onder de testImplementation
groep.
Merk op dat dit betekent dat dependencies in de testRuntimeOnly
groep eigenlijk runtime dependencies zijn: deze worden niet gebruikt om mee te builden. Denk aan het verschil tussen statisch en dynamisch linken in C.
Welke Task moet ik uitvoeren?
./gradlew tasks --all
voorziet een overzicht van alle beschikbare taken voor een bepaald Gradle project, opgesplitst per fase (build tasks, build setup tasks, documentation tasks, help tasks, verification tasks). Plugins voorzien vaak extra tasks, zoals bovenstaande maven plugin.
Belangrijke taken zijn onder andere:
test
: voer alle unit testen uit. Een rapport hiervan is beschikbaar op build/reports/tests/test/index.html.clean
: verwijder alle binaries en metadata.build
: compile en test het project.publish
: (maven plugin) publiceert naar een repository.jar
: compile en package in een jar bestandjavadoc
: (plugin) genereert HTML javadoc. Een rapport hiervan is beschikbaar op build/docs/javadoc/index.html.
Onderstaande screenshot is een voorbeeld van een Unit Test HTML rapport voor de SESsy library:
Ik wil meer output bij het uitvoeren van mijn tasks!
De standaard output geeft enkel weer of er iets gelukt is of niet:
Meer informatie kan met de volgende parameters:
--info
, output LogLevelINFO
. Veel irrelevante info wordt ook getoond.--warning-mode all
, toont detail informatie van warning messages--stacktrace
, toont de detail stacktrace bij exceptions
De Gradle (wrapper) Upgraden
Indien de Gralde wrapper een oudere versie aanmaakt (< v6), update met gradle wrapper --gradle-version 6.0.1
. Gradle versie 6
of groter is vereist voor JDK 13
of groter.
Gradle/Java compatibiliteitsmatrix:
- JDK
21
: Gradle8.5
of nieuwer
Indien je de fout “Could not initialize class org.codehaus.groovy.reflection.ReflectionCache
” krijgt, betekent dit dat je JDK te nieuw is voor de gradle versie (bijvoorbeeld JDK 17
met Gradle 6.7
in plaats van 7.0
of nieuwer). Controleer de huidige gralde versie met gradle --info
of kijk in gradle/wrapper/gradle-wrapper.properties
.
Opgave
Maak een nieuw JavaFX (gradle) project aan. Maak een kleine darts-applicatie die gebruik maakt van de scorebord-library die je in het vorige deel hebt aangemaakt. De JavaFX applicatie bestaat uit een dartsbord met 3 concentrische cirkels. Je kan je naam ingeven in een tekstveld. Je kan op de cirkels klikken om een score te krijgen. Binnenste cirkel is 3 punten, middenste cirkel is 2 punten, buitenste cirkel is 1 punt. Dit punt wordt direct toegevoegd aan je scorebord. Met een load-knop moet een vorig scorebord ingeladen worden, met een save-knop moet je het huidige scorebord kunnen opslaan. De huidige winnaar moet ook steeds getoond worden in een label.
Aangezien IntelliJ je JavaFX project automatich als een module instelt is het belangrijk ook je eigen libraries in modules te steken. (Normaal gezien wordt er een naamloze module aangemaakt in het geval je dit niet doet, maar IntelliJ laat je met de standaard instellingen niet toe die naamloze modules te gebruiken.)
Voeg daarom aan je library project een module-info.java
file toe in de ./src/main/java
directory. Hieronder zie je een voorbeeld van hoe de file er moet uitzien voor je scorebord library. En hoe je JavaFX module-info file er moet uitzien om de library te kunnen gebruiken.
(Je moet de library zelf ook nog altijd als dependency toevoegen in je build.gradle
file zoals we al gewoon zijn)
In de scorebord source files
//module-info.java
//De naam van de module mag je in principe kiezen. In dit geval is het "be.kuleuven.scorebord"
module be.kuleuven.scorebord {
requires com.google.gson; // Je moet alle modules oplijsten waarvan jouw project afhankelijk is
exports be.kuleuven; // Moet de juiste package naam zijn
opens be.kuleuven to com.google.gson; // Geef je dependencies ook toegang tot je package
}
In de darts JavaFX source files
//module-info.java
module be.kuleuven.darts {
requires javafx.controls;
requires javafx.fxml;
requires be.kuleuven.scorebord; // Mijn scorebord module
opens be.kuleuven.darts to javafx.fxml;
exports be.kuleuven.darts;
}