-
Notifications
You must be signed in to change notification settings - Fork 0
/
Makefile
157 lines (122 loc) · 6.52 KB
/
Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# Inspiration:
# - https://devhints.io/makefile
# - https://tech.davis-hansson.com/p/make/
# - https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
# Default - top level rule is what gets run when you run just 'make' without specifying a goal/target.
.DEFAULT_GOAL := build
# Make will delete the target of a rule if it has changed and its recipe exits with a nonzero exit status, just as it
# does when it receives a signal.
.DELETE_ON_ERROR:
# When a target is built, all lines of the recipe will be given to a single invocation of the shell rather than each
# line being invoked separately.
.ONESHELL:
# If this variable is not set, the program '/bin/sh' is used as the shell.
SHELL := bash
# The default value of .SHELLFLAGS is -c normally, or -ec in POSIX-conforming mode.
# Extra options are set for Bash:
# -e Exit immediately if a command exits with a non-zero status.
# -u Treat unset variables as an error when substituting.
# -o pipefail The return value of a pipeline is the status of the last command to exit with a non-zero status,
# or zero if no command exited with a non-zero status.
.SHELLFLAGS := -euo pipefail -c
# Eliminate use of Make's built-in implicit rules.
MAKEFLAGS += --no-builtin-rules
# Issue a warning message whenever Make sees a reference to an undefined variable.
MAKEFLAGS += --warn-undefined-variables
# Bring in variables from the '.env' file, ignoring errors if it does not exist.
-include .env
# Export all variables to child processes by default.
# This is used to bring forward all of the values that have been set in the '.env' file included above.
.EXPORT_ALL_VARIABLES:
# Check that the version of Make running this file supports the .RECIPEPREFIX special variable.
# We set it to '>' to clarify inlined scripts and disambiguate whitespace prefixes.
# All script lines start with "> " which is the angle bracket and one space, with no tabs.
ifeq ($(origin .RECIPEPREFIX), undefined)
$(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later.)
endif
.RECIPEPREFIX = >
# GNU make knows how to execute several recipes at once.
# Normally, make will execute only one recipe at a time, waiting for it to finish before executing the next.
# However, the '-j' or '--jobs' option tells make to execute many recipes simultaneously.
# With no argument, make runs as many recipes simultaneously as possible.
MAKEFLAGS += --jobs
# Configure an 'all' target to cover the bases.
all: test lint build ## Test and lint and build.
.PHONY: all
binary_name := $(shell basename $(CURDIR))
image_repository := jlucktay/$(binary_name)
# Adjust the width of the first column by changing the '-20s' value in the printf pattern.
help:
> @grep -E '^[a-zA-Z0-9_-]+:.*? ## .*$$' $(filter-out .env, $(MAKEFILE_LIST)) | sort \
| awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
.PHONY: help
# Set up some lazy initialisation functions to find code files, so that targets using the output of '$(shell ...)' only
# execute their respective shell commands when they need to, rather than every single instance of '$(shell ...)' being
# executed every single time 'make' is run for any target and wasting a lot of time.
# Further reading at https://www.oreilly.com/library/view/managing-projects-with/0596006101/ch10.html under the 'Lazy
# Initialization' heading.
find-go-files = $(shell find $1 -name vendor -prune -or -type f \( -iname '*.go' -or -name go.mod -or -name go.sum \))
GO_FILES = $(redefine-go-files) $(GO_FILES)
redefine-go-files = $(eval GO_FILES := $(call find-go-files, .))
# Tests look for sentinel files to determine whether or not they need to be run again.
# If any Go code file has been changed since the sentinel file was last touched, it will trigger a retest.
test: tmp/.tests-passed.sentinel ## Run tests.
test-cover: tmp/.cover-tests-passed.sentinel ## Run all tests with the race detector and output a coverage profile.
bench: tmp/.benchmarks-ran.sentinel ## Run enough iterations of each benchmark to take ten seconds each.
# Linter checks look for sentinel files to determine whether or not they need to check again.
# If any Go code file has been changed since the sentinel file was last touched, it will trigger a rerun.
lint: tmp/.linted.sentinel ## Lint the Go code. Will also test.
# If any Go code file has been changed since the binary was last touched, it will trigger a rebuild.
build: $(binary_name) ## [DEFAULT] Build a binary. Will also lint and test as necessary.
.PHONY: all test test-cover bench lint build
clean: ## Clean up the built binaries, test coverage, and the temp and output sub-directories.
> go clean -x -v
> rm -rf cover.out tmp out dist
.PHONY: clean
clean-hack: ## Deletes all binaries under 'hack'.
> rm -rf hack/bin
.PHONY: clean-hack
clean-all: clean clean-hack ## Clean all of the things.
.PHONY: clean-all
# Tests - re-run if any Go files have changes since 'tmp/.tests-passed.sentinel' was last touched.
tmp/.tests-passed.sentinel: $(GO_FILES)
> mkdir -p $(@D)
> go test -v ./...
> touch $@
tmp/.cover-tests-passed.sentinel: $(GO_FILES)
> mkdir -p $(@D)
> go test -count=1 -covermode=atomic -coverprofile=cover.out -race -v ./...
> touch $@
tmp/.benchmarks-ran.sentinel: $(GO_FILES)
> mkdir -p $(@D)
> go test -bench=. -benchmem -benchtime=10s -run='^DoNotRunTests$$' -v ./...
> touch $@
hack/bin/golangci-lint:
> mkdir -p $(@D)
> curl --fail --location --show-error --silent \
https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
| sh -s -- -b $(CURDIR)/hack/bin
# Lint - re-run if the tests have been re-run (and so, by proxy, whenever the source files have changed).
# These checks are all read-only and will not make any changes.
tmp/.linted.sentinel: tmp/.linted.gofmt.sentinel tmp/.linted.go.vet.sentinel \
tmp/.linted.golangci-lint.sentinel
> mkdir -p $(@D)
> touch $@
tmp/.linted.gofmt.sentinel: tmp/.tests-passed.sentinel
> mkdir -p $(@D)
> find . -type f -iname "*.go" -exec gofmt -d -e -l -s "{}" + \
| awk '{ print } END { if (NR != 0) { print "Please run \"make gofmt\" to fix these issues!"; exit 1 } }'
> touch $@
tmp/.linted.go.vet.sentinel: tmp/.tests-passed.sentinel
> mkdir -p $(@D)
> go vet ./...
> touch $@
tmp/.linted.golangci-lint.sentinel: .golangci.yaml hack/bin/golangci-lint tmp/.tests-passed.sentinel
> mkdir -p $(@D)
> hack/bin/golangci-lint run --verbose ./...
> touch $@
gofmt: ## Runs 'gofmt -s' to format and simplify all Go code.
> find . -type f -iname "*.go" -exec gofmt -s -w "{}" +
.PHONY: gofmt
$(binary_name): tmp/.linted.sentinel
> go build -trimpath -v -o $(binary_name)