16 KiB
title, description, tags
| title | description | tags | ||
|---|---|---|---|---|
| kotlinc | Командная строка компилятора, REPL, скрипты .main.kts и ключевые флаги |
|
kotlinc - это компилятор командной строки. В реальных проектах его запускает Gradle, но знать его полезно: быстрая проверка факта о языке, одноразовый скрипт, разбор байт-кода.
kotlinc Hello.kt -include-runtime -d hello.jar
java -jar hello.jar
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 <v> |
версия байт-кода (по умолчанию 1.8) |
-module-name <n> |
имя модуля (влияет на манглирование internal) |
-nowarn |
подавить предупреждения |
-Werror |
считать предупреждения ошибками |
-Wextra |
дополнительные проверки K2 |
-progressive |
исправления применяются сразу, без цикла миграции |
-language-version <v> |
ограничить возможности языка |
-api-version <v> |
запретить API новее указанной версии stdlib |
-opt-in <fq.name> |
включить 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).
@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:
// 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
Подробнее: Структура проекта
Демон компилятора
Запуск 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 в байт-коде
[!info] Отличия от Java
Java Kotlin javac Demo.javakotlinc Demo.ktjava Demojava -jar app.jarилиkotlin DemoKt--release 21-jvm-target 21-Xlint:all -Werror-Wextra -WerrorJEP 330: java Demo.javakotlinc -scriptили.main.ktsjshellkotlincбез аргументовнет аналога @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
[!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