diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..d0905f28b2e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,125 @@ +# AGENTS.md + +GMT is a C library plus ~100 command-line modules (`src/*.c`), supplement +modules (`src//`), 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 `/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/_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, )` instead of removing it. + +## Documentation + +- Any change to an option or modifier updates `usage()` and the module page + (`doc/rst/source/.rst`, or `doc/rst/source/supplements//`) + 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 `|_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//.sh`. Test directories and scripts are + discovered at configure time, so re-run `cmake` after adding one. +- Always call `gmt `; 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//.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, `/test///`, + 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/.dvc`). + `dvc add test/baseline/` 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= gmt end`, where `` is the suffix of + `~/.gmt/sessions/gmt_session.`. 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.