Skip to content

Commit

Permalink
maint/build ~ convert to a cross-platform 'Makefile'
Browse files Browse the repository at this point in the history
  • Loading branch information
rivy committed Apr 19, 2020
1 parent 1f89ae0 commit 966b077
Showing 1 changed file with 253 additions and 25 deletions.
278 changes: 253 additions & 25 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,36 +1,264 @@
.PHONY: bootstrap
bootstrap: clean deps
# cross platform (*nix/windows) Makefile

.PHONY: deps
deps:
dep ensure -v
## NOTE: requirements ...
## * windows ~ `awk`, `grep`, and `make`; use `scoop install gawk grep make`
## * all platforms ~ `dep` and `goverage`; use `go get -u github.com/golang/dep/cmd/dep github.com/haya14busa/goverage`

NAME = ## optional (defaults to name of parent directory)

####

# spell-checker:ignore () busa chglog haya haya14busa
# spell-checker:ignore (targets) deps realclean veryclean
# spell-checker:ignore (make) abspath addprefix addsuffix endef findstring firstword ifeq ifneq lastword undefine notdir
#
# spell-checker:ignore (MSVC flags) defaultlib nologo
# spell-checker:ignore (abbrev/acronyms/names) MSVC
# spell-checker:ignore (clang flags) flto Xclang Wextra Werror
# spell-checker:ignore (flags) coverprofile
# spell-checker:ignore (go) GOBIN GOPATH dep goverage
# spell-checker:ignore (misc) brac cmdbuf forwback lessecho lesskey libcmt libpath linenum optfunc opttbl stdext ttyin
# spell-checker:ignore (shell/nix) printf uname
# spell-checker:ignore (shell/win) COMSPEC USERPROFILE delims findstr goawk mkdir
# spell-checker:ignore (vars) BQUOTE BSLASH CFLAGS CXXFLAGS DQUOTE LDFLAGS RMDIR SQUOTE devnull mkfile

####

# require at least `make` v4 (minimum needed for correct path functions)
MAKE_VERSION_major := $(word 1,$(subst ., ,$(MAKE_VERSION)))
MAKE_VERSION_fail := $(filter $(MAKE_VERSION_major),3 2 1 0)
ifneq ($(MAKE_VERSION_fail),)
$(error ERR!: `make` v4+ required)
endif

/ := /
NULL :=
SPACE := $() $()
BSLASH := $(strip \)
BQUOTE := $(firstword \` \`)
DQUOTE := \"
SQUOTE := \'
ifeq ($(OS),Windows_NT)
/ := $(strip \)
BQUOTE := $(subst \,,$(BQUOTE))
DQUOTE := $(subst \,,$(DQUOTE))
SQUOTE := $(subst \,,$(SQUOTE))
endif

mkfile_path := $(subst /,$(/),$(lastword $(MAKEFILE_LIST)))
mkfile_abs_path := $(subst /,$(/),$(abspath $(mkfile_path)))
mkfile_dir := $(subst /,$(/),$(abspath $(dir $(mkfile_abs_path))))
ifeq (${OS},Windows_NT)
SHELL := cmd
COMSPEC := $(shell echo %COMSPEC%)## avoid any env variable case variance issue
current_dir := $(subst /,$(/),$(abspath $(shell "$(COMSPEC)" /x/d/c echo %CD%)))## %CD% requires CMD extensions
else
SHELL := bash
current_dir := $(subst /,$(/),$(abspath $(shell echo $$PWD)))
endif

ifeq (${NAME},)
override NAME = $(notdir $(mkfile_dir))
endif

# $(info mkfile_path='$(mkfile_path)')
# $(info mkfile_abs_path='$(mkfile_abs_path)')
# $(info mkfile_dir='$(mkfile_dir)')
# $(info current_dir='$(current_dir)')

ifeq (${SPACE},$(findstring ${SPACE},${mkfile_abs_path}))
$(error ERR!: <space>'s within project directory may cause issues)
endif

####

OS_PREFIX=
ifeq (${OS},Windows_NT)
OS_ID = win
OS_PREFIX = $(OS_ID).
EXT = .exe
HOME ?= $(shell echo %USERPROFILE%)## avoid any env variable case variance issue
SystemRoot := $(shell echo %SystemRoot%)## avoid any env variable case variance issue
AWK = gawk
CP = copy /y
ECHO = echo
GREP = grep
MKDIR = mkdir
RM = del
RM_r = $(RM) /s
RM_rf = $(RM) /s
RMDIR = rmdir /s/q
RMDIR_f = rmdir /s/q
FIND = "$(SystemRoot)\System32\find"
FINDSTR = "$(SystemRoot)\System32\findstr"
MORE = "$(SystemRoot)\System32\more"
SORT = "$(SystemRoot)\System32\sort"
devnull = NUL
shell_true = cd .
else
/ := /
OS := $(shell uname | tr '[:upper:]' '[:lower:]')
OS_ID = $(OS)
OS_PREFIX = $(OS_ID).
EXT =
AWK = awk
CP = cp
ECHO = echo
GREP = grep
MKDIR = mkdir
PRINTF = printf
RM = rm
RM_r = $(RM) -r
RM_rf = $(RM) -rf
RMDIR = $(RM) -r
RMDIR_f = $(RM) -rf
SORT = sort
devnull = /dev/null
shell_true = true
endif

## xMKDIR_p == cross-platform MKDIR
ifeq ($(OS),Windows_NT)
define xMKDIR_p =
if NOT EXIST "$(subst /,\,$(1:"%"=%))" $(MKDIR) "$(subst /,\,$(1:"%"=%))" >$(devnull)
endef
else
define xMKDIR_p =
$(MKDIR) -p "$(1:"%"=%)" >$(devnull)
endef
endif

## xRM == cross-platform RM
ifeq ($(OS),Windows_NT)
define xRM =
if EXIST "$(subst /,\,$(1:"%"=%))" $(RM) "$(subst /,\,$(1:"%"=%))" >$(devnull) 2>&1 && echo "$(subst /,\,$(1:"%"=%))" removed
endef
else
define xRM =
ls -d "$(1:"%"=%)" >$(devnull) 2>&1 && $(RM) "$(1:"%"=%)" && echo \""$(1:"%"=%)"\" removed || ${shell_true}
endef
endif

## xRMDIR == cross-platform RMDIR
ifeq ($(OS),Windows_NT)
define xRMDIR =
if EXIST "$(subst /,\,$(1:"%"=%))" $(RMDIR_f) "$(subst /,\,$(1:"%"=%))" >$(devnull) 2>&1 && echo "$(subst /,\,$(1:"%"=%))" directory removed
endef
else
define xRMDIR =
ls -d "$(1:"%"=%)" >$(devnull) 2>&1 && $(RMDIR_f) "$(1:"%"=%)" && echo \""$(1:"%"=%)"\" directory removed || ${shell_true}
endef
endif

lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1)))))))))))))))))))))))))))

####

BUILD_DIR ?= \#builds

## `go` packages are generally compiled to targets which include debug and symbol information
CONFIG ?= debug
override CONFIG := $(call lc,$(CONFIG))

OUT_DIR = $(BUILD_DIR)$(/)$(OS_PREFIX)$(CONFIG)
# OUT_DIR = $(BUILD_DIR)$(/)$(OS_ID)

GOPATH ?= $(HOME)$(/)go
GOBIN ?= $(GOPATH)$(/)bin

# $(info BUILD_DIR='$(BUILD_DIR)')
# $(info CONFIG='$(CONFIG)')
# $(info GOPATH='$(GOPATH)')
# $(info NAME='$(NAME)')
# $(info OUT_DIR='$(OUT_DIR)')

####

GO_BUILD_FLAGS =
ifeq ($(call lc,$(CONFIG)),release)
## -ldflags="-s -w" == remove symbol and debug info from target
GO_BUILD_FLAGS = -ldflags="-s -w"
endif

####

# since we rely on paths relative to the makefile location, abort if current directory != makefile directory
ifneq ($(current_dir),$(mkfile_dir))
$(error ERR!: Invalid current directory; this makefile must be invoked from the directory it resides in ('$(mkfile_dir)'))
endif

####

ifeq (${mkfile_path},Makefile)
BUILD_HELP_ALIAS ?= make
else
BUILD_HELP_ALIAS ?= make -f "$(mkfile_path)"
endif

####

.PHONY: default
default: clean deps test ## [*DEFAULT*] Build and test project

####

.PHONY: clean
clean:
rm -rf ./vendor/
rm -rf ./dist/
rm -rf ./git-chglog
rm -rf $(GOPATH)/bin/git-chglog
rm -rf cover.out
clean: ## Remove build artifacts (including intermediate files)
@$(call xRMDIR,"dist")
@$(call xRM,"$(BUILD_DIR)$(/)cover.out")

.PHONY: bulid
build:
go build -i -o git-chglog
.PHONY: help
help: ## Display help
@echo usage: $(BQUOTE)$(BUILD_HELP_ALIAS) [TARGET [TARGET]...]$(BQUOTE)
@echo TARGETs:
ifeq ($(OS),Windows_NT)
@$(FINDSTR) "^[a-zA-Z-]*:" "$(mkfile_path)" | $(SORT) | for /f "tokens=1-2,* delims=:#" %%g in ('$(MORE)') do @(@call set "t=%%g " & @call echo %%t:~0,15%% %%i )
else
@$(GREP) -P "(?i)^[[:alpha:]-]+:" "$(mkfile_path)" | $(SORT) | $(AWK) 'match($$0,"^([[:alpha:]]+):.*?##\\s*(.*)$$",m){ $(PRINTF) "%-10s\t%s\n", m[1], m[2] }'
endif

.PHONY: test
test:
go test -v `go list ./... | grep -v /vendor/`
realclean: clean ## Remove *all* build artifacts (including all configurations and the build directory)
@$(call xRMDIR,"$(BUILD_DIR)")

veryclean: realclean ## Remove *all* build artifacts and GOPATH installed binary
@$(call xRM,"$(GOBIN)$(/)$(NAME)$(EXT)")

####

.PHONY: all build compile rebuild veryclean
# build: $(PROJECT_TARGET) ## Build project
# compile: $(OBJ_files) ## Build intermediate files
rebuild: clean build ## Clean and rebuild project

####

.PHONY: deps
deps: ## Ensure/install dependencies
dep ensure -v

.PHONY: build
build: ## Build project
@go build -i $(GO_BUILD_FLAGS) -o "$(OUT_DIR)$(/)$(NAME)$(EXT)" ./cmd/$(NAME)
@echo $(SQUOTE)$(OUT_DIR)$(/)$(NAME)$(EXT)$(SQUOTE) created

.PHONY: coverage
coverage:
goverage -coverprofile=cover.out `go list ./... | grep -v /vendor/`
go tool cover -func=cover.out
@rm -rf cover.out
coverage: ## Display test coverage for project files
@$(call xMKDIR_p,"$(BUILD_DIR)")
goverage -coverprofile="$(BUILD_DIR)$(/)cover.out" $(shell go list ./... | $(GREP) -v /vendor/)
go tool cover -func="$(BUILD_DIR)$(/)cover.out"
@$(call xRM,"$(BUILD_DIR)$(/)cover.out")

.PHONY: install
install:
go install ./cmd/git-chglog
install: ## Install project binary
@go install $(GO_BUILD_FLAGS) "./cmd/$(NAME)"
@echo $(SQUOTE)$(GOBIN)$(/)$(NAME)$(EXT)$(SQUOTE) created/installed

.PHONY: test
test: ## Test project
go test -v $(shell go list ./... | $(GREP) -v /vendor/)

####

.PHONY: changelog
changelog:
@git-chglog --next-tag $(tag) $(tag)
changelog: ## `git-chglog --next-tag $TAG $TAG` ~ create CHANGELOG for planned next tag (requires $TAG)
@git-chglog --next-tag $(TAG) $(TAG)

0 comments on commit 966b077

Please sign in to comment.