JavaRush /Курси /Kotlin SELF /Gradle і build.gradle.kts: залежності та модель збирання

Gradle і build.gradle.kts: залежності та модель збирання

Kotlin SELF
Рівень 17 , Лекція 2
Відкрита

1. Вступ

Коли проєкт маленький, здається, що все просто: написали main, скомпілювали, запустили — і ви молодці. Але щойно з’являється другий файл, третій файл, залежність від бібліотеки, різні режими запуску, різні версії Kotlin і JDK, раптом з’ясовується: потрібен хтось, хто стежитиме за порядком.

Gradle — це як «прораб» на будівництві: він не кладе цеглу (це робить компілятор), але каже, що, у якому порядку і з якими матеріалами потрібно робити, щоб будинок не розвалився.

Якщо коротко, Gradle відповідає за три дуже практичні речі. По-перше, він знає, як зібрати проєкт (скомпілювати Kotlin-код і розкласти результати по потрібних папках). По-друге, він уміє під’єднувати зовнішні бібліотеки (завантажувати їх із репозиторіїв і класти туди, де компілятор їх побачить). І, по-третє, він надає зручні «команди збирання» — задачі (tasks): зібрати, почистити, запустити.

Gradle tasks: чому кажуть «задачі» і до чого тут команди

Коли ви запускаєте проєкт з IDE, здається, що «IDE сама все робить». Насправді вона зазвичай просить Gradle виконати задачу. Задача (task) — це іменований крок збирання: «скомпілювати Kotlin», «запустити застосунок», «зібрати результат». Gradle будує граф задач: які кроки від яких залежать, що треба виконати спочатку, а що можна пропустити, якщо нічого не змінилося.

На цьому етапі вам не потрібно писати власні задачі й налаштовувати складні конвеєри. Достатньо розуміти одне: Gradle живе у світі задач, і коли ви кажете «збери» або «запусти», це майже завжди означає «виконай таку-то задачу».

Зручно уявити це як ланцюжок:

flowchart LR
    A[Ваш Kotlin-код у src] --> B[Gradle: задачі компіляції]
    B --> C[Скомпільовані класи]
    C --> D[Запуск main через задачу run]

Сама діаграма — не «іспит із Gradle», а лише ілюстрація моделі: Gradle організовує шлях від вихідних кодів до запуску.

2. build.gradle.kts і Gradle DSL: як читати конфігурацію

Що таке build.gradle.kts і чому це не код застосунку

Файл build.gradle.kts виглядає як Kotlin-код: фігурні дужки, рядки, виклики. І так, технічно це Kotlin DSL (скрипт на Kotlin). Але за змістом — це конфігурація збирання, а не ваш застосунок. Тут ви не пишете бізнес-логіку, не перевіряєте введення й не викликаєте readln(); ви описуєте, як проєкт має збиратися і які компоненти для цього потрібні.

Корисна звичка: build.gradle.kts варто читати як «конфігурацію з блоками налаштувань». Блок plugins { ... } — це «увімкни такі можливості збирання». Блок dependencies { ... } — це «під’єднай такі бібліотеки». Приклад таких блоків у документації Kotlin виглядає саме як набір конфігураційних секцій.

Щоб мозок не перегрівався, можна тримати в голові просту таблицю «який блок — що означає»:

Блок у build.gradle.kts Людський зміст
plugins { ... }
«Яким типом проєкту ми займаємося і які інструменти під’єднуємо»
repositories { ... }
«Звідки завантажувати бібліотеки»
dependencies { ... }
«Які бібліотеки потрібні проєкту»
application { ... }
«Це застосунок, у нього є main, ось який»
tasks { ... }
«Тонке налаштування задач збирання»

Чому всюди блоки { ... } і як їх не боятися

Якщо ви раніше бачили Kotlin, то фігурні дужки у вас асоціюються з if, while, функціями. У Gradle вони теж є, але зміст інший: це групування налаштувань.

Уявіть, що ви заповнюєте анкету, де є розділи «Особисті дані», «Адреса», «Контакти». Gradle робить те саме: plugins { }, repositories { }, dependencies { } — це просто розділи анкети.

Звідси важливе практичне правило: не намагайтеся повністю «зрозуміти Kotlin» у build.gradle.kts на рівні мови. На цьому етапі достатньо вміти: побачити блок, зрозуміти його призначення й акуратно правити його малими кроками.

Якщо ви зламаєте дужку, Gradle лаятися так, ніби ви випадково відкусили шматок від конфігурації всесвіту. І, чесно кажучи, він матиме рацію.

3. Плагіни, репозиторії та залежності

Блок plugins { ... }: який у нас проєкт

Плагіни в Gradle — це як «режими збирання». Один плагін каже: «це Java-проєкт». Інший: «це Kotlin/JVM». Третій додає: «а ще це застосунок, який можна запускати командою run». Якщо не під’єднати потрібний плагін, Gradle просто не знатиме, які задачі створювати і чим компілювати код.

Типова мінімальна конфігурація для Kotlin/JVM виглядає так:

plugins {
    kotlin("jvm") version "2.3.0"
    application
}

Що тут відбувається «по-людськи»:

kotlin("jvm") вмикає підтримку Kotlin на JVM: з’являться задачі компіляції Kotlin-коду, Gradle розумітиме ваші файли .kt.

application каже: «це застосунок». Зазвичай це додає зручну задачу запуску (часто її називають run) і конфігурацію того, який main вважати точкою входу. Ми не будемо заглиблюватися в механіку, але зафіксуємо ідею: плагін додає проєкту «вміння».

Блок repositories { ... }: звідки беруться бібліотеки

Якщо ви пишете dependencies { implementation("...") }, Gradle має десь знайти цю бібліотеку. Для цього й потрібен repositories { ... }. Найпопулярніший варіант для навчальних і більшості реальних проєктів — mavenCentral().

repositories {
    mavenCentral()
}

Це схоже на ситуацію «хочу купити книжку» → потрібно вказати, де магазин. mavenCentral() — це величезний публічний репозиторій бібліотек для JVM-світу. Можете думати про нього як про «центральний склад».

Gradle не зберігає всі бібліотеки всередині проєкту — він завантажує їх за потреби, кладе у свій кеш і під’єднує під час збирання.

Тут важливо розуміти межу: репозиторій — це «звідки», а залежності — це «що саме».

Блок dependencies { ... }: під’єднуємо бібліотеки й не плутаємо з import

Залежності — це те, заради чого багато хто вперше й «знайомиться» з Gradle. Логіка така: ваш код може використовувати чужі класи й функції, але компілятор має мати доступ до їхніх файлів .jar. dependencies { ... } якраз і каже Gradle: «ось ці бібліотеки потрібні проєкту».

У документації Kotlin приклад залежності виглядає так: усередині dependencies додається рядок implementation("group:artifact:version").

dependencies {
    implementation("org.example:lib:1.0.0")
}

Тут "org.example:lib:1.0.0" — це координати залежності. Часто кажуть «GAV»: group, artifact, version. Це просто унікальна «адреса» бібліотеки в репозиторії.

Тепер ключове: dependencies та import — це різні рівні.

  • dependencies — робить бібліотеку доступною проєкту: Gradle її завантажив, компілятор зможе її бачити.
  • import — робить ім’я зручним у конкретному файлі: ви можете писати LocalDate замість повного імені.

Тобто спочатку «під’єднали бібліотеку в Gradle», а потім «використовуємо її імена в коді через import».

Найчастіші конфігурації залежностей

У навчальному Kotlin/JVM-проєкті ви найчастіше побачите ось такі слова:

Конфігурація Для чого
implementation(...)
Бібліотека потрібна, щоб збирати й запускати основну програму
testImplementation(...)
Бібліотека потрібна лише для тестів

Поки достатньо запам’ятати: у 90 % випадків на старті вам потрібен саме implementation.

Мініприклад: додали залежність і отримали робочий import

Досі ми говорили абстрактно: «бібліотека», «під’єднати». Давайте зробимо маленький крок, який добре показує причинно-наслідковий зв’язок.

Уявімо, що в нас є консольний застосунок, де ми друкуємо підказки користувачеві. І нам захотілося зробити виведення трохи помітнішим: наприклад, друкувати важливі повідомлення «жирніше» або кольором. У звичайній консолі це часто роблять через ANSI-послідовності, але вручну писати ці керувальні коди — задоволення на любителя.

Ми під’єднаємо просту бібліотеку для ANSI-виведення. Приклад навчальний: сама ідея важливіша, ніж конкретна бібліотека. У build.gradle.kts додамо:

dependencies {
    implementation("org.fusesource.jansi:jansi:2.4.1")
}

Тепер у Kotlin-коді (наприклад, у src/main/kotlin/app/Main.kt) у нас з’являється можливість імпортувати класи цієї бібліотеки. Зауважте: цей приклад потрібен, щоб ви відчули механіку «dependency → можна import», а не щоб ви запам’ятали API бібліотеки.

package app

import org.fusesource.jansi.Ansi.ansi

fun main() {
    val warning = ansi().fgRed().a("Увага!").reset().toString()
    println(warning) // у консолі буде червоний текст, якщо термінал підтримує ANSI
}

Якщо ви видалите залежність із build.gradle.kts, то import org.fusesource... почне підсвічуватися червоним, а компілятор скаже щось на кшталт Unresolved reference. І це дуже корисне відчуття: проблема не в Kotlin-синтаксисі, а в тому, що збирання не принесло бібліотеку в проєкт.

Ще одна практична деталь: після правок залежностей IDE зазвичай «оновлює модель Gradle». Часто це кнопка Sync. І якщо ви забули синхронізуватися, можете опинитися в дивному стані: у build.gradle.kts залежність є, а IDE вперто робить вигляд, що її немає. Це не містика — просто IDE ще не перечитала конфігурацію.

4. Метадані, версії та читання помилок

Метадані проєкту: group і version

Коли проєкт починає жити довше одного вечора, раптом виявляється, що в нього є «паспорт»: ім’я, група і версія. Навіть якщо ви не публікуєте бібліотеку й не робите релізи, ці поля часто присутні в шаблонах.

group = "com.example"
version = "1.0.0"

Зараз вам достатньо сприймати це як метадані: «як проєкт називається для зовнішнього світу». Не ускладнюймо: це не впливає на ваш println, але допомагає Gradle та інструментам орієнтуватися.

Чому версії важливі

У plugins { kotlin("jvm") version "..." } і в dependencies { implementation("...:...:...") } версії — це не декорація.

Якщо ви вкажете версію, якої не існує, Gradle чесно скаже: «не можу завантажити». Якщо ж поставите версію, яка погано поєднується з вашою версією Kotlin, можуть з’явитися вже хитріші помилки.

На старті курсу тримаємо правило простим: змінюємо версії рідко й усвідомлено, малими кроками.

Як читати помилки build.gradle.kts і не панікувати

Помилки в build.gradle.kts у новачків майже завжди бувають двох типів.

Перший тип — «зламав синтаксис». Це коли ви забули закрити дужку, лапки або випадково видалили шматок блока. Такі помилки виглядають моторошно, але лікуються просто: відновіть баланс дужок і рядків. Дуже допомагає стратегія «правимо маленькими кроками»: додали один рядок залежності, зберегли, дочекалися синхронізації — і лише потім додаємо наступний.

Другий тип — «не знайшов залежність». Це коли ви вказали координати неправильно або забули репозиторій. У такому разі Gradle пише, що не може розв’язати (resolve) залежність. «Ліки» теж зазвичай побутові: перевірте repositories { mavenCentral() }, перевірте рядок залежності, перевірте версію.

Є ще один тип, який часто виглядає як баг IDE: ви все написали правильно, але IDE продовжує підсвічувати import червоним. У таких випадках насамперед згадуємо: після правок build.gradle.kts потрібен Gradle Sync. Це не чарівний ритуал, а просто «IDE, перечитай конфіг і перебудуй модель проєкту».

5. Типові помилки під час роботи з build.gradle.kts

Помилка №1: сприймати build.gradle.kts як місце для логіки застосунку.
Іноді хочеться: «ну раз це Kotlin-файл, то давайте я тут напишу функцію і викличу її». Технічно Gradle-скрипт справді виконується, але це інший всесвіт: там немає сенсу писати вашу прикладну логіку. Це конфігурація збирання. Щойно ви починаєте «програмувати» збирання замість того, щоб налаштовувати його, ви швидко потрапляєте в стан «працює лише в автора на ноутбуці за Місяця в третій чверті».

Помилка №2: плутати dependencies та import.
Часта картина: студент додав import some.library.Foo, очікуючи, що бібліотека «підтягнеться сама». Але import не завантажує бібліотеки. Він лише каже компілятору: «ось це ім’я використовуй без повного шляху». Завантаження бібліотек — це задача Gradle, і робиться воно лише через dependencies + repositories.

Помилка №3: додати залежність і забути Gradle Sync.
У результаті з’являється відчуття «я все зробив, а воно не працює». Особливо в IntelliJ IDEA: ви додали рядок implementation(...), але IDE ще не оновила classpath. Майже завжди це розв’язується синхронізацією Gradle-проєкту.

Помилка №4: використовувати import ...* у коді як «швидке рішення», бо «я ж під’єднав залежність».
Під’єднання залежності не означає, що вам тепер потрібно імпортувати «все підряд». На рівні коду імпорт краще тримати точковим і зрозумілим, бо інакше ви самі собі створюєте конфлікти імен і дивні підсвічування. Gradle розв’язує «які бібліотеки доступні», а ви в коді розв’язуєте «які імена мені потрібні».

Помилка №5: ламати дужки й лапки, а потім лагодити «методом навмання» одразу десять рядків.
build.gradle.kts чутливий до структури. Якщо помилка з’явилася після правки, відкочуватися зручніше, коли правка маленька. Тому найкращий стиль — додали одну залежність, дочекалися успішного оновлення і лише потім додаємо наступну. Це трохи нудно, зате економить години життя.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ