У desktop-приложений на Python часто одна и та же проблема: код уже работает, интерфейс готов, но релиз все еще выглядит как ручной ритуал. Собрать exe, не забыть иконку, положить файл в архив, создать GitHub Release, проверить, что пользователь скачает правильную версию. Пока проект маленький, это терпимо. После нескольких правок такой процесс начинает мешать.
На примере учебного Tkinter-приложения я разложу, как сделать релизный контур спокойнее: один тег в Git, автоматическая сборка Windows exe, ZIP в GitHub Release и проверка обновлений внутри приложения.
Почему ручная сборка быстро начинает мешать
Ручная сборка почти всегда ломается не в сложном месте, а в мелочах. Один раз забыли обновить версию. Другой раз собрали exe с консольным окном. Потом отправили пользователю старый архив, потому что имя файла осталось прежним.
Для внутреннего инструмента это тоже проблема. Если пользователь не понимает, какая версия у него стоит и где брать новую, поддержка превращается в переписку: “скачайте еще раз”, “нет, не этот файл”, “удалите старую папку”.
Минимальный релизный контур
Для небольшого Python desktop-приложения обычно достаточно такого контура:
- версия приложения хранится в коде;
- релиз запускается Git-тегом вида
v1.0.0; - GitHub Actions собирает exe на Windows runner;
- PyInstaller упаковывает entrypoint в один оконный файл;
- workflow кладет exe в ZIP и загружает архив в GitHub Release;
- приложение умеет проверить последний Release и показать ссылку на скачивание.
Это не сложная система обновлений. Приложение не заменяет само себя на диске, не трогает запущенный exe и не требует отдельного updater-процесса. Оно только сообщает, что вышла новая версия, и открывает страницу загрузки. Для первого стабильного варианта этого обычно хватает.
Сборка через PyInstaller
У PyInstaller есть два важных режима для такого сценария: --onefile и --windowed. Первый собирает приложение в один exe. Второй убирает консольное окно, что нормально для Tkinter-интерфейса.
pyinstaller --noconfirm --clean --onefile --windowed ^
--name OpenAiKeyChecker ^
--icon assets/app.ico ^
--add-data "assets/app.ico;assets" ^
main.py
Отдельно стоит подумать о путях. В onefile-сборке PyInstaller распаковывает ресурсы во временную директорию. Если приложение хранит SQLite-базу рядом с исходниками, в exe это может внезапно стать временная папка. Поэтому для собранной версии лучше явно хранить пользовательские данные рядом с exe, а ресурсы читать из PyInstaller bundle.
| Что | Где хранить | Почему |
|---|---|---|
| Иконка | Внутри bundle | Это ресурс приложения, он не меняется пользователем. |
| SQLite-база | Рядом с exe | Пользовательские данные должны переживать перезапуск и обновление. |
| ZIP релиза | GitHub Release | У пользователя должна быть одна понятная точка скачивания. |
GitHub Actions: релиз по тегу
Самый простой триггер для релизов — push тега. Разработчик делает обычный коммит, затем создает тег:
git tag v1.0.0
git push origin v1.0.0
Workflow запускается только на такие теги, ставит зависимости, прогоняет тесты, подставляет версию из тега и собирает exe. После этого архив загружается в GitHub Release.
Ключевой момент — не пропускать тесты. Даже если проект небольшой, релизная сборка должна хотя бы проверить чистую логику: генерацию тестовых строк, работу SQLite-хранилища, HTTP-клиент через mock и проверку обновлений.
Релиз должен быть результатом повторяемого сценария, а не удачного запуска команды на локальной машине.
Проверка обновлений без самозамены
Полный автоапдейтер для Windows — отдельная задача. Нужно закрывать приложение, заменять exe, учитывать права доступа, антивирусы, недокачанные файлы и rollback. Для небольшого инструмента это часто лишний риск.
Более безопасный первый шаг — проверка GitHub Releases API:
https://api.github.com/repos/OWNER/REPO/releases/latest
Приложение сравнивает текущую версию с последним тегом. Если версия новее, показывает диалог и открывает страницу скачивания. Пользователь сам скачивает ZIP и заменяет приложение, когда ему удобно.
Что важно не забыть
- добавить
build/,dist/и*.specв.gitignore; - не коммитить локальную SQLite-базу;
- проверять workflow на реальном теге, а не только локально;
- показывать версию в окне “О программе”;
- документировать, где лежит ZIP и как выпускать новый релиз;
- не усложнять автообновление, пока нет реальной причины.
Итог
Хороший релизный контур для Python desktop-приложения не обязан быть большим. Достаточно, чтобы сборка была повторяемой, версия была видна пользователю, архив появлялся в GitHub Release автоматически, а приложение умело сообщить о новой версии.
После этого релиз перестает быть ручной операцией и становится частью обычного процесса разработки: коммит, тег, проверка workflow, готовый ZIP.