### ----------------------- # --- Building ### ----------------------- # first is default target when running "make" without args build: ##- Default 'make' target: sql, swagger, go-generate-handlers, go-format, go-build and lint. @$(MAKE) build-pre @$(MAKE) go-format @$(MAKE) go-build @$(MAKE) lint # useful to ensure that everything gets resetuped from scratch all: clean init ##- Runs all of our common make targets: clean, init, build and test. @$(MAKE) build @$(MAKE) test @$(MAKE) trivy info: info-db info-handlers info-go ##- Prints info about spec db, handlers, and go.mod updates, module-name and current go version. info-db: ##- (opt) Prints info about spec db. @echo "[spec DB]" > tmp/.info-db @cat scripts/sql/info.sql | psql -q -d "${PSQL_DBNAME}" >> tmp/.info-db @cat tmp/.info-db info-handlers: ##- (opt) Prints info about handlers. @echo "[handlers]" > tmp/.info-handlers @gsdev handlers check --print-all >> tmp/.info-handlers @echo "" >> tmp/.info-handlers @cat tmp/.info-handlers info-go: ##- (opt) Prints go.mod updates, module-name and current go version. @echo "[go.mod]" > tmp/.info-go @$(MAKE) get-go-outdated-modules >> tmp/.info-go @$(MAKE) info-module-name >> tmp/.info-go @go version >> tmp/.info-go @cat tmp/.info-go lint: check-gen-dirs check-script-dir check-handlers check-embedded-modules-go-not go-lint ##- Runs golangci-lint and make check-*. # these recipies may execute in parallel build-pre: sql swagger go-generate ##- (opt) Runs pre-build related targets (sql, swagger, go-generate-handlers, go-generate). @$(MAKE) go-generate-handlers go-format: ##- (opt) Runs go format. go fmt ./... go-build: ##- (opt) Runs go build. go build -ldflags $(LDFLAGS) -o bin/app go-lint: ##- (opt) Runs golangci-lint. golangci-lint run --timeout 5m go-generate-handlers: ##- (opt) Generates the internal/api/handlers/handlers.go binding. gsdev handlers gen go-generate: ##- Runs go generate. @go generate ./... check-handlers: ##- (opt) Checks if implemented handlers match their spec (path). gsdev handlers check # https://golang.org/pkg/cmd/go/internal/generate/ # To convey to humans and machine tools that code is generated, # generated source should have a line that matches the following # regular expression (in Go syntax): # ^// Code generated .* DO NOT EDIT\.$ check-gen-dirs: ##- (opt) Ensures internal/models|types only hold generated files. @echo "make check-gen-dirs" @find ./internal/types -name ".*" -prune -o -type f -print | xargs -L1 grep -L '// Code generated .* DO NOT EDIT\.' \ || (echo "Error: Non generated file(s) in ./internal/types!" && exit 1) @find ./internal/models -name ".*" -prune -o -type f -print | xargs -L1 grep -L '// Code generated .* DO NOT EDIT\.' \ || (echo "Error: Non generated file(s) in ./internal/models!" && exit 1) check-script-dir: ##- (opt) Ensures all scripts/**/*.go files have the "//go:build scripts" build tag set. @echo "make check-script-dir" @find ./scripts -type f -name '*.go' | xargs -L1 grep -L '//go:build scripts' || (echo "Error: Found unset '//go:build scripts' in ./scripts/**/*.go!" && exit 1) # https://github.com/gotestyourself/gotestsum#format # w/o cache https://github.com/golang/go/issues/24573 - see "go help testflag" # note that these tests should not run verbose by default (e.g. use your IDE for this) # TODO: add test shuffling/seeding when landed in go v1.15 (https://github.com/golang/go/issues/28592) # tests by pkgname test: ##- Run tests, output by package, print coverage. @$(MAKE) go-test-by-pkg @$(MAKE) go-test-print-coverage # tests by testname test-by-name: ##- Run tests, output by testname, print coverage. @$(MAKE) go-test-by-name @$(MAKE) go-test-print-coverage test-update-golden: ##- Refreshes all golden files / snapshot tests by running tests, output by package. @echo "Attempting to refresh all golden files / snapshot tests (TEST_UPDATE_GOLDEN=true)!" @echo -n "Are you sure? [y/N] " && read ans && [ $${ans:-N} = y ] @TEST_UPDATE_GOLDEN=true gotestsum --hide-summary=skipped -- -race -count=1 ./... # note that we explicitly don't want to use a -coverpkg=./... option, per pkg coverage take precedence go-test-by-pkg: ##- (opt) Run tests, output by package. gotestsum --format pkgname-and-test-fails --format-hide-empty-pkg --jsonfile /tmp/test.log -- -race -cover -count=1 -coverprofile=/tmp/coverage.out ./... go-test-by-name: ##- (opt) Run tests, output by testname. gotestsum --format testname --jsonfile /tmp/test.log -- -race -cover -count=1 -coverprofile=/tmp/coverage.out ./... go-test-print-coverage: ##- (opt) Print overall test coverage (must be done after running tests). @printf "coverage " @go tool cover -func=/tmp/coverage.out | tail -n 1 | awk '{$$1=$$1;print}' go-test-print-slowest: ##- Print slowest running tests (must be done after running tests). gotestsum tool slowest --jsonfile /tmp/test.log --threshold 2s # TODO: switch to "-m direct" after go 1.17 hits: https://github.com/golang/go/issues/40364 get-go-outdated-modules: ##- (opt) Prints outdated (direct) go modules (from go.mod). @((go list -u -m -f '{{if and .Update (not .Indirect)}}{{.}}{{end}}' all) 2>/dev/null | grep " ") || echo "go modules are up-to-date." watch-tests: ##- Watches *.go files and runs package tests on modifications. gotestsum --format testname --watch -- -race -count=1 test-scripts: ##- (opt) Run scripts tests (gsdev), output by package, print coverage. @$(MAKE) go-test-scripts-by-pkg @printf "coverage " @go tool cover -func=/tmp/coverage-scripts.out | tail -n 1 | awk '{$$1=$$1;print}' go-test-scripts-by-pkg: ##- (opt) Run scripts tests (gsdev), output by package. gotestsum --format pkgname-and-test-fails --jsonfile /tmp/test.log -- $$(go list -tags scripts ./... | grep "${GO_MODULE_NAME}/scripts") -tags scripts -race -cover -count=1 -coverprofile=/tmp/coverage-scripts.out ./... ### ----------------------- # --- Initializing ### ----------------------- init: ##- Runs make modules, tools and tidy. @$(MAKE) modules @$(MAKE) tools @$(MAKE) tidy # cache go modules (locally into .pkg) modules: ##- (opt) Cache packages as specified in go.mod. go mod download tools: ##- (opt) Install tools. @go install tool tidy: ##- (opt) Tidy our go.sum file. go mod tidy ### ----------------------- # --- SQL ### ----------------------- sql-reset: ##- Wizard to drop and create our development database. @echo "DROP & CREATE database:" @echo " PGHOST=${PGHOST} PGDATABASE=${PGDATABASE}" PGUSER=${PGUSER} @echo -n "Are you sure? [y/N] " && read ans && [ $${ans:-N} = y ] psql -d postgres -c 'DROP DATABASE IF EXISTS "${PGDATABASE}";' psql -d postgres -c 'CREATE DATABASE "${PGDATABASE}" WITH OWNER ${PGUSER} TEMPLATE "template0";' sql-drop-all: ##- Wizard to drop ALL databases: spec, development and tracked by integresql. @echo "DROP ALL:" TO_DROP=$$(psql -qtz0 -d postgres -c "SELECT 'DROP DATABASE \"' || datname || '\";' FROM pg_database WHERE datistemplate = FALSE AND datname != 'postgres';") @echo "$$TO_DROP" @echo -n "Are you sure? [y/N] " && read ans && [ $${ans:-N} = y ] @echo "Resetting integresql..." curl --fail -X DELETE http://integresql:5000/api/v1/admin/templates @echo "Drop databases..." echo $$TO_DROP | psql -tz0 -d postgres @echo "Done. Please run 'make sql-reset && make sql-spec-reset && make sql-spec-migrate' to reinitialize." # This step is only required to be executed when the "migrations" folder has changed! sql: ##- Runs sql format, all sql related checks and finally generates internal/models/*.go. @$(MAKE) sql-format @$(MAKE) sql-regenerate sql-regenerate: ##- (opt) Runs sql related checks and finally generates internal/models/*.go. @$(MAKE) sql-check-files @$(MAKE) sql-spec-reset @$(MAKE) sql-spec-migrate @$(MAKE) sql-check-and-generate sql-check-and-generate: sql-check-structure sql-boiler ##- (opt) Runs make sql-check-structure and sql-boiler. sql-boiler: ##- (opt) Runs sql-boiler introspects the spec db to generate internal/models/*.go. @echo "make sql-boiler" sqlboiler psql sql-format: ##- (opt) Formats all *.sql files. @echo "make sql-format" @find ${PWD} -path "*/tmp/*" -prune -name ".*" -prune -o -type f -iname "*.sql" -print \ | grep --invert "/app/dumps/" \ | grep --invert "/app/test/" \ | xargs -i pg_format --inplace {} sql-check-files: sql-check-syntax sql-check-migrations-unnecessary-null ##- (opt) Check syntax and unnecessary use of NULL keyword. # check syntax via the real database # https://stackoverflow.com/questions/8271606/postgresql-syntax-check-without-running-the-query sql-check-syntax: ##- (opt) Checks syntax of all *.sql files. @echo "make sql-check-syntax" @find ${PWD} -path "*/tmp/*" -prune -name ".*" -prune -path ./dumps -prune -false -o -type f -iname "*.sql" -print \ | grep --invert "/app/dumps/" \ | grep --invert "/app/test/" \ | xargs -i sed '1s#^#DO $$SYNTAX_CHECK$$ BEGIN RETURN;#; $$aEND; $$SYNTAX_CHECK$$;' {} \ | psql -d postgres --quiet -v ON_ERROR_STOP=1 sql-check-migrations-unnecessary-null: ##- (opt) Checks migrations/*.sql for unnecessary use of NULL keywords. @echo "make sql-check-migrations-unnecessary-null" @(grep -R "NULL" ./migrations/ | grep --invert "DEFAULT NULL" | grep --invert "NOT NULL" | grep --invert "WITH NULL" | grep --invert "NULL, " | grep --invert ", NULL" | grep --invert "RETURN NULL" | grep --invert "SET NULL") \ && exit 1 || exit 0 sql-spec-reset: ##- (opt) Drop and creates our spec database. @echo "make sql-spec-reset" @psql --quiet -d postgres -c 'DROP DATABASE IF EXISTS "${PSQL_DBNAME}";' @psql --quiet -d postgres -c 'CREATE DATABASE "${PSQL_DBNAME}" WITH OWNER ${PSQL_USER} TEMPLATE "template0";' sql-spec-migrate: ##- (opt) Applies migrations/*.sql to our spec database. @echo "make sql-spec-migrate" @sql-migrate up -env spec | xargs -i echo "[spec DB]" {} sql-check-structure: sql-check-structure-fk-missing-index sql-check-structure-default-zero-values ##- (opt) Runs make sql-check-structure-*. sql-check-structure-fk-missing-index: ##- (opt) Ensures spec database objects have FK-indices set. @echo "make sql-check-structure-fk-missing-index" @cat scripts/sql/fk_missing_index.sql | psql -qtz0 --no-align -d "${PSQL_DBNAME}" -v ON_ERROR_STOP=1 sql-check-structure-default-zero-values: ##- (opt) Ensures spec database objects default values match go zero values. @echo "make sql-check-structure-default-zero-values" @cat scripts/sql/default_zero_values.sql | psql -qtz0 --no-align -d "${PSQL_DBNAME}" -v ON_ERROR_STOP=1 dumpfile := /app/dumps/development_$(shell date '+%Y-%m-%d-%H-%M-%S').sql sql-dump: ##- Dumps the development database to '/app/dumps/development_YYYY-MM-DD-hh-mm-ss.sql'. @mkdir -p /app/dumps @pg_dump development --format=p --clean --if-exists > $(dumpfile) @echo "Dumped '$(dumpfile)'. Use 'cat $(dumpfile) | psql' to restore" watch-sql: ##- Watches *.sql files in /migrations and runs 'make sql-regenerate' on modifications. @echo Watching /migrations. Use Ctrl-c to stop a run or exit. watchexec -p -w migrations --exts sql $(MAKE) sql-regenerate ### ----------------------- # --- Swagger ### ----------------------- swagger: ##- Runs make swagger-concat and swagger-server. @$(MAKE) swagger-concat @$(MAKE) swagger-server # Any sibling elements of a $ref are ignored. This is because $ref works by replacing itself and everything on its level with the definition it is pointing at. # https://swagger.io/docs/specification/using-ref/ swagger-lint-ref-siblings: ##- (opt) Checks api/**/*.[yml|yaml] for invalid usage of $ref (no siblings). @echo "make swagger-lint-ref-siblings" @rm -f /tmp/swagger-lint-ref-siblings-errors.log && touch /tmp/swagger-lint-ref-siblings-errors.log @find api -type f -name "*.yml" -o -name "*.yaml" \ | { \ while read ymlfile; \ do \ ref_siblings=$$(yq e '.. | select(has("$$ref") and length != 1)' $$ymlfile); \ ([[ -z "$$ref_siblings" ]] \ || (echo "Error: Found invalid \$$ref siblings within $$ymlfile:" \ && (yq -P e '[.. | select(has("$$ref") and length != 1)]' $$ymlfile) \ && (echo $$ymlfile >> /tmp/swagger-lint-ref-siblings-errors.log))); \ done \ }; @[[ "$$(cat /tmp/swagger-lint-ref-siblings-errors.log | wc -l)" -eq "0" ]] \ || (echo "Error: $$(cat /tmp/swagger-lint-ref-siblings-errors.log | wc -l) files have \$$ref(s) with siblings!" \ && false) swagger-lint: @echo "make swagger-lint" @spectral lint --fail-severity=warn api/swagger.yml # https://goswagger.io/usage/mixin.html # https://goswagger.io/usage/flatten.html swagger-concat: ##- (opt) Regenerates api/swagger.yml based on api/paths/*. @echo "make swagger-concat" @mkdir -p api/tmp @rm -rf api/tmp/* @swagger mixin \ --output=api/tmp/tmp.yml \ --format=yaml \ --keep-spec-order \ api/config/main.yml api/paths/* \ -q @swagger flatten api/tmp/tmp.yml \ --output=api/swagger.yml \ --format=yaml \ -q @sed -i '1s@^@# // Code generated by "make swagger"; DO NOT EDIT.\n@' api/swagger.yml swagger-server: swagger-generate swagger-lint-ref-siblings swagger-validate swagger-lint ##- (opt) Lint/validate api/swagger.yml and generate /internal/types. # https://goswagger.io/generate/server.html # Note that we first flag all files to delete (as previously generated), regenerate, then delete all still flagged files # This allows us to ensure that any filewatchers (VScode) don't panic as these files are removed. # --keep-spec-order is broken (/tmp spec resolving): https://github.com/go-swagger/go-swagger/issues/2216 swagger-generate: ##- (opt) Generate swagger /internal/types. @echo "make swagger-generate" @rm -rf tmp/testdata/types @mkdir -p tmp/testdata/types @swagger generate server \ --allow-template-override \ --template-dir=api/templates \ --spec=api/swagger.yml \ --server-package=tmp/testdata/types \ --model-package=tmp/testdata/types \ --exclude-main \ --skip-validation \ --config-file=api/config/go-swagger-config.yml \ -q @find tmp/testdata/types -type f -exec sed -i "s|${GO_MODULE_NAME}/tmp/testdata/types|${GO_MODULE_NAME}/internal/types|g" {} \; rsync -au --ignore-times --delete tmp/testdata/types/ internal/types/ swagger-validate: ##- (opt) Validate api/swagger.yml. @echo "make swagger-validate" @swagger validate --skip-warnings --stop-on-error -q api/swagger.yml watch-swagger: ##- Watches *.yml|yaml|gotmpl files in /api and runs 'make swagger' on modifications. @echo "Watching /api/**/*.yml|yaml|gotmpl. Use Ctrl-c to stop a run or exit." watchexec -p -w api -i tmp -i api/swagger.yml --exts yml,yaml,gotmpl $(MAKE) swagger ### ----------------------- # --- Binary checks ### ----------------------- # Got license issues with some dependencies? Provide a custom lichen --config # see https://github.com/uw-labs/lichen#config get-licenses: ##- Prints licenses of embedded modules in the compiled bin/app. lichen bin/app get-embedded-modules: ##- Prints embedded modules in the compiled bin/app. go version -m -v bin/app get-embedded-modules-count: ##- (opt) Prints count of embedded modules in the compiled bin/app. go version -m -v bin/app | grep $$'\tdep' | wc -l check-embedded-modules-go-not: ##- (opt) Checks embedded modules in compiled bin/app against go.not, throws on occurrence. @echo "make check-embedded-modules-go-not" @(mkdir -p tmp 2> /dev/null && go version -m -v bin/app > tmp/.modules) grep -f go.not -F tmp/.modules && (echo "go.not: Found disallowed embedded module(s) in bin/app!" && exit 1) || exit 0 trivy-report: ##- Prints trivy report for all severities without failing @echo "make trivy-report" @trivy fs /app --exit-code 0 --quiet trivy: ##- Runs trivy analysis and fails on HIGH, CRITICAL vulnerabilities. @echo "make trivy" @trivy fs /app --exit-code 1 --severity HIGH,CRITICAL --quiet govulncheck: ##- Runs govulncheck. @echo "make govulncheck" @govulncheck /app/... ### ----------------------- # --- Git related ### ----------------------- # This is the default upstream go-starter branch we will use for our comparisons. # You may use a different tag/branch/commit like this: # - Merge with a specific tag, e.g. `go-starter-2021-10-19`: `GIT_GO_STARTER_TARGET=go-starter-2021-10-19 make git-merge-go-starter` # - Merge with a specific branch, e.g. `mr/housekeeping`: `GIT_GO_STARTER_TARGET=go-starter/mr/housekeeping make git-merge-go-starter` (heads up! it's `go-starter/`) # - Merge with a specific commit, e.g. `e85bedb9`: `GIT_GO_STARTER_TARGET=e85bedb94c3562602bc23d2bfd09fca3b13d1e02 make git-merge-go-starter` GIT_GO_STARTER_TARGET ?= go-starter/master GIT_GO_STARTER_BASE ?= $(GIT_GO_STARTER_TARGET:go-starter/%=%) git-fetch-go-starter: ##- (opt) Fetches upstream GIT_GO_STARTER_TARGET (creating git remote 'go-starter'). @echo "GIT_GO_STARTER_TARGET=${GIT_GO_STARTER_TARGET} GIT_GO_STARTER_BASE=${GIT_GO_STARTER_BASE}" @git config remote.go-starter.url >&- || git remote add go-starter https://github.com/allaboutapps/go-starter.git @git fetch go-starter ${GIT_GO_STARTER_BASE} git-compare-go-starter: ##- (opt) Compare upstream GIT_GO_STARTER_TARGET to HEAD displaying commits away and git log. @$(MAKE) git-fetch-go-starter @echo "Commits away from upstream go-starter ${GIT_GO_STARTER_TARGET}:" git --no-pager rev-list --pretty=oneline --left-only --count ${GIT_GO_STARTER_TARGET}...HEAD @echo "" @echo "Git log:" git --no-pager log --left-only --pretty="%C(Yellow)%h %C(reset)%ad (%C(Green)%cr%C(reset))%x09 %C(Cyan)%an: %C(reset)%s" --abbrev-commit --count ${GIT_GO_STARTER_TARGET}...HEAD git-merge-go-starter: ##- Merges upstream GIT_GO_STARTER_TARGET into current HEAD. @$(MAKE) git-compare-go-starter @(echo "" \ && echo "Attempting to execute 'git merge --no-commit --no-ff --allow-unrelated-histories ${GIT_GO_STARTER_TARGET}' into your current HEAD." \ && echo -n "Are you sure? [y/N]" \ && read ans && [ $${ans:-N} = y ]) || exit 1 git merge --no-commit --no-ff --allow-unrelated-histories ${GIT_GO_STARTER_TARGET} || true @echo "Done. We recommend to run 'make force-module-name' to automatically fix all import paths." ### ----------------------- # --- Helpers ### ----------------------- clean: ##- Cleans ./tmp and ./api/tmp folder. @echo "make clean" @rm -rf tmp/* 2> /dev/null @rm -rf api/tmp/* 2> /dev/null get-module-name: ##- Prints current go module-name (pipeable). @echo "${GO_MODULE_NAME}" info-module-name: ##- (opt) Prints current go module-name. @echo "go module-name: '${GO_MODULE_NAME}'" set-module-name: ##- Wizard to set a new go module-name. @rm -f tmp/.modulename @$(MAKE) info-module-name @echo "Enter new go module-name:" \ && read new_module_name \ && echo "new go module-name: '$${new_module_name}'" \ && echo -n "Are you sure? [y/N]" \ && read ans && [ $${ans:-N} = y ] \ && echo -n "Please wait..." \ && find . -not -path '*/\.*' -not -path './Makefile' -type f -exec sed -i "s|${GO_MODULE_NAME}|$${new_module_name}|g" {} \; \ && echo "new go module-name: '$${new_module_name}'!" @rm -f tmp/.modulename force-module-name: ##- Overwrite occurrences of 'allaboutapps.dev/aw/go-starter' with current go module-name. find . -not -path '*/\.*' -not -path './Makefile' -type f -exec sed -i "s|allaboutapps.dev/aw/go-starter|${GO_MODULE_NAME}|g" {} \; get-go-ldflags: ##- (opt) Prints used -ldflags as evaluated in Makefile used in make go-build @echo $(LDFLAGS) help: ##- Show common make targets. @echo -e "usage: make \033[36m\033[0m" @echo "note: use 'make help-all' to see all make targets." @awk 'BEGIN {FS = ":.*#\#-"; } /^[a-zA-Z_-]+:.*?/ && !/(opt)/ { printf " \033[36m%-40s\033[0m %s\n", $$1, $$2 } /^# --- / { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) help-all: ##- Show all make targets. @echo -e "usage: make \033[36m\033[0m" @awk 'BEGIN {FS = ":.*#\#-"; } /^[a-zA-Z_-]+:.*?/ { printf " \033[36m%-40s\033[0m %s\n", $$1, $$2 } /^# --- / { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) ### ----------------------- # --- Changelog ### ----------------------- changelog-prerelease: ##- Generates a changelog prerelease. @echo "make changelog-prerelease" @changie batch 0.0.0 --prerelease prerelease-$(shell date +%Y-%m-%d-%H%M%S) --move-dir prerelease @git add --all @git commit -m "PRERELEASE: $(shell date +%Y-%m-%d-%H%M%S)" changelog-release: ##- Generates a changelog release using the $(VERSION) variable. Usage: make changelog-release VERSION=24.11.0 @echo "make changelog-release with version $(VERSION)" @changie batch $(VERSION) --include prerelease --remove-prereleases @changie merge @git add --all @git commit -m "RELEASE: $(VERSION)" ### ----------------------- # --- Make variables ### ----------------------- # only evaluated if required by a recipe # http://make.mad-scientist.net/deferred-simple-variable-expansion/ # go module name (as in go.mod) GO_MODULE_NAME = $(eval GO_MODULE_NAME := $$(shell \ (mkdir -p tmp 2> /dev/null && cat tmp/.modulename 2> /dev/null) \ || (gsdev modulename 2> /dev/null | tee tmp/.modulename) || echo "unknown" \ ))$(GO_MODULE_NAME) # https://medium.com/the-go-journey/adding-version-information-to-go-binaries-e1b79878f6f2 ARG_COMMIT = $(eval ARG_COMMIT := $$(shell \ (git rev-list -1 HEAD 2> /dev/null) \ || (echo "unknown") \ ))$(ARG_COMMIT) ARG_BUILD_DATE = $(eval ARG_BUILD_DATE := $$(shell \ (date -Is 2> /dev/null || date 2> /dev/null || echo "unknown") \ ))$(ARG_BUILD_DATE) # https://www.digitalocean.com/community/tutorials/using-ldflags-to-set-version-information-for-go-applications LDFLAGS = $(eval LDFLAGS := "\ -X '$(GO_MODULE_NAME)/internal/config.ModuleName=$(GO_MODULE_NAME)'\ -X '$(GO_MODULE_NAME)/internal/config.Commit=$(ARG_COMMIT)'\ -X '$(GO_MODULE_NAME)/internal/config.BuildDate=$(ARG_BUILD_DATE)'\ ")$(LDFLAGS) ### ----------------------- # --- Special targets ### ----------------------- # https://www.gnu.org/software/make/manual/html_node/Special-Targets.html # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html # ignore matching file/make rule combinations in working-dir .PHONY: test help # https://unix.stackexchange.com/questions/153763/dont-stop-makeing-if-a-command-fails-but-check-exit-status # https://www.gnu.org/software/make/manual/html_node/One-Shell.html # required to ensure make fails if one recipe fails (even on parallel jobs) and on pipefails .ONESHELL: # # normal POSIX bash shell mode # SHELL = /bin/bash # .SHELLFLAGS = -cEeuo pipefail # wrapped make time tracing shell, use it via MAKE_TRACE_TIME=true make SHELL = /app/rksh .SHELLFLAGS = $@