# secrethider developer commands.  Run `make help`.
#
#   make fix     format + auto-fix lint: C++ (clang-format), Python (ruff), JavaScript/TypeScript (prettier)
#   make test    build everything, run every test suite (C++, Python, Node), then the benchmarks
#
# Needs: cmake, ninja, a C++20 compiler, Python 3.9+ (uv is used when installed), Node 18+, clang-format.
# Windows: GNU make with Git's sh. Visual Studio's C++ tools are found automatically (scripts/msvc-env.bat).

.DEFAULT_GOAL := help

# ---- platform ----------------------------------------------------------------------------------
ifeq ($(OS),Windows_NT)
  PRESET      ?= dev-msvc
  VENV_BIN    := .venv/Scripts
  EXE         := .exe
  ENVRUN      := $(CURDIR)/scripts/msvc-env.bat
  PYBUILD_ENV := CMAKE_GENERATOR=Ninja CC=cl CXX=cl
  # the extensionless npm/npx are shell scripts that pick WSL's bash under Git's sh: use the .cmd shims
  NPM         := npm.cmd
  NPX         := npx.cmd
  NODE_BUILD  := npx.cmd cmake-js compile -G Ninja --CDCMAKE_C_COMPILER=cl --CDCMAKE_CXX_COMPILER=cl
else
  PRESET      ?= dev
  VENV_BIN    := .venv/bin
  EXE         :=
  ENVRUN      :=
  PYBUILD_ENV := CMAKE_GENERATOR=Ninja
  NPM         := npm
  NPX         := npx
  NODE_BUILD  := npx cmake-js compile
endif

# ---- tools -------------------------------------------------------------------------------------
PYTHON       ?= python
CLANG_FORMAT ?= clang-format
HAVE_UV      := $(shell command -v uv 2>/dev/null)
HAVE_RUFF    := $(shell command -v ruff 2>/dev/null)
PY           := $(VENV_BIN)/python$(EXE)
export UV_LINK_MODE := copy
ifdef HAVE_UV
  VENV_CREATE := uv venv .venv
  PIP_INSTALL := uv pip install --python $(PY)
else
  VENV_CREATE := $(PYTHON) -m venv .venv
  PIP_INSTALL := $(PY) -m pip install
endif
ifdef HAVE_RUFF
  RUFF ?= ruff
else ifdef HAVE_UV
  RUFF ?= uv tool run ruff
else
  RUFF ?= $(PYTHON) -m ruff
endif

# ---- sources (tracked or untracked-but-not-ignored, so it works before the first commit) -------
GIT_FILES = git ls-files --cached --others --exclude-standard
CPP_FILES  := $(shell $(GIT_FILES) -- '*.cpp' '*.hpp' '*.h')
SRC_CORE   := $(shell $(GIT_FILES) -- core cmake CMakeLists.txt)
SRC_PY     := $(SRC_CORE) $(shell $(GIT_FILES) -- bindings/python pyproject.toml README.md)
SRC_NODE   := $(SRC_CORE) $(shell $(GIT_FILES) -- bindings/node/src bindings/node/CMakeLists.txt bindings/node/package.json)

BUILD_DIR    := build/$(PRESET)
BENCH_FILTER ?= -Pathological

NODE_DEPS  := bindings/node/node_modules/.installed
NODE_ADDON := bindings/node/build/Release/secrethider.node
PY_STAMP   := .venv/.installed

.PHONY: help fix fix-cpp fix-python fix-js lint test check bench bench-full build \
        test-core test-python test-node bench-core bench-python bench-node qa clean

help: ## list the commands
	@grep -E '^[a-zA-Z_-]+:.*## ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*## "}; {printf "  make %-13s %s\n", $$1, $$2}'

# ---- format / lint -----------------------------------------------------------------------------
fix: fix-cpp fix-python fix-js ## format and auto-fix lint in place (C++, Python, JS/TS)

fix-cpp:
	$(CLANG_FORMAT) -i $(CPP_FILES)

fix-python:
	$(RUFF) check --fix bindings/python
	$(RUFF) format bindings/python

fix-js: $(NODE_DEPS)
	cd bindings/node && $(NPX) prettier --write "lib/**/*.{js,ts}" "test/**/*.js" "qa/**/*.js" "bench/**/*.js"

lint: $(NODE_DEPS) ## check formatting and lint without changing files (what CI should run)
	$(CLANG_FORMAT) --dry-run --Werror $(CPP_FILES)
	$(RUFF) check bindings/python
	$(RUFF) format --check bindings/python
	cd bindings/node && $(NPX) prettier --check "lib/**/*.{js,ts}" "test/**/*.js" "qa/**/*.js" "bench/**/*.js"

# ---- build -------------------------------------------------------------------------------------
build: ## configure and compile the C++ core, tests and benchmarks
	$(ENVRUN) cmake --preset $(PRESET)
	$(ENVRUN) cmake --build --preset $(PRESET)

$(PY_STAMP): $(SRC_PY)
	test -x $(PY) || $(VENV_CREATE)
	$(PYBUILD_ENV) $(ENVRUN) $(PIP_INSTALL) --reinstall . pytest faker psutil
	@touch $@

$(NODE_DEPS): bindings/node/package.json
	cd bindings/node && $(NPM) install --no-audit --no-fund
	@touch $@

$(NODE_ADDON): $(NODE_DEPS) $(SRC_NODE)
	cd bindings/node && $(ENVRUN) $(NODE_BUILD)

# ---- tests -------------------------------------------------------------------------------------
test: test-core test-python test-node bench ## build, run all tests (C++, Python, Node), then the benchmarks

check: test-core test-python test-node ## all tests, no benchmarks

test-core: build
	$(ENVRUN) ctest --preset $(PRESET)

test-python: $(PY_STAMP)
	$(PY) -m pytest -q

test-node: $(NODE_ADDON)
	cd bindings/node && $(NPM) test

# ---- benchmarks --------------------------------------------------------------------------------
bench: bench-core bench-python bench-node ## quick benchmarks for all three languages (BENCH_FILTER=. for everything)

bench-full: ## include the adversarial-input benchmarks
	$(MAKE) bench BENCH_FILTER=.

bench-core: build
	./$(BUILD_DIR)/core/secrethider_bench$(EXE) --benchmark_min_time=0.2s --benchmark_filter='$(BENCH_FILTER)'

bench-python: $(PY_STAMP)
	$(PY) bindings/python/bench/bench_redact.py

bench-node: $(NODE_ADDON)
	cd bindings/node && node bench/bench.js

# ---- deeper checks -----------------------------------------------------------------------------
qa: build $(PY_STAMP) $(NODE_ADDON) ## leak checks (C++, Python, Node) and a long randomized property run
	./$(BUILD_DIR)/core/secrethider_leakcheck$(EXE) 3000
	SECRETHIDER_PROP_ITERS=300000 ./$(BUILD_DIR)/core/secrethider_tests$(EXE) "property: random structured inputs keep all invariants"
	$(PY) bindings/python/qa/leakcheck.py 3000
	cd bindings/node && node --expose-gc qa/leakcheck.js 3000

clean: ## remove build outputs and the Python venv
	rm -rf build bindings/node/build .venv bindings/node/node_modules
