APP := contra

# What `carlos ship -target` defaults to, and therefore what a release
# must be built for. Building for your own machine and shipping that is a
# silent architecture mismatch: the binary uploads fine and fails to exec
# on the instance. Override for a one-off:
#   make release RELEASE_GOARCH=amd64
RELEASE_GOOS   ?= linux
RELEASE_GOARCH ?= arm64
RELEASE_BIN := releases/$(APP)-$(RELEASE_GOOS)-$(RELEASE_GOARCH)

# What the running binary reports from GET /api/version. The platform's
# deploy verification polls that endpoint on every instance socket and
# compares what it finds against the release it believes it adopted; an
# unstamped binary answers "dev" from every build you have ever made, so
# those two facts can never disagree and a process that was never
# recycled onto the new release verifies green. Stamping this is what
# gives that check the ability to fail.
#
# git describe, because the tag is what you ship against. Outside a
# repository, or before the first commit, name it yourself:
#   make release VERSION=v0.1.0
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null)

.PHONY: build release version-check test test-js test-oracles test-webkit oracles vet fmt-check migration-check ci

# build is the compile check: with more than one package matched, go
# build discards the output, so this catches a broken package without
# producing an artifact.
build:
	CGO_ENABLED=0 go build ./...

# release is what ships. -s -w drops the symbol table and DWARF, which on
# this family's apps is 23-31% off the binary and 45-53% off the
# compressed artifact — and compressed size is what actually gets
# transferred. carlos is built the same way (see platform's Makefile).
#
# What survives: panic traces keep their function names AND line numbers
# (Go's pclntab is untouched by these flags), debug.ReadBuildInfo works,
# and go version -m still reports module metadata. What you lose is
# source-level debugging with delve or gdb — build without the flags for
# that. rastrillo dev never strips, for the same reason.
release: version-check
	@mkdir -p releases
	CGO_ENABLED=0 GOOS=$(RELEASE_GOOS) GOARCH=$(RELEASE_GOARCH) \
		go build -ldflags="-s -w -X 'github.com/carlosframework/rastrillo.BuildVersion=$(VERSION)'" \
		-o $(RELEASE_BIN) ./cmd/$(APP)
	@echo
	@echo "built $(RELEASE_BIN) reporting version $(VERSION)"
	@echo "ship it with:"
	@echo "  carlos ship -app $(APP) -target $(RELEASE_GOOS)-$(RELEASE_GOARCH) $(RELEASE_BIN)"

# version-check refuses rather than letting release ship a binary whose
# version is untrue. Both refusals are about the same thing: /api/version
# is only evidence if the answer identifies one artifact.
#
# An empty stamp is worse than the "dev" default, because "dev" is
# saying something true about itself and "" looks like a value that
# arrived from somewhere.
#
# A dirty tree is refused rather than suffixed. A -dirty stamp names a
# commit plus changes nobody can name, so two different binaries can
# carry the identical string — which does not fix the check, it makes
# it look reliable while it is not, and that is the more expensive
# failure. Refusing costs one `git stash`; a wrong stamp ships. If you
# mean to ship uncommitted work, say so out loud:
#   make release VERSION=v0.1.0-wip
version-check:
	@if [ -z "$(VERSION)" ]; then \
		echo "make release: no version to stamp."; \
		echo "  git describe found nothing here: no repository, or no commit yet."; \
		echo "  Commit, or name it:  make release VERSION=v0.1.0"; \
		exit 1; \
	fi
	@case "$(VERSION)" in *-dirty) \
		echo "make release: the tree is dirty, so $(VERSION) does not name what you are about to ship."; \
		echo "  Commit or stash first, or name it:  make release VERSION=v0.1.0-wip"; \
		exit 1;; \
	esac

test:
	go test ./...

# The crypto twin. AGENTS.md: "Crypto is byte-identical in Go and JS, pinned
# by shared golden vectors." This runs the browser modules against the same
# vectors/contra-v1.json the Go suite uses, so a change to one side that is
# not made to the other fails here rather than in somebody's vault.
#
# A HARD gate, and it does not soften when node is missing. A security product
# must not silently stop verifying that the browser's crypto matches the
# server's: a skip would be indistinguishable from a pass on the one check
# that stops a Mac opening a vault the browser cannot.
test-js:
	@command -v node >/dev/null 2>&1 || { \
		echo "make test-js: node is not installed."; \
		echo "  This gate is not optional -- it is what proves the browser's"; \
		echo "  crypto matches the server's. Install node, do not skip it."; \
		exit 1; }
	node --test test/crypto.test.mjs test/ecies.test.mjs test/items.test.mjs \
		test/sharing.test.mjs

# The third-party oracles: the QR symbols decoded by zxing-cpp and drawn
# again by python-qrcode for a module-by-module comparison, and the two kit
# PDFs read back by PyMuPDF and pypdf.
#
# These matter. Comparing matrices rather than only decoding is how the
# reversed format-information bits were found — twelve wrong modules that a
# decode-only check reports as "does not scan" and leaves you guessing.
#
# NOT part of `make ci`, and that is a deliberate, reluctant split made on
# 2026-09-02 for the same reason test-webkit is out: the shared CI runner's
# python3 is a bare 3.12.3 with no pip module and no ensurepip, so `make
# oracles` cannot install anything there and the box is immutable-boot. A
# gate that cannot run on the runner is a gate that is permanently red, and
# permanently red CI teaches people to ignore red — which costs more than
# this check is worth defending in the wrong place.
#
# It does NOT skip when the packages are missing; it fails with the command
# that fixes it. Run it before trusting a change to qr.js, qrtables.js,
# qrgf.js, pdf.js or kit.js:
#
#   make oracles && make test-oracles
#
# Getting these back into CI is one line of python3-pip in the runner's
# user-data. Filed rather than worked around.
test-oracles:
	@command -v node >/dev/null 2>&1 || { \
		echo "make test-oracles: node is not installed."; exit 1; }
	node --test test/qr.test.mjs test/kit.test.mjs

# The third-party oracles' Python packages, for `make test-oracles`.
#
# Opt-in rather than a dependency of anything: a machine whose system python3
# already has them needs nothing, and test/oracle.mjs reaches for what this
# builds only when it exists. Pinned in test/requirements.txt, because an
# oracle that drifts is an oracle that stops meaning what it said last week.
#
# Two ways in, because a Debian python3 without the python3-venv package
# cannot create a venv at all (no ensurepip) — which is exactly what the
# shared runner turned out to be. A venv is preferred where it works; where
# it does not, the packages go into .oracles/ and test/oracle.mjs puts that
# on PYTHONPATH. Both give the oracles their imports without touching the
# box, which is the requirement: its user-data is fixed at launch.
oracles:
	@command -v python3 >/dev/null 2>&1 || { \
		echo "make oracles: python3 is not installed."; exit 1; }
	@set -e; \
	if python3 -m venv .venv >/dev/null 2>&1; then \
		.venv/bin/pip install --quiet --upgrade pip; \
		.venv/bin/pip install --quiet -r test/requirements.txt; \
		echo "oracles ready: .venv/bin/python3"; \
	elif python3 -m pip --version >/dev/null 2>&1; then \
		rm -rf .venv; \
		python3 -m pip install --quiet --target .oracles \
			-r test/requirements.txt; \
		echo "oracles ready: python3 with PYTHONPATH=.oracles"; \
	else \
		rm -rf .venv; \
		echo "make oracles: this python3 can neither create a venv nor run pip."; \
		echo "  python3: $$(python3 --version 2>&1)"; \
		echo "  venv   : $$(python3 -m venv /tmp/contra-venv-probe 2>&1 | tail -1)"; \
		echo "  pip    : $$(python3 -m pip --version 2>&1 | tail -1)"; \
		rm -rf /tmp/contra-venv-probe; \
		echo "  Install python3-venv or python3-pip, or set CONTRA_PYTHON to"; \
		echo "  an interpreter that already has test/requirements.txt."; \
		exit 1; \
	fi

# The browser crypto in WebKit, which is the engine this product can least
# afford to be wrong about and the one the Chromium rig never touches.
#
# NOT part of `make ci`, on purpose. It needs Playwright, which not every
# machine will have, and a gate that cannot run everywhere is a gate that
# teaches people to ignore red. It skips visibly rather than silently, and it
# is what you run before trusting a change to the crypto modules.
#
#   npm install playwright && make test-webkit
test-webkit:
	node --test test/webkit.test.mjs

vet:
	go vet ./...

fmt-check:
	@out=$$(gofmt -l .); if [ -n "$$out" ]; then echo "gofmt needed:"; echo "$$out"; exit 1; fi

# A step of its own, not folded into another target's recipe: ci.d/
# steps each exec one make target, so migration-check needs its own
# name to get its own reported step on a runner with step support.
#
# go run, not a bare "rastrillo": go.mod already requires rastrillo, so
# this needs nothing on PATH but the Go toolchain, and works offline
# after go mod download. A bare "rastrillo migration check" failed a
# fresh clone's very first CI run with "make: rastrillo: Command not
# found", and nothing in the scaffold said what to install.
migration-check:
	go run github.com/carlosframework/rastrillo/cmd/rastrillo migration check

# ci is the one gate: what a runner executes and what you run before
# pushing are the same definition. amadan's runner falls back to this
# target when .amadan/ci is absent; ci.d/ carries the same targets,
# reported one by one, for a runner with step support. migration-check
# fails the build the moment models.go and migrations/ disagree,
# instead of at boot on whatever machine notices next. If the app
# declares manifest resources, also add: rastrillo generate --check
ci: vet fmt-check test test-js migration-check
