Перейти к содержимому

Выпуск релизов

Релиз — это тег. CI (.github/workflows/android.yml) собирает каждый тег v*, подписывает APK и публикует их релизом на GitHub; F-Droid пересобирает релизные теги и раздаёт наш APK, если его сборка совпала с нашей байт в байт (DISTRIBUTION_RU.md). На пуш в main ничего не собирается, а пушит только автор.

Одно приложение, один пакет, один ключ подписи. К какому каналу относится сборка, говорит тег.

Стабильный Бета
Тег v5.1.0 v5.1.0-beta.1 … v5.1.0-beta.9
VERSION_NAME 5.1.0 5.1.0-beta.1
Релиз на GitHub последний (latest) pre-release, никогда не latest; заголовок кончается на «(beta)»
Описание релиза раздел ## [5.1.0] из CHANGELOG.md постоянная шапка (scripts/release/beta-notes.md), затем ## [Unreleased] — как есть на момент тега
Обновлятор в приложении предлагает всем предлагает, только если включено «Получать бета-версии»
F-Droid пересобирает и публикует не видит: его проверка обновлений берёт только релизные теги
Obtainium следит по умолчанию только с опцией Include prereleases

Бета-тег собирается ровно так же, как релизный: те же задания, те же закреплённые инструменты, те же имена APK (TailSocks-v5.1.0-beta.1-arm64-v8a-release.apk). Отличается только публикация.

Что предлагает обновлятор (Настройки → Оформление и язык → «Получать бета-версии», по умолчанию выключено):

Установлено Переключатель Что предложит
стабильная выкл. следующий стабильный релиз
стабильная вкл. самое новое по версии — стабильное или бету
бета выкл. ничего, пока не выйдет стабильный релиз новее этой беты, — тогда его
бета вкл. следующую бету или финальный релиз

Версии сравниваются так: 5.1.0-beta.2 < 5.1.0-beta.10 < 5.1.0 < 5.1.1-beta.1 (core/AppVersion.kt); pre-release старше установленной версии не предлагается никогда. У копии, которую поставил магазин, обновлятора нет вовсе (UpdateChannel), и переключателя там нет.

В бета-сборке окно «О приложении» называет приложение «TailSocks бета», отчёт диагностики добавляет «beta» в строку с версией, а «Что нового» показывает раздел [Unreleased] под именем сборки — заново после каждой беты, по-русски или по-английски.

  • Бета — это сборка: следующая версия, пока она не закончена. Сломаться в ней может что угодно; получают её только те, кто сам попросил.
  • Экспериментальная функция выходит в стабильной сборке — выключенной по умолчанию или с явной пометкой, — и её поведение ещё может поменяться. Строка о ней в списке изменений начинается с «Экспериментально:» (пример — удалённое управление в 5.0.0).

Одно не заменяет другое: функция, не готовая для всех, остаётся экспериментальной и в финальном релизе, а бета — не место для недоделок без пометки.

В version.properties две строки, и код следует из имени:

  • релиз: major×1000000 + minor×10000 + patch×100;
  • его бета N, где N от 1 до 9: этот код − 100 + 10×N.

Каждый APK добавляет к коду номер своей ABI: 0 у универсального, 1–4 у armeabi-v7a, arm64-v8a, x86, x86_64.

Версия VERSION_CODE APK
5.0.0 5000000 5000000–5000004
5.0.1-beta.1 5000010 5000010–5000014
5.0.1 5000100 5000100–5000104
5.1.0-beta.1 5009910 5009910–5009914
5.1.0-beta.2 5009920 5009920–5009924
5.1.0 5010000 5010000–5010004
6.0.0-beta.1 5999910 5999910–5999914
6.0.0 6000000 6000000–6000004

Почему порядок верный: версия вместе со своими бетами занимает коды от код − 90 до код + 4, а между двумя версиями не меньше сотни. Значит, любая бета обновляет любой более ранний релиз и любую более раннюю бету, релиз обновляет свои беты, и ни один уже опубликованный код не меняется.

Ограничения:

  • Девять бет на версию. У десятой был бы код самого релиза. Если девяти не хватило, следующая — бета 1 следующего патча, и выпускается тогда уже этот патч.
  • Minor и patch — до 99, как и раньше.
  • Код беты похож на версию ниже. Беты 5.1.0 лежат в конце сотни 5.0.99, беты 6.0.0 — чуть ниже шести миллионов. Пересечений нет: релиз занимает только первые пять кодов своей сотни, и 5.0.99 тоже. А сам код никто не читает, от него нужен только порядок.

Правило записано один раз — в buildSrc/src/main/java/io/github/bropines/tailscaled/release/ReleaseVersion.java. Gradle отказывается конфигурировать сборку, в которой код не следует из имени; CI запускает тот же файл на теге перед сборкой; ReleaseVersionTest проверяет порядок, описанный выше. Проверить тег руками, из корня репозитория:

Окно терминала
java buildSrc/src/main/java/io/github/bropines/tailscaled/release/ReleaseVersion.java v5.1.0-beta.1
  1. Список изменений не трогать: работа остаётся под ## [Unreleased] в CHANGELOG.md и CHANGELOG_RU.md. Своего раздела у беты нет.
  2. В version.properties поставить VERSION_NAME=5.1.0-beta.1 и VERSION_CODE=5009910.
  3. Проверить: команда выше печатает kind=beta, затем ./gradlew :app:testDebugUnitTest :app:assembleDebug.
  4. Коммит (chore: release 5.1.0-beta.1), тег v5.1.0-beta.1, запушить коммит и тег.
  5. CI опубликует pre-release. На странице релизов у него должна стоять метка Pre-release, а Latest — оставаться на последнем стабильном релизе: именно его спрашивает у GitHub каждая установленная копия без переключателя.

Следующая бета — то же самое с 5.1.0-beta.2 и 5009920. [Unreleased] продолжает расти, и в описании каждой беты — весь раздел, а не разница с предыдущей.

  1. Переименовать ## [Unreleased] в ## [5.1.0] - ГГГГ-ММ-ДД в обоих списках изменений.
  2. Поставить VERSION_NAME=5.1.0 и VERSION_CODE=5010000.
  3. Коммит (chore: release 5.1.0), тег v5.1.0, пуш.
  4. CI опубликует его последним релизом, с этим разделом в описании. Тем, кто на бете, он предлагается так же, как всем.
  5. Перевести рецепт F-Droid на новую версию (ниже).

F-Droid публикует наш APK, только пока его пересборка релизного тега совпадает с нашей, а новые версии находит сам. Это держится на следующем:

  • Релизный тег — v<major>.<minor>.<patch> и ничего больше. Беты отсекает строка рецепта UpdateCheckMode: Tags ^v\d+\.\d+\.\d+$; без $ бета-тег был бы принят за релиз.
  • В version.properties остаются две строки, VERSION_NAME= и VERSION_CODE=, которые читаются из коммита с тегом, а код релиза — по-прежнему major×1000000 + minor×10000 + patch×100 с добавками 1–4 для ABI (VercodeOperation). F-Droid берёт первое совпадение каждой из них в файле, поэтому в комментариях над ними присваивание целиком писать нельзя.
  • APK релиза называются TailSocks-v<версия>-<abi>-release.apk и лежат в релизе с тегом v<версия> — это адрес binary: в рецепте.
  • Путь сборки релизного тега: ядро на Go собирается в /home/vagrant/build/io.github.bropines.tailscaled закреплёнными Go, gomobile и NDK, C-библиотеки — через Gradle, сборка чистая, APK один для всех каналов. Бета идёт тем же путём, чтобы проверять ту сборку, которую получит релиз; до шага публикации ничто не должно зависеть от вида тега.
  • buildSrc остаётся простой Java без зависимостей: он компилируется перед каждой сборкой, у F-Droid тоже, и репозитории ему не нужны.

Сам рецепт живёт в форке fdroiddata; distribution/io.github.bropines.tailscaled.yml — его черновик. Релиз меняет там versionName, versionCode и commit в каждой из четырёх записей, а также CurrentVersion и CurrentVersionCode; бета не меняет ничего.