# Copyright (C) 2026 Intel Corporation
# SPDX-License-Identifier: PostgreSQL

EXTENSION = svs
EXTVERSION = 0.1.0

MODULE_big = svs
DATA = sql/$(EXTENSION)--$(EXTVERSION).sql
OBJS = src/svs.o \
       src/vamana.o \
       src/svs_cpu_budget.o \
       src/svs_memory.o \
       src/svs_index_residency.o \
       src/svs_slot_naming.o \
       src/vamanalauncher.o \
       src/vamana_replication.o \
       src/vamana_checkpoint.o \
       src/vamana_undo.o \
       src/vamana_subxid_pending_array.o \
       src/vamana_subxact_guard.o \
       src/svs_vector_buffer.o \
       src/vamanabuild.o \
       src/svs_parallel_build.o \
       src/svs_build_thread_grant.o \
       src/svs_build_request_protocol.o \
       src/svs_cpu_slots.o \
       src/vamanacache.o \
       src/vamanaio.o \
       src/vamanainsert.o \
       src/vamanascan.o \
       src/vamanautils.o \
       src/vamanavacuum.o \
       src/vamanaworker.o \
       src/vamanaworkerstats.o \
       src/vamanaworkershmem.o \
       src/vamanaworkerindex.o \
       src/vamanaworkersearch.o \
       src/vamanaworkerwrite.o \
       src/svs_wrapper.o \
       src/svs_capacity_search.o \
       src/vamana_databases.o \
       src/vamana_teardown.o \
       src/vamana_warmup.o
HEADERS = src/vamana.h src/svs_wrapper.h src/vamana_databases.h src/vamanalauncher.h src/svs_cpu_budget.h src/svs_memory.h src/svs_index_residency.h src/svs_slot_naming.h src/svs_cpu_slots.h src/svs_capacity_search.h src/svs_vector_buffer.h

# halfvec_compression/vector_compression build real LeanVec and LVQ indexes,
# which require hardware most CI runners lack. Excluded from TESTS/REGRESS so
# plain 'make installcheck' never attempts them; run via 'make installcheck-hw'.
HW_TESTS = halfvec_compression vector_compression
TESTS = $(filter-out $(addprefix test/sql/,$(addsuffix .sql,$(HW_TESTS))),$(wildcard test/sql/*.sql))

# Same hardware limitation, TAP side: these files build real LeanVec/LVQ
# indexes. Excluded from the default 'prove_installcheck' file list so it
# never attempts them; run via 'make prove_installcheck-hw'.
HW_TAP_TESTS = test/t/39_persistence_compression.pl test/t/44_build_memory_calibration_compression.pl
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
# Load pgvector first (for vector/halfvec types), then this extension
REGRESS_OPTS = --inputdir=test --load-extension=vector --load-extension=$(EXTENSION)

# To compile for portability, run: make OPTFLAGS=""
OPTFLAGS = -march=native

# Mac ARM doesn't always support -march=native
ifeq ($(shell uname -s), Darwin)
	ifeq ($(shell uname -p), arm)
		OPTFLAGS =
	endif
endif

# PowerPC doesn't support -march=native
ifneq ($(filter ppc64%, $(shell uname -m)), )
	OPTFLAGS =
endif

# RISC-V64 doesn't support -march=native
ifeq ($(shell uname -m), riscv64)
	OPTFLAGS =
endif

# Hardening flags. -Wall, -Wformat, -Wformat-security, -Wimplicit-fallthrough,
# and -fPIC already come from pg_config; they are restated here for clarity.
# New protections added by this Makefile: -Werror=format-security,
# -fstack-protector-strong, -fstack-clash-protection, _FORTIFY_SOURCE uplift,
# and -Wl,-z,now (see HARDENING_LDFLAGS).
# -Werror is opt-in via WERROR=1 (e.g. in CI): a shipped -Werror is not safe
# across future compiler and PostgreSQL releases. -Werror=format-security is
# unconditional as it guards a specific, narrow class of bug.
# -Wconversion/-Wextra omitted: trigger errors in PostgreSQL system headers.
# -U_FORTIFY_SOURCE before -D avoids a redefinition diagnostic on distros that
# predefine it. Level 3 requires GCC 12+; GCC 11 gets 2.
FORTIFY_LEVEL := $(shell [ "$$(${CC:-gcc} -dumpversion 2>/dev/null | cut -d. -f1)" -ge 12 ] 2>/dev/null && echo 3 || echo 2)
HARDENING_CFLAGS = -Wall -Wimplicit-fallthrough \
                   -Wformat -Wformat-security -Werror=format-security \
                   -fstack-protector-strong -fstack-clash-protection \
                   -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=$(FORTIFY_LEVEL) -fPIC
ifeq ($(WERROR),1)
HARDENING_CFLAGS += -Werror
endif

# Linker hardening flags. -z,noexecstack and -z,relro are distro defaults;
# -z,now is the load-bearing addition (full RELRO; not a default for shared libs).
# -z,nodlopen omitted: PostgreSQL loads extensions via dlopen.
HARDENING_LDFLAGS = -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now

PG_CFLAGS += $(OPTFLAGS) -ftree-vectorize -fassociative-math -fno-signed-zeros -fno-trapping-math $(HARDENING_CFLAGS)

# Coverage instrumentation, opt-in via 'make COVERAGE=1'. Kept out of default
# builds because --coverage slows the binary and writes .gcda files at runtime.
ifeq ($(COVERAGE),1)
PG_CFLAGS  += --coverage
SHLIB_LINK += --coverage
endif

# SVS library paths
SVS_INSTALL ?= svs_install_public
# Resolve to an absolute path: a relative RUNPATH is resolved by the dynamic
# loader against the process's current working directory, and the postmaster
# changes into $PGDATA before loading shared_preload_libraries.
override SVS_INSTALL := $(abspath $(SVS_INSTALL))

# Validate SVS library exists
ifeq (,$(wildcard $(SVS_INSTALL)/lib/libsvs_c_api.so))
$(error SVS library not found at $(SVS_INSTALL)/lib/libsvs_c_api.so. Run build_svs_public.sh first or set SVS_INSTALL correctly)
endif

# -isystem, not -I, for the SVS C API headers: they are a third-party
# dependency this project does not control, and svs_c.h trips
# -Wdeclaration-after-statement (part of pg_config's own --cflags). -isystem
# suppresses warnings from headers found through it, so WERROR=1 in CI is not
# tripped by someone else's header.
PG_CPPFLAGS += -isystem $(SVS_INSTALL)/include -I$(shell $(PG_CONFIG) --includedir-server)/extension/vector
SHLIB_LINK += -L$(SVS_INSTALL)/lib -lsvs_c_api -Wl,-rpath,$(SVS_INSTALL)/lib $(HARDENING_LDFLAGS)

PG_CONFIG ?= pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)

# Remove GCC coverage artefacts on 'make clean'
EXTRA_CLEAN = $(wildcard src/*.gcda) $(wildcard src/*.gcno)

include $(PGXS)

# Expose the build's injection-point setting to TAP tests (fault-path tests
# skip themselves when it is not 'yes').
export enable_injection_points

# for Mac
ifeq ($(PROVE),)
	PROVE = prove
endif

# for Postgres < 15
PROVE_FLAGS += -I ./test/perl

# ---------------------------------------------------------------------------
# Stale-install guard
#
# 'installcheck' and 'prove_installcheck' run against whatever is already
# installed in pkglibdir, not against what was just built: neither target
# depends on 'install'. That means a tree that fails to build, or one whose
# source changed since the last 'make install', can still report a full test
# pass, because the tests never look at the tree. This guard closes that gap
# by refusing to run either target when the built and installed shlib differ.
#
# It deliberately does not add 'install' as a prerequisite: testing an
# already-installed build is a normal developer workflow, and an implicit
# install would write into a system directory without being asked.
# ---------------------------------------------------------------------------
.PHONY: guard-fresh-install
guard-fresh-install:
	@if [ ! -f '$(DESTDIR)$(pkglibdir)/$(shlib)' ]; then \
		echo "error: $(shlib) is not installed at $(DESTDIR)$(pkglibdir)/$(shlib)."; \
		echo "  Run 'make install' first."; \
		exit 1; \
	fi
	@if ! cmp -s '$(CURDIR)/$(shlib)' '$(DESTDIR)$(pkglibdir)/$(shlib)'; then \
		echo "error: built $(CURDIR)/$(shlib) differs from installed $(DESTDIR)$(pkglibdir)/$(shlib)."; \
		echo "  Run 'make install' to test what was just built."; \
		exit 1; \
	fi

installcheck: guard-fresh-install

# ---------------------------------------------------------------------------
# Hardware-dependent regression tests
#
# halfvec_compression.sql and vector_compression.sql build real LeanVec and
# LVQ compressed indexes via SVS, which require hardware most CI runners
# lack. Run this target explicitly on hardware that supports it; it is not
# part of plain 'installcheck'.
# ---------------------------------------------------------------------------
.PHONY: installcheck-hw
installcheck-hw: guard-fresh-install
	$(MAKE) installcheck REGRESS="$(HW_TESTS)"

# ---------------------------------------------------------------------------
# Hardening verification (SDL429 evidence)
#
# Confirms the protections in HARDENING_CFLAGS/HARDENING_LDFLAGS actually took
# effect in the built svs.so, using only readelf and nm from binutils, which
# the build already requires. Checks svs.so only: the SVS C API library it
# links against is a dependency this project does not build or release.
# ---------------------------------------------------------------------------
.PHONY: hardening-check
SVS_SO ?= $(CURDIR)/$(shlib)
hardening-check:
	@sh $(CURDIR)/ci/hardening_check.sh '$(SVS_SO)'

prove_installcheck: guard-fresh-install
	rm -rf $(CURDIR)/tmp_check
	cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" LD_LIBRARY_PATH="$(shell $(PG_CONFIG) --libdir):$$LD_LIBRARY_PATH" PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),$(filter-out $(HW_TAP_TESTS),$(wildcard test/t/*.pl)))

# ---------------------------------------------------------------------------
# Hardware-dependent TAP tests
#
# 39_persistence_compression.pl and 44_build_memory_calibration_compression.pl
# build real LeanVec and LVQ compressed indexes via SVS, which require
# hardware most CI runners lack. Run this target explicitly on hardware that
# supports it; it is not part of plain 'prove_installcheck'.
# ---------------------------------------------------------------------------
.PHONY: prove_installcheck-hw
prove_installcheck-hw: guard-fresh-install
	$(MAKE) prove_installcheck PROVE_TESTS="$(HW_TAP_TESTS)"

# ---------------------------------------------------------------------------
# Coverage report targets
#
# Workflow:
#   1. make COVERAGE=1                          (builds with instrumentation)
#   2. make install && make prove_installcheck  (accumulates .gcda counters)
#   3. make coverage                            (generates all three report formats)
#
# To start a clean measurement run:
#   make coverage-clean && make prove_installcheck && make coverage
# ---------------------------------------------------------------------------

GCOVR      ?= gcovr
COVERAGE_FILTER = --filter src/

# coverage: generate text summary, AI-consumable JSON, and interactive HTML
coverage:
	mkdir -p coverage_reports
	$(GCOVR) $(COVERAGE_FILTER) --txt coverage_reports/coverage.txt
	$(GCOVR) $(COVERAGE_FILTER) --json-pretty --output coverage_reports/coverage.json
	$(GCOVR) $(COVERAGE_FILTER) --html-details coverage_reports/index.html
	@echo "Coverage reports written to coverage_reports/"

# coverage-clean: zero .gcda counters so the next test run starts fresh.
# Does NOT remove .gcno files (those require a recompile to regenerate).
coverage-clean:
	find $(CURDIR)/src -name '*.gcda' -delete
	@echo "Coverage counters reset (*.gcda removed)"

.PHONY: coverage coverage-clean
