include make/targets.mk

UNAME_S ?= $(shell uname -s)

SECTION_GC_LDFLAGS ?=
ifeq ($(UNAME_S),Darwin)
SECTION_GC_LDFLAGS := -Wl,-dead_strip
else
SECTION_GC_LDFLAGS := -Wl,--gc-sections
endif

CLANG ?= clang
SANITIZER_FLAGS ?= -O1 -fno-omit-frame-pointer -fsanitize=address,undefined
ASAN_OPTIONS_BASE ?= abort_on_error=1
ifeq ($(UNAME_S),Linux)
ASAN_OPTIONS_BASE := $(strip $(ASAN_OPTIONS_BASE))
ifeq ($(ASAN_OPTIONS_BASE),)
ASAN_OPTIONS_BASE := detect_leaks=1
else
ASAN_OPTIONS_BASE := $(ASAN_OPTIONS_BASE):detect_leaks=1
endif
endif
SANITIZER_ENV ?= ASAN_OPTIONS=$(ASAN_OPTIONS_BASE) UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1

.PHONY: unit unit-smoke unit-clang-smoke unit-sanitize-smoke $(addprefix unit-,$(UNIT_NAMES)) integration-c integration-nginx e2e unit-coverage clean

# Rebuild a unit target when any src/ file it directly includes changes
# (see -MMD -MP in targets.mk).  Files are generated next to each binary.
-include $(wildcard build/unit/*.d)

build/unit build/integration build/generated:
	@mkdir -p $@

# Unit test build rule.  The case statement selects per-target flags:
#   streaming_impl  - full streaming pipeline; needs NGINX src headers, nginx_stubs,
#                      Rust FFI headers, streaming enabled, and -lz for decomp.
#   config_handlers_impl / config_core_impl - config-layer unit tests; need
#                      nginx_stubs for NGINX API redefinitions and -ffunction-sections
#                      for dead-code elimination of unused stubs; directly include
#                      broad production implementation headers, suppressing only
#                      the resulting unreferenced static-function warnings.
build/unit/%: unit/%_test.c include/test_common.h | build/unit build/generated
	@extra_srcs=""; \
	extra_cflags=""; \
	extra_ldflags=""; \
	case "$*" in \
	  headers) extra_srcs="helpers/headers_standalone.c" ;; \
	  reason_code_ffi|diagnostics_production) extra_srcs="../src/ngx_http_markdown_diagnostics_reason.c"; extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED" ;; \
	  header_compile|reason_code|error_impl|error_classification|diagnostics_accessors|header_plan_apply|header_plan_prepare_commit) extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED" ;; \
	  diagnostics_streaming_response) extra_cflags="-DMARKDOWN_STREAMING_ENABLED" ;; \
	  ffi_layout_check) extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED" ;; \
	  gzip_deflate_decompression|decompression_production) extra_cflags="-I../src -Iinclude/nginx_stubs -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS) -lz" ;; \
	  deflate_parity) extra_cflags="-I../src -Iinclude/nginx_stubs -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS) -lz" ;; \
	  buf_len_safe) extra_cflags="-I../src -Iinclude/nginx_stubs" ;; \
	  streaming_decomp|streaming_decomp_routing) extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED"; extra_ldflags="-lz" ;; \
	  decomp_routing_property) extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED" ;; \
	  postcommit_terminal_origin) extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED" ; extra_ldflags="-lz" ;; \
	  streaming_decomp_brotli) extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED -DNGX_HTTP_BROTLI $$(pkg-config --cflags libbrotlidec libbrotlienc 2>/dev/null || echo '-I/opt/homebrew/include -I/usr/local/include')"; extra_ldflags="-lz $$(pkg-config --libs libbrotlidec libbrotlienc 2>/dev/null || echo '-L/opt/homebrew/lib -L/usr/local/lib -lbrotlidec -lbrotlienc')" ;; \
	  brotli_trailing_property|brotli_budget_property|brotli_error_prop_property|brotli_noprogress_property) extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED -DNGX_HTTP_BROTLI $$(pkg-config --cflags libbrotlidec libbrotlienc 2>/dev/null || echo '-I/opt/homebrew/include -I/usr/local/include')"; extra_ldflags="-lz $$(pkg-config --libs libbrotlidec libbrotlienc 2>/dev/null || echo '-L/opt/homebrew/lib -L/usr/local/lib -lbrotlidec -lbrotlienc')" ;; \
	  streaming_equiv_brotli_property) extra_cflags="$$(pkg-config --cflags libbrotlidec libbrotlienc 2>/dev/null || echo '-I/opt/homebrew/include -I/usr/local/include')"; extra_ldflags="$$(pkg-config --libs libbrotlidec libbrotlienc 2>/dev/null || echo '-L/opt/homebrew/lib -L/usr/local/lib -lbrotlidec -lbrotlienc')" ;; \
	  streaming_metrics_increment) extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED" ;; \
	  streaming) extra_cflags="-DMARKDOWN_STREAMING_ENABLED" ;; \
	  stream_postcommit|stream_replay|stream_commit|streaming_replay_overflow_security) extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED" ;; \
	  streaming_impl) extra_cflags="-I../src -Iinclude/nginx_stubs -I../../rust-converter/include -DMARKDOWN_STREAMING_ENABLED -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS) -lz" ;; \
	  conversion_impl_base_url) extra_cflags="-Iinclude/nginx_stubs -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  failopen_delivery_after_downstream|delivery_decision_failopen) extra_cflags="-Iinclude/nginx_stubs -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  copy_reduction) extra_cflags="-I../src -Iinclude/nginx_stubs -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS) -lz" ;; \
  metrics_access) extra_cflags="-I../src -Iinclude/nginx_stubs -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  config_handlers_impl|limits_parsing_property) extra_cflags="-Iinclude/nginx_stubs -ffunction-sections -Wno-unused-function" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  streaming_config_contract|directive_context_default_inherit_property|context_negative_property) extra_cflags="-Iinclude/nginx_stubs -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  eligibility_impl|hard_excluded_params_case|hard_excluded_types_security|status_code_excluded_security) extra_cflags="-Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  config_core_impl) extra_cflags="-Iinclude/nginx_stubs -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  config_default_values) extra_cflags="-Iinclude/nginx_stubs -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  effective_conf) extra_cflags="-Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  accept_production|auth_production|auth_deny_security|conditional_production) extra_cflags="-I../src -Iinclude/nginx_stubs -DMARKDOWN_STREAMING_ENABLED -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  request_bypass_production) extra_cflags="-I../src -Iinclude/nginx_stubs -ffunction-sections -Wno-unused-function -Wno-unused-but-set-variable -Wno-unused-parameter" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	  inflight) extra_srcs="helpers/inflight_peer.c"; extra_cflags="-Iinclude/nginx_stubs -ffunction-sections" ; extra_ldflags="$(SECTION_GC_LDFLAGS)" ;; \
	esac; \
	objs=""; \
	i=0; \
	for src in "$<" $$extra_srcs; do \
	  i=$$((i + 1)); \
	  obj="build/unit/$*-src$${i}.o"; \
	  objs="$${objs} $${obj}"; \
	  $(CC) $(CFLAGS_COMMON) $$extra_cflags -MT "$@" -c "$${src}" -o "$${obj}" || { \
	    printf 'ERROR: failed to compile unit source %s\n' "$${src}" >&2; \
	    exit 1; \
	  }; \
	done; \
	$(CC) $(LDFLAGS_COMMON) -o "$@" $$objs $$extra_ldflags

unit: $(UNIT_BINS)
	@set -e; \
	for t in $(UNIT_BINS); do \
	  echo "Running $$t"; \
	  "$$t"; \
	done

# fast smoke subset used by top-level make test
unit-smoke: build/unit/body_filter build/unit/eligibility build/unit/headers
	@build/unit/body_filter
	@build/unit/eligibility
	@build/unit/headers

unit-clang-smoke:
	@$(MAKE) clean
	@$(MAKE) unit-smoke CC=$(CLANG)

unit-sanitize-smoke:
	@$(MAKE) clean
	@$(SANITIZER_ENV) $(MAKE) unit-smoke \
		CC=$(CLANG) \
		CFLAGS_COMMON="-Wall -Wextra -Werror=implicit-function-declaration -Werror=incompatible-pointer-types -g $(SANITIZER_FLAGS) -std=$(CSTD) -Iinclude" \
		LDFLAGS_COMMON="$(SANITIZER_FLAGS)"

$(addprefix unit-,$(UNIT_NAMES)): unit-%: build/unit/%
	@"$<"

$(INTEGRATION_C_BIN): integration/nginx_runtime_integration_test.c include/test_common.h | build/integration
	$(CC) $(CFLAGS_COMMON) -o "$@" "$<"

integration-c: $(INTEGRATION_C_BIN)
	@"$(INTEGRATION_C_BIN)"

integration-nginx:
	@bash integration/run_integration_tests.sh

e2e:
	@bash ../../../tools/e2e/run_e2e_suite.sh

# ── Coverage target ─────────────────────────────────────────────────
# Rebuild all unit tests with gcov instrumentation, run them, and
# collect an lcov report.  The output directory is passed via COV_DIR
# (default: build/coverage).
COV_DIR ?= build/coverage

unit-coverage:
	@$(MAKE) clean
	@$(MAKE) unit \
		CFLAGS_COMMON="-Wall -Wextra -Werror=implicit-function-declaration -Werror=incompatible-pointer-types -g --coverage -std=$(CSTD) -Iinclude -I../../rust-converter/include -MMD -MP" \
		LDFLAGS_COMMON="--coverage"
	@mkdir -p $(COV_DIR)
	lcov --capture --directory build/unit --output-file $(COV_DIR)/raw.lcov \
		--rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors inconsistent,inconsistent --ignore-errors unsupported,unsupported
	lcov --remove $(COV_DIR)/raw.lcov '*/tests/*' \
		--output-file $(COV_DIR)/c-coverage.lcov \
		--rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors unused,unused --ignore-errors inconsistent,inconsistent
	@rm -f $(COV_DIR)/raw.lcov
	@echo "C coverage report: $(COV_DIR)/c-coverage.lcov"

clean:
	rm -rf build
