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 чувствителен к структуре. Если ошибка появилась после правки, откатываться удобнее, когда правка маленькая. Поэтому лучший стиль — добавили одну зависимость, дождались успешного обновления, только потом добавляем следующую. Это скучновато, зато экономит часы жизни.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ