jvmToolchain(17))../gradlew build
./gradlew :composeApp:run
Gradle wrapper (gradlew, gradlew.bat, gradle/wrapper/) лежит в репозитории,
устанавливать Gradle отдельно не нужно — при первом запуске он сам скачает
нужный дистрибутив.
./gradlew :composeApp:packageWixExe
Результат — composeApp/build/distributions/OpenDisk-<версия>-<архитектура>.exe,
где архитектура — та, на которой идёт сборка (x64 или arm64).
Это обёртка Burn (composeApp/wix/Bundle.wxs)
поверх MSI, который собирает задача packageWixMsi и который уезжает внутрь
exe. Сам по себе MSI с 0.5.0 не выпускается, но вся логика установки живёт
в нём: обёртка только показывает окно и передаёт каталог.
Сценарии PowerShell, которыми приложение ставит обновление и удаляет себя,
лежат файлами в composeApp/src/desktopMain/resources/windows/. Их же
запускает CI на настоящей установке — scripts/verify-windows-install.ps1.
Установщик описан своим файлом composeApp/wix/Product.wxs,
а не генерируется jpackage. Причина одна: jpackage не умеет закрывать работающее
приложение, и обновление поверх запущенной копии упиралось в занятые файлы —
Windows Installer сам перезагружал компьютер, а установка оставалась
повреждённой. В своём описании есть util:CloseApplication, который закрывает
приложение до того, как трогать файлы.
Инструменты WiX отдельно ставить не нужно: их приносит задача unzipWix
плагина Compose.
Три грабли, на которые здесь легко наступить снова:
heat -ag, а не -gg). Со
случайными GUID-ами installer не понимает, что файлы старой и новой версии —
одни и те же: ссылки не пересчитываются, и удаление старой версии сносит
только что скопированное. При одном порядке действий пропадали библиотеки
JVM, при другом — почти весь образ.light нужно указывать культуру (-cultures:ru-ru), иначе он собирает
базу в кодовой странице 1252 и падает на любой кириллице../gradlew :composeApp:packageMsi
Оставлен как запасной вариант, но обновление поверх запущенного приложения им делать нельзя.
./gradlew :composeApp:packageMsi
Результат — composeApp/build/compose/binaries/main/msi/OpenDisk-<версия>.msi
(около 92 МБ: внутри JRE и встроенный rclone). WiX ставить не нужно — плагин
Compose скачивает и распаковывает его сам (задача unzipWix).
Портативный вариант без установщика:
./gradlew :composeApp:createDistributable
Он кладёт готовое приложение в composeApp/build/compose/binaries/main/app/OpenDisk/,
запускать — OpenDisk.exe.
Блок windows { } в nativeDistributions задаёт три вещи, без которых
установщик получается неудобным, и все три проверены на реальной установке:
menu / shortcut — без них jpackage не создаёт никаких ярлыков, и после
установки приложение запускается только вручную из Program Files;upgradeUuid — закреплён явно. Без фиксации новая версия рискует встать
рядом со старой вместо обновления; значение взято то самое, которое jpackage
вывел из имени пакета для 0.1.5, чтобы не сломать обновление с неё.Установщик машинный (ALLUSERS=1), то есть требует прав администратора и ставит
в Program Files. Без повышения прав msiexec падает с кодом 1603 и ошибкой 1402
(отказ записи в HKEY_LOCAL_MACHINE).
Только ASCII в метаданных установщика. WiX собирает MSI в кодовой странице 1252 и падает с
LGHT0311на кириллице вdescription/vendor. Поэтому описание вnativeDistributionsанглийское, хотя интерфейс русский.
Её нет и быть не может:
windows-x64 и windows-arm64 — сборки под x86 не существует;В дистрибутив вшиты rclone и установщик WinFsp с зафиксированными версиями и контрольными суммами. Фиксация нужна, чтобы сборка падала, а не собирала пакет с подменённым бинарником — но сами по себе они от этого не обновятся.
Раз в неделю CI сверяет зафиксированные версии с актуальными и заводит issue,
если отстали (.github/workflows/bundled-updates.yml). Автоматически ничего
не поднимается: смена версии требует новых сумм и проверки работоспособности.
Порядок обновления:
rcloneVersion или winFspVersion в composeApp/build.gradle.kts.https://downloads.rclone.org/v<версия>/SHA256SUMS;sha256sum скачанного установщика.Собрать и проверить, что приложение работает. Это не формальность:
формы ответов RC API между версиями rclone менялись, и на них завязан
разбор ответов в rclone-bridge. Интеграционные тесты гоняются против
настоящего rclone как раз для этого:
./gradlew test -Dopendisk.rclone.path=composeApp/build/appResources/common/rclone
./gradlew test
Тесты rclone-bridge мокают HTTP-ответы и настоящий rclone не требуют. Отдельно
есть RcloneIntegrationTest — он поднимает реальный rclone rcd и проверяет, что
формы ответов RC API те, на которые рассчитан клиент. По умолчанию он пропускается;
чтобы прогнать, укажите бинарник (после сборки он лежит в ресурсах приложения):
./gradlew test -Dopendisk.rclone.path="$PWD/composeApp/build/appResources/common/rclone"
rclone не ставится в систему отдельно — он едет внутри OpenDisk. Задача
:composeApp:downloadRclone на этапе сборки скачивает официальный архив с
downloads.rclone.org, сверяет SHA-256 с суммами, зафиксированными в
composeApp/build.gradle.kts, и кладёт бинарник в ресурсы приложения. Она
подцеплена к prepareAppResources, поэтому отрабатывает и при
./gradlew :composeApp:run, и при сборке установщика — вручную вызывать не нужно.
Обновление версии rclone: поменяйте rcloneVersion и суммы в rcloneChecksums,
сверив их с https://downloads.rclone.org/v<version>/SHA256SUMS.
Собрать без скачивания (офлайн, CI без сети) — приложение тогда будет искать
rclone в PATH:
./gradlew build -Popendisk.skipRcloneDownload=true
Подсунуть свой бинарник вместо встроенного:
OPENDISK_RCLONE=/path/to/rclone ./gradlew :composeApp:run
Проверить, на чём запустится сборка:
java -version
Если это не 17 и не 21 — укажите нужный JDK явно через JAVA_HOME:
# Linux / macOS
JAVA_HOME=/path/to/jdk-21 ./gradlew build
# Windows PowerShell
$env:JAVA_HOME = "C:\Users\<user>\.jdks\jdk-21"; .\gradlew.bat build
Чтобы не указывать это каждый раз, можно прописать путь в ~/.gradle/gradle.properties
(файл вне репозитория, поэтому не мешает остальным):
org.gradle.java.home=/path/to/jdk-21
Модуль :composeApp собирается плагином kotlin("jvm"), который по умолчанию
видит только src/main/kotlin. Каталоги src/commonMain/kotlin и
src/desktopMain/kotlin (см. ARCHITECTURE.md) подключены
вручную в composeApp/build.gradle.kts. Если в будущем модуль переедет на
kotlin("multiplatform"), этот блок sourceSets нужно будет убрать.