|
| 1 | +.DEFAULT_GOAL := help |
| 2 | +.PHONY: help format tidy lint vet test test-wasm test-browser cover check install-linters docs |
| 3 | + |
| 4 | +# The targets that matter are `format` and `check`, and they mean the same |
| 5 | +# thing here as in 0pcom/skywire, which is the reference for these repos. |
| 6 | +# .golangci.yml is copied from there. |
| 7 | + |
| 8 | +PROJECT_BASE := github.com/0magnet/netscrape |
| 9 | +OPTS ?= GO111MODULE=on |
| 10 | + |
| 11 | +# Whether the host checks run with cgo. 0 matches skywire, which is pure Go. |
| 12 | +# Repos whose real build needs cgo — a GUI, audio, anything binding a C library |
| 13 | +# — set this to 1, because linting them without it type-checks almost nothing. |
| 14 | +CGO ?= 0 |
| 15 | + |
| 16 | +# Packages this toolchain cannot build at all — firmware for a different |
| 17 | +# target, say. Not the same as code that merely does not build for this host: |
| 18 | +# js/wasm is handled below by running the checks again in that context, which |
| 19 | +# is the better answer whenever it is available. Empty in most repos. |
| 20 | +SKIP ?= |
| 21 | + |
| 22 | +# Directories rather than import paths, because golangci-lint resolves a bare |
| 23 | +# import path against the working directory and then cannot find it. |
| 24 | +# |
| 25 | +# Listed with the same CGO_ENABLED the checks use. Listed with cgo on and |
| 26 | +# linted with it off, a cgo-only package is named and then found to have no |
| 27 | +# files in it, which fails the run rather than reporting anything about it. |
| 28 | +# |
| 29 | +# -e so that one package that cannot be listed does not empty the list. Without |
| 30 | +# it, a single unbuildable package makes `go list` fail for the whole module, |
| 31 | +# this comes back blank, and the run below reports that there is nothing to |
| 32 | +# check — which reads exactly like passing. |
| 33 | +# Nested modules. `go list ./...` STOPS AT A MODULE BOUNDARY, so a repo that |
| 34 | +# keeps a second go.mod — desk's panes, pisano's web — had that half silently |
| 35 | +# unchecked: not failing, not skipped with a message, simply absent, which reads |
| 36 | +# exactly like passing. Each one is handled by re-running this same Makefile |
| 37 | +# inside it, so a submodule is checked the way the root is by construction |
| 38 | +# rather than by remembering to add it somewhere. |
| 39 | +# |
| 40 | +# Discovered rather than listed, so adding a module does not also mean editing |
| 41 | +# this. SUBMODULES= on the recursive call is what stops it recursing forever. |
| 42 | +THIS := $(abspath $(firstword $(MAKEFILE_LIST))) |
| 43 | +# The one lint config, by absolute path: a submodule has no .golangci.yml of |
| 44 | +# its own, and the recursive call runs with that submodule as the working |
| 45 | +# directory, so a relative -c would look for it there and fail. |
| 46 | +LINTCFG := $(dir $(THIS)).golangci.yml |
| 47 | +# Vendor mode only where there IS a vendor directory. The config names one mode |
| 48 | +# for the whole repo, and a submodule that does not vendor cannot satisfy it — |
| 49 | +# golangci-lint then reports "inconsistent vendoring" for a tree that is |
| 50 | +# perfectly consistent, because it was told to look for something that was |
| 51 | +# never there. Evaluated in the working directory, so the recursion gets the |
| 52 | +# answer for the module it is actually in. |
| 53 | +MODMODE = $(if $(wildcard vendor/modules.txt),vendor,readonly) |
| 54 | +SUBMODULES := $(patsubst %/go.mod,%,$(shell find . -mindepth 2 -name go.mod -not -path './vendor/*' -not -path '*/vendor/*' 2>/dev/null | sort)) |
| 55 | + |
| 56 | +define recurse |
| 57 | +@for m in $(SUBMODULES); do \ |
| 58 | + echo "--- $$m"; \ |
| 59 | + $(MAKE) --no-print-directory -C $$m -f $(THIS) $@ SUBMODULES= || exit 1; \ |
| 60 | + done |
| 61 | +endef |
| 62 | + |
| 63 | +PKGS = $(shell CGO_ENABLED=$(CGO) go list -e -f '{{.Dir}}' ./... 2>/dev/null $(if $(SKIP),| grep -vE '$(SKIP)')) |
| 64 | +JSPKGS = $(shell CGO_ENABLED=0 GOOS=js GOARCH=wasm go list -e -f '{{.Dir}}' ./... 2>/dev/null $(if $(SKIP),| grep -vE '$(SKIP)')) |
| 65 | + |
| 66 | +help: ## Show this help |
| 67 | + @grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \ |
| 68 | + | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-18s\033[0m %s\n", $$1, $$2}' |
| 69 | + |
| 70 | +tidy: ## Tidy dependencies |
| 71 | + ${OPTS} go mod tidy -v |
| 72 | + |
| 73 | +format: tidy ## Format the code. Needs goimports (make install-linters) |
| 74 | + @if grep -qE '^(replace|exclude)' go.mod; then \ |
| 75 | + echo "ERROR: go.mod contains replace or exclude directives which break go install @version"; \ |
| 76 | + grep -E '^(replace|exclude)' go.mod; \ |
| 77 | + exit 1; \ |
| 78 | + fi |
| 79 | + ${OPTS} goimports -w -local ${PROJECT_BASE} $(shell go list -f '{{.Dir}}' ./... 2>/dev/null | grep -v /vendor/) |
| 80 | + |
| 81 | +lint: ## Run golangci-lint. Needs it installed (make install-linters) |
| 82 | + command -v golangci-lint || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest |
| 83 | + golangci-lint --version |
| 84 | + @# Some of these repos are entirely js/wasm-tagged, so the host context has |
| 85 | + @# nothing in it and linting it is an error rather than a pass. |
| 86 | + @if [ -n "$(PKGS)" ]; then \ |
| 87 | + CGO_ENABLED=$(CGO) ${OPTS} golangci-lint run --modules-download-mode=$(MODMODE) -c $(LINTCFG) $(PKGS); \ |
| 88 | + else \ |
| 89 | + echo '--- nothing builds for this host; skipping the host pass'; \ |
| 90 | + fi |
| 91 | + @# A host run cannot see js/wasm-tagged files, so anything only they use |
| 92 | + @# reads as dead — and anything wrong inside them is never checked at all. |
| 93 | + @if grep -rlq '^//go:build js' --include='*.go' . 2>/dev/null; then \ |
| 94 | + echo '--- again in the js/wasm build context'; \ |
| 95 | + CGO_ENABLED=0 GOOS=js GOARCH=wasm ${OPTS} golangci-lint run --modules-download-mode=$(MODMODE) -c $(LINTCFG) $(JSPKGS); \ |
| 96 | + fi |
| 97 | + $(recurse) |
| 98 | + |
| 99 | +vet: ## Run go vet |
| 100 | + @if [ -n "$(PKGS)" ]; then \ |
| 101 | + CGO_ENABLED=$(CGO) ${OPTS} go vet $(PKGS); \ |
| 102 | + fi |
| 103 | + @if grep -rlq '^//go:build js' --include='*.go' . 2>/dev/null; then \ |
| 104 | + CGO_ENABLED=0 GOOS=js GOARCH=wasm ${OPTS} go vet $(JSPKGS); \ |
| 105 | + fi |
| 106 | + $(recurse) |
| 107 | + |
| 108 | +test: ## Run tests. Falls back to the js/wasm ones where nothing builds here |
| 109 | + @if [ -n "$(PKGS)" ]; then \ |
| 110 | + ${OPTS} go test $(PKGS); \ |
| 111 | + else \ |
| 112 | + echo '--- nothing builds for this host; running the js/wasm tests instead'; \ |
| 113 | + $(MAKE) --no-print-directory -f $(THIS) test-wasm; \ |
| 114 | + fi |
| 115 | + $(recurse) |
| 116 | + |
| 117 | +# The exec wrapper Go ships for running a js/wasm binary under Node. It is what |
| 118 | +# makes `go test` work for code the host cannot build at all. |
| 119 | +# The wrapper moved from misc/wasm to lib/wasm in Go 1.24, and CI may install |
| 120 | +# either side of that, so both are tried. Empty means this toolchain cannot |
| 121 | +# run js/wasm tests at all. |
| 122 | +WASMEXEC = $(shell for d in lib misc; do p="$$(go env GOROOT)/$$d/wasm/go_js_wasm_exec"; if [ -x "$$p" ]; then echo "$$p"; break; fi; done) |
| 123 | + |
| 124 | +test-wasm: ## Run the js/wasm tests under Node |
| 125 | + @# One shell for the whole target. An `exit 0` in a recipe line of its own |
| 126 | + @# ends that line and nothing else, so the earlier form announced it was |
| 127 | + @# skipping and then ran the tests regardless, against the exec wrapper it |
| 128 | + @# had just reported missing. |
| 129 | + @# Node has no DOM: document, window and requestAnimationFrame are all |
| 130 | + @# undefined. JS core is there, and a fake document can be installed from |
| 131 | + @# Go with js.Global().Set, which is how the DOM-facing code is covered. |
| 132 | + @if [ -z "$(WASMEXEC)" ]; then \ |
| 133 | + echo 'no js/wasm exec wrapper in this Go installation; skipping'; \ |
| 134 | + elif ! command -v node >/dev/null; then \ |
| 135 | + echo 'node is not installed; skipping'; \ |
| 136 | + elif [ -n "$(JSPKGS)" ]; then \ |
| 137 | + CGO_ENABLED=0 GOOS=js GOARCH=wasm ${OPTS} go test -exec="$(WASMEXEC)" $(JSPKGS); \ |
| 138 | + else \ |
| 139 | + echo 'no js/wasm packages'; \ |
| 140 | + fi |
| 141 | + $(recurse) |
| 142 | + |
| 143 | +# Running the js/wasm tests in a real browser instead of Node. Node has no DOM; |
| 144 | +# a browser has one, plus canvas and WebGL. Tests that build a fake DOM should |
| 145 | +# detect a real one and use it, so the same tests run both ways — anything the |
| 146 | +# fake gets wrong then shows up as a test that passes under Node and fails here. |
| 147 | +# |
| 148 | +# CHROME may name any Chrome-compatible binary. Brave is one. Not called BROWSER: |
| 149 | +# that is a conventional environment variable (xdg uses it) and ?= would take |
| 150 | +# whatever it happens to be set to — firefox, on this machine. |
| 151 | +CHROME ?= $(shell command -v google-chrome chromium chromium-browser brave 2>/dev/null | head -1) |
| 152 | + |
| 153 | +test-browser: ## Run the js/wasm tests in a headless browser (needs wasmbrowsertest) |
| 154 | + @command -v wasmbrowsertest >/dev/null || { \ |
| 155 | + echo 'wasmbrowsertest is not installed; go install github.com/agnivade/wasmbrowsertest@latest'; exit 0; } |
| 156 | + @[ -n "$(CHROME)" ] || { echo 'no Chrome-compatible browser found (set CHROME=); skipping'; exit 0; } |
| 157 | + @if [ -z "$(JSPKGS)" ]; then echo 'no js/wasm packages'; exit 0; fi |
| 158 | + @# chromedp looks for a binary called google-chrome and does not pass |
| 159 | + @# --no-sandbox, which some environments require. A shim supplies both. |
| 160 | + @d=$$(mktemp -d); printf '#!/bin/sh\nexec %s --no-sandbox "$$@"\n' '$(CHROME)' > $$d/google-chrome; \ |
| 161 | + chmod +x $$d/google-chrome; \ |
| 162 | + PATH=$$d:$$PATH CGO_ENABLED=0 GOOS=js GOARCH=wasm ${OPTS} \ |
| 163 | + go test -exec=wasmbrowsertest $(JSPKGS); \ |
| 164 | + rc=$$?; rm -rf $$d; exit $$rc |
| 165 | + $(recurse) |
| 166 | + |
| 167 | +cover: ## Report test coverage per package |
| 168 | + @if [ -n "$(PKGS)" ]; then \ |
| 169 | + CGO_ENABLED=$(CGO) ${OPTS} go test -cover $(PKGS) 2>&1 | grep -v '^ok.*no test files'; \ |
| 170 | + fi |
| 171 | + $(recurse) |
| 172 | + |
| 173 | +check: lint vet test ## Run linters, vet and tests |
| 174 | + |
| 175 | +install-linters: ## Install the linters |
| 176 | + ${OPTS} go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest |
| 177 | + ${OPTS} go install golang.org/x/tools/cmd/goimports@latest |
| 178 | + |
| 179 | +docs: ## Regenerate the dependency graph and code counts in the README |
| 180 | + ./gendocs.sh |
0 commit comments