Files
latchd/Makefile
Zane Walker faf5b27328 feat: ship Rust login manager and GitLab release pipeline
Replace the legacy Go implementation as the production path, quarantine the reference tree, add deterministic release packaging and version metadata, and run verification through GitLab CI.
2026-09-25 21:33:46 +05:30

169 lines
7.7 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 \
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
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 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