aboutsummaryrefslogtreecommitdiff
path: root/Makefile
diff options
context:
space:
mode:
Diffstat (limited to 'Makefile')
-rw-r--r--Makefile250
1 files changed, 250 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9052026
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,250 @@
+# Makefile for duet.el
+# Test targets delegate to tests/Makefile.
+# setup / deps / compile / coverage / complexity / doctor operate at project root.
+# Run 'make help' for available commands.
+
+EASK ?= eask
+EMACS_BATCH = $(EASK) emacs --batch
+# Coverage / test loops need default-directory = tests/ so test files'
+# relative paths (../duet.el, sibling test files) resolve as they do under
+# tests/Makefile.
+EMACS_BATCH_TESTS = $(EASK) emacs --batch --eval '(cd "tests/")'
+
+TEST_DIR = tests
+SOURCE_FILE = duet.el
+
+# Transport CLIs DUET shells out to. rsync is the stage-1 default; rclone,
+# lftp, and unison arrive in later stages but `deps' installs the full set so
+# a contributor's environment is ready ahead of the code that uses them.
+TRANSPORT_CLIS = rsync rclone lftp unison
+
+# Cyclomatic-complexity gate (scripts/duet-complexity.el). The budget is soft
+# (the design spec allows a written justification past it); override per run
+# with `make complexity COMPLEXITY_THRESHOLD=12'.
+COMPLEXITY_THRESHOLD ?= 10
+COMPLEXITY_FILES = $(SOURCE_FILE)
+
+# Coverage configuration
+COVERAGE_DIR = .coverage
+COVERAGE_FILE = $(COVERAGE_DIR)/simplecov.json
+
+# Test-file list used by the coverage loop, mirroring tests/Makefile.
+# Coverage runs every test file so the report represents the full suite.
+ALL_TESTS = $(filter-out $(TEST_DIR)/test-bootstrap.el, \
+ $(wildcard $(TEST_DIR)/test-*.el))
+
+# Include local overrides if present (per-machine knobs, not committed)
+-include makefile-local
+
+.PHONY: help test test-all test-unit test-integration test-file test-one test-name \
+ count list validate lint check-deps clean clean-compiled clean-tests \
+ setup deps deps-elisp deps-system compile coverage coverage-summary coverage-clean \
+ complexity doctor test-live
+
+help:
+ @$(MAKE) -C $(TEST_DIR) help
+
+# Test target delegations
+test:
+ @$(MAKE) -C $(TEST_DIR) test
+
+test-all:
+ @$(MAKE) -C $(TEST_DIR) test-all
+
+test-unit:
+ @$(MAKE) -C $(TEST_DIR) test-unit
+
+test-integration:
+ @$(MAKE) -C $(TEST_DIR) test-integration
+
+test-file:
+ @$(MAKE) -C $(TEST_DIR) test-file FILE="$(FILE)"
+
+test-one:
+ @$(MAKE) -C $(TEST_DIR) test-one TEST="$(TEST)"
+
+test-name:
+ @$(MAKE) -C $(TEST_DIR) test-name TEST="$(TEST)"
+
+count:
+ @$(MAKE) -C $(TEST_DIR) count
+
+list:
+ @$(MAKE) -C $(TEST_DIR) list
+
+validate:
+ @$(MAKE) -C $(TEST_DIR) validate
+
+lint:
+ @$(MAKE) -C $(TEST_DIR) lint
+
+check-deps:
+ @$(MAKE) -C $(TEST_DIR) check-deps
+
+#
+# Dependencies
+#
+
+# Install everything DUET needs: Emacs Lisp deps (via eask) plus the transport
+# CLIs (via the system package manager).
+deps: deps-elisp deps-system
+
+# Install runtime + development Emacs Lisp dependencies via eask.
+deps-elisp setup:
+ @if ! command -v $(EASK) >/dev/null 2>&1; then \
+ echo "[✗] eask not found on PATH"; \
+ echo " Install: npm install -g @emacs-eask/cli"; \
+ echo " Or: https://emacs-eask.github.io/Getting-Started/Install-Eask/"; \
+ exit 1; \
+ fi
+ @echo "[i] Installing Emacs Lisp dependencies via eask..."
+ @$(EASK) install-deps --dev
+ @echo "[✓] Emacs Lisp dependencies installed in .eask/"
+
+# Install the transport CLIs via the detected system package manager. pacman
+# is checked first (the maintainer's environment); apt/dnf/zypper/brew follow.
+# An unknown manager is not a failure — it prints the package list so the user
+# can install by hand.
+deps-system:
+ @echo "[i] Installing transport CLIs: $(TRANSPORT_CLIS)"
+ @if command -v pacman >/dev/null 2>&1; then \
+ sudo pacman -S --needed $(TRANSPORT_CLIS); \
+ elif command -v apt-get >/dev/null 2>&1; then \
+ sudo apt-get install -y $(TRANSPORT_CLIS); \
+ elif command -v dnf >/dev/null 2>&1; then \
+ sudo dnf install -y $(TRANSPORT_CLIS); \
+ elif command -v zypper >/dev/null 2>&1; then \
+ sudo zypper install -y $(TRANSPORT_CLIS); \
+ elif command -v brew >/dev/null 2>&1; then \
+ brew install $(TRANSPORT_CLIS); \
+ else \
+ echo "[!] No supported package manager found."; \
+ echo " Install these yourself: $(TRANSPORT_CLIS)"; \
+ fi
+
+# Byte-compile duet.el — surfaces free-variable / unused-let / suspicious-call
+# warnings that checkdoc and elisp-lint don't catch. byte-compile-error-on-warn
+# is t so any warning fails the build.
+compile:
+ @echo "[i] Byte-compiling $(SOURCE_FILE)..."
+ @$(EMACS_BATCH) \
+ --eval "(progn \
+ (setq byte-compile-error-on-warn t) \
+ (batch-byte-compile))" $(SOURCE_FILE)
+ @echo "[✓] Compilation complete"
+
+#
+# Cyclomatic complexity (scripts/duet-complexity.el)
+#
+
+# Report per-function complexity and exit non-zero if any function exceeds the
+# soft budget. Advisory: run on demand, not wired into `make test'.
+complexity:
+ @echo "[i] Scanning complexity (budget = $(COMPLEXITY_THRESHOLD))..."
+ @emacs -Q --batch -L scripts -l duet-complexity \
+ --eval "(duet-complexity-batch '($(foreach f,$(COMPLEXITY_FILES),\"$(f)\")) $(COMPLEXITY_THRESHOLD))"
+
+#
+# Doctor — verify the runtime environment
+#
+
+# Report which transport CLIs are present and confirm duet loads. Missing CLIs
+# are warnings (rclone/lftp/unison land in later stages); a load failure is the
+# only hard error.
+doctor:
+ @echo "[i] DUET doctor"
+ @echo "Transport executables:"
+ @for c in $(TRANSPORT_CLIS); do \
+ if command -v $$c >/dev/null 2>&1; then \
+ printf " [✓] %-8s %s\n" "$$c" "$$(command -v $$c)"; \
+ else \
+ printf " [!] %-8s missing (install via 'make deps-system')\n" "$$c"; \
+ fi; \
+ done
+ @echo "Package load:"
+ @if $(EMACS_BATCH) -l $(SOURCE_FILE) --eval "(require 'duet)" >/dev/null 2>&1; then \
+ echo " [✓] (require 'duet) succeeds"; \
+ else \
+ echo " [✗] duet failed to load"; \
+ exit 1; \
+ fi
+
+# Run env-gated live remote tests. Skipped unless DUET_LIVE_TESTS is set, since
+# they need real remote hosts and credentials.
+test-live:
+ @if [ -z "$$DUET_LIVE_TESTS" ]; then \
+ echo "[i] DUET_LIVE_TESTS is unset — skipping live remote tests."; \
+ echo " Set DUET_LIVE_TESTS=1 (and the host/path vars the tests document) to run them."; \
+ else \
+ $(MAKE) -C $(TEST_DIR) test-name TEST='live'; \
+ fi
+
+#
+# Coverage (undercover + simplecov JSON)
+#
+# Each test file runs in its own Emacs process (matching test-unit);
+# tests/run-coverage-file.el instruments duet.el before the source is loaded,
+# and undercover merges per-file results into a single simplecov JSON.
+
+coverage: coverage-clean $(COVERAGE_DIR)
+ @echo "[i] Cleaning .elc files so undercover can instrument source..."
+ @find . -name "*.elc" -delete
+ @echo "[i] Running coverage across $(words $(ALL_TESTS)) test file(s)..."
+ @echo " (slower than 'make test' — each file runs in its own Emacs)"
+ @failed=0; \
+ for test in $(ALL_TESTS); do \
+ echo " Coverage: $$test..."; \
+ testfile=$$(basename $$test); \
+ $(EMACS_BATCH_TESTS) \
+ -l ert \
+ -l run-coverage-file.el \
+ -l ../$(SOURCE_FILE) \
+ -l $$testfile \
+ --eval "(ert-run-tests-batch-and-exit t)" || failed=$$((failed + 1)); \
+ done; \
+ if [ $$failed -gt 0 ]; then \
+ echo "[!] $$failed test file(s) failed during coverage run"; \
+ exit 1; \
+ fi
+ @coverage_file="$${COVERAGE_FILE_ACTUAL:-$(COVERAGE_FILE)}"; \
+ [ -n "$$CI" ] && coverage_file="$(COVERAGE_DIR)/coveralls.json"; \
+ if [ -f "$$coverage_file" ]; then \
+ echo "[✓] Coverage report: $$coverage_file ($$(du -h $$coverage_file | cut -f1))"; \
+ else \
+ echo "[!] No coverage file produced; check that undercover is installed"; \
+ exit 1; \
+ fi
+ @# Print the human-readable summary after a local run. CI emits
+ @# coveralls.json (not simplecov.json) and the upload action reports
+ @# instead, so skip the terminal summary there.
+ @if [ -z "$$CI" ] && [ -f $(COVERAGE_FILE) ]; then \
+ $(MAKE) --no-print-directory coverage-summary; \
+ fi
+
+# Print a human-readable summary of the SimpleCov report.
+coverage-summary:
+ @if [ ! -f $(COVERAGE_FILE) ]; then \
+ echo "[!] No coverage report at $(COVERAGE_FILE). Run 'make coverage' first."; \
+ exit 1; \
+ fi
+ @$(EMACS_BATCH) -L scripts -l coverage-summary \
+ --eval '(duet-coverage-print-summary "$(COVERAGE_FILE)" (list "$(SOURCE_FILE)") "$(CURDIR)")'
+
+coverage-clean:
+ @rm -f $(COVERAGE_FILE)
+
+$(COVERAGE_DIR):
+ @mkdir -p $(COVERAGE_DIR)
+
+#
+# Cleaning
+#
+
+clean: clean-compiled clean-tests
+ @rm -rf $(COVERAGE_DIR)
+
+clean-compiled:
+ @rm -f *.elc scripts/*.elc
+
+clean-tests:
+ @$(MAKE) -C $(TEST_DIR) clean