github.com/rivy-go/git-changelog@v0.0.0-20240424224517-b86e6ab57773/Makefile (about) 1 # Makefile (GoLang; OOS-build support; gmake-form/style; v2024.04.21) 2 # Cross-platform (*nix/windows) 3 # GNU make (gmake) compatible; ref: <https://www.gnu.org/software/make/manual> 4 # Copyright (C) 2020-2024 ~ Roy Ivy III <rivy.dev@gmail.com>; MIT+Apache-2.0 license 5 6 ## NOTE: requirements ... 7 ## * windows ~ `awk`, `grep`, and `make`; use `scoop install gawk grep make` 8 ## * all platforms ~ `goverage`; use `go install github.com/haya14busa/goverage@latest` (or `go get -u github.com/haya14busa/goverage` for earlier `go` versions) 9 10 # NOTE: * requires `make` version 4.0+ (minimum needed for correct path functions); for windows, install using `scoop install make` 11 # NOTE: `make` doesn't handle spaces within file names without gyrations (see <https://stackoverflow.com/questions/9838384/can-gnu-make-handle-filenames-with-spaces>@@<https://archive.is/PYKKq>) 12 # NOTE: `make -d` will display full debug output (`make` and makefile messages) during the build/make process 13 # NOTE: `make MAKEFLAGS_debug=1` will display just the makefile debug messages during the build/make process 14 # NOTE: use `make ... run -- <OPTIONS>` to pass options to the run TARGET; otherwise, `make` will interpret the options as targeted for itself 15 16 # `make [CONFIG=debug|release] [DEBUG=<truthy>] [STATIC=<truthy>] [SUBSYSTEM=console|windows|..] [TARGET=..] [COLOR=<truthy>] [MAKEFLAGS_debug=<truthy>] [VERBOSE=<truthy>] [MAKE_TARGET...]` 17 18 #### 19 20 # spell-checker:ignore (project) busa changelog haya haya14busa 21 22 # spell-checker:ignore (targets) realclean veryclean 23 # spell-checker:ignore (make) BASEPATH CURDIR MAKECMDGOALS MAKEFLAGS SHELLSTATUS TERMERR TERMOUT abspath addprefix addsuffix endef eval findstring firstword gmake ifeq ifneq lastword notdir patsubst prepend undefine wordlist 24 # 25 # spell-checker:ignore (MSVC flags) defaultlib nologo 26 # spell-checker:ignore (abbrev/acronyms/names) Deno MSDOS MSVC 27 # spell-checker:ignore (clang flags) flto Xclang Wextra Werror 28 # spell-checker:ignore (flags) coverprofile extldflags 29 # spell-checker:ignore (go) GOBIN GOPATH goverage golint asmflags gccgoflags gcflags ldflags 30 # spell-checker:ignore (jargon) autoset delims executables maint multilib 31 # spell-checker:ignore (misc) brac cmdbuf forwback lessecho lesskey libcmt libpath linenum optfunc opttbl stdext ttyin 32 # spell-checker:ignore (people) benhoyt rivy 33 # spell-checker:ignore (shell/nix) mkdir printf rmdir uname 34 # spell-checker:ignore (shell/win) COMSPEC SystemDrive SystemRoot findstr findstring mkdir windir 35 # spell-checker:ignore (utils) goawk 36 # spell-checker:ignore (vars) CFLAGS CLICOLOR CPPFLAGS CXXFLAGS DEFINETYPE EXEEXT LDFLAGS LDXFLAGS LIBPATH LIBs MAKEDIR OBJ_deps OBJs OSID PAREN RCFLAGS REZ REZs devnull dotslash falsey fileset filesets globset globsets punct truthy 37 38 #### 39 40 NAME := $()## $()/empty/null => autoset to name of containing folder 41 42 # SRC_PATH := $()## path to source relative to makefile (defaults to first of ['cmd','src','source']); used to create ${SRC_DIR} which is then used as the source base directory path 43 BUILD_PATH := $()## path to build storage relative to makefile (defaults to '#build'); used to create ${BUILD_DIR} which is then used as the base path for build outputs 44 45 #### 46 47 # `make ...` command line flag/option defaults 48 # ARCH := $()## default ARCH for compilation ([$(),...]); $()/empty/null => use CC default ARCH 49 # CC_DEFINES := false## provide compiler info (as `CC_...` defines) to compiling targets ('truthy'-type) 50 CONFIG := debug## default build configuration (debug/release); `go` packages are generally compiled to targets with debug and symbol information 51 COLOR := auto## defaults to "auto" mode ("on/true" if STDOUT is tty, "off/false" if STDOUT is redirected); will be modified later, in-process, to respect CLICOLOR/CLICOLOR_FORCE and NO_COLOR (but overridden by `COLOR=..` on command line); refs: <https://bixense.com/clicolors>@@<https://archive.is/mF4IA> , <https://no-color.org>@@<https://archive.ph/c32Wn> 52 DEBUG := false## enable compiler debug flags/options ('truthy'-type) 53 STATIC := true## compile to statically linked executable ('truthy'-type) 54 VERBOSE := false## verbose `make` output ('truthy'-type) 55 MAKEFLAGS_debug := $(if $(findstring d,${MAKEFLAGS}),true,false)## Makefile debug output ('truthy'-type; default == false) ## NOTE: use `-d` or `MAKEFLAGS_debug=1`, `--debug[=FLAGS]` does not set MAKEFLAGS correctly (see <https://savannah.gnu.org/bugs/?func=detailitem&item_id=58341>) 56 57 #### 58 59 MAKE_VERSION_major := $(word 1,$(subst ., ,${MAKE_VERSION})) 60 MAKE_VERSION_minor := $(word 2,$(subst ., ,${MAKE_VERSION})) 61 62 # require at least `make` v4.0 (minimum needed for correct path functions) 63 MAKE_VERSION_fail := $(filter ${MAKE_VERSION_major},3 2 1 0) 64 ifeq (${MAKE_VERSION_major},4) 65 MAKE_VERSION_fail := $(filter ${MAKE_VERSION_minor},) 66 endif 67 ifneq (${MAKE_VERSION_fail},) 68 # $(call %error,`make` v4.0+ required (currently using v${MAKE_VERSION})) 69 $(error ERR!: `make` v4.0+ required (currently using v${MAKE_VERSION})) 70 endif 71 72 makefile_path := $(lastword ${MAKEFILE_LIST})## note: *must* precede any makefile imports (ie, `include ...`) 73 74 makefile_abs_path := $(abspath ${makefile_path}) 75 makefile_dir := $(abspath $(dir ${makefile_abs_path})) 76 make_invoke_alias ?= $(if $(filter-out Makefile,${makefile_path}),${MAKE} -f "${makefile_path}",${MAKE}) 77 current_dir := ${CURDIR} 78 makefile_set := $(wildcard ${makefile_path} ${makefile_path}.config ${makefile_path}.target) 79 makefile_set_abs := $(abspath ${makefile_set}) 80 81 #### * determine OS ID 82 83 # note: environment/${OS}=="Windows_NT" for XP, 2000, Vista, 7, 10, 11, ... 84 OSID := $(or $(and $(filter .exe,$(patsubst %.exe,.exe,$(subst $() $(),_,${SHELL}))),$(filter win,${OS:Windows_NT=win})),nix)## OSID == [nix,win] 85 ifeq (${OSID},win) 86 # WinOS-specific settings 87 # * set SHELL (from COMSPEC or SystemRoot, if possible) 88 # ... `make` may otherwise use an incorrect shell (eg, `sh` or `bash`, if found in PATH); "syntax error: unexpected end of file" or "CreateProcess(NULL,...)" error output is indicative 89 SHELL := cmd$()## start with a known default shell (`cmd` for WinOS XP+) 90 # * set internal variables from environment variables (if available) 91 # ... avoid env var case variance issues and use fallbacks 92 # ... note: assumes *no spaces* within the path values specified by ${ComSpec}, ${SystemRoot}, or ${windir} 93 HOME := $(or $(strip $(shell echo %HOME%)),$(strip $(shell echo %UserProfile%))) 94 COMSPEC := $(strip $(shell echo %ComSpec%)) 95 SystemRoot := $(or $(strip $(shell echo %SystemRoot%)),$(strip $(shell echo %windir%))) 96 SHELL := $(firstword $(wildcard ${COMSPEC} ${SystemRoot}/System32/cmd.exe) cmd) 97 endif 98 99 #### * determine BASEPATH 100 101 # use ${BASEPATH} as an anchor to allow otherwise relative path specification of files 102 ifneq (${makefile_dir},${current_dir}) 103 BASEPATH := ${makefile_dir:${current_dir}/%=%} 104 # BASEPATH := $(patsubst ./%,%,${makefile_dir:${current_dir}/%=%}/) 105 endif 106 ifeq (${BASEPATH},) 107 BASEPATH := . 108 endif 109 110 #### * constants and methods 111 112 falsey_list := false 0 f n never no none off 113 falsey := $(firstword ${falsey_list}) 114 false := $() 115 true := true 116 truthy := ${true} 117 118 devnull := $(if $(filter win,${OSID}),NUL,/dev/null) 119 int_max := 2147483647## largest signed 32-bit integer; used as arbitrary max expected list length 120 121 NULL := $() 122 BACKSLASH := $()\$() 123 COMMA := , 124 DOLLAR := $$ 125 DOT := . 126 ESC := $()$()## literal ANSI escape character (required for ANSI color display output; also used for some string matching) 127 HASH := \# 128 PAREN_OPEN := $()($() 129 PAREN_CLOSE := $())$() 130 SLASH := / 131 SPACE := $() $() 132 133 [lower] := a b c d e f g h i j k l m n o p q r s t u v w x y z 134 [upper] := A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 135 [alpha] := ${[lower]} ${[upper]} 136 [digit] := 1 2 3 4 5 6 7 8 9 0 137 [punct] := ~ ` ! @ ${HASH} ${DOLLAR} % ^ & * ${PAREN_OPEN} ${PAREN_CLOSE} _ - + = { } [ ] | ${BACKSLASH} : ; " ' < > ${COMMA} ? ${SLASH} ${DOT} 138 139 %not = $(if ${1},${false},$(or ${1},${true})) 140 %eq = $(or $(and $(findstring ${1},${2}),$(findstring ${2},${1})),$(if ${1}${2},${false},${true}))# note: `call %eq,$(),$()` => ${true} 141 %neq = $(if $(call %eq,${1},${2}),${false},$(or ${1},${2},${true}))# note: ${1} != ${2} => ${false}; ${1} == ${2} => first non-empty value (or ${true}) 142 143 # %falsey := $(firstword ${falsey}) 144 # %truthy := $(firstword ${truthy}) 145 146 %as_truthy = $(if $(call %is_truthy,${1}),${truthy},${falsey})# note: returns 'truthy'-type text value (eg, true => 'true' and false => 'false') 147 %is_truthy = $(if $(filter-out ${falsey_list},$(call %lc,${1})),${1},${false})# note: returns `make`-type boolean value (eg, true => non-empty and false => $()/empty/null) 148 %is_falsey = $(call %not,$(call %is_truthy,${1}))# note: returns `make`-type boolean value (eg, true => non-empty and false => $()/empty/null) 149 150 %range = $(if $(word ${1},${2}),$(wordlist 1,${1},${2}),$(call %range,${1},${2} $(words _ ${2}))) 151 %repeat = $(if $(word ${2},${1}),$(wordlist 1,${2},${1}),$(call %repeat,${1} ${1},${2})) 152 153 %head = $(firstword ${1}) 154 %tail = $(wordlist 2,${int_max},${1}) 155 %chop = $(wordlist 2,$(words ${1}),_ ${1}) 156 %append = ${2} ${1} 157 %prepend = ${1} ${2} 158 %length = $(words ${1}) 159 160 %_position_ = $(if $(findstring ${1},${2}),$(call %_position_,${1},$(wordlist 2,$(words ${2}),${2}),_ ${3}),${3}) 161 %position = $(words $(call %_position_,${1},${2})) 162 163 %map = $(foreach elem,${2},$(call ${1},${elem}))# %map(fn,list) == [ fn(list[N]),... ] 164 %filter_by = $(strip $(foreach elem,${3},$(and $(filter $(call ${1},${2}),$(call ${1},${elem})),${elem})))# %filter_by(fn,item,list) == [ list[N] iff fn(item)==fn(list[N]), ... ] 165 %uniq = $(if ${1},$(firstword ${1}) $(call %uniq,$(filter-out $(firstword ${1}),${1}))) 166 167 %none = $(if $(call %map,${1},${2}),${false},${true})## %none(fn,list) => all of fn(list_N) == "" 168 %some = $(if $(call %map,${1},${2}),${true},${false})## %some(fn,list) => any of fn(list_N) != "" 169 %any = %some## %any(), aka %some(); %any(fn,list) => any of fn(list_N) != "" 170 %all = $(if $(call %map,%not,$(call %map,${1},${2})),${false},${true})## %all(fn,list) => all of fn(list_N) != "" 171 172 %cross = $(foreach a,${2},$(foreach b,${3},$(call ${1},${a},${b})))# %cross(fn,listA,listB) == [ fn(listA[N],listB[M]), ... {for all combinations of listA and listB }] 173 %join = $(subst ${SPACE},${1},$(strip ${2}))# %join(text,list) == join all list elements with text 174 %replace = $(foreach elem,${3},$(foreach pat,${1},${elem:${pat}=${2}}))# %replace(pattern(s),replacement,list) == [ ${list[N]:pattern[M]=replacement}, ... ] 175 176 %tr = $(strip $(if ${1},$(call %tr,$(wordlist 2,$(words ${1}),${1}),$(wordlist 2,$(words ${2}),${2}),$(subst $(firstword ${1}),$(firstword ${2}),${3})),${3})) 177 %lc = $(call %tr,${[upper]},${[lower]},${1}) 178 %uc = $(call %tr,${[lower]},${[upper]},${1}) 179 180 %as_nix_path = $(subst \,/,${1}) 181 %as_win_path = $(subst /,\,${1}) 182 %as_os_path = $(call %as_${OSID}_path,${1}) 183 184 %strip_leading_cwd = $(patsubst ./%,%,${1})# %strip_leading_cwd(list) == normalize paths; stripping any leading './' 185 %strip_leading_dotslash = $(patsubst ./%,%,${1})# %strip_leading_dotslash(list) == normalize paths; stripping any leading './' 186 187 %dirs_in = $(dir $(wildcard ${1:=/*/.})) 188 %filename = $(notdir ${1}) 189 %filename_base = $(basename $(notdir ${1})) 190 %filename_ext = $(suffix ${1}) 191 %filename_stem = $(firstword $(subst ., ,$(basename $(notdir ${1})))) 192 %recursive_wildcard = $(strip $(foreach entry,$(wildcard ${1:=/*}),$(strip $(call %recursive_wildcard,${entry},${2}) $(filter $(subst *,%,${2}),${entry})))) 193 194 %filter_by_stem = $(call %filter_by,%filename_stem,${1},${2}) 195 196 # * `%is_gui()` tests filenames for a match to '*[-.]gui{${EXEEXT},.${O}}' 197 %is_gui = $(if $(or $(call %is_gui_exe,${1}),$(call %is_gui_obj,${1})),${1},${false}) 198 %is_gui_exe = $(if $(and $(patsubst %-gui${EXEEXT},,${1}),$(patsubst %.gui${EXEEXT},,${1})),${false},${1}) 199 %is_gui_obj = $(if $(and $(patsubst %-gui.${O},,${1}),$(patsubst %.gui.${O},,${1})),${false},${1}) 200 201 # %any_gui = $(if $(foreach file,${1},$(call %is_gui,${file})),${true},${false}) 202 # %all_gui = $(if $(foreach file,${1},$(call %not,$(call %is_gui,${file}))),${false},${true}) 203 # %any_gui = $(call %any,%is_gui,${1}) 204 # %all_gui = $(call %all,%is_gui,${1}) 205 206 ifeq (${OSID},win) 207 %mkdir_shell_s = (if NOT EXIST $(call %shell_escape,$(call %as_win_path,${1})) ${MKDIR} $(call %shell_escape,$(call %as_win_path,${1})) >${devnull} 2>&1 && ${ECHO} ${true}) 208 else 209 %mkdir_shell_s = (${MKDIR} $(call %shell_escape,${1}) >${devnull} 2>&1 && ${ECHO} ${true}) 210 endif 211 %mkdir = $(shell $(call %mkdir_shell_s,${1})) 212 213 # * `rm` shell commands; note: return `${true}` result when argument (`${1}`) is successfully removed (to support verbose feedback display) 214 ifeq (${OSID},win) 215 %rm_dir_shell_s = (if EXIST $(call %shell_quote,$(call %as_win_path,${1})) (${RMDIR} $(call %shell_quote,$(call %as_win_path,${1})) >${devnull} 2>&1 && ${ECHO} ${true})) 216 %rm_file_shell_s = (if EXIST $(call %shell_quote,$(call %as_win_path,${1})) (${RM} $(call %shell_quote,$(call %as_win_path,${1})) >${devnull} 2>&1 && ${ECHO} ${true})) 217 %rm_file_globset_shell_s = (for %%G in $(call %shell_quote,($(call %as_win_path,${1}))) do (${RM} "%%G" >${devnull} 2>&1 && ${ECHO} ${true})) 218 else 219 %rm_dir_shell_s = (ls -d $(call %shell_escape,${1}) >${devnull} 2>&1 && { ${RMDIR} $(call %shell_escape,${1}) >${devnull} 2>&1 && ${ECHO} ${true}; } || true) 220 %rm_file_shell_s = (ls -d $(call %shell_escape,${1}) >${devnull} 2>&1 && { ${RM} $(call %shell_escape,${1}) >${devnull} 2>&1 && ${ECHO} ${true}; } || true) 221 %rm_file_globset_shell_s = (for file in $(call %shell_escape,${1}); do ls -d "$${file}" >${devnull} 2>&1 && ${RM} "$${file}"; done && ${ECHO} "${true}"; done) 222 endif 223 224 # NOTE: `_ := $(call %rm_dir,...)` or `$(if $(call %rm_dir,...))` can be used to avoid interpreting in-line output as a makefile command/rule (avoids `*** missing separator` errors) 225 %rm_dir = $(shell $(call %rm_dir_shell_s,${1})) 226 %rm_file = $(shell $(call %rm_file_shell_s,${1})) 227 %rm_file_globset = $(shell $(call %rm_file_globset_shell_s,${1})) 228 %rm_dirs = $(strip $(call %map,%rm_dir,${1})) 229 %rm_dirs_verbose = $(strip $(call %map,$(eval %f=$$(if $$(call %rm_dir,$${1}),$$(call %info,'$${1}' removed.),))%f,${1})) 230 %rm_files = $(strip $(call %map,%rm_file,${1})) 231 %rm_files_verbose = $(strip $(call %map,$(eval %f=$$(if $$(call %rm_file,$${1}),$$(call %info,'$${1}' removed.),))%f,${1})) 232 %rm_file_globsets = $(strip $(call %map,%rm_file_globset,${1})) 233 %rm_file_globsets_verbose = $(strip $(call %map,$(eval %f=$$(if $$(call %rm_file_globset,$${1}),$$(call %info,'$${1}' removed.),))%f,${1})) 234 235 # %rm_dirs_verbose_cli = $(call !shell_noop,$(call %rm_dirs_verbose,${1})) 236 237 ifeq (${OSID},win) 238 %shell_escape = $(call %tr,^ | < > %,^^ ^| ^< ^> ^%,${1}) 239 else 240 %shell_escape = '$(call %tr,','"'"',${1})' 241 endif 242 243 ifeq (${OSID},win) 244 %shell_quote = "$(call %shell_escape,${1})" 245 else 246 %shell_quote = $(call %shell_escape,${1}) 247 endif 248 249 # ref: <https://superuser.com/questions/10426/windows-equivalent-of-the-linux-command-touch/764716> @@ <https://archive.is/ZjFSm> 250 ifeq (${OSID},win) 251 %touch_shell_s = type NUL >> $(call %shell_quote,$(call %as_win_path,${1})) & copy >NUL /B $(call %shell_quote,$(call %as_win_path,${1})) +,, $(call %shell_quote,$(call %as_win_path,${1})) 252 else 253 %touch_shell_s = touch $(call %shell_quote,${1}) 254 endif 255 %touch = $(shell $(call %touch_shell_s,${1})) 256 257 @mkdir_rule = ${1} : ${2} ; @${MKDIR} $(call %shell_quote,$$@) >${devnull} 2>&1 && ${ECHO} $(call %shell_escape,$(call %info_text,created '$$@'.)) 258 259 !shell_noop = ${ECHO} >${devnull} 260 261 #### 262 263 ## determine COLOR based on NO_COLOR and CLICOLOR_FORCE/CLICOLOR; refs: <https://bixense.com/clicolors>@@<https://archive.is/mF4IA> , <https://no-color.org>@@<https://archive.ph/c32Wn> 264 COLOR := $(if $(call %is_truthy,${NO_COLOR}),false,${COLOR})## unconditionally NO_COLOR => COLOR=false 265 COLOR := $(if $(filter auto,${COLOR}),$(if $(call %is_truthy,${CLICOLOR_FORCE}),true,${COLOR}),${COLOR})## if autoset default ('auto') && CLICOLOR_FORCE => COLOR=true 266 COLOR := $(if $(filter auto,${COLOR}),$(if $(and ${CLICOLOR},$(call %is_falsey,${CLICOLOR})),false,${COLOR}),${COLOR})## if autoset default ('auto') && defined CLICOLOR && !CLICOLOR => COLOR=false 267 268 #### 269 270 override COLOR := $(call %as_truthy,$(or $(filter-out auto,$(call %lc,${COLOR})),${MAKE_TERMOUT})) 271 override DEBUG := $(call %as_truthy,${DEBUG}) 272 override STATIC := $(call %as_truthy,${STATIC}) 273 override VERBOSE := $(call %as_truthy,${VERBOSE}) 274 275 override MAKEFLAGS_debug := $(call %as_truthy,$(or $(call %is_truthy,${MAKEFLAGS_debug}),$(call %is_truthy,${MAKEFILE_debug}))) 276 277 #### 278 279 color_black := $(if $(call %is_truthy,${COLOR}),${ESC}[0;30m,) 280 color_blue := $(if $(call %is_truthy,${COLOR}),${ESC}[0;34m,) 281 color_cyan := $(if $(call %is_truthy,${COLOR}),${ESC}[0;36m,) 282 color_green := $(if $(call %is_truthy,${COLOR}),${ESC}[0;32m,) 283 color_magenta := $(if $(call %is_truthy,${COLOR}),${ESC}[0;35m,) 284 color_red := $(if $(call %is_truthy,${COLOR}),${ESC}[0;31m,) 285 color_yellow := $(if $(call %is_truthy,${COLOR}),${ESC}[0;33m,) 286 color_white := $(if $(call %is_truthy,${COLOR}),${ESC}[0;37m,) 287 color_bold := $(if $(call %is_truthy,${COLOR}),${ESC}[1m,) 288 color_dim := $(if $(call %is_truthy,${COLOR}),${ESC}[2m,) 289 color_hide := $(if $(call %is_truthy,${COLOR}),${ESC}[8;30m,) 290 color_reset := $(if $(call %is_truthy,${COLOR}),${ESC}[0m,) 291 # 292 color_command := ${color_dim} 293 color_path := $() 294 color_target := ${color_green} 295 color_success := ${color_green} 296 color_failure := ${color_red} 297 color_debug := ${color_cyan} 298 color_info := ${color_blue} 299 color_warning := ${color_yellow} 300 color_error := ${color_red} 301 302 %error_text = ${color_error}ERR!:${color_reset} ${1} 303 %debug_text = ${color_debug}debug:${color_reset} ${1} 304 %info_text = ${color_info}info:${color_reset} ${1} 305 %success_text = ${color_success}SUCCESS:${color_reset} ${1} 306 %failure_text = ${color_failure}FAILURE:${color_reset} ${1} 307 %warning_text = ${color_warning}WARN:${color_reset} ${1} 308 %error = $(error $(call %error_text,${1})) 309 %debug = $(if $(call %is_truthy,${MAKEFLAGS_debug}),$(info $(call %debug_text,${1})),) 310 %info = $(info $(call %info_text,${1})) 311 %success = $(info $(call %success_text,${1})) 312 %failure = $(info $(call %failure_text,${1})) 313 %warn = $(info $(call %warning_text,${1})) 314 %warning = $(info $(call %warning_text,${1})) 315 316 %debug_var = $(call %debug,${1}="${${1}}") 317 %info_var = $(call %info,${1}="${${1}}") 318 319 #### * OS-specific tools and vars 320 321 EXEEXT_nix := $() 322 EXEEXT_win := .exe 323 324 ifeq (${OSID},win) 325 OSID_name := windows 326 OS_PREFIX := win. 327 EXEEXT := ${EXEEXT_win} 328 # 329 AWK := gawk## from `scoop install gawk`; or "goawk" from `go get github.com/benhoyt/goawk` 330 CAT := "${SystemRoot}\System32\findstr" /r .*## note: (unlike `type`) will read from STDIN; BUT with multiple file arguments, this will prefix each line with the file name 331 CP := copy /y 332 ECHO := echo 333 GREP := grep## from `scoop install grep` 334 MKDIR := mkdir 335 RM := del 336 RM_r := ${RM} /s 337 RMDIR := rmdir /s/q 338 RMDIR_f := rmdir /s/q 339 FIND := "${SystemRoot}\System32\find" 340 FINDSTR := "${SystemRoot}\System32\findstr" 341 MORE := "${SystemRoot}\System32\more" 342 SORT := "${SystemRoot}\System32\sort" 343 TYPE := type## note: will not read from STDIN unless invoked as `${TYPE} CON` 344 WHICH := where 345 # 346 ECHO_newline := echo. 347 shell_true := cd . 348 else 349 OSID_name ?= $(shell uname | tr '[:upper:]' '[:lower:]') 350 OS_PREFIX := ${OSID_name}. 351 EXEEXT := $(if $(call %is_truthy,${CC_is_MinGW_w64}),${EXEEXT_win},${EXEEXT_nix}) 352 # 353 AWK := awk 354 CAT := cat 355 CP := cp 356 ECHO := echo 357 GREP := grep 358 MKDIR := mkdir -p 359 RM := rm 360 RM_r := ${RM} -r 361 RMDIR := ${RM} -r 362 RMDIR_f := ${RM} -rf 363 SORT := sort 364 WHICH := which 365 # 366 ECHO_newline := echo 367 shell_true := true 368 endif 369 370 #### 371 372 make_ARGS := ${MAKECMDGOALS} 373 has_runner_target := $(strip $(call %map,$(eval %f=$$(findstring $${1},${MAKECMDGOALS}))%f,run test)) 374 has_runner_first := $(strip $(call %map,$(eval %f=$$(findstring $${1},$$(firstword ${MAKECMDGOALS})))%f,run test)) 375 runner_positions := $(call %map,$(eval %f=$$(call %position,$${1},${MAKECMDGOALS}))%f,${has_runner_target}) 376 runner_position := $(firstword ${runner_positions}) 377 378 make_runner_ARGS := $(if ${has_runner_target},$(call %tail,$(wordlist ${runner_position},$(call %length,${make_ARGS}),${make_ARGS})),) 379 380 $(call %debug_var,has_runner_first) 381 $(call %debug_var,has_runner_target) 382 $(call %debug_var,runner_position) 383 $(call %debug_var,MAKECMDGOALS) 384 $(call %debug_var,make_ARGS) 385 $(call %debug_var,make_runner_ARGS) 386 $(call %debug_var,ARGS_default_${has_runner_target}) 387 $(call %debug_var,ARGS) 388 389 has_debug_target := $(strip $(call %map,$(eval %f=$$(findstring $${1},${MAKECMDGOALS}))%f,debug)) 390 ifneq (${has_debug_target},) 391 override DEBUG := $(call %as_truthy,${true}) 392 endif 393 $(call %debug_var,has_debug_target) 394 $(call %debug_var,DEBUG) 395 396 $(call %debug_var,COLOR) 397 $(call %debug_var,DEBUG) 398 $(call %debug_var,STATIC) 399 $(call %debug_var,VERBOSE) 400 $(call %debug_var,MAKEFILE_debug) 401 402 #### 403 404 # include sibling configuration file, if exists (easier project config with a stable base Makefile) 405 -include ${makefile_path}.config 406 407 #### 408 409 override ARGS := $(or $(and ${ARGS},${ARGS}${SPACE})${make_runner_ARGS},${ARGS_default_${has_runner_target}}) 410 411 $(call %debug_var,has_runner_first) 412 $(call %debug_var,has_runner_target) 413 $(call %debug_var,runner_position) 414 $(call %debug_var,MAKECMDGOALS) 415 $(call %debug_var,make_ARGS) 416 $(call %debug_var,make_runner_ARGS) 417 $(call %debug_var,ARGS_default_${has_runner_target}) 418 $(call %debug_var,ARGS) 419 420 #### * optional target-specific flags 421 422 override TAG := $(if ${TAG},${TAG},v-next) 423 424 $(call %debug_var,TAG) 425 426 #### End of basic configuration section #### 427 428 # ref: [Understanding and Using Makefile Flags](https://earthly.dev/blog/make-flags) @@ <https://archive.is/vEpEU> 429 430 #### * GoLang compiler configuration 431 432 override GOPATH := $(call %as_nix_path,$(or ${GOPATH},${HOME}/go)) 433 override GOBIN := $(call %as_nix_path,$(or ${GOBIN},${GOPATH}/bin)) 434 435 $(call %debug_var,GOPATH) 436 $(call %debug_var,GOBIN) 437 438 GO_BUILD_FLAGS := $() 439 # note: (from `go help build`): '-asmflags', '-gccgoflags', '-gcflags', and '-ldflags' are not additive; the last option specified will be used for each matching "package pattern" 440 GO_BUILD_LDFLAGS := $() 441 442 GO_BUILD_FLAGS_go116+_false := -i 443 444 ## -ldflags="-s -w" == remove symbol and debug info from target 445 GO_BUILD_LDFLAGS_CONFIG_release := -s -w 446 447 ## ref: [](https://www.arp242.net/static-go.html) @@ <https://archive.ph/YP82Y> 448 ## * enforce static linking (an error will be raised if any dynamic linking is attempted) 449 GO_BUILD_LDFLAGS_STATIC_true := -extldflags=-static 450 451 #### End of compiler configuration section. #### 452 453 #### * ensure `make` environment requirements 454 455 # # detect `go` 456 # ifeq (,$(shell go version >${devnull} 2>&1 <${devnull} && echo `go` present)) 457 # $(call %error,Missing required compiler (`go`)) 458 # endif 459 460 ifeq (${SPACE},$(findstring ${SPACE},${makefile_abs_path})) 461 $(call %error,<SPACE>'s within project directory path are not allowed)## `make` has very limited ability to quote <SPACE> characters 462 endif 463 464 # use of BASEPATH allows `make -f ../Makefile ...`; no need for this error 465 ## # since we rely on paths relative to the makefile location, abort if current directory != makefile directory 466 ## ifneq ($(current_dir),$(makefile_dir)) 467 ## $(call %error,Invalid current directory; this makefile must be invoked from the directory it resides in ('$(makefile_dir)')) 468 ## endif 469 470 #### 471 472 $(call %debug_var,MAKE_VERSION) 473 $(call %debug_var,MAKE_VERSION_major) 474 $(call %debug_var,MAKE_VERSION_minor) 475 476 $(call %debug_var,MAKE_VERSION_fail) 477 478 $(call %debug_var,makefile_path) 479 $(call %debug_var,makefile_abs_path) 480 $(call %debug_var,makefile_dir) 481 $(call %debug_var,current_dir) 482 $(call %debug_var,make_invoke_alias) 483 $(call %debug_var,makefile_set) 484 $(call %debug_var,makefile_set_abs) 485 486 $(call %debug_var,BASEPATH) 487 488 # discover NAME 489 NAME := $(strip ${NAME}) 490 ifeq (${NAME},) 491 # * generate a default NAME from Makefile project path 492 working_NAME := $(notdir ${makefile_dir}) 493 ## remove any generic repo and/or category tag prefix 494 tags_repo := repo.GH repo.GL repo.github repo.gitlab repo 495 tags_category := cxx deno djs js-cli js-user js rs rust ts sh 496 tags_combined := $(call %cross,$(eval %f=$${1}${DOT}$${2})%f,${tags_repo},${tags_category}) ${tags_repo} ${tags_category} 497 tag_patterns := $(call %map,$(eval %f=$${1}${DOT}% $${1})%f,${tags_combined}) 498 # $(call %debug_var,tags_combined) 499 # $(call %debug_var,tag_patterns) 500 clipped_NAMEs := $(strip $(filter-out ${working_NAME},$(call %replace,${tag_patterns},%,$(filter-out ${tags_repo},${working_NAME})))) 501 # $(call %debug_var,clipped_NAMEs) 502 working_NAME := $(firstword $(filter-out ${tags_repo},${clipped_NAMEs} ${working_NAME})) 503 ifeq (${working_NAME},) 504 working_NAME := $(notdir $(abspath $(dir ${makefile_dir}))) 505 endif 506 override NAME := ${working_NAME} 507 endif 508 $(call %debug_var,working_NAME) 509 $(call %debug_var,NAME) 510 511 #### 512 513 # `go` version determination 514 s := $(shell go version) 515 # remove all non-version-compatible punctuation characters (leaving common version characters [${BACKSLASH} ${SLASH} ${DOT} _ - +]) 516 s := $(call %tr,$(filter-out ${SLASH} ${BACKSLASH} ${DOT} _ - +,${[punct]}),$(),${s}) 517 # $(call %debug_var,s) 518 # filter_map ${DOT}-containing words 519 %f = $(and $(findstring ${DOT},${1}),${1}) 520 s := $(strip $(call %map,%f,${s})) 521 # remove go version prefix 522 s := $(call %tr,go,$(),${s}) 523 # $(call %debug_var,s) 524 525 # take first word as full version 526 GO_version := $(firstword ${s}) 527 GO_version_parts := $(strip $(subst ${DOT},${SPACE},${GO_version})) 528 GO_version_M := $(strip $(word 1,${GO_version_parts})) 529 GO_version_m := $(strip $(word 2,${GO_version_parts})) 530 GO_version_r := $(strip $(word 3,${GO_version_parts})) 531 GO_version_Mm := $(strip ${GO_version_M}.${GO_version_m}) 532 533 is_go116+ := $(call %as_truthy,$(and $(or $(filter ${GO_version_M},0 1)),$(call %not,$(filter ${GO_version_m},0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)))) 534 # is_go118+ := $(call %as_truthy,$(and $(or $(filter ${GO_version_M},0 1)),$(call %not,$(filter ${GO_version_m},0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17)))) 535 536 $(call %debug_var,GO_version) 537 $(call %debug_var,GO_version_parts) 538 $(call %debug_var,is_go116+) 539 # $(call %debug_var,is_go118+) 540 541 #### 542 543 OUT_DIR_EXT := $(if $(call %is_truthy,${STATIC}),,.dynamic) 544 545 $(call %debug_var,OUT_DIR_EXT) 546 547 #### 548 549 # note: (from `go help build`): '-asmflags', '-gccgoflags', '-gcflags', and '-ldflags' are not additive; the last option specified will be used for each matching "package pattern" 550 551 GO_BUILD_FLAGS += ${GO_BUILD_FLAGS_GO116+_${is_go116+}} 552 553 GO_BUILD_LDFLAGS += ${GO_BUILD_LDFLAGS_CONFIG_${CONFIG}} 554 GO_BUILD_LDFLAGS += ${GO_BUILD_LDFLAGS_STATIC_${STATIC}} 555 556 GO_BUILD_LD_FLAGS := $(strip ${GO_BUILD_LDFLAGS}) 557 GO_BUILD_FLAGS := $(strip ${GO_BUILD_FLAGS}$(if ${GO_BUILD_LDFLAGS}, -ldflags="${GO_BUILD_LDFLAGS}",)) 558 559 $(call %debug_var,GO_BUILD_FLAGS) 560 561 #### 562 563 RUNNER := $()## place-holder for more specific executable runner (eg, `DOSBox-run`, `MSDOS-run`, `wine`, etc for 'special' executables) 564 565 #### 566 567 BUILD_DIR := ${BASEPATH}/$(or ${BUILD_PATH},${HASH}build)## note: `${HASH}build` causes issues with OpenWatcom-v2.0 [2020-09-01], but `${DOLLAR}build` causes variable expansion issues for VSCode debugging; note: 'target' is a common alternative 568 569 $(call %debug_var,BUILD_DIR) 570 571 ## `go` packages are generally compiled to targets which include debug and symbol information 572 # $(call %debug_var,CONFIG) 573 override CONFIG := $(call %lc,$(if $(findstring install,${make_ARGS}),release,${CONFIG})) 574 575 $(call %debug_var,CONFIG) 576 577 # SOURCE_dirs := cmd src source 578 SOURCE_dirs := $(call %replace,${makefile_dir}/%,${BASEPATH}/%,$(call %as_nix_path,$(shell go list -f {{.Dir}} ./... 2>${devnull}))) 579 SOURCE_exts = *.go **/*.go 580 581 SRC_files := $(strip $(foreach p,$(foreach segment,${SOURCE_dirs},$(foreach elem,${SOURCE_exts},${segment}/${elem})),$(wildcard ${p}))) 582 583 $(call %debug_var,SOURCE_dirs) 584 $(call %debug_var,SRC_files) 585 586 BIN_DIR := ${BASEPATH}/cmd$()## by `go` convention, executables are placed in the `cmd` directory 587 588 $(call %debug_var,BIN_DIR) 589 590 OUT_DIR := ${BUILD_DIR}/${OS_PREFIX}${CONFIG}${OUT_DIR_EXT} 591 OUT_DIR_bin := ${OUT_DIR} 592 593 $(call %debug_var,OUT_DIR) 594 $(call %debug_var,OUT_DIR_bin) 595 596 #### 597 598 PROJECT_TARGET := ${OUT_DIR_bin}/${NAME}${EXEEXT} 599 600 .DEFAULT_GOAL := $(if ${SRC_files},${PROJECT_TARGET},$(if ${BIN_SRC_files},bins,$(if ${EG_SRC_files},examples,)))# *default* target 601 602 $(call %debug_var,PROJECT_TARGET) 603 $(call %debug_var,.DEFAULT_GOAL) 604 605 #### 606 607 out_dirs += $(strip $(call %uniq,$(if ${has_debug_target},${DEBUG_DIR},) ${OUT_DIR} $(if $(filter-out bins examples,${.DEFAULT_GOAL}),${OUT_DIR_bin},) $(if ${BIN_SRC_files},${BIN_OUT_DIR_bin},) $(if ${EG_SRC_files},${EG_OUT_DIR_bin},) $(if ${TEST_SRC_files},${TEST_OUT_DIR_bin},) $(if $(filter-out bins examples,${.DEFAULT_GOAL}),${OUT_DIR_obj},) $(patsubst %/,%,$(dir ${OBJ_files} ${OBJ_sup_files} $(if ${BIN_SRC_files},${BIN_OBJ_files} ${BIN_OBJ_sup_files} ${BIN_REZ_files} ,) $(if ${EG_SRC_files},${EG_OBJ_files} ${EG_OBJ_sup_files} ${EG_REZ_files},) $(if ${TEST_SRC_files},${TEST_OBJ_files} ${TEST_OBJ_sup_files} ${TEST_REZ_files},) ${REZ_files})) ${OUT_DIR_targets})) 608 609 out_dirs_for_rules = $(strip $(call %tr,${DOLLAR} ${HASH},${DOLLAR}${DOLLAR} ${BACKSLASH}${HASH},${out_dirs})) 610 611 $(call %debug_var,out_dirs) 612 $(call %debug_var,out_dirs_for_rules) 613 614 #### 615 616 all_phony_targets += $() 617 618 #### 619 620 # include sibling target(s) file (if/when sibling file exists; provides easy project customization upon a stable base Makefile) 621 # * note: `-include ${makefile_path}.target` is placed as late as possible, just prior to any goal/target declarations 622 -include ${makefile_path}.target.config 623 624 #### 625 626 ifneq (${NULL},$(filter-out all bins,${.DEFAULT_GOAL}))## define 'run' target only for real executable targets (ignore 'all' or 'bins') 627 all_phony_targets += run 628 run: ${.DEFAULT_GOAL} ## Build and execute project executable (for ARGS, use `-- [ARGS]` or `ARGS="..."`) 629 $(strip ${RUNNER} $(call %shell_quote,$^)) ${ARGS} 630 endif 631 632 #### 633 ifeq (${false},${has_run_first})## define standard phony targets only when 'run' is not the first target (all text following 'run' is assumed to be arguments for the run; minimizes recipe duplication/overwrite warnings) 634 #### 635 636 # have_git := $(shell git --version 2>${devnull}) 637 # have_git_repo := $(if $(shell git status 2>${devnull}),${true},) 638 have_git_changelog := $(shell git changelog --version 2>${devnull}) 639 640 # $(call %debug_var,have_git) 641 # $(call %debug_var,have_git_repo) 642 $(call %debug_var,have_git_changelog) 643 644 have_tests := $(wildcard ${SRC_files}/*_test.go)## .or. (slower) `$(if $(shell go test -v ./... -list . 2>${devnull}),${true},)` 645 646 $(call %debug_var,have_tests) 647 648 ifeq (${OSID},win) 649 shell_filter_targets := ${FINDSTR} -rc:"^[a-zA-Z][^: ]*:[^=].*${HASH}${HASH}" 650 shell_filter_targets := $(strip ${shell_filter_targets} $(if $(or $(call %not,${.DEFAULT_GOAL}),$(filter all bins examples,${.DEFAULT_GOAL})), | ${FINDSTR} -v "^run:")) 651 shell_filter_targets := $(strip ${shell_filter_targets} $(if $(or $(call %not,${.DEFAULT_GOAL}),$(filter all bins examples,${.DEFAULT_GOAL})), | ${FINDSTR} -v "^install:")) 652 shell_filter_targets := $(strip ${shell_filter_targets} $(if $(or $(call %not,${.DEFAULT_GOAL}),$(filter all bins examples,${.DEFAULT_GOAL})), | ${FINDSTR} -v "^uninstall:")) 653 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${FINDSTR} -v "^build:")) 654 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${FINDSTR} -v "^compile:")) 655 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${FINDSTR} -v "^rebuild:")) 656 # shell_filter_targets := $(strip ${shell_filter_targets} $(if ${SRC_files}${BIN_SRC_files}${EG_SRC_files}${TEST_SRC_files},, | ${FINDSTR} -v "^all:")) 657 # shell_filter_targets := $(strip ${shell_filter_targets} $(if ${SRC_files}${BIN_SRC_files}${EG_SRC_files}${TEST_SRC_files},, | ${FINDSTR} -v "^debug:")) 658 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${BIN_SRC_files}), | ${FINDSTR} -v "^bins:")) 659 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${EG_SRC_files}), | ${FINDSTR} -v "^examples:")) 660 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${FINDSTR} -v "^coverage:")) 661 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${FINDSTR} -v "^lint:")) 662 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${FINDSTR} -v "^reformat:")) 663 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${have_tests}), | ${FINDSTR} -v "^test:")) 664 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${have_tests}), | ${FINDSTR} -v "^tests:")) 665 # 666 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${have_git_changelog}), | ${FINDSTR} -v "^changelog:")) 667 else 668 shell_filter_targets := ${GREP} -P '(?i)^[[:alpha:]][^:\s]*:[^=].*${HASH}${HASH}' 669 shell_filter_targets := $(strip ${shell_filter_targets} $(if $(or $(call %not,${.DEFAULT_GOAL}),$(filter all bins examples,${.DEFAULT_GOAL})), | ${FINDSTR} -v "^run:")) 670 shell_filter_targets := $(strip ${shell_filter_targets} $(if $(or $(call %not,${.DEFAULT_GOAL}),$(filter all bins examples,${.DEFAULT_GOAL})), | ${FINDSTR} -v "^install:")) 671 shell_filter_targets := $(strip ${shell_filter_targets} $(if $(or $(call %not,${.DEFAULT_GOAL}),$(filter all bins examples,${.DEFAULT_GOAL})), | ${FINDSTR} -v "^uninstall:")) 672 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${GREP} -Pv "^build:")) 673 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${GREP} -Pv "^compile:")) 674 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${GREP} -Pv "^rebuild:")) 675 # shell_filter_targets := $(strip ${shell_filter_targets} $(if ${SRC_files}${BIN_SRC_files}${EG_SRC_files}${TEST_SRC_files},, | ${GREP} -Pv "^all:")) 676 # shell_filter_targets := $(strip ${shell_filter_targets} $(if ${SRC_files}${BIN_SRC_files}${EG_SRC_files}${TEST_SRC_files},, | ${GREP} -Pv "^debug:")) 677 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${BIN_SRC_files}), | ${GREP} -Pv "^bins:")) 678 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${EG_SRC_files}), | ${GREP} -Pv "^examples:")) 679 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${GREP} -Pv "^coverage:")) 680 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${GREP} -Pv "^lint:")) 681 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${SRC_files}), | ${GREP} -Pv "^reformat:")) 682 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${have_tests}), | ${GREP} -Pv "^test:")) 683 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${have_tests}), | ${GREP} -Pv "^tests:")) 684 # 685 shell_filter_targets := $(strip ${shell_filter_targets} $(and $(call %not,${have_git_changelog}), | ${GREP} -Pv "^changelog:")) 686 endif 687 $(call %debug_var,shell_filter_targets) 688 689 all_phony_targets += help 690 help: ## Display help 691 @${ECHO_newline} 692 @${ECHO} $(call %shell_escape,Usage: `${color_command}${make_invoke_alias} [MAKE_TARGET...] [CONFIG=debug|release] [COLOR=<truthy>] [MAKEFLAGS_debug=<truthy>] [VERBOSE=<truthy>]${color_reset}`) 693 ifneq (,$(or ${SRC_files},${BIN_SRC_files},${EG_SRC_files})) 694 @${ECHO} $(call %shell_escape,Builds $(if $(filter all bins examples,${.DEFAULT_GOAL}),'${color_target}${.DEFAULT_GOAL}${color_reset}' targets,'${color_target}$(call %strip_leading_dotslash,${.DEFAULT_GOAL})${color_reset}') within '${color_path}$(call %strip_leading_dotslash,${current_dir})${color_reset}') 695 endif 696 @${ECHO_newline} 697 @${ECHO} $(call %shell_escape,MAKE_TARGETs:) 698 @${ECHO_newline} 699 ifeq (${OSID},win) 700 @${TYPE} $(call %map,%shell_quote,${makefile_set}) 2>${devnull} | ${shell_filter_targets} | ${SORT} | for /f "tokens=1-2,* delims=:${HASH}" %%g in ('${MORE}') do @(@call set "t=%%g " & @call echo ${color_success}%%t:~0,15%%${color_reset} ${color_info}%%i${color_reset}) 701 else 702 @${CAT} $(call %map,%shell_quote,${makefile_set}) | ${shell_filter_targets} | ${SORT} | ${AWK} 'match($$0,"^([^:]+):.*?${HASH}${HASH}\\s*(.*)$$",m){ printf "${color_success}%-10s${color_reset}\t${color_info}%s${color_reset}\n", m[1], m[2] }END{}' 703 endif 704 @${ECHO} ${color_hide}${DOT}${color_reset} 705 706 #### 707 708 all_phony_targets += clean realclean 709 710 clean: ## Remove build artifacts (for the active configuration; includes intermediate files) 711 # * notes: avoid removing the main directory and filter-out directories which are obviously invalid 712 @$(call !shell_noop,::note ~ pre-executed call::$(call %rm_dirs_verbose,$(filter-out filter-out ${DOT} ${DOT}${DOT} ${SLASH} ${BACKSLASH},${out_dirs}))) 713 714 realclean: clean ## Remove *all* build artifacts (including all configurations and the build directory) 715 ifeq ($(filter-out ${DOT} ${DOT}${DOT} ${SLASH} ${BACKSLASH},${BUILD_DIR}),) 716 @${ECHO} $(call %failure,'realclean' is unavailable for the current build directory ('${BUILD_DIR}').) 717 else 718 @$(call !shell_noop,::note ~ pre-executed call::$(call %rm_dirs_verbose,${BUILD_DIR})) 719 endif 720 721 #### 722 723 all_phony_targets += build rebuild 724 725 build: ${.DEFAULT_GOAL} ## Build project 726 rebuild: clean build ## Clean and re-build project 727 728 #### 729 730 all_phony_targets += fmt format reformat 731 fmt: reformat 732 format: reformat 733 reformat: ## Reformat source files (using `go fmt ...`) [alias: 'fmt','format'] 734 go fmt ${SOURCE_dirs} 735 736 #### 737 738 all_phony_targets += cov cover coverage 739 cov: coverage 740 cover: coverage 741 coverage: build | ${BUILD_DIR} ## Display test coverage for project files [alias: 'cov','cover'] 742 goverage -coverprofile="${BUILD_DIR}/cover.out" ${SOURCE_dirs} 743 go tool cover -func="${BUILD_DIR}/cover.out" 744 @$(call %rm_file_shell_s,${BUILD_DIR}/cover.out) >${devnull} 2>&1 745 746 #### 747 748 all_phony_targets += lint 749 lint: ## Display lint warnings for source files (using `golint ...`) 750 golint ${SOURCE_dirs} 751 752 all_phony_targets += test 753 test: build ## Test project 754 go test -v ${SOURCE_dirs} 755 756 #### 757 758 all_phony_targets += install uninstall 759 install: ## Install project executable (to host GOBIN) 760 go install ${GO_BUILD_FLAGS} "${BIN_DIR}/${NAME}" 761 @${ECHO} $(call %shell_escape,$(call %success_text,installed as '${GOBIN}/${NAME}${EXEEXT}'.)) 762 763 uninstall: ## Remove *installed executable* (from host GOBIN) 764 @$(call %rm_file_shell_s,${GOBIN}/${NAME}${EXEEXT}) >${devnull} 765 @${ECHO} $(call %shell_escape,$(call %success_text,un-installed '${GOBIN}/${NAME}${EXEEXT}'.)) 766 767 #### 768 769 all_phony_targets += changelog 770 changelog: ## Display changelog for planned next TAG (using `git-changelog ...`; optionally use TAG="M.m.r") 771 git-changelog --next-tag ${TAG} ${TAG} 772 773 #### 774 endif ## not ${has_run_first} 775 #### 776 777 # ref: [`make` default rules]<https://www.gnu.org/software/make/manual/html_node/Catalogue-of-Rules.html> @@ <https://archive.is/KDNbA> 778 # ref: [make ~ `eval()`](http://make.mad-scientist.net/the-eval-function) @ <https://archive.is/rpUfG> 779 # * note: for pattern-based rules/targets, `%` has some special matching mechanics; ref: <https://stackoverflow.com/a/21193953> , <https://www.gnu.org/software/make/manual/html_node/Pattern-Match.html#Pattern-Match> @@ <https://archive.is/GjJ3P> 780 781 #### 782 783 %*[makefile.run]*: % 784 @${ECHO} $(call %shell_escape,$(call %info_text,running '$<')) 785 @$(strip ${RUNNER_${CC}} $(call %shell_quote,$<)) ${ARGS} 786 787 #### 788 789 # ${NAME}: ${PROJECT_TARGET} 790 ${PROJECT_TARGET}: ${SRC_files} ${makefile_set} | ${OUT_DIR} 791 @go build $(GO_BUILD_FLAGS) -o "$(OUT_DIR)" ${SOURCE_dirs} 792 @${ECHO} $(call %shell_escape,$(call %success_text,made '$@'.)) 793 794 #### * auxiliary/configuration rules 795 796 # * directory rules 797 # $(foreach dir,$(filter-out ${DOT} ${DOT}${DOT},${out_dirs_for_rules}),$(call %info,eval $(call @mkdir_rule,${dir}))) 798 $(foreach dir,$(filter-out ${DOT} ${DOT}${DOT},${out_dirs_for_rules}),$(eval $(call @mkdir_rule,${dir}))) 799 800 # * all known phony targets 801 .PHONY: ${all_phony_targets} 802 803 # suppress auto-deletion of intermediate files 804 # ref: [`gmake` ~ removing intermediate files](https://stackoverflow.com/questions/47447369/gnu-make-removing-intermediate-files) @@ <https://archive.is/UXrIv> 805 .SECONDARY: 806 807 # suppress recipe output if not verbose (note: '@' prefix is suppressed no matter the VERBOSE setting) 808 $(call %is_truthy,${VERBOSE}).SILENT: 809 810 #### * final checks and hints 811 812 ifeq (${NULL},$(or ${SRC_files},${BIN_SRC_files},${EG_SRC_files})) 813 msg := no source files found; unrecognized project format and `go list -f {{.Dir}} ./...` finds no files 814 $(call %warning,${msg}) 815 endif 816 817 # $(call %debug_var,NULL) 818 $(call %debug_var,has_runner_target) 819 $(call %debug_var,all_phony_targets) 820 $(call %debug_var,make_runner_ARGS) 821 822 ifeq (${true},$(call %as_truthy,${has_runner_target})) 823 ifneq (${NULL},$(if ${has_runner_target},$(filter ${all_phony_targets},${make_runner_ARGS}),${NULL})) 824 $(call %warning,runner arguments duplicate (and overwrite) standard targets; try using `${make_invoke_alias} run ARGS=...`) 825 endif 826 # $(info make_runner_ARGS=:${make_runner_ARGS}:) 827 $(eval ${make_runner_ARGS}:;@:) 828 endif