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