Skip to content

Commit b23ca4d

Browse files
Sync addons-source@maintenance/gramps60 with upstream/gramps-project (2026-07-29)
2 parents 93bf864 + 153d65b commit b23ca4d

33 files changed

Lines changed: 5834 additions & 478 deletions

AGENTS.md

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
# Agent Guidelines for gramps-project/addons-source
2+
3+
This document specifies rules and conventions that agents should follow when
4+
making changes to this repository.
5+
6+
For the normative addon rules — MUST / SHOULD / MAY, with the origin of
7+
each rule (upstream PR, maintainer ruling, Mantis id) cited inline — see
8+
[docs/addon-development/16-guidelines.md](docs/addon-development/16-guidelines.md)
9+
from the Addon Development manual (PR 994). This document covers the
10+
hands-on agent workflow around those rules.
11+
12+
## Repository Overview
13+
14+
`addons-source` holds the source code for third-party Gramps addons —
15+
gramplets, views, tools, reports, importers/exporters, filter rules, database
16+
backends, and other plugins that are not part of Gramps core. It is one of
17+
three tightly-linked repositories:
18+
19+
- **`gramps`** — Gramps core (the application these addons plug into).
20+
- **`addons-source`** (this repo) — addon source code, developed here.
21+
- **`addons`** — built `.addon.tgz` packages and listing JSON consumed by the
22+
in-app Plugin Manager. Populated from this repo via `make.py`; not edited
23+
by hand except by the maintainer doing a release build.
24+
25+
There is no unified build or CI pipeline for the whole repo (the checked-in
26+
`.travis.yml` is stale/unused) — each addon is largely independent.
27+
28+
## Branch Model
29+
30+
Branches are per Gramps major or feature release, e.g. `maintenance/gramps60`,
31+
`maintenance/gramps61` — not one branch per patch release. **Almost all addon
32+
work should target the maintenance branch matching the current released
33+
Gramps version.** A `master` branch exists but is not currently used for
34+
addon work.
35+
36+
Pick the branch carefully:
37+
- When creating a new addon or fixing an existing one, branch from
38+
`origin/maintenance/gramps6X` (the current release line), not from a local
39+
tracking branch that may carry unrelated WIP commits.
40+
- When opening the PR, target that same branch on
41+
`gramps-project/addons-source`.
42+
- Before pushing, sync/rebase against upstream (`git pull --rebase`) so the
43+
PR applies cleanly.
44+
45+
All PRs must come from a personal fork, never from a branch pushed directly
46+
to `gramps-project/addons-source` (see `CONTRIBUTING.md`, "Commit Your
47+
Changes": "you want to put your changes into _your_ fork, _not_ the upstream
48+
`gramps-project/addons-source` repository").
49+
50+
Weblate translation PRs are the one exception where commits must **not** be
51+
squashed on merge; everything else can be merged/rebased normally.
52+
53+
## Repository Layout
54+
55+
Each addon is a single top-level directory, CamelCase-named to match its
56+
Python import name (e.g. `FilterRules/`, `DataEntryGramplet/`). A directory
57+
typically contains:
58+
59+
- `AddonName.py` — the implementation.
60+
- `AddonName.gpr.py` — the Gramps plugin registration file (required; see
61+
below).
62+
- `po/` — translation `.po` files, managed by `make.py`.
63+
- `tests/` — optional unit tests (see Testing below).
64+
- `MANIFEST` — optional; lists extra files (docs, data) to include in the
65+
built package beyond the default `*.py`, `*.glade`, `*.xml`, `*.txt`,
66+
`locale/*/LC_MESSAGES/*.mo`.
67+
- `locale/` — generated `.mo` files; not something you hand-edit.
68+
69+
Addon directories generally have **no `__init__.py`** (they rely on Python 3
70+
namespace packages) so that Gramps can add each one to `sys.path` at load
71+
time and other addons can `import AddonName` directly.
72+
73+
## Commands
74+
75+
Addon packaging/translation tasks go through `make.py`, run from the repo
76+
root. Point `GRAMPSPATH` at wherever *your* local Gramps checkout lives (it
77+
defaults to `../../..`, i.e. it assumes `make.py` is being invoked from one
78+
level inside a sibling-layout workspace — don't rely on that default, always
79+
set it explicitly), and make sure that checkout has the matching branch
80+
checked out too (e.g. `maintenance/gramps61` in core when building against
81+
`gramps61` here). Setting `LANGUAGE=en_US.UTF-8` is no longer required on
82+
Gramps v6.0 and later:
83+
84+
```bash
85+
GRAMPSPATH=/path/to/your/gramps python3 make.py gramps61 build AddonDirectory
86+
```
87+
88+
Common subcommands (first positional arg is the branch/version tag, e.g.
89+
`gramps61`):
90+
91+
- `init AddonDirectory [lang]` — scaffold dirs / `.pot` for a new addon, or
92+
an initial `po/<lang>-local.po`.
93+
- `update AddonDirectory <lang>` — refresh a language `.po` from the `.pot`.
94+
- `compile [AddonDirectory|all]` — compile `.po``.mo`.
95+
- `build [AddonDirectory|all]` — produce the downloadable `.addon.tgz`.
96+
- `listing [AddonDirectory|all]` — generate/update the Plugin Manager
97+
listing JSON.
98+
- `clean AddonDirectory` — strip generated files (`locale/`, `*.pot`, etc.)
99+
before committing.
100+
- `manifest-check` — validate `MANIFEST` files.
101+
- `as-needed` — build/list/clean only what's out of date, repo-wide.
102+
103+
Unlike `GRAMPSPATH`, the `build`/`listing`/`check` subcommands write to a
104+
**hardcoded relative path**, `../addons/<version>/...` — this isn't a
105+
convention, it's how `make.py` itself resolves the output location, so it
106+
only works if you've cloned the `addons` repo as a sibling of
107+
`addons-source` (see `MAINTAINERS.md` for the recommended three-repo
108+
workspace layout: `gramps` / `addons` / `addons-source` side by side). You
109+
don't need that sibling checkout at all unless you're packaging/publishing
110+
an addon.
111+
112+
You do not need `make.py` just to write/test an addon's Python code — only
113+
when packaging, translating, or updating the listing.
114+
115+
## Testing
116+
117+
Not every addon has tests, but when adding or fixing one, add regression
118+
tests under `AddonName/tests/`, mirroring existing examples (`Form/tests/`,
119+
`DataEntryGramplet/tests/`, `Sqlite/tests/`, `FilterRules/tests/`).
120+
121+
Conventions here differ from Gramps core:
122+
123+
- Test files are named `test_*.py` (pytest-style), **not** the `*_test.py`
124+
suffix core Gramps uses — `unittest discover` here is invoked per-addon or
125+
from the repo root without the core `-p "*_test.py"` pattern.
126+
- Still use the `unittest` framework (`unittest.TestCase`), not `pytest`
127+
APIs, even though filenames follow the `test_*.py` convention.
128+
- `AddonName/tests/__init__.py` should be **empty**. Do not add
129+
`gi.require_version(...)` pinning there or in individual test modules —
130+
see GTK/GDK below.
131+
- Because the addon directory has no `__init__.py`, test modules need a
132+
`sys.path` hack to import it as a namespace package:
133+
134+
```python
135+
ADDON_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
136+
if ADDON_DIR not in sys.path:
137+
sys.path.insert(0, ADDON_DIR)
138+
```
139+
140+
then `from AddonName.addonmodule import SomeClass`.
141+
- Build test data programmatically against a real (temp-dir) database rather
142+
than relying on fixtures that don't exist in this repo:
143+
144+
```python
145+
from gramps.gen.db.utils import make_database
146+
from gramps.gen.db import DbTxn
147+
148+
db = make_database("sqlite")
149+
db.load(tempfile.mkdtemp(prefix="myaddon_"))
150+
with DbTxn("build test db", db) as txn:
151+
...
152+
```
153+
- If a test needs `gramps.gen.filters.CustomFilters`, call
154+
`reload_custom_filters()` **before** importing the `CustomFilters` name
155+
from `gramps.gen.filters` — that function rebinds the module-level global
156+
rather than mutating it in place, so importing the name first leaves you
157+
with a stale `None`.
158+
159+
### GTK / GDK version pinning
160+
161+
The repo-root `tests/__init__.py` pins `gi.require_version("Gtk", "3.0")` and
162+
`gi.require_version("Gdk", "3.0")` for the whole test suite (added in PR
163+
#950). `gi.require_version` sets process-global state, so **individual addon
164+
test files should not re-pin GTK/GDK** — that's now redundant. A new test
165+
module that imports something pulling in `gramps.gui.*` (which happens at
166+
import time for most GUI-facing addons) only needs a guard for hosts with no
167+
PyGObject at all:
168+
169+
```python
170+
try:
171+
import gi
172+
except ImportError as err:
173+
raise unittest.SkipTest("PyGObject not available: %s" % err)
174+
```
175+
176+
Do not add `gi.require_version(...)` calls back into new test files — if you
177+
see them in an addon you're touching, they're safe to remove as redundant
178+
(see the `Themes` addon's `tests/__init__.py` cleanup for precedent), but
179+
don't remove them from files you aren't otherwise changing.
180+
181+
### Running tests
182+
183+
```bash
184+
export GRAMPS_RESOURCES=/path/to/gramps/build/share # built Gramps checkout
185+
export GDK_BACKEND=-
186+
python3 -m unittest AddonName.tests.test_something -v
187+
```
188+
189+
Run from the `addons-source` repo root so the addon's namespace package
190+
resolves.
191+
192+
## Windows Development
193+
194+
Per `CONTRIBUTING.md`, addons here are developed and tested primarily on
195+
Linux; Windows and macOS are possible but far less thoroughly reviewed.
196+
"Windows" itself covers two different things agents should not conflate:
197+
198+
- **MSYS2** — the POSIX-like shell/toolchain environment Gramps's own
199+
Windows installer is built with. A shell running inside MSYS2 behaves like
200+
a Linux shell (`bash`, POSIX paths) and can run this repo's `make.py` and
201+
`unittest` invocations normally.
202+
- **Native Windows** — a bare `cmd.exe` or PowerShell host with no MSYS2 or
203+
WSL layer underneath. The commands and paths used elsewhere in this
204+
document (`make.py`, `python3 -m unittest ...`, the `GRAMPSPATH` /
205+
`GRAMPS_RESOURCES` env vars) assume a POSIX shell and are not verified
206+
against native `cmd`/PowerShell.
207+
208+
If you are running as an agent on a native Windows host:
209+
210+
- Reading, writing, and refactoring addon Python code is fine — the code
211+
itself is portable.
212+
- Do not run this repo's tests or `make.py` directly in `cmd`/PowerShell and
213+
report the result as a real pass/fail — the tooling isn't validated there,
214+
so a failure may be environmental noise rather than an actual regression.
215+
- Ask the user to run the same command inside an MSYS2 shell, WSL, or a
216+
Linux container/VM and share the output, rather than guessing at the
217+
result yourself.
218+
219+
If you're already inside MSYS2 or WSL, treat it as a POSIX shell and follow
220+
the rest of this document (Commands, Testing) as written.
221+
222+
## Code Style
223+
224+
- Every new `.py` file needs the same GPL-2.0-or-later header with copyright
225+
used throughout Gramps core.
226+
- Group imports under comment-banner sections (`Standard Python modules`,
227+
`GTK/Gnome modules`, `Gramps modules`), matching the style already used
228+
across this repo (e.g. `FilterRules/isfamilyfiltermatchevent.py`).
229+
- [Black](https://black.readthedocs.io/) formatting is not currently required
230+
for addons — there's no checked-in CI workflow enforcing it here, though
231+
that may change in the future. The existing codebase is largely
232+
Black-formatted, so running `black <file>` on files you're already
233+
touching is a reasonable default.
234+
- Docstrings: concise; full Sphinx `:param:`/`:returns:` style only when the
235+
parameters aren't already obvious from the signature.
236+
237+
## Internationalization
238+
239+
Addons manage their own translations, separately from Gramps core, though
240+
both go through the same Weblate instance: strings already translated in
241+
Gramps core are matched first, so those strings are excluded from the
242+
Weblate _Addons_ component and don't need separate translation there. At the
243+
top of an addon's main module:
244+
245+
```python
246+
from gramps.gen.const import GRAMPS_LOCALE as glocale
247+
248+
try:
249+
_trans = glocale.get_addon_translator(__file__)
250+
except ValueError:
251+
_trans = glocale.translation
252+
_ = _trans.gettext
253+
```
254+
255+
(`get_addon_translator` raises `ValueError` if the addon has no
256+
`locale/` translations yet — fall back to the core translator rather than
257+
letting the import crash.) Use `ngettext(singular, plural, n)` instead of a
258+
single `_()` call for any string that counts a noun, for the same reason as
259+
Gramps core: many languages have plural rules English doesn't, and only
260+
`ngettext` lets gettext apply the target language's actual rule.
261+
262+
## Plugin Registration (`.gpr.py`)
263+
264+
Every addon needs an `AddonName.gpr.py` alongside `AddonName.py`:
265+
266+
```python
267+
register(
268+
RULE, # or TOOL, GRAMPLET, REPORT, VIEW, GENERAL, IMPORT, EXPORT, ...
269+
id="uniqueid",
270+
name=_("Human-Readable Name"),
271+
description=_("What it does."),
272+
version="1.0.0",
273+
gramps_target_version="6.1",
274+
status=STABLE, # or UNSTABLE
275+
fname="AddonName.py",
276+
authors=["Author Name"],
277+
authors_email=["author@example.com"],
278+
maintainers=["Maintainer Name"],
279+
maintainers_email=["maintainer@example.com"],
280+
help_url="Addon:WikiPageName",
281+
... # PTYPE-specific attributes, e.g. ruleclass/namespace for RULE
282+
)
283+
```
284+
285+
`gramps_target_version` and `version` are required. `help_url` should point
286+
at the addon's Gramps wiki page (`Addon:PageName`), not a GitHub URL.
287+
`maintainers`/`maintainers_email` identify who currently maintains the
288+
addon and may differ from `authors`/`authors_email` (the original author)
289+
once upkeep passes to someone else.
290+
291+
> **Do not manually change `version` in a PR.** The `MAJOR.MINOR.PATCH`
292+
> version in `.gpr.py` is bumped automatically by the `addons` repo's
293+
> packaging build (`make.py`) when a change is released — the patch
294+
> component increments on its own. Hand-editing it in a source PR just
295+
> creates spurious diffs and can conflict with what the release build
296+
> assigns; leave it as-is unless you're intentionally doing a MAJOR/MINOR
297+
> bump for a breaking addon change.
298+
299+
## Bug Reports and Commit Messages
300+
301+
Bugs against addons are tracked in two places: the shared
302+
[Gramps Mantis BT](https://gramps-project.org/bugs/view_all_bug_page.php)
303+
and, informally, the [Gramps Discourse forum](https://gramps.discourse.group/)
304+
— many addon issues start as a discourse thread rather than a Mantis report.
305+
Unlike Gramps core, this repo has no automated changelog tooling parsing
306+
commit messages, so there's no required `Fixes #N` keyword syntax — just
307+
describe *why* the change was made, and link whatever report (Mantis bug or
308+
discourse URL) motivated it if one exists. Don't invent a Mantis bug number
309+
if you only have a discourse link or user report — link what you actually
310+
have.

0 commit comments

Comments
 (0)