Skip to content

[ci-validate] windows gramps-series git-fallback (gramps61) #69

[ci-validate] windows gramps-series git-fallback (gramps61)

[ci-validate] windows gramps-series git-fallback (gramps61) #69

Workflow file for this run

name: CI
# Branch-neutral workflow: the image tag, make.py argument, and Gramps
# series pin are all derived from the branch ref at runtime, so the same
# file runs unchanged on maintenance/gramps60, maintenance/gramps61, and
# any future maintenance/grampsNN branch.
#
# NOTE for maintainers — first push to a new maintenance branch: the
# corresponding gramps-ci:<suffix> image does not exist in GHCR yet, so
# the container jobs in this workflow will fail at "Initialize containers"
# on the very first run. The companion docker-build.yml workflow fires
# on the same push and builds/pushes the image (~5 min cold). After it
# finishes, re-run the failed CI jobs (Actions tab → this run → "Re-run
# failed jobs") and they will pull the now-existing image. This race
# happens only on initial branch creation — every subsequent push to
# that branch finds the image already in GHCR.
#
# Full operational runbook (GHCR visibility, PyPI-release transitions,
# diagnostic log markers, etc.): .github/CI-MAINTAINER.md
on:
push:
branches: [maintenance/gramps**]
pull_request:
branches: [maintenance/gramps**]
jobs:
# -----------------------------------------------------------------
# Setup — derive the branch suffix (gramps60 / gramps61 / …) from
# the ref. On push events github.ref_name is the branch being
# pushed; on pull_request events github.base_ref is the target
# branch. Either way the suffix is what follows "maintenance/".
# -----------------------------------------------------------------
setup:
name: Setup
runs-on: ubuntu-latest
outputs:
branch_suffix: ${{ steps.compute.outputs.branch_suffix }}
ci_image: ${{ steps.compute.outputs.ci_image }}
steps:
- id: compute
shell: bash
run: |
ref="${{ github.base_ref || github.ref_name }}"
suffix="${ref#maintenance/}"
case "$suffix" in
gramps[0-9][0-9]) ;;
*) echo "::error::unexpected ref '$ref' (suffix '$suffix')"; exit 1 ;;
esac
echo "branch_suffix=$suffix" >> "$GITHUB_OUTPUT"
echo "ci_image=ghcr.io/${{ github.repository }}/gramps-ci:$suffix" >> "$GITHUB_OUTPUT"
# -----------------------------------------------------------------
# Lint (ci container)
# -----------------------------------------------------------------
lint:
name: Lint
needs: setup
runs-on: ubuntu-latest
container:
image: ${{ needs.setup.outputs.ci_image }}
steps:
- uses: actions/checkout@v4
- name: Run ruff (syntax and import errors only)
# Skip addon directories whose every register() in .gpr.py sets
# include_in_listing=False — those addons are not built or released
# by make.py, so CI does not gate on their lint state (per Gary
# Griffin's request on PR #820). To re-enable lint gating for an
# addon, set include_in_listing=True on at least one register()
# call in its descriptor (or remove the field — Gramps' default
# is True). Repeated inline rather than centralised so each job
# step stays self-contained.
shell: bash
run: |
is_active() {
local addon="$1" g
for g in "$addon"/*.gpr.py; do
[ -f "$g" ] || continue
grep -qE 'include_in_listing[[:space:]]*=[[:space:]]*True' "$g" && return 0
grep -qE 'include_in_listing[[:space:]]*=' "$g" || return 0
done
return 1
}
excludes=""
for d in */; do
d="${d%/}"
ls "$d"/*.gpr.py >/dev/null 2>&1 || continue
is_active "$d" || excludes="$excludes --exclude=$d"
done
ruff check --select=E9,F63,F7,F82 --no-fix --exclude='*.gpr.py' $excludes .
- name: Check trailing whitespace in Python files
run: |
# Use PCRE (-P): in BRE/ERE the bracket expression [ \t] is the
# set { space, backslash, 't' } — git grep matches anything ending
# in 't', not just whitespace. -P makes \t a tab.
if git --no-pager grep --color -n --full-name -P '[ \t]+$' -- '*.py'; then
echo "::error::Trailing whitespace found in Python files"
exit 1
fi
# -----------------------------------------------------------------
# Addon structure (bare runner — just bash, no deps needed)
# -----------------------------------------------------------------
addon-structure:
name: Addon Structure
runs-on: ubuntu-latest
# Non-blocking until the four addons missing po/template.pot are fixed
# in a follow-up PR. Flip this off in that PR.
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Check all listed addons have po/template.pot
# Skip include_in_listing=False addons (see lint job for rationale).
shell: bash
run: |
is_active() {
local addon="$1" g
for g in "$addon"/*.gpr.py; do
[ -f "$g" ] || continue
grep -qE 'include_in_listing[[:space:]]*=[[:space:]]*True' "$g" && return 0
grep -qE 'include_in_listing[[:space:]]*=' "$g" || return 0
done
return 1
}
failed=0
for gpr in */*.gpr.py; do
addon_dir="$(dirname "$gpr")"
is_active "$addon_dir" || continue
if [ ! -d "$addon_dir/po" ]; then
echo "::error::$addon_dir is missing po/ directory"
failed=1
elif [ ! -f "$addon_dir/po/template.pot" ]; then
echo "::error::$addon_dir is missing po/template.pot"
failed=1
fi
done
if [ "$failed" -eq 0 ]; then
echo "All listed addons have po/template.pot"
fi
exit $failed
# -----------------------------------------------------------------
# Compile check (ci container)
# -----------------------------------------------------------------
compile-check:
name: Compile Check
needs: setup
runs-on: ubuntu-latest
container:
image: ${{ needs.setup.outputs.ci_image }}
steps:
- uses: actions/checkout@v4
- name: Compile all Python files in listed addons (excluding .gpr.py)
# Skip include_in_listing=False addons (see lint job for rationale).
shell: bash
run: |
is_active() {
local addon="$1" g
for g in "$addon"/*.gpr.py; do
[ -f "$g" ] || continue
grep -qE 'include_in_listing[[:space:]]*=[[:space:]]*True' "$g" && return 0
grep -qE 'include_in_listing[[:space:]]*=' "$g" || return 0
done
return 1
}
skipped=""
for d in */; do
d="${d%/}"
ls "$d"/*.gpr.py >/dev/null 2>&1 || continue
is_active "$d" || skipped="$skipped $d"
done
failed=0
while IFS= read -r f; do
skip=0
for s in $skipped; do
case "$f" in ./$s/*) skip=1; break;; esac
done
[ "$skip" = 1 ] && continue
if ! python3 -m py_compile "$f" 2>&1; then
failed=1
fi
done < <(find . -name '*.py' ! -name '*.gpr.py' ! -path './.git/*' ! -path '*/__pycache__/*')
exit $failed
# -----------------------------------------------------------------
# Unit tests — Linux (ci container)
# -----------------------------------------------------------------
unit-test-linux:
name: Unit Tests (Linux)
needs: setup
runs-on: ubuntu-latest
container:
image: ${{ needs.setup.outputs.ci_image }}
steps:
- uses: actions/checkout@v4
- name: Install addon system deps (derived from requires_gi / requires_exe)
# System deps (GI typelibs, executables) are not pip-installable and
# gramps only *checks* them, so the image cannot bake them generically
# (its build context excludes addons-source). Derive the apt set from
# every .gpr.py via the single-source map and install it — the container
# runs as root. Mirrors the requires_mod derivation below.
shell: bash
run: |
pkgs=$(python3 .github/scripts/addon_system_deps.py --platform apt)
if [ -n "$pkgs" ]; then
echo "→ addon system deps (apt): $pkgs"
apt-get update
apt-get install -y --no-install-recommends $pkgs
else
echo "no requires_gi / requires_exe declarations found"
fi
- name: Validate addon system deps are mapped
# Every requires_gi / requires_exe an addon declares must have an entry
# in addon_system_deps.py, so the install list never silently drifts
# from what addons declare (the system-dep analogue of the requires_mod
# find_spec gate below).
shell: bash
run: |
python3 .github/scripts/addon_system_deps.py --unmapped . || {
echo "::error::Addon(s) declare requires_gi/requires_exe with no entry in .github/scripts/addon_system_deps.py — add a mapping row."
exit 1
}
- name: Install addon runtime deps (derived from requires_mod)
# Auto-derive the union of requires_mod across every .gpr.py in
# the repo. Mirrors Gramps' Addon Manager install path
# (gramps/gui/plug/_windows.py __on_install_clicked → req.install →
# gen/utils/requirements.py). Keeps .gpr.py files as the single
# source of truth for addon deps — no parallel list to maintain
# in the image or workflow. Best-effort: a package needing exotic
# system deps (pygraphviz → graphviz-dev, psycopg2 → libpq-dev)
# may fail here; the affected addon's tests will skip or fail in
# isolation without blocking the rest.
shell: bash
run: |
addon_mods=$(python3 - <<'PY'
import ast, glob, re
pat = re.compile(r"requires_mod\s*=\s*(\[[^\]]*\])")
mods = set()
for f in glob.glob("*/*.gpr.py"):
try:
text = open(f, encoding="utf-8").read()
except OSError:
continue
for m in pat.finditer(text):
try:
mods.update(ast.literal_eval(m.group(1)))
except (ValueError, SyntaxError):
pass
print(" ".join(sorted(mods)))
PY
)
if [ -n "$addon_mods" ]; then
echo "→ addon deps: $addon_mods"
for mod in $addon_mods; do
pip install "$mod" || echo "× $mod failed to install (continuing)"
done
else
echo "no requires_mod declarations found"
fi
- name: Validate requires_mod names against Gramps' dep gate
# Cross-check: every requires_mod entry that pip successfully
# installed in the previous step must also resolve via
# find_spec(), since that is what Gramps' Addon Manager calls
# (gramps/gen/utils/requirements.py:check_mod). A name that
# pip-installs but does not import is a declaration bug — e.g.
# requires_mod=["Pillow"] when the importable name is "PIL".
# Pip-install failures upstream are skipped: those are
# system-dep / image gaps, not PR-caused.
shell: bash
run: |
python3 - <<'PY'
import ast, glob, re, subprocess, sys
from importlib.util import find_spec
pat = re.compile(r"requires_mod\s*=\s*(\[[^\]]*\])")
names = set()
for f in glob.glob("*/*.gpr.py"):
try:
text = open(f, encoding="utf-8").read()
except OSError:
continue
for m in pat.finditer(text):
try:
names.update(ast.literal_eval(m.group(1)))
except (ValueError, SyntaxError):
pass
bad = []
for name in sorted(names):
installed = subprocess.run(
[sys.executable, "-m", "pip", "show", name],
capture_output=True,
).returncode == 0
if not installed:
print(f"~ {name} (pip-install failed earlier, skipping)")
continue
if find_spec(name) is None:
bad.append(name)
print(f"x {name} (pip-installed but find_spec returned None)")
else:
print(f"ok {name}")
if bad:
print()
print(f"::error::Wrong requires_mod names: {bad}")
print("These pip-install but are not importable. requires_mod is")
print("consumed by gramps' check_mod() via find_spec(), so the")
print("importable module name is required (e.g. 'PIL', not 'Pillow').")
sys.exit(1)
PY
- name: Run per-addon unit tests
# Filename convention (all OSes):
# test_*.py — general (any OS)
# test_linux_*.py — Linux-only
# test_windows_*.py — Windows-only
# test_integration_*.py — Linux-only, full-pipeline/DB-backed
# The Linux job runs test_*.py except the Windows-only and
# integration buckets. Integration tests run in their own job.
#
# shell: bash — the container's default shell is /bin/sh
# (dash on python:3.12-slim), which does not support the
# ${var//pattern/repl} and ${var%.py} parameter expansions
# used below.
shell: bash
env:
PYTHONPATH: .
run: |
is_active() {
local addon="$1" g
for g in "$addon"/*.gpr.py; do
[ -f "$g" ] || continue
grep -qE 'include_in_listing[[:space:]]*=[[:space:]]*True' "$g" && return 0
grep -qE 'include_in_listing[[:space:]]*=' "$g" || return 0
done
return 1
}
modules=""
for f in */tests/test_*.py; do
[ -f "$f" ] || continue
addon="${f%%/*}"
is_active "$addon" || continue
case "$(basename "$f")" in
test_integration*) continue ;;
test_windows_*) continue ;;
esac
case "$f" in
Sqlite/tests/test_sqlite.py) continue ;;
esac
mod="${f%.py}"
mod="${mod//\//.}"
modules="$modules $mod"
done
if [ -n "$modules" ]; then
echo "Running unit tests:$modules"
# xvfb-run: some addons create a Gtk style context at import and
# need a display (else a hard Gtk-ERROR abort, not a clean skip).
# run_addon_tests.py: pins the GI versions like gramps' launcher
# (so gramps.gui imports load GTK 3, no PyGIWarning) and fails a
# wholly-skipped module unless the addon's deps are unavailable on
# this platform.
xvfb-run -a --server-args="-screen 0 1920x1080x24" \
python3 .github/scripts/run_addon_tests.py --platform apt --root . $modules
else
echo "No per-addon unit test modules found"
fi
# -----------------------------------------------------------------
# Unit tests — Windows (conda-forge: bundles PyGObject + GTK + Gramps)
# -----------------------------------------------------------------
unit-test-windows:
name: Unit Tests (Windows)
needs: setup
runs-on: windows-latest
defaults:
run:
shell: bash -el {0}
steps:
- uses: actions/checkout@v4
- name: Set up Miniforge
uses: conda-incubator/setup-miniconda@v3
with:
miniforge-version: latest
activate-environment: addons-ci
environment-file: .github/environment.yml
use-mamba: true
- name: Verify environment
run: |
mamba info
mamba list | head -30
python -c "import gramps, gi; print('deps OK')"
- name: Match gramps to branch series (git-install if conda-forge lacks it)
# conda-forge has no gramps 6.1 yet, so environment.yml's "gramps<6.1"
# pin resolves to the newest in range (6.0.x). On a maintenance/gramps61
# (or later) branch that means addons would be tested against the WRONG
# gramps — a silent version mismatch (e.g. gramps61 addon code exercised
# against gramps 6.0.8's DB API). When the installed series doesn't match
# the branch, install the matching gramps from
# gramps-project/gramps@maintenance/grampsNN. This mirrors the Linux
# gramps-ci image's "PyPI-first, git-fallback" strategy
# (.github/docker/gramps-ci/Dockerfile) so both lanes test addons against
# the SAME gramps the branch targets. On Windows the wheel build only
# runs build_trans + build_intl (build_man is skipped on win32), so the
# one extra build tool needed is msgfmt, supplied by conda-forge gettext.
run: |
suffix="${{ needs.setup.outputs.branch_suffix }}" # e.g. gramps61
digits="${suffix#gramps}" # e.g. 61
want="${digits:0:1}.${digits:1}" # e.g. 6.1
have="$(python -c 'from gramps.version import major_version; print(major_version)')"
echo "branch series=$want installed gramps=$have"
if [ "$want" = "$have" ]; then
echo "conda-forge gramps already matches the branch series; no git-install needed"
else
echo "::warning::conda gramps $have != branch series $want; installing gramps from maintenance/$suffix"
# gettext-tools (not the plain "gettext" runtime) is the conda-forge
# package that ships the msgfmt executable the build hook shells out
# to (build_trans + build_intl).
mamba install -y -c conda-forge gettext-tools
command -v msgfmt >/dev/null || { echo "::error::msgfmt not on PATH after installing gettext-tools"; exit 1; }
# </dev/null: build_hook.py prompts via input() if a .po fails to
# compile; closed stdin turns that into a fast EOFError abort rather
# than a CI hang.
pip install --no-cache-dir --force-reinstall --no-deps \
"git+https://github.com/gramps-project/gramps.git@maintenance/$suffix" </dev/null
now="$(python -c 'from gramps.version import major_version; print(major_version)')"
echo "gramps after git-install: $now"
if [ "$now" != "$want" ]; then
echo "::error::git-install did not yield gramps $want (got $now)"; exit 1
fi
fi
- name: Install addon system deps (derived, conda-available subset)
# Only the conda-forge-available subset (e.g. graphviz). The GTK 3 addon
# GI libs (goocanvas/osm-gps-map/gexiv2) are NOT on conda-forge, so the
# map returns None for them on conda and they are not installed; addons
# needing them skip on Windows by necessity, which run_addon_tests
# tolerates (--platform conda).
run: |
pkgs=$(python .github/scripts/addon_system_deps.py --platform conda)
if [ -n "$pkgs" ]; then
echo "→ addon system deps (conda): $pkgs"
mamba install -y -c conda-forge $pkgs
else
echo "no conda-available addon system deps to install"
fi
- name: Install addon runtime deps (derived from requires_mod)
# See unit-test-linux for rationale. Uses `python` (conda-forge
# env) to match the surrounding Windows job style.
run: |
addon_mods=$(python - <<'PY'
import ast, glob, re
pat = re.compile(r"requires_mod\s*=\s*(\[[^\]]*\])")
mods = set()
for f in glob.glob("*/*.gpr.py"):
try:
text = open(f, encoding="utf-8").read()
except OSError:
continue
for m in pat.finditer(text):
try:
mods.update(ast.literal_eval(m.group(1)))
except (ValueError, SyntaxError):
pass
print(" ".join(sorted(mods)))
PY
)
if [ -n "$addon_mods" ]; then
echo "→ addon deps: $addon_mods"
for mod in $addon_mods; do
pip install "$mod" || echo "× $mod failed to install (continuing)"
done
else
echo "no requires_mod declarations found"
fi
- name: Validate requires_mod names against Gramps' dep gate
# See unit-test-linux for rationale. Uses `python` (conda-forge
# env) to match the surrounding Windows job style.
run: |
python - <<'PY'
import ast, glob, re, subprocess, sys
from importlib.util import find_spec
pat = re.compile(r"requires_mod\s*=\s*(\[[^\]]*\])")
names = set()
for f in glob.glob("*/*.gpr.py"):
try:
text = open(f, encoding="utf-8").read()
except OSError:
continue
for m in pat.finditer(text):
try:
names.update(ast.literal_eval(m.group(1)))
except (ValueError, SyntaxError):
pass
bad = []
for name in sorted(names):
installed = subprocess.run(
[sys.executable, "-m", "pip", "show", name],
capture_output=True,
).returncode == 0
if not installed:
print(f"~ {name} (pip-install failed earlier, skipping)")
continue
if find_spec(name) is None:
bad.append(name)
print(f"x {name} (pip-installed but find_spec returned None)")
else:
print(f"ok {name}")
if bad:
print()
print(f"::error::Wrong requires_mod names: {bad}")
print("These pip-install but are not importable. requires_mod is")
print("consumed by gramps' check_mod() via find_spec(), so the")
print("importable module name is required (e.g. 'PIL', not 'Pillow').")
sys.exit(1)
PY
- name: Run per-addon unit tests
# See filename-convention note in unit-test-linux. The Windows
# job runs test_*.py except test_linux_* and test_integration_*.
env:
PYTHONPATH: .
run: |
is_active() {
local addon="$1" g
for g in "$addon"/*.gpr.py; do
[ -f "$g" ] || continue
grep -qE 'include_in_listing[[:space:]]*=[[:space:]]*True' "$g" && return 0
grep -qE 'include_in_listing[[:space:]]*=' "$g" || return 0
done
return 1
}
modules=""
for f in */tests/test_*.py; do
[ -f "$f" ] || continue
addon="${f%%/*}"
is_active "$addon" || continue
case "$(basename "$f")" in
test_integration*) continue ;;
test_linux_*) continue ;;
esac
case "$f" in
Sqlite/tests/test_sqlite.py) continue ;;
esac
mod="${f%.py}"
mod="${mod//\//.}"
modules="$modules $mod"
done
if [ -n "$modules" ]; then
echo "Running unit tests:$modules"
# No xvfb on Windows (GTK renders natively). run_addon_tests pins
# the GI versions and tolerates addons whose GI deps are not on
# conda-forge (they skip here by platform necessity).
python .github/scripts/run_addon_tests.py --platform conda --root . $modules
else
echo "No per-addon unit test modules found"
fi
# -----------------------------------------------------------------
# Integration tests — Gramps (ci container, xvfb available)
# -----------------------------------------------------------------
integration-test:
name: Integration Tests (Gramps)
runs-on: ubuntu-latest
needs: [setup, unit-test-linux]
container:
image: ${{ needs.setup.outputs.ci_image }}
options: --init
steps:
- uses: actions/checkout@v4
- name: Install addon system deps (derived from requires_gi / requires_exe)
# Same as unit-test-linux: derive the apt set from the single-source
# map and install it (container runs as root). The plugin registration
# test subprocess-loads each addon module, so the GI typelibs those
# modules import must be present here too. (Mapping is drift-guarded in
# unit-test-linux, which this job needs:, so no duplicate gate here.)
shell: bash
run: |
pkgs=$(python3 .github/scripts/addon_system_deps.py --platform apt)
if [ -n "$pkgs" ]; then
echo "→ addon system deps (apt): $pkgs"
apt-get update
apt-get install -y --no-install-recommends $pkgs
fi
- name: Install addon runtime deps (derived from requires_mod)
# See unit-test-linux for rationale. The plugin registration test
# subprocess-loads each addon's module, which imports its
# requires_mod packages.
shell: bash
run: |
addon_mods=$(python3 - <<'PY'
import ast, glob, re
pat = re.compile(r"requires_mod\s*=\s*(\[[^\]]*\])")
mods = set()
for f in glob.glob("*/*.gpr.py"):
try:
text = open(f, encoding="utf-8").read()
except OSError:
continue
for m in pat.finditer(text):
try:
mods.update(ast.literal_eval(m.group(1)))
except (ValueError, SyntaxError):
pass
print(" ".join(sorted(mods)))
PY
)
if [ -n "$addon_mods" ]; then
echo "→ addon deps: $addon_mods"
for mod in $addon_mods; do
pip install "$mod" || echo "× $mod failed to install (continuing)"
done
else
echo "no requires_mod declarations found"
fi
- name: Validate requires_mod names against Gramps' dep gate
# See unit-test-linux for rationale.
shell: bash
run: |
python3 - <<'PY'
import ast, glob, re, subprocess, sys
from importlib.util import find_spec
pat = re.compile(r"requires_mod\s*=\s*(\[[^\]]*\])")
names = set()
for f in glob.glob("*/*.gpr.py"):
try:
text = open(f, encoding="utf-8").read()
except OSError:
continue
for m in pat.finditer(text):
try:
names.update(ast.literal_eval(m.group(1)))
except (ValueError, SyntaxError):
pass
bad = []
for name in sorted(names):
installed = subprocess.run(
[sys.executable, "-m", "pip", "show", name],
capture_output=True,
).returncode == 0
if not installed:
print(f"~ {name} (pip-install failed earlier, skipping)")
continue
if find_spec(name) is None:
bad.append(name)
print(f"x {name} (pip-installed but find_spec returned None)")
else:
print(f"ok {name}")
if bad:
print()
print(f"::error::Wrong requires_mod names: {bad}")
print("These pip-install but are not importable. requires_mod is")
print("consumed by gramps' check_mod() via find_spec(), so the")
print("importable module name is required (e.g. 'PIL', not 'Pillow').")
sys.exit(1)
PY
- name: Run plugin registration tests
# shell: bash for consistency with the surrounding steps; the
# current command uses no bashisms, but keeps this block safe
# against future edits. Container default is /bin/sh → dash.
#
# gi_bootstrap on PYTHONPATH pins the GI versions (like the gramps GUI
# launcher) for this process AND the addon-module subprocesses this test
# spawns, so gramps.gui imports load GTK 3 without a PyGIWarning.
#
# NOT run under xvfb: this test only *loads* (imports) addon modules in
# subprocesses and tolerates load failures; it does not render. Giving it
# a display made an addon load hang on the (absent) AT-SPI accessibility
# bus until the per-load timeout. Imports that build a Gtk style context
# are exercised under xvfb in the unit/integration test runs instead.
shell: bash
env:
PYTHONPATH: .github/scripts/gi_bootstrap:.
run: |
python3 -m unittest discover -s tests -p "test_*.py" -t . -v
- name: Run per-addon integration tests
# shell: bash — see unit-test-linux for rationale; the
# ${var//pattern/repl} and ${var%.py} expansions below are
# bash-only.
shell: bash
env:
PYTHONPATH: .
run: |
is_active() {
local addon="$1" g
for g in "$addon"/*.gpr.py; do
[ -f "$g" ] || continue
grep -qE 'include_in_listing[[:space:]]*=[[:space:]]*True' "$g" && return 0
grep -qE 'include_in_listing[[:space:]]*=' "$g" || return 0
done
return 1
}
modules=""
for f in */tests/test_integration*.py; do
[ -f "$f" ] || continue
addon="${f%%/*}"
is_active "$addon" || continue
mod="${f%.py}"
mod="${mod//\//.}"
modules="$modules $mod"
done
if [ -n "$modules" ]; then
echo "Running per-addon integration tests:$modules"
xvfb-run -a --server-args="-screen 0 1920x1080x24" \
python3 .github/scripts/run_addon_tests.py --platform apt --root . $modules
else
echo "No per-addon integration test modules found"
fi
# -----------------------------------------------------------------
# Build (ci container)
# -----------------------------------------------------------------
build:
name: Build
needs: setup
runs-on: ubuntu-latest
container:
image: ${{ needs.setup.outputs.ci_image }}
steps:
- uses: actions/checkout@v4
- name: Determine GRAMPSPATH
id: gramps-path
run: |
GPATH=$(python3 -c "import gramps, os; print(os.path.dirname(os.path.dirname(gramps.__file__)))")
echo "path=$GPATH" >> "$GITHUB_OUTPUT"
- name: Build all addons
env:
GRAMPSPATH: ${{ steps.gramps-path.outputs.path }}
run: |
mkdir -p ../download
python3 make.py "${{ needs.setup.outputs.branch_suffix }}" build all