Skip to content

Commit 9c2ac1d

Browse files
authored
Merge pull request #1 from eduralph/feature/ci-cd-pipeline
Add a CI-CD Pipeline
2 parents b4457a9 + 5f5c3e5 commit 9c2ac1d

10 files changed

Lines changed: 1221 additions & 0 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# .github/docker/gramps-gtk/Dockerfile
2+
#
3+
# Full Gramps 6.0 image with GTK/GObject Introspection for integration tests.
4+
# Extends gramps-headless (which already has PyGObject + GLib) with the GTK
5+
# typelibs and supporting libraries.
6+
#
7+
ARG BASE_IMAGE=ghcr.io/gramps-project/addons-source/gramps-headless:gramps60
8+
FROM ${BASE_IMAGE}
9+
10+
LABEL org.opencontainers.image.source="https://github.com/gramps-project/addons-source"
11+
LABEL org.opencontainers.image.description="Gramps 6.0 with GTK for integration tests"
12+
13+
# Install GTK typelibs and supporting libraries.
14+
# PyGObject is already pip-installed in the headless image, so we only need
15+
# the GI typelib data files for GTK, Pango, etc.
16+
RUN apt-get update && apt-get install -y --no-install-recommends \
17+
gir1.2-gtk-3.0 \
18+
gir1.2-pango-1.0 \
19+
gir1.2-gdkpixbuf-2.0 \
20+
gir1.2-atk-1.0 \
21+
xvfb \
22+
xauth \
23+
&& rm -rf /var/lib/apt/lists/*
24+
25+
# Smoke-test: GTK is importable via the pip-installed PyGObject
26+
RUN python -c "import gi; gi.require_version('Gtk', '3.0'); from gi.repository import Gtk; print('GTK OK')"
27+
28+
WORKDIR /workspace
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# .github/docker/gramps-headless/Dockerfile
2+
#
3+
# Headless Gramps 6.0 image for lint, compile-check, unit tests, and build.
4+
# Includes PyGObject (GLib only, no GTK) because gramps.gen.const
5+
# requires gi.repository.GLib for path resolution.
6+
#
7+
ARG PYTHON_VERSION=3.12
8+
FROM python:${PYTHON_VERSION}-slim
9+
10+
LABEL org.opencontainers.image.source="https://github.com/gramps-project/addons-source"
11+
LABEL org.opencontainers.image.description="Gramps 6.0 headless (GLib only, no GTK) for CI"
12+
13+
# System libraries needed to build PyGObject from pip + build tools for make.py
14+
RUN apt-get update && apt-get install -y --no-install-recommends \
15+
libgirepository-2.0-dev \
16+
gir1.2-glib-2.0 \
17+
gcc \
18+
pkg-config \
19+
python3-dev \
20+
libcairo2-dev \
21+
intltool \
22+
gettext \
23+
git \
24+
&& rm -rf /var/lib/apt/lists/*
25+
26+
# Install PyGObject (compiled for this Python) + Gramps + CI tooling
27+
RUN pip install --no-cache-dir \
28+
PyGObject \
29+
pycairo \
30+
"gramps>=6.0,<6.1" \
31+
orjson \
32+
pytest \
33+
ruff \
34+
dbf
35+
36+
# Clean up build deps that are no longer needed at runtime
37+
RUN apt-get purge -y gcc python3-dev pkg-config && apt-get autoremove -y
38+
39+
# Smoke-test: gramps core is importable via GLib
40+
RUN python -c "from gramps.gen.const import VERSION; print('Gramps', VERSION)"
41+
42+
WORKDIR /workspace

.github/workflows/ci.yml

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [maintenance/gramps60]
6+
pull_request:
7+
branches: [maintenance/gramps60]
8+
9+
env:
10+
HEADLESS_IMAGE: ghcr.io/${{ github.repository }}/gramps-headless:gramps60
11+
GTK_IMAGE: ghcr.io/${{ github.repository }}/gramps-gtk:gramps60
12+
13+
jobs:
14+
# -----------------------------------------------------------------
15+
# Lint (headless container)
16+
# -----------------------------------------------------------------
17+
lint:
18+
name: Lint
19+
runs-on: ubuntu-latest
20+
container:
21+
image: ghcr.io/${{ github.repository }}/gramps-headless:gramps60
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Run ruff (syntax and import errors only)
26+
run: ruff check --select=E9,F63,F7,F82 --no-fix --exclude='*.gpr.py' .
27+
28+
- name: Check trailing whitespace in Python files
29+
run: |
30+
if git --no-pager grep --color -n --full-name '[ \t]$' -- '*.py'; then
31+
echo "::error::Trailing whitespace found in Python files"
32+
exit 1
33+
fi
34+
35+
# -----------------------------------------------------------------
36+
# Addon structure (bare runner — just bash, no deps needed)
37+
# -----------------------------------------------------------------
38+
addon-structure:
39+
name: Addon Structure
40+
runs-on: ubuntu-latest
41+
steps:
42+
- uses: actions/checkout@v4
43+
44+
- name: Check all addons have po/template.pot
45+
run: |
46+
failed=0
47+
for gpr in */*.gpr.py; do
48+
addon_dir="$(dirname "$gpr")"
49+
if [ ! -d "$addon_dir/po" ]; then
50+
echo "::error::$addon_dir is missing po/ directory"
51+
failed=1
52+
elif [ ! -f "$addon_dir/po/template.pot" ]; then
53+
echo "::error::$addon_dir is missing po/template.pot"
54+
failed=1
55+
fi
56+
done
57+
if [ "$failed" -eq 0 ]; then
58+
echo "All addons have po/template.pot"
59+
fi
60+
exit $failed
61+
62+
# -----------------------------------------------------------------
63+
# Compile check (headless container)
64+
# -----------------------------------------------------------------
65+
compile-check:
66+
name: Compile Check
67+
runs-on: ubuntu-latest
68+
container:
69+
image: ghcr.io/${{ github.repository }}/gramps-headless:gramps60
70+
steps:
71+
- uses: actions/checkout@v4
72+
73+
- name: Compile all Python files (excluding .gpr.py)
74+
run: |
75+
failed=0
76+
while IFS= read -r f; do
77+
if ! python3 -m py_compile "$f" 2>&1; then
78+
failed=1
79+
fi
80+
done < <(find . -name '*.py' ! -name '*.gpr.py' ! -path './.git/*' ! -path '*/__pycache__/*')
81+
exit $failed
82+
83+
# -----------------------------------------------------------------
84+
# Unit tests — Linux (headless container)
85+
# -----------------------------------------------------------------
86+
unit-test-linux:
87+
name: Unit Tests (Linux)
88+
runs-on: ubuntu-latest
89+
container:
90+
image: ghcr.io/${{ github.repository }}/gramps-headless:gramps60
91+
steps:
92+
- uses: actions/checkout@v4
93+
94+
- name: Run per-addon unit tests
95+
env:
96+
PYTHONPATH: .
97+
run: |
98+
test_dirs=""
99+
for d in */tests/; do
100+
if ls "$d"/test_*.py 1>/dev/null 2>&1; then
101+
test_dirs="$test_dirs $d"
102+
fi
103+
done
104+
for f in */test_*.py; do
105+
if [ -f "$f" ]; then
106+
test_dirs="$test_dirs $(dirname "$f")"
107+
fi
108+
done
109+
if [ -n "$test_dirs" ]; then
110+
echo "Running unit tests in: $test_dirs"
111+
python3 -m pytest $test_dirs -v --tb=short \
112+
--ignore-glob='**/test_integration*.py' \
113+
-m 'not gui'
114+
else
115+
echo "No per-addon unit test files found"
116+
fi
117+
118+
# -----------------------------------------------------------------
119+
# Unit tests — Windows (bare runner, pip install)
120+
# -----------------------------------------------------------------
121+
unit-test-windows:
122+
name: Unit Tests (Windows)
123+
runs-on: windows-latest
124+
steps:
125+
- uses: actions/checkout@v4
126+
127+
- name: Set up Python
128+
uses: actions/setup-python@v5
129+
with:
130+
python-version: "3.12"
131+
132+
- name: Install dependencies
133+
run: pip install gramps orjson pytest dbf
134+
135+
- name: Run per-addon unit tests
136+
env:
137+
PYTHONPATH: .
138+
shell: bash
139+
run: |
140+
test_dirs=""
141+
for d in */tests/; do
142+
if ls "$d"/test_*.py 1>/dev/null 2>&1; then
143+
test_dirs="$test_dirs $d"
144+
fi
145+
done
146+
for f in */test_*.py; do
147+
if [ -f "$f" ]; then
148+
test_dirs="$test_dirs $(dirname "$f")"
149+
fi
150+
done
151+
if [ -n "$test_dirs" ]; then
152+
echo "Running unit tests in: $test_dirs"
153+
python -m pytest $test_dirs -v --tb=short \
154+
--ignore-glob='**/test_integration*.py' \
155+
-m 'not gui'
156+
else
157+
echo "No per-addon unit test files found"
158+
fi
159+
160+
# -----------------------------------------------------------------
161+
# Integration tests — Gramps (GTK container)
162+
# -----------------------------------------------------------------
163+
integration-test:
164+
name: Integration Tests (Gramps)
165+
runs-on: ubuntu-latest
166+
needs: [unit-test-linux]
167+
container:
168+
image: ghcr.io/${{ github.repository }}/gramps-gtk:gramps60
169+
steps:
170+
- uses: actions/checkout@v4
171+
with:
172+
lfs: true
173+
174+
- name: Run plugin registration tests
175+
env:
176+
PYTHONPATH: .
177+
run: xvfb-run python3 -m pytest tests/ -v --tb=short
178+
179+
- name: Run per-addon integration tests
180+
env:
181+
PYTHONPATH: .
182+
run: |
183+
integration_tests=""
184+
for f in */tests/test_integration*.py */test_integration*.py; do
185+
if [ -f "$f" ]; then
186+
integration_tests="$integration_tests $f"
187+
fi
188+
done
189+
if [ -n "$integration_tests" ]; then
190+
echo "Running per-addon integration tests: $integration_tests"
191+
xvfb-run python3 -m pytest $integration_tests -v --tb=short
192+
else
193+
echo "No per-addon integration test files found"
194+
fi
195+
196+
# -----------------------------------------------------------------
197+
# Build (headless container — has intltool/gettext)
198+
# -----------------------------------------------------------------
199+
build:
200+
name: Build
201+
runs-on: ubuntu-latest
202+
container:
203+
image: ghcr.io/${{ github.repository }}/gramps-headless:gramps60
204+
steps:
205+
- uses: actions/checkout@v4
206+
207+
- name: Determine GRAMPSPATH
208+
id: gramps-path
209+
run: |
210+
GPATH=$(python3 -c "import gramps, os; print(os.path.dirname(os.path.dirname(gramps.__file__)))")
211+
echo "path=$GPATH" >> "$GITHUB_OUTPUT"
212+
213+
- name: Build all addons
214+
env:
215+
GRAMPSPATH: ${{ steps.gramps-path.outputs.path }}
216+
run: |
217+
mkdir -p ../download
218+
python3 make.py gramps60 build all

.github/workflows/docker-build.yml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Build Docker Images
2+
3+
on:
4+
push:
5+
branches: [maintenance/gramps60]
6+
paths:
7+
- '.github/docker/**'
8+
workflow_dispatch:
9+
10+
env:
11+
REGISTRY: ghcr.io
12+
REPO: ${{ github.repository }}
13+
14+
permissions:
15+
contents: read
16+
packages: write
17+
18+
jobs:
19+
build-headless:
20+
name: Build gramps-headless
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Log in to GHCR
26+
uses: docker/login-action@v3
27+
with:
28+
registry: ghcr.io
29+
username: ${{ github.actor }}
30+
password: ${{ secrets.GITHUB_TOKEN }}
31+
32+
- name: Set up Docker Buildx
33+
uses: docker/setup-buildx-action@v3
34+
35+
- name: Docker metadata
36+
id: meta
37+
uses: docker/metadata-action@v5
38+
with:
39+
images: ${{ env.REGISTRY }}/${{ env.REPO }}/gramps-headless
40+
tags: |
41+
type=raw,value=gramps60
42+
type=sha,prefix=gramps60-
43+
44+
- name: Build and push gramps-headless
45+
uses: docker/build-push-action@v6
46+
with:
47+
context: .github/docker/gramps-headless
48+
push: true
49+
tags: ${{ steps.meta.outputs.tags }}
50+
labels: ${{ steps.meta.outputs.labels }}
51+
cache-from: type=gha
52+
cache-to: type=gha,mode=max
53+
54+
build-gtk:
55+
name: Build gramps-gtk
56+
runs-on: ubuntu-latest
57+
needs: [build-headless]
58+
steps:
59+
- uses: actions/checkout@v4
60+
61+
- name: Log in to GHCR
62+
uses: docker/login-action@v3
63+
with:
64+
registry: ghcr.io
65+
username: ${{ github.actor }}
66+
password: ${{ secrets.GITHUB_TOKEN }}
67+
68+
- name: Set up Docker Buildx
69+
uses: docker/setup-buildx-action@v3
70+
71+
- name: Docker metadata
72+
id: meta
73+
uses: docker/metadata-action@v5
74+
with:
75+
images: ${{ env.REGISTRY }}/${{ env.REPO }}/gramps-gtk
76+
tags: |
77+
type=raw,value=gramps60
78+
type=sha,prefix=gramps60-
79+
80+
- name: Build and push gramps-gtk
81+
uses: docker/build-push-action@v6
82+
with:
83+
context: .github/docker/gramps-gtk
84+
push: true
85+
tags: ${{ steps.meta.outputs.tags }}
86+
labels: ${{ steps.meta.outputs.labels }}
87+
cache-from: type=gha
88+
cache-to: type=gha,mode=max
89+
build-args: |
90+
BASE_IMAGE=${{ env.REGISTRY }}/${{ env.REPO }}/gramps-headless:gramps60

0 commit comments

Comments
 (0)