151cea3ecb
Adds .gitea/workflows/build-android.yml — Linux runner installs JDK 17, Python 3.11, Android SDK/NDK, symlinks server/src/ledgrab into the Chaquopy python source dir, and runs assembleDebug on master pushes / assembleRelease on v* tags. APK is uploaded as an artifact and attached to the Gitea release on tag push. Conditional signing config in build.gradle.kts reads keystore from env vars (CI secrets) and falls back to debug signing locally. Gradle wrapper (gradlew/gradlew.bat/ gradle-wrapper.jar) committed so CI can drive the build. Rebuilds pydantic-core wheels for arm64-v8a and x86_64 — both were missing libpython3.11.so in NEEDED, which would have crashed at import on real devices. build-pydantic-core.sh rewritten as a multi-ABI builder: selects targets via args, sets RUSTFLAGS=-C link-arg=-Wl,--no-as-needed -C link-arg=-lpython3.11 to force the symbol-resolution dependency, uses the per-ABI sysconfigdata + libpython staged in android/.build-cache/, prefers `py -3.11` on Windows (Git Bash's python3.11 is an MSStore stub), uses the .cmd clang wrapper on Windows (fixes os error 193), and verifies NEEDED via llvm-readelf after each build. abiFilters restored to the full triple in build.gradle.kts; multi-ABI debug APK builds cleanly (~99 MB).
156 lines
6.8 KiB
Markdown
156 lines
6.8 KiB
Markdown
# CI/CD & Release Workflow
|
|
|
|
> **Reference guide:** [Gitea Python CI/CD Guide](https://git.dolgolyov-family.by/alexei.dolgolyov/claude-code-facts/src/branch/main/gitea-python-ci-cd.md) — reusable patterns for Gitea Actions, cross-build, NSIS, Docker. When modifying workflows or build scripts, consult this guide to stay in sync with established patterns.
|
|
|
|
## Workflows
|
|
|
|
| File | Trigger | Purpose |
|
|
|------|---------|---------|
|
|
| `.gitea/workflows/test.yml` | Push/PR to master | Lint (ruff) + pytest |
|
|
| `.gitea/workflows/release.yml` | Tag `v*` | Build Windows/Linux/Docker artifacts + create Gitea release |
|
|
| `.gitea/workflows/build-android.yml` | Push master (android/server paths), tag `v*`, manual | Build Android APK; attach to release on tag |
|
|
|
|
## Release Pipeline (`release.yml`)
|
|
|
|
Four parallel jobs triggered by pushing a `v*` tag:
|
|
|
|
### 1. `create-release`
|
|
Creates the Gitea release with a description table listing all artifacts. **The description must stay in sync with actual build outputs** — if you add/remove/rename an artifact, update the body template here.
|
|
|
|
### 2. `build-windows` (cross-built from Linux)
|
|
- Runs `build/build-dist-windows.sh` on Ubuntu with NSIS + msitools
|
|
- Downloads Windows embedded Python 3.11 + pip wheels cross-platform
|
|
- Bundles tkinter from Python MSI via msiextract
|
|
- Builds frontend (`npm run build`)
|
|
- Pre-compiles Python bytecode (`compileall`)
|
|
- Produces: **`LedGrab-{tag}-win-x64.zip`** (portable) and **`LedGrab-{tag}-setup.exe`** (NSIS installer)
|
|
|
|
### 3. `build-linux`
|
|
- Runs `build/build-dist.sh` on Ubuntu
|
|
- Creates a venv, installs deps, builds frontend
|
|
- Produces: **`LedGrab-{tag}-linux-x64.tar.gz`**
|
|
|
|
### 4. `build-docker`
|
|
- Plain `docker build` + `docker push` (no Buildx — TrueNAS runners lack nested networking)
|
|
- Registry: `{gitea_host}/{repo}:{tag}`
|
|
- Tags: `v0.x.x`, `0.x.x`, and `latest` (stable only, not alpha/beta/rc)
|
|
|
|
## Build Scripts
|
|
|
|
| Script | Platform | Output |
|
|
|--------|----------|--------|
|
|
| `build/build-dist-windows.sh` | Linux → Windows cross-build | ZIP + NSIS installer |
|
|
| `build/build-dist.sh` | Linux native | tarball |
|
|
| `server/Dockerfile` | Docker | Container image |
|
|
|
|
## Release Versioning
|
|
|
|
- Tags: `v{major}.{minor}.{patch}` for stable, `v{major}.{minor}.{patch}-alpha.{n}` for pre-release
|
|
- Pre-release tags set `prerelease: true` on the Gitea release
|
|
- Docker `latest` tag only applied to stable releases
|
|
- Version in `server/pyproject.toml` should match the tag (without `v` prefix)
|
|
|
|
## CI Runners
|
|
|
|
- Two TrueNAS Gitea runners with `ubuntu` tags
|
|
- No Windows runner available — Windows builds are cross-compiled from Linux
|
|
- Docker Buildx not available (networking limitations) — use plain `docker build`
|
|
|
|
## Test Pipeline (`test.yml`)
|
|
|
|
- Installs `opencv-python-headless` and `libportaudio2` for CI compatibility
|
|
- Display-dependent tests are skipped via `@requires_display` marker
|
|
- Uses `python` not `python3` (Git Bash on Windows resolves `python3` to MS Store stub)
|
|
|
|
## Version Detection Pattern
|
|
|
|
Build scripts use a fallback chain: CLI argument → exact git tag → CI env var (`GITEA_REF_NAME` / `GITHUB_REF_NAME`) → hardcoded in source. Always strip leading `v` for clean version strings.
|
|
|
|
## NSIS Installer Best Practices
|
|
|
|
- **User-scoped install** (`$LOCALAPPDATA`, `RequestExecutionLevel user`) — no admin required
|
|
- **Launch after install**: Use `MUI_FINISHPAGE_RUN_FUNCTION` (not `MUI_FINISHPAGE_RUN_PARAMETERS` — NSIS `Exec` chokes on quoting). Still requires `MUI_FINISHPAGE_RUN ""` defined for checkbox visibility
|
|
- **Detect running instance**: `.onInit` checks file lock on `python.exe`, offers to kill process before install
|
|
- **Uninstall preserves user data**: Remove `python/`, `app/`, `logs/` but NOT `data/`
|
|
- **CI build**: `sudo apt-get install -y nsis msitools zip` then `makensis -DVERSION="${VERSION}" build/installer.nsi`
|
|
|
|
## Hidden Launcher (VBS)
|
|
|
|
All shortcuts and the installer finish page use `scripts/start-hidden.vbs` instead of `.bat` to avoid console window flash. The VBS launcher must include an embedded Python fallback — installed distributions don't have Python on PATH, dev environment uses system Python.
|
|
|
|
## Gitea vs GitHub Actions Differences
|
|
|
|
| Feature | GitHub Actions | Gitea Actions |
|
|
| ------- | -------------- | ------------- |
|
|
| Context prefix | `github.*` | `gitea.*` |
|
|
| Ref name | `${{ github.ref_name }}` | `${{ gitea.ref_name }}` |
|
|
| Server URL | `${{ github.server_url }}` | `${{ gitea.server_url }}` |
|
|
| Output vars | `$GITHUB_OUTPUT` | `$GITHUB_OUTPUT` (same) |
|
|
| Secrets | `${{ secrets.NAME }}` | `${{ secrets.NAME }}` (same) |
|
|
| Docker Buildx | Available | May not work (runner networking) |
|
|
|
|
## Common Tasks
|
|
|
|
### Creating a release
|
|
```bash
|
|
git tag v0.2.0
|
|
git push origin v0.2.0
|
|
```
|
|
|
|
### Creating a pre-release
|
|
```bash
|
|
git tag v0.2.0-alpha.1
|
|
git push origin v0.2.0-alpha.1
|
|
```
|
|
|
|
### Adding a new build artifact
|
|
1. Update the build script to produce the new file
|
|
2. Add upload step in the relevant `build-*` job
|
|
3. **Update the release description** in `create-release` job body template
|
|
4. Test with a pre-release tag first
|
|
|
|
### Re-triggering a failed release workflow
|
|
|
|
```bash
|
|
# Option A: Delete and re-push the same tag
|
|
git push origin :refs/tags/v0.1.0-alpha.2
|
|
# Delete the release in Gitea UI or via API
|
|
git tag -f v0.1.0-alpha.2
|
|
git push origin v0.1.0-alpha.2
|
|
|
|
# Option B: Just bump the version (simpler)
|
|
git tag v0.1.0-alpha.3
|
|
git push origin v0.1.0-alpha.3
|
|
```
|
|
|
|
The `create-release` job has fallback logic — if the release already exists for a tag, it fetches and reuses the existing release ID.
|
|
|
|
## Local Build Testing (Windows)
|
|
|
|
### Prerequisites
|
|
|
|
- NSIS: `& "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe" install NSIS.NSIS`
|
|
- Installs to `C:\Program Files (x86)\NSIS\makensis.exe`
|
|
|
|
### Build steps
|
|
|
|
```bash
|
|
npm ci && npm run build # frontend
|
|
bash build/build-dist-windows.sh v1.0.0 # Windows dist
|
|
"/c/Program Files (x86)/NSIS/makensis.exe" -DVERSION="1.0.0" build/installer.nsi # installer
|
|
```
|
|
|
|
### Iterating on installer only
|
|
If only `installer.nsi` changed (not app code), skip the full rebuild — just re-run `makensis`. If app code changed, re-run `build/build-dist-windows.sh` first since `dist/` is a snapshot.
|
|
|
|
### Common issues
|
|
|
|
| Issue | Fix |
|
|
| ----- | --- |
|
|
| `zip: command not found` | Git Bash doesn't include `zip` — harmless for installer builds |
|
|
| `Exec expects 1 parameters, got 2` | Use `MUI_FINISHPAGE_RUN_FUNCTION` instead of `MUI_FINISHPAGE_RUN_PARAMETERS` |
|
|
| `Error opening file for writing: python\_asyncio.pyd` | Server is running — stop it before installing |
|
|
| App doesn't start after install | VBS must use embedded Python fallback, not bare `python` |
|
|
| `winget` not recognized | Use full path: `$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe` |
|
|
| `dist/` has stale files | Re-run full build script — `dist/` doesn't auto-update |
|