-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathsql.mk
More file actions
271 lines (247 loc) · 14.3 KB
/
Copy pathsql.mk
File metadata and controls
271 lines (247 loc) · 14.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#
# sql.mk -- versioned SQL generation and everything that depends on it.
#
# Owns `include pgxntool/base.mk` (which pulls in control.mk and, at its end,
# PGXS), included at the top of this file before the DATA/rules below. The
# Makefile includes THIS file (once) and does NOT include base.mk itself:
# base.mk has no include guard, so a second include causes "overriding recipe"
# errors (upstream: https://github.com/Postgres-Extensions/pgxntool/issues/50).
# The code below uses vars from those includes: EXTENSION__CURRENT_VERSION__FILES
# (control.mk); PG_CONFIG / MAJORVER / $(call test,...) (base.mk); datadir (PGXS).
#
# ============================================================================
# GNU Make is TWO-PHASE, and we GENERATE the .sql we install -- beware.
# ============================================================================
# Phase 1 (parse) expands every $(wildcard ...) and CACHES directory listings
# before any recipe runs. Phase 2 (recipes) then generates our versioned SQL
# (sql/cat_tools--X.Y.Z.sql install scripts and sql/cat_tools--A.B.C--X.Y.Z.sql
# update scripts) from .sql.in sources. Those generated .sql are gitignored (see
# sql/.gitignore) and absent on a clean tree at parse time, so any parse-time
# $(wildcard) over them comes up short -- and the cached listing means Make never
# notices them later either.
#
# https://github.com/Postgres-Extensions/cat_tools/issues/28 is the canonical
# symptom: base.mk seeds DATA with $(wildcard sql/*--*--*.sql), a phase-1 glob
# that misses our generated update scripts on a clean tree, so `make install`
# skips them and a later `ALTER EXTENSION cat_tools UPDATE` fails with "no update
# path".
#
# The rule this file follows: glob what git TRACKS; name-DERIVE what we generate.
# We build the install/update lists from the .sql.in SOURCE names (present at
# parse time), not from the generated .sql, then add those names to DATA and
# $(sort) to dedup against base.mk's own wildcard. Such lists are parse-stable:
# identical on a clean tree and a built tree.
# ============================================================================
# See header: base.mk must be included exactly once, before the DATA/rules below.
include pgxntool/base.mk
LT95 = $(call test, $(MAJORVER), -lt, 95)
LT93 = $(call test, $(MAJORVER), -lt, 93)
#
# Parse-stable lists derived from the .sql.in SOURCES (see header). versioned_in
# globs the committed .sql.in; _out renames each to the .sql we generate.
#
versioned_in = $(wildcard sql/*--*.sql.in)
versioned_out = $(subst .sql.in,.sql,$(versioned_in))
# base.mk also seeds the update scripts via $(wildcard sql/*--*--*.sql), but that
# glob misses the generated ones on a clean tree (see header), so add by name.
upgrade_scripts_out = $(subst .sql.in,.sql,$(wildcard sql/*--*--*.sql.in))
#
# Historical install scripts committed directly as .sql with no .sql.in (the
# frozen cat_tools--0.1.* set; see sql/.gitignore). Globbing committed files is
# safe. Derive rather than hardcode: take every install-shaped .sql, then drop
# the update scripts and everything we generate from .sql.in. Parse-stable (the
# subtracted names are name-derived), and picks up new historical files auto.
historical_installs = $(filter-out $(versioned_out) $(wildcard sql/*--*--*.sql), $(wildcard sql/*--*.sql))
#
# DATA -- the INSTALL manifest: what `make install` copies into the extension
# dir. Installed != committed. The versioned .sql (install and update scripts)
# are BUILT from the committed .sql.in at build time and installed, but are
# gitignored, not tracked -- only the .sql.in are. The historical
# cat_tools--0.1.*.sql are the exception: committed directly (no .sql.in source).
# base.mk only seeds DATA; we append here.
#
DATA += $(historical_installs)
# Generated install scripts, minus the current-version file
# (EXTENSION__CURRENT_VERSION__FILES, from control.mk) and the update scripts.
DATA += $(filter-out $(EXTENSION__CURRENT_VERSION__FILES) $(upgrade_scripts_out), $(versioned_out))
# Generated update scripts: base.mk's parse-time wildcard misses these on a clean
# tree (see header), so add by name.
DATA += $(upgrade_scripts_out)
# Dedup against base.mk's wildcard on trees where the generated .sql already
# exist (also gives a stable order).
DATA := $(sort $(DATA))
all: sql/cat_tools.sql $(versioned_out)
installcheck: sql/cat_tools.sql $(versioned_out)
EXTRA_CLEAN += sql/cat_tools.sql $(versioned_out)
# pgxntool 2.2.0's check-stale-expected depends directly on installcheck, but
# base.mk appends it to TEST_DEPS -- and so freezes `test`'s prerequisite list,
# expanded at the `test: $(TEST_DEPS)` rule's parse time -- BEFORE
# test-build/install/installcheck are appended. Reassigning TEST_DEPS from
# here does not help: it's too late to change a prerequisite list Make already
# expanded. So `make test` resolves check-stale-expected's installcheck edge
# BEFORE test-build's install edge, running the full suite before install ever
# runs on a tree where nothing is installed yet (e.g. fresh CI) -- reproduced
# there as every test failing with schema "cat_tools" does not exist. Filed
# upstream: https://github.com/Postgres-Extensions/pgxntool/issues/79.
#
# Give installcheck itself an install prerequisite to fix it, EXCEPT in
# TEST_EXISTING_DEPLOY=pgtle mode: bin/test_existing's pg_tle-mode run_suite
# deliberately calls `testdeps installcheck` directly (bypassing `test`'s own
# install prerequisite, and thus never touching check-stale-expected at all)
# specifically so the filesystem is never touched -- an unconditional
# installcheck-level fix would defeat that.
ifneq ($(TEST_EXISTING_DEPLOY),pgtle)
installcheck: install
endif
#
# relkind drift source generation
#
# Generate the canonical set of pg_class.relkind values from the server headers
# we are building against, for the relkind drift check in test/sql/relation__.sql.
# The output is gitignored (per-version). When postgresql-server-dev-NN is not
# installed the script emits an empty view so `make test` still works locally;
# CI runs check-relkind-source (below) so a missing header can never let the
# drift check pass silently, and `make test` warns about it locally.
RELKIND_SRC = test/.generated/pg_class_relkinds.sql
# Absolute path to the header we extract relkinds from. Computed each `make`;
# it changes when we build against a different PostgreSQL (pg_config differs).
RELKIND_HEADER := $(shell $(PG_CONFIG) --includedir-server)/catalog/pg_class.h
# Stamp recording the header path. FORCE runs the recipe every time, but it only
# rewrites the stamp (bumping its mtime) when the path actually changed, so the
# source is regenerated after a PostgreSQL-version switch even though the old
# header file itself is untouched. This is the same "name-derive the source"
# trick as versioned_out, but for a source (a server header) that lives OUTSIDE
# the tree: we cannot glob it into the dependency graph reliably, so we track its
# PATH in a committed-shaped stamp file instead.
RELKIND_STAMP = test/.generated/.relkind-header-path
# Directory the generated files live in. It is listed as an ORDER-ONLY
# prerequisite (after `|`) on the stamp and source recipes below: the directory
# must exist before we write into it, but we must NOT rebuild those files just
# because the directory changed. A directory's mtime bumps every time a file is
# added to or removed from it, so as a normal prerequisite it would force
# needless regeneration on every run; after `|` it means "ensure it exists, but
# its timestamp is not a rebuild trigger."
test/.generated:
@mkdir -p $@
.PHONY: FORCE
FORCE:
$(RELKIND_STAMP): FORCE | test/.generated
@echo '$(RELKIND_HEADER)' | cmp -s - "$@" 2>/dev/null || echo '$(RELKIND_HEADER)' > "$@"
# Real file target (not .PHONY): regenerate only when the generator, the header
# file, or the header path change -- not on every `make`. $(wildcard ...) yields
# no prereq (rather than an error) when the header is absent, in which case
# gen-relkinds.sh emits an empty view.
$(RELKIND_SRC): test/gen-relkinds.sh $(RELKIND_STAMP) $(wildcard $(RELKIND_HEADER)) | test/.generated
test/gen-relkinds.sh "$(RELKIND_HEADER)" > "$@"
.PHONY: gen-relkinds
gen-relkinds: $(RELKIND_SRC)
testdeps: $(RELKIND_SRC)
EXTRA_CLEAN += $(RELKIND_SRC) $(RELKIND_STAMP)
# Guard for CI: fail if the relkind source has no relkinds (server headers
# missing), so the drift check in test/sql/relation__.sql cannot silently pass
# on an empty view. CI runs `make check-relkind-source` before `make test` on
# every PostgreSQL version; local `make test` stays lenient (see
# warn-relkind-source).
.PHONY: check-relkind-source
check-relkind-source: $(RELKIND_SRC)
@grep -q 'RELKIND_' $(RELKIND_SRC) || { \
echo "ERROR: PostgreSQL catalog header not found at"; \
echo " $(RELKIND_HEADER)"; \
echo " so the relkind drift check in test/sql/relation__.sql would"; \
echo " pass without running. Install this PostgreSQL version's server"; \
echo " development headers so that path exists."; \
exit 1; \
}
@echo "check-relkind-source: $(RELKIND_SRC) is populated"
# Non-fatal counterpart, run at the end of `make test`: warn (do not fail) when
# the drift source is empty because the server headers are missing, so a local
# run without postgresql-server-dev-NN makes clear the drift check did not run.
# Listed as a `test` prerequisite after base.mk's, so it runs once tests are done.
.PHONY: warn-relkind-source
warn-relkind-source: $(RELKIND_SRC)
@grep -q 'RELKIND_' $(RELKIND_SRC) || echo "WARNING: PostgreSQL catalog header not found at $(RELKIND_HEADER); the relkind drift check in test/sql/relation__.sql did NOT run. Install this PostgreSQL version's server development headers to enable it."
test: warn-relkind-source
#
# Versioned-SQL generation from .sql.in
#
# 9.x version-conditional markers (dotted, e.g. -- SED: REQUIRES/PRIOR TO 9.5!)
# are handled by these two safesed blocks; 10+ (two-digit) markers by the awk in
# _apply_version_seds. The two sets are disjoint -- the awk's [1-9][0-9]+ can't
# match a dotted "9.5" -- and the sources still use the 9.x markers, so both are
# needed. Remove these blocks only once the sources drop all 9.x markers.
#
# FUTURE CLEANUP: once support for the relevant old PG versions is dropped, a
# .sql.in whose only version-conditional content targets those versions can be
# frozen to a raw committed .sql (dropping its .sql.in and thus its share of this
# processing chain). Precedent: the historical cat_tools--0.1.*.sql are already
# committed raw, with no .sql.in source.
#
# $@ is deferred (via =) and expands to the target name at recipe time.
ifeq ($(LT95),yes)
_sql_sed_95 = pgxntool/safesed $@.tmp -E -e 's/(.*)-- SED: REQUIRES 9\.5!/-- Requires 9.5: \1/'
else
_sql_sed_95 = pgxntool/safesed $@.tmp -E -e 's/(.*)-- SED: PRIOR TO 9\.5!/-- Not used prior to 9.5: \1/'
endif
ifeq ($(LT93),yes)
_sql_sed_93 = pgxntool/safesed $@.tmp -E -e 's/(.*)-- SED: REQUIRES 9\.3!/-- Requires 9.3: \1/'
else
_sql_sed_93 = pgxntool/safesed $@.tmp -E -e 's/(.*)-- SED: PRIOR TO 9\.3!/-- Not used prior to 9.3: \1/'
endif
# Apply all version-conditional SED markers to $@.tmp: 9.x via the safesed vars
# above; 10+ generically via awk (REQUIRES N -> commented if MAJORVER < N*10;
# PRIOR TO N -> commented if MAJORVER >= N*10). POSIX awk only (no gawk
# extensions like gensub() / 3-arg match()) -- portability is why this uses awk.
define _apply_version_seds
$(_sql_sed_95)
$(_sql_sed_93)
awk -v mv=$(MAJORVER) '\
/-- SED: REQUIRES [1-9][0-9]+!/ {t=$$0; sub(/.*REQUIRES /,"",t); sub(/!.*/,"",t); if(mv<t*10) $$0="-- Requires "t": "$$0}\
/-- SED: PRIOR TO [1-9][0-9]+!/ {t=$$0; sub(/.*PRIOR TO /,"",t); sub(/!.*/,"",t); if(mv>=t*10) $$0="-- Not used prior to "t": "$$0}\
{print}' $@.tmp > $@.tmp2 && mv $@.tmp2 $@.tmp
endef
# ----------------------------------------------------------------------------
# Two .sql.in sources feed the same .sql.in -> .sql transform below (wrap with
# @generated@ markers, then _apply_version_seds), producing two different
# targets:
#
# sql/cat_tools.sql.in (hand-maintained master, committed)
# --pattern rule--> sql/cat_tools.sql
#
# sql/cat_tools.sql.in
# --copy rule, tags @generated@ as "VERSIONED FILE!"-->
# sql/cat_tools--X.Y.Z.sql.in (committed per-version snapshot; frozen
# once released, see CLAUDE.md's SQL file conventions)
# --override rule, same transform as the pattern rule-->
# sql/cat_tools--X.Y.Z.sql = $(EXTENSION__CURRENT_VERSION__FILES)
# (what CREATE EXTENSION actually installs)
#
# The bottom (override) rule can't just be left to the sql/%.sql pattern rule
# matching it: control.mk (auto-generated by pgxntool from cat_tools.control,
# see pgxntool/base.mk) already defines its OWN recipe for
# $(EXTENSION__CURRENT_VERSION__FILES) -- straight from cat_tools.sql, no .sql.in layer,
# no version seds -- and GNU Make always prefers an explicit rule over a
# pattern rule for the same target. Overriding it here is the only way to
# route that target through the same .sql.in / version-sed pipeline as
# everything else; its "overriding recipe" warning is expected. Both final
# steps build $@.tmp then move it into place atomically.
#
# TODO: refactor the version handling into a function.
# ----------------------------------------------------------------------------
# @generated@ becomes the "-- GENERATED FILE! DO NOT EDIT!" marker below via a
# plain, unanchored substring match -- it also fires on a handful of
# coincidental @generated@ occurrences inside real-code comments in
# cat_tools.sql.in, which is harmless (already inside a -- comment).
sql/%.sql: sql/%.sql.in pgxntool/safesed
(echo @generated@ && cat $< && echo @generated@) | sed -e 's#@generated@#-- GENERATED FILE! DO NOT EDIT! See $<#' > $@.tmp
$(_apply_version_seds)
mv $@.tmp $@
# Appends " VERSIONED FILE!" after every @generated@ occurrence; the pattern
# rule above resolves the leading @generated@ either way, so the tag rides
# through into the final marker text untouched.
$(EXTENSION__CURRENT_VERSION__FILES:.sql=.sql.in): sql/cat_tools.sql.in cat_tools.control
sed -e 's/@generated@/@generated@ VERSIONED FILE!/' $< > $@
# See the overview above for why this duplicates the pattern rule's recipe.
$(EXTENSION__CURRENT_VERSION__FILES): $(EXTENSION__CURRENT_VERSION__FILES:.sql=.sql.in) pgxntool/safesed
(echo @generated@ && cat $< && echo @generated@) | sed -e 's#@generated@#-- GENERATED FILE! DO NOT EDIT! See $<#' > $@.tmp
$(_apply_version_seds)
mv $@.tmp $@