|
| 1 | +:orphan: |
| 2 | + |
| 3 | +Custom GMT Supplements --- In-Tree vs Out-of-Tree |
| 4 | +================================================= |
| 5 | + |
| 6 | +.. note:: |
| 7 | + |
| 8 | + The investigation underlying this document and the document itself |
| 9 | + (including the accompanying template files under |
| 10 | + :file:`src/custom_supp_templates/`) were produced by Claude Opus 4.7 |
| 11 | + (Anthropic), working from the GMT source tree and a real-world |
| 12 | + out-of-tree supplement port (MB-System's ``mbsystem.dll``). Treat |
| 13 | + the contents as a starting point that has been mechanically verified |
| 14 | + against the GMT source as of the date of writing but has not been |
| 15 | + human-reviewed for stylistic or policy fit with the GMT project. |
| 16 | + Corrections welcome. |
| 17 | + |
| 18 | +This document describes the two ways to build a GMT supplemental shared |
| 19 | +library ("supplement" or "suppl"): the **in-tree** path (your supplement |
| 20 | +lives under :file:`gmt/src/<name>/` and is built when GMT itself is |
| 21 | +built) and the **out-of-tree** path (your supplement is built by an |
| 22 | +independent CMake project that links against an already-installed GMT). |
| 23 | +Both produce a DLL or shared module that exposes GMT modules via |
| 24 | +``gmt --show-modules``, ``gmt --help``, and the external-API key/group |
| 25 | +lookup used by Julia, Python, and MATLAB wrappers. |
| 26 | + |
| 27 | +The accompanying :file:`src/custom_supp_templates/in-tree-template/` |
| 28 | +and :file:`src/custom_supp_templates/out-of-tree-template/` directories |
| 29 | +contain copy-and-modify starter trees for each case. |
| 30 | + |
| 31 | +How a GMT supplement actually works |
| 32 | +----------------------------------- |
| 33 | + |
| 34 | +A GMT supplement is just a shared library that the GMT core loads at |
| 35 | +runtime through ``dlopen`` / ``LoadLibrary``. GMT discovers libraries |
| 36 | +from the ``GMT_CUSTOM_LIBS`` GMT default (or the built-in supplement |
| 37 | +path). For each loaded library, GMT looks up symbols **by name**, using |
| 38 | +the library **basename** as a prefix. For a supplement DLL named |
| 39 | +``mylib``, GMT resolves these five symbols with ``dlsym``: |
| 40 | + |
| 41 | +.. list-table:: |
| 42 | + :widths: 35 65 |
| 43 | + :header-rows: 1 |
| 44 | + |
| 45 | + * - Symbol |
| 46 | + - Purpose |
| 47 | + * - ``mylib_module_show_all`` |
| 48 | + - Pretty-print all modules + purpose for ``gmt --help`` |
| 49 | + * - ``mylib_module_list_all`` |
| 50 | + - Plain list of modern module names for ``gmt --show-modules`` |
| 51 | + * - ``mylib_module_classic_all`` |
| 52 | + - Plain list of classic module names for ``gmt --show-classic`` |
| 53 | + * - ``mylib_module_keys`` |
| 54 | + - Return ``THIS_MODULE_KEYS`` string for an external-API consumer |
| 55 | + * - ``mylib_module_group`` |
| 56 | + - Return ``THIS_MODULE_LIB`` string for an external-API consumer |
| 57 | + |
| 58 | +Per individual module ``xxx``, GMT also looks up the actual entry point |
| 59 | +``GMT_xxx`` (for example ``GMT_grdbarb``, ``GMT_psbarb``). That function |
| 60 | +must be exported from the DLL |
| 61 | +(``EXTERN_MSC int GMT_xxx(void *API, int mode, void *args);``). |
| 62 | + |
| 63 | +The five ``_module_*`` lookup functions all share a per-supplement |
| 64 | +table called ``static struct GMT_MODULEINFO modules[]``, populated with |
| 65 | +one row per module: |
| 66 | + |
| 67 | +.. code-block:: c |
| 68 | +
|
| 69 | + struct GMT_MODULEINFO { |
| 70 | + const char *mname; /* THIS_MODULE_MODERN_NAME */ |
| 71 | + const char *cname; /* THIS_MODULE_CLASSIC_NAME */ |
| 72 | + const char *component; /* THIS_MODULE_LIB */ |
| 73 | + const char *purpose; /* THIS_MODULE_PURPOSE */ |
| 74 | + const char *keys; /* THIS_MODULE_KEYS */ |
| 75 | + }; |
| 76 | +
|
| 77 | +The table is the heart of a supplement. Everything else is plumbing |
| 78 | +around it. The lookup-function bodies are nearly identical across all |
| 79 | +supplements; they just delegate to ``GMT_Show_ModuleInfo`` / |
| 80 | +``GMT_Get_ModuleInfo`` from GMT's public API. |
| 81 | + |
| 82 | +Because the body is boilerplate and the table content is derived |
| 83 | +mechanically from ``#define THIS_MODULE_*`` macros in each module's |
| 84 | +``.c``, GMT auto-generates both during the build. That generation is |
| 85 | +what the in-tree mechanism gives you for free --- and what the |
| 86 | +out-of-tree case must reproduce by hand. |
| 87 | + |
| 88 | +The in-tree mechanism |
| 89 | +--------------------- |
| 90 | + |
| 91 | +Look at :file:`src/windbarbs/` for the canonical, minimal example: two |
| 92 | +module sources (``grdbarb.c``, ``psbarb.c``) plus one shared helper |
| 93 | +(``windbarb.c``) and a fourteen-line :file:`CMakeLists.txt`. That |
| 94 | +``CMakeLists.txt`` declares only the sources --- no ``add_library``, no |
| 95 | +glue file, no ``gen_*`` command: |
| 96 | + |
| 97 | +.. code-block:: cmake |
| 98 | +
|
| 99 | + set (SUPPL_NAME windbarbs) |
| 100 | + set (SUPPL_PROGS_SRCS grdbarb.c psbarb.c) |
| 101 | + set (SUPPL_LIB_SRCS ${SUPPL_PROGS_SRCS} windbarb.c) |
| 102 | + set (SUPPL_EXAMPLE_FILES README.windbarb) |
| 103 | +
|
| 104 | +:file:`src/CMakeLists.txt` does the rest. The loop in that file walks |
| 105 | +every supplement directory listed in ``GMT_SUPPL_DIRS`` (which by |
| 106 | +default includes ``geodesy gsfml gshhg img mgd77 potential segy seis |
| 107 | +spotter x2sys windbarbs`` plus anything the user puts in |
| 108 | +``SUPPL_EXTRA_DIRS``) and for each one: |
| 109 | + |
| 110 | +1. Calls ``add_subdirectory(<dir>)`` so the supplement's |
| 111 | + :file:`CMakeLists.txt` sets ``SUPPL_NAME``, ``SUPPL_PROGS_SRCS``, |
| 112 | + ``SUPPL_LIB_SRCS``, optionally ``SUPPL_LIB_NAME``, |
| 113 | + ``SUPPL_EXTRA_LIBS``, ``SUPPL_EXTRA_INCLUDES``, ``SUPPL_DLL_RENAME``. |
| 114 | +2. Reads those variables back via ``get_subdir_var``. |
| 115 | +3. Accumulates per-library source lists into |
| 116 | + ``SUPPL_<lib>_PROGS_SRCS`` and ``SUPPL_<lib>_LIB_SRCS``. (Multiple |
| 117 | + supplement directories can contribute to one library by sharing |
| 118 | + ``SUPPL_LIB_NAME``.) |
| 119 | +4. For each resulting library: |
| 120 | + |
| 121 | + - Runs ``gen_gmt_moduleinfo_h`` from |
| 122 | + :file:`cmake/modules/GmtGenExtraHeaders.cmake`. The macro greps |
| 123 | + every module source for ``THIS_MODULE_MODERN_NAME``, |
| 124 | + ``THIS_MODULE_CLASSIC_NAME``, ``THIS_MODULE_LIB``, |
| 125 | + ``THIS_MODULE_PURPOSE``, ``THIS_MODULE_KEYS`` and writes |
| 126 | + :file:`gmt_<lib>_moduleinfo.h` (one row per module). |
| 127 | + - Runs ``configure_file(gmt_glue.c.in gmt_<lib>_glue.c)`` substituting |
| 128 | + ``@SHARED_LIB_NAME@`` and ``@SHARED_LIB_PURPOSE@``. The generated |
| 129 | + ``.c`` ``#include`` s the generated ``.h`` to fill in ``modules[]`` |
| 130 | + and defines the five ``<lib>_module_*`` entry points as |
| 131 | + ``EXTERN_MSC``. |
| 132 | + - Builds the shared library / module from the union of accumulated |
| 133 | + module sources, library sources, generated glue, and generated |
| 134 | + header. |
| 135 | + |
| 136 | +5. Installs the library into :file:`gmt/plugins/`. |
| 137 | + |
| 138 | +That is the entire mechanism. The supplement author writes only the |
| 139 | +module sources (with the right ``THIS_MODULE_*`` macros at the top) |
| 140 | +plus a fourteen-line :file:`CMakeLists.txt`; everything that exposes |
| 141 | +the modules to the runtime is generated. |
| 142 | + |
| 143 | +Adding a custom in-tree supplement |
| 144 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 145 | + |
| 146 | +1. Create :file:`gmt/src/<myname>/` containing your ``.c`` sources |
| 147 | + plus a :file:`CMakeLists.txt` shaped like |
| 148 | + :file:`src/windbarbs/CMakeLists.txt`. See the |
| 149 | + :file:`src/custom_supp_templates/in-tree-template/` directory. |
| 150 | +2. Edit (or create) :file:`cmake/ConfigUserAdvanced.cmake` and set |
| 151 | + ``set (SUPPL_EXTRA_DIRS <myname>)``. (Multiple custom supplement |
| 152 | + directories can be listed.) |
| 153 | +3. Configure and build GMT normally. Your DLL appears under |
| 154 | + :file:`gmt/plugins/` alongside the official supplements. |
| 155 | +4. Verify with ``gmt --show-modules`` (your module names should appear) |
| 156 | + and ``gmt <yourmodule>``. |
| 157 | + |
| 158 | +The five-symbol contract and the ``THIS_MODULE_*`` macro requirement |
| 159 | +described above are non-negotiable in either path. The in-tree |
| 160 | +mechanism just satisfies them automatically. |
| 161 | + |
| 162 | +The out-of-tree mechanism |
| 163 | +------------------------- |
| 164 | + |
| 165 | +A custom supplement built **outside** the GMT source tree (as part of |
| 166 | +an independent project --- for example MB-System builds ``mbsystem.dll`` |
| 167 | +from its own CMake project that links against an installed GMT) does |
| 168 | +**not** get the auto-generated glue and moduleinfo header. The |
| 169 | +supplement author has to either: |
| 170 | + |
| 171 | +(a) Reproduce the generation in the host project's CMake, or |
| 172 | +(b) Hand-author :file:`gmt_<lib>_glue.c` and |
| 173 | + :file:`gmt_<lib>_moduleinfo.h` and treat them as ordinary source |
| 174 | + files. |
| 175 | + |
| 176 | +Option (a) is strongly recommended --- the moduleinfo header should |
| 177 | +track the module sources automatically, and hand-editing it is |
| 178 | +error-prone. The :file:`src/custom_supp_templates/out-of-tree-template/` |
| 179 | +directory shows option (a) end-to-end. |
| 180 | + |
| 181 | +What out-of-tree projects must provide |
| 182 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 183 | + |
| 184 | +1. **Their own copy of** :file:`gmt_glue.c.in`. GMT does not install |
| 185 | + :file:`gmt_glue.c.in` to a public location (it lives in the source |
| 186 | + tree only). Vendor a copy into your project; you only need to |
| 187 | + change ``@SHARED_LIB_NAME@`` and ``@SHARED_LIB_PURPOSE@``. |
| 188 | + |
| 189 | +2. **Their own moduleinfo generator.** GMT's |
| 190 | + :file:`cmake/modules/GmtGenExtraHeaders.cmake` is similarly |
| 191 | + source-tree-only and the ``gen_gmt_moduleinfo_h`` macro is tightly |
| 192 | + coupled to GMT's own directory layout (it reads from |
| 193 | + ``${GMT_SRC}/src/${prog}``). Vendor a standalone CMake ``-P`` |
| 194 | + script that does the same regex extraction over your project's |
| 195 | + module sources. See |
| 196 | + :file:`src/custom_supp_templates/out-of-tree-template/cmake/GenSupplModuleInfo.cmake`. |
| 197 | + |
| 198 | +3. **A CMakeLists.txt that wires up the generator → glue → library.** |
| 199 | + See |
| 200 | + :file:`src/custom_supp_templates/out-of-tree-template/CMakeLists.txt`. |
| 201 | + |
| 202 | +4. **The right library output name.** GMT calls ``dlsym`` using the |
| 203 | + **DLL basename**, so the CMake target's ``OUTPUT_NAME`` (and any |
| 204 | + ``*_DLL_RENAME``) must match the ``SHARED_LIB_NAME`` you bake into |
| 205 | + the glue. If the DLL is :file:`mylib.dll`, the glue must export |
| 206 | + ``mylib_module_list_all`` etc. --- mismatch means GMT silently |
| 207 | + finds nothing. |
| 208 | + |
| 209 | +5. **DLL export visibility.** On Windows the entry points need |
| 210 | + ``__declspec(dllexport)``. The vendored :file:`gmt_glue.c.in` uses |
| 211 | + ``EXTERN_MSC``, which GMT's :file:`declspec.h` flips to |
| 212 | + ``dllexport`` when ``LIBRARY_EXPORTS`` is defined. Add |
| 213 | + |
| 214 | + .. code-block:: cmake |
| 215 | +
|
| 216 | + target_compile_definitions(<target> PRIVATE LIBRARY_EXPORTS) |
| 217 | +
|
| 218 | + to your CMakeLists. Each module's ``GMT_<modulename>`` entry point |
| 219 | + should also be declared ``EXTERN_MSC`` (or you can rely on CMake's |
| 220 | + ``CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS``, but the explicit attribute is |
| 221 | + cleaner and matches what in-tree supplements do). |
| 222 | + |
| 223 | +Real-world worked example |
| 224 | +~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 225 | + |
| 226 | +MB-System builds ``mbsystem.dll`` out-of-tree against an installed GMT. |
| 227 | +Its :file:`src/gmt/CMakeLists.txt` followed the recipe above (using a |
| 228 | +vendored ``MbsysGenModuleInfo.cmake`` and ``gmt_mbsystem_glue.c.in``) |
| 229 | +after diagnosing that an earlier hand-written ``mbgmt_module.c`` |
| 230 | +exported the wrong symbol names (``gmt_mbgmt_module_show_all`` instead |
| 231 | +of ``mbsystem_module_list_all``), which is why ``gmt --show-modules`` |
| 232 | +showed nothing and Julia's ``GMT_Encode_Options`` returned null keys. |
| 233 | + |
| 234 | +The MB-System fix is a close analogue of the |
| 235 | +:file:`src/custom_supp_templates/out-of-tree-template/` provided here; |
| 236 | +consult that project as a secondary reference if you want to see the |
| 237 | +mechanism integrated into a larger codebase. |
| 238 | + |
| 239 | +Module source requirements (both paths) |
| 240 | +--------------------------------------- |
| 241 | + |
| 242 | +Every module ``.c`` must define these macros near the top, before any |
| 243 | +``#include "gmt_dev.h"``: |
| 244 | + |
| 245 | +.. code-block:: c |
| 246 | +
|
| 247 | + #define THIS_MODULE_CLASSIC_NAME "mymodule" /* required */ |
| 248 | + #define THIS_MODULE_MODERN_NAME "mymodule" /* required */ |
| 249 | + #define THIS_MODULE_LIB "mylib" /* must equal DLL basename */ |
| 250 | + #define THIS_MODULE_PURPOSE "One-line description" |
| 251 | + #define THIS_MODULE_KEYS "<G{,>X}" /* API key string; "" if none */ |
| 252 | + #define THIS_MODULE_NEEDS "Jg" /* projection requirements; "" if none */ |
| 253 | + #define THIS_MODULE_OPTIONS "->BJKOPRUVXY" /* common options accepted */ |
| 254 | +
|
| 255 | +The legacy ``THIS_MODULE_NAME`` (a single string used by very old GMT5 |
| 256 | +ports) is **not** recognised by the modern generator. If you are |
| 257 | +porting old code, add explicit ``THIS_MODULE_MODERN_NAME`` and |
| 258 | +``THIS_MODULE_CLASSIC_NAME`` (typically both equal to the old name) or |
| 259 | +adapt your generator to fall back. The vendored |
| 260 | +:file:`src/custom_supp_templates/out-of-tree-template/cmake/GenSupplModuleInfo.cmake` |
| 261 | +does the fallback for convenience; the in-tree GMT generator does not. |
| 262 | + |
| 263 | +Each module must also expose: |
| 264 | + |
| 265 | +.. code-block:: c |
| 266 | +
|
| 267 | + EXTERN_MSC int GMT_mymodule(void *V_API, int mode, void *args); |
| 268 | +
|
| 269 | +as a defined function (not just a declaration). That is what GMT |
| 270 | +dispatches to when the user runs ``gmt mymodule``. |
| 271 | + |
| 272 | +Loading a custom supplement at runtime |
| 273 | +-------------------------------------- |
| 274 | + |
| 275 | +After building and installing, point GMT at the new library. Either: |
| 276 | + |
| 277 | +- Set the GMT default ``GMT_CUSTOM_LIBS`` to the absolute path of the |
| 278 | + DLL (or to a colon/semicolon separated list of paths), for example |
| 279 | + ``gmt set GMT_CUSTOM_LIBS C:/path/to/mylib.dll``. |
| 280 | +- Or drop the DLL into GMT's default plugin directory |
| 281 | + (:file:`<gmt-install>/lib/gmt/plugins/` on Unix, |
| 282 | + :file:`<gmt-install>/bin/gmt_plugins/` on Windows). |
| 283 | + |
| 284 | +Verify: |
| 285 | + |
| 286 | +.. code-block:: sh |
| 287 | +
|
| 288 | + gmt --show-modules # your modules should appear in the list |
| 289 | + gmt mymodule --help # your module should run |
| 290 | +
|
| 291 | +If ``gmt --show-modules`` does not list your modules but the DLL |
| 292 | +clearly loaded (no error printed), the most likely cause is a mismatch |
| 293 | +between the DLL basename and the ``SHARED_LIB_NAME`` baked into the |
| 294 | +glue. Inspect the DLL with ``dumpbin /exports mylib.dll`` (MSVC) or |
| 295 | +``nm -D mylib.so`` (Unix) and confirm that ``mylib_module_list_all`` |
| 296 | +and friends are present. |
| 297 | + |
| 298 | +File overview in :file:`src/custom_supp_templates/` |
| 299 | +--------------------------------------------------- |
| 300 | + |
| 301 | +.. code-block:: text |
| 302 | +
|
| 303 | + src/custom_supp_templates/ |
| 304 | + ├── README.md this document (markdown copy) |
| 305 | + ├── in-tree-template/ |
| 306 | + │ ├── CMakeLists.txt drop-in for src/<myname>/ |
| 307 | + │ └── mymodule.c skeleton module source |
| 308 | + └── out-of-tree-template/ |
| 309 | + ├── CMakeLists.txt standalone CMake project |
| 310 | + ├── gmt_mylib_glue.c.in vendored from gmt_glue.c.in |
| 311 | + ├── mymodule.c skeleton module source |
| 312 | + └── cmake/ |
| 313 | + └── GenSupplModuleInfo.cmake vendored moduleinfo generator |
0 commit comments