Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# AGENTS.md

GMT is a C library plus ~100 command-line modules (`src/*.c`), supplement
modules (`src/<name>/`), reStructuredText docs (`doc/rst/source/`), shell-script
tests (`test/`) and a CMake build. Read a similar existing module before
writing code: this file only lists what cannot be learned from reading the
code (build and CI setup, files that must change together, test harness
behaviour). See also `BUILDING.md` and `doc/rst/source/devdocs/contributing.rst`.

## Build and test

Build out of source in a separate directory (`build/`, `_build/`, ...):

```
cp cmake/ConfigUserTemplate.cmake cmake/ConfigUser.cmake
cp cmake/ConfigUserAdvancedTemplate.cmake cmake/ConfigUserAdvanced.cmake
# both files are git-ignored. In ConfigUserAdvanced.cmake uncomment:
# enable_testing(), DO_EXAMPLES, DO_TESTS, SUPPORT_EXEC_IN_BINARY_DIR, DO_API_TESTS
mkdir build && cd build && cmake .. -G Ninja && cmake --build .
```

Run tests from the build directory:

```
ctest -j 4 # everything
ctest --output-on-failure -R blockmean # by regexp on the script path
ctest --rerun-failed
```

Docs: `python -m pip install -r doc/rst/requirements.txt` (Sphinx theme and
extensions) and `dvc pull`, then `cmake --build . --target docs_html` (it
builds `docs_depends` first; both targets exist only if CMake found Sphinx).
The build prints nothing: warnings and errors go to `<build>/doc/rst/html.log`.
`conf.py` is generated from `doc/rst/conf.py.in` and the sources are copied
into the build tree, so do not run `sphinx-build` on `doc/rst/source`
directly. The log already has thousands of warnings (mostly duplicate labels
from the shared `explain_*.rst_` includes); judge your change by the warnings
that mention your own files.

## Modules and options

- A core module is registered only by adding its `.c` to `GMT_PROGS_SRCS` in
`src/CMakeLists.txt`; the module table is generated from its `THIS_MODULE_*`
macros.
- For a new option, pick a letter the module does not use and that is not a
common option (`-B -J -K -O -P -R -U -V -X -Y` and the lowercase set, see
`doc/rst/source/std-opts.rst`). A module parses a common letter itself only
when it is missing from its `THIS_MODULE_OPTIONS` (`gmt2kml` and
`greenspline` have their own `-R`). Common options are parsed in
`src/gmt_init.c`, so changes there affect every module.
- Keep `src/longopt/<module>_inc.h` (long-option names, edited by hand) in sync
when adding an option or modifier; nearly every core module has one.
- Keep old syntax working. When an option's syntax changes, keep accepting the
old form behind `gmt_M_compat_check (GMT, <version>)` instead of removing it.

## Documentation

- Any change to an option or modifier updates `usage()` and the module page
(`doc/rst/source/<module>.rst`, or `doc/rst/source/supplements/<suppl>/`)
together: synopsis and description.
- A new core module needs entries in `doc/rst/source/modules.rst` (toctree,
category list and summary table), plus `modules-classic.rst` if it has a
classic name.
- The `|<module>_purpose|` substitutions live in
`doc/rst/source/module_core_purpose.rst_` and
`doc/rst/source/supplements/module_supplements_purpose.rst_`, which are
generated by running `src/gmt_make_module_purpose.sh` from `src/` (needs
gawk). The script splits the `#define THIS_MODULE_PURPOSE` line on a TAB,
so put exactly one TAB before the string or the purpose comes out empty. The
committed files are not always in sync with the sources: keep only your
module's lines from the diff. A missing substitution shows up as an
"Undefined substitution referenced" error in the docs build.

## Tests

- A test is `test/<dir>/<name>.sh`. Test directories and scripts are
discovered at configure time, so re-run `cmake` after adding one.
- Always call `gmt <module>`; a bare module name is deliberately invalidated by
`test/invalidate_modules.sh`.
- Plot tests write a `.ps` that is rasterized and compared with
`test/baseline/<dir>/<name>.ps`. Without GraphicsMagick the harness prints
`[PASS] (without comparison)`, which is not a pass.
- `GMT_KNOWN_FAILURE` (or `_LINUX`, `_MACOS`, `_WINDOWS`) in a script makes
ctest report a pass even when the test fails, so never add it to hide a
regression.
- A failed test keeps its working directory, `<build>/test/<dir>/<name>/`,
with the `.ps` and the `*_diff.png`; it is deleted when the test passes.
- Only PostScript is compared. A PDF is made only after a failure, so bugs that
appear only in PDF output pass CI.
- The harness sets only `LANG=C`; `LC_ALL` is not pinned.
- Baselines live in DVC, not git (`test/baseline/<dir>.dvc`).
`dvc add test/baseline/<dir>` records the directory exactly as it is on disk,
so `dvc pull` it first or the files you did not have are dropped. Anonymous
`dvc pull` from DagsHub returns HTTP 401 (checked October 2026); CI uses a
token.

## Running gmt by hand

`ctest` isolates each test, but commands you run yourself share state through
modern-mode sessions, which are keyed on `GMT_SESSION_NAME` or else on the
parent process (your shell):

- Run a whole `gmt begin` ... `gmt end` block in one shell invocation, or
export the same `GMT_SESSION_NAME` in each one. A command started from
another shell (for example, a new shell per tool call) does not see the
session and runs in classic mode, where modern names such as `plot` fail with
"Shared GMT module not found".
- A `gmt begin` that never reaches `gmt end` leaves the session open. A later
plot command from the same shell (`gmt basemap ... > a.ps`, or the classic
`gmt psbasemap`) exits 0 but its redirect gets an empty file: the drawing
goes into the session's figure instead. Only classic-only options (`-K`,
`-O`, `-P`) fail with an error. Close it with `gmt end`; from another shell,
`GMT_SESSION_NAME=<pid> gmt end`, where `<pid>` is the suffix of
`~/.gmt/sessions/gmt_session.<pid>`. Avoid `gmt clear sessions`: it deletes
every session in the user directory.
- Set `GMT_END_SHOW=off` so that `gmt end` does not try to open a viewer.

## Pull requests

Open an issue first. Keep each PR small and focused; fix bugs in separate PRs;
do not touch unrelated files. Fill in the PR template (description, `Fixes #`)
and add a test that would have caught the bug. CI runs the tests only when a
PR touches `src/`, `test/`, `doc/examples/` or `doc/scripts/`, and the docs
build runs only after a merge to master, so build the docs locally for any
change to `doc/` or to a module.