Files
latchd/Makefile

179 lines
8.2 KiB
Makefile

SHELL := /usr/bin/env bash
.SHELLFLAGS := -eu -o pipefail -c
.DEFAULT_GOAL := help
.DELETE_ON_ERROR:
PROJECT := latchd
BINARY := latchd
PREFIX ?= /usr
DESTDIR ?=
BINDIR := $(DESTDIR)$(PREFIX)/bin
CONFDIR := $(DESTDIR)/etc/$(PROJECT)
SYSTEMD_DIR := $(DESTDIR)/usr/lib/systemd/system
BUILD_DIR ?= build
DIST_DIR ?= dist
CARGO_TARGET_DIR ?= target
CARGO ?= cargo
GO ?= go
TARGET ?=
TAG ?=
VERSION ?= $(shell sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
GIT_COMMIT ?= $(shell git rev-parse --short=12 HEAD 2>/dev/null || printf unknown)
SOURCE_DATE_EPOCH ?= $(shell git log -1 --format=%ct 2>/dev/null || date +%s)
TARGET_TRIPLE ?= $(shell rustc -vV 2>/dev/null | sed -n 's/^host: //p' || printf unknown)
TARGET_ARGS := $(if $(TARGET),--target $(TARGET),)
RELEASE_DIR := $(CARGO_TARGET_DIR)/$(if $(TARGET),$(TARGET)/)release
PACKAGE_NAME := $(PROJECT)-$(VERSION)-$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))
PACKAGE_FILE := $(DIST_DIR)/$(PACKAGE_NAME).tar.gz
PACKAGE_STAGE := $(DIST_DIR)/stage/$(PACKAGE_NAME)
CARGO_ENV := LATCHD_VERSION=$(VERSION) LATCHD_COMMIT=$(GIT_COMMIT) LATCHD_BUILD_TIME=$(SOURCE_DATE_EPOCH) SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH)
COLOR ?= 1
ifeq ($(COLOR),1)
RESET := \033[0m
DIM := \033[2m
CYAN := \033[36m
GREEN := \033[32m
YELLOW := \033[33m
RED := \033[31m
else
RESET :=
DIM :=
CYAN :=
GREEN :=
YELLOW :=
RED :=
endif
.PHONY: all deps build release-build test check fmt lint doc audit docker-test \
integration-test system-e2e system-e2e-strict \
package release release-check version install uninstall clean run-preview \
legacy-build legacy-check enable update help
all: release-build ## Build the optimized release binary
version: ## Print the package, commit, and reproducibility metadata
@printf 'version=%s\ncommit=%s\nepoch=%s\ntarget=%s\n' \
'$(VERSION)' '$(GIT_COMMIT)' '$(SOURCE_DATE_EPOCH)' '$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))'
deps: ## Check native dependencies needed for builds and tests
@command -v $(CARGO) >/dev/null || { echo 'cargo (Rust 1.88+) is required'; exit 1; }
@command -v rustfmt >/dev/null || { echo 'rustfmt is required'; exit 1; }
@command -v pkg-config >/dev/null || { echo 'pkg-config is required'; exit 1; }
@pkg-config --exists pam || { echo 'libpam development files are required'; exit 1; }
@printf '$(GREEN)dependencies ok$(RESET)\n'
build: release-check ## Build and copy the optimized binary to build/
@mkdir -p '$(BUILD_DIR)'
@printf '$(CYAN)building$(RESET) %s %s\n' '$(VERSION)' '$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))'
@env $(CARGO_ENV) $(CARGO) build --release --locked $(TARGET_ARGS)
@install -m755 '$(RELEASE_DIR)/$(BINARY)' '$(BUILD_DIR)/$(BINARY)'
@'$(BUILD_DIR)/$(BINARY)' --version
release-build: build ## Alias for the optimized release build
fmt: ## Check Rust formatting
@$(CARGO) fmt --all -- --check
test: ## Run all Rust tests
@$(CARGO) test --locked --workspace --all-targets
lint: ## Run Clippy with warnings denied
@$(CARGO) clippy --locked --workspace --all-targets --all-features -- -D warnings
doc: ## Build rustdoc with warnings denied
@RUSTDOCFLAGS='-D warnings' $(CARGO) doc --locked --workspace --no-deps
audit: ## Run cargo-audit when installed
@command -v cargo-audit >/dev/null || { echo 'cargo-audit is required; install with cargo install cargo-audit --locked'; exit 1; }
@cargo audit
check: fmt test lint doc ## Run the complete local verification gate
docker-test: ## Run the pinned Docker verification suite
@./scripts/test-docker.sh
integration-test: build ## Exercise safe, non-authenticating CLI paths
@./scripts/test-cli.sh '$(BUILD_DIR)/$(BINARY)'
system-e2e: ## Probe PAM, VT, X11, Wayland, and logind without requiring privileges
@./scripts/test-system-e2e.sh '$(BUILD_DIR)/$(BINARY)'
system-e2e-strict: ## Require all real login-manager capabilities (prepared VM only)
@./scripts/test-system-e2e.sh --strict '$(BUILD_DIR)/$(BINARY)'
release-check: ## Validate a release version and optional tag
@[[ '$(VERSION)' =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$$ ]] || { echo 'invalid VERSION: $(VERSION)'; exit 1; }
@cargo_version="$$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)"; \
[[ "$$cargo_version" == '$(VERSION)' ]] || { echo "Cargo version $$cargo_version does not match $(VERSION)"; exit 1; }
@if [[ -n '$(TAG)' ]]; then \
tag_version="$$(printf %s '$(TAG)' | sed 's/^v//')"; \
[[ "$$tag_version" == '$(VERSION)' ]] || { echo "tag $(TAG) does not match $(VERSION)"; exit 1; }; \
fi
package: release-check build ## Create a deterministic install archive and checksum
@rm -rf '$(PACKAGE_STAGE)'
@mkdir -p '$(PACKAGE_STAGE)/usr/bin' '$(PACKAGE_STAGE)/usr/lib/systemd/system' \
'$(PACKAGE_STAGE)/etc/pam.d' '$(PACKAGE_STAGE)/etc/$(PROJECT)/wms' '$(PACKAGE_STAGE)/etc/$(PROJECT)/wayland'
@install -m755 '$(BUILD_DIR)/$(BINARY)' '$(PACKAGE_STAGE)/usr/bin/$(BINARY)'
@install -m644 extra/config.toml '$(PACKAGE_STAGE)/etc/$(PROJECT)/config.toml'
@install -m644 extra/latchd.pam '$(PACKAGE_STAGE)/etc/pam.d/$(PROJECT)'
@install -m644 extra/latchd.service '$(PACKAGE_STAGE)/usr/lib/systemd/system/$(PROJECT).service'
@install -m644 README.md LICENSE CHANGELOG.md '$(PACKAGE_STAGE)/'
@printf 'version=%s\ncommit=%s\nepoch=%s\ntarget=%s\n' \
'$(VERSION)' '$(GIT_COMMIT)' '$(SOURCE_DATE_EPOCH)' '$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))' \
> '$(PACKAGE_STAGE)/BUILD-METADATA'
@mkdir -p '$(DIST_DIR)'
@tar --sort=name --numeric-owner --owner=0 --group=0 --mtime='@$(SOURCE_DATE_EPOCH)' \
-C '$(DIST_DIR)/stage' -czf '$(PACKAGE_FILE)' '$(PACKAGE_NAME)'
@(cd '$(DIST_DIR)' && sha256sum '$(notdir $(PACKAGE_FILE))' > '$(notdir $(PACKAGE_FILE)).sha256')
@(cd '$(DIST_DIR)' && sha256sum --check '$(notdir $(PACKAGE_FILE)).sha256')
@checksum="$$(sha256sum '$(PACKAGE_FILE)' | cut -d' ' -f1)"; \
printf 'package=%s\nsha256=%s\nversion=%s\n' \
'$(PACKAGE_FILE)' "$$checksum" '$(VERSION)' > '$(DIST_DIR)/release.env'
@printf '$(GREEN)created$(RESET) %s\n' '$(PACKAGE_FILE)'
release: check audit integration-test system-e2e docker-test package ## Run all gates and produce release artifacts
install: build ## Install the binary, config, PAM policy, and systemd unit
@install -Dm755 '$(BUILD_DIR)/$(BINARY)' '$(BINDIR)/$(BINARY)'
@install -Dm644 extra/config.toml '$(CONFDIR)/config.toml'
@install -Dm644 extra/latchd.pam '$(DESTDIR)/etc/pam.d/$(PROJECT)'
@install -Dm644 extra/latchd.service '$(SYSTEMD_DIR)/$(PROJECT).service'
@install -d '$(CONFDIR)/wms' '$(CONFDIR)/wayland'
uninstall: ## Remove files installed by make install
@rm -f '$(BINDIR)/$(BINARY)' '$(DESTDIR)/etc/pam.d/$(PROJECT)' '$(SYSTEMD_DIR)/$(PROJECT).service'
@printf '$(YELLOW)configuration at %s was preserved; remove it manually if no longer needed$(RESET)\n' '$(CONFDIR)'
clean: ## Remove generated build, release, and Cargo artifacts
@rm -rf '$(BUILD_DIR)' '$(DIST_DIR)'
@$(CARGO) clean
run-preview: build ## Run the safe non-authenticating preview UI
@'$(BUILD_DIR)/$(BINARY)' --preview --no-log
legacy-build: ## Build the quarantined Go reference only when explicitly requested
@printf '$(YELLOW)legacy Go is reference-only and excluded from release builds$(RESET)\n'
@command -v $(GO) >/dev/null || { echo 'go is required for legacy-build'; exit 1; }
@mkdir -p '$(BUILD_DIR)'
@cd legacy/go && $(GO) build -trimpath -o '../../$(BUILD_DIR)/$(BINARY)-go' .
legacy-check: ## Format-check and test the quarantined Go reference
@command -v $(GO) >/dev/null || { echo 'go is required for legacy-check'; exit 1; }
@command -v gofmt >/dev/null || { echo 'gofmt is required for legacy-check'; exit 1; }
@test -z "$$(find legacy/go -type f -name '*.go' -print0 | xargs -0 gofmt -l)" || { echo 'legacy Go files need gofmt'; exit 1; }
@cd legacy/go && $(GO) test ./...
enable: ## Enable the systemd service after host validation
@sudo systemctl enable $(PROJECT).service
update: ## Rebuild and reinstall the current checkout without changing git state
@sudo $(MAKE) install
help: ## Show available targets
@printf '$(CYAN)$(PROJECT)$(RESET) $(VERSION) — production build targets\n\n'
@awk -F ':|##' '/^[a-zA-Z0-9_-]+:.*##/ { printf " %-18s %s\n", $$1, $$NF }' '$(MAKEFILE_LIST)' | sort