opendisk

Первый запуск проекта

Требования

Сборка

./gradlew build
./gradlew :composeApp:run

Gradle wrapper (gradlew, gradlew.bat, gradle/wrapper/) лежит в репозитории, устанавливать Gradle отдельно не нужно — при первом запуске он сам скачает нужный дистрибутив.

Сборка установщика под Windows

./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.

Три грабли, на которые здесь легко наступить снова:

Устаревший установщик jpackage

./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 задаёт три вещи, без которых установщик получается неудобным, и все три проверены на реальной установке:

Установщик машинный (ALLUSERS=1), то есть требует прав администратора и ставит в Program Files. Без повышения прав msiexec падает с кодом 1603 и ошибкой 1402 (отказ записи в HKEY_LOCAL_MACHINE).

Только ASCII в метаданных установщика. WiX собирает MSI в кодовой странице 1252 и падает с LGHT0311 на кириллице в description/vendor. Поэтому описание в nativeDistributions английское, хотя интерфейс русский.

Про 32-битную сборку

Её нет и быть не может:

Обновление встроенных программ

В дистрибутив вшиты rclone и установщик WinFsp с зафиксированными версиями и контрольными суммами. Фиксация нужна, чтобы сборка падала, а не собирала пакет с подменённым бинарником — но сами по себе они от этого не обновятся.

Раз в неделю CI сверяет зафиксированные версии с актуальными и заводит issue, если отстали (.github/workflows/bundled-updates.yml). Автоматически ничего не поднимается: смена версии требует новых сумм и проверки работоспособности.

Порядок обновления:

  1. Поднять rcloneVersion или winFspVersion в composeApp/build.gradle.kts.
  2. Обновить контрольные суммы:
    • rclone — суммы всех платформ лежат на https://downloads.rclone.org/v<версия>/SHA256SUMS;
    • WinFsp — посчитать sha256sum скачанного установщика.
  3. Собрать и проверить, что приложение работает. Это не формальность: формы ответов 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

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

Если в системе несколько JDK

Проверить, на чём запустится сборка:

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 нужно будет убрать.