Liigu peamise sisu juurde

Gradle elutsükkel

Sissejuhatus

Gradle’i tõhusaks kasutamiseks tuleb mõista erinevust ehituse seadistamise ja selle käivitamise vahel. Sarnane kood võib käivituda väga erinevatel aegadel ning loogika paigutamine valesse faasi on levinud vigade allikas, mida on keeruline diagnoosida.

Antud peatükk käsitleb Gradle’i kolme ehitusfaasi, kuidas haakuda ülesannete täitmise elutsükliga ning kuidas töötab Gradle vahemälu süsteem.

Kolm etappi

Iga Gradle poolt algatatud ehitusprotsess läbib järgnevat kolme faasi:

1. Initsialiseerimine

Gradle loeb settings.gradle faili, et määrata, millised projektid kuuluvad ehitusse. Ühe projektiga ehituse puhul on see minimaalne. Mitme projektiga ehituse korral avastab Gradle siinkohal kõik alamprojektid.

2. Konfigureerimine

Gradle analüüsib kõik build.gradle failid üle ja loob ülesannete graafi - suunatud atsüklilise graafi kõigist ülesannetest ja nende sõltuvustest. Iga build.gradle lause, mis asub väljaspool ülesande tegevust, käivitatakse selles faasis.

Oluline tähelepanek: build.gradle faili ülemisel tasemel kirjutatud kood käivitatakse iga kord, kui Gradle’i käivitad, olenemata sellest, millist ülesannet jooksutad. Isegi ./gradlew clean korral.

// This runs during CONFIGURATION — every single build
println "Configuring the build..."

tasks.register('hello') {
// With tasks.register this block is lazy — it only runs when the task is needed.
// With the older 'task' keyword, this would run eagerly during every configuration.
println "Configuring hello task..."

doLast {
// This runs during EXECUTION — only when 'hello' is executed
println "Hello!"
}
}
info

Vanemad Gradle versioonid kasutavad ülesannete loomiseks task võtmesõna:

task hello { ... }

See loob ülesande kohe konfiguratsiooni ajal. Kaasaegne tasks.register on laisk (lazy) - konfiguratsiooniplokk käivitatakse alles siis, kui ülesannet tegelikult vaja läheb. task võtmesõna on alates Gradle 9-st aegunud (deprecated) ja eemaldatakse Gradle 10-s.

3. Käivitamine

Gradle käivitab nõutud ülesanded järjekorras, mis on määratud ülesannete graafi poolt. Ainult kood, mis asub plokkides doFirst { } ja doLast { }, käivitatakse selles faasis.

hoiatus

Kõrvalmõjude (failikirjutamine, võrgupäringud, väljatrükid) paigutamine otse build.gradle faili väljapoole ülesannete tegevusi tähendab, et need käivitatakse igal ehitusel. Isegi siis, kui see ei ole selle loogikaga seotud. See aeglustab kõiki ehitusi ja võib põhjustada ootamatut käitumist.

Ülesande elutsükklite hook'id

doFirst and doLast

doFirst ja doLast lisavad loogikat ülesande täitmisele:

tasks.named('compileJava') {
doFirst {
println "About to compile Java sources..."
}
doLast {
println "Compilation finished."
}
}

doFirst käivitatakse enne ülesande peamist tegevust. doLast käivitatakse pärast seda. Võid lisada mitu doFirst ja doLast plokki, need täidetakse lisamise järjekorras.

finalizedBy

finalizedBy tagab, et üks ülesanne käivitatakse alati pärast tesit, isegi kui esimene ebaõnnestub:

test.finalizedBy jacocoTestReport

See on kasulik näiteks igasuguste raportide koostamiseks.

dependsOn

dependsOn tagab, et üks ülesanne käivitatakse enne teist:

jacocoTestReport.dependsOn test

Erinevalt finalizedBy-st ei käivitata seda, kui sõltuv ülesanne ebaõnnestub.

Ülesannete graaf

Selleks et paremat arusaama tekitada endale sellest, mida Gradle käivitab, tasub uurida ülesannete graafi lähemalt:

gradle.taskGraph.whenReady { graph ->
println "Tasks to execute:"
graph.allTasks.each { task ->
println " - ${task.path}"
}
}

./gradlew test ja ./gradlew build käsud annavad erinevad ülesannete nimekirjad, näidates täpselt, milliseid ülesandeid iga käsk käivitab.

Inkrementaalsed ehitused

Gradle jälgib iga ülesande sisendeid ja väljundeid. Kui kumbki pole alates viimasest käivitamisest muutunud, jäetakse ülesanne vahele märgendiga UP-TO-DATE.

Näiteks compileJava kohta:

  • sisendid: Java lähtekoodi failid, kompilaatori konfiguratsioon, sõltuvused
  • väljundid: kompileeritud .class failid build/classes kataloogis

Kui käivitada ./gradlew build kaks korda ilma lähtekoode muutmata, jätab teine käivitus peaaegu kõik vahele:

> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :jar UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :test UP-TO-DATE

Seetõttu on Gradle iteratiivses arenduses kiire - tegelikult käivitatakse ainult need ülesanded, mida muudatused mõjutavad.

Vahemälu

Inkrementaalsed ehitused toimivad ühe ehituskataloogi piires. Gradle'sse on ka sisseehitatud vahemälu funktsionaalsus. See tähendab, et ülesannete väljundid salvestatakse nende sisendite alusel. See võimaldab nende taastamise isegi peale ./gradlew clean käsu käivitamist.

Vahemälu sisse lülitamine

See funktsionaalsus pole vaikimisi sisse lülitatud. Selle lubamiseks tuleb settings.gradle faili muuta järgnevalt:

buildCache {
local {
enabled = true
directory = new File(rootDir, '.gradle/build-cache')
}
}

UP-TO-DATE vs FROM-CACHE

OlekMis juhtus
UP-TO-DATEVäljundid on endiselt kataloogis build/. Midagi pole vaja teha.
FROM-CACHEVäljundid kustutati (nt käsuga clean), kuid taastati vahemälust.

Võite ka ise katsetada seda järgmiste käskudega:

./gradlew test --build-cache      # First run: executes normally
./gradlew test --build-cache # Second run: UP-TO-DATE
./gradlew clean # Deletes build/
./gradlew test --build-cache # Third run: FROM-CACHE

Kolmas käivitus ei kompileeri ega käivita teste uuesti. Gradle tuvastab, et sisendid on täpselt samad varasema käivitusega, ja taastab vahemällu salvestatud väljundid otse.

Millal on võimalik vahemälu funktsionaalsust

Kõik ülesanded ei toeta vahemälu funktsionaalsust. Ülesannet saab vahemällu paigutada, kui

  • Selle sisendid ja väljundid on täielikult deklareeritud
  • Väljund on deterministlik (samad sisendid annavad alati sama tulemuse)

Kompileerimine ja testide käivitamine vastavad nendele tingimustele. Ülesanded, mis sõltuvad välisest olekust (nt praegune aeg, võrgupäringud), ei pruugi seda olla.

Miks see on CI jaoks oluline

CI (Continuous Integration) serverites algab iga ehitusprotsess tavaliselt puhtalt lehelt. Ilma vahemäluta kompileeritakse terve projekt iga kord nullist. Vahemälu abil saab CI-server taaskasutada varasemate ehituste väljundeid, vähendades oluliselt ehitsprotsessile kuluvat aega. Samuti tuleb see kasuks olukorras, kus mõni muudatus võetake tagasi. Kuna vana olek on jätkuvalt vahemälus olemas, siis projekti uuesti ehitamine on kohene.