1+ # Binary & paths
2+ BINARY_NAME := deeplink
3+ CMD_DIR := ./cmd
4+ BUILD_DIR := build
5+ DIST_DIR := dist
6+ INSTALL_PREFIX ?= /usr/local/bin
7+
8+ # Version info baked into binary at link time
9+ VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
10+ COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
11+ DATE := $(shell date -u +"% Y-% m-% dT% H:% M:% SZ")
12+ LDFLAGS := -ldflags "-X main.version=$(VERSION ) -X main.commit=$(COMMIT ) -X main.date=$(DATE ) "
13+
14+ # Go toolchain
15+ GO := go
16+ GOBIN := $(shell $(GO ) env GOPATH) /bin
17+
18+ # Lint timeout
19+ GOLANGCI_LINT_TIMEOUT ?= 5m
20+
21+ # Colors
22+ GREEN := \033[0;32m
23+ YELLOW := \033[0;33m
24+ BLUE := \033[0;34m
25+ RED := \033[0;31m
26+ NC := \033[0m
27+
28+ .PHONY : all
29+ all : build
30+
31+ # # build: Compile the binary
32+ .PHONY : build
33+ build :
34+ @echo " $( BLUE) Building $( BINARY_NAME) $( VERSION) ...$( NC) "
35+ @mkdir -p $(BUILD_DIR )
36+ $(GO ) build $(LDFLAGS ) -o $(BUILD_DIR ) /$(BINARY_NAME ) $(CMD_DIR )
37+ @echo " $( GREEN) ✓ Build complete: $( BUILD_DIR) /$( BINARY_NAME) $( NC) "
38+
39+ # # build-debug: Compile with debug symbols (no optimisations)
40+ .PHONY : build-debug
41+ build-debug :
42+ @echo " $( BLUE) Building debug binary...$( NC) "
43+ @mkdir -p $(BUILD_DIR )
44+ $(GO ) build -gcflags=" all=-N -l" -o $(BUILD_DIR ) /$(BINARY_NAME ) -debug $(CMD_DIR )
45+ @echo " $( GREEN) ✓ Debug binary: $( BUILD_DIR) /$( BINARY_NAME) -debug$( NC) "
46+
47+ # # release: Cross-compile for macOS (arm64/amd64), Linux, Windows
48+ .PHONY : release
49+ release : clean
50+ @echo " $( BLUE) Cross-compiling for all platforms...$( NC) "
51+ @mkdir -p $(DIST_DIR )
52+ GOOS=darwin GOARCH=arm64 $(GO ) build $(LDFLAGS ) -o $(DIST_DIR ) /$(BINARY_NAME ) -darwin-arm64 $(CMD_DIR )
53+ GOOS=darwin GOARCH=amd64 $(GO ) build $(LDFLAGS ) -o $(DIST_DIR ) /$(BINARY_NAME ) -darwin-amd64 $(CMD_DIR )
54+ GOOS=linux GOARCH=amd64 $(GO ) build $(LDFLAGS ) -o $(DIST_DIR ) /$(BINARY_NAME ) -linux-amd64 $(CMD_DIR )
55+ GOOS=linux GOARCH=arm64 $(GO ) build $(LDFLAGS ) -o $(DIST_DIR ) /$(BINARY_NAME ) -linux-arm64 $(CMD_DIR )
56+ GOOS=windows GOARCH=amd64 $(GO ) build $(LDFLAGS ) -o $(DIST_DIR ) /$(BINARY_NAME ) -windows-amd64.exe $(CMD_DIR )
57+ @echo " $( GREEN) ✓ Release binaries in $( DIST_DIR) /$( NC) "
58+ @ls -lh $(DIST_DIR )
59+
60+ # # run: Build and run with --help
61+ .PHONY : run
62+ run : build
63+ @echo " $( BLUE) Running $( BINARY_NAME) ...$( NC) "
64+ ./$(BUILD_DIR ) /$(BINARY_NAME ) --help
65+
66+ # # test: Run all unit tests
67+ .PHONY : test
68+ test :
69+ @echo " $( BLUE) Running tests...$( NC) "
70+ $(GO ) test -v -race ./...
71+ @echo " $( GREEN) ✓ All tests passed$( NC) "
72+
73+ # # test-coverage: Run tests and open HTML coverage report
74+ .PHONY : test-coverage
75+ test-coverage :
76+ @echo " $( BLUE) Running tests with coverage...$( NC) "
77+ $(GO ) test -race -coverprofile=coverage.out ./...
78+ $(GO ) tool cover -html=coverage.out -o coverage.html
79+ @echo " $( GREEN) ✓ Coverage report: coverage.html$( NC) "
80+ @$(GO ) tool cover -func=coverage.out | grep total
81+
82+ # # test-short: Run tests skipping slow/integration tests
83+ .PHONY : test-short
84+ test-short :
85+ @echo " $( BLUE) Running short tests...$( NC) "
86+ $(GO ) test -short ./...
87+
88+ # # lint: Run golangci-lint (falls back to go vet if not installed)
89+ .PHONY : lint
90+ lint :
91+ @echo " $( BLUE) Linting...$( NC) "
92+ @if command -v golangci-lint > /dev/null 2>&1 ; then \
93+ golangci-lint run --timeout=$(GOLANGCI_LINT_TIMEOUT ) ./...; \
94+ else \
95+ echo " $( YELLOW) golangci-lint not found, falling back to go vet$( NC) " ; \
96+ echo " $( YELLOW) Install with: make tools$( NC) " ; \
97+ $(GO ) vet ./...; \
98+ fi
99+ @echo " $( GREEN) ✓ Lint passed$( NC) "
100+
101+ # # format: Format code with gofmt + gofumpt
102+ .PHONY : format
103+ format :
104+ @echo " $( BLUE) Formatting code...$( NC) "
105+ $(GO ) fmt ./...
106+ @if command -v gofumpt > /dev/null 2>&1 ; then \
107+ gofumpt -w . ; \
108+ else \
109+ echo " $( YELLOW) gofumpt not found — only gofmt applied. Install with: make tools$( NC) " ; \
110+ fi
111+ @echo " $( GREEN) ✓ Code formatted$( NC) "
112+
113+ # # format-check: Check formatting without writing files (CI-safe)
114+ .PHONY : format-check
115+ format-check :
116+ @echo " $( BLUE) Checking formatting...$( NC) "
117+ @unformatted=" $$ (gofmt -l .)" ; \
118+ if [ -n " $$ unformatted" ]; then \
119+ echo " $( RED) ✗ Unformatted files (run 'make format'):$( NC) " ; \
120+ echo " $$ unformatted" ; \
121+ exit 1; \
122+ fi
123+ @if command -v gofumpt > /dev/null 2>&1 ; then \
124+ unformatted_gofumpt=" $$ (gofumpt -l .)" ; \
125+ if [ -n " $$ unformatted_gofumpt" ]; then \
126+ echo " $( RED) ✗ gofumpt issues detected (run 'make format'):$( NC) " ; \
127+ echo " $$ unformatted_gofumpt" ; \
128+ exit 1; \
129+ fi ; \
130+ fi
131+ @echo " $( GREEN) ✓ Formatting OK$( NC) "
132+
133+ # # vet: Run go vet
134+ .PHONY : vet
135+ vet :
136+ @echo " $( BLUE) Running go vet...$( NC) "
137+ $(GO ) vet ./...
138+ @echo " $( GREEN) ✓ vet passed$( NC) "
139+
140+ # # security: Check for known vulnerabilities (requires gosec)
141+ .PHONY : security
142+ security :
143+ @echo " $( BLUE) Checking for vulnerabilities...$( NC) "
144+ @if command -v gosec > /dev/null 2>&1 ; then \
145+ gosec ./...; \
146+ else \
147+ echo " $( YELLOW) gosec not found. Install with: go install github.com/securego/gosec/v2/cmd/gosec@latest$( NC) " ; \
148+ fi
149+
150+ # # deps: Download and tidy dependencies
151+ .PHONY : deps
152+ deps :
153+ @echo " $( BLUE) Installing dependencies...$( NC) "
154+ $(GO ) mod download
155+ $(GO ) mod tidy
156+ @echo " $( GREEN) ✓ Dependencies ready$( NC) "
157+
158+ # # update-deps: Upgrade all dependencies to latest
159+ .PHONY : update-deps
160+ update-deps :
161+ @echo " $( BLUE) Updating dependencies...$( NC) "
162+ $(GO ) get -u ./...
163+ $(GO ) mod tidy
164+ @echo " $( GREEN) ✓ Dependencies updated$( NC) "
165+
166+ # # tools: Install dev tools via mise (see .mise.toml)
167+ .PHONY : tools
168+ tools :
169+ @echo " $( BLUE) Installing dev tools via mise...$( NC) "
170+ @if ! command -v mise > /dev/null 2>&1 ; then \
171+ echo " $( RED) ✗ mise not found.$( NC) " ; \
172+ echo " $( YELLOW) Install: curl https://mise.run | sh$( NC) " ; \
173+ echo " $( YELLOW) Docs: https://mise.jdx.dev$( NC) " ; \
174+ exit 1; \
175+ fi
176+ mise install
177+ @echo " $( GREEN) ✓ Tools installed (versions pinned in .mise.toml)$( NC) "
178+
179+ # # install: Build and install binary to INSTALL_PREFIX (default: /usr/local/bin)
180+ .PHONY : install
181+ install : build
182+ @echo " $( BLUE) Installing to $( INSTALL_PREFIX) /$( BINARY_NAME) ...$( NC) "
183+ @install -d $(INSTALL_PREFIX )
184+ @install -m 755 $(BUILD_DIR ) /$(BINARY_NAME ) $(INSTALL_PREFIX ) /$(BINARY_NAME )
185+ @echo " $( GREEN) ✓ Installed: $( INSTALL_PREFIX) /$( BINARY_NAME) $( NC) "
186+
187+ # # uninstall: Remove installed binary
188+ .PHONY : uninstall
189+ uninstall :
190+ @echo " $( BLUE) Uninstalling $( BINARY_NAME) ...$( NC) "
191+ @if [ -f " $( INSTALL_PREFIX) /$( BINARY_NAME) " ]; then \
192+ rm -f $(INSTALL_PREFIX ) /$(BINARY_NAME ) ; \
193+ echo " $( GREEN) ✓ Removed $( INSTALL_PREFIX) /$( BINARY_NAME) $( NC) " ; \
194+ else \
195+ echo " $( YELLOW) $( BINARY_NAME) not found at $( INSTALL_PREFIX) /$( BINARY_NAME) $( NC) " ; \
196+ fi
197+
198+ # # install-hooks: Install pre-commit git hook (format-check + lint + test)
199+ .PHONY : install-hooks
200+ install-hooks :
201+ @echo " $( BLUE) Installing git hooks...$( NC) "
202+ @mkdir -p .githooks
203+ @printf ' #!/bin/sh\nset -e\nmake format-check\nmake vet\nmake test-short\n' > .githooks/pre-commit
204+ @chmod +x .githooks/pre-commit
205+ @git config core.hooksPath .githooks
206+ @echo " $( GREEN) ✓ pre-commit hook installed (.githooks/pre-commit)$( NC) "
207+
208+ # # clean: Remove build artifacts and coverage files
209+ .PHONY : clean
210+ clean :
211+ @echo " $( BLUE) Cleaning...$( NC) "
212+ @rm -rf $(BUILD_DIR ) $(DIST_DIR )
213+ @rm -f coverage.out coverage.html
214+ @echo " $( GREEN) ✓ Clean$( NC) "
215+
216+ # # dev: Full dev cycle — format, vet, lint, test, build
217+ .PHONY : dev
218+ dev : format vet lint test build
219+ @echo " $( GREEN) ✓ Dev cycle complete — ready to ship!$( NC) "
220+
221+ # # ci: What CI runs — no writes, strict checks
222+ .PHONY : ci
223+ ci : deps format-check vet lint test
224+ @echo " $( GREEN) ✓ CI checks passed$( NC) "
225+
226+ # # help: Show this help
227+ .PHONY : help
228+ help :
229+ @echo " "
230+ @echo " $( GREEN) $( BINARY_NAME) $( NC) $( VERSION) — Build System"
231+ @echo " "
232+ @echo " Usage: make [target]"
233+ @echo " "
234+ @awk ' BEGIN {FS = ":.*?## "} /^## / { \
235+ split($$ 0, a, " : " ); \
236+ printf " $( BLUE) %-22s$( NC) %s\n" , a[2], substr($$ 0, index($$ 0, a[2]) + length(a[2]) + 2) \
237+ }' $(MAKEFILE_LIST) | sort
238+ @echo " "
239+ @echo " Variables:"
240+ @echo " $( BLUE) INSTALL_PREFIX$( NC) Install path (default: /usr/local/bin)"
241+ @echo " $( BLUE) VERSION$( NC) $( VERSION) "
242+ @echo " $( BLUE) COMMIT$( NC) $( COMMIT) "
243+ @echo " "
0 commit comments