@@ -12,7 +12,17 @@ PG_INCLUDEDIR := $(shell $(PG_CONFIG) --includedir-server 2>/dev/null)
1212
1313# Extension metadata
1414EXTENSION = cloudsync
15- EXTVERSION = 1.0
15+ # Read the binary version (full semver) from src/cloudsync.h, and derive
16+ # EXTVERSION as just MAJOR.MINOR. Rationale: PATCH bumps are binary-only
17+ # (no SQL surface change, no ALTER EXTENSION UPDATE needed); MINOR/MAJOR bumps
18+ # are the SQL-surface-changing ones that require a per-release upgrade script.
19+ # cloudsync_version() in the .so still reports the full semver for debugging.
20+ #
21+ # Recursive (=) rather than immediate (:=) assignment so the shell calls only
22+ # fire when a PG-related target actually references these variables. Non-PG
23+ # targets parse this included makefile without invoking sed/cut at all.
24+ CLOUDSYNC_VERSION_FULL = $(shell sed -n 's/^\#define CLOUDSYNC_VERSION[[:space:]]*"\([^"]*\)".*/\1/p' src/cloudsync.h)
25+ EXTVERSION = $(shell echo '$(CLOUDSYNC_VERSION_FULL)' | cut -d. -f1-2)
1626
1727# Detect OS for platform-specific settings
1828ifneq ($(OS),Windows_NT)
@@ -82,29 +92,61 @@ endif
8292PG_EXTENSION_SQL = src/postgresql/$(EXTENSION)--$(EXTVERSION).sql
8393PG_EXTENSION_CONTROL = docker/postgresql/$(EXTENSION).control
8494
95+ # Input templates (tracked). @EXTVERSION@ is substituted at build time.
96+ PG_EXTENSION_SQL_IN = src/postgresql/$(EXTENSION).sql.in
97+ PG_EXTENSION_CONTROL_IN = docker/postgresql/$(EXTENSION).control.in
98+
99+ # Upgrade scripts (cloudsync--<from>--<to>.sql) are hand-written per release.
100+ PG_MIGRATIONS_DIR = src/postgresql/migrations
101+ PG_MIGRATION_SQLS = $(wildcard $(PG_MIGRATIONS_DIR)/$(EXTENSION)--*--*.sql)
102+
85103# ============================================================================
86104# PostgreSQL Build Targets
87105# ============================================================================
88106
89- .PHONY: postgres-check postgres-build postgres-install postgres-package postgres-clean postgres-test \
107+ .PHONY: postgres-check postgres-check-migration postgres-generate-files postgres- build postgres-install postgres-package postgres-clean postgres-test \
90108 postgres-docker-build postgres-docker-build-asan postgres-docker-run postgres-docker-run-asan postgres-docker-stop postgres-docker-rebuild \
91109 postgres-docker-debug-build postgres-docker-debug-run postgres-docker-debug-rebuild \
92110 postgres-docker-shell postgres-dev-rebuild postgres-help unittest-pg \
93111 postgres-supabase-build postgres-supabase-rebuild postgres-supabase-run-smoke-test \
94112 postgres-docker-run-smoke-test
95113
114+ # Verify that a cloudsync--<prev>--<curr>.sql upgrade script exists for the
115+ # current CLOUDSYNC_VERSION in src/cloudsync.h. Release-blocking: a missing
116+ # upgrade script silently breaks ALTER EXTENSION cloudsync UPDATE for every
117+ # existing deployment. Runs in <1s; safe to call on every PR.
118+ postgres-check-migration:
119+ @scripts/check-postgres-migration.sh
120+
96121# Check if PostgreSQL is available
97122postgres-check:
98123 @echo "Checking PostgreSQL installation..."
99124 @which $(PG_CONFIG) > /dev/null || (echo "Error: pg_config not found. Install postgresql-server-dev." && exit 1)
125+ @[ -n "$(CLOUDSYNC_VERSION_FULL)" ] || (echo "Error: could not read CLOUDSYNC_VERSION from src/cloudsync.h" && exit 1)
126+ @[ -n "$(EXTVERSION)" ] || (echo "Error: could not derive MAJOR.MINOR EXTVERSION from CLOUDSYNC_VERSION '$(CLOUDSYNC_VERSION_FULL)'" && exit 1)
100127 @echo "PostgreSQL version: $$($(PG_CONFIG) --version)"
128+ @echo "CloudSync version : $(CLOUDSYNC_VERSION_FULL) (extension version $(EXTVERSION))"
101129 @echo "Extension directory: $(PG_PKGLIBDIR)"
102130 @echo "Share directory: $(PG_SHAREDIR)"
103131 @echo "Include directory: $(PG_INCLUDEDIR)"
104132
133+ # Render the versioned .sql install script and .control file from their .in
134+ # templates. This is a phony target (rather than file rules keyed on
135+ # $(PG_EXTENSION_SQL) / $(PG_EXTENSION_CONTROL)) so that $(EXTVERSION) never
136+ # appears in a rule's target or prerequisite position — those expansions
137+ # happen at parse time and would force sed/cut to run on every `make`
138+ # invocation, including for non-PG goals. Here, the references live inside
139+ # the recipe body and are only evaluated when this target actually fires.
140+ # Writes via a .tmp + atomic mv so a failed sed can't leave a half-rendered
141+ # output file in the extension share dir.
142+ postgres-generate-files: postgres-check
143+ @echo "Rendering extension files at version $(EXTVERSION) (binary $(CLOUDSYNC_VERSION_FULL))"
144+ @sed 's/@EXTVERSION@/$(EXTVERSION)/g' $(PG_EXTENSION_SQL_IN) > $(PG_EXTENSION_SQL).tmp && mv $(PG_EXTENSION_SQL).tmp $(PG_EXTENSION_SQL)
145+ @sed 's/@EXTVERSION@/$(EXTVERSION)/g' $(PG_EXTENSION_CONTROL_IN) > $(PG_EXTENSION_CONTROL).tmp && mv $(PG_EXTENSION_CONTROL).tmp $(PG_EXTENSION_CONTROL)
146+
105147# Build PostgreSQL extension
106- postgres-build: postgres-check
107- @echo "Building PostgreSQL extension..."
148+ postgres-build: postgres-generate-files
149+ @echo "Building PostgreSQL extension (version $(EXTVERSION)) ..."
108150 @echo "Compiling source files..."
109151 @for src in $(PG_ALL_SRC); do \
110152 echo " CC $$src"; \
@@ -125,26 +167,37 @@ postgres-install: postgres-build
125167 install -m 644 $(PG_EXTENSION_SQL) $(PG_SHAREDIR)/extension/
126168 @echo "Installing control file to $(PG_SHAREDIR)/extension/"
127169 install -m 644 $(PG_EXTENSION_CONTROL) $(PG_SHAREDIR)/extension/
170+ @if [ -n "$(PG_MIGRATION_SQLS)" ]; then \
171+ echo "Installing $(words $(PG_MIGRATION_SQLS)) migration script(s) to $(PG_SHAREDIR)/extension/"; \
172+ install -m 644 $(PG_MIGRATION_SQLS) $(PG_SHAREDIR)/extension/; \
173+ fi
128174 @echo ""
129175 @echo "Installation complete!"
130176 @echo "To use the extension, run in psql:"
131177 @echo " CREATE EXTENSION $(EXTENSION);"
178+ @echo "To upgrade an existing installation, run in psql:"
179+ @echo " ALTER EXTENSION $(EXTENSION) UPDATE;"
132180
133181# Package extension files for distribution
134182PG_DIST_DIR = dist/postgresql
135183
136184postgres-package: postgres-build
137- @echo "Packaging PostgreSQL extension..."
185+ @echo "Packaging PostgreSQL extension (version $(EXTVERSION)) ..."
138186 @mkdir -p $(PG_DIST_DIR)
139187 cp $(PG_EXTENSION_LIB) $(PG_DIST_DIR)/
140188 cp $(PG_EXTENSION_SQL) $(PG_DIST_DIR)/
141189 cp $(PG_EXTENSION_CONTROL) $(PG_DIST_DIR)/
190+ @if [ -n "$(PG_MIGRATION_SQLS)" ]; then \
191+ echo "Including $(words $(PG_MIGRATION_SQLS)) migration script(s)"; \
192+ cp $(PG_MIGRATION_SQLS) $(PG_DIST_DIR)/; \
193+ fi
142194 @echo "Package ready in $(PG_DIST_DIR)/"
143195
144196# Clean PostgreSQL build artifacts
145197postgres-clean:
146198 @echo "Cleaning PostgreSQL build artifacts..."
147199 rm -f $(PG_OBJS) $(PG_EXTENSION_LIB)
200+ rm -f $(PG_EXTENSION_SQL) $(PG_EXTENSION_CONTROL)
148201 @echo "Clean complete"
149202
150203# Test extension (requires running PostgreSQL)
@@ -314,7 +367,7 @@ postgres-supabase-build:
314367 echo "Using base image: $$supabase_cli_image"; \
315368 echo "Pulling fresh base image to avoid layer accumulation..."; \
316369 docker pull "$$supabase_cli_image" 2>/dev/null || true; \
317- docker build --build-arg SUPABASE_POSTGRES_TAG="$(SUPABASE_POSTGRES_TAG)" -f "$$tmp_dockerfile" -t "$$supabase_cli_image" .; \
370+ docker build --build-arg SUPABASE_POSTGRES_TAG="$(SUPABASE_POSTGRES_TAG)" --build-arg CLOUDSYNC_VERSION="$(CLOUDSYNC_VERSION_FULL)" - f "$$tmp_dockerfile" -t "$$supabase_cli_image" .; \
318371 rm -f "$$tmp_dockerfile"; \
319372 echo "Build complete: $$supabase_cli_image"
320373
@@ -361,6 +414,8 @@ postgres-help:
361414 @echo ""
362415 @echo "Build & Install:"
363416 @echo " postgres-check - Verify PostgreSQL installation"
417+ @echo " postgres-check-migration - Verify a migration script exists for the current version"
418+ @echo " postgres-generate-files - Render cloudsync.control and cloudsync--<ext>.sql from templates"
364419 @echo " postgres-build - Build extension (.so file)"
365420 @echo " postgres-install - Install extension to PostgreSQL"
366421 @echo " postgres-clean - Clean build artifacts"
0 commit comments