Skip to content

Commit 458ebd0

Browse files
eduralphclaude
andcommitted
End-to-end test: branch-neutral CI + hybrid install on maintenance/gramps61
Bootstrap of the full CI pipeline as it now sits at the tip of PR 820 (eduralph/addons-source@feature/ci-cd-pipeline-upstream, commit 3b2a947), landed directly on maintenance/gramps61 for an end-to-end CI verification. This branch is a one-shot test bed: if Gary/Nick merge the PR upstream, fork's maintenance/gramps61 will eventually be forward-merged from gramps60, not from this state. Equivalent shape to what the cherry-picked PR 820 contains: - .github/workflows/ci.yml — branch-neutral setup job (suffix / ci_image derived from github.ref_name); container jobs use needs.setup.outputs.ci_image; lint + unit-test-linux + unit-test-windows now blocking, addon-structure still advisory. - .github/workflows/docker-build.yml — branch-neutral params step (suffix, series, fallback SHA captured via git ls-remote of gramps-project/gramps@maintenance/gramps${suffix}); image tag derived; GRAMPS_FALLBACK_SHA threaded through as a build-arg so the buildx cache key invalidates when upstream tip moves. - .github/docker/gramps-ci/Dockerfile — required GRAMPS_SERIES arg; hybrid install (pip first, SHA-pinned git clone fallback when no gramps==${SERIES}.* exists on PyPI, fatal on any other pip failure). - .github/environment.yml — conda env for the Windows unit-test job (unchanged from PR 820's prior state). - tests/* — shared test harness (gramps_test_env.py base classes, test_plugin_registration.py). - .github/CI-MAINTAINER.md — runbook for the gramps-project maintainer covering one-time setup (GHCR visibility, first-push race), ongoing operations (new maintenance branches, PyPI release transitions), diagnostic log markers, and future-proofing knobs. Expected on push: - docker-build fires; params step derives suffix=gramps61, series=6.1, fallback_sha=<current tip of gramps-project/gramps maintenance/gramps61>. - Dockerfile install logs ::warning::no gramps==6.1.* on PyPI; installing from gramps-project/gramps@maintenance/gramps61 at <sha>, since no 6.1.0 release exists yet. - Image published to ghcr.io/eduralph/addons-source/gramps-ci: gramps61. - First CI run: container jobs may race the image push (option 1 first-push behavior documented in ci.yml header); re-run failed jobs once docker-build finishes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a020a48 commit 458ebd0

8 files changed

Lines changed: 1482 additions & 0 deletions

File tree

.github/CI-MAINTAINER.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# CI Maintainer Notes
2+
3+
Operational steps that a `gramps-project/addons-source` maintainer
4+
needs to handle once PR 820 (and the branch-neutral follow-up) lands.
5+
The pipeline is otherwise self-driving — this document is only about
6+
the rough edges.
7+
8+
For day-to-day addon-release maintenance see [MAINTAINERS.md](../MAINTAINERS.md);
9+
for the contributor-facing summary of what a green CI check means on
10+
an unreleased branch see [CONTRIBUTING.md](../CONTRIBUTING.md#work-towards-a-merge).
11+
12+
## Contents
13+
14+
1. [One-time setup when the PR first merges](#one-time-setup-when-the-pr-first-merges)
15+
2. [Creating a new maintenance branch](#creating-a-new-maintenance-branch)
16+
3. [When a Gramps minor release lands on PyPI](#when-a-gramps-minor-release-lands-on-pypi)
17+
4. [Diagnostic log markers](#diagnostic-log-markers)
18+
5. [Optional future-proofing knobs](#optional-future-proofing-knobs)
19+
20+
## One-time setup when the PR first merges
21+
22+
### 1. Make the `gramps-ci` GHCR package public
23+
24+
`docker-build.yml` pushes images to
25+
`ghcr.io/gramps-project/addons-source/gramps-ci:<suffix>` using the
26+
workflow's `GITHUB_TOKEN`. GHCR creates the package as **private** the
27+
first time. Same-repo CI keeps working (token covers own packages),
28+
but **fork PRs cannot pull the image** because their `GITHUB_TOKEN`
29+
has no read access to private packages in `gramps-project`. Fork-PR
30+
container jobs would fail at "Initialize containers" with an
31+
authentication error.
32+
33+
Fix once, immediately after the first `Build Docker Images` run
34+
finishes:
35+
36+
1. Go to <https://github.com/orgs/gramps-project/packages/container/addons-source%2Fgramps-ci/settings>
37+
2. Under "Danger Zone" → "Change visibility" → set to **Public**
38+
39+
Every existing and future `gramps-ci:<suffix>` tag inherits public
40+
visibility from this single setting.
41+
42+
### 2. Expect the first-push race on `maintenance/gramps60`
43+
44+
The first push event after merge fires both workflows in parallel:
45+
46+
- `Build Docker Images` builds and pushes `gramps-ci:gramps60`
47+
(~5 min cold).
48+
- `CI` runs `setup` (~2 s), then its container jobs try to pull the
49+
image.
50+
51+
Because both start on the same push, the CI container jobs race the
52+
image push and may fail at "Initialize containers" the first time.
53+
This race only happens once per branch:
54+
55+
1. Wait for `Build Docker Images` to complete.
56+
2. Open the failed CI run → click "Re-run failed jobs".
57+
3. Subsequent pushes find the image already in GHCR — no race.
58+
59+
This is also explained in the header comment of `ci.yml`.
60+
61+
## Creating a new maintenance branch
62+
63+
When a new Gramps minor series goes into development and addons need
64+
a corresponding branch (e.g. `maintenance/gramps62` once 6.2 opens):
65+
66+
```
67+
git branch maintenance/gramps62 maintenance/gramps61
68+
git push origin maintenance/gramps62
69+
```
70+
71+
No workflow edits required. The workflows derive everything from
72+
`github.ref_name`. The first push fires the same race described
73+
above — re-run the failed CI jobs once `Build Docker Images`
74+
finishes.
75+
76+
The setup job's regex (`gramps[0-9][0-9]`) requires a two-digit
77+
suffix. When Gramps 10.0 opens this regex needs updating in two
78+
places (`ci.yml` setup job and `docker-build.yml` params step).
79+
80+
## When a Gramps minor release lands on PyPI
81+
82+
The hybrid Dockerfile auto-detects PyPI availability:
83+
84+
- `pip install "gramps==X.Y.*"` succeeds → image installs the tagged
85+
PyPI release (`::notice::` log line).
86+
- pip reports "No matching distribution found" → image falls back to a
87+
SHA-pinned `git clone` of `gramps-project/gramps@maintenance/grampsNN`
88+
at the SHA captured by `docker-build.yml`'s params step
89+
(`::warning::` log line).
90+
91+
When `gramps==6.1.0` is finally published to PyPI, the next image
92+
rebuild on `maintenance/gramps61` silently switches from "git tip" to
93+
"PyPI release" — no maintainer action needed.
94+
95+
To **immediately** rebuild against the new PyPI release without
96+
waiting for the next push:
97+
98+
1. Open the `Build Docker Images` workflow in the Actions tab.
99+
2. "Run workflow" → select `maintenance/grampsNN` → Run.
100+
101+
The `::notice::` line in the build log confirms the switch.
102+
103+
## Diagnostic log markers
104+
105+
When investigating a CI failure, the install-step output in
106+
`Build Docker Images` carries these annotations:
107+
108+
| Annotation | Meaning |
109+
| --- | --- |
110+
| `::notice::installed gramps==X.Y.* from PyPI` | Released-path install. CI is testing against a tagged release. |
111+
| `::warning::no gramps==X.Y.* on PyPI; installing from gramps-project/gramps@maintenance/grampsNN at <sha>` | Unreleased-branch fallback. CI is testing against the upstream branch tip at `<sha>`. Visible to contributors via the CONTRIBUTING.md note. |
112+
| `::error::no gramps==X.Y.* on PyPI and GRAMPS_FALLBACK_SHA is unset` | `git ls-remote` in `docker-build.yml`'s params step returned no SHA for `maintenance/grampsNN` on `gramps-project/gramps`. The matching upstream branch is missing, or the addons-source branch is misnamed. |
113+
| `::error::pip install gramps failed (non-version reason)` | pip failed for a network/registry reason, not because the version is missing. Captured stderr is dumped after the line. The build does **not** fall back to git in this case — by design, so a transient PyPI hiccup cannot silently flip a released branch into "git tip" mode. |
114+
115+
Other useful entry points:
116+
117+
- `ci.yml` setup job log shows the derived `branch_suffix` and
118+
`ci_image`. A failure here means the branch name doesn't match
119+
`maintenance/gramps[0-9][0-9]`.
120+
- `docker-build.yml` params step log shows the captured
121+
`fallback_sha`. An empty value means upstream `gramps-project/gramps`
122+
has no matching maintenance branch (warning issued; image build will
123+
fail iff the fallback is needed).
124+
125+
## Optional future-proofing knobs
126+
127+
Not needed today; record here in case they ever come up.
128+
129+
### Upstream gramps repo URL
130+
131+
The Dockerfile hardcodes `https://github.com/gramps-project/gramps.git`
132+
as the fallback source. If the project ever reorgs or renames, this
133+
URL must change in one place (`.github/docker/gramps-ci/Dockerfile`).
134+
It could be parameterised as a build arg (`ARG GRAMPS_UPSTREAM_REPO`)
135+
with the current URL as default, but a hardcoded value keeps the
136+
Dockerfile simpler and a rename is a sufficiently large event that
137+
editing one string is not the bottleneck.
138+
139+
### GHCR tag retention
140+
141+
`docker-build.yml` pushes both a moving `gramps-ci:<suffix>` tag (e.g.
142+
`gramps60`) and a per-commit `gramps-ci:<suffix>-<short-sha>` tag.
143+
The moving tag is always overwritten; the SHA tags accumulate over
144+
time. Set a retention policy on the GHCR package settings page if
145+
the count becomes inconvenient — the moving tags are what CI consumes.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# .github/docker/gramps-ci/Dockerfile
2+
#
3+
# Gramps CI image. The Gramps minor series (6.0, 6.1, …) is picked at
4+
# build time via the GRAMPS_SERIES build arg, so the same Dockerfile
5+
# produces gramps-ci:gramps60, gramps-ci:gramps61, … without per-branch
6+
# edits. Includes everything jobs need:
7+
# - Python + pip-installed Gramps, PyGObject, pycairo
8+
# - GTK typelibs (so addon modules that `from gi.repository import Gtk`
9+
# at module load time are importable — widgets still need xvfb to render)
10+
# - intltool/gettext/git for make.py builds
11+
# - ruff, dbf for lint/test tooling (tests use stdlib unittest per AGENTS.md)
12+
# - xvfb + xauth for tests that actually render (wrap with `xvfb-run`)
13+
#
14+
# No display server runs by default — the image is headless unless a command
15+
# explicitly invokes `xvfb-run`. When running with docker locally, pass
16+
# `--init` (or use a container runtime that injects tini) because xvfb-run
17+
# hangs if it inherits PID 1.
18+
#
19+
# Gramps install path. PyPI is tried first; when no gramps==${SERIES}.*
20+
# release exists (e.g. while 6.1 is still in development) the build
21+
# falls back to a SHA-pinned git clone of
22+
# gramps-project/gramps@maintenance/gramps${SERIES_NODOT}. Other pip
23+
# failures (network, etc.) are NOT silently retried so a transient
24+
# PyPI outage cannot flip a normally-released branch to "test against
25+
# moving tip" mode. docker-build.yml captures the upstream SHA via
26+
# git ls-remote and passes it in as GRAMPS_FALLBACK_SHA; the SHA
27+
# becomes part of the buildx cache key so a moved upstream tip
28+
# actually re-runs the install layer.
29+
#
30+
# Local build (released series):
31+
# docker build --build-arg GRAMPS_SERIES=6.0 .github/docker/gramps-ci
32+
#
33+
# Local build (unreleased series, e.g. 6.1):
34+
# sha=$(git ls-remote https://github.com/gramps-project/gramps.git \
35+
# refs/heads/maintenance/gramps61 | awk '{print $1}')
36+
# docker build --build-arg GRAMPS_SERIES=6.1 \
37+
# --build-arg GRAMPS_FALLBACK_SHA=$sha \
38+
# .github/docker/gramps-ci
39+
#
40+
ARG PYTHON_VERSION=3.12
41+
FROM python:${PYTHON_VERSION}-slim
42+
43+
# Gramps minor series to install (e.g. 6.0, 6.1). No default — must be
44+
# passed explicitly so a wrong default cannot silently produce an image
45+
# for the wrong Gramps series. docker-build.yml derives this from the
46+
# branch ref.
47+
ARG GRAMPS_SERIES
48+
# Commit SHA on gramps-project/gramps@maintenance/grampsNN. Only used
49+
# when no gramps==${GRAMPS_SERIES}.* release exists on PyPI. Ignored
50+
# otherwise. Empty default is intentional — on released branches the
51+
# fallback never fires.
52+
ARG GRAMPS_FALLBACK_SHA=""
53+
RUN [ -n "$GRAMPS_SERIES" ] || { echo "GRAMPS_SERIES is required (e.g. 6.0)"; exit 1; }
54+
55+
LABEL org.opencontainers.image.source="https://github.com/gramps-project/addons-source"
56+
LABEL org.opencontainers.image.description="Gramps ${GRAMPS_SERIES} CI image (Python, Gramps, GTK typelibs, xvfb)"
57+
58+
RUN apt-get update && apt-get install -y --no-install-recommends \
59+
libgirepository-2.0-dev \
60+
gir1.2-glib-2.0 \
61+
gir1.2-gtk-3.0 \
62+
gir1.2-pango-1.0 \
63+
gir1.2-gdkpixbuf-2.0 \
64+
gir1.2-atk-1.0 \
65+
gir1.2-gexiv2-0.10 \
66+
gcc \
67+
pkg-config \
68+
python3-dev \
69+
libcairo2-dev \
70+
intltool \
71+
gettext \
72+
git \
73+
xvfb \
74+
xauth \
75+
&& rm -rf /var/lib/apt/lists/*
76+
77+
# Addon runtime deps (dbf, networkx, lxml, svgwrite, boto3, etc.) are
78+
# NOT baked in here — ci.yml's "Install addon runtime deps (derived from
79+
# requires_mod)" step pip-installs them at CI runtime from every
80+
# .gpr.py's requires_mod list, matching what Gramps' Addon Manager does
81+
# for an end user. Keeps .gpr.py the single source of truth.
82+
RUN pip install --no-cache-dir PyGObject pycairo orjson ruff
83+
84+
# Install gramps: PyPI first, SHA-pinned git clone as fallback.
85+
RUN /bin/bash -eo pipefail <<'BASH'
86+
suffix_nodot="${GRAMPS_SERIES//./}"
87+
if pip install --no-cache-dir "gramps==${GRAMPS_SERIES}.*" 2>/tmp/pip.err; then
88+
echo "::notice::installed gramps==${GRAMPS_SERIES}.* from PyPI"
89+
elif grep -q "No matching distribution found for gramps==" /tmp/pip.err; then
90+
if [ -z "${GRAMPS_FALLBACK_SHA}" ]; then
91+
echo "::error::no gramps==${GRAMPS_SERIES}.* on PyPI and GRAMPS_FALLBACK_SHA is unset"
92+
exit 1
93+
fi
94+
echo "::warning::no gramps==${GRAMPS_SERIES}.* on PyPI; installing from gramps-project/gramps@maintenance/gramps${suffix_nodot} at ${GRAMPS_FALLBACK_SHA}"
95+
mkdir -p /tmp/gramps
96+
cd /tmp/gramps
97+
git init -q
98+
git remote add origin https://github.com/gramps-project/gramps.git
99+
git fetch --depth 1 origin "${GRAMPS_FALLBACK_SHA}"
100+
git checkout FETCH_HEAD
101+
pip install --no-cache-dir .
102+
cd /
103+
rm -rf /tmp/gramps
104+
else
105+
echo "::error::pip install gramps failed (non-version reason); aborting rather than silently switching to git tip"
106+
cat /tmp/pip.err
107+
exit 1
108+
fi
109+
BASH
110+
111+
RUN apt-get purge -y gcc python3-dev pkg-config && apt-get autoremove -y
112+
113+
RUN python -c "from gramps.gen.const import VERSION; print('Gramps', VERSION)" \
114+
&& python -c "import gi; gi.require_version('Gtk', '3.0'); from gi.repository import Gtk; print('GTK OK')"
115+
116+
WORKDIR /workspace

.github/environment.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: addons-ci
2+
channels:
3+
- conda-forge
4+
dependencies:
5+
- python=3.12
6+
- pygobject
7+
- gtk3
8+
- pip
9+
# Addon runtime deps (dbf, networkx, lxml, svgwrite, boto3, etc.) are
10+
# installed at CI runtime by ci.yml's auto-derive step from .gpr.py
11+
# requires_mod — single source of truth. Keep only the stable base
12+
# here (Gramps + orjson for plugin registration).
13+
- pip:
14+
- "gramps>=6.0,<6.1"
15+
- orjson

0 commit comments

Comments
 (0)