Gradle Kasutamine IntelliJ-s

See juhend selgitab, mis on Gradle, miks seda kasutada ning kuidas seadistada Gradle'i abil Java projekti IntelliJ IDEA keskkonnas.

Enne kui käsitleme Gradle'i kasutamist, on oluline mõista, mis probleemi Gradle lahendab.

Kujuta ette, et su projekt vajab töötamiseks mitut välist teeki (nt andmebaasi draiver, võrgunduse teek nagu KryoNet, testimise raamistik nagu JUnit). Ilma automaatse tööriistata peaksid sa:

  1. Kõik need teegid (.jar failid) käsitsi internetist üles otsima.

  2. Need oma projekti kausta lisama.

  3. IntelliJ-s käsitsi projekti sõltuvustena seadistama.

  4. Kui mõni teek uueneb, kordama kogu protsessi.

  5. Projekti jagamisel tiimikaaslasega veenduma, et ka tema teeb täpselt sama seadistuse.

Gradle on ehitusautomaatika tööriist (build automation tool), mis teeb selle kõik sinu eest ära.

Gradle'i ehitamise elutsükkel

Joonis 1. Gradle'i ehitamise elutsükkel. Allikas: Gradle (2026). https://docs.gradle.org/current/userguide/img/gradle-basic-10.png

Gradle'i peamised ülesanded:

  • Sõltuvuste haldus (Dependency Management): Ütled Gradle'ile, mis teeke ja mis versioone sa vajad, ning Gradle laeb need ise alla ja lisab projekti.

  • Projekti ehitamine (Building): Gradle kompileerib sinu koodi, pakib selle koos kõikide sõltuvustega ühte käivitatavasse .jar faili ja teeb palju muud.

  • Testimine (Testing): Gradle suudab automaatselt käivitada kõik sinu kirjutatud testid.

  • Standardiseerimine: Tagab, et projekt töötab ühtemoodi igas arvutis, kus on Gradle paigaldatud.

Gradle toetab kahte domeenispetsiifilist keelt (DSL). Traditsioonilist "Groovy" põhist ja uuemat "Kotlin" põhist lähenemist. Kuigi mõlemad täidavad sama eesmärki, on neil olulised erinevused kasutajakogemuses ja jõudluses.

Kotlin DSL:

  • Tüübiturvalisus: Rangem süntaks vähendab juhuslikke vigu ja leiab need juba kompileerimise ajal.

  • Parem IDE tugi Pakub tõhusamat automaatset koodilõpetust (IntelliJ-s autocomplete)

  • Skaleeritavus: Selgem ja turvalisem ülesehitus muudab suurte projektide haldamise lihtsamaks.

Groovy DSL:

  • Jõudlus: Pakub kiiremat ehitusprotsessi ja paindlikumat, vähem piiravat süntaksi.

  • Dünaamilisus: Puudub range tüübikontroll

Uue Gradle Projekti Loomine IntelliJ-s

  1. IntelliJ IDEA Avamine: Avage IntelliJ IDEA ja valige "Create New Project".

  2. Projekti Seadistamine: Sisestage projekti nimi ja asukoht.

  3. Gradle Projekti Valik: Valige vasakpoolses aknas "Java" ning määrake paremal ehitussüsteemiks (Build system) Gradle.

  4. Gradle DSL Valik: Valige "Groovy" ja vajutage "Create".

Projekti Struktuuri Seadistamine

IntelliJ loob automaatselt standardse Gradle projekti struktuuri. On oluline teada, mis kuhu käib:

  • build.gradle: Projekti "aju". Siin defineerid kõik reeglid, sõltuvused ja ehitamise loogika.

  • settings.gradle: Määrab, millised moodulid projektis on.

  • src/main/java Java koodi jaoks

  • src/main/resources ressursside jaoks

  • src/test/java testide jaoks

build.gradle Faili Konfigureerimine

Leidke build.gradle fail projekti juurkaustas ja lisage järgmine sisu:

NB! Gradle'il on mitmeid erinevaid süntakseid peaaegu iga tegevuse jaoks. Kui leiate juhendist mõne alternatiivse lahenduse, on tõenäoline, et see saavutab sama tulemuse.

Mõnel juhul võivad need süntaksid siiski veidi erineda, seega tasub olla tähelepanelik, et kasutatav süntaks vastaks soovitud tulemusele.

// 'plugins' plokk määrab, milliseid Gradle'i laiendusi projekt kasutab.
// Need laiendused lisavad projektile uusi võimekusi (nt Java koodi kompileerimine).
plugins {
    id 'java'
    id 'java-library'
}

group 'ee.taltech'    // See on sarnane Java paketinimele ja aitab vältida nimekonflikte.
version '1.0'         // 'version' määrab projekti hetkeversiooni.

// Siin on defineeritud kõik kohad, kust gradle otsib sinu rakenduse sõltuvusi (lisatud all dependencies plokis)
allprojects {
    repositories {
        mavenCentral()                        // Seda tahad sa ilmselt alati lisada, siin on kõike https://mvnrepository.com/repos/central
        maven { url 'https://jitpack.io' }    // Vajadusel saab lisada ka alternatiive
    }
}

// Siia lisa kõik teegid, mida su rakendus kasutab
// (alati uuri igaks juhuks, kas eksisteerib uuemaid versioone)
dependencies {
    // Näiteks kryoneti lisamine (mida ilmselt lisate oma serverisse):
    implementation 'com.github.crykn:kryonet:2.22.8'
    implementation 'com.esotericsoftware:kryo:5.4.0'

    // Kui midagi on vaja testide jooksutamiseks, siis see on defineeritud nii
    // Kuna test frameworki pole lõplikus rakenduses vaja, siis on see siin
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'

}

// Kui sa tahad teste kirjutada, peab olema see lisatud koos junit dependencyga
test {
    useJUnitPlatform()
}

// Siin on defineeritud, kuidas gradle peaks ehitama JAR faili, mida saaks jooksutada ka teistes arvutites
jar {
    manifest {
        attributes(
                // Main class väärtus oleneb sinu rakendusest.
                // Siia peab minema klass, mille jooksutamisel rakendus käima pannakse
                'Main-Class': 'ee.taltech.server.Main'
        )
    }

    duplicatesStrategy = DuplicatesStrategy.EXCLUDE    // Lahendab olukorra, kus mitu sõltuvust sisaldavad sama nimega faili.
    from (
            // Valib kõik sinu failid jari sisse pakkimiseks
            configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    )
}

Projekti Ehitamine ja Käivitamine IntelliJ-s

  1. Gradle Tööriistakast: Avage paremal Gradle tööriistakast. Kui seda ei ole näha, valige menüüst View -> Tool Windows -> Gradle.

  2. Projekti Ehitamine: Topeltklõpsake Tasks -> build -> build ülesandel, et kompileerida oma projekt ja luua JAR-fail. idea_gradle_view

  3. Rakenduse Käivitamine: Topeltklõpsake run ülesandel application grupis, et käivitada oma rakendus IntelliJ IDEA-st.

Siin on näide Gradle'i seadistusest näidismängus:

buildscript {
    repositories {
        mavenCentral()
        maven { url 'https://s01.oss.sonatype.org' }
        gradlePluginPortal()
        mavenLocal()
        google()
        maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
        maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
    }
    dependencies {


    }
}

allprojects {
    apply plugin: 'eclipse'
    apply plugin: 'idea'

    // This allows you to "Build and run using IntelliJ IDEA", an option in IDEA's Settings.
    idea {
        module {
        outputDir file('build/classes/java/main')
        testOutputDir file('build/classes/java/test')
        }
    }
}

configure(subprojects) {
    apply plugin: 'java-library'
    sourceCompatibility = 21

    // From https://lyze.dev/2021/04/29/libGDX-Internal-Assets-List/
    // The article can be helpful when using assets.txt in your project.
    tasks.register('generateAssetList') {
        inputs.dir("${project.rootDir}/assets/")
        // projectFolder/assets
        File assetsFolder = new File("${project.rootDir}/assets/")
        // projectFolder/assets/assets.txt
        File assetsFile = new File(assetsFolder, "assets.txt")
        // delete that file in case we've already created it
        assetsFile.delete()

        // iterate through all files inside that folder
        // convert it to a relative path
        // and append it to the file assets.txt
        fileTree(assetsFolder).collect { assetsFolder.relativePath(it) }.sort().each {
        assetsFile.append(it + "\n")
        }
    }
    processResources.dependsOn 'generateAssetList'

    compileJava {
        options.incremental = true
    }
}

subprojects {
    version = '$projectVersion'
    ext.appName = 'example-game'
    repositories {
        mavenCentral()
        maven { url 'https://s01.oss.sonatype.org' }
        // You may want to remove the following line if you have errors downloading dependencies.
        mavenLocal()
        maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
        maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
        maven { url 'https://jitpack.io' }
    }
}

eclipse.project.name = 'example-game' + '-parent'

Võetud näidismängu build.gradle'st

buildscript

See määrab Gradle’i skripti jaoks vajalikud sõltuvused ja hoidlad.

  • repositories – Siin on määratud erinevad hoidlad, kust sõltuvusi alla laaditakse:
    • mavenCentral() – Maven Central repositoorium.

  • dependencies – Siin oleks võimalik määrata sõltuvusi, mida Gradle vajab enne projektiga töötamist. Hetkel on see tühi.

allprojects

See plokk rakendub kõigile projektidele, kaasa arvatud alamprojektid.

  • apply plugin: 'eclipse' – Lisab toe Eclipse IDE jaoks.

  • apply plugin: 'idea' – Lisab toe IntelliJ IDEA jaoks.

  • idea plokk – Konfigureerib IntelliJ IDEA ehituskataloogid:
    • outputDir file('build/classes/java/main') – Määrab kompileeritud klassifailide väljundkausta.

    • testOutputDir file('build/classes/java/test') – Määrab testide kompileeritud klassifailide väljundkausta.

configure(subprojects)

Siin konfigureeritakse kõik alamprojektid.

  • apply plugin: 'java-library' – Määrab, et kõik alamprojektid kasutavad java-library pluginit.

  • sourceCompatibility = 21 – Määrab Java versiooniks 21.

Automaatne asset'ide nimekirja genereerimine (generateAssetList ülesanne)

  • Loob ülesande generateAssetList, mis:
    1. Määrab, et inputs.dir("${project.rootDir}/assets/") jälgib assets/ kausta.

    2. Loob faili assets.txt kaustas assets/.

    3. Kustutab faili, kui see eksisteerib.

    4. Läbib kõik failid assets/ kaustas, teisendab need suhtelisteks teedeks (relative path) ja lisab need assets.txt faili.

  • processResources.dependsOn 'generateAssetList' – Tagab, et generateAssetList käivitatakse enne processResources ülesannet.

compileJava ülesanne

  • options.incremental = true – Lubab Java kompileerimisel järk-järgulise kompileerimise, mis kiirendab arendustööd.

subprojects

Konfigureerib kõik alamprojektid.

  • version = '$projectVersion' – Määrab projekti versiooni.

  • ext.appName = 'example-game' – Määrab kohandatud muutuja appName väärtuseks 'example-game'.

  • repositories – Määrab, kust sõltuvused alla laaditakse:
    • mavenCentral() – Maven Central.

Server build.gradle näidismängust

Nüüd vaatame serveri build.gradle faili.

apply plugin: 'application'


java.sourceCompatibility = 21
java.targetCompatibility = 21
if (JavaVersion.current().isJava9Compatible()) {
  compileJava.options.release.set(21)
}

mainClassName = 'ee.taltech.examplegame.server.ServerLauncher'
application.setMainClass(mainClassName)
eclipse.project.name = appName + '-server'

dependencies {
  implementation project(':shared')
  implementation group: 'com.esotericsoftware', name: 'kryonet', version: '2.22.0-RC1'
  implementation group: 'com.google.code.gson', name: 'gson', version: '2.11.0'

  // you must enable annotation processing in intellij settings.
  compileOnly 'org.projectlombok:lombok:1.18.36'
  annotationProcessor 'org.projectlombok:lombok:1.18.36'
}

jar {
  archiveBaseName.set(appName)
// the duplicatesStrategy matters starting in Gradle 7.0; this setting works.
  duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
  dependsOn configurations.runtimeClasspath
  from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
// these "exclude" lines remove some unnecessary duplicate files in the output JAR.
  exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
  dependencies {
    exclude('META-INF/INDEX.LIST', 'META-INF/maven/**')
  }
// setting the manifest makes the JAR runnable.
  manifest {
    attributes 'Main-Class': project.mainClassName
  }
// this last step may help on some OSes that need extra instruction to make runnable JARs.
  doLast {
    file(archiveFile).setExecutable(true, false)
  }
}

// Equivalent to the jar task; here for compatibility with gdx-setup.
task dist(dependsOn: [jar]) {
}

Võetud näidismängu build.gradle failist.

Üldine seadistus

apply plugin: 'application' // Aktiveerib application plugina, mis võimaldab määrata rakenduse käivitusklassi ja luua käivitatava JAR-faili.

Põhiklass ja projekti nimi

mainClassName = 'ee.taltech.examplegame.server.ServerLauncher' // Määrab rakenduse käivitusklassiks ServerLauncher
application.setMainClass(mainClassName)
eclipse.project.name = appName + '-server' // Eclipse projekti nimeks pannakse appName + -server.

Sõltuvused

dependencies {
  implementation project(':shared') // Kohalik projektimoodul :shared kus asuvad ühised konstandid, sõnumid ja seaded, mida kasutatakse kliendi ja serveri vaheliseks suhtluseks.
  implementation group: 'com.esotericsoftware', name: 'kryonet', version: '2.22.0-RC1' // Võrgu teek KryoNet, mida kasutatakse serveri ja kliendi loomiseks
  implementation group: 'com.google.code.gson', name: 'gson', version: '2.11.0' // Java objektide ja JSON-i vahelise teisendamise teek Gson
  // Lombok ainult kompileerimiseks ja annotatsioonide töötlemiseks (vajab IntelliJ-s annotatsioonitöötluse lubamist)
  compileOnly 'org.projectlombok:lombok:1.18.36'
  annotationProcessor 'org.projectlombok:lombok:1.18.36'
}

Mis on erinevus "annotationProcessor" ja "compileOnly" vahel antud näites?

Lombok annab võimaluse lisada klassi ees anotsioone, nagu näiteks "getters", mis lisavad automaatselt selle klassi iga atribuudi jaoks getter'id, aidates vältida koodi ülekoormamist.

annotationProcessor annab võimaluse arenduskeskkonnal pakkuda arendajale auto complete valikuid klassi jaoks, millel on "getters" anotsioon, ehkki neid getter'eid füüsiliselt koodis ei ole.

compileOnly - kompilatsiooni ajal lisatakse need kõik getter'id füüsiliselt byte-koodi.

Seega erinevus seisneb selles, et esimene teeb näo, et mingid meetodid eksisteerivad, võimaldades neid kasutada IDE-s ilma veateateta. Teine lisab need meetodid tegelikult, et programmi käivitamisel ei tekiks viga, et koodis kasutatakse meetodit, mida tegelikult ei ole.

JAR-faili konfigureerimine

jar {
  archiveBaseName.set(appName) // Seab JAR-faili nimeks muutuja appName väärtuse (näiteks "example-game").
  duplicatesStrategy(DuplicatesStrategy.EXCLUDE) // Välistab duplikaadid ja ebavajalikud failid (META-INF)

  dependsOn configurations.runtimeClasspath // Lisab kõik vajalikud klassiteegid JAR-i sisse (fat JAR)
  from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } // kõik vajalikud sõltuvused, mis ei ole juba kataloogid, pakitakse välja (näiteks .jar failid).

  exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
  dependencies {
    exclude('META-INF/INDEX.LIST', 'META-INF/maven/**')
  }
  manifest {
    attributes 'Main-Class': project.mainClassName // JAR-faili saab käivitada ainult siis, kui see sisaldab määratud põhiklassi (Main-Class). See on klass, kus main meetod asub, ja see käivitab kogu rakenduse.
    // project.mainClassName on eelnevalt määratud kui ee.taltech.examplegame.server.ServerLauncher.
  }
  doLast {
    file(archiveFile).setExecutable(true, false) // Märgistab JAR-faili käivitatavaks (mõnel OS-il vajalik)
  }
}

Mis on META-INF

META-INF on eriline kaust JAR-failides, mis sisaldab metainfot – ehk infot selle JAR-faili enda kohta, mitte tingimata programmi enda kohta. Kui avada mõne .jar faili (nt arhiivina ZIP-iga), siis seal sees on tavaliselt üks kataloog nimega META-INF.

META-INF

Faili nimi

Kirjeldus

MANIFEST.MF

Peamine metainfoga fail – sisaldab infot nagu Main-Class, versioon, teegid jne.

*.SF, *.DSA, *.RSA

Digiallkirjade failid, mida kasutatakse allkirjastatud JAR-ides (nt turvalisus, päritolu kontroll).

INDEX.LIST

Kiiremaks JAR-i klasside leidmiseks – ei ole alati vajalik.

maven/**

Kui JAR on loodud Maveniga, võib siin olla infot selle artefakti päritolu kohta.

Kui tehakse fat JAR-i (kus on sees mitu teeki korraga), siis igas JAR-is võib olla oma META-INF, ja need võivad kattuda või konflikti minna.

Näiteks, kui JAR-fail sisaldab META-INF/*.RSA või .SF ja muudada midagi selles JAR-is (nt lisad faile), siis allkiri ei vasta enam failile, ja Java võib keelduda selle käivitamisest.

Task dist

// Equivalent to the jar task; here for compatibility with gdx-setup.
task dist(dependsOn: [jar]) { // loob lihtsalt uue käsu nimega dist, mida saab käsurealt käivitada (./gradlew dist).#
    // dependsOn: [jar] tähendab, et enne kui dist töö käivitatakse, peab kindlasti valmis saama jar töö – see on töö, mis loob JAR-faili.
}

gdx-setup on LibGDX-i projekti seadistustööriist. Mõned vanemad tööriistad (nagu gdx-setup) ootavad, et Gradle projektis oleks töö nimega dist, kuigi jar töö teeks tegelikult sama asja.

dist = alias jar-ile

Klient build.gradle näidismängust

Nüüd vaatame kliendi build.gradle faili.

[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
eclipse.project.name = appName + '-core'

dependencies {
  api "com.badlogicgames.gdx:gdx:$gdxVersion"
  api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
  api "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop"

  api project(':shared')

  if (enableGraalNative == 'true') {
    implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion"
  }

  implementation group: 'com.esotericsoftware', name: 'kryonet', version: '2.22.0-RC1'
}

Võetud siit

Sõltuvused

dependencies {
  api "com.badlogicgames.gdx:gdx:$gdxVersion" // põhiliselt kasutatud teek (kogu mängu loogika).
  api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" // tugi TrueType fontidele (nt .ttf-failid).
  api "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop"

  api project(':shared') // // Kohalik projektimoodul :shared kus asuvad ühised konstandid, sõnumid ja seaded, mida kasutatakse kliendi ja serveri vaheliseks suhtluseks.

  if (enableGraalNative == 'true') { // Kui enableGraalNative == 'true', siis lisatakse tugi GraalVM Native Image jaoks (gdx-svmhelper-annotations).
    // See aitab LibGDX rakendust kompileerida native binaariks.
    implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion"
  }

  implementation group: 'com.esotericsoftware', name: 'kryonet', version: '2.22.0-RC1'
}

implementation vs api

Mis vahe on nende märksõnade vahel?

Eelkõige puudutab see ligipääsu — võib öelda, et api on nagu public ja implementation nagu private.

Mida see annab?

Oletame, et me lisame oma projekti teegi nimega "library" (pole oluline, kas läbi "api" või "implementation"), ja see teek omakorda sõltub teegist "libraryInner". Kui "libraryInner" on lisatud "api" kaudu, siis meie projektist saab kasutada ka "libraryInner" meetodeid. Kui aga "library" kasutab implementation — kaotame selle võimaluse.

Milleks seda vaja on?

Põhjuseks on kompileerimiskiirus. Kui "library" kasutab api, siis muudatused "libraryInner" teegis toovad kaasa vajaduse ümber kompileerida nii "library" kui ka kogu meie projekt, sest meil võib olla otsene sõltuvus "libraryInner"-ist. Aga kui kasutatakse implementation, siis me ei saa "libraryInner" meetodeid kasutada ja seetõttu piisab ainult "library" ümber kompileerimisest.

Troubleshooting

  • Lisasin dependency, aga seda ei ole projektis?
    • Refreshi projekti (Gradle aknas noolte ring)

    • Kontrolli, et sinu build.gradle failis oleks lisatud õige repository, kust Gradle saaks teeke importida (allprojects -> repositories).

  • Rakendus töötab minu arvutis, aga mitte tiimikaaslase omas (või vastupidi). - See on tihti seotud erinevate Java versioonidega. Veenduge, et nii sinul kui ka tiimikaaslasel on IntelliJ-s seadistatud sama Java versioon projekti jaoks (File -> Project Structure -> Project SDK). - Samuti on hea build.gradle faili lisada sourceCompatibility = 17 (või muu versioon), et Gradle teaks, millist Java versiooni eeldada.

  • Kuidas Gradle projekti alustada?
    • Vajuta File -> New -> Project. Language: Java, Build system: Gradle, Gradle DSL: Groovy

  • IntelliJ ei tuvasta ära minu Gradle projekti
    • Proovi IntelliJ uuesti käivitada. Tavaliselt ilmub seejärel popup Gradle projekti importimiseks (all paremal).

    • Proovi manuaalselt moodul lisada. File -> New -> Module from existing sources, vali kaust, kus asuvad sinu Gradle failid (nt build.gradle).

    • Proovi IntelliJ cache kustutada. File -> Invalidate Caches, seejärel korda eelnevaid samme.

    • Proovi projekt ära kustutada ja klooni see uuesti.

    • Kui kõik eelnev ei aidanud, siis on võimalik, et projektis on midagi valesti seadistatud või mõni samm läks valesti. Gradle + IntelliJ töötavad tavaliselt stabiilselt, kuid kui probleemi ei õnnestu lahendada, pöördu õppejõu poole.