#! /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<<EOF $1a\\ # begin $2 $1r$(build_resolve $2) $1a\\ # end $2 EOF fi } ## usage: #@strict -> build_strict_mode \1 build_strict_mode() { cat<<EOF $1a\\ set -euf\\ (set -o posix 2>/dev/null) && set -o posix || : EOF } ## usage: #@info -> build_info \1 build_info() { gitinfo=$(git describe --always --dirty --abbrev=0 2>/dev/null || :) cat<<EOF $1a\\ # this file was generated by //ship/build\\ # build date: $(date -u --rfc-3339=s)\\ # git describe: ${gitinfo:-not under version control} EOF } ## usage: #@mainifyme\( \([A-Za-z_][A-Za-z0-9_]*\)\)\? -> build_mainifyme \1 \3 build_mainifyme() { mainifyme_name="${2:-main}" cat<<EOF $1a\\ $mainifyme_name(){ \$a\\ }\\ $mainifyme_name "\$@" EOF } ### ### main subroutines ### ## usage: build_compile SRCFILE DSTFILE build_compile() { script='s/^'"$defmacro_pattern"'$/\2_macro/p' \ setf macro_names '$(sed -n "$script" "$%s")' 0 setf unexpanded_macros_pattern \ '$(make_unexpanded_macros_pattern $%s)' macro_names script=' s/^'"$defmacro_pattern"'$/s:^ *\\([0-9]\\+\\) \1$:\2 \3:/p $a\ t;s:^ *\\([0-9]\\+\\) .*:echo \\1p: ' \ setf input_parser '$(sed -n "$script" "$%s")' 0 SRCFILE="$1" setf src '$(cat "$%s")' SRCFILE buildcache_initialize "$2" while echo "$src" | grep -q "$unexpanded_macros_pattern"; do setf sedgen '$(echo "$%s" | nl -b a -s \ | sed "$%s")' src input_parser setf sedscript '$(eval "$%s")' sedgen setf src '$(echo "$%s" | sed -n "$%s")' src sedscript done buildcache_finalize echo "$src" > "$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 "[35m$1=\"$value_script\"[m"' 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