mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
Publish builds to a branch instead of to releases.
Releases and version numbers are gone. CI now rebuilds all three platforms on every change to main and commits the packages to the `builds` branch, so what anyone downloads is the current state of the repository. The branch is rewritten from scratch each time rather than added to, so the repository keeps one copy of the packages instead of a ~70 MB set per commit. Without tags there is no version to compare, so a build carries the commit it was made from and the branch carries a VERSION file naming the same commit. The updater fetches those few bytes and treats any difference as being behind; commits are not ordered, and the branch only ever holds the current build. The installers fetch from the branch over raw.githubusercontent.com for the same reason. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
4566336b1b
commit
737e27deb0
10 changed files with 190 additions and 198 deletions
|
|
@ -1,9 +1,18 @@
|
|||
name: Release
|
||||
name: Build
|
||||
|
||||
# Packages are published to the `builds` branch instead of to a GitHub
|
||||
# Release, so everything the installers and the in-app updater fetch comes
|
||||
# straight from the repository and always matches what is on main.
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
branches:
|
||||
- main
|
||||
# Nothing to rebuild when only documentation changed.
|
||||
paths-ignore:
|
||||
- "README.md"
|
||||
- "LICENSE"
|
||||
- ".gitignore"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
|
@ -26,7 +35,7 @@ jobs:
|
|||
|
||||
- name: Build AppImage
|
||||
env:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
COMMIT: ${{ github.sha }}
|
||||
run: |
|
||||
chmod +x scripts/bundle-linux.sh
|
||||
./scripts/bundle-linux.sh
|
||||
|
|
@ -60,7 +69,7 @@ jobs:
|
|||
|
||||
- name: Build and bundle
|
||||
env:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
COMMIT: ${{ github.sha }}
|
||||
run: |
|
||||
make bundle
|
||||
cd dist
|
||||
|
|
@ -87,7 +96,7 @@ jobs:
|
|||
|
||||
- name: Build .app zip
|
||||
env:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
COMMIT: ${{ github.sha }}
|
||||
run: |
|
||||
chmod +x scripts/bundle-macos.sh
|
||||
./scripts/bundle-macos.sh
|
||||
|
|
@ -99,7 +108,7 @@ jobs:
|
|||
path: dist/maturita-macos-*.zip
|
||||
|
||||
publish:
|
||||
name: Publish GitHub Release
|
||||
name: Publish to builds branch
|
||||
needs: [linux, windows, macos]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
@ -108,34 +117,34 @@ jobs:
|
|||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-assets
|
||||
path: staged
|
||||
merge-multiple: true
|
||||
|
||||
# The installers carry no application: each downloads the newest release
|
||||
# when the user runs it. The macOS one is zipped because a release asset
|
||||
# loses its executable bit, which a .command needs to be double-clicked.
|
||||
- name: Collect installers
|
||||
run: |
|
||||
cp installers/maturita-installer-linux.sh release-assets/
|
||||
cp installers/maturita-installer-windows.cmd release-assets/
|
||||
cp installers/maturita-installer-linux.sh staged/
|
||||
cp installers/maturita-installer-windows.cmd staged/
|
||||
chmod +x installers/maturita-installer-macos.command
|
||||
(cd installers && zip -q ../release-assets/maturita-installer-macos.zip \
|
||||
(cd installers && zip -q ../staged/maturita-installer-macos.zip \
|
||||
maturita-installer-macos.command)
|
||||
# The updater downloads this to see whether it is behind.
|
||||
echo "${{ github.sha }}" > staged/VERSION
|
||||
|
||||
- name: List assets
|
||||
run: find release-assets -type f -print
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
release-assets/maturita-linux-*.AppImage
|
||||
release-assets/maturita-windows-x64.zip
|
||||
release-assets/maturita-macos-*.zip
|
||||
release-assets/maturita-installer-macos.zip
|
||||
release-assets/maturita-installer-windows.cmd
|
||||
release-assets/maturita-installer-linux.sh
|
||||
generate_release_notes: true
|
||||
fail_on_unmatched_files: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# The branch is rewritten from scratch every time rather than added to,
|
||||
# so the repository keeps one copy of the packages instead of a new
|
||||
# ~70 MB set per commit.
|
||||
- name: Push to builds branch
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
rm -rf .git/index.lock
|
||||
git checkout --orphan builds
|
||||
git rm -rf --cached . >/dev/null
|
||||
find . -maxdepth 1 ! -name . ! -name .git ! -name staged \
|
||||
-exec rm -rf {} +
|
||||
mv staged/* .
|
||||
rmdir staged
|
||||
ls -la
|
||||
git add -A
|
||||
git commit -q -m "Build from ${{ github.sha }}"
|
||||
git push -f origin builds
|
||||
|
|
@ -52,9 +52,9 @@ else()
|
|||
add_executable(maturita ${MATURITA_SOURCES})
|
||||
endif()
|
||||
|
||||
# Mirrors VERSION in the Makefile: bakes the release tag in for the updater.
|
||||
if(DEFINED APP_VERSION AND NOT APP_VERSION STREQUAL "")
|
||||
target_compile_definitions(maturita PRIVATE APP_VERSION="${APP_VERSION}")
|
||||
# Mirrors COMMIT in the Makefile: bakes the source commit in for the updater.
|
||||
if(DEFINED APP_COMMIT AND NOT APP_COMMIT STREQUAL "")
|
||||
target_compile_definitions(maturita PRIVATE APP_COMMIT="${APP_COMMIT}")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
|
|
|
|||
12
Makefile
12
Makefile
|
|
@ -17,12 +17,12 @@ GTK_LIBS := $(shell $(PKG_CONFIG) --libs gtk4)
|
|||
CFLAGS = -Wall -Wextra -Wno-deprecated-declarations $(GTK_CFLAGS)
|
||||
LIBS = $(GTK_LIBS) -lm
|
||||
|
||||
# Release builds pass the git tag (the CI workflow exports it), which bakes the
|
||||
# version into the binary for the updater to compare against. Without it the
|
||||
# build keeps the "dev" default from maturita.h and never offers an update.
|
||||
VERSION ?=
|
||||
ifneq ($(VERSION),)
|
||||
CFLAGS += -DAPP_VERSION='"$(VERSION)"'
|
||||
# Published builds pass the commit they were made from (the CI workflow
|
||||
# exports it), which the updater compares against the build in the repository.
|
||||
# Without it the build keeps the "dev" default and never offers an update.
|
||||
COMMIT ?=
|
||||
ifneq ($(COMMIT),)
|
||||
CFLAGS += -DAPP_COMMIT='"$(COMMIT)"'
|
||||
endif
|
||||
|
||||
# Windows (MSYS2 / MinGW): .exe suffix, no console window, embed .ico.
|
||||
|
|
|
|||
97
README.md
97
README.md
|
|
@ -125,44 +125,35 @@ GTK 4 interface.
|
|||
|
||||
### Downloads (prebuilt)
|
||||
|
||||
Precompiled, self-contained builds are published on the
|
||||
**GitHub Releases** page of this project (enable Actions on a GitHub remote)
|
||||
when a version tag (`v*`) is pushed. No compiler or GTK development packages
|
||||
are required:
|
||||
There are no releases and no version numbers. CI rebuilds all three platforms
|
||||
whenever `main` changes and commits the packages to the
|
||||
[`builds`](../../tree/builds) branch, so what you download is always the
|
||||
current state of the repository. No compiler or GTK development packages are
|
||||
required.
|
||||
|
||||
| Platform | Asset | How to run |
|
||||
| -------- | ----- | ---------- |
|
||||
| Linux (x86_64) | `maturita-linux-x86_64.AppImage` | `chmod +x` the file, then double-click or run it |
|
||||
| macOS (Apple Silicon) | `maturita-macos-arm64.zip` | Unzip and open `Maturita.app` |
|
||||
| Windows (x64) | `maturita-windows-x64.zip` | Unzip and run `maturita.exe` |
|
||||
Start with the installer for your platform. It contains no application of its
|
||||
own — it fetches the current build when you run it, so the file never goes out
|
||||
of date and you only ever need this one download:
|
||||
|
||||
Progress and settings are saved next to the AppImage / `.app` / unzipped
|
||||
folder in a `progress/` directory.
|
||||
| Platform | File | How to run |
|
||||
| -------- | ---- | ---------- |
|
||||
| macOS (Apple Silicon) | [`maturita-installer-macos.zip`](../../raw/builds/maturita-installer-macos.zip) | Unzip, then double-click the `.command` file |
|
||||
| Windows (x64) | [`maturita-installer-windows.cmd`](../../raw/builds/maturita-installer-windows.cmd) | Double-click the file |
|
||||
| Linux (x86_64) | [`maturita-installer-linux.sh`](../../raw/builds/maturita-installer-linux.sh) | `sh maturita-installer-linux.sh` |
|
||||
|
||||
#### Installers
|
||||
|
||||
Each release also ships a small installer that contains no application of its
|
||||
own: it downloads the newest build when you run it, so the file never goes out
|
||||
of date. Once installed, the app keeps itself up to date on its own — it checks
|
||||
shortly after startup and offers the update in a banner, and there is a manual
|
||||
check under **Settings → Updates**.
|
||||
|
||||
| Platform | Asset | How to run |
|
||||
| -------- | ----- | ---------- |
|
||||
| macOS (Apple Silicon) | `maturita-installer-macos.zip` | Unzip, then double-click the `.command` file |
|
||||
| Windows (x64) | `maturita-installer-windows.cmd` | Double-click the file |
|
||||
| Linux (x86_64) | `maturita-installer-linux.sh` | `sh maturita-installer-linux.sh` |
|
||||
Once installed, the app keeps itself up to date: it compares the commit it was
|
||||
built from against `VERSION` on the `builds` branch shortly after startup and
|
||||
offers the update in a banner. There is a manual check under
|
||||
**Settings → Updates**.
|
||||
|
||||
The builds are not code-signed, so the first launch needs one extra step:
|
||||
on macOS right-click the installer and pick **Open**, and on Windows choose
|
||||
**More info → Run anyway** if SmartScreen appears.
|
||||
|
||||
To cut a release yourself (needs a GitHub remote with Actions enabled):
|
||||
|
||||
```bash
|
||||
git tag v1.0.0
|
||||
git push origin v1.0.0
|
||||
```
|
||||
The packages themselves are on the same branch if you would rather skip the
|
||||
installer: `maturita-linux-x86_64.AppImage`, `maturita-macos-arm64.zip` and
|
||||
`maturita-windows-x64.zip`. Progress and settings are saved next to the
|
||||
AppImage / `.app` / unzipped folder in a `progress/` directory.
|
||||
|
||||
Local packaging (same scripts the CI uses):
|
||||
|
||||
|
|
@ -605,43 +596,37 @@ moderním GTK 4 rozhraním stylovaným přes CSS.
|
|||
|
||||
### Stažení (předkompilované)
|
||||
|
||||
Hotové self-contained buildy vycházejí na stránce
|
||||
**GitHub Releases** tohoto projektu (Actions na GitHub remote)
|
||||
při pushi tagu `v*`. Kompilátor ani vývojové balíčky GTK nejsou potřeba:
|
||||
Žádné releasy ani čísla verzí nejsou. CI přestaví všechny tři platformy
|
||||
pokaždé, když se změní `main`, a balíčky commitne do větve
|
||||
[`builds`](../../tree/builds) — stahujete tedy vždycky aktuální stav repa.
|
||||
Kompilátor ani vývojové balíčky GTK nejsou potřeba.
|
||||
|
||||
Začněte instalátorem pro svou platformu. Samotnou aplikaci neobsahuje, stáhne
|
||||
si aktuální build až při spuštění, takže soubor nikdy nezestárne a stačí vám
|
||||
tohle jedno stažení:
|
||||
|
||||
| Platforma | Soubor | Spuštění |
|
||||
| --------- | ------ | -------- |
|
||||
| Linux (x86_64) | `maturita-linux-x86_64.AppImage` | `chmod +x` a spustit / dvojklik |
|
||||
| macOS (Apple Silicon) | `maturita-macos-arm64.zip` | Rozbalit a otevřít `Maturita.app` |
|
||||
| Windows (x64) | `maturita-windows-x64.zip` | Rozbalit a spustit `maturita.exe` |
|
||||
| macOS (Apple Silicon) | [`maturita-installer-macos.zip`](../../raw/builds/maturita-installer-macos.zip) | Rozbalit a dvojklik na soubor `.command` |
|
||||
| Windows (x64) | [`maturita-installer-windows.cmd`](../../raw/builds/maturita-installer-windows.cmd) | Dvojklik na soubor |
|
||||
| Linux (x86_64) | [`maturita-installer-linux.sh`](../../raw/builds/maturita-installer-linux.sh) | `sh maturita-installer-linux.sh` |
|
||||
|
||||
Postup a nastavení se ukládají vedle AppImage / `.app` / rozbalené složky
|
||||
do adresáře `progress/`.
|
||||
|
||||
#### Instalátory
|
||||
|
||||
Ke každému releasu vychází i malý instalátor, který samotnou aplikaci
|
||||
neobsahuje: nejnovější build si stáhne až při spuštění, takže soubor nikdy
|
||||
nezestárne. Nainstalovaná aplikace se pak udržuje aktuální sama — chvíli po
|
||||
startu zkontroluje nové verze a nabídne je v pruhu nahoře, ruční kontrola je
|
||||
Nainstalovaná aplikace se pak udržuje aktuální sama: chvíli po startu porovná
|
||||
commit, ze kterého je postavená, se souborem `VERSION` ve větvi `builds`
|
||||
a případnou aktualizaci nabídne v pruhu nahoře. Ruční kontrola je
|
||||
v **Nastavení → Aktualizace**.
|
||||
|
||||
| Platforma | Soubor | Spuštění |
|
||||
| --------- | ------ | -------- |
|
||||
| macOS (Apple Silicon) | `maturita-installer-macos.zip` | Rozbalit a dvojklik na soubor `.command` |
|
||||
| Windows (x64) | `maturita-installer-windows.cmd` | Dvojklik na soubor |
|
||||
| Linux (x86_64) | `maturita-installer-linux.sh` | `sh maturita-installer-linux.sh` |
|
||||
|
||||
Buildy nejsou podepsané, takže první spuštění chce jeden krok navíc: na macOS
|
||||
klikněte na instalátor pravým tlačítkem a zvolte **Otevřít**, na Windows při
|
||||
hlášce SmartScreenu **Další informace → Přesto spustit**.
|
||||
|
||||
Vytvoření releasu (GitHub remote s Actions):
|
||||
Kdo chce instalátor přeskočit, najde na stejné větvi i samotné balíčky:
|
||||
`maturita-linux-x86_64.AppImage`, `maturita-macos-arm64.zip` a
|
||||
`maturita-windows-x64.zip`. Postup a nastavení se ukládají vedle AppImage /
|
||||
`.app` / rozbalené složky do adresáře `progress/`.
|
||||
|
||||
```bash
|
||||
git tag v1.0.0
|
||||
git push origin v1.0.0
|
||||
```
|
||||
Vydání nové verze je prostý push na `main` — build i publikování do větve
|
||||
`builds` obstará CI.
|
||||
|
||||
Lokální balíček (stejné skripty jako CI):
|
||||
|
||||
|
|
|
|||
15
i18n.c
15
i18n.c
|
|
@ -20,11 +20,12 @@ const TrEntry tr_ui[] = {
|
|||
{"theme", "TÉMA", "THEME"},
|
||||
{"language", "JAZYK", "LANGUAGE"},
|
||||
{"updates", "AKTUALIZACE", "UPDATES"},
|
||||
{"update_current", "Verze %s", "Version %s"},
|
||||
{"update_current", "Build %s", "Build %s"},
|
||||
{"update_checking", "Hledám aktualizace…", "Checking for updates…"},
|
||||
{"update_uptodate", "Máte nejnovější verzi (%s).",
|
||||
{"update_uptodate", "Máte nejnovější build (%s).",
|
||||
"You are up to date (%s)."},
|
||||
{"update_available", "Je dostupná verze %s.", "Version %s is available."},
|
||||
{"update_available", "Je dostupný novější build (%s).",
|
||||
"A newer build is available (%s)."},
|
||||
{"update_downloading", "Stahuji aktualizaci…", "Downloading the update…"},
|
||||
{"update_staged", "Aktualizace je připravená — restartujte aplikaci.",
|
||||
"The update is ready — restart the app to finish."},
|
||||
|
|
@ -38,16 +39,16 @@ const TrEntry tr_ui[] = {
|
|||
{"update_err_download", "Stahování se nepovedlo.", "The download failed."},
|
||||
{"update_err_curl", "V systému chybí curl, aktualizace nejsou dostupné.",
|
||||
"curl is missing, so updates are unavailable."},
|
||||
{"update_err_unsupported", "Pro tuto platformu se aktualizace nevydává.",
|
||||
"No update is published for this platform."},
|
||||
{"update_err_unsupported", "Pro tuto platformu se build nepublikuje.",
|
||||
"No build is published for this platform."},
|
||||
{"update_err_readonly",
|
||||
"Do umístění aplikace nelze zapisovat. Přesuňte ji třeba do Aplikací.",
|
||||
"The app's location is not writable. Move it somewhere else, for "
|
||||
"example to Applications."},
|
||||
{"update_err_stage", "Aktualizaci nešlo připravit.",
|
||||
"Could not prepare the update."},
|
||||
{"update_err_devbuild", "Tento build nemá číslo verze (dev).",
|
||||
"This build carries no version number (dev)."},
|
||||
{"update_err_devbuild", "Tento build nepochází z repozitáře (dev).",
|
||||
"This build did not come from the repository (dev)."},
|
||||
{"welcome_body",
|
||||
"maturita.C je vzdělávací program pro studenty středních škol a "
|
||||
"gymnázií na přípravu k maturitě.\n\n"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#!/bin/sh
|
||||
# maturita.c installer for Linux.
|
||||
#
|
||||
# Carries no application of its own: it downloads whatever the newest release
|
||||
# is at the moment you run it, so this file never goes stale. GitHub resolves
|
||||
# the /releases/latest/download/ path to the current tag on every request.
|
||||
# Carries no application of its own: it downloads the current build straight
|
||||
# from the repository's `builds` branch, which CI rewrites whenever main
|
||||
# changes. This file therefore never goes stale.
|
||||
#
|
||||
# Run it with: sh maturita-installer-linux.sh
|
||||
# The app updates itself from then on.
|
||||
|
|
@ -11,8 +11,9 @@
|
|||
set -eu
|
||||
|
||||
REPO="pepaskopec-blip/maturita.c"
|
||||
BRANCH="builds"
|
||||
ASSET="maturita-linux-x86_64.AppImage"
|
||||
URL="https://github.com/$REPO/releases/latest/download/$ASSET"
|
||||
URL="https://raw.githubusercontent.com/$REPO/$BRANCH/$ASSET"
|
||||
|
||||
# ~/Applications is where desktop environments look for AppImages, and the app
|
||||
# keeps its progress/ directory next to its own file.
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
#!/bin/sh
|
||||
# maturita.c installer for macOS.
|
||||
#
|
||||
# Carries no application of its own: it downloads whatever the newest release
|
||||
# is at the moment you run it, so this file never goes stale. GitHub resolves
|
||||
# the /releases/latest/download/ path to the current tag on every request.
|
||||
# Carries no application of its own: it downloads the current build straight
|
||||
# from the repository's `builds` branch, which CI rewrites whenever main
|
||||
# changes. This file therefore never goes stale.
|
||||
#
|
||||
# Double-click the file to run it. The app updates itself from then on.
|
||||
|
||||
set -eu
|
||||
|
||||
REPO="pepaskopec-blip/maturita.c"
|
||||
BRANCH="builds"
|
||||
ASSET="maturita-macos-arm64.zip"
|
||||
URL="https://github.com/$REPO/releases/latest/download/$ASSET"
|
||||
URL="https://raw.githubusercontent.com/$REPO/$BRANCH/$ASSET"
|
||||
|
||||
say() { printf '%s\n' "$*"; }
|
||||
die() { printf '\nError: %s\n' "$*" >&2; printf 'Press Return to close. '; read -r _; exit 1; }
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
@echo off
|
||||
rem maturita.c installer for Windows.
|
||||
rem
|
||||
rem Carries no application of its own: it downloads whatever the newest release
|
||||
rem is at the moment you run it, so this file never goes stale. GitHub resolves
|
||||
rem the /releases/latest/download/ path to the current tag on every request.
|
||||
rem Carries no application of its own: it downloads the current build straight
|
||||
rem from the repository's `builds` branch, which CI rewrites whenever main
|
||||
rem changes. This file therefore never goes stale.
|
||||
rem
|
||||
rem Double-click the file to run it. The app updates itself from then on.
|
||||
|
||||
setlocal
|
||||
set "REPO=pepaskopec-blip/maturita.c"
|
||||
set "BRANCH=builds"
|
||||
set "ASSET=maturita-windows-x64.zip"
|
||||
set "URL=https://github.com/%REPO%/releases/latest/download/%ASSET%"
|
||||
set "URL=https://raw.githubusercontent.com/%REPO%/%BRANCH%/%ASSET%"
|
||||
set "DEST=%LOCALAPPDATA%\Programs\Maturita"
|
||||
|
||||
echo Installing maturita.C
|
||||
|
|
@ -51,7 +52,7 @@ exit /b 0
|
|||
echo.
|
||||
echo Error: this installer needs curl and tar, which come with Windows 10
|
||||
echo version 1803 and later. Please update Windows, or download the zip
|
||||
echo manually from https://github.com/%REPO%/releases/latest
|
||||
echo manually from https://github.com/%REPO%/tree/%BRANCH%
|
||||
pause
|
||||
exit /b 1
|
||||
|
||||
|
|
|
|||
13
maturita.h
13
maturita.h
|
|
@ -21,13 +21,16 @@
|
|||
#define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */
|
||||
#define CSS_FILE "style.css"
|
||||
#define APP_ID "org.maturita.Maturita"
|
||||
/* Release builds bake the git tag in (see VERSION in the Makefile), so the
|
||||
* updater can tell what it is running. Plain builds stay at "dev" and never
|
||||
* report an update as available. */
|
||||
#ifndef APP_VERSION
|
||||
#define APP_VERSION "dev"
|
||||
/* Published builds bake in the commit they were made from (see COMMIT in the
|
||||
* Makefile), which is what the updater compares against the build published
|
||||
* in the repository. Local builds stay at "dev" and never offer an update. */
|
||||
#ifndef APP_COMMIT
|
||||
#define APP_COMMIT "dev"
|
||||
#endif
|
||||
#define UPDATE_REPO "pepaskopec-blip/maturita.c"
|
||||
/* Packages live on their own branch rather than in releases, so the update
|
||||
* always matches whatever is on main. */
|
||||
#define UPDATE_BRANCH "builds"
|
||||
#define ICON_NAME "maturita"
|
||||
#define ICON_FILE "assets/app-icon.png"
|
||||
#define ICON_THEME_DIR "assets/icons" /* cwd when run from source tree */
|
||||
|
|
|
|||
139
update.c
139
update.c
|
|
@ -19,9 +19,13 @@
|
|||
* Every call is asynchronous, so a slow or unreachable network never blocks
|
||||
* the interface.
|
||||
*
|
||||
* The newest version is read from the redirect GitHub serves for
|
||||
* /releases/latest, which points at /releases/tag/<tag>. That avoids the REST
|
||||
* API together with its unauthenticated rate limit, and needs no JSON parser.
|
||||
* Packages are not published as releases. They live on the `builds` branch of
|
||||
* the repository, which CI rewrites on every change to main, so an update is
|
||||
* simply whatever that branch currently holds. The branch also carries a
|
||||
* VERSION file naming the commit its packages were built from; comparing that
|
||||
* against the commit baked into this binary is the whole check. Serving it
|
||||
* from raw.githubusercontent.com avoids the REST API and its unauthenticated
|
||||
* rate limit, and needs no JSON parser.
|
||||
*
|
||||
* An application cannot reliably replace its own files while it is running, so
|
||||
* all three platforms follow the same shape: unpack the new version into a
|
||||
|
|
@ -30,9 +34,9 @@
|
|||
* script detached and quit. Staging beside the install keeps the final move on
|
||||
* one filesystem and proves up front that the location is writable. */
|
||||
|
||||
#define UPDATE_LATEST_URL "https://github.com/" UPDATE_REPO "/releases/latest"
|
||||
#define UPDATE_ASSET_URL "https://github.com/" UPDATE_REPO \
|
||||
"/releases/latest/download/"
|
||||
#define UPDATE_BASE_URL "https://raw.githubusercontent.com/" UPDATE_REPO \
|
||||
"/" UPDATE_BRANCH "/"
|
||||
#define UPDATE_VERSION_URL UPDATE_BASE_URL "VERSION"
|
||||
#define UPDATE_STAGING_DIR ".maturita-update"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
|
@ -64,7 +68,7 @@ typedef enum {
|
|||
static struct {
|
||||
UpdateState state;
|
||||
gboolean interactive; /* user asked, so also report "up to date" */
|
||||
char *latest; /* newest release version, without any "v" */
|
||||
char *latest; /* commit the published packages were built from */
|
||||
char *staging; /* scratch directory beside the install */
|
||||
char *archive; /* asset downloaded into the staging directory */
|
||||
char *source; /* unpacked version the swap script moves in */
|
||||
|
|
@ -81,39 +85,33 @@ static struct {
|
|||
} up;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Versions */
|
||||
/* Build identity */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static const char *strip_v(const char *v) {
|
||||
return (v && (v[0] == 'v' || v[0] == 'V')) ? v + 1 : v;
|
||||
/* Commits are not ordered, so there is nothing to compare: the branch only
|
||||
* ever holds the current build, and anything else means this copy is behind.
|
||||
* Short and full forms count as the same commit, so the interface can show a
|
||||
* short one without confusing the check. */
|
||||
static gboolean same_commit(const char *a, const char *b) {
|
||||
size_t la, lb, n;
|
||||
|
||||
if (!a || !b || !*a || !*b)
|
||||
return FALSE;
|
||||
la = strlen(a);
|
||||
lb = strlen(b);
|
||||
n = MIN(la, lb);
|
||||
return n >= 7 && g_ascii_strncasecmp(a, b, n) == 0;
|
||||
}
|
||||
|
||||
/* Component-wise numeric comparison, so 1.0.10 sorts above 1.0.9. Missing
|
||||
* components count as zero, which makes 1.1 and 1.1.0 equal. */
|
||||
static int version_cmp(const char *a, const char *b) {
|
||||
char **pa = g_strsplit(strip_v(a), ".", -1);
|
||||
char **pb = g_strsplit(strip_v(b), ".", -1);
|
||||
guint na = g_strv_length(pa);
|
||||
guint nb = g_strv_length(pb);
|
||||
guint n = MAX(na, nb);
|
||||
int result = 0;
|
||||
|
||||
for (guint i = 0; i < n && result == 0; i++) {
|
||||
gint64 va = i < na ? g_ascii_strtoll(pa[i], NULL, 10) : 0;
|
||||
gint64 vb = i < nb ? g_ascii_strtoll(pb[i], NULL, 10) : 0;
|
||||
|
||||
if (va != vb)
|
||||
result = va < vb ? -1 : 1;
|
||||
}
|
||||
|
||||
g_strfreev(pa);
|
||||
g_strfreev(pb);
|
||||
return result;
|
||||
/* Only builds published by CI carry a commit; see APP_COMMIT in maturita.h. */
|
||||
static gboolean is_published_build(void) {
|
||||
return g_ascii_isxdigit(APP_COMMIT[0]) && strlen(APP_COMMIT) >= 7;
|
||||
}
|
||||
|
||||
/* Only tagged builds know their version; see APP_VERSION in maturita.h. */
|
||||
static gboolean is_release_build(void) {
|
||||
return g_ascii_isdigit(strip_v(APP_VERSION)[0]);
|
||||
/* Commit hashes are unreadable at full length; seven characters is what git
|
||||
* itself shows. */
|
||||
static char *short_commit(const char *sha) {
|
||||
return g_strndup(sha ? sha : "", 7);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -272,22 +270,28 @@ static void update_refresh_ui(void) {
|
|||
|
||||
switch (up.state) {
|
||||
case UPDATE_IDLE:
|
||||
owned = g_strdup_printf(tr("update_current"), APP_VERSION);
|
||||
case UPDATE_UP_TO_DATE: {
|
||||
char *mine = short_commit(APP_COMMIT);
|
||||
|
||||
owned = g_strdup_printf(tr(up.state == UPDATE_IDLE
|
||||
? "update_current"
|
||||
: "update_uptodate"), mine);
|
||||
status = owned;
|
||||
g_free(mine);
|
||||
break;
|
||||
}
|
||||
case UPDATE_CHECKING:
|
||||
status = tr("update_checking");
|
||||
break;
|
||||
case UPDATE_UP_TO_DATE:
|
||||
owned = g_strdup_printf(tr("update_uptodate"), APP_VERSION);
|
||||
status = owned;
|
||||
break;
|
||||
case UPDATE_AVAILABLE:
|
||||
owned = g_strdup_printf(tr("update_available"),
|
||||
up.latest ? up.latest : "");
|
||||
case UPDATE_AVAILABLE: {
|
||||
char *newest = short_commit(up.latest);
|
||||
|
||||
owned = g_strdup_printf(tr("update_available"), newest);
|
||||
status = owned;
|
||||
action = "update_install";
|
||||
g_free(newest);
|
||||
break;
|
||||
}
|
||||
case UPDATE_DOWNLOADING:
|
||||
status = tr("update_downloading");
|
||||
action = "update_install";
|
||||
|
|
@ -581,7 +585,7 @@ static void size_probe_done(GObject *src, GAsyncResult *res, gpointer data) {
|
|||
if (up.state != UPDATE_DOWNLOADING)
|
||||
return;
|
||||
|
||||
url = g_strconcat(UPDATE_ASSET_URL, asset_name(), NULL);
|
||||
url = g_strconcat(UPDATE_BASE_URL, asset_name(), NULL);
|
||||
argv[0] = "curl";
|
||||
argv[1] = "-fsSL";
|
||||
argv[2] = "--max-time";
|
||||
|
|
@ -630,7 +634,7 @@ static void update_start_download(void) {
|
|||
update_set_state(UPDATE_DOWNLOADING);
|
||||
|
||||
/* HEAD the asset with its headers on stdout, to size the progress bar. */
|
||||
url = g_strconcat(UPDATE_ASSET_URL, asset_name(), NULL);
|
||||
url = g_strconcat(UPDATE_BASE_URL, asset_name(), NULL);
|
||||
argv[0] = "curl";
|
||||
argv[1] = "-fsSLI";
|
||||
argv[2] = "--max-time";
|
||||
|
|
@ -651,18 +655,6 @@ static void update_start_download(void) {
|
|||
/* Check */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* The effective URL ends in /releases/tag/<tag>. */
|
||||
static char *tag_from_url(const char *url) {
|
||||
const char *slash;
|
||||
|
||||
if (!url || !*url)
|
||||
return NULL;
|
||||
slash = g_strrstr(url, "/");
|
||||
if (!slash || !slash[1])
|
||||
return NULL;
|
||||
return g_strdup(slash + 1);
|
||||
}
|
||||
|
||||
/* A failed background check stays silent; only an explicit one reports. */
|
||||
static void check_giveup(void) {
|
||||
if (up.interactive)
|
||||
|
|
@ -674,7 +666,6 @@ static void check_giveup(void) {
|
|||
static void check_done(GObject *src, GAsyncResult *res, gpointer data) {
|
||||
GSubprocess *proc = G_SUBPROCESS(src);
|
||||
char *out = NULL;
|
||||
char *tag;
|
||||
gboolean ok;
|
||||
|
||||
(void)data;
|
||||
|
|
@ -682,30 +673,30 @@ static void check_done(GObject *src, GAsyncResult *res, gpointer data) {
|
|||
ok = g_subprocess_get_successful(proc);
|
||||
g_object_unref(proc);
|
||||
|
||||
tag = ok ? tag_from_url(g_strstrip(out ? out : "")) : NULL;
|
||||
g_free(out);
|
||||
if (!tag) {
|
||||
if (out)
|
||||
g_strstrip(out);
|
||||
if (!ok || !out || !*out) {
|
||||
g_free(out);
|
||||
check_giveup();
|
||||
return;
|
||||
}
|
||||
|
||||
g_free(up.latest);
|
||||
up.latest = g_strdup(strip_v(tag));
|
||||
g_free(tag);
|
||||
up.latest = out;
|
||||
|
||||
if (version_cmp(APP_VERSION, up.latest) >= 0)
|
||||
if (same_commit(APP_COMMIT, up.latest))
|
||||
update_set_state(up.interactive ? UPDATE_UP_TO_DATE : UPDATE_IDLE);
|
||||
else if (update_supported())
|
||||
update_set_state(UPDATE_AVAILABLE);
|
||||
else if (up.interactive)
|
||||
/* Newer release exists, but this copy cannot swap itself out. */
|
||||
/* A newer build exists, but this copy cannot swap itself out. */
|
||||
update_fail(tr("update_err_unsupported"));
|
||||
else
|
||||
update_set_state(UPDATE_IDLE);
|
||||
}
|
||||
|
||||
void update_check_async(gboolean interactive) {
|
||||
const char *argv[10];
|
||||
const char *argv[8];
|
||||
|
||||
if (up.state == UPDATE_CHECKING || up.state == UPDATE_DOWNLOADING ||
|
||||
up.state == UPDATE_STAGED)
|
||||
|
|
@ -713,8 +704,8 @@ void update_check_async(gboolean interactive) {
|
|||
|
||||
up.interactive = interactive;
|
||||
|
||||
/* Nothing to compare against without a baked-in version. */
|
||||
if (!is_release_build()) {
|
||||
/* Nothing to compare against without a baked-in commit. */
|
||||
if (!is_published_build()) {
|
||||
if (interactive)
|
||||
update_fail(tr("update_err_devbuild"));
|
||||
return;
|
||||
|
|
@ -722,16 +713,16 @@ void update_check_async(gboolean interactive) {
|
|||
|
||||
update_set_state(UPDATE_CHECKING);
|
||||
|
||||
/* The CDN in front of raw.githubusercontent.com caches for a few minutes,
|
||||
* so ask it not to hand back a stale commit. */
|
||||
argv[0] = "curl";
|
||||
argv[1] = "-fsSLI";
|
||||
argv[1] = "-fsSL";
|
||||
argv[2] = "--max-time";
|
||||
argv[3] = "20";
|
||||
argv[4] = "-o";
|
||||
argv[5] = NULL_DEVICE;
|
||||
argv[6] = "-w";
|
||||
argv[7] = "%{url_effective}";
|
||||
argv[8] = UPDATE_LATEST_URL;
|
||||
argv[9] = NULL;
|
||||
argv[4] = "-H";
|
||||
argv[5] = "Cache-Control: no-cache";
|
||||
argv[6] = UPDATE_VERSION_URL;
|
||||
argv[7] = NULL;
|
||||
|
||||
if (!curl_async(argv, check_done)) {
|
||||
if (interactive)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue