...
1# +--------------------------------------------------------------+
2# | * * * moul.io/rules.mk |
3# +--------------------------------------------------------------+
4# | |
5# | ++ ______________________________________ |
6# | ++++ / \ |
7# | ++++ | | |
8# | ++++++++++ | https://moul.io/rules.mk is a set | |
9# | +++ | | of common Makefile rules that can | |
10# | ++ | | be configured from the Makefile | |
11# | + -== ==| | or with environment variables. | |
12# | ( <*> <*> | | |
13# | | | /| Manfred Touron | |
14# | | _) / | manfred.life | |
15# | | +++ / \______________________________________/ |
16# | \ =+ / |
17# | \ + |
18# | |\++++++ |
19# | | ++++ ||// |
20# | ___| |___ _||/__ __|
21# | / --- \ \| ||| __ _ ___ __ __/ /|
22# |/ | | \ \ / / ' \/ _ \/ // / / |
23# || | | | | | /_/_/_/\___/\_,_/_/ |
24# +--------------------------------------------------------------+
25
26.PHONY: _default_entrypoint
27_default_entrypoint: help
28
29##
30## Common helpers
31##
32
33rwildcard = $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
34check-program = $(foreach exec,$(1),$(if $(shell PATH="$(PATH)" which $(exec)),,$(error "No $(exec) in PATH")))
35my-filter-out = $(foreach v,$(2),$(if $(findstring $(1),$(v)),,$(v)))
36novendor = $(call my-filter-out,vendor/,$(1))
37
38##
39## rules.mk
40##
41ifneq ($(wildcard rules.mk),)
42.PHONY: rulesmk.bumpdeps
43rulesmk.bumpdeps:
44 wget -O rules.mk https://raw.githubusercontent.com/moul/rules.mk/master/rules.mk
45BUMPDEPS_STEPS += rulesmk.bumpdeps
46endif
47
48##
49## Maintainer
50##
51
52ifneq ($(wildcard .git/HEAD),)
53.PHONY: generate.authors
54generate.authors: AUTHORS
55AUTHORS: .git/
56 echo "# This file lists all individuals having contributed content to the repository." > AUTHORS
57 echo "# For how it is generated, see 'https://github.com/moul/rules.mk'" >> AUTHORS
58 echo >> AUTHORS
59 git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf >> AUTHORS
60GENERATE_STEPS += generate.authors
61endif
62
63##
64## Golang
65##
66
67ifndef GOPKG
68ifneq ($(wildcard go.mod),)
69GOPKG = $(shell sed '/module/!d;s/^omdule\ //' go.mod)
70endif
71endif
72ifdef GOPKG
73GO ?= go
74GOPATH ?= $(HOME)/go
75GO_INSTALL_OPTS ?=
76GO_TEST_OPTS ?= -test.timeout=30s
77GOMOD_DIRS ?= $(sort $(call novendor,$(dir $(call rwildcard,*,*/go.mod go.mod))))
78GOCOVERAGE_FILE ?= ./coverage.txt
79GOTESTJSON_FILE ?= ./go-test.json
80GOBUILDLOG_FILE ?= ./go-build.log
81GOINSTALLLOG_FILE ?= ./go-install.log
82
83ifdef GOBINS
84.PHONY: go.install
85go.install:
86ifeq ($(CI),true)
87 @rm -f /tmp/goinstall.log
88 @set -e; for dir in $(GOBINS); do ( set -xe; \
89 cd $$dir; \
90 $(GO) install -v $(GO_INSTALL_OPTS) .; \
91 ); done 2>&1 | tee $(GOINSTALLLOG_FILE)
92
93else
94 @set -e; for dir in $(GOBINS); do ( set -xe; \
95 cd $$dir; \
96 $(GO) install $(GO_INSTALL_OPTS) .; \
97 ); done
98endif
99INSTALL_STEPS += go.install
100
101.PHONY: go.release
102go.release:
103 $(call check-program, goreleaser)
104 goreleaser --snapshot --skip-publish --rm-dist
105 @echo -n "Do you want to release? [y/N] " && read ans && \
106 if [ $${ans:-N} = y ]; then set -xe; goreleaser --rm-dist; fi
107RELEASE_STEPS += go.release
108endif
109
110.PHONY: go.unittest
111go.unittest:
112ifeq ($(CI),true)
113 @echo "mode: atomic" > /tmp/gocoverage
114 @rm -f $(GOTESTJSON_FILE)
115 @set -e; for dir in $(GOMOD_DIRS); do (set -e; (set -euf pipefail; \
116 cd $$dir; \
117 (($(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race -json && touch $@.ok) | tee -a $(GOTESTJSON_FILE) 3>&1 1>&2 2>&3 | tee -a $(GOBUILDLOG_FILE); \
118 ); \
119 rm $@.ok 2>/dev/null || exit 1; \
120 if [ -f /tmp/profile.out ]; then \
121 cat /tmp/profile.out | sed "/mode: atomic/d" >> /tmp/gocoverage; \
122 rm -f /tmp/profile.out; \
123 fi)); done
124 @mv /tmp/gocoverage $(GOCOVERAGE_FILE)
125else
126 @echo "mode: atomic" > /tmp/gocoverage
127 @set -e; for dir in $(GOMOD_DIRS); do (set -e; (set -xe; \
128 cd $$dir; \
129 $(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race); \
130 if [ -f /tmp/profile.out ]; then \
131 cat /tmp/profile.out | sed "/mode: atomic/d" >> /tmp/gocoverage; \
132 rm -f /tmp/profile.out; \
133 fi); done
134 @mv /tmp/gocoverage $(GOCOVERAGE_FILE)
135endif
136
137.PHONY: go.checkdoc
138go.checkdoc:
139 go doc $(first $(GOMOD_DIRS))
140
141.PHONY: go.coverfunc
142go.coverfunc: go.unittest
143 go tool cover -func=$(GOCOVERAGE_FILE) | grep -v .pb.go: | grep -v .pb.gw.go:
144
145.PHONY: go.lint
146go.lint:
147 @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
148 cd $$dir; \
149 golangci-lint run --verbose ./...; \
150 ); done
151
152.PHONY: go.tidy
153go.tidy:
154 @# tidy dirs with go.mod files
155 @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
156 cd $$dir; \
157 $(GO) mod tidy; \
158 ); done
159
160.PHONY: go.depaware-update
161go.depaware-update: go.tidy
162 @# gen depaware for bins
163 @set -e; for dir in $(GOBINS); do ( set -xe; \
164 cd $$dir; \
165 $(GO) run github.com/tailscale/depaware --update .; \
166 ); done
167 @# tidy unused depaware deps if not in a tools_test.go file
168 @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
169 cd $$dir; \
170 $(GO) mod tidy; \
171 ); done
172
173.PHONY: go.depaware-check
174go.depaware-check: go.tidy
175 @# gen depaware for bins
176 @set -e; for dir in $(GOBINS); do ( set -xe; \
177 cd $$dir; \
178 $(GO) run github.com/tailscale/depaware --check .; \
179 ); done
180
181
182.PHONY: go.build
183go.build:
184 @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
185 cd $$dir; \
186 $(GO) build ./...; \
187 ); done
188
189.PHONY: go.bump-deps
190go.bumpdeps:
191 @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
192 cd $$dir; \
193 $(GO) get -u ./...; \
194 ); done
195
196.PHONY: go.bump-deps
197go.fmt:
198 @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
199 cd $$dir; \
200 $(GO) run golang.org/x/tools/cmd/goimports -w `go list -f '{{.Dir}}' ./...` \
201 ); done
202
203VERIFY_STEPS += go.depaware-check
204BUILD_STEPS += go.build
205BUMPDEPS_STEPS += go.bumpdeps go.depaware-update
206TIDY_STEPS += go.tidy
207LINT_STEPS += go.lint
208UNITTEST_STEPS += go.unittest
209FMT_STEPS += go.fmt
210
211# FIXME: disabled, because currently slow
212# new rule that is manually run sometimes, i.e. `make pre-release` or `make maintenance`.
213# alternative: run it each time the go.mod is changed
214#GENERATE_STEPS += go.depaware-update
215endif
216
217##
218## Gitattributes
219##
220
221ifneq ($(wildcard .gitattributes),)
222.PHONY: _linguist-ignored
223_linguist-kept:
224 @git check-attr linguist-vendored $(shell git check-attr linguist-generated $(shell find . -type f | grep -v .git/) | grep unspecified | cut -d: -f1) | grep unspecified | cut -d: -f1 | sort
225
226.PHONY: _linguist-kept
227_linguist-ignored:
228 @git check-attr linguist-vendored linguist-ignored `find . -not -path './.git/*' -type f` | grep '\ set$$' | cut -d: -f1 | sort -u
229endif
230
231##
232## Node
233##
234
235ifndef NPM_PACKAGES
236ifneq ($(wildcard package.json),)
237NPM_PACKAGES = .
238endif
239endif
240ifdef NPM_PACKAGES
241.PHONY: npm.publish
242npm.publish:
243 @echo -n "Do you want to npm publish? [y/N] " && read ans && \
244 @if [ $${ans:-N} = y ]; then \
245 set -e; for dir in $(NPM_PACKAGES); do ( set -xe; \
246 cd $$dir; \
247 npm publish --access=public; \
248 ); done; \
249 fi
250RELEASE_STEPS += npm.publish
251endif
252
253##
254## Docker
255##
256
257docker_build = docker build \
258 --build-arg VCS_REF=`git rev-parse --short HEAD` \
259 --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
260 --build-arg VERSION=`git describe --tags --always` \
261 -t "$2" -f "$1" "$(dir $1)"
262
263ifndef DOCKERFILE_PATH
264DOCKERFILE_PATH = ./Dockerfile
265endif
266ifndef DOCKER_IMAGE
267ifneq ($(wildcard Dockerfile),)
268DOCKER_IMAGE = $(notdir $(PWD))
269endif
270endif
271ifdef DOCKER_IMAGE
272ifneq ($(DOCKER_IMAGE),none)
273.PHONY: docker.build
274docker.build:
275 $(call check-program, docker)
276 $(call docker_build,$(DOCKERFILE_PATH),$(DOCKER_IMAGE))
277
278BUILD_STEPS += docker.build
279endif
280endif
281
282##
283## Common
284##
285
286TEST_STEPS += $(UNITTEST_STEPS)
287TEST_STEPS += $(LINT_STEPS)
288TEST_STEPS += $(TIDY_STEPS)
289
290ifneq ($(strip $(TEST_STEPS)),)
291.PHONY: test
292test: $(PRE_TEST_STEPS) $(TEST_STEPS)
293endif
294
295ifdef INSTALL_STEPS
296.PHONY: install
297install: $(PRE_INSTALL_STEPS) $(INSTALL_STEPS)
298endif
299
300ifdef UNITTEST_STEPS
301.PHONY: unittest
302unittest: $(PRE_UNITTEST_STEPS) $(UNITTEST_STEPS)
303endif
304
305ifdef LINT_STEPS
306.PHONY: lint
307lint: $(PRE_LINT_STEPS) $(FMT_STEPS) $(LINT_STEPS)
308endif
309
310ifdef TIDY_STEPS
311.PHONY: tidy
312tidy: $(PRE_TIDY_STEPS) $(TIDY_STEPS)
313endif
314
315ifdef BUILD_STEPS
316.PHONY: build
317build: $(PRE_BUILD_STEPS) $(BUILD_STEPS)
318endif
319
320ifdef VERIFY_STEPS
321.PHONY: verify
322verify: $(PRE_VERIFY_STEPS) $(VERIFY_STEPS)
323endif
324
325ifdef RELEASE_STEPS
326.PHONY: release
327release: $(PRE_RELEASE_STEPS) $(RELEASE_STEPS)
328endif
329
330ifdef BUMPDEPS_STEPS
331.PHONY: bumpdeps
332bumpdeps: $(PRE_BUMDEPS_STEPS) $(BUMPDEPS_STEPS)
333endif
334
335ifdef FMT_STEPS
336.PHONY: fmt
337fmt: $(PRE_FMT_STEPS) $(FMT_STEPS)
338endif
339
340ifdef GENERATE_STEPS
341.PHONY: generate
342generate: $(PRE_GENERATE_STEPS) $(GENERATE_STEPS)
343endif
344
345.PHONY: help
346help::
347 @echo "General commands:"
348 @[ "$(BUILD_STEPS)" != "" ] && echo " build" || true
349 @[ "$(BUMPDEPS_STEPS)" != "" ] && echo " bumpdeps" || true
350 @[ "$(FMT_STEPS)" != "" ] && echo " fmt" || true
351 @[ "$(GENERATE_STEPS)" != "" ] && echo " generate" || true
352 @[ "$(INSTALL_STEPS)" != "" ] && echo " install" || true
353 @[ "$(LINT_STEPS)" != "" ] && echo " lint" || true
354 @[ "$(RELEASE_STEPS)" != "" ] && echo " release" || true
355 @[ "$(TEST_STEPS)" != "" ] && echo " test" || true
356 @[ "$(TIDY_STEPS)" != "" ] && echo " tidy" || true
357 @[ "$(UNITTEST_STEPS)" != "" ] && echo " unittest" || true
358 @[ "$(VERIFY_STEPS)" != "" ] && echo " verify" || true
359 @# FIXME: list other commands
360
361print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true
View as plain text