docs: add maintenance guide; fix AppImage version selection; bump to 1.1.0
- Add MAINTENANCE.md (dependency updates, EOL watch, build/smoke-test workflows, orientation-fix invariants) - Remove AGENTS.md; fold its unique context into MAINTENANCE.md - Add .venv/.posit/appimage to .dockerignore to shrink build context - Fix build-appimage.sh to select the wheel matching pyproject.toml version instead of alphabetically-first (bundled stale 1.0.0 code) - Bump version 1.0.0 -> 1.1.0 in pyproject.toml, uv.lock, AppImage recipe
This commit is contained in:
parent
5f71b61596
commit
21f761406d
7 changed files with 289 additions and 158 deletions
|
|
@ -11,3 +11,6 @@ out
|
||||||
.env
|
.env
|
||||||
.scanslicer
|
.scanslicer
|
||||||
.uv-cache
|
.uv-cache
|
||||||
|
.venv
|
||||||
|
.posit
|
||||||
|
appimage
|
||||||
|
|
|
||||||
152
AGENTS.md
152
AGENTS.md
|
|
@ -1,152 +0,0 @@
|
||||||
# ScanSlicer (fork) — Project Guide
|
|
||||||
|
|
||||||
Purpose of this file: give any LLM working in this repo enough context to be
|
|
||||||
effective without re-reading everything. Keep it current as the code changes.
|
|
||||||
|
|
||||||
## What this project is
|
|
||||||
|
|
||||||
ScanSlicer is a desktop Qt application (Python/PySide6) that detects multiple
|
|
||||||
photos in a flatbed scan image, lets the user correct each box manually, then
|
|
||||||
exports perspective-corrected crops. Inspired by
|
|
||||||
[PhotoSlicer](https://github.com/francescovannini/photoslicer) (Francesco
|
|
||||||
Vannini). Runs on Linux over Wayland or X11.
|
|
||||||
|
|
||||||
This repo is a **local fork** of
|
|
||||||
[macrodev8/ScanSlicer](https://github.com/macrodev8/ScanSlicer) (upstream owner
|
|
||||||
inactive). License: GNU GPL v3 (must be preserved). Attribution: keep upstream
|
|
||||||
credit and the "inspired by PhotoSlicer" note; `pyproject.toml` lists both the
|
|
||||||
fork maintainer and the upstream author, and `README.md` states the fork
|
|
||||||
relationship and the changes made.
|
|
||||||
|
|
||||||
## Goals of this fork
|
|
||||||
|
|
||||||
1. Fix the mirrored-export orientation bug and add an explicit left/right
|
|
||||||
orientation workflow. Implemented, tested in the GUI, and committed — see
|
|
||||||
"The orientation fix" below. This was the primary patch.
|
|
||||||
2. Keep the app usable on the maintainer's own photo scans; afterwards only
|
|
||||||
minimal maintenance is expected.
|
|
||||||
3. Push to the maintainer's Forgejo instance at `code.dgerdesmann.de` (no
|
|
||||||
GitHub fork). Keep licensing, attribution, and repository description proper.
|
|
||||||
|
|
||||||
The maintainer is primarily an R user and new to Python/Git. Changes should be
|
|
||||||
small, self-checked, clean, and secure; do not assume the maintainer can vet
|
|
||||||
Python code.
|
|
||||||
|
|
||||||
## The orientation fix (implemented)
|
|
||||||
|
|
||||||
The bug: exported slices could come out mirrored (left/right swapped). Root
|
|
||||||
cause is `PhotoSlice.normalize_top_left()` (in `slices.py`), which re-derives
|
|
||||||
"top" from geometry — it picks a top-left corner and resolves winding so edge
|
|
||||||
0→1 is the "most horizontal" top edge. That inference is ambiguous, and because
|
|
||||||
`squarize()` and `reset_rotation()` both re-run `normalize_top_left()` at the
|
|
||||||
end, any manual correction was silently overwritten.
|
|
||||||
|
|
||||||
The fix adds an explicit left/right orientation workflow:
|
|
||||||
|
|
||||||
- `PhotoSlice.swapped` — persistent flag (default `False`): "user has explicitly
|
|
||||||
mirrored left/right".
|
|
||||||
- `PhotoSlice.swap_left_right()` — toggles `swapped` and reindexes the corners
|
|
||||||
`[TL,TR,BR,BL] -> [TR,TL,BL,BR]` (i.e. `bbox[[1,0,3,2]]`), a pure left/right
|
|
||||||
mirror that keeps the top (edge 0→1) and bottom edges in place. NOTE: the
|
|
||||||
earlier `[0,3,2,1]` reindex was wrong — it rotated the quad 90°.
|
|
||||||
- `normalize_top_left()` applies the same `[1,0,3,2]` reindex when `swapped` is
|
|
||||||
true (after its geometric inference), so manual orientation survives
|
|
||||||
`squarize()` / `reset_rotation()`.
|
|
||||||
- "Swap L/R" toolbar button (`ui_builders_preview.py`) ->
|
|
||||||
`CanvasStateMixin.swap_active_slice_left_right()` ->
|
|
||||||
`ScanSlicerCanvasActionsMixin.swap_active_box_left_right()`.
|
|
||||||
- Visual indication in `CanvasRenderMixin.paintEvent()` (`canvas_render.py`):
|
|
||||||
corners color-coded left = orange, right = cyan; edges colored red = top
|
|
||||||
(0→1), green = right (1→2), orange = bottom (2→3), and the left edge (3→0)
|
|
||||||
follows lock state (blue locked / lightgreen unlocked).
|
|
||||||
|
|
||||||
## Key implementation facts
|
|
||||||
|
|
||||||
- `PhotoSlice.bbox` is a 4×2 numpy array in the order
|
|
||||||
`[top-left, top-right, bottom-right, bottom-left]`.
|
|
||||||
- `Autoslicer.save_slice()` (in `autoslicer.py`) builds the perspective
|
|
||||||
transform assuming the `[TL,TR,BR,BL]` order. Do NOT change that contract
|
|
||||||
without updating every consumer.
|
|
||||||
- `PhotoSlice.squarize()` and `reset_rotation()` call `normalize_top_left()` at
|
|
||||||
the end; the `swapped` flag makes that safe for manual orientation.
|
|
||||||
|
|
||||||
## Repository structure
|
|
||||||
|
|
||||||
```
|
|
||||||
scanslicer/ application source (package)
|
|
||||||
entry.py CLI parsing, Wayland/X11 backend selection
|
|
||||||
main.py QtScanSlicer main window, theme, app bootstrap
|
|
||||||
controller*.py UI action handlers (status, canvas, detection,
|
|
||||||
image I/O, params)
|
|
||||||
ui_builders*.py widget construction (controls, preview, shared)
|
|
||||||
canvas*.py interactive canvas (interaction/render/state mixins)
|
|
||||||
slices.py PhotoSlice geometry model + polys_iou
|
|
||||||
autoslicer.py detection/export engine wrapper (save_slice)
|
|
||||||
autoslicer_detection.py detection pipelines, contour→box extraction
|
|
||||||
autoslicer_feedback.py auto-tune profiles, scoring, feedback persistence
|
|
||||||
params.py parameter model, defaults, presets
|
|
||||||
packaging/appimage/ AppImage recipe + build scripts (preferred deploy)
|
|
||||||
Dockerfile container runtime image
|
|
||||||
docker-compose.yml GUI container wiring
|
|
||||||
pyproject.toml package metadata + dependencies (uv.lock pinned)
|
|
||||||
docs/ user-guide, architecture, parameters, autotune,
|
|
||||||
runtime-config, appimage, development
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture in brief
|
|
||||||
|
|
||||||
- `QtScanSlicer` (`main.py`) is a `QMainWindow` composed from UI builder mixins
|
|
||||||
and controller mixins.
|
|
||||||
- `QtSlicingCanvas` (`canvas.py`) = interaction + render + state mixins; holds
|
|
||||||
the image, zoom/pan state, and the list of `PhotoSlice` objects.
|
|
||||||
- Background work (detection, auto-tune) runs on a single-worker `QThreadPool`;
|
|
||||||
results are delivered back to the UI via Qt signals.
|
|
||||||
- Export flow: `save_all()` (`controller_image_io.py`) loops over locked slices
|
|
||||||
and calls `Autoslicer.save_slice(bbox, path, params)` for each.
|
|
||||||
|
|
||||||
## Run / build
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uv sync --frozen
|
|
||||||
uv run scanslicer /path/to/scans # or: python -m scanslicer
|
|
||||||
```
|
|
||||||
|
|
||||||
- AppImage (preferred): `bash packaging/appimage/build-appimage.sh`
|
|
||||||
- AppImage via Docker: `bash packaging/appimage/build-appimage-docker.sh`
|
|
||||||
- Docker runtime: `docker compose up --build`
|
|
||||||
- Dependencies (from `pyproject.toml`): PySide6, opencv-python-headless, numpy.
|
|
||||||
- Python >= 3.10; dev env is Python 3.12 in `.venv`.
|
|
||||||
|
|
||||||
Dependency / supply-chain decisions (deliberate — keep them):
|
|
||||||
- `opencv-python-headless` is intentional: the app uses no OpenCV GUI/highgui.
|
|
||||||
- Pillow was removed (it was unused). Don't re-add without a concrete need.
|
|
||||||
- The AppImage build exports hashes from `uv.lock` and installs with
|
|
||||||
`pip install --require-hashes`.
|
|
||||||
|
|
||||||
### AppImage build: Docker context gotcha
|
|
||||||
|
|
||||||
The maintainer's Docker points at Docker Desktop (`desktop-linux` context). Its
|
|
||||||
bind-mount filesystem forbids `chown`, but `appimage-builder` needs root
|
|
||||||
ownership while extracting `.deb` packages, so the build fails with
|
|
||||||
`dpkg-deb ... Cannot change ownership to uid 0, gid 0: Operation not permitted`.
|
|
||||||
|
|
||||||
Workaround: run the Dockerized build against the native daemon (or switch
|
|
||||||
context with `docker context use default`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DOCKER_HOST=unix:///var/run/docker.sock bash packaging/appimage/build-appimage-docker.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Output lands in `appimage/dist/ScanSlicer-<version>-x86_64.AppImage` (the
|
|
||||||
`appimage/` dir is git-ignored).
|
|
||||||
|
|
||||||
## Config / persistence
|
|
||||||
|
|
||||||
Runtime files live in `~/.config/scanslicer/` (or `$XDG_CONFIG_HOME/scanslicer/`)
|
|
||||||
by default: `defaults.json`, `presets.json`, `autotune-feedback.json`,
|
|
||||||
`status.log`. Paths are overridable via `SCANSLICER_*_PATH` env vars. In the
|
|
||||||
Docker setup these land in `./.scanslicer/` on the host.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- No automated test suite exists; validate changes by running the app on sample scans (startup, detection, save, auto-tune).
|
|
||||||
266
MAINTENANCE.md
Normal file
266
MAINTENANCE.md
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
# Maintaining ScanSlicer
|
||||||
|
|
||||||
|
This file is a how-to for keeping the fork alive over time. The goals are
|
||||||
|
narrow and deliberate:
|
||||||
|
|
||||||
|
1. **Keep the app functional** for its one real job (detect photos in a flatbed
|
||||||
|
scan, correct the boxes, export straightened crops).
|
||||||
|
2. **Avoid security issues and breakage from end-of-life (EOL) components.**
|
||||||
|
|
||||||
|
There is **no ambition to develop new features**. Maintenance is manual: when
|
||||||
|
something drifts out of date, you bump a version, re-verify the app, and commit.
|
||||||
|
Do not add features, refactor, or "improve" things while doing maintenance —
|
||||||
|
small, focused changes only.
|
||||||
|
|
||||||
|
> Scope guard: this repo is a local fork of `macrodev8/ScanSlicer`, published at
|
||||||
|
> `https://code.dgerdesmann.de/dan/ScanSlicer.git`. License is GPL v3. Keep the
|
||||||
|
> upstream credit and the "inspired by PhotoSlicer" note intact. See `README.md`
|
||||||
|
> and `docs/` for project context.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where things live
|
||||||
|
|
||||||
|
| Component | File(s) |
|
||||||
|
|---|---|
|
||||||
|
| Package metadata + dependency constraints | `pyproject.toml` |
|
||||||
|
| **Pinned** dependency versions (source of truth) | `uv.lock` |
|
||||||
|
| Runtime container image | `Dockerfile` (ubuntu:24.04 + uv) |
|
||||||
|
| AppImage recipe + build scripts | `packaging/appimage/` |
|
||||||
|
| Bundled third-party license notice | `THIRD_PARTY_NOTICES` |
|
||||||
|
| Dev env (Python 3.12) | `.venv/` (managed by uv) |
|
||||||
|
|
||||||
|
Dependency management is done with **uv**. `pyproject.toml` declares the
|
||||||
|
requirements (mostly loose), and `uv.lock` pins the *exact* versions that get
|
||||||
|
installed and shipped. `uv.lock` is committed — do not edit it by hand; let uv
|
||||||
|
rewrite it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code invariants (orientation fix + bbox order)
|
||||||
|
|
||||||
|
This fork's one real code change is the mirrored-export orientation fix. Keep
|
||||||
|
these invariants in mind when touching geometry code.
|
||||||
|
|
||||||
|
- **`PhotoSlice.bbox` is a 4×2 numpy array in the order
|
||||||
|
`[top-left, top-right, bottom-right, bottom-left]`.** `Autoslicer.save_slice()`
|
||||||
|
(`autoslicer.py`) builds the perspective transform assuming that order. Do NOT
|
||||||
|
change this contract without updating every consumer.
|
||||||
|
- **`PhotoSlice.swapped`** (default `False`) is the persistent "user explicitly
|
||||||
|
mirrored left/right" flag. `swap_left_right()` toggles it and reindexes
|
||||||
|
corners `[TL,TR,BR,BL] -> [TR,TL,BL,BR]` (`bbox[[1,0,3,2]]`) — a pure
|
||||||
|
left/right mirror that keeps the top and bottom edges in place.
|
||||||
|
- **Root cause of the original bug:** `normalize_top_left()` (`slices.py`)
|
||||||
|
re-derives "top" from geometry, which is ambiguous, and `squarize()` /
|
||||||
|
`reset_rotation()` both re-run it at the end, silently overwriting manual
|
||||||
|
corrections. The fix makes `normalize_top_left()` apply the same `[1,0,3,2]`
|
||||||
|
reindex when `swapped` is true, so manual orientation survives. (Historical:
|
||||||
|
an earlier `[0,3,2,1]` reindex was wrong — it rotated the quad 90°.)
|
||||||
|
- **UI wiring:** "Swap L/R" button (`ui_builders_preview.py`) →
|
||||||
|
`CanvasStateMixin.swap_active_slice_left_right()` (`canvas_state.py`) →
|
||||||
|
`ScanSlicerCanvasActionsMixin.swap_active_box_left_right()`
|
||||||
|
(`controller_canvas_actions.py`) → `PhotoSlice.swap_left_right()`.
|
||||||
|
- **Visual color coding** (`canvas_render.py`): corners — left = orange, right
|
||||||
|
= cyan; edges — red = top (0→1), green = right (1→2), orange = bottom (2→3),
|
||||||
|
left edge (3→0) follows lock state (blue locked / lightgreen unlocked).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current pinned versions (snapshot)
|
||||||
|
|
||||||
|
Update this table whenever you bump something, so the next session has a
|
||||||
|
starting point.
|
||||||
|
|
||||||
|
| Component | Pinned version | Where |
|
||||||
|
|---|---|---|
|
||||||
|
| PySide6 / PySide6-Addons / -Essentials / shiboken6 | 6.10.2 | `uv.lock` |
|
||||||
|
| opencv-python-headless | 4.13.0.92 | `uv.lock` (constraint `~=4.13.0` in `pyproject.toml`) |
|
||||||
|
| numpy | 2.2.6 (Py<3.11) / 2.4.2 (Py≥3.11) | `uv.lock` |
|
||||||
|
| Python (dev env) | 3.12 | `.venv` |
|
||||||
|
| Python floor | `>=3.10` | `pyproject.toml` |
|
||||||
|
| uv (both Dockerfiles) | 0.10.2 | `Dockerfile`, `packaging/appimage/Dockerfile` |
|
||||||
|
| appimage-builder | 1.1.0 | `packaging/appimage/Dockerfile` |
|
||||||
|
| AppImage base OS | Ubuntu **noble** (24.04) | `packaging/appimage/AppImageBuilder.yml` |
|
||||||
|
| Runtime image base | Ubuntu 24.04 | `Dockerfile` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What can go stale, and the priority order
|
||||||
|
|
||||||
|
Review these roughly in this order — the first items are the real security/EOL
|
||||||
|
surface; the last ones are packaging plumbing that only matters at build time.
|
||||||
|
|
||||||
|
1. **PySide6 / Qt** — the largest attack surface and the component with the
|
||||||
|
most aggressive EOL cadence. Qt 6.x minor releases stop receiving updates
|
||||||
|
quickly; PySide6 tracks Qt. Keep this on a recent 6.x.
|
||||||
|
2. **opencv-python-headless** — image processing; bump to the latest 4.x.
|
||||||
|
3. **numpy** — bump to the latest 2.x.
|
||||||
|
4. **Python** — the *shipped* runtime is what matters. The AppImage bundles its
|
||||||
|
own Python (from Ubuntu noble, currently 3.12), so the app does not run on
|
||||||
|
the host's Python. The `requires-python = ">=3.10"` floor only affects
|
||||||
|
development resolution. Watch Python 3.10's EOL: when it goes EOL, raise the
|
||||||
|
floor (e.g. to `>=3.11`) so no EOL interpreter is ever considered valid.
|
||||||
|
5. **Packaging toolchain** — `uv` and `appimage-builder` versions pinned in the
|
||||||
|
two Dockerfiles. Only bump these when you have a reason (build breaks, or a
|
||||||
|
notable security fix in the tool itself).
|
||||||
|
6. **Ubuntu base images** — `noble` is an LTS (supported to ~2029). Revisit
|
||||||
|
the apt source and GPG key in `AppImageBuilder.yml` and the `FROM` line in
|
||||||
|
both Dockerfiles when noble approaches EOL.
|
||||||
|
|
||||||
|
Suggested cadence: check PyPI/upstream releases every ~3–6 months, or promptly
|
||||||
|
when a security advisory is published for any of the above.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Updating Python dependencies
|
||||||
|
|
||||||
|
All commands run from the repo root. The dev env is `.venv` (Python 3.12).
|
||||||
|
|
||||||
|
### 1. Bring the environment up to the committed lock (baseline)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync --frozen
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Bump a single dependency to its latest
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv lock --upgrade-package PySide6
|
||||||
|
uv lock --upgrade-package opencv-python-headless
|
||||||
|
uv lock --upgrade-package numpy
|
||||||
|
```
|
||||||
|
|
||||||
|
`--dry-run` previews the change without touching anything:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv lock --upgrade-package PySide6 --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
To bump *everything* at once (use sparingly — harder to bisect if something
|
||||||
|
breaks):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv lock --upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Install the updated set and verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
uv run scanslicer /path/to/test/scans
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the smoke-test checklist below.
|
||||||
|
|
||||||
|
### Special case: opencv major/minor bump
|
||||||
|
|
||||||
|
`opencv-python-headless` is the **only** dependency with a constraint in
|
||||||
|
`pyproject.toml` (`~=4.13.0`), which pins it to the 4.13.x line. To move to
|
||||||
|
4.14+, edit that constraint in `pyproject.toml` first, then run
|
||||||
|
`uv lock --upgrade-package opencv-python-headless`. PySide6 and numpy are
|
||||||
|
unpinned in `pyproject.toml`, so uv can move them freely.
|
||||||
|
|
||||||
|
### After any version change
|
||||||
|
|
||||||
|
- Commit `pyproject.toml` (if edited) **and** `uv.lock` together.
|
||||||
|
- Update the version table above.
|
||||||
|
- Update `THIRD_PARTY_NOTICES` — it lists concrete PySide6/Qt, numpy, and
|
||||||
|
opencv versions, and it ships inside every AppImage. Keep it aligned with
|
||||||
|
`uv.lock`.
|
||||||
|
- Rebuild and smoke-test the AppImage if the runtime stack (PySide6/Qt) changed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Updating the AppImage packaging toolchain
|
||||||
|
|
||||||
|
The AppImage build pins its own tool versions in `packaging/appimage/Dockerfile`:
|
||||||
|
|
||||||
|
- `uv` (`COPY --from=ghcr.io/astral-sh/uv:0.10.2 ...`)
|
||||||
|
- `appimage-builder==1.1.0` (an `ARG` near the top)
|
||||||
|
|
||||||
|
Bump either by editing that file. If you change `appimage-builder`, the
|
||||||
|
`AppImageBuilder.yml` recipe may need adjusting for new versions (rare). The
|
||||||
|
Ubuntu apt source and GPG key live in `AppImageBuilder.yml` — update those when
|
||||||
|
noble goes EOL or the key rotates.
|
||||||
|
|
||||||
|
> **Hardcoded Python paths:** `AppImageBuilder.yml` hardcodes `python3.12` in
|
||||||
|
> `QT_PLUGIN_PATH` and `LD_LIBRARY_PATH`. If the AppImage base OS ever moves to
|
||||||
|
> a different Python minor, those two lines must be updated to match.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Updating the runtime Docker image
|
||||||
|
|
||||||
|
`Dockerfile` (repo root) pins the base (`ubuntu:24.04`) and `uv 0.10.2`.
|
||||||
|
Bump these when needed, then rebuild and run the container smoke test:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verifying a change (smoke-test checklist)
|
||||||
|
|
||||||
|
There is **no automated test suite**. After any dependency or packaging change,
|
||||||
|
manually confirm the app still works:
|
||||||
|
|
||||||
|
1. **Startup** on both Wayland and X11:
|
||||||
|
```bash
|
||||||
|
uv run scanslicer --backend wayland
|
||||||
|
uv run scanslicer --backend x11
|
||||||
|
```
|
||||||
|
2. **Detection** on a representative sample scan (boxes appear on load/Detect).
|
||||||
|
3. **Save flow** — lock a box, export, confirm the output is straight and not
|
||||||
|
mirrored (this fork's orientation fix is the thing most likely to regress).
|
||||||
|
4. **Auto-tune** — run a profile and confirm feedback is written/read without
|
||||||
|
error.
|
||||||
|
5. If the runtime stack changed: rebuild the AppImage and confirm it launches
|
||||||
|
and saves.
|
||||||
|
6. If the Docker image changed: `docker compose up --build` and confirm the app
|
||||||
|
opens.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Docker gotcha (recap — keep this in mind)
|
||||||
|
|
||||||
|
The maintainer's Docker points at **Docker Desktop** (`desktop-linux` context).
|
||||||
|
Its bind-mount filesystem forbids `chown`, but `appimage-builder` needs root
|
||||||
|
ownership while extracting `.deb` packages, so the Dockerized AppImage build
|
||||||
|
fails with:
|
||||||
|
|
||||||
|
```
|
||||||
|
dpkg-deb ... Cannot change ownership to uid 0, gid 0: Operation not permitted
|
||||||
|
```
|
||||||
|
|
||||||
|
Workaround: run the build against the **native** daemon instead:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DOCKER_HOST=unix:///var/run/docker.sock bash packaging/appimage/build-appimage-docker.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
(or `docker context use default` first). Output lands in
|
||||||
|
`appimage/dist/ScanSlicer-<version>-x86_64.AppImage` (the `appimage/` dir is
|
||||||
|
git-ignored).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commit / release hygiene
|
||||||
|
|
||||||
|
- Commit `uv.lock` and `pyproject.toml` (and `THIRD_PARTY_NOTICES` when
|
||||||
|
applicable) together as a single change.
|
||||||
|
- Write a short, factual commit message, e.g. `Bump PySide6 to 6.10.x`.
|
||||||
|
- Push to the Forgejo remote: `git push origin`.
|
||||||
|
- The AppImage build verifies package hashes (`--require-hashes`) from
|
||||||
|
`uv.lock`; publishing the AppImage alongside the matching committed source is
|
||||||
|
already handled by committing before building.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## One-line summary
|
||||||
|
|
||||||
|
Bump PySide6/Qt first and most often; bump opencv and numpy when convenient;
|
||||||
|
treat uv/appimage-builder/Ubuntu as build plumbing you only touch when
|
||||||
|
necessary. After every change, run the smoke-test checklist, rebuild the
|
||||||
|
AppImage if the runtime stack moved, and commit `uv.lock` + `pyproject.toml`
|
||||||
|
together.
|
||||||
|
|
@ -6,7 +6,7 @@ AppDir:
|
||||||
id: scanslicer
|
id: scanslicer
|
||||||
name: ScanSlicer
|
name: ScanSlicer
|
||||||
icon: scanslicer
|
icon: scanslicer
|
||||||
version: "1.0.0"
|
version: "1.1.0"
|
||||||
exec: usr/bin/python3
|
exec: usr/bin/python3
|
||||||
exec_args: "-m scanslicer $@"
|
exec_args: "-m scanslicer $@"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,25 @@ uv export \
|
||||||
--no-emit-project \
|
--no-emit-project \
|
||||||
--format requirements-txt \
|
--format requirements-txt \
|
||||||
--output-file "${BUILD_DIR}/requirements.txt"
|
--output-file "${BUILD_DIR}/requirements.txt"
|
||||||
|
# Remove stale wheels from previous versions so a version-specific lookup
|
||||||
|
# below cannot accidentally pick up an outdated build artifact.
|
||||||
|
rm -f "${BUILD_DIR}"/scanslicer-*.whl
|
||||||
uv build --wheel --out-dir "${BUILD_DIR}"
|
uv build --wheel --out-dir "${BUILD_DIR}"
|
||||||
|
|
||||||
WHEEL_PATH="$(ls -1 "${BUILD_DIR}"/scanslicer-*.whl | head -n 1)"
|
# Select the wheel matching the project version from pyproject.toml. Listing
|
||||||
if [[ -z "${WHEEL_PATH}" ]]; then
|
# with `ls` and taking the first result breaks once an older wheel for a
|
||||||
echo "Could not find built wheel in ${BUILD_DIR}" >&2
|
# previous version is left in the build dir, because `ls` sorts
|
||||||
|
# alphabetically and would pick the stale artifact.
|
||||||
|
PROJECT_VERSION="$(sed -n 's/^version *= *"\([^"]*\)".*/\1/p' "${ROOT_DIR}/pyproject.toml" | head -n 1)"
|
||||||
|
if [[ -z "${PROJECT_VERSION}" ]]; then
|
||||||
|
echo "Could not determine project version from pyproject.toml" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
WHEEL_PATH="${BUILD_DIR}/scanslicer-${PROJECT_VERSION}-py3-none-any.whl"
|
||||||
|
if [[ ! -f "${WHEEL_PATH}" ]]; then
|
||||||
|
echo "Could not find built wheel for version ${PROJECT_VERSION} in ${BUILD_DIR}" >&2
|
||||||
|
echo "Found wheels:" >&2
|
||||||
|
ls -1 "${BUILD_DIR}"/scanslicer-*.whl >&2 || true
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "scanslicer"
|
name = "scanslicer"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
description = "Detect, straighten and save photos from a flatbed scanner source image"
|
description = "Detect, straighten and save photos from a flatbed scanner source image"
|
||||||
readme = { file = "README.md", content-type = "text/markdown" }
|
readme = { file = "README.md", content-type = "text/markdown" }
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|
|
||||||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -222,7 +222,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scanslicer"
|
name = "scanslicer"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue