274 lines
16 KiB
Markdown
274 lines
16 KiB
Markdown
---
|
||
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 <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)`.
|
||
|
||
```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)
|