From adc1e02b1435e18622e8c0c38849b713033d528f Mon Sep 17 00:00:00 2001 From: "Michael D. Lowis" Date: Wed, 25 Jan 2017 16:26:55 -0500 Subject: [PATCH] Version 1.0 - Single-threaded proof of concept --- abt | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100755 abt diff --git a/abt b/abt new file mode 100755 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 " + 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 -- 2.49.0