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:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
branches:
|
||||||
- "v*"
|
- main
|
||||||
|
# Nothing to rebuild when only documentation changed.
|
||||||
|
paths-ignore:
|
||||||
|
- "README.md"
|
||||||
|
- "LICENSE"
|
||||||
|
- ".gitignore"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
@ -26,7 +35,7 @@ jobs:
|
||||||
|
|
||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ github.ref_name }}
|
COMMIT: ${{ github.sha }}
|
||||||
run: |
|
run: |
|
||||||
chmod +x scripts/bundle-linux.sh
|
chmod +x scripts/bundle-linux.sh
|
||||||
./scripts/bundle-linux.sh
|
./scripts/bundle-linux.sh
|
||||||
|
|
@ -60,7 +69,7 @@ jobs:
|
||||||
|
|
||||||
- name: Build and bundle
|
- name: Build and bundle
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ github.ref_name }}
|
COMMIT: ${{ github.sha }}
|
||||||
run: |
|
run: |
|
||||||
make bundle
|
make bundle
|
||||||
cd dist
|
cd dist
|
||||||
|
|
@ -87,7 +96,7 @@ jobs:
|
||||||
|
|
||||||
- name: Build .app zip
|
- name: Build .app zip
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ github.ref_name }}
|
COMMIT: ${{ github.sha }}
|
||||||
run: |
|
run: |
|
||||||
chmod +x scripts/bundle-macos.sh
|
chmod +x scripts/bundle-macos.sh
|
||||||
./scripts/bundle-macos.sh
|
./scripts/bundle-macos.sh
|
||||||
|
|
@ -99,7 +108,7 @@ jobs:
|
||||||
path: dist/maturita-macos-*.zip
|
path: dist/maturita-macos-*.zip
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
name: Publish GitHub Release
|
name: Publish to builds branch
|
||||||
needs: [linux, windows, macos]
|
needs: [linux, windows, macos]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -108,34 +117,34 @@ jobs:
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: release-assets
|
path: staged
|
||||||
merge-multiple: true
|
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
|
- name: Collect installers
|
||||||
run: |
|
run: |
|
||||||
cp installers/maturita-installer-linux.sh release-assets/
|
cp installers/maturita-installer-linux.sh staged/
|
||||||
cp installers/maturita-installer-windows.cmd release-assets/
|
cp installers/maturita-installer-windows.cmd staged/
|
||||||
chmod +x installers/maturita-installer-macos.command
|
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)
|
maturita-installer-macos.command)
|
||||||
|
# The updater downloads this to see whether it is behind.
|
||||||
|
echo "${{ github.sha }}" > staged/VERSION
|
||||||
|
|
||||||
- name: List assets
|
# The branch is rewritten from scratch every time rather than added to,
|
||||||
run: find release-assets -type f -print
|
# so the repository keeps one copy of the packages instead of a new
|
||||||
|
# ~70 MB set per commit.
|
||||||
- name: Create release
|
- name: Push to builds branch
|
||||||
uses: softprops/action-gh-release@v2
|
run: |
|
||||||
with:
|
git config user.name "github-actions[bot]"
|
||||||
files: |
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
release-assets/maturita-linux-*.AppImage
|
rm -rf .git/index.lock
|
||||||
release-assets/maturita-windows-x64.zip
|
git checkout --orphan builds
|
||||||
release-assets/maturita-macos-*.zip
|
git rm -rf --cached . >/dev/null
|
||||||
release-assets/maturita-installer-macos.zip
|
find . -maxdepth 1 ! -name . ! -name .git ! -name staged \
|
||||||
release-assets/maturita-installer-windows.cmd
|
-exec rm -rf {} +
|
||||||
release-assets/maturita-installer-linux.sh
|
mv staged/* .
|
||||||
generate_release_notes: true
|
rmdir staged
|
||||||
fail_on_unmatched_files: true
|
ls -la
|
||||||
env:
|
git add -A
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
git commit -q -m "Build from ${{ github.sha }}"
|
||||||
|
git push -f origin builds
|
||||||
|
|
@ -52,9 +52,9 @@ else()
|
||||||
add_executable(maturita ${MATURITA_SOURCES})
|
add_executable(maturita ${MATURITA_SOURCES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Mirrors VERSION in the Makefile: bakes the release tag in for the updater.
|
# Mirrors COMMIT in the Makefile: bakes the source commit in for the updater.
|
||||||
if(DEFINED APP_VERSION AND NOT APP_VERSION STREQUAL "")
|
if(DEFINED APP_COMMIT AND NOT APP_COMMIT STREQUAL "")
|
||||||
target_compile_definitions(maturita PRIVATE APP_VERSION="${APP_VERSION}")
|
target_compile_definitions(maturita PRIVATE APP_COMMIT="${APP_COMMIT}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(MSVC)
|
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)
|
CFLAGS = -Wall -Wextra -Wno-deprecated-declarations $(GTK_CFLAGS)
|
||||||
LIBS = $(GTK_LIBS) -lm
|
LIBS = $(GTK_LIBS) -lm
|
||||||
|
|
||||||
# Release builds pass the git tag (the CI workflow exports it), which bakes the
|
# Published builds pass the commit they were made from (the CI workflow
|
||||||
# version into the binary for the updater to compare against. Without it the
|
# exports it), which the updater compares against the build in the repository.
|
||||||
# build keeps the "dev" default from maturita.h and never offers an update.
|
# Without it the build keeps the "dev" default and never offers an update.
|
||||||
VERSION ?=
|
COMMIT ?=
|
||||||
ifneq ($(VERSION),)
|
ifneq ($(COMMIT),)
|
||||||
CFLAGS += -DAPP_VERSION='"$(VERSION)"'
|
CFLAGS += -DAPP_COMMIT='"$(COMMIT)"'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Windows (MSYS2 / MinGW): .exe suffix, no console window, embed .ico.
|
# 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)
|
### Downloads (prebuilt)
|
||||||
|
|
||||||
Precompiled, self-contained builds are published on the
|
There are no releases and no version numbers. CI rebuilds all three platforms
|
||||||
**GitHub Releases** page of this project (enable Actions on a GitHub remote)
|
whenever `main` changes and commits the packages to the
|
||||||
when a version tag (`v*`) is pushed. No compiler or GTK development packages
|
[`builds`](../../tree/builds) branch, so what you download is always the
|
||||||
are required:
|
current state of the repository. No compiler or GTK development packages are
|
||||||
|
required.
|
||||||
|
|
||||||
| Platform | Asset | How to run |
|
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
|
||||||
| Linux (x86_64) | `maturita-linux-x86_64.AppImage` | `chmod +x` the file, then double-click or run it |
|
of date and you only ever need this one download:
|
||||||
| macOS (Apple Silicon) | `maturita-macos-arm64.zip` | Unzip and open `Maturita.app` |
|
|
||||||
| Windows (x64) | `maturita-windows-x64.zip` | Unzip and run `maturita.exe` |
|
|
||||||
|
|
||||||
Progress and settings are saved next to the AppImage / `.app` / unzipped
|
| Platform | File | How to run |
|
||||||
folder in a `progress/` directory.
|
| -------- | ---- | ---------- |
|
||||||
|
| 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
|
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
|
||||||
Each release also ships a small installer that contains no application of its
|
offers the update in a banner. There is a manual check under
|
||||||
own: it downloads the newest build when you run it, so the file never goes out
|
**Settings → Updates**.
|
||||||
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` |
|
|
||||||
|
|
||||||
The builds are not code-signed, so the first launch needs one extra step:
|
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
|
on macOS right-click the installer and pick **Open**, and on Windows choose
|
||||||
**More info → Run anyway** if SmartScreen appears.
|
**More info → Run anyway** if SmartScreen appears.
|
||||||
|
|
||||||
To cut a release yourself (needs a GitHub remote with Actions enabled):
|
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
|
||||||
```bash
|
`maturita-windows-x64.zip`. Progress and settings are saved next to the
|
||||||
git tag v1.0.0
|
AppImage / `.app` / unzipped folder in a `progress/` directory.
|
||||||
git push origin v1.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
Local packaging (same scripts the CI uses):
|
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é)
|
### Stažení (předkompilované)
|
||||||
|
|
||||||
Hotové self-contained buildy vycházejí na stránce
|
Žádné releasy ani čísla verzí nejsou. CI přestaví všechny tři platformy
|
||||||
**GitHub Releases** tohoto projektu (Actions na GitHub remote)
|
pokaždé, když se změní `main`, a balíčky commitne do větve
|
||||||
při pushi tagu `v*`. Kompilátor ani vývojové balíčky GTK nejsou potřeba:
|
[`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í |
|
| Platforma | Soubor | Spuštění |
|
||||||
| --------- | ------ | -------- |
|
| --------- | ------ | -------- |
|
||||||
| Linux (x86_64) | `maturita-linux-x86_64.AppImage` | `chmod +x` a spustit / dvojklik |
|
| macOS (Apple Silicon) | [`maturita-installer-macos.zip`](../../raw/builds/maturita-installer-macos.zip) | Rozbalit a dvojklik na soubor `.command` |
|
||||||
| macOS (Apple Silicon) | `maturita-macos-arm64.zip` | Rozbalit a otevřít `Maturita.app` |
|
| Windows (x64) | [`maturita-installer-windows.cmd`](../../raw/builds/maturita-installer-windows.cmd) | Dvojklik na soubor |
|
||||||
| Windows (x64) | `maturita-windows-x64.zip` | Rozbalit a spustit `maturita.exe` |
|
| 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
|
Nainstalovaná aplikace se pak udržuje aktuální sama: chvíli po startu porovná
|
||||||
do adresáře `progress/`.
|
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
|
||||||
#### 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
|
|
||||||
v **Nastavení → Aktualizace**.
|
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
|
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
|
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**.
|
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
|
Vydání nové verze je prostý push na `main` — build i publikování do větve
|
||||||
git tag v1.0.0
|
`builds` obstará CI.
|
||||||
git push origin v1.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
Lokální balíček (stejné skripty jako 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"},
|
{"theme", "TÉMA", "THEME"},
|
||||||
{"language", "JAZYK", "LANGUAGE"},
|
{"language", "JAZYK", "LANGUAGE"},
|
||||||
{"updates", "AKTUALIZACE", "UPDATES"},
|
{"updates", "AKTUALIZACE", "UPDATES"},
|
||||||
{"update_current", "Verze %s", "Version %s"},
|
{"update_current", "Build %s", "Build %s"},
|
||||||
{"update_checking", "Hledám aktualizace…", "Checking for updates…"},
|
{"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)."},
|
"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_downloading", "Stahuji aktualizaci…", "Downloading the update…"},
|
||||||
{"update_staged", "Aktualizace je připravená — restartujte aplikaci.",
|
{"update_staged", "Aktualizace je připravená — restartujte aplikaci.",
|
||||||
"The update is ready — restart the app to finish."},
|
"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_download", "Stahování se nepovedlo.", "The download failed."},
|
||||||
{"update_err_curl", "V systému chybí curl, aktualizace nejsou dostupné.",
|
{"update_err_curl", "V systému chybí curl, aktualizace nejsou dostupné.",
|
||||||
"curl is missing, so updates are unavailable."},
|
"curl is missing, so updates are unavailable."},
|
||||||
{"update_err_unsupported", "Pro tuto platformu se aktualizace nevydává.",
|
{"update_err_unsupported", "Pro tuto platformu se build nepublikuje.",
|
||||||
"No update is published for this platform."},
|
"No build is published for this platform."},
|
||||||
{"update_err_readonly",
|
{"update_err_readonly",
|
||||||
"Do umístění aplikace nelze zapisovat. Přesuňte ji třeba do Aplikací.",
|
"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 "
|
"The app's location is not writable. Move it somewhere else, for "
|
||||||
"example to Applications."},
|
"example to Applications."},
|
||||||
{"update_err_stage", "Aktualizaci nešlo připravit.",
|
{"update_err_stage", "Aktualizaci nešlo připravit.",
|
||||||
"Could not prepare the update."},
|
"Could not prepare the update."},
|
||||||
{"update_err_devbuild", "Tento build nemá číslo verze (dev).",
|
{"update_err_devbuild", "Tento build nepochází z repozitáře (dev).",
|
||||||
"This build carries no version number (dev)."},
|
"This build did not come from the repository (dev)."},
|
||||||
{"welcome_body",
|
{"welcome_body",
|
||||||
"maturita.C je vzdělávací program pro studenty středních škol a "
|
"maturita.C je vzdělávací program pro studenty středních škol a "
|
||||||
"gymnázií na přípravu k maturitě.\n\n"
|
"gymnázií na přípravu k maturitě.\n\n"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# maturita.c installer for Linux.
|
# maturita.c installer for Linux.
|
||||||
#
|
#
|
||||||
# Carries no application of its own: it downloads whatever the newest release
|
# Carries no application of its own: it downloads the current build straight
|
||||||
# is at the moment you run it, so this file never goes stale. GitHub resolves
|
# from the repository's `builds` branch, which CI rewrites whenever main
|
||||||
# the /releases/latest/download/ path to the current tag on every request.
|
# changes. This file therefore never goes stale.
|
||||||
#
|
#
|
||||||
# Run it with: sh maturita-installer-linux.sh
|
# Run it with: sh maturita-installer-linux.sh
|
||||||
# The app updates itself from then on.
|
# The app updates itself from then on.
|
||||||
|
|
@ -11,8 +11,9 @@
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
REPO="pepaskopec-blip/maturita.c"
|
REPO="pepaskopec-blip/maturita.c"
|
||||||
|
BRANCH="builds"
|
||||||
ASSET="maturita-linux-x86_64.AppImage"
|
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
|
# ~/Applications is where desktop environments look for AppImages, and the app
|
||||||
# keeps its progress/ directory next to its own file.
|
# keeps its progress/ directory next to its own file.
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# maturita.c installer for macOS.
|
# maturita.c installer for macOS.
|
||||||
#
|
#
|
||||||
# Carries no application of its own: it downloads whatever the newest release
|
# Carries no application of its own: it downloads the current build straight
|
||||||
# is at the moment you run it, so this file never goes stale. GitHub resolves
|
# from the repository's `builds` branch, which CI rewrites whenever main
|
||||||
# the /releases/latest/download/ path to the current tag on every request.
|
# changes. This file therefore never goes stale.
|
||||||
#
|
#
|
||||||
# Double-click the file to run it. The app updates itself from then on.
|
# Double-click the file to run it. The app updates itself from then on.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
REPO="pepaskopec-blip/maturita.c"
|
REPO="pepaskopec-blip/maturita.c"
|
||||||
|
BRANCH="builds"
|
||||||
ASSET="maturita-macos-arm64.zip"
|
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' "$*"; }
|
say() { printf '%s\n' "$*"; }
|
||||||
die() { printf '\nError: %s\n' "$*" >&2; printf 'Press Return to close. '; read -r _; exit 1; }
|
die() { printf '\nError: %s\n' "$*" >&2; printf 'Press Return to close. '; read -r _; exit 1; }
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
@echo off
|
@echo off
|
||||||
rem maturita.c installer for Windows.
|
rem maturita.c installer for Windows.
|
||||||
rem
|
rem
|
||||||
rem Carries no application of its own: it downloads whatever the newest release
|
rem Carries no application of its own: it downloads the current build straight
|
||||||
rem is at the moment you run it, so this file never goes stale. GitHub resolves
|
rem from the repository's `builds` branch, which CI rewrites whenever main
|
||||||
rem the /releases/latest/download/ path to the current tag on every request.
|
rem changes. This file therefore never goes stale.
|
||||||
rem
|
rem
|
||||||
rem Double-click the file to run it. The app updates itself from then on.
|
rem Double-click the file to run it. The app updates itself from then on.
|
||||||
|
|
||||||
setlocal
|
setlocal
|
||||||
set "REPO=pepaskopec-blip/maturita.c"
|
set "REPO=pepaskopec-blip/maturita.c"
|
||||||
|
set "BRANCH=builds"
|
||||||
set "ASSET=maturita-windows-x64.zip"
|
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"
|
set "DEST=%LOCALAPPDATA%\Programs\Maturita"
|
||||||
|
|
||||||
echo Installing maturita.C
|
echo Installing maturita.C
|
||||||
|
|
@ -51,7 +52,7 @@ exit /b 0
|
||||||
echo.
|
echo.
|
||||||
echo Error: this installer needs curl and tar, which come with Windows 10
|
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 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
|
pause
|
||||||
exit /b 1
|
exit /b 1
|
||||||
|
|
||||||
|
|
|
||||||
13
maturita.h
13
maturita.h
|
|
@ -21,13 +21,16 @@
|
||||||
#define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */
|
#define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */
|
||||||
#define CSS_FILE "style.css"
|
#define CSS_FILE "style.css"
|
||||||
#define APP_ID "org.maturita.Maturita"
|
#define APP_ID "org.maturita.Maturita"
|
||||||
/* Release builds bake the git tag in (see VERSION in the Makefile), so the
|
/* Published builds bake in the commit they were made from (see COMMIT in the
|
||||||
* updater can tell what it is running. Plain builds stay at "dev" and never
|
* Makefile), which is what the updater compares against the build published
|
||||||
* report an update as available. */
|
* in the repository. Local builds stay at "dev" and never offer an update. */
|
||||||
#ifndef APP_VERSION
|
#ifndef APP_COMMIT
|
||||||
#define APP_VERSION "dev"
|
#define APP_COMMIT "dev"
|
||||||
#endif
|
#endif
|
||||||
#define UPDATE_REPO "pepaskopec-blip/maturita.c"
|
#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_NAME "maturita"
|
||||||
#define ICON_FILE "assets/app-icon.png"
|
#define ICON_FILE "assets/app-icon.png"
|
||||||
#define ICON_THEME_DIR "assets/icons" /* cwd when run from source tree */
|
#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
|
* Every call is asynchronous, so a slow or unreachable network never blocks
|
||||||
* the interface.
|
* the interface.
|
||||||
*
|
*
|
||||||
* The newest version is read from the redirect GitHub serves for
|
* Packages are not published as releases. They live on the `builds` branch of
|
||||||
* /releases/latest, which points at /releases/tag/<tag>. That avoids the REST
|
* the repository, which CI rewrites on every change to main, so an update is
|
||||||
* API together with its unauthenticated rate limit, and needs no JSON parser.
|
* 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
|
* 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
|
* 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
|
* script detached and quit. Staging beside the install keeps the final move on
|
||||||
* one filesystem and proves up front that the location is writable. */
|
* one filesystem and proves up front that the location is writable. */
|
||||||
|
|
||||||
#define UPDATE_LATEST_URL "https://github.com/" UPDATE_REPO "/releases/latest"
|
#define UPDATE_BASE_URL "https://raw.githubusercontent.com/" UPDATE_REPO \
|
||||||
#define UPDATE_ASSET_URL "https://github.com/" UPDATE_REPO \
|
"/" UPDATE_BRANCH "/"
|
||||||
"/releases/latest/download/"
|
#define UPDATE_VERSION_URL UPDATE_BASE_URL "VERSION"
|
||||||
#define UPDATE_STAGING_DIR ".maturita-update"
|
#define UPDATE_STAGING_DIR ".maturita-update"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
@ -64,7 +68,7 @@ typedef enum {
|
||||||
static struct {
|
static struct {
|
||||||
UpdateState state;
|
UpdateState state;
|
||||||
gboolean interactive; /* user asked, so also report "up to date" */
|
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 *staging; /* scratch directory beside the install */
|
||||||
char *archive; /* asset downloaded into the staging directory */
|
char *archive; /* asset downloaded into the staging directory */
|
||||||
char *source; /* unpacked version the swap script moves in */
|
char *source; /* unpacked version the swap script moves in */
|
||||||
|
|
@ -81,39 +85,33 @@ static struct {
|
||||||
} up;
|
} up;
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Versions */
|
/* Build identity */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
static const char *strip_v(const char *v) {
|
/* Commits are not ordered, so there is nothing to compare: the branch only
|
||||||
return (v && (v[0] == 'v' || v[0] == 'V')) ? v + 1 : v;
|
* 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
|
/* Only builds published by CI carry a commit; see APP_COMMIT in maturita.h. */
|
||||||
* components count as zero, which makes 1.1 and 1.1.0 equal. */
|
static gboolean is_published_build(void) {
|
||||||
static int version_cmp(const char *a, const char *b) {
|
return g_ascii_isxdigit(APP_COMMIT[0]) && strlen(APP_COMMIT) >= 7;
|
||||||
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 tagged builds know their version; see APP_VERSION in maturita.h. */
|
/* Commit hashes are unreadable at full length; seven characters is what git
|
||||||
static gboolean is_release_build(void) {
|
* itself shows. */
|
||||||
return g_ascii_isdigit(strip_v(APP_VERSION)[0]);
|
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) {
|
switch (up.state) {
|
||||||
case UPDATE_IDLE:
|
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;
|
status = owned;
|
||||||
|
g_free(mine);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case UPDATE_CHECKING:
|
case UPDATE_CHECKING:
|
||||||
status = tr("update_checking");
|
status = tr("update_checking");
|
||||||
break;
|
break;
|
||||||
case UPDATE_UP_TO_DATE:
|
case UPDATE_AVAILABLE: {
|
||||||
owned = g_strdup_printf(tr("update_uptodate"), APP_VERSION);
|
char *newest = short_commit(up.latest);
|
||||||
status = owned;
|
|
||||||
break;
|
owned = g_strdup_printf(tr("update_available"), newest);
|
||||||
case UPDATE_AVAILABLE:
|
|
||||||
owned = g_strdup_printf(tr("update_available"),
|
|
||||||
up.latest ? up.latest : "");
|
|
||||||
status = owned;
|
status = owned;
|
||||||
action = "update_install";
|
action = "update_install";
|
||||||
|
g_free(newest);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case UPDATE_DOWNLOADING:
|
case UPDATE_DOWNLOADING:
|
||||||
status = tr("update_downloading");
|
status = tr("update_downloading");
|
||||||
action = "update_install";
|
action = "update_install";
|
||||||
|
|
@ -581,7 +585,7 @@ static void size_probe_done(GObject *src, GAsyncResult *res, gpointer data) {
|
||||||
if (up.state != UPDATE_DOWNLOADING)
|
if (up.state != UPDATE_DOWNLOADING)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
url = g_strconcat(UPDATE_ASSET_URL, asset_name(), NULL);
|
url = g_strconcat(UPDATE_BASE_URL, asset_name(), NULL);
|
||||||
argv[0] = "curl";
|
argv[0] = "curl";
|
||||||
argv[1] = "-fsSL";
|
argv[1] = "-fsSL";
|
||||||
argv[2] = "--max-time";
|
argv[2] = "--max-time";
|
||||||
|
|
@ -630,7 +634,7 @@ static void update_start_download(void) {
|
||||||
update_set_state(UPDATE_DOWNLOADING);
|
update_set_state(UPDATE_DOWNLOADING);
|
||||||
|
|
||||||
/* HEAD the asset with its headers on stdout, to size the progress bar. */
|
/* 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[0] = "curl";
|
||||||
argv[1] = "-fsSLI";
|
argv[1] = "-fsSLI";
|
||||||
argv[2] = "--max-time";
|
argv[2] = "--max-time";
|
||||||
|
|
@ -651,18 +655,6 @@ static void update_start_download(void) {
|
||||||
/* Check */
|
/* 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. */
|
/* A failed background check stays silent; only an explicit one reports. */
|
||||||
static void check_giveup(void) {
|
static void check_giveup(void) {
|
||||||
if (up.interactive)
|
if (up.interactive)
|
||||||
|
|
@ -674,7 +666,6 @@ static void check_giveup(void) {
|
||||||
static void check_done(GObject *src, GAsyncResult *res, gpointer data) {
|
static void check_done(GObject *src, GAsyncResult *res, gpointer data) {
|
||||||
GSubprocess *proc = G_SUBPROCESS(src);
|
GSubprocess *proc = G_SUBPROCESS(src);
|
||||||
char *out = NULL;
|
char *out = NULL;
|
||||||
char *tag;
|
|
||||||
gboolean ok;
|
gboolean ok;
|
||||||
|
|
||||||
(void)data;
|
(void)data;
|
||||||
|
|
@ -682,30 +673,30 @@ static void check_done(GObject *src, GAsyncResult *res, gpointer data) {
|
||||||
ok = g_subprocess_get_successful(proc);
|
ok = g_subprocess_get_successful(proc);
|
||||||
g_object_unref(proc);
|
g_object_unref(proc);
|
||||||
|
|
||||||
tag = ok ? tag_from_url(g_strstrip(out ? out : "")) : NULL;
|
if (out)
|
||||||
g_free(out);
|
g_strstrip(out);
|
||||||
if (!tag) {
|
if (!ok || !out || !*out) {
|
||||||
|
g_free(out);
|
||||||
check_giveup();
|
check_giveup();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(up.latest);
|
g_free(up.latest);
|
||||||
up.latest = g_strdup(strip_v(tag));
|
up.latest = out;
|
||||||
g_free(tag);
|
|
||||||
|
|
||||||
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);
|
update_set_state(up.interactive ? UPDATE_UP_TO_DATE : UPDATE_IDLE);
|
||||||
else if (update_supported())
|
else if (update_supported())
|
||||||
update_set_state(UPDATE_AVAILABLE);
|
update_set_state(UPDATE_AVAILABLE);
|
||||||
else if (up.interactive)
|
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"));
|
update_fail(tr("update_err_unsupported"));
|
||||||
else
|
else
|
||||||
update_set_state(UPDATE_IDLE);
|
update_set_state(UPDATE_IDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_check_async(gboolean interactive) {
|
void update_check_async(gboolean interactive) {
|
||||||
const char *argv[10];
|
const char *argv[8];
|
||||||
|
|
||||||
if (up.state == UPDATE_CHECKING || up.state == UPDATE_DOWNLOADING ||
|
if (up.state == UPDATE_CHECKING || up.state == UPDATE_DOWNLOADING ||
|
||||||
up.state == UPDATE_STAGED)
|
up.state == UPDATE_STAGED)
|
||||||
|
|
@ -713,8 +704,8 @@ void update_check_async(gboolean interactive) {
|
||||||
|
|
||||||
up.interactive = interactive;
|
up.interactive = interactive;
|
||||||
|
|
||||||
/* Nothing to compare against without a baked-in version. */
|
/* Nothing to compare against without a baked-in commit. */
|
||||||
if (!is_release_build()) {
|
if (!is_published_build()) {
|
||||||
if (interactive)
|
if (interactive)
|
||||||
update_fail(tr("update_err_devbuild"));
|
update_fail(tr("update_err_devbuild"));
|
||||||
return;
|
return;
|
||||||
|
|
@ -722,16 +713,16 @@ void update_check_async(gboolean interactive) {
|
||||||
|
|
||||||
update_set_state(UPDATE_CHECKING);
|
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[0] = "curl";
|
||||||
argv[1] = "-fsSLI";
|
argv[1] = "-fsSL";
|
||||||
argv[2] = "--max-time";
|
argv[2] = "--max-time";
|
||||||
argv[3] = "20";
|
argv[3] = "20";
|
||||||
argv[4] = "-o";
|
argv[4] = "-H";
|
||||||
argv[5] = NULL_DEVICE;
|
argv[5] = "Cache-Control: no-cache";
|
||||||
argv[6] = "-w";
|
argv[6] = UPDATE_VERSION_URL;
|
||||||
argv[7] = "%{url_effective}";
|
argv[7] = NULL;
|
||||||
argv[8] = UPDATE_LATEST_URL;
|
|
||||||
argv[9] = NULL;
|
|
||||||
|
|
||||||
if (!curl_async(argv, check_done)) {
|
if (!curl_async(argv, check_done)) {
|
||||||
if (interactive)
|
if (interactive)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue