Changes

From Gramps
Synced from repo@41d611f via publish.py
<!--
This page covers the source-to-distribution PIPELINE only. The normative
rules for contributors (branch targeting, version-field discipline, PR
body shape, Mantis trailers, test-with-fix requirement) live in
[[16-guidelines]] so there is one source of truth — link there, don't restate.

Authoritative scraped source:
../04 - Technical Documentation/addons-development.md (sections
"Develop your addon" through "List your addon in the Gramps Plugin
Manager"). The make.py target name in the scrape is shown as
`gramps``` because the wiki template literal "{{Stable_branch}}"
didn't render; the real target is gramps60 for our maintenance
branch and gramps61 for master.
-->

== Overview ==

From &quot;works on my machine&quot; to &quot;users can install it from the addon manager.&quot; This chapter is the source-to-distribution pipeline: how <code>addons-source</code> becomes a <code>.addon.tgz</code> in <code>addons</code>, how the in-app addon manager picks it up, and what to send upstream.

The normative ''rules'' a submission must satisfy (branch targeting, version-field discipline, PR body shape, Mantis trailers) live in [15-guidelinesN-guidelines.md). This page covers the ''workflow'' — what to run, what files appear, where they end up.

== The three repositories ==

[[File:packaging-pipeline.svg|Fig. 1 — The source-to-distribution pipeline. Authors edit in <code>addons-source/</code>; <code>make.py build</code> packages each addon into <code>addons/grampsXY/download/&lt;Addon&gt;.addon.tgz</code> and <code>make.py listing</code> refreshes <code>addons/grampsXY/listings/*.json</code>; the in-app addon manager fetches both over HTTPS and installs to the user's plugin directory. Edits in the user plugin dir are not pushed back — the flow is one-way only.]]

Gramps addons live across three repositories. You'll have all three cloned side-by-side under one base directory:

<pre>base/
├── gramps/ # gramps-project/gramps — Gramps itself; source of truth for the API
├── addons-source/ # gramps-project/addons-source — addon source code, one folder per addon
└── addons/ # gramps-project/addons — built distribution, one folder per Gramps version</pre>
{|
! Repo
! What's there
! You edit?
|-
| <code>gramps</code>
| The Gramps source tree; provides <code>GRAMPSPATH</code> for the build
| No (unless writing a core change)
|-
| <code>addons-source</code>
| The addon source: <code>&lt;Addon&gt;/&lt;Addon&gt;.gpr.py</code>, <code>&lt;Addon&gt;/&lt;Addon&gt;.py</code>, <code>po/</code>, <code>tests/</code>
| '''Yes — author addons here'''
|-
| <code>addons</code>
| Built <code>.addon.tgz</code> packages and listing JSON, organised by Gramps minor
| No — <code>make.py</code> writes to it
|}

<code>addons</code> is the '''output'''. The in-app addon manager hits its HTTPS mirror to fetch listings and downloads. Editing files in <code>addons</code> directly does nothing — the next <code>make.py</code> run overwrites them.

=== Branch directory split inside <code>addons/</code> ===

Inside <code>addons/</code>, each Gramps minor gets its own subdirectory:

<pre>addons/
├── gramps42/
├── gramps50/
├── gramps51/
├── gramps52/
├── gramps60/
│ ├── download/ # &lt;Addon&gt;.addon.tgz files
│ └── listings/ # JSON catalogues fetched by the addon manager
└── gramps61/
├── download/
└── listings/</pre>
The same addon can ship to multiple minors, each with its own <code>.addon.tgz</code> — that's why the addon manager reads only the listing for the running Gramps version.

== Initial clone ==

<syntaxhighlight lang="bash">mkdir gramps-addons && cd gramps-addons

git clone https://github.com/gramps-project/gramps.git
git clone https://github.com/gramps-project/addons-source.git
git clone https://github.com/gramps-project/addons.git

cd addons-source
git checkout -b gramps60 origin/maintenance/gramps60 # for 6.0
# or for master/6.1:
# git checkout -b gramps61 origin/master</syntaxhighlight>
The branch you check out in <code>addons-source</code> determines which Gramps minor your built addons target — <code>make.py</code> reads the branch name to pick the output directory inside <code>addons/</code>.

See [[User:Eduralph/Sandbox/Gramps_6.0_Wiki_Manual_-_Addon_Development_-_Fundamentals#translation|13-compatibilityL-compatibility.md) for branch-targeting guidance per Gramps minor.

## Build prerequisites

`make.py` calls out to two environment things and one OS tool:

- **`GRAMPSPATH`** — absolute path to your `gramps/` clone.
- **`LANGUAGE`** — must be set to `en_US.UTF-8` for the build to run.
- **`intltool`** — `sudo apt-get install intltool` on Debian/Ubuntu.

The standard invocation:

```bash
GRAMPSPATH=/path/to/gramps LANGUAGE='en_US.UTF-8' python3 make.py gramps60 <command> <Addon>
```

Cumbersome to type each time. Set the env vars in your shell startup once; only the `make.py` line varies per command.

In the examples below, `gramps60` is the maintenance/gramps60 target; substitute `gramps61` when you're working on the master branch.

## `make.py` cheat sheet

`make.py` lives at the top of `addons-source` and runs against one addon at a time, or `all` for everything. The commands you'll use most:

| Command | What it does |
|------------------------------------------|-------------------------------------------------------------------------------|
| `make.py gramps60 init <Addon>` | Create `<Addon>/po/template.pot` from extracted strings |
| `make.py gramps60 init <Addon> <lang>` | Create `<Addon>/po/<lang>-local.po` from the template |
| `make.py gramps60 update <Addon> <lang>` | Merge new strings from Gramps + the addon into `<lang>-local.po` |
| `make.py gramps60 compile <Addon>` | Compile every `<lang>-local.po` into `.mo` files |
| `make.py gramps60 build <Addon>` | Compile translations *and* produce `<Addon>.addon.tgz` in `addons/gramps60/download/` |
| `make.py gramps60 listing <Addon>` | Refresh `addons/gramps60/listings/*.json` so the addon manager sees it |
| `make.py gramps60 clean <Addon>` | Delete generated files (`locale/`, `*.mo`) — run before `git add` |
| `make.py gramps60 build all` | Build every addon |

`build` includes `compile`, so the standard release cycle is `clean` → edit → `build` → `listing` → commit + push to `addons/`.

### What `build` packages

By default `build` includes:

- every `*.py` in the addon folder,
- every `*.glade`, `*.xml`, `*.txt`,
- every `locale/*/LC_MESSAGES/*.mo`.

Anything else — README images, extra data files, help HTML — needs an explicit `MANIFEST` file in the addon's root listing them, **with the addon folder name prefixed on each line**:

```
<Addon>/README.md
<Addon>/help/index.html
<Addon>/data/*
```

The `MANIFEST` mechanism was added in Gramps 5.0 and is the way to ship anything beyond the default file types.

## The localisation flow

Per-addon translations live under `<Addon>/po/`. They are independent of Gramps' core catalogues — Gramps' plugin loader binds `_()` to the addon's own catalog when the implementation module declares:

```python
from gramps.gen.const import GRAMPS_LOCALE as glocale

_ = glocale.get_addon_translator(__file__).gettext
```

See [05-fundamentals → Translation]] for the full opt-in. Without that line, strings fall back to the core catalog regardless of where translators put them.

=== Adding a new language ===

<syntaxhighlight lang="bash"># 1. Generate (or refresh) the template from extracted strings.
make.py gramps60 init <Addon>

# 2. Initialise a fresh language file from the template.
make.py gramps60 init <Addon> fr

# 3. Translator edits <Addon>/po/fr-local.po manually.

# 4. Recompile so the .mo file lands under <Addon>/locale/fr/LC_MESSAGES/.
make.py gramps60 compile <Addon>

# 5. Commit the .po (NOT the .mo or the generated locale/ tree).
git add <Addon>/po/fr-local.po
git commit -m "Add French translation for <Addon>"</syntaxhighlight>
=== Refreshing an existing language ===

When you've changed user-visible strings in the addon, every existing <code>&lt;lang&gt;-local.po</code> needs new entries merged in:

<syntaxhighlight lang="bash">make.py gramps60 update <Addon> fr</syntaxhighlight>
This preserves existing translations and marks new or changed entries as <code>fuzzy</code> for the translator to review.

=== Editing <code>template.pot</code>'s header once ===

The header of <code>&lt;Addon&gt;/po/template.pot</code> carries author, maintainer, and team metadata. Edit it after the first <code>init</code> — the values propagate into every per-language file the next time you <code>init &lt;Addon&gt; &lt;lang&gt;</code>.

=== What to commit, what to skip ===

Commit:

* <code>&lt;Addon&gt;/po/template.pot</code>
* <code>&lt;Addon&gt;/po/&lt;lang&gt;-local.po</code> (one per language)

Don't commit:

* <code>&lt;Addon&gt;/locale/**</code> — generated by <code>compile</code> and <code>build</code>. Always run <code>make.py gramps60 clean &lt;Addon&gt;</code> (or <code>rm -rf &lt;Addon&gt;/locale</code>) before <code>git add</code>.
* <code>&lt;Addon&gt;/*.mo</code> outside <code>locale/</code> — same reason.

== Publishing to <code>addons/</code> ==

<code>build</code> writes one <code>.addon.tgz</code> per addon; <code>listing</code> rebuilds the JSON catalogues the addon manager fetches. Both go into the <code>addons/</code> repository and need committing '''there''', not in <code>addons-source/</code>:

<syntaxhighlight lang="bash"># In addons-source/, build the package.
make.py gramps60 build <Addon>
make.py gramps60 listing <Addon>

# Switch to the addons/ checkout.
cd ../addons

# Stage the new .tgz and the refreshed listings.
git add gramps60/download/<Addon>.addon.tgz
git add gramps60/listings/*
git commit -m "Add <Addon> for Gramps 6.0"

# Push when you have write access — see [16-guidelines] for the review gate.</syntaxhighlight>
The in-app addon manager hits the <code>addons</code> repo over HTTPS and shows the addon to users on their next &quot;Check for updates&quot; cycle.

== Editing <code>addons-source/</code>, not the live plugin directory ==

During development it's tempting to edit directly in <code>~/.local/share/gramps/gramps60/plugins/&lt;Addon&gt;/</code> so a Gramps restart picks up the change without copying. '''Don't''' — that directory is the auto-sync ''target'', and Gramps writes back through it on every save. Edits made there are silently overwritten on the next source save.

Edit in <code>addons-source/&lt;Addon&gt;/</code>. The dev loop is one of:

* '''Gramps 6.0''' — copy / <code>rsync</code> the folder into the user plugin directory on each save. Plugin discovery doesn't follow symlinks.
* '''Gramps 6.1+ on Linux/macOS''' — symlink the working tree into the user plugin directory once, then edit in place. Plugin discovery follows symlinks with realpath-based loop dedup (commit <code>9443dcbb30</code>).
* '''Windows, any version''' — physical copy. The 6.1 symlink test is skipped on Windows because the platform's symlink behaviour is inconsistent without elevated privileges.

See [[User:Eduralph/Sandbox/Gramps_6.0_Wiki_Manual_-_Addon_Development_-_Getting_Started#where-addons-live|02-get-started]] for the user plugin directory paths.

== Submitting a new addon ==

The first time you add an addon to <code>addons-source/</code>:

<syntaxhighlight lang="bash">cd addons-source

# 1. Create the folder and the two required files.
mkdir <Addon>
$EDITOR <Addon>/<Addon>.gpr.py
$EDITOR <Addon>/<Addon>.py

# 2. (Optional, recommended) Translation scaffold + tests.
mkdir <Addon>/po <Addon>/tests
$EDITOR <Addon>/tests/__init__.py # empty marker — see 08-testing
$EDITOR <Addon>/tests/test_<Addon>.py

# 3. Clean any build artefacts before committing.
make.py gramps60 clean <Addon>

# 4. Commit and push to your fork, then open a PR.
git add <Addon>
git commit -m "Add <Addon>: <one-line description>"</syntaxhighlight>
Open the PR against the '''correct branch''' — for an addon targeting Gramps 6.0, that's <code>maintenance/gramps60</code>, which the maintainer cherry-picks forward to <code>gramps61</code> (see [[User:Eduralph/Sandbox/Gramps_6.0_Wiki_Manual_-_Addon_Development|16-guidelines → Contributor workflowN-guidelines.md#contributor-workflow) for the full submission shape).

## Update an existing addon

After editing source:

```bash
cd addons-source

# 1. Iterate: edit, restart Gramps, verify behaviour.
# 2. Refresh translations if user-visible strings changed.
make.py gramps60 update <Addon> all
# 3. Compile-check.
make.py gramps60 compile <Addon>
# 4. Clean and commit.
make.py gramps60 clean <Addon>
git add <Addon>
git commit -m "<Addon>: <what changed>"
```

The `version` field in `<Addon>.gpr.py` **stays untouched** in PRs — the maintainer manages versions centrally. (See [15-guidelinesN-guidelines.md#contributor-workflow); this came up on PR 911 where a bump was rejected.)

## See also

- [01-overview]] — what an addon is.

* [[User:Eduralph/Sandbox/Gramps_6.0_Wiki_Manual_-_Addon_Development_-_Fundamentals#translation|05-fundamentals → Translation]] — the <code>_()</code> injection that makes per-addon <code>.po</code> files load.
* [[User:Eduralph/Sandbox/Gramps_6.0_Wiki_Manual_-_Addon_Development_-_Testing|08-testing]] — what to put in <code>tests/</code> before packaging.
* [[Addons_development|13-compatibilityL-compatibility.md) — picking the right Gramps minor and branch for your addon.
- [15-guidelinesN-guidelines.md) — the normative rules a PR must satisfy.
- [Addons development]] — the standalone wiki page; primary scraped source for this chapter.
* [https://github.com/gramps-project/addons-source/blob/maintenance/gramps60/CONTRIBUTING.md addons-source CONTRIBUTING.md] — the addons-source-side contributor guide.

{{stub}}
28
edits

Navigation menu