#! /bin/sh set -euf ## SYNOPSIS # [debug=true] build compile SRCFILE DSTFILE # [debug=true] build deps SRCFILE... build() { ## Load macro definitions. defmacro_pattern='## usage: \(.*\) -> \([^ ]\+\) \(.*\)' script='s/^'"$defmacro_pattern"'$/\2_macro='"'"'\1'"'"'/p' \ setf defmacros '$(sed -n "$script" "$%s")' 0 eval "$defmacros" ## Dispatch. case "$1" in compile) build_compile "$2" "$3";; deps) shift; build_deps "$@";; *) echo "build: $1: bad command" >&2; return 23;; esac } ### ### macros ### ## usage: #@include \([0-9A-Za-z_]\+\) -> build_include \1 \2 build_include() { if buildcache_has "#@include:$2"; then printf '%da\\\n##include %s: already done\n' $1 $2 else buildcache_add "#@include:$2" cat< build_strict_mode \1 build_strict_mode() { cat</dev/null) && set -o posix || : EOF } ## usage: #@info -> build_info \1 build_info() { gitinfo=$(git describe --always --dirty --abbrev=0 2>/dev/null || :) cat< build_mainifyme \1 \3 build_mainifyme() { mainifyme_name="${2:-main}" cat< "$2" chmod +x "$2" } ## usage: build_deps SRCFILE... # Print all the dependencies of SRCFILE... to stdout. (alphabetic order) build_deps() { while test $# -gt 0; do deps="$( for f; do for d in $(sed -n 's:^'"$build_include_macro"'$:\1:p' "$f"); do build_resolve $d done done )" set -- $deps if test $# -gt 0; then echo "$deps" fi done | sort | uniq } ### ### misc utilities ### ## usage: build_resolve LIBNAME build_resolve() { echo "$BUILD_PATH" | tr : \\n | xargs -I: printf '%s/%s\n' : "$1" | xargs -I: ls -d : 2>/dev/null | head -n 1 | grep . || { echo "build resolve: $1: library not found" >&2 return 23 } } ## usage: make_unexpanded_macros_pattern BUILD_DIRECTIVES... make_unexpanded_macros_pattern() { echo "^\\($( for macro; do eval echo \"\$$macro\" done | tr \\n \| | sed 's/|/\\|/' )\\)$" } ## usage: setf NAME FMT [ARG...] setf() { value_script="$(shift; printf "$@")" eval "$1=$value_script" if is_debug_mode; then eval 'echo "$1=\"$value_script\""' eval 'echo "'"\$$1"'"' | nl -b a fi >&2 } ## usage: is_debug_mode is_debug_mode() { test "${debug-false}" = true } ### ### buildcache utilities ### ## usage: buildcache_initialize DESTFILE buildcache_initialize() { buildcache="$1.buildcache" cat /dev/null > "$buildcache" } ## usage: buildcache_finalize buildcache_finalize() { if is_debug_mode; then rm "$buildcache" fi } ## usage: buildcache_has BRE # Check if buildcache contains a line matching BRE. buildcache_has() { grep -q "^$1\$" "$buildcache" } ## usage: buildcache_add LINE # Add LINE to buildcache. buildcache_add() { echo "$1" >> "$buildcache" } ### ### main invocation ### if echo "$0" | grep -q '^\(.*/\)\?build$'; then build "$@" fi