--- title: kotlinc description: Командная строка компилятора, REPL, скрипты .main.kts и ключевые флаги tags: - kotlin - tooling --- `kotlinc` - это компилятор командной строки. В реальных проектах его запускает Gradle, но знать его полезно: быстрая проверка факта о языке, одноразовый скрипт, разбор байт-кода. ``` kotlinc Hello.kt -include-runtime -d hello.jar java -jar hello.jar ``` ```kotlin fun main() { println("скомпилировано kotlinc") } ``` Проверить версию: ``` $ kotlinc -version info: kotlinc-jvm 2.4.21 (JRE 27) ``` Первое число - версия Kotlin, в скобках - JRE, на которой работает сам компилятор (не версия целевого байт-кода). ## Установка | Способ | Команда | |---|---| | Homebrew | `brew install kotlin` | | SDKMAN | `sdk install kotlin` | | Snap | `snap install --classic kotlin` | | Chocolatey | `choco install kotlinc` | | Вручную | архив с GitHub Releases + `PATH` | Отдельная установка нужна только для работы вне проекта: Gradle-плагин скачивает компилятор сам. ## Три режима компиляции **В каталог с `.class`** - для дальнейшей работы с байт-кодом: ``` kotlinc Demo.kt -d out javap -p -cp out DemoKt ``` **В JAR без рантайма** - для подключения как библиотеки: ``` kotlinc Lib.kt -d lib.jar ``` JAR не запустится напрямую: в нём нет stdlib. **В исполняемый JAR** - со встроенной stdlib (~5 МБ): ``` kotlinc App.kt -include-runtime -d app.jar java -jar app.jar ``` ## Ключевые флаги | Флаг | Что делает | |---|---| | `-d <путь>` | каталог или `.jar` для результата | | `-include-runtime` | вложить stdlib в JAR | | `-classpath` / `-cp` | зависимости | | `-jvm-target ` | версия байт-кода (по умолчанию **1.8**) | | `-module-name ` | имя модуля (влияет на манглирование `internal`) | | `-nowarn` | подавить предупреждения | | `-Werror` | считать предупреждения ошибками | | `-Wextra` | дополнительные проверки K2 | | `-progressive` | исправления применяются сразу, без цикла миграции | | `-language-version ` | ограничить возможности языка | | `-api-version ` | запретить API новее указанной версии stdlib | | `-opt-in ` | включить API, требующее opt-in | | `-verbose` | подробный лог | | `-X` | список экспериментальных флагов | **Про `-jvm-target`.** Значение по умолчанию - `1.8`, то есть байт-код major version 52, даже на JDK 27: ``` $ kotlinc Demo.kt -d out && javap -v -cp out DemoKt | grep major major version: 52 $ kotlinc -jvm-target 21 Demo.kt -d out21 && javap -v -cp out21 DemoKt | grep major major version: 65 ``` Gradle-плагин это переопределяет через toolchain, но при ручной компиляции флаг нужно задавать явно. **Про `-Werror`.** Останавливает сборку на любом предупреждении: ``` $ kotlinc -Werror Demo.kt -d out error: warnings found and -Werror specified Demo.kt:5:13: warning: 'fun old(): Int' is deprecated. устарело. ``` В CI это полезно: не даёт предупреждениям накапливаться. В Gradle - `allWarningsAsErrors.set(true)`. ```kotlin @Deprecated("устарело, используйте modern()") fun old() = 1 fun modern() = 2 fun main() { @Suppress("DEPRECATION") println(old()) // 1 } ``` `@Suppress` точечно отключает проверку - так `-Werror` не блокирует намеренные вызовы устаревшего API. ## Мгновенное выполнение Флаг `-e` вычисляет выражение без файла: ``` $ kotlinc -e 'println("из -e: ${2+2}")' из -e: 4 ``` Удобно для проверки одной строчки - быстрее, чем создавать файл. ## Скрипты .main.kts Файл с расширением `.main.kts` - это Kotlin-скрипт: код на верхнем уровне, без `fun main`. ``` $ cat hello.main.kts println("из скрипта: ${listOf(1,2,3).sum()}") $ kotlinc -script hello.main.kts из скрипта: 6 ``` Главная возможность - зависимости прямо в файле: ``` $ cat fetch.main.kts @file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0") import kotlinx.coroutines.* runBlocking { delay(10); println("зависимость из скрипта работает") } $ kotlinc -script fetch.main.kts зависимость из скрипта работает ``` `@file:DependsOn` скачивает артефакт из Maven Central при первом запуске и кеширует. Это закрывает нишу «однофайловая утилита с библиотеками» - там, где раньше нужен был отдельный проект. Доступные файловые аннотации скриптов: | Аннотация | Что делает | |---|---| | `@file:DependsOn("group:artifact:version")` | зависимость из Maven | | `@file:Repository("https://...")` | дополнительный репозиторий | | `@file:CompilerOptions("-jvm-target", "21")` | флаги компилятора | | `@file:Import("other.main.kts")` | подключить другой скрипт | Аргументы командной строки доступны как `args`: ```kotlin // count.main.kts — запуск: kotlinc -script count.main.kts файл.txt // val path = args.firstOrNull() ?: error("укажите файл") // println(java.io.File(path).readLines().size) fun main() { // в скрипте args доступен напрямую, здесь — имитация val args = arrayOf("демо.txt") println("аргументов: ${args.size}") // аргументов: 1 } ``` Shebang делает скрипт исполняемым: ``` #!/usr/bin/env kotlin println("запущено как ./script.main.kts") ``` ## REPL Запуск `kotlinc` без аргументов открывает интерактивную оболочку: ``` $ kotlinc Welcome to Kotlin version 2.4.21 >>> val x = 42 >>> x * 2 res1: kotlin.Int = 84 >>> :quit ``` REPL полезен для проверки поведения stdlib. Ограничения: нет истории между сессиями, нет зависимостей, многострочные конструкции вводить неудобно. Для исследований лучше Kotlin Notebook в IDEA или `.main.kts`. ## Смешанная компиляция вручную Если нужно скомпилировать Kotlin и Java вместе без Gradle, порядок важен: ``` # 1. Kotlin видит Java-исходники kotlinc -cp . Lib.kt JLib.java -d out # 2. javac видит скомпилированные Kotlin-классы javac -cp out -d out Main.java # 3. запуск с stdlib в classpath java -cp out:$KOTLIN_HOME/lib/kotlin-stdlib.jar Main ``` Kotlin-компилятор принимает `.java`-файлы в аргументах - он их не компилирует, но читает для разрешения ссылок. Без stdlib в classpath запуск падает: ``` Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics ``` Подробнее: [Структура проекта](Структура%20проекта.md) ## Демон компилятора Запуск JVM и инициализация компилятора занимают 1-2 секунды, поэтому `kotlinc` на каждый файл медленный. Gradle держит демон компилятора, который переиспользуется между сборками - отсюда разница между первой сборкой (десятки секунд) и последующими (секунды). Настройка демона в `gradle.properties`: ``` kotlin.daemon.jvmargs=-Xmx2g org.gradle.jvmargs=-Xmx4g kotlin.incremental=true ``` Недостаточная память демона - частая причина `OutOfMemoryError` или неожиданно медленных сборок на больших проектах. ## Полезные комбинации ``` # что реально попало в байт-код kotlinc Demo.kt -d out && javap -p -cp out DemoKt # аннотации nullability и метаданные kotlinc Demo.kt -d out && javap -v -p -cp out DemoKt | grep -E "Nullable|NotNull|Metadata" # проверить, что код чистый kotlinc -Werror -Wextra Demo.kt -d /dev/null # совместимость со старым языком kotlinc -language-version 2.0 -api-version 2.0 Demo.kt -d out # список экспериментальных флагов kotlinc -X ``` Подробнее про чтение байт-кода: [Kotlin в байт-коде](../Взаимодействие%20с%20Java/Kotlin%20в%20байт-коде.md) > [!info] Отличия от Java > | Java | Kotlin | > |---|---| > | `javac Demo.java` | `kotlinc Demo.kt` | > | `java Demo` | `java -jar app.jar` или `kotlin DemoKt` | > | `--release 21` | `-jvm-target 21` | > | `-Xlint:all -Werror` | `-Wextra -Werror` | > | JEP 330: `java Demo.java` | `kotlinc -script` или `.main.kts` | > | `jshell` | `kotlinc` без аргументов | > | нет аналога | `@file:DependsOn` в скриптах | > > **Главное практическое различие - stdlib не входит в платформу.** `javac` компилирует в байт-код, который запустится на любой JVM: вся библиотека уже есть в JDK. Kotlin-код зависит от `kotlin-stdlib.jar`, поэтому нужен либо `-include-runtime`, либо stdlib в classpath. Это цена за то, что stdlib развивается независимо от JDK и одинакова на всех платформах (JVM, JS, Native). > > **`-jvm-target 1.8` по умолчанию - решение о совместимости, а не недоработка.** Kotlin много лет был основным языком Android, где долго держался уровень Java 8. Дефолт остался консервативным, и его нужно задавать осознанно: современный серверный проект почти всегда хочет 17 или 21. > > **Скрипты в Kotlin сильнее, чем однофайловый запуск в Java.** JEP 330 позволяет `java Demo.java`, но только с тем, что есть в JDK: зависимостей нет. `@file:DependsOn` в `.main.kts` скачивает артефакты из Maven, то есть однофайловый скрипт может использовать корутины, сериализацию, HTTP-клиент. Это полноценная замена Python/Bash для задач автоматизации. > > Сравнить: [JVM](../../Программирование%20на%20Java/JVM/JVM.md) > [!warning] Подводные камни > **`-jvm-target` по умолчанию 1.8.** Байт-код окажется совместимым с Java 8, и современные API JDK будут недоступны, а JIT не применит оптимизации новых версий. На это легко не обратить внимания - предупреждения нет. > > **JAR без `-include-runtime` не запускается:** > ``` > $ kotlinc App.kt -d app.jar && java -jar app.jar > Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics > ``` > Либо добавьте флаг, либо укажите stdlib в `-cp`. > > **`-include-runtime` даёт JAR на ~5 МБ.** Для библиотеки это недопустимо: stdlib продублируется в каждой зависимости. Флаг только для конечных приложений. > > **`-e` и `-script` требуют разных форм записи.** `-e` принимает выражение в кавычках, `-script` - путь к `.kts`-файлу. Передать `.kts` в `-e` или выражение в `-script` не получится. > > **Файл скрипта должен называться `*.main.kts`, не `*.kts`.** Расширение `.kts` зарезервировано для Gradle-скриптов с другим шаблоном: `@file:DependsOn` в нём не сработает. > > **Первый запуск `@file:DependsOn` медленный.** Скачивание артефактов занимает секунды или десятки секунд. Дальше работает кеш `~/.m2`, но в CI без сохранённого кеша задержка повторится на каждом прогоне. > > **`-Werror` падает на предупреждениях из зависимостей.** Вызов устаревшего API библиотеки даст warning и остановит сборку. Нужен точечный `@Suppress("DEPRECATION")`, а не отключение флага. > > **REPL не видит зависимостей.** `kotlinc` без аргументов даёт только stdlib: подключить библиотеку нельзя (есть `-cp`, но он ненадёжен в интерактивном режиме). Для исследований с библиотеками используйте Kotlin Notebook или `.main.kts`. > > **Kotlin-компилятор не компилирует `.java`-файлы.** Передача `.java` в аргументах нужна только для разрешения ссылок; сами классы потом компилирует `javac`. Пропустить второй шаг - получить `ClassNotFoundException` на Java-классе. > > **`-module-name` влияет на видимость `internal`.** `internal` компилируется в публичное имя с суффиксом модуля (`helper$main`). Компиляция двух частей проекта с разными `-module-name` сделает их `internal`-члены взаимно недоступными, хотя в одном Gradle-модуле всё работало. > > **`-d /dev/null` для проверки работает не везде.** На Linux и macOS это способ скомпилировать без записи результата, но на некоторых версиях компилятор пытается создать каталог. Надёжнее писать во временный каталог и удалять. Далее: [Gradle и Kotlin DSL](Gradle%20и%20Kotlin%20DSL.md)