Files
obsidian/Kotlin/Инструментарий/kotlinc.md
T

274 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)