diff --git a/.gitignore b/.gitignore index c6fd02e..25a3e6e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,21 +18,16 @@ control.mk # built targets # Note: Version-specific files (sql/*--*.sql) are now tracked in git and should be committed -# Exception: the `stable` pseudo-version (default_version between releases, -# see ../ai/RELEASE.md and ../ai/CLAUDE.md's "Version-specific SQL files") -# is permanently current, not a frozen release -- it would be regenerated -# and re-diffed on every source edit for zero test-coverage value if -# tracked. Named explicitly, not `sql/*--stable.sql`: that glob's `*` also -# matches across the second `--`, so it silently swept up the *update* -# scripts too (sql/----stable.sql, which must stay -# committed -- confirmed the hard way when `git status` didn't show them -# as untracked after creating them). -sql/test_factory--stable.sql -sql/test_factory_pgtap--stable.sql # Generated by asciidoctor from doc/*.asc; only the .asc source is tracked doc/*.html +# Post-tag placeholder version's generated SQL file (see PGXNTOOL_POST_TAG_VERSION +# in README.asc's "post-tag-version-bump" section). Update both lines if you override that variable. +# The negation keeps the update script (sql/{ext}--{old}--stable.sql) tracked. +sql/*--stable.sql +!sql/*--*--stable.sql + # Test artifacts results/ regression.diffs @@ -46,6 +41,11 @@ test/build/sql/ # Created by make when test/install/*.sql files exist. test/install/schedule +# test/install/*.out is rewritten by every `make test` run and never +# meaningfully compared against anything (see base.mk's test/install +# comments) -- there's no reason to commit it or track changes to it. +test/install/*.out + # Misc tmp/ .DS_Store diff --git a/pgxntool/CLAUDE-POSTGRES.md b/pgxntool/CLAUDE-POSTGRES.md new file mode 100644 index 0000000..03b84b3 --- /dev/null +++ b/pgxntool/CLAUDE-POSTGRES.md @@ -0,0 +1,38 @@ +# General PostgreSQL Guidance + +Guidance in this file is about **PostgreSQL itself** rather than about pgxntool -- it +applies just as much in a project that has never heard of pgxntool. `CLAUDE.md`, next to +this file, covers pgxntool proper. + +As with `CLAUDE.md`, an extension project's own CLAUDE.md and instructions take +precedence over anything here. + +The human-facing version of this material is `POSTGRES-NOTES.asc`. + +## Never Mention `trusted` Without `superuser = false` and the Security Caveat + +Whenever you bring up the `trusted` control-file parameter -- in code, a comment, a +document, a commit message, a PR, or a chat reply -- you MUST in the same breath also: + +1. Offer `superuser = false` as the alternative, and +2. State that `trusted = true` carries security consequences, pointing at PostgreSQL's + [Security Considerations for Extensions](https://www.postgresql.org/docs/current/extend-extensions.html#EXTEND-EXTENSIONS-SECURITY) + rather than reassuring the reader yourself. + +The rule is one-directional: mentioning `superuser = false` on its own is fine. It is +specifically `trusted` that must never appear unaccompanied. + +**Why**: `trusted = true` is the answer that comes to mind fastest for "how do I let a +non-superuser install this?", so raising it alone reads as a recommendation. It isn't a +free switch -- it runs the install script as the bootstrap superuser on behalf of an +unprivileged caller, which turns any flaw in that script into privilege escalation. +`superuser = false` solves the same problem while granting nothing: the caller simply +needs the privileges the script actually uses. When the extension doesn't require +superuser-only capabilities, that's the better suggestion. + +See `POSTGRES-NOTES.asc` for the user-facing explanation of both parameters. + +## Metrics and Estimates + +Never produce any kind of metrics or estimates unless you have data to back them up. If +you do have data you MUST reference it. diff --git a/pgxntool/CLAUDE.md b/pgxntool/CLAUDE.md index 65e3bf7..cca492c 100644 --- a/pgxntool/CLAUDE.md +++ b/pgxntool/CLAUDE.md @@ -12,9 +12,12 @@ sequentially. The CI monitor lives in the pgxntool-test checkout: run `bash ../pgxntool-test/.claude/skills/ci/scripts/monitor-ci.sh` (the `/ci` skill). It monitors both repos and derives the owner from the current repo. -Pass the exact push SHA when available — `gh run list --branch` has a race -condition: if two pushes land close together on the same branch, `--branch` -may pick up the wrong run. `--commit SHA` targets the exact push and avoids it. +Pass the exact push SHA(s) as positional arguments when available (the +script takes `[repos] [branch] [sha_pgxntool_test] [sha_pgxntool]`, not a +`--commit` flag — see the `/ci` skill for exact usage) — `gh run list +--branch` has a race condition: if two pushes land close together on the +same branch, `--branch` may pick up the wrong run. An exact SHA targets +the push directly and avoids this. ## Scope of This File @@ -30,6 +33,16 @@ development must be done from the **pgxntool-test** repository, not from here. S Any agent working in an extension project should always defer to that project's own CLAUDE.md and instructions over anything stated here. +Guidance that is about PostgreSQL itself rather than about pgxntool lives in +`CLAUDE-POSTGRES.md` instead, so that this file stays about pgxntool. + +## Read `CLAUDE-POSTGRES.md` + +**`CLAUDE-POSTGRES.md` (next to this file) contains rules you must follow.** The `@` line +below imports it where that is supported; read the file directly if it did not. + +@CLAUDE-POSTGRES.md + ## Git Commit Guidelines **IMPORTANT**: When creating commit messages, do not attribute commits to yourself (Claude). Commit messages should reflect the work being done without AI attribution in the message body. The standard Co-Authored-By trailer is acceptable. @@ -78,7 +91,7 @@ https://github.com/Postgres-Extensions/pgxntool-test Extension projects include pgxntool via git subtree: ```bash -git subtree add -P pgxntool --squash git@github.com:decibel/pgxntool.git release +git subtree add -P pgxntool --squash git@github.com:Postgres-Extensions/pgxntool.git release pgxntool/setup.sh ``` @@ -138,7 +151,7 @@ project/ │ ├── deps.sql # Load extension and test dependencies │ ├── sql/*.sql # Test SQL files │ └── expected/*.out # Expected test outputs -└── doc/ # Optional docs (*.adoc, *.asciidoc) +└── doc/ # Optional docs (*.adoc, *.asciidoc, *.asc) ``` ## Commands for Extension Developers (End Users) @@ -147,7 +160,7 @@ These are the commands extension developers use (documented for context): ```bash make # Build extension (generates versioned SQL, docs) -make test # Full test: testdeps → install → installcheck → show diffs +make test # Full test: testdeps → [test-build] → [install] → installcheck → safeguard checks → show diffs ([...] = when enabled; see Critical Testing Rules) make results # Run tests and update expected output files make html # Generate HTML from Asciidoc sources make tag # Create git tag for current META.json version @@ -163,17 +176,18 @@ make pgxntool-sync # Update to latest pgxntool via git subtree pull ### Critical Testing Rules **NEVER use `make installcheck` directly**. Always use `make test` instead. The `make test` target ensures: -- Correct test dependency installation (`testdeps`, and `test-build` when enabled) -- Extension is installed before tests run (`install`) +- Correct test dependency installation (`testdeps`, and `test-build` when enabled); `testdeps`' default `pgtap` prerequisite auto-installs pgtap via `pgxn install`, a no-op when `PGXNTOOL_ENABLE_PGXN_INSTALL=no` +- Extension is filesystem-installed before tests run (`install`), unless `PGXNTOOL_ENABLE_FS_INSTALL=no` - Test comparison via `installcheck`, with diffs shown on failure +- Safeguard checks that fail the run: the stale-expected-file check (orphaned or non-`.out` files in `test/expected/`, run after `pg_regress`), and, when `test/install` is in use, the check that every `test/install/*.sql` file sets `ON_ERROR_STOP` Note: `make test` intentionally does *not* depend on `clean` — depending on `clean` caused problems with incremental/watch-based builds (see `base.mk`). If your tests need a clean build to pass, that's a sign of a missing dependency elsewhere, not something to fix by adding `clean` back. **Database Connection Requirement**: PostgreSQL must be running before executing `make test`. If you get connection errors (e.g., "could not connect to server"), stop and ask the user to start PostgreSQL. -**Claude Code MUST NEVER run `make results`**. This target updates test expected output files and requires manual human verification of test changes before execution. +**Claude Code MUST NEVER run `make results` or `make results-build`**. Both update test expected output files and require manual human verification of test changes before execution. -**Claude Code MUST NEVER modify files in `test/expected/`**. These are expected test outputs that define correct behavior and must only be updated through the `make results` workflow. +**Claude Code MUST NEVER modify files in `test/expected/` or `test/build/expected/`**. These are expected test outputs that define correct behavior and must only be updated through the `make results`/`make results-build` workflows. The workflow is: 1. Human runs `make test` and examines diffs @@ -190,6 +204,11 @@ pgxntool uses PostgreSQL's pg_regress test framework: When tests fail, examine the diff output carefully. The actual test output in `test/results/` shows what your code produced, while `test/expected/` shows what was expected. +**Exceptions to the above** -- `test-build` and `test/install` (both optional, see `README.asc`) don't follow the `test/results` vs `test/expected` model: + +- **test-build** runs first, in its own separate `pg_regress` pass over `test/build/*.sql`, and gates the main suite: if it fails, `test/install`/`test/sql` never run at all. It does compare actual vs expected normally (`test/build/results/` vs `test/build/expected/`) -- use `make results-build` to refresh its expected output, not `make results`. +- **test/install** does NOT get a real diff at all: its actual output is written to the exact same file as its expected output, so a content difference can never fail the build, no matter what changed. The only thing that still fails the build is a hard SQL error, and only if the file has `ON_ERROR_STOP` set (directly, or via `\i test/pgxntool/psql.sql`) -- pgxntool checks for this by default, passing any file that includes `psql.sql` or has its own `\set`/`\unset ON_ERROR_STOP`, whatever the value. If a `test/install/*.sql` file is misbehaving, don't go looking for a diff; check whether it errored, and don't assume a stale-looking `.out` for it means anything. + ## Key Implementation Details ### PostgreSQL Version Handling @@ -205,7 +224,7 @@ When tests fail, examine the diff output carefully. The actual test output in `t ### Document Generation - Auto-detects `asciidoctor` or `asciidoc` -- Generates HTML from `*.adoc` and `*.asciidoc` in `$(DOC_DIRS)` +- Generates HTML from `*.adoc`, `*.asciidoc` and `*.asc` in `$(DOC_DIRS)` - HTML required for `make dist`, optional for `make install` - Template-based rules via `ASCIIDOC_template` @@ -288,5 +307,4 @@ may lack such a header. ## Related Repositories -- **pgxntool-test** - Test harness for validating pgxntool functionality: https://github.com/Postgres-Extensions/pgxntool-test -- Never produce any kind of metrics or estimates unless you have data to back them up. If you do have data you MUST reference it. \ No newline at end of file +- **pgxntool-test** - Test harness for validating pgxntool functionality: https://github.com/Postgres-Extensions/pgxntool-test \ No newline at end of file diff --git a/pgxntool/HISTORY.asc b/pgxntool/HISTORY.asc index bdd5121..3d588c0 100644 --- a/pgxntool/HISTORY.asc +++ b/pgxntool/HISTORY.asc @@ -1,3 +1,137 @@ +2.4.0 +----- +== Breaking changes + +=== `test/install` now requires `ON_ERROR_STOP` +`test/install/*.sql` files never get a real diff -- their actual output is +written to the same file as their expected output, so a content difference +can never fail the build. Without `ON_ERROR_STOP`, a hard SQL error was +silently swallowed too, making the file "pass" regardless of what happened. +`make test` now fails if a `test/install/*.sql` file doesn't set +`ON_ERROR_STOP`: it must either include `test/pgxntool/psql.sql` (which +sets it) or have its own `\set ON_ERROR_STOP` or `\unset ON_ERROR_STOP` +command, to any value; `\include` and `\include_relative` count as including `psql.sql`, the same as +`\i` and `\ir`; see +`PGXNTOOL_ENABLE_TEST_INSTALL_ERROR_STOP_CHECK` to disable. Since +`test/install/*.out` was never really compared against anything, and is +rewritten by every run, it's now gitignored -- stop committing it. + +=== `test-build` now actually runs before the main suite +`make test` was meant to run `test-build` first and stop if it failed, but +with `check-stale-expected` enabled (the default) the main suite ran first. +`test-build` now always runs before `installcheck`, including a direct +`make installcheck`, so a broken build -- or a stale +`test/build/expected/*.out` -- stops the run before the main suite starts. +Use `make results-build` to refresh stale test-build output. + +=== `PGXNTOOL_PGTLE_VERSION` must be an exact pg_tle range name +`make pgtle PGXNTOOL_PGTLE_VERSION=...` (and `pgtle.sh --pgtle-version`) now +errors unless the value is exactly `1.0.0-1.4.0`, `1.4.0-1.5.0` or `1.5.0+`. +A plain pg_tle version such as `1.5.0` used to be accepted silently, +generating a bogus `pg_tle/1.5.0/` directory; the error now names the range +it belongs to (`did you mean 1.5.0+?`). + +=== Reject invalid `PGXNTOOL_VERIFY_RESULTS_MODE` values +Any value other than exactly `pgtap` silently selected `diffs` mode, so a +typo like `pgTap` quietly weakened the `make results` safeguard. The value +is now case-insensitive, and anything other than `pgtap` or `diffs` is a +hard `make` error. + +=== `PGXNTOOL_ENABLE_TEST_INSTALL=yes` errors when `test/install/` is empty +Explicitly setting `PGXNTOOL_ENABLE_TEST_INSTALL=yes` now fails when +`test/install/` has no SQL files, as documented and matching +`PGXNTOOL_ENABLE_TEST_BUILD`; it used to silently run nothing. + +=== Rename internal-only variables to the `_PGXNTOOL_` shape +Several variables that are pure internal plumbing -- never designed as user +override points -- were named with a bare `PGXNTOOL_` prefix, which reads +like a supported override point by name alone. Renamed to signal "internal, +not part of the user-facing surface", matching the existing +`_CHECK_STALE_EXPECTED_SCRIPT` precedent (itself renamed here too, for the +same `_PGXNTOOL_` consistency): `PGXNTOOL_BASE_MK_INCLUDED` -> +`_PGXNTOOL_BASE_MK_INCLUDED`, `PGXNTOOL_CONTROL_FILES` -> +`_PGXNTOOL_CONTROL_FILES`, `PGXNTOOL_EXTENSIONS` -> `_PGXNTOOL_EXTENSIONS`, +`PGXNTOOL_INSTALL_SCHEDULE` -> `_PGXNTOOL_INSTALL_SCHEDULE`, and +`_CHECK_STALE_EXPECTED_SCRIPT` -> `_PGXNTOOL_CHECK_STALE_EXPECTED_SCRIPT`. +None of these are documented anywhere as override points, but if you happened +to reference one directly (unsupported, but possible), update to the new +name. The internal `check-stale-expected` target is likewise renamed to +`_check-stale-expected`; `make test` runs it as before. + +== Behavior changes + +=== Guarantee `test/install/*.sql` ordering +Install files now always run in byte-value (ASCII) filename order, +regardless of filesystem or locale. If one install file depends on another, +encode that in the filenames (e.g. a numeric prefix); see README. + +== New features + +=== Add `make post-tag-version-bump` and `releasing-with-pgxntool.md` +New target `post-tag-version-bump` sets each extension's `default_version` +to a placeholder (`stable` by default) after a release, so later `make` +runs don't overwrite the released versioned SQL file; see README. New +`releasing-with-pgxntool.md` is a recommended release checklist. New +`tree-is-clean` target fails on uncommitted or untracked changes; `tag` +and `post-tag-version-bump` depend on it. + +=== Add `PGXNTOOL_ENABLE_FS_INSTALL` and `PGXNTOOL_ENABLE_PGXN_INSTALL` +`test`/`verify-results` unconditionally filesystem-installed the extension +(via PGXS's `install`), and `installcheck` unconditionally auto-installed +the pgtap dependency via `pgxn install pgtap --sudo` when it wasn't already +present -- with no way to disable either. That defeated "existing mode" +testing, where the extension (and/or pgtap) was deployed some other way, +e.g. registered via pg_tle instead of the filesystem: calling `test` or +`verify-results` would silently filesystem-install as a side effect, +undermining the point of proving the other deployment path works. + +`PGXNTOOL_ENABLE_FS_INSTALL` (default `yes`) now gates `install` as a +prerequisite of `test`/`verify-results`, and whether `installcheck` depends +on `install`. `PGXNTOOL_ENABLE_PGXN_INSTALL` (default: follows +`PGXNTOOL_ENABLE_FS_INSTALL`, but can be set independently) gates the pgtap +auto-install. Set either to `no` to disable. + +=== Add `print-pgtle` target for combined multi-extension pg_tle files +`make print-pgtle` prints the generated pg_tle registration SQL for each +extension to stdout, selecting the version directory from the new +`PGXNTOOL_PGTLE_TARGET_VERSION` variable (an actual pg_tle version, e.g. +`1.5.2`) or, if unset, the installed pg_tle version. This lets a consumer +build a combined multi-extension install file (e.g. +`$(MAKE) -C ../deps/cat_tools print-pgtle >> pgtle-all.sql`) without needing +to know the extension name or which `pg_tle/` version-range directory +applies. When invoking it recursively for stdout capture, pass +`--no-print-directory` -- otherwise GNU Make's own `Entering +directory`/`Leaving directory` announcements pollute the captured output. + +=== Add `make results-build` +Refreshes `test/build/expected/*.out` from the last `test-build` run's +actual output, mirroring `make results` for the main suite. Refuses to +bless any file whose actual output contains an `ERROR:` line, since that +would defeat the point of test-build; skip such a file and bless it by hand +if the error is intentional. + +=== Warn when two `DOCS` entries install to the same filename +The new `check-duplicate-docs` target warns about `DOCS` entries that share a +basename, which would otherwise fail `make install` with `will not overwrite +just-created`. `all` depends on it, so a plain `make` reports it too. + +== Bug fixes + +=== Fix `make results` refusing to create a new test's first expected output +`make verify-results` used to block `make results` anytime +`regression.diffs` existed, which would be the case when there were no +`test/expected/` files at all. Switch to actually examining +`regression.diffs` to determine if tests are broken. Note this is only in +effect when `PGXNTOOL_VERIFY_RESULTS_MODE=pgtap`. + +=== Fix `make test` failing when `test/sql/` doesn't exist +`installcheck` needs `test/sql/` to exist, but the rule that creates it was +spelled `test/sql` (no trailing slash), which Make treats as a different +target. A project without that directory got `No rule to make target +'test/sql/'`; it now gets created like `test/expected/` and `results/`. + +Issues fixed in this release: #20, #21, #55, #87, #90, #97, #108, #111, #115, #119 + 2.3.0 ----- == Rename `PGTLE_VERSION` to `PGXNTOOL_PGTLE_VERSION` diff --git a/pgxntool/POSTGRES-NOTES.asc b/pgxntool/POSTGRES-NOTES.asc new file mode 100644 index 0000000..ea1001c --- /dev/null +++ b/pgxntool/POSTGRES-NOTES.asc @@ -0,0 +1,43 @@ += General PostgreSQL Notes +Not about PGXNtool +:sectlinks: +:sectanchors: +:toc: + +This file collects guidance about *PostgreSQL itself* rather than about PGXNtool. It +lives here because these questions come up constantly while writing an extension, and +PGXNtool's docs are where extension authors already are. Nothing here describes +PGXNtool behavior, and nothing PGXNtool does depends on it. + +For PGXNtool's own documentation, see link:README.asc[README.asc]. + +== Who Can Install Your Extension: `superuser` and `trusted` + +By default only a superuser can run `CREATE EXTENSION` or `ALTER EXTENSION ... UPDATE` +for your extension. Two `.control` file parameters change that. + +`superuser = false`:: +Removes the superuser requirement outright. The install/update script then runs as the +user who invoked `CREATE EXTENSION`, so that user must already hold every privilege the +script needs -- nothing is granted implicitly. + +`trusted = true`:: +Lets any non-superuser with `CREATE` privilege on the database install the extension, +but runs the install/update script as the *bootstrap superuser* instead of as the +caller. It is only consulted when `superuser` is true (the default), and requires +PostgreSQL 13 or later. + +WARNING: `trusted = true` has real security consequences. Because the script runs with +superuser rights on behalf of an unprivileged caller, any weakness in it becomes a +privilege-escalation path, and writing a trusted extension's SQL safely takes deliberate +effort. Read PostgreSQL's own +https://www.postgresql.org/docs/current/extend-extensions.html#EXTEND-EXTENSIONS-SECURITY[Security +Considerations for Extensions] before setting it. + +If your extension doesn't need superuser-only capabilities, `superuser = false` is +usually the better of the two: it grants nothing, it merely stops requiring superuser. + +PostgreSQL's reference documentation for both parameters: +https://www.postgresql.org/docs/current/extend-extensions.html#EXTEND-EXTENSIONS-FILES-SUPERUSER[`superuser`] +and +https://www.postgresql.org/docs/current/extend-extensions.html#EXTEND-EXTENSIONS-FILES-TRUSTED[`trusted`]. diff --git a/pgxntool/README.asc b/pgxntool/README.asc index 45c4b59..92f132d 100644 --- a/pgxntool/README.asc +++ b/pgxntool/README.asc @@ -42,19 +42,38 @@ These are the make targets that are provided by base.mk NOTE: all the targets normally provided by Postgres http://www.postgresql.org/docs/current/static/extend-pgxs.html[PGXS] still work. If you need to skip PGXS being included entirely (for advanced/non-standard setups), set `PGXNTOOL_NO_PGXS_INCLUDE` in your Makefile before `include pgxntool/base.mk`. === html -This will build any .html files that can be created. See <<_Document_Handling>>. +This will build any .html files that can be created. See <<_document_handling>>. + +=== check-duplicate-docs +Warns if two `$(DOCS)` entries would install to the same filename, which would otherwise fail `make install`. + +`all` depends on this, so it also runs as part of a plain `make`, `make install` and `make test`; you only need to invoke it directly to check without building anything. === test -Runs your extension's test suite: installs the extension and runs it through PGXS's `installcheck`, first pulling in anything you've hooked into <<_testdeps>> and, if enabled, sanity-checking your test SQL via <<_test_build>>. +Runs your extension's test suite: filesystem-installs the extension (unless `PGXNTOOL_ENABLE_FS_INSTALL=no`) and runs it through PGXS's `installcheck`, first pulling in anything you've hooked into <<_testdeps>> and, if enabled, sanity-checking your test SQL via <<_test_build>>. + +`make test` also fails on two safeguard checks: + +- The stale-expected-file check, run after `pg_regress` finishes: fails if `test/expected/` (or `test/build/expected/`) has a `.out` file with no matching `.sql`, or a file that isn't `.out`. See <<_pgxntool_enable_check_stale_expected>>. +- When <<_testinstall,test/install>> is in use, the `ON_ERROR_STOP` check: fails if any `test/install/*.sql` file neither includes `test/pgxntool/psql.sql` nor sets `ON_ERROR_STOP` itself. See <<_pgxntool_enable_test_install_error_stop_check>>. + +`make test` runs `pg_regress` in up to two separate passes: + +1. If <<_test_build,test-build>> is enabled, it runs first, in its own isolated pass over `test/build/*.sql`. If it fails, the second pass never happens — there's no point checking install/query behavior against a build that doesn't even come up cleanly. +2. The main pass follows: any <<_testinstall,test/install>> files, then your regular `test/sql/*.sql` files, together in a single `pg_regress` invocation (so state the install files create persists into the regular tests). + +Within each pass, files run in byte-value (ASCII) order of their filenames. Whether `test-build` runs is controlled by the `PGXNTOOL_ENABLE_TEST_BUILD` variable — see <<_test_build>> for what it does and how to turn it on/off. NOTE: `test` intentionally does *not* depend on `clean` — that caused problems with incremental/watch-based builds. If your tests need a clean build to pass, that's a sign of a missing dependency elsewhere rather than something to fix by adding `clean` back. -NOTE: `test` exits non-zero (after printing `regression.diffs`) if any test fails. Previously it always exited 0 regardless of test results, silently masking failures from CI and other automation that relies on the exit code. +NOTE: `test` exits non-zero (after printing `regression.diffs`) if any test fails, so CI and other automation can rely on its exit code. NOTE: While you can still run `make installcheck` or any other valid PGXS make target directly, it's recommended to use `make test` when using pgxntool. The `test` target ensures proper test isolation and correct dependency installation. +Whether `test`/`installcheck`/`verify-results` filesystem-install the extension first is controlled by `PGXNTOOL_ENABLE_FS_INSTALL`, and whether `installcheck` auto-installs the pgtap dependency via `pgxn install` is controlled by `PGXNTOOL_ENABLE_PGXN_INSTALL` — see <<_pgxntool_enable_fs_install>> and <<_pgxntool_enable_pgxn_install>> for "existing mode"/pg_tle-style testing, where the extension (and/or pgtap) was deployed some other way and a filesystem install would defeat the point of the test. + === test-build Validates that extension SQL files are syntactically correct before running the full test suite. This feature runs SQL files from `test/build/` through `pg_regress`, providing better error messages than `CREATE EXTENSION` failures when there are syntax errors in your extension code. @@ -63,7 +82,9 @@ Validates that extension SQL files are syntactically correct before running the 1. Place SQL files in `test/build/*.sql` 2. Place expected output in `test/build/expected/*.out` 3. These files run through `pg_regress` before `make test` runs the main test suite -4. If any build test fails, the test run stops immediately with clear error messages +4. If any build test fails, `make test` stops immediately — the main suite (test/install + test/sql) never runs, since its results would be meaningless against a broken build + +NOTE: This also means a stale or simply wrong `+test/build/expected/*.out+` -- not just a genuinely broken build -- blocks the whole suite. See <<_results_build,results-build>> below for the supported way to refresh it, including how to handle a file that's *supposed* to show an error. **Directory structure:** @@ -128,27 +149,43 @@ This approach catches SQL syntax errors *before* running `CREATE EXTENSION`, giv When `CREATE EXTENSION` fails, PostgreSQL shows only "syntax error" with limited context. Running the SQL directly via `\i` shows the exact line and position of errors, making debugging much faster. +==== results-build + +Refreshes `test/build/expected/*.out` from the actual output of the last `test-build` run, mirroring <<_results,results>> for the main suite: + +---- +make results-build +---- + +`results-build` will *not* bless a file whose actual output contains an `ERROR:` line — accepting an errored build as the new expected baseline would defeat the point of test-build. It skips that file (leaving its existing expected output in place), tells you exactly which file and the command to bless it with, and exits non-zero so the skip can't go unnoticed. + +If your project intentionally exercises a build-time error (e.g. asserting a migration fails the way it should), bless that file by hand instead — this is a deliberate, supported case, not a workaround, and you'll need to repeat it by hand every time that file's output legitimately changes, since `results-build` will always skip it: + +---- +cp test/build/results/.out test/build/expected/.out +---- + === test/install Runs setup files before the main test suite within the same `pg_regress` invocation. This allows expensive one-time operations (like extension installation) to set up state that persists into the regular test files. **How it works:** 1. Place SQL files in `test/install/*.sql` -2. Place expected output alongside as `test/install/*.out` -3. A schedule file is auto-generated that lists install files with `../install/` relative paths -4. `pg_regress` processes the install schedule first, then runs regular test files — all in one invocation, so database state persists +2. A schedule file is auto-generated that lists install files with `../install/` relative paths +3. `pg_regress` processes the install schedule first, then runs regular test files — all in one invocation, so database state persists **Directory structure:** ---- test/install/ ├── *.sql # SQL setup files (checked in) -├── *.out # Expected output (checked in, alongside .sql) -├── .gitignore # Ignores pg_regress artifacts (*.out.diff) -└── schedule # GENERATED - auto-created by make +├── *.out # GENERATED, gitignored -- see WARNING below +└── schedule # GENERATED, gitignored - auto-created by make ---- -The `schedule` file is generated automatically and listed in `.gitignore`. Do not edit it. +The `schedule` file is generated automatically and listed in the top-level `.gitignore` that `setup.sh` installs. Do not edit it. `*.out` is gitignored too, for the reason explained below — don't commit it. + +**Ordering:** Install files run in plain byte-value (ASCII) order of their filenames, guaranteed regardless of filesystem or locale. If one install file depends on another having already run, encode that dependency in the filenames themselves (e.g. `01_roles.sql` before `02_extension.sql`). **Configuration:** @@ -169,6 +206,8 @@ Without `test/install`, each test file typically needs to run `CREATE EXTENSION` **Key detail:** Install files and regular tests run in a single `pg_regress` invocation. This means the database is NOT dropped between install and test phases — state created by install files persists into the main test suite. +WARNING: **`+test/install/*.out+` is never actually compared against anything.** Unlike every other test type pgxntool supports, the `test/install` directory's actual output is written to the exact same file as its expected output, so there is no diff — a wrong or changed result will never fail the build, no matter how much it changes. The *only* thing that still fails the build is a hard SQL error, and only if the file sets `ON_ERROR_STOP` (directly, or via `\i test/pgxntool/psql.sql`) — without it, psql prints the error, keeps going, and the file "passes" regardless. pgxntool enforces this by default (`_check-test-install-error-stop`, see `PGXNTOOL_ENABLE_TEST_INSTALL_ERROR_STOP_CHECK` to disable), but that only guarantees errors are caught — it does not give you real output validation. If you need actual output comparison, put that logic in `test/sql` instead (or use pgTap assertions from within the install file itself). + ==== Update & Upgrade (U&U) Testing Beyond validating a plain install, it's worth testing that your extension behaves correctly across the two transitions every extension with more than one release eventually goes through: @@ -202,7 +241,7 @@ NOTE: This pattern isn't (yet) native to pgxntool — you wire up the mode selec - https://github.com/Postgres-Extensions/pg_count_nulls/blob/9f088ac36f6345023bb5b3d20ad0e4aa6ade0d69/test/sql/extension_tests.sql[pg_count_nulls `test/sql/extension_tests.sql`] additionally demonstrates testing an extension installed into a non-default schema. It's a useful reference for that specific problem, but it also defines its assertions as plpgsql functions rather than plain pgTAP calls — a pattern that adds complexity of its own and isn't recommended as a model for U&U testing itself. === testdeps -This rule allows you to ensure certain actions have taken place before running tests. By default it has a single prerequisite, `pgtap`, which will attempt to install http://pgtap.org[pgtap] from PGXN. This depneds on having the pgxn client installed. +This rule allows you to ensure certain actions have taken place before running tests. By default it has a single prerequisite, `pgtap`, which installs http://pgtap.org[pgtap] from PGXN (via the pgxn client) if it isn't already filesystem-installed. `pgtap` is a no-op when `PGXNTOOL_ENABLE_PGXN_INSTALL=no`; see <<_pgxntool_enable_pgxn_install>>. You can add any other dependencies you want by simply adding another `testdeps` rule. For example: @@ -233,13 +272,15 @@ IMPORTANT: *`make results` requires manual verification first*. The correct work Never run `make results` without first verifying the test changes are correct. The `results` target copies files from `test/results/` to `test/expected/`, so running it blindly will make incorrect output become the new expected behavior. ==== verify-results safeguard -By default, `make results` will refuse to run if your tests are failing, so you can't accidentally promote incorrect output into the new expected results. Which failures it looks for is controlled by `PGXNTOOL_VERIFY_RESULTS_MODE`: +By default, `make results` will refuse to run if your tests are failing, so you can't accidentally promote incorrect output into the new expected results. Which failures it looks for is controlled by `PGXNTOOL_VERIFY_RESULTS_MODE`, which must be one of these two values (case-insensitive; anything else is a hard `make` error): -`pgtap` (the default):: Scans `test/results/*.out` for pgTAP `not ok` lines (ignoring `# TODO` items) and plan-count mismatches, then also checks `test/results/regression.diffs` for any output mismatch pgTAP itself wouldn't catch. -`diffs`:: Only checks whether `test/results/regression.diffs` exists. +`pgtap` (the default):: Scans `test/results/*.out` for pgTAP `not ok` lines (ignoring `# TODO` items) and plan-count mismatches, then also checks `test/regression.diffs` for any output mismatch pgTAP itself wouldn't catch. A test that has no expected output file yet is the one exception: it always shows up in `regression.diffs` (pgxntool creates an empty expected file for it, because `pg_regress` aborts on a missing one), so `make results` is allowed to create its first expected output — provided its own pgTAP output is clean and free of SQL errors. If a new test legitimately needs output containing a SQL error blessed, use `PGXNTOOL_ENABLE_VERIFY_RESULTS=no` for that one run. +`diffs`:: Only checks whether `test/regression.diffs` exists, so it can't create a new test's first expected output; use `pgtap` mode, or `PGXNTOOL_ENABLE_VERIFY_RESULTS=no` for that one run. If tests are failing, `make results` prints an explanation of what failed and exits without touching `test/expected/`; fix the failures, then re-run `make results`. +It also refuses when `pg_regress` didn't finish at all — an unreachable server, an interrupt — since `test/results/` then still holds an earlier run's output and blessing it would make that stale output the new expected results. + To select a mode explicitly: ---- # In your Makefile @@ -256,12 +297,22 @@ make PGXNTOOL_ENABLE_VERIFY_RESULTS=no results ---- === tag -`make tag` will create a git tag for the current version of your extension, as determined by the META.json file (generated from `META.in.json` — see <<_pgxn_distributions_vs_extensions>>), and push it to `origin`. The reason to do this is so you can always refer to the exact code that went into a released version. +`make tag` will create a git tag for the current version of your extension, as determined by the META.json file (generated from `META.in.json` — see <<_pgxn_distributions_vs_extensions>>), and push it to `$(PGXN_REMOTE)` (`origin` by default; see <<_pgxn_remote>>). The reason to do this is so you can always refer to the exact code that went into a released version. For where `tag`, `dist`, and `post-tag-version-bump` fit in a full release, see link:releasing-with-pgxntool.md[releasing-with-pgxntool.md]. If a tag for the current version already exists and points at your current commit, `make tag` does nothing — this makes it safe for other targets (like `dist`) to depend on `tag` without worrying about re-running it. If the existing tag points at a *different* commit — meaning you likely forgot to bump the version — you'll get an error. If you're certain you want to over-write the tag, you can do `make forcetag`, which removes the existing tag (via `make rmtag`) and creates a new one. WARNING: You will be very unhappy if you forget to update the .control file for your extension! There is an https://github.com/Postgres-Extensions/pgxntool/issues/1[open issue] to improve this. +=== tree-is-clean +`make tree-is-clean` fails, printing `git status`, if the working tree has uncommitted or untracked changes. `tag` and `post-tag-version-bump` depend on it. + +[[_post_tag_version_bump]] +=== post-tag-version-bump + +Committing versioned SQL files (see <<_committing_version_files>>) means ongoing development after a release can silently regenerate and overwrite the file that was just released, since `make` always regenerates whatever file matches the current `default_version`. `make post-tag-version-bump` bumps each extension's `default_version` to a placeholder alias -- `stable` by default -- so a subsequent `make` instead regenerates `sql/{ext}--stable.sql`, keeping the released file frozen. + +Run `make post-tag-version-bump` yourself as an explicit step in your own release process (see link:releasing-with-pgxntool.md[releasing-with-pgxntool.md] for a recommended one), right after the tag you're actually releasing has been created and pushed. See `PGXNTOOL_POST_TAG_VERSION` below to customize the placeholder value. + === dist `make dist` will create a .zip file for your current version that you can upload to PGXN. It first runs `tag` (creating/pushing the version's git tag as described above if needed), then uses `git archive` at that tag to build the .zip file, so the archive always matches the exact tagged commit. The file is named after the PGXN name and version (the top-level "name" and "version" attributes in META.json, generated from `META.in.json`). The .zip file is placed in the *parent* directory so as not to clutter up your git repo. @@ -282,12 +333,18 @@ TIP: The actual work is done by `pgxntool/pgxntool-sync.sh`, so you can run it d TIP: `pgxntool-sync.sh` itself delegates the 3-way merge of `setup.sh`-copied files to `pgxntool/update-setup-files.sh `, which you can also run directly -- for example, to redo or debug just the merge step without pulling again. `` is the pgxntool subtree commit that was current *before* the sync you want to re-merge. -TIP: There is also a `pgxntool-sync-%` rule if you need to do more advanced things. `make pgxntool-sync-` pulls from the ` ` defined by the `pgxntool-sync-` make variable. +TIP: There is also a `pgxntool-sync-%` rule if you need to do more advanced things. `make pgxntool-sync-` pulls from the ` ` defined by the `pgxntool-sync-` make variable. Three are built in: + +`pgxntool-sync-master`:: `https://github.com/Postgres-Extensions/pgxntool.git master` -- the unreleased development branch. +`pgxntool-sync-local`:: `../pgxntool release` -- the `release` branch of a sibling `../pgxntool` checkout. +`pgxntool-sync-local-master`:: `../pgxntool master` -- the `master` branch of that sibling checkout. + +CAUTION: Syncing from anything other than a release can add unwanted history to your repo and make later merges harder to resolve. === pgxntool-version `make pgxntool-version` prints the version of the embedded pgxntool copy, read from the first line of `pgxntool/HISTORY.asc`. That line is "STABLE" instead of a version number if this copy was synced from an unreleased commit rather than a tagged release. -TIP: The actual work is done by `pgxntool/bin/version`, so you can run it directly (`pgxntool/bin/version`) if you'd rather not go through `make`. +TIP: The actual work is done by `pgxntool/bin/version`, so you can run it directly (`pgxntool/bin/version`) if you'd rather not go through `make`. It optionally takes a `` argument to read instead of `pgxntool/HISTORY.asc`. It exits non-zero if that file is missing, empty, or its first line is neither `STABLE` nor an `X.Y.Z` version. === list `make list` prints every make target defined in your project — including ones provided by PGXS and pgxntool, not just ones you wrote — one per line. Useful for discovering what's available without reading through the Makefile. @@ -310,12 +367,14 @@ Generates pg_tle (Trusted Language Extensions) registration SQL files for deploy `make pgtle` generates SQL files in `pg_tle/` subdirectories organized by pg_tle version ranges. For version range details, see `pgtle_versions.md`. -Set `PGXNTOOL_PGTLE_VERSION` on the command line to limit generation to the single version range that value falls into, instead of every known range: +Set `PGXNTOOL_PGTLE_VERSION` on the command line to a range name (`1.0.0-1.4.0`, `1.4.0-1.5.0` or `1.5.0+`) to generate only that range instead of every known range: ---- -make pgtle PGXNTOOL_PGTLE_VERSION=1.5.0 +make pgtle PGXNTOOL_PGTLE_VERSION=1.5.0+ ---- +The value must be one of those range names exactly; it is not mapped from a plain pg_tle version. Any other value is rejected with an error listing the valid ranges; for a plain pg_tle version (e.g. `1.5.0`), the error also names the range it falls in. + === check-pgtle Checks if pg_tle is installed and reports the version. This target: - Reports the version from `pg_extension` if `CREATE EXTENSION pg_tle` has been run in the database @@ -346,6 +405,25 @@ After running `make run-pgtle`, you can create your extension in the database: CREATE EXTENSION "your-extension-name"; ---- +=== print-pgtle +Prints the generated pg_tle registration SQL to stdout instead of running it, for consumers building a combined multi-extension install file. This target: +- Depends on `pgtle`, so the SQL files are (re)generated first +- Selects the version directory from `PGXNTOOL_PGTLE_TARGET_VERSION` if set (an actual pg_tle version like `1.5.2`, not a range like `PGXNTOOL_PGTLE_VERSION` uses), otherwise from the installed pg_tle version (queried the same way `run-pgtle` does) +- Errors if neither is available (no `PGXNTOOL_PGTLE_TARGET_VERSION` and pg_tle isn't installed) +- When invoked recursively (`$(MAKE) -C ... print-pgtle`) for stdout capture, pass `--no-print-directory` -- see warning below + +---- +make print-pgtle +make print-pgtle PGXNTOOL_PGTLE_TARGET_VERSION=1.5.2 +---- + +A dependency's registration SQL can be pulled into a combined file without the caller needing to know its extension name or which `pg_tle/` version-range directory applies: +---- +$(MAKE) --no-print-directory -C ../deps/cat_tools print-pgtle >> pgtle-all.sql +---- + +WARNING: Always pass `--no-print-directory` when invoking `print-pgtle` recursively from another Makefile's recipe (as above). GNU Make auto-prints `Entering directory`/`Leaving directory` announcements to stdout for recursive invocations, which silently corrupts a redirected combined SQL file with garbage lines. `--no-print-directory` suppresses them. + == Version-Specific SQL Files PGXNtool automatically generates version-specific SQL files from your base SQL file. These files follow the pattern `sql/{extension}--{version}.sql` and are used by PostgreSQL's extension system to install specific versions of your extension. @@ -384,7 +462,7 @@ To change the version of one of your extensions: 1. Update `default_version` in that extension's `.control` file 2. Run `make` to regenerate the version-specific file 3. Update that extension's `provides.{extension}.version` in `META.in.json` to match by hand, and regenerate `META.json` (`make META.json`) — pgxntool won't do this for you -4. Update `META.in.json`'s top-level `version` too if you're also cutting a new distribution release +4. Update `META.in.json`'s top-level `version` too if you're also cutting a new distribution release (see link:releasing-with-pgxntool.md[releasing-with-pgxntool.md] for the full release checklist) === Committing Version Files @@ -509,7 +587,7 @@ PGXNtool replaces each `$(ASCIIDOC_EXTS)` in `$(ASCIIDOC_FILES)` with `html`. The result is appended to `ASCIIDOC_HTML` using `+=`. === Document Rules -If Asciidoc is found (or `$(ASCIIDOC)` is set), `html` is added as a dependency of the `all` target, which both `install` and `installcheck` depend on — so docs get built for install and test, but only if Asciidoc is available. +If Asciidoc is found (or `$(ASCIIDOC)` is set), `html` is added as a dependency of the `all` target, which `install` depends on (and `installcheck` reaches through `install` when `PGXNTOOL_ENABLE_FS_INSTALL=yes`) — so docs get built for install and test, but only if Asciidoc is available. The `dist` rule always depends on `html` directly, regardless of whether Asciidoc was found, to ensure html files are up-to-date before creating a distribution. The `html` rule simply depends on `$(ASCIIDOC_HTML). @@ -554,7 +632,7 @@ PGXNtool appends *all* files found in all `$(DOC_DIRS)` to `DOCS`. [[_pg_tle_Support]] pgxntool can generate link:https://github.com/aws/pg_tle[pg_tle (Trusted Language Extensions)] registration SQL for deploying PostgreSQL extensions in managed environments like AWS RDS and Aurora where filesystem access is not available. -For make targets, see: <<_pgtle>>, <<_check_pgtle>>, <<_run_pgtle>>. +For make targets, see: <<_pgtle>>, <<_check_pgtle>>, <<_run_pgtle>>, <<_print_pgtle>>. === What is pg_tle? @@ -678,13 +756,21 @@ Default: `origin`. The git remote used by `tag`, `rmtag`, `forcetag`, and `dist` make dist PGXN_REMOTE=upstream ---- +=== PGXNTOOL_POST_TAG_VERSION + +Default: `stable`. The placeholder value `make post-tag-version-bump` sets `default_version` to. It must be non-empty and contain only ASCII letters, digits, `.` and `-`; anything else makes `post-tag-version-bump` fail. Not valid semver, but PostgreSQL doesn't require semver for extension versions. If you override this, also update the `sql/*--stable.sql` line and its `!sql/*--*--stable.sql` negation (which keeps the `{old}--stable` update script tracked) in your `.gitignore` to match. + === ASCIIDOC Default: auto-detected, the first of `asciidoctor` or `asciidoc` found on `PATH`. Path to the Asciidoc processor used to build `.html` files from `$(ASCIIDOC_EXTS)` source files. Override if the processor you want isn't first on `PATH`, or isn't on `PATH` at all. See <<_document_handling>>. === PGXNTOOL_PGTLE_VERSION -Default: unset (generates every known pg_tle version range). Set on the command line to limit `make pgtle` to the single version range this value falls into. Not named `PGTLE_VERSION`: make auto-imports same-named environment variables, and that name collided silently with CI jobs that set a `PGTLE_VERSION` env var for an unrelated purpose (which pg_tle to test against). See <<_pgtle>>. +Default: unset (generates every known pg_tle version range). Set on the command line to a range name (`1.0.0-1.4.0`, `1.4.0-1.5.0` or `1.5.0+`, exactly) to limit `make pgtle` to that one range; any other value is rejected with an error. Not named `PGTLE_VERSION`: make auto-imports same-named environment variables, and that name collided silently with CI jobs that set a `PGTLE_VERSION` env var for an unrelated purpose (which pg_tle to test against). See <<_pgtle>>. + +=== PGXNTOOL_PGTLE_TARGET_VERSION + +Default: unset (falls back to the installed pg_tle version). Set on the command line to an actual pg_tle version (e.g. `1.5.2`, not a range) to select which `pg_tle/` directory `make print-pgtle` reads from, without needing a database connection. Distinct from `PGXNTOOL_PGTLE_VERSION`, which holds a range and controls generation rather than selection. See <<_print_pgtle>>. === PG_CONFIG @@ -700,11 +786,11 @@ Default: `$(TESTDIR)`. Directory `pg_regress` writes actual test output to -- `$ === PGXNTOOL_VERIFY_RESULTS_MODE -Default: `pgtap`. Controls how the <<_verify_results_safeguard,verify-results safeguard>> decides whether tests are failing. +Default: `pgtap`. Controls how the <<_verify_results_safeguard,verify-results safeguard>> decides whether tests are failing. Must be `pgtap` or `diffs` (case-insensitive) when given explicitly; any other value, including an empty one, is a hard `make` error. ==== pgtap -Scans `test/results/*.out` for pgTap `not ok` lines and plan mismatches, falling back to checking for `regression.diffs` too. Use this mode when your test suite uses pgTap. +Scans `test/results/*.out` for pgTap `not ok` lines and plan mismatches, falling back to checking `regression.diffs` too — except for a test that has no expected output yet, whose first expected file `make results` is allowed to create. Use this mode when your test suite uses pgTap. ==== diffs @@ -718,24 +804,43 @@ Default: auto-detected -- `yes` if `test/build/*.sql` files exist, `no` otherwis Default: auto-detected -- `yes` if `test/install/*.sql` files exist, `no` otherwise. Enables or disables the <<_testinstall,test/install>> schedule-based setup feature. Same explicit-override semantics as `PGXNTOOL_ENABLE_TEST_BUILD`. +=== PGXNTOOL_ENABLE_TEST_INSTALL_ERROR_STOP_CHECK * + +Default: `yes`. Enables or disables a build-time check that every `test/install/*.sql` file either includes `test/pgxntool/psql.sql` (which sets `ON_ERROR_STOP`) or has its own `\set ON_ERROR_STOP` or `\unset ON_ERROR_STOP` command; a file that sets `ON_ERROR_STOP` itself, to any value, is assumed to know what it's doing -- see <<_testinstall,test/install>> for why this is the only thing that makes a hard error in one of those files actually fail the build. Set to `no` to disable the check. + +=== PGXNTOOL_ENABLE_FS_INSTALL * + +Default: `yes`. Controls whether `install` is a prerequisite of `test`/`verify-results`, and whether `installcheck` and <<_test_build,test-build>> depend on `install`. Set to `no` for "existing mode" testing, where the extension under test was deployed some other way (e.g. registered via pg_tle instead of the filesystem, or installed by a binary pg_upgrade) -- with this set, `make test`/`make installcheck`/`make verify-results` run against whatever is already installed instead of filesystem-installing first. + +=== PGXNTOOL_ENABLE_PGXN_INSTALL * + +Default: follows `PGXNTOOL_ENABLE_FS_INSTALL` (so it's off automatically whenever filesystem install is off), but can be set independently. Controls whether `installcheck` auto-installs the pgtap dependency via `pgxn install pgtap --sudo` when it isn't already filesystem-installed. Set to `no` to make this a complete no-op, e.g. when pgtap is already provided some other way and you don't want a `sudo pgxn install` invoked as a side effect of testing. + === PGXNTOOL_ENABLE_VERIFY_RESULTS * Default: `yes`. Enables or disables the <<_verify_results_safeguard,verify-results safeguard>> that blocks `make results` when tests are failing. Setting it to empty on the command line (`make PGXNTOOL_ENABLE_VERIFY_RESULTS= results`) also disables it. === PGXNTOOL_ENABLE_CHECK_STALE_EXPECTED * -Default: `yes`. Enables or disables the check-stale-expected safeguard, which fails `make test` if `test/expected/` (or `test/build/expected/`) contains a `.out` file with no corresponding `.sql` file -- catching a stale file left behind after a test was renamed or removed. Set to `no` to make the check a complete no-op (it's dropped from `TEST_DEPS` entirely). +Default: `yes`. Enables or disables the `_check-stale-expected` safeguard, which fails `make test` if `test/expected/` (or `test/build/expected/`) contains a `.out` file with no corresponding `.sql` file -- catching a stale file left behind after a test was renamed or removed. Set to `no` to make the check a complete no-op (it's dropped from `TEST_DEPS` entirely). This is also the supported pattern for disabling one specific optional pgxntool feature: a documented `PGXNTOOL_ENABLE_*` variable, gating both the target's registration into `TEST_DEPS`/etc. and (where applicable) the target's own definition. Cleanly replacing or overriding an arbitrary pgxntool-generated recipe with your own entirely is a separate, larger, and not-yet-decided design question -- see https://github.com/Postgres-Extensions/pgxntool/issues/30[issue #30] (two proposed approaches) -- and is *not* what this mechanism solves. === PGXNTOOL_CHECK_EXPECTED_FILE_TYPES * -Default: `yes`. Sub-check of check-stale-expected, independent of `PGXNTOOL_ENABLE_CHECK_STALE_EXPECTED`: fails (with a distinct error message and exit code from the orphaned-`.out` check) if `test/expected/` (or `test/build/expected/`) contains any file that isn't `*.out`. Set to `no` to disable just this sub-check while leaving the orphaned-`.out` check active. +Default: `yes`. Sub-check of `_check-stale-expected`, independent of `PGXNTOOL_ENABLE_CHECK_STALE_EXPECTED`: fails (with a distinct error message and exit code from the orphaned-`.out` check) if `test/expected/` (or `test/build/expected/`) contains any file that isn't `*.out`. Set to `no` to disable just this sub-check while leaving the orphaned-`.out` check active. === PGXNTOOL_NO_PGXS_INCLUDE Default: unset (PGXS is included normally). Skips including PGXS (`$(PGXS)`) entirely. This is only for advanced scenarios where you need to manage the PGXS include yourself; most projects should never set this. +== General PostgreSQL Notes + +link:POSTGRES-NOTES.asc[POSTGRES-NOTES.asc] collects guidance about PostgreSQL itself +rather than about PGXNtool -- things that come up constantly while writing an extension +but that PGXNtool has no part in. It currently covers which `.control` file parameters +(`superuser`, `trusted`) control who is allowed to install your extension. + == Copyright Copyright (c) 2026 Jim Nasby diff --git a/pgxntool/README.html b/pgxntool/README.html index ba71c2a..10a92d7 100644 --- a/pgxntool/README.html +++ b/pgxntool/README.html @@ -449,21 +449,25 @@

PGXNtool

  • 4. make targets
  • 5. Version-Specific SQL Files @@ -499,21 +503,27 @@

    PGXNtool

  • 8. Configuration Variables
  • -
  • 9. Copyright
  • +
  • 9. General PostgreSQL Notes
  • +
  • 10. Copyright
  • @@ -605,13 +615,51 @@

    4.1. html

    -

    This will build any .html files that can be created. See [_Document_Handling].

    +

    This will build any .html files that can be created. See Document Handling.

    -

    4.2. test

    +

    4.2. check-duplicate-docs

    -

    Runs your extension’s test suite: installs the extension and runs it through PGXS’s installcheck, first pulling in anything you’ve hooked into testdeps and, if enabled, sanity-checking your test SQL via test-build.

    +

    Warns if two $(DOCS) entries would install to the same filename, which would otherwise fail make install.

    +
    +
    +

    all depends on this, so it also runs as part of a plain make, make install and make test; you only need to invoke it directly to check without building anything.

    +
    +
    +
    +

    4.3. test

    +
    +

    Runs your extension’s test suite: filesystem-installs the extension (unless PGXNTOOL_ENABLE_FS_INSTALL=no) and runs it through PGXS’s installcheck, first pulling in anything you’ve hooked into testdeps and, if enabled, sanity-checking your test SQL via test-build.

    +
    +
    +

    make test also fails on two safeguard checks:

    +
    +
    + +
    +
    +

    make test runs pg_regress in up to two separate passes:

    +
    +
    +
      +
    1. +

      If test-build is enabled, it runs first, in its own isolated pass over test/build/*.sql. If it fails, the second pass never happens — there’s no point checking install/query behavior against a build that doesn’t even come up cleanly.

      +
    2. +
    3. +

      The main pass follows: any test/install files, then your regular test/sql/*.sql files, together in a single pg_regress invocation (so state the install files create persists into the regular tests).

      +
    4. +
    +
    +
    +

    Within each pass, files run in byte-value (ASCII) order of their filenames.

    Whether test-build runs is controlled by the PGXNTOOL_ENABLE_TEST_BUILD variable — see test-build for what it does and how to turn it on/off.

    @@ -635,14 +683,29 @@

    Note

    +test exits non-zero (after printing regression.diffs) if any test fails, so CI and other automation can rely on its exit code. + + + +
    +
    + + + +
    +
    Note
    +
    While you can still run make installcheck or any other valid PGXS make target directly, it’s recommended to use make test when using pgxntool. The test target ensures proper test isolation and correct dependency installation.
    +
    -

    4.3. test-build

    +

    4.4. test-build

    Validates that extension SQL files are syntactically correct before running the full test suite. This feature runs SQL files from test/build/ through pg_regress, providing better error messages than CREATE EXTENSION failures when there are syntax errors in your extension code.

    @@ -661,10 +724,22 @@

    These files run through pg_regress before make test runs the main test suite

  • -

    If any build test fails, the test run stops immediately with clear error messages

    +

    If any build test fails, make test stops immediately — the main suite (test/install + test/sql) never runs, since its results would be meaningless against a broken build

  • +
    + + + + + +
    +
    Note
    +
    +This also means a stale or simply wrong test/build/expected/*.out — not just a genuinely broken build — blocks the whole suite. See results-build below for the supported way to refresh it, including how to handle a file that’s supposed to show an error. +
    +

    Directory structure:

    @@ -749,9 +824,31 @@

    When CREATE EXTENSION fails, PostgreSQL shows only "syntax error" with limited context. Running the SQL directly via \i shows the exact line and position of errors, making debugging much faster.

    +
    +

    4.4.1. results-build

    +
    +

    Refreshes test/build/expected/*.out from the actual output of the last test-build run, mirroring results for the main suite:

    +
    +
    +
    +
    make results-build
    +
    +
    +
    +

    results-build will not bless a file whose actual output contains an ERROR: line — accepting an errored build as the new expected baseline would defeat the point of test-build. It skips that file (leaving its existing expected output in place), tells you exactly which file and the command to bless it with, and exits non-zero so the skip can’t go unnoticed.

    +
    +
    +

    If your project intentionally exercises a build-time error (e.g. asserting a migration fails the way it should), bless that file by hand instead — this is a deliberate, supported case, not a workaround, and you’ll need to repeat it by hand every time that file’s output legitimately changes, since results-build will always skip it:

    +
    +
    +
    +
    cp test/build/results/<name>.out test/build/expected/<name>.out
    +
    +
    +
    -

    The schedule file is generated automatically and listed in .gitignore. Do not edit it.

    +

    The schedule file is generated automatically and listed in the top-level .gitignore that setup.sh installs. Do not edit it. *.out is gitignored too, for the reason explained below — don’t commit it.

    +
    +
    +

    Ordering: Install files run in plain byte-value (ASCII) order of their filenames, guaranteed regardless of filesystem or locale. If one install file depends on another having already run, encode that dependency in the filenames themselves (e.g. 01_roles.sql before 02_extension.sql).

    +
    + + + + + +
    +
    Warning
    +
    +test/install/*.out is never actually compared against anything. Unlike every other test type pgxntool supports, the test/install directory’s actual output is written to the exact same file as its expected output, so there is no diff — a wrong or changed result will never fail the build, no matter how much it changes. The only thing that still fails the build is a hard SQL error, and only if the file sets ON_ERROR_STOP (directly, or via \i test/pgxntool/psql.sql) — without it, psql prints the error, keeps going, and the file "passes" regardless. pgxntool enforces this by default (_check-test-install-error-stop, see PGXNTOOL_ENABLE_TEST_INSTALL_ERROR_STOP_CHECK to disable), but that only guarantees errors are caught — it does not give you real output validation. If you need actual output comparison, put that logic in test/sql instead (or use pgTap assertions from within the install file itself). +
    +
    -

    4.4.1. Update & Upgrade (U&U) Testing

    +

    4.5.1. Update & Upgrade (U&U) Testing

    Beyond validating a plain install, it’s worth testing that your extension behaves correctly across the two transitions every extension with more than one release eventually goes through:

    @@ -900,9 +1008,9 @@

    -

    4.5. testdeps

    +

    4.6. testdeps

    -

    This rule allows you to ensure certain actions have taken place before running tests. By default it has a single prerequisite, pgtap, which will attempt to install pgtap from PGXN. This depneds on having the pgxn client installed.

    +

    This rule allows you to ensure certain actions have taken place before running tests. By default it has a single prerequisite, pgtap, which installs pgtap from PGXN (via the pgxn client) if it isn’t already filesystem-installed. pgtap is a no-op when PGXNTOOL_ENABLE_PGXN_INSTALL=no; see PGXNTOOL_ENABLE_PGXN_INSTALL *.

    You can add any other dependencies you want by simply adding another testdeps rule. For example:

    @@ -936,7 +1044,7 @@

    -

    4.6. results

    +

    4.7. results

    Because make test ultimately runs installcheck, it’s using the Postgres test suite. Unfortunately, that suite is based on running diff between a raw output file and expected results. I STRONGLY recommend you use pgTap instead! With pgTap, it’s MUCH easier to determine whether a test is passing or not - tests explicitly pass or fail rather than requiring you to examine diff output. The extra effort of learning pgTap will quickly pay for itself. This example might help get you started.

    @@ -972,19 +1080,19 @@

    -

    4.6.1. verify-results safeguard

    +

    4.7.1. verify-results safeguard

    -

    By default, make results will refuse to run if your tests are failing, so you can’t accidentally promote incorrect output into the new expected results. Which failures it looks for is controlled by PGXNTOOL_VERIFY_RESULTS_MODE:

    +

    By default, make results will refuse to run if your tests are failing, so you can’t accidentally promote incorrect output into the new expected results. Which failures it looks for is controlled by PGXNTOOL_VERIFY_RESULTS_MODE, which must be one of these two values (case-insensitive; anything else is a hard make error):

    pgtap (the default)
    -

    Scans test/results/*.out for pgTAP not ok lines (ignoring # TODO items) and plan-count mismatches, then also checks test/results/regression.diffs for any output mismatch pgTAP itself wouldn’t catch.

    +

    Scans test/results/*.out for pgTAP not ok lines (ignoring # TODO items) and plan-count mismatches, then also checks test/regression.diffs for any output mismatch pgTAP itself wouldn’t catch. A test that has no expected output file yet is the one exception: it always shows up in regression.diffs (pgxntool creates an empty expected file for it, because pg_regress aborts on a missing one), so make results is allowed to create its first expected output — provided its own pgTAP output is clean and free of SQL errors. If a new test legitimately needs output containing a SQL error blessed, use PGXNTOOL_ENABLE_VERIFY_RESULTS=no for that one run.

    diffs
    -

    Only checks whether test/results/regression.diffs exists.

    +

    Only checks whether test/regression.diffs exists, so it can’t create a new test’s first expected output; use pgtap mode, or PGXNTOOL_ENABLE_VERIFY_RESULTS=no for that one run.

    @@ -992,6 +1100,9 @@

    +

    It also refuses when pg_regress didn’t finish at all — an unreachable server, an interrupt — since test/results/ then still holds an earlier run’s output and blessing it would make that stale output the new expected results.

    +

    +

    To select a mode explicitly:

    @@ -1015,9 +1126,9 @@

    -

    4.7. tag

    +

    4.8. tag

    -

    make tag will create a git tag for the current version of your extension, as determined by the META.json file (generated from META.in.json — see PGXN Distributions vs. Extensions), and push it to origin. The reason to do this is so you can always refer to the exact code that went into a released version.

    +

    make tag will create a git tag for the current version of your extension, as determined by the META.json file (generated from META.in.json — see PGXN Distributions vs. Extensions), and push it to $(PGXN_REMOTE) (origin by default; see PGXN_REMOTE). The reason to do this is so you can always refer to the exact code that went into a released version. For where tag, dist, and post-tag-version-bump fit in a full release, see releasing-with-pgxntool.md.

    If a tag for the current version already exists and points at your current commit, make tag does nothing — this makes it safe for other targets (like dist) to depend on tag without worrying about re-running it. If the existing tag points at a different commit — meaning you likely forgot to bump the version — you’ll get an error. If you’re certain you want to over-write the tag, you can do make forcetag, which removes the existing tag (via make rmtag) and creates a new one.

    @@ -1036,7 +1147,22 @@

    4.

    -

    4.8. dist

    +

    4.9. tree-is-clean

    +
    +

    make tree-is-clean fails, printing git status, if the working tree has uncommitted or untracked changes. tag and post-tag-version-bump depend on it.

    +
    +
    +
    +

    4.10. post-tag-version-bump

    +
    +

    Committing versioned SQL files (see Committing Version Files) means ongoing development after a release can silently regenerate and overwrite the file that was just released, since make always regenerates whatever file matches the current default_version. make post-tag-version-bump bumps each extension’s default_version to a placeholder alias — stable by default — so a subsequent make instead regenerates sql/{ext}--stable.sql, keeping the released file frozen.

    +
    +
    +

    Run make post-tag-version-bump yourself as an explicit step in your own release process (see releasing-with-pgxntool.md for a recommended one), right after the tag you’re actually releasing has been created and pushed. See PGXNTOOL_POST_TAG_VERSION below to customize the placeholder value.

    +
    +
    +
    +

    4.11. dist

    make dist will create a .zip file for your current version that you can upload to PGXN. It first runs tag (creating/pushing the version’s git tag as described above if needed), then uses git archive at that tag to build the .zip file, so the archive always matches the exact tagged commit. The file is named after the PGXN name and version (the top-level "name" and "version" attributes in META.json, generated from META.in.json). The .zip file is placed in the parent directory so as not to clutter up your git repo.

    @@ -1069,9 +1195,9 @@

    -

    4.9. pgxntool-sync

    +

    4.12. pgxntool-sync

    -

    This rule will pull down the latest released version of PGXNtool via git subtree pull and then reconcile the files setup.sh copied into your project (.gitignore, test/deps.sql) with a 3-way merge.

    +

    This rule will pull down the latest released version of PGXNtool via git subtree pull and then reconcile the files setup.sh copied into your project (.gitignore, test/deps.sql) with a 3-way merge (it also verifies the test/pgxntool symlink, recreating it if missing).

    @@ -1128,14 +1254,42 @@

    Tip

    + +
    -There is also a pgxntool-sync-% rule if you need to do more advanced things. make pgxntool-sync-<name> pulls from the <repo> <ref> defined by the pgxntool-sync-<name> make variable. +There is also a pgxntool-sync-% rule if you need to do more advanced things. make pgxntool-sync-<name> pulls from the <repo> <ref> defined by the pgxntool-sync-<name> make variable. Three are built in: +
    +
    +
    +
    +
    pgxntool-sync-master
    +
    +

    https://github.com/Postgres-Extensions/pgxntool.git master — the unreleased development branch.

    +
    +
    pgxntool-sync-local
    +
    +

    ../pgxntool release — the release branch of a sibling ../pgxntool checkout.

    +
    +
    pgxntool-sync-local-master
    +
    +

    ../pgxntool master — the master branch of that sibling checkout.

    +
    +
    +
    +
    + + + +
    +
    Caution
    +
    +Syncing from anything other than a release can add unwanted history to your repo and make later merges harder to resolve.
    -

    4.10. pgxntool-version

    +

    4.13. pgxntool-version

    make pgxntool-version prints the version of the embedded pgxntool copy, read from the first line of pgxntool/HISTORY.asc. That line is "STABLE" instead of a version number if this copy was synced from an unreleased commit rather than a tagged release.

    @@ -1146,26 +1300,26 @@

    Tip

    -The actual work is done by pgxntool/bin/version, so you can run it directly (pgxntool/bin/version) if you’d rather not go through make. +The actual work is done by pgxntool/bin/version, so you can run it directly (pgxntool/bin/version) if you’d rather not go through make. It optionally takes a <history-file> argument to read instead of pgxntool/HISTORY.asc. It exits non-zero if that file is missing, empty, or its first line is neither STABLE nor an X.Y.Z version.
    -

    4.11. list

    +

    4.14. list

    make list prints every make target defined in your project — including ones provided by PGXS and pgxntool, not just ones you wrote — one per line. Useful for discovering what’s available without reading through the Makefile.

    -

    4.12. print-%

    +

    4.15. print-%

    make print-VARNAME prints the current value (and origin) of any make variable, e.g. make print-PGXNVERSION. Useful for debugging why a variable isn’t set to what you expect.

    -

    4.13. distclean

    +

    4.16. distclean

    make distclean removes generated configuration files (META.json — generated from META.in.json — plus meta.mk and control.mk) that survive a normal make clean.

    @@ -1191,7 +1345,7 @@

    -
    make pgtle PGXNTOOL_PGTLE_VERSION=1.5.0
    +
    make pgtle PGXNTOOL_PGTLE_VERSION=1.5.0+
    +
    +

    The value must be one of those range names exactly; it is not mapped from a plain pg_tle version. Any other value is rejected with an error listing the valid ranges; for a plain pg_tle version (e.g. 1.5.0), the error also names the range it falls in.

    +
    -

    4.15. check-pgtle

    +

    4.18. check-pgtle

    Checks if pg_tle is installed and reports the version. This target: - Reports the version from pg_extension if CREATE EXTENSION pg_tle has been run in the database @@ -1224,7 +1381,7 @@

    -

    4.16. run-pgtle

    +

    4.19. run-pgtle

    Registers all extensions with pg_tle by executing the generated pg_tle registration SQL files in a PostgreSQL database. This target: - Requires pg_tle to be installed in the target database. This isn’t a make-level dependency on check-pgtle — pgtle.sh checks it itself when it runs, and will tell you to run make check-pgtle if pg_tle isn’t there @@ -1260,6 +1417,42 @@

    +
    +

    4.20. print-pgtle

    +
    +

    Prints the generated pg_tle registration SQL to stdout instead of running it, for consumers building a combined multi-extension install file. This target: +- Depends on pgtle, so the SQL files are (re)generated first +- Selects the version directory from PGXNTOOL_PGTLE_TARGET_VERSION if set (an actual pg_tle version like 1.5.2, not a range like PGXNTOOL_PGTLE_VERSION uses), otherwise from the installed pg_tle version (queried the same way run-pgtle does) +- Errors if neither is available (no PGXNTOOL_PGTLE_TARGET_VERSION and pg_tle isn’t installed) +- When invoked recursively ($(MAKE) -C …​ print-pgtle) for stdout capture, pass --no-print-directory — see warning below

    +
    +
    +
    +
    make print-pgtle
    +make print-pgtle PGXNTOOL_PGTLE_TARGET_VERSION=1.5.2
    +
    +
    +
    +

    A dependency’s registration SQL can be pulled into a combined file without the caller needing to know its extension name or which pg_tle/ version-range directory applies:

    +
    +
    +
    +
    $(MAKE) --no-print-directory -C ../deps/cat_tools print-pgtle >> pgtle-all.sql
    +
    +
    +
    + + + + + +
    +
    Warning
    +
    +Always pass --no-print-directory when invoking print-pgtle recursively from another Makefile’s recipe (as above). GNU Make auto-prints Entering directory/Leaving directory announcements to stdout for recursive invocations, which silently corrupts a redirected combined SQL file with garbage lines. --no-print-directory suppresses them. +
    +
    +
    @@ -1330,7 +1523,7 @@

    releasing-with-pgxntool.md for the full release checklist)

    @@ -1573,7 +1766,7 @@

    <

    6.2. Document Rules

    -

    If Asciidoc is found (or $(ASCIIDOC) is set), html is added as a dependency of the all target, which both install and installcheck depend on — so docs get built for install and test, but only if Asciidoc is available. +

    If Asciidoc is found (or $(ASCIIDOC) is set), html is added as a dependency of the all target, which install depends on (and installcheck reaches through install when PGXNTOOL_ENABLE_FS_INSTALL=yes) — so docs get built for install and test, but only if Asciidoc is available. The dist rule always depends on html directly, regardless of whether Asciidoc was found, to ensure html files are up-to-date before creating a distribution.

    @@ -1649,7 +1842,7 @@

    pgxntool can generate pg_tle (Trusted Language Extensions) registration SQL for deploying PostgreSQL extensions in managed environments like AWS RDS and Aurora where filesystem access is not available.

    -

    For make targets, see: pgtle, check-pgtle, run-pgtle.

    +

    For make targets, see: pgtle, check-pgtle, run-pgtle, print-pgtle.

    7.1. What is pg_tle?

    @@ -1881,88 +2074,118 @@

    -

    8.2. ASCIIDOC

    +

    8.2. PGXNTOOL_POST_TAG_VERSION

    +
    +

    Default: stable. The placeholder value make post-tag-version-bump sets default_version to. It must be non-empty and contain only ASCII letters, digits, . and -; anything else makes post-tag-version-bump fail. Not valid semver, but PostgreSQL doesn’t require semver for extension versions. If you override this, also update the sql/--stable.sql line and its !sql/--*--stable.sql negation (which keeps the {old}--stable update script tracked) in your .gitignore to match.

    +
    +
    +
    +

    8.3. ASCIIDOC

    Default: auto-detected, the first of asciidoctor or asciidoc found on PATH. Path to the Asciidoc processor used to build .html files from $(ASCIIDOC_EXTS) source files. Override if the processor you want isn’t first on PATH, or isn’t on PATH at all. See Document Handling.

    -

    8.3. PGXNTOOL_PGTLE_VERSION

    +

    8.4. PGXNTOOL_PGTLE_VERSION

    +
    +

    Default: unset (generates every known pg_tle version range). Set on the command line to a range name (1.0.0-1.4.0, 1.4.0-1.5.0 or 1.5.0+, exactly) to limit make pgtle to that one range; any other value is rejected with an error. Not named PGTLE_VERSION: make auto-imports same-named environment variables, and that name collided silently with CI jobs that set a PGTLE_VERSION env var for an unrelated purpose (which pg_tle to test against). See pgtle.

    +
    +
    +
    +

    8.5. PGXNTOOL_PGTLE_TARGET_VERSION

    -

    Default: unset (generates every known pg_tle version range). Set on the command line to limit make pgtle to the single version range this value falls into. Not named PGTLE_VERSION: make auto-imports same-named environment variables, and that name collided silently with CI jobs that set a PGTLE_VERSION env var for an unrelated purpose (which pg_tle to test against). See pgtle.

    +

    Default: unset (falls back to the installed pg_tle version). Set on the command line to an actual pg_tle version (e.g. 1.5.2, not a range) to select which pg_tle/ directory make print-pgtle reads from, without needing a database connection. Distinct from PGXNTOOL_PGTLE_VERSION, which holds a range and controls generation rather than selection. See print-pgtle.

    -

    8.4. PG_CONFIG

    +

    8.6. PG_CONFIG

    Default: pg_config. Path to the pg_config binary used to detect the PostgreSQL version and locate PGXS. Override when the right pg_config isn’t the one on PATH, such as when testing against a specific PostgreSQL install.

    -

    8.5. TESTDIR

    +

    8.7. TESTDIR

    Default: test. Root directory for test input files: $(TESTDIR)/sql/, $(TESTDIR)/expected/, $(TESTDIR)/build/, $(TESTDIR)/install/. Overriding this on its own is unusual; it mainly exists so TESTOUT has a sensible default.

    -

    8.6. TESTOUT

    +

    8.8. TESTOUT

    Default: $(TESTDIR). Directory pg_regress writes actual test output to — $(TESTOUT)/results/, $(TESTOUT)/regression.diffs, etc. — via --outputdir in REGRESS_OPTS. Kept separate from TESTDIR so generated output can be pointed somewhere other than the directory holding your checked-in sql/expected files, if you want that separation.

    -

    8.7. PGXNTOOL_VERIFY_RESULTS_MODE

    +

    8.9. PGXNTOOL_VERIFY_RESULTS_MODE

    -

    Default: pgtap. Controls how the verify-results safeguard decides whether tests are failing.

    +

    Default: pgtap. Controls how the verify-results safeguard decides whether tests are failing. Must be pgtap or diffs (case-insensitive) when given explicitly; any other value, including an empty one, is a hard make error.

    -

    8.7.1. pgtap

    +

    8.9.1. pgtap

    -

    Scans test/results/*.out for pgTap not ok lines and plan mismatches, falling back to checking for regression.diffs too. Use this mode when your test suite uses pgTap.

    +

    Scans test/results/*.out for pgTap not ok lines and plan mismatches, falling back to checking regression.diffs too — except for a test that has no expected output yet, whose first expected file make results is allowed to create. Use this mode when your test suite uses pgTap.

    -

    8.7.2. diffs

    +

    8.9.2. diffs

    Checks only for regression.diffs, matching classic pg_regress behavior. Use this mode when your tests use plain SQL expected-output comparison only.

    -

    8.8. PGXNTOOL_ENABLE_TEST_BUILD *

    +

    8.10. PGXNTOOL_ENABLE_TEST_BUILD *

    Default: auto-detected — yes if test/build/*.sql files exist, no otherwise. Enables or disables the test-build pre-flight SQL check. Set explicitly to yes if you want an error when test/build/ unexpectedly has no SQL files (catches accidental deletion of its contents), or to no to disable the check even when files are present.

    -

    8.9. PGXNTOOL_ENABLE_TEST_INSTALL *

    +

    8.11. PGXNTOOL_ENABLE_TEST_INSTALL *

    Default: auto-detected — yes if test/install/*.sql files exist, no otherwise. Enables or disables the test/install schedule-based setup feature. Same explicit-override semantics as PGXNTOOL_ENABLE_TEST_BUILD.

    -

    8.10. PGXNTOOL_ENABLE_VERIFY_RESULTS *

    +

    8.12. PGXNTOOL_ENABLE_TEST_INSTALL_ERROR_STOP_CHECK *

    +
    +

    Default: yes. Enables or disables a build-time check that every test/install/*.sql file either includes test/pgxntool/psql.sql (which sets ON_ERROR_STOP) or has its own \set ON_ERROR_STOP or \unset ON_ERROR_STOP command; a file that sets ON_ERROR_STOP itself, to any value, is assumed to know what it’s doing — see test/install for why this is the only thing that makes a hard error in one of those files actually fail the build. Set to no to disable the check.

    +
    +
    +
    +

    8.13. PGXNTOOL_ENABLE_FS_INSTALL *

    +
    +

    Default: yes. Controls whether install is a prerequisite of test/verify-results, and whether installcheck and test-build depend on install. Set to no for "existing mode" testing, where the extension under test was deployed some other way (e.g. registered via pg_tle instead of the filesystem, or installed by a binary pg_upgrade) — with this set, make test/make installcheck/make verify-results run against whatever is already installed instead of filesystem-installing first.

    +
    +
    +
    +

    8.14. PGXNTOOL_ENABLE_PGXN_INSTALL *

    +
    +

    Default: follows PGXNTOOL_ENABLE_FS_INSTALL (so it’s off automatically whenever filesystem install is off), but can be set independently. Controls whether installcheck auto-installs the pgtap dependency via pgxn install pgtap --sudo when it isn’t already filesystem-installed. Set to no to make this a complete no-op, e.g. when pgtap is already provided some other way and you don’t want a sudo pgxn install invoked as a side effect of testing.

    +
    +
    +
    +

    8.15. PGXNTOOL_ENABLE_VERIFY_RESULTS *

    Default: yes. Enables or disables the verify-results safeguard that blocks make results when tests are failing. Setting it to empty on the command line (make PGXNTOOL_ENABLE_VERIFY_RESULTS= results) also disables it.

    -

    8.11. PGXNTOOL_ENABLE_CHECK_STALE_EXPECTED *

    +

    8.16. PGXNTOOL_ENABLE_CHECK_STALE_EXPECTED *

    -

    Default: yes. Enables or disables the check-stale-expected safeguard, which fails make test if test/expected/ (or test/build/expected/) contains a .out file with no corresponding .sql file — catching a stale file left behind after a test was renamed or removed. Set to no to make the check a complete no-op (it’s dropped from TEST_DEPS entirely).

    +

    Default: yes. Enables or disables the _check-stale-expected safeguard, which fails make test if test/expected/ (or test/build/expected/) contains a .out file with no corresponding .sql file — catching a stale file left behind after a test was renamed or removed. Set to no to make the check a complete no-op (it’s dropped from TEST_DEPS entirely).

    This is also the supported pattern for disabling one specific optional pgxntool feature: a documented PGXNTOOL_ENABLE_* variable, gating both the target’s registration into TEST_DEPS/etc. and (where applicable) the target’s own definition. Cleanly replacing or overriding an arbitrary pgxntool-generated recipe with your own entirely is a separate, larger, and not-yet-decided design question — see issue #30 (two proposed approaches) — and is not what this mechanism solves.

    -

    8.12. PGXNTOOL_CHECK_EXPECTED_FILE_TYPES *

    +

    8.17. PGXNTOOL_CHECK_EXPECTED_FILE_TYPES *

    -

    Default: yes. Sub-check of check-stale-expected, independent of PGXNTOOL_ENABLE_CHECK_STALE_EXPECTED: fails (with a distinct error message and exit code from the orphaned-.out check) if test/expected/ (or test/build/expected/) contains any file that isn’t *.out. Set to no to disable just this sub-check while leaving the orphaned-.out check active.

    +

    Default: yes. Sub-check of _check-stale-expected, independent of PGXNTOOL_ENABLE_CHECK_STALE_EXPECTED: fails (with a distinct error message and exit code from the orphaned-.out check) if test/expected/ (or test/build/expected/) contains any file that isn’t *.out. Set to no to disable just this sub-check while leaving the orphaned-.out check active.

    -

    8.13. PGXNTOOL_NO_PGXS_INCLUDE

    +

    8.18. PGXNTOOL_NO_PGXS_INCLUDE

    Default: unset (PGXS is included normally). Skips including PGXS ($(PGXS)) entirely. This is only for advanced scenarios where you need to manage the PGXS include yourself; most projects should never set this.

    @@ -1970,7 +2193,18 @@

    - +

    9. General PostgreSQL Notes

    +
    +
    +

    POSTGRES-NOTES.asc collects guidance about PostgreSQL itself +rather than about PGXNtool — things that come up constantly while writing an extension +but that PGXNtool has no part in. It currently covers which .control file parameters +(superuser, trusted) control who is allowed to install your extension.

    +
    +
    +
    +
    +

    Copyright (c) 2026 Jim Nasby <Jim.Nasby@gmail.com>

    @@ -1983,7 +2217,7 @@