]> git.mdlowis.com Git - projs/abt.git/commitdiff
Version 1.0 - Single-threaded proof of concept master
authorMichael D. Lowis <mike.lowis@gentex.com>
Wed, 25 Jan 2017 21:26:55 +0000 (16:26 -0500)
committerMichael D. Lowis <mike.lowis@gentex.com>
Wed, 25 Jan 2017 21:26:55 +0000 (16:26 -0500)
abt [new file with mode: 0755]

diff --git a/abt b/abt
new file mode 100755 (executable)
index 0000000..4d3f986
--- /dev/null
+++ b/abt
@@ -0,0 +1,275 @@
+#!/bin/env bash
+# 
+# Aardvark Build Tool
+# A simple Scons/RScons inspired build system written in Bash (>= 4.0)
+# 
+# Author:    Michael D. Lowis
+# Copyright: 2017 
+# License:   2-Clause BSD License (https://opensource.org/licenses/BSD-2-Clause)
+
+# usage
+# Prints helpful usage information
+usage(){
+    echo "Usage: $0 <action>"
+    echo ""
+    echo "build     - Build the project"
+    echo "clean     - Cleanup all build artifacts"
+    echo "config    - Configure the project for the current system"
+    echo "help      - Print this help message"
+    echo "install   - Install the files to the system"
+    echo "uninstall - Uninstall previously installed files"
+    exit 1
+}
+
+# default_env
+# Reset command flags and variables back to the default settings.
+default_env(){
+    # Default C compiler setup
+    CC=cc
+    CCCMDFLAGS=(-MMD -c -o)
+    INCPREFIX=-I
+    INCS=()
+    DEFPREFIX=-D
+    DEFS=()
+    CFLAGS=()
+    CPPFLAGS=()
+    
+    # Default linker setup
+    LD=cc
+    LDCMDFLAGS=(-o)
+    LIBPREFIX=-l
+    LIBS=()
+    LIBPATHPREFIX=-L
+    LIBPATHS=()
+    
+    # Default archiver setup
+    AR=ar
+    ARFLAGS=("rcs")
+}
+
+#------------------------------------------------------------------------------
+
+# add_prefix PREFIX [ITEM...]
+# Prepend each ITEM with PREFIX
+add_prefix(){
+    local prefix="$1"
+    for e in "${@:2}"; do
+        echo "$prefix$e"
+    done
+}
+
+# add_suffix SUFFIX [ITEM...]
+# Append SUFFIX to each ITEM
+add_suffix(){
+    local suffix="$1"
+    for e in "${@:2}"; do
+        echo "$e$suffix"
+    done
+}
+
+# runcmd CMD [ARG...]
+# Prints CMD to run and runs it with the ARG list provided
+runcmd(){
+    printf '%s ' "$@"
+    printf '\n'
+    "$@" || finish $?
+}
+
+# checksum FILE
+# Returns the checksum of FILE as generated by the cksum command
+checksum(){
+    if [[ -z "${CHECKSUMS[$1]}" ]]; then
+        CHECKSUMS["$1"]="$(cksum "$1" | cut -d ' ' -f1)"
+    fi
+    echo "${CHECKSUMS[$1]}"
+}
+
+# checksum_mismatch FILE
+# Determines if the
+checksum_mismatch(){
+    [[ "$(checksum "$1")" != "${CACHE_CHECKSUMS[$1]}" ]]
+}
+
+# cache_update [FILE...]
+# Calculates and caches a checksum of each FILE
+cache_update(){
+    for f in "$@"; do
+        CHECKSUMS[$f]=$(checksum "$f")
+    done
+}
+
+# parse_deps VAR TARGET [DEP...]
+# Parses a dependency file for TARGET and combines the dependencies in the file
+# with the DEP list provided. The combined list is the deduplicated and each
+# entry is checksumed. The list is then returned to the caller in an array 
+# named VAR.
+parse_deps(){
+    local resvar="$1"
+    local depfile="${2%.*}.d"
+    if [[ -f "$depfile" ]]; then
+        mapfile -t deps < <(tr -s ': \\' '\n' < "$depfile")
+    fi
+    local combined=("${@:3}" "${deps[@]:2}")
+    declare -A filtered
+    for f in "${combined[@]}"; do
+        if [[ $f != /* ]]; then
+            filtered["$f"]=1;
+            CHECKSUMS[$f]=$(checksum "$f")
+        fi
+    done
+    eval "$resvar=(${!filtered[@]})"
+}
+
+# sources_changed TARGET [SRC...]
+# Determines if the sources for the TARGET have changed. The provided list of
+# SRC files is combined with any dependencies listed in an on-disk dependency 
+# file for the TARGET. The resulting list is checked for any checksum mismatches.
+sources_changed(){
+    local DEPS=()
+    parse_deps DEPS "$@"
+    for f in "${DEPS[@]}"; do
+        if checksum_mismatch "$f"; then
+            return 0
+        fi
+    done
+    return 1
+}
+
+# should_rebuild TARGET [SRC...]
+# Determines if TARGET should be rebuilt. Returns 1 if TARGET does not exist
+# or if any SRC provided or any implicit dependency of a provided SRC fails
+# a checksum match.
+should_rebuild(){
+    [[ ! -f "$1" ]] || sources_changed "$@"
+}
+
+# build_sources VAR [SRC...]
+# Builds each SRC using the Object function. The resulting list of object files
+# is returned to the caller in an array named VAR.
+build_sources(){
+    local resvar="$1"
+    local srcs=()
+    for s in "${@:2}"; do
+        local obj="${s%.*}.o"
+        if [[ $s == *.c ]]; then
+            Object "$obj" "$s"
+            srcs+=("$obj")
+        else
+            srcs+=("$s")
+        fi
+    done
+    eval "$resvar=(${srcs[@]})"
+}
+
+# finish RETCODE
+# Finishes the build process with an exit status of RETCODE. This function is 
+# called to signal a successful or unsuccessful completion of the build process.
+# It is responsible for ensuring that the cache file is written before the 
+# process exits.
+finish(){
+    echo "CACHE_CHECKSUMS=(" > cache.sh
+    for file in "${!CHECKSUMS[@]}"; do
+        echo "    ['$file']='${CHECKSUMS[$file]}'" >> cache.sh
+    done
+    echo ")" >> cache.sh
+    exit "$1"
+}
+
+#------------------------------------------------------------------------------
+
+# cccmd TARGET [SRC...]
+# Execute the compiler to produce TARGET from the list of SRCs. TARGET is 
+# assumed to be an object file.
+cccmd(){
+    local cmd=(
+        "$CC" "${CCCMDFLAGS[@]}" "$1" "${CFLAGS[@]}" "${CPPFLAGS[@]}"
+        $(add_prefix "$INCPREFIX" "${INCS[@]}")
+        $(add_prefix "$DEFPREFIX" "${DEFS[@]}")
+        ${@:2}
+    )
+    runcmd "${cmd[@]}"
+}
+
+# ldcmd TARGET [SRC...]
+# Execute the linker to produce TARGET from the list of SRCs.
+ldcmd(){
+    local cmd=(
+        "$LD" "${LDCMDFLAGS[@]}" "$1" ${@:2}  "${LDFLAGS[@]}"
+        $(add_prefix "$LIBPATHPREFIX" "${LIBPATHS[@]}")
+        $(add_prefix "$LIBPREFIX" "${LIBS[@]}")
+    )
+    runcmd "${cmd[@]}"
+}
+
+# arcmd TARGET [SRC...]
+# Execute the archiver to produce TARGET from the list of SRCs.
+arcmd(){
+    local cmd=("$AR" "${ARFLAGS[@]}" "$@")
+    runcmd "${cmd[@]}"
+}
+
+#------------------------------------------------------------------------------
+
+# Object TARGET [SRC...]
+# Builds TARGET as an object file based on the list or SRCs.
+Object(){
+    if should_rebuild "$@"; then
+        cccmd "$@"
+        parse_deps DEPS "$@"
+        cache_update "$@"
+    fi
+}
+
+# Program TARGET [SRC...]
+# Builds TARGET as an executable based on the list or SRCs.
+Program(){
+    build_sources OBJS "${@:2}"
+    if should_rebuild "$@"; then
+        ldcmd "$1" "${OBJS[@]}"
+        cache_update "$@"
+    fi
+}
+
+# StaticLib TARGET [SRC...]
+# Builds TARGET as a static library based on the list or SRCs.
+StaticLib(){
+    build_sources OBJS "${@:2}"
+    if should_rebuild "$@"; then
+        arcmd "$1" "${OBJS[@]}"
+        cache_update "$@"
+    fi
+}
+
+#------------------------------------------------------------------------------
+
+# Init the cache variables
+declare -A CHECKSUMS
+declare -A CACHE_CHECKSUMS
+
+# Create dummy versions of the actions. These can/will be overridden by the
+# Build file
+build(){ :; }
+clean(){ :; }
+config(){ :; }
+install(){ :; }
+uninstall(){ :; }
+
+# Load the cache and the build script
+[[ -f "cache.sh" ]] && source cache.sh
+default_env
+source "Build"
+
+# execute the desired action
+ACTION="${1:-build}"; shift
+case "$ACTION" in
+    "build")     build ;;
+    "clean")     clean ;;
+    "config")    config ;;
+    "install")   install ;;
+    "uninstall") uninstall ;;
+    "help")      usage ;;
+    *)           usage ;;
+esac
+
+# wrap up and write the cache
+finish 0