Kotlin guide done. (20.10.26)
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
---
|
||||
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)
|
||||
Reference in New Issue
Block a user