#!/bin/bash

# Part of the SMLinux distribution
# http://git.pktsurf.in/smlinux
#
# Bash script to build SMLinux-specific packages
#
# Copyright (c) 2022 PktSurf <smlinux@pktsurf.in>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
############

# TODO
# -> Uncomment entirety of the code where compilers are to be hard-validated and improve C code
#    in the test files and also add suitable bldpkg.conf switches for it
# -> Write code to log build output to a log file inside a particular directory
# -> Email the user about the outcome of the build?
# -> Increment warnings as they are issued during the build and show final warning total in the build summary
# -> Determine if the staging directory is empty at the end of the build, and if it is, error out

# Determine whether we are using bash version 4 and later. If not, exit.
if ((BASH_VERSINFO[0] < 4)) ; then
  echo "[ERROR] bldpkg requires a minimum of bash shell version 4 to run"
  exit 1
fi

set -e

# Time when the build commenced. Note: elapsed time is logged by the runtime function way below. 
# This output goes into package build summary.
commencedate=$(date '+%a, %d %b %Y, %T')

# Function to generate help message
help() {
cat << EOF
Bash script for building SMLinux-compatible packages from source.
Any option used as an argument with this script overrides the 
corresponding option, if present, in /etc/bldpkg.conf or "'$HOME/.bldpkg.conf'"

If no arguments are provided, this script attempts to first look for
a build file with a .SMBuild extension that matches the name of
the parent directory, sources that .SMBuild file and starts the build.

For example, if the package source directory is $HOME/smlinux/alsa-lib, this script will
look and source from alsa-lib.SMBuild and build alsa-lib package

# pwd
/home/user/smlinux/alsa-lib

# ls
alsa-lib.SMBuild 

# bldpkg
Building package 'alsa-lib' version '1.x'...
...build output...

Usage: 

  -d         Produce a package with debug symbols preserved, i.e., don't strip 
             resulting ELF objects. Uses -g3 by default

  -e         Extract the package installer file in the user's PWD if the build
             completes successfully

  -f <file>  Name of an alternate build file to source build variables. Should
             be compatible with standard SMLinux package build file format

  -g         Generate SHA512 checksums of all tarballs and patches in the current 
             directory and insert them into the package build file

  -h         Show this help message

  -j <N>     Provide a number of jobs to be run simultaneously

  -o <file>  Copy this file into the package installer rather than the default one
             the build was initiated from. Usually the default one is either
             <app>.SMBuild or the one supplied using -f <file> but in certain 
             exceptional cases a different file is required to be copied into the 
             package installer. Do note that this file will also undergo validation

  -s         Display build summary. A summary is produced whenever a build is either
             interrupted, exits cleanly or aborts due to a build error

  -x         Invoke bash shell command trace mode. This one isn't akin to running
             bash -x <script> but close enough, because the mode is inserted half-way
             while bldpkg is running
EOF
exit 0
}

# Then source the configuration file holding all values
if [[ -f $HOME/.bldpkg.conf ]] ; then
  source "$HOME/.bldpkg.conf"
elif [[ -f /etc/bldpkg.conf ]] ; then
  source /etc/bldpkg.conf 
else 
  echo "[ERROR] /etc/bldpkg.conf not found!"
  exit 1
fi

# Function to validate the build file.
validatebldfile() {
  local buildfile
  buildfile=$1

  if [[ ! -f $buildfile ]] ; then
    echo "[ERROR] No build file to validate from!"
    exit 1
  fi
  
  # Start a subshell. We don't want the build file variables in the environment just yet.
  (
  source "$buildfile"

  # If any of the following variables are not set in the build file, abort.
  # $download variable is optional but recommended
  for buildvariables in app version build homepage desc requires ; do
    if [[ ! ${!buildvariables} ]] ; then
      echo "[ERROR] Required variable \"${buildvariables}\" is not set. Please check your build file."
      exit 1
    elif grep -E -q "$buildvariables='*'" "$buildfile" ; then
      echo "[ERROR] Please dont use single quotes to define the \"${buildvariables}\" variable"
      exit 1
    fi
  done

  # Validate $app
  if ! echo "$app" | grep -E -q '^[a-z0-9-]+$' ; then
    echo "[ERROR] Only lower case, numeric characters and dash allowed in the 'app' variable in the build file."
    exit 1

  # Validate $version 
  elif ! echo "$version" | grep -E -q '^[a-z0-9.]+$' ; then
    echo "[ERROR] Only lower case, numeric characters and a period allowed in the 'version' variable in the build file."
    exit 1

  # Validate $homepage
  elif ! echo "$homepage" | grep -E -q '^http://|^https://|^ftp://' ; then
    echo "[ERROR] Invalid URL in the 'homepage' variable in the build file."
    exit 1

  # Validate $download, first the URL type
  elif [[ -n $download ]]; then
    if ! echo "$download" | grep -E -q '^http://|^https://|^ftp://' ; then
      echo "[ERROR] Invalid URL in the 'download' variable in the build file."
      exit 1

    # Then check for single quotes
    elif grep -E -q "download='*'" "$buildfile" ; then
      echo "[ERROR] Single quotes disallowed in the 'download' variable in the build file"
      exit 1
    fi

  # Validate $desc using bash shell's ability to count variable length
  elif [[ ${#desc} -gt 100 ]] ; then
    echo "[ERROR] Package description should not exceed 100 characters in the build file."
    exit 1

  # Check if build() function exists in the build file
  elif ! grep -E -q '^build()' "$buildfile" ; then
    echo "[ERROR] 'build()' function does not exist in your build file."
    exit 1
  fi
  )
  # End subshell
}

# Store the source directory path the build was initiated from 
srcdir="$PWD"

# Presume that the build file name will match the name of the parent directory
# unless otherwise overridden using -f <buildfile>
buildfile="$(basename $srcdir).SMBuild"

# Find all required files. If either of them don't exist, abort.
rqfiles=( installpkg upgradepkg sha512sum patch find findmnt patch tput bc tar )

# Run a for loop to find the files
for requiredfile in "${rqfiles[@]}"; do
  if [[ ! -x $(type -p "$requiredfile") ]] ; then
    echo "[ERROR] Could not find required program '$requiredfile'!"
    exit 1
  fi
done

# Function for providing handy arguments to users. Some will override bldpkg.conf.
while getopts ':def:ghj:o:sx' option; do
  case "$option" in
    d) debug=1 ;; # Produce a debug build with -g3
    e) extractprompt=0; 
       autoextract=1 ;; # Automatically extract the final pkg installer inside user's PWD
    f) setbuildfile="$OPTARG" ;;
    g) genchecksum=1 ;;
    h) help ;;
    j) customcputhreads="$OPTARG" ;;
    o) origbuildfile="$OPTARG" ;;
    s) showsummary=1 ;; # Show build summary at the end of the build irrespective of the build status
    x) set -xv ;; # Invoke bash's -x option for command tracing
    *) help ;;
  esac
done

# The getopts builtin sets the OPTIND environment variable whose value is set to 1 if no argument
# is given. For every argument passed, the number is incremented by 1. In our case, if OPTIND equals
# 1, no argument was passed. We therefore expect a package build file to be present which will be then sourced.
if [[ $OPTIND = 1 ]] ; then
  if [[ ! -f $buildfile ]] ; then
    echo "[ERROR] No package build file to source from!"
    echo "[ERROR] Was expecting '$buildfile' to be present inside this directory '$PWD'."
    echo "[ERROR] Try -f <build_file> if your build file has a different name (Not recommended)"
    exit 1
  else
    if ! validatebldfile "$buildfile" ; then
      echo "[ERROR] '$buildfile' Build file validation failed!"
      exit 1
    else
      source "$buildfile"
    fi
  fi

# If OPTIND is greater than 1, perform various checks and actions
elif [[ $OPTIND -gt 1 ]] ; then

  # Override cputhreads sourced from bldpkg.conf if customcputhreads is set. Need to append '-j' so that it gets
  # passed on to MAKEFLAGS
  if [[ -n $customcputhreads ]] ; then
    # And validate whether the value is a number
    if ! echo "$customcputhreads" | grep -E -q '^[0-9]+$' ; then
      echo "[ERROR] Invalid CPU job number '$customcputhreads'. Please provide a valid number."
      exit 1
    fi
    
    # Reset $customcputhreads as $cputhreads
    cputhreads="-j$customcputhreads"
  fi

  # If $origbuildfile is set and is a file, check if $setbuildfile and $origbuildfile are the same
  if [[ -n $origbuildfile ]] ; then
    if [[ ! -f $origbuildfile ]] ; then
      echo "[ERROR] Original build file '$origbuildfile' does not exist!"
      exit 1
    fi

    if [[ -n $setbuildfile ]] && [[ -f $setbuildfile ]] ; then
      if [[ $origbuildfile = "$setbuildfile" ]] ; then
        echo "[ERROR] Original build file and alternate build file are the same!"
	exit 1
      fi
    fi

    if ! validatebldfile "$origbuildfile" ; then
      echo "[ERROR] Alternative build file validation failed!"
      exit 1
    fi
  fi

    
  # If $setbuildfile is set and is a file, set buildfile to its value, source it and initialise the build
  if [[ -n $setbuildfile ]] && [[ -f $setbuildfile ]] ; then
    buildfile="$setbuildfile"
    if ! validatebldfile "$buildfile" ; then
      echo "[ERROR] '$buildfile' validation failed!"
      exit 1
    else
      source "$buildfile"
    fi

  # If $setbuildfile is set but a file passed as an argument is not found, give an error
  elif [[ -n $setbuildfile ]] && [[ ! -f $setbuildfile ]] ; then
    echo "[ERROR] '$setbuildfile' not found!"
    exit 1

  # If the above two conditions don't meet, get the presumed $buildfile value as a file and source it
  elif [[ -f "$buildfile" ]] ; then
    if ! validatebldfile "$buildfile" ; then
      echo "[ERROR] '$buildfile' validation failed!"
      exit 1
    else
      source "$buildfile"
    fi

  # If even that file is not found, throw an error and exit
  else
    echo "[ERROR] No package build file to source from!"
    echo "[ERROR] Was expecting '$buildfile' to be present inside this directory '$PWD'."
    echo "[ERROR] Try -f <build_file> if your build file has a different name (Not recommended)"
    exit 1
  fi
fi

# Unset OPTIND and OPTARG to avoid problems if getopts is used again in the future
unset OPTIND OPTARG

# Generate and insert sha512sums into the build file
if [[ $genchecksum = 1 ]] ; then

  # File types whose checksums will go into the new build file
  echo "[INFO] Discarding any old sha512sums from '$buildfile'"
  sed -E -i \
    -e '/^sha512sums=".*"$/d' \
    -e '/^sha512sums="/,/"$/d' \
    -e "/^sha512sums='.*'\$/d" \
    "$buildfile"

  echo "[INFO] Adding new sha512sums in '$buildfile'"
  printf 'sha512sums="\n' >> "$buildfile"

  files=( *.tar.* *.zip *.t?z *.patch *.diff *.c *.h )

  # For loop that searches for files matching the above extension 
  # and prints them to the bottom of the build file
  for file in "${files[@]}" ; do
    if [[ -f $file ]] ; then
      sha512sum "$file" >> "$buildfile"
    fi
  done

  printf '"' >> "$buildfile"

  if [[ $(find . -type d -maxdepth 1 -print0 | wc -l) -ge 1 ]]; then
    echo "[WARNING] SHA512 checksums not generated for files inside directories!"
  fi

  echo "[INFO] You may now run 'bldpkg' again"
  exit 0
fi

# Display the package and its version we are building
echo "[INFO] Building package '$app' version '$version' ..."
sleep 0.5

# Invoke auditd if useauditd is set to 1 in bldpkg.conf
if [[ $useauditd = 1 ]] ; then
  
  if [[ ! -x /bin/auditd ]] ; then
    echo "[ERROR] Program 'auditd' not found!"
    exit 1
  fi

  # First clear out the log file
  auditlogfile="/var/log/audit/audit.log"
  echo > $auditlogfile

  # Now run auditd 
  /bin/auditd -n &

  # Store the PID inside a variable
  auditpid=$!

  # Note: auditd writes about 6-8 lines for our setup when initialized.
  echo "[INFO] /bin/auditd initialised."
fi

# Function to safely terminate auditd.
terminateauditd() {
  if [[ $useauditd = 1 ]] ; then
    # Terminate auditd, log number of lines inside a variable
    /bin/kill "$auditpid"
    echo "[INFO] /bin/auditd stopped."

    auditlogtermsize=$(wc -l < $auditlogfile)

    if [[ $auditlogtermsize -gt 10 ]] ; then
      echo "[WARNING] Auditd log file '$auditlogfile' is greater than 10 lines!"
      echo "[WARNING] Highly recommend that you examine its file!"
      sleep 5
    fi
  fi
}

# sha512sums variable is expected in every single package build file
if [[ -z $sha512sums ]] ; then 
  echo "[ERROR] SHA512 checksums don't exist in '$buildfile' !"
  echo "[ERROR] Please run 'bldpkg -g' to add them"
  exit 1
fi
  
eval sums=\"\$sha512sums\"

echo "[INFO] Verifying SHA512 checksums against source files..."
  
IFS=$'\n'

for src in $sums; do
  if ! echo $src | sha512sum -c ; then
    echo "[ERROR] Checksums failed to match!"
    exit 1
  fi
done

unset IFS

# Function to output to the user which patch is about to be applied. Useful when there are many
# patches and you want to determine which patch failed. 
applypatch() {
  # Take patch file name as the first argument
  patchfile="$1"

  # Perform a simple check 
  if [[ -z $patchfile ]] ; then
    echo "[ERROR] Please provide valid patch file name"
    exit 1
  elif [[ ! -f $patchfile ]] ; then
    echo "[ERROR] Patch file '$patchfile' not found inside $srcdir!"
    exit 1
  fi

  # Get relative path of the patch file
  relativepath=$(basename $patchfile)
  echo "[INFO] Applying patch '$relativepath'.."
  # We use if/else to determine if the patch applied successfully
  if ! patch -p1 < "$patchfile" ; then
    echo "[ERROR] Failed to apply patch file '$patchfile'"
  fi
}

# Do a preliminary package dependency check if $checkdependencies is set to 1 in bldpkg.conf
if [[ $checkdependencies = 1 ]] ; then
  
  echo "[INFO] Parsing $app 's dependency list..."
  for packagedep in $requires; do
    depcount=$(find /share/doc -type f -name "$packagedep.SMBuild" | wc -l)
    # If count is 1, we are ok
    if [[ $depcount = 1 ]] ; then
      echo "[INFO] Found dependency '$packagedep'"
    # If count is 0, we exit, because we are in trouble
    elif [[ $depcount = 0 ]] ; then
      echo "[ERROR] Did not find dependency '$packagedep'."
      exit 1
    # If count is greater than or equal to 2, we are in slightly less trouble
    elif [[ $depcount -ge 2 ]] ; then
      echo "[WARNING] Found multiple versions of '$packagedep' !"
      sleep 0.5
    fi
  done

fi

# Function to specifically match arrays inside a value. This function will be used later on to perform
# package and directory matches using certain conditions. Note: "${ARRAY[@]}" =~ "${VARIABLE}" isn't fool-proof.
inarray() {
  local n=$1 h
  shift
  for h ; do
    [[ $n = "$h" ]] && return
  done
  return 1
}

# Check if $parenttmp is set and is a directory
if [[ -z $parenttmp ]] ; then
  echo "[ERROR] parenttmp variable not set in /etc/bldpkg.conf."
  exit 1
elif [[ ! -d $parenttmp ]] ; then
  echo "[ERROR] parenttmp variable set to '$parenttmp' in /etc/bldpkg.conf is not a directory."
  exit 1
fi

# Attempt to write to the $parenttmp directory. This directory is used for everything related to the
# build process outside the source directory $srcdir
if ! touch "$parenttmp/.smlinuxwritetest" ; then
  echo "[ERROR] Parent temp directory '$parenttmp' is not writable!"
  exit 1
fi

# Discard the test file
[[ -e "$parenttmp/.smlinuxwritetest" ]] && rm -f "$parenttmp/.smlinuxwritetest"

# Determine if $tmpfsdir is listed inside $protecteddirectories array
if inarray "${parenttmp}" "${protecteddirectories[@]}" ; then
  echo "############ ATTENTION ############"
  echo "[ERROR] parenttmp IS SET TO '$tmpfsdir' WHICH IS A PROTECTED DIRECTORY!! EXITING!!"
  exit 1
fi

# If htmloutput is set to 1, echo $app, $version and $build as file names inside the parent build directory. 
# This will output into an HTML file so that the basic status of the build process (whether started, stopped, 
# interrupted or failed) can be viewed in the web browser. 
if [[ $htmloutput = 1 ]] ; then

  if [[ -n $autobuild ]] ; then
    cat << EOF >> "$parenttmp/BUILDMONITOR"
<b>$commencedate | Building package # $currentpkgnumber / $totalpkgnumber: <i><a href="/smlinux/pkgresults?pkg=$app&amp;smver=1.0&amp;arch=all&amp;resultnum=25">$app $version</a></i></b>
EOF
  else 
    cat << EOF >> "$parenttmp/BUILDMONITOR"
<b>$commencedate | Building package <i><a href="/smlinux/pkgresults?pkg=$app&amp;smver=1.0&amp;arch=all&amp;resultnum=25">$app $version</a></i></b>
EOF
  fi

  touch "$parenttmp/BUILDING"
fi
  
# Validate compressor and set extension
validpkgextensions=( tgz tbz tlz txz )
if ! inarray "${pkgext}" "${validpkgextensions[@]}" ; then
  echo "[ERROR] '$pkgext' is not a valid package extension for an SMLinux installer file."
  exit 1
fi

# Figure out the compression tool to be used based on the $pkgext variable set in bldpkg.conf. 
case "$pkgext" in 
  tgz) if [[ -x /bin/pigz ]] ; then
         compressor=pigz
       else
         compressor=gzip 
       fi
       compressopts="$gzipopts" ;;

  tbz) compressor=bzip2 
       compressopts="$bzipopts" ;;

  tlz) if [[ -x /bin/plzip ]] ; then
         compressor=plzip
       else 
         compressor=lzip
       fi
       compressopts="$lzipopts" ;;

  txz) compressor=xz 
       compressopts="$xzopts" ;;
esac

# Borrowed from slackware's installpkg utility
if ! "$compressor" --help > /dev/null 2>&1  ; then
  echo "[ERROR] '$compressor' Compressor validation failed."
  exit 1
fi

# Validate the TMPFS directory if usetmpfs is set to 1 and tmpfsdir variable is set. If it fails,
# declare a variable for the build summary.
if [[ $usetmpfs = 1 ]] && [[ -n $tmpfsdir ]]; then
  if [[ ! -d $tmpfsdir ]] ||  ! touch "$tmpfsdir/.smlinuxtmpwritetest"  \
    || [[ $(findmnt -no TARGET "$tmpfsdir") != "$tmpfsdir" ]] \
    || [[ $(findmnt -no FSTYPE "$tmpfsdir") != "tmpfs" ]]; then
      tmpfscheckfailed=1
  fi

  # Discard the file used to test the tmp directory
  [[ -e "$tmpfsdir/.smlinuxtmpwritetest" ]] && rm -f "$tmpfsdir/.smlinuxtmpwritetest"

  # Check the tmpfsdir for stale directories other than the current package about to be built. If found, issue a warning.
  if [[ $(find "$tmpfsdir" -type d -maxdepth 1 -print0 | wc -l) -gt 1 ]]; then
    if [[ ! -d $app.src ]] || [[ ! -d package-$app ]] ; then
      echo "[WARNING] TMPFS directory '$tmpfsdir' has stale directories from previous builds!"
      sleep 5
    fi
  fi
fi
  
# Validate system swap if swapcheck is defined and set to 1
if [[ $swapcheck = 1 ]]; then
  if inarray "${app}" "${packagesrequiringswap[@]}" ; then    

    # Here we determine available system swap size needed to compile exceptional packages that pull
    # in a lot of RAM. Those packages are listed under the packagesrequiringswap array in /etc/bldpkg.conf. 
    # Check whether swap is available on the system and if it is, determine its size. If its size
    # is >= swapsize, we are all good. If it's less than swapsize, we exit with a status 1.

    swapcheck=$(grep "SwapFree" /proc/meminfo | awk '{print $2}')
    if [[ $swapcheck -lt $swapsize ]]; then
      echo "[ERROR] Insufficient swap to build '$app' which is listed"
      echo "[ERROR] in '$packagesrequiringswap'. Kindly add/increase"
      echo "[ERROR] swap size on this system and try again."
      exit 1
    fi
  fi
fi

# Set the temporary directory for building the package. Also define package staging directory.
# This is where package files that get "installed" go into, for example 'make install DESTDIR=$pkg' 
# or 'DESTDIR="$pkg" ninja install'.

# If usetmpfs is set to 1, tmpfsdir is defined and tmpfscheckfailed  variable is unset, determine 
# if the $app is in the exception list and whether to build inside or outside the TMPFS directory.
if [[ $usetmpfs = 1 ]] && [[ -n $tmpfsdir ]] && [[ -z $tmpfscheckfailed ]] ; then
   
  # If $app is in the TMPFS exception list inside /etc/bldpkg.conf, compile it *OUTSIDE* the TMPFS
  # directory, i.e the non-TMPFS directory, else compile it *INSIDE* the TMPFS directory. This if/else
  # is solely for deciding whether $app is in the exception list or not. 
  if inarray "${app}" "${tmpfsexceptionlist[@]}" ; then

    # We DO NOT compile inside tmpfsdir
    tmpfsenabledforthispackage=0

    # In the absence of tmpfs, we use the normal directory 
    tmp="$nontmpfsdir/$app.src"
    pkg="${pkg:-$nontmpfsdir/package-$app}"
  else
    # We compile inside tmpfsdir. Set the tmpfsenabledforthispackage variable here to inform build
    # summary function at the bottom
    tmpfsenabledforthispackage=1

    # Disable ccache
    ccache=0

    # Override preservebuilddir and preservepackagedir to remove both build and package staging directories
    preservebuilddir=0
    preservepackagedir=0

    # Get the directory from the tmpfsdir variable for extracting the source and set it as our build
    # and staging directory
    tmp="$tmpfsdir/$app.src"
    pkg="${pkg:-$tmpfsdir/package-$app}"

  fi
else
  # If usetmpfs is disabled, we compile in the non-TMPFS directory
  tmp="$nontmpfsdir/$app.src"
  pkg=${pkg:-$nontmpfsdir/package-$app}
fi

# Validate and export $cputhreads as MAKEFLAGS variable 
if [[ -n $cputhreads ]]; then
  # export the user-defined number
  MAKEFLAGS="$cputhreads"
  export MAKEFLAGS
else
  # Or fetch the number from nproc
  MAKEFLAGS=$(nproc --all)
  export MAKEFLAGS
fi
  
# Validate all compilers starting with distcc
# Validate everything related to distcc if globaldistcc is set
if [[ $globaldistcc = 1 ]] ; then
  # Check if distcc exists and is an executable
  if [[ ! -x $distccbinpath ]]; then
    echo "[ERROR] Oops! Distcc binary was not found but building with it"
    echo '[ERROR] was requested! Either ensure distcc is in your $PATH or'
    echo "[ERROR] disable this option in bldpkg.conf file."
    exit 1

  # Check if the symlinks are right
  elif [[ ! $(echo "$PATH" | grep "$distccsympath") ]] ; then
    echo "[ERROR] '"$distccsympath"' directory not found in your env PATH"
    exit 1
  elif [[ ! -d $distccsympath ]] ; then 
    echo "[ERROR] '$distccsympath' directory containing symlinks to distcc"
    echo "[ERROR] does not exist! Kindly create it and create symlinks" 
    echo "[ERROR] based on instructions in bldpkg.conf!"
    exit 1
  fi
     
  # Trace symlinks to the binary
  for f in gcc g++ cc c++ ; do 
    if [[ -e $distccsympath/$f ]] && [[ -L $distccsympath/$f ]]; then
      # We use "realpath" to follow the $distccsympath/$f symlink and act on the exit code.
      if [[ $(realpath -e "$distccsympath/$f") != "$distccbinpath" ]] ; then
        echo "[ERROR] $distccsympath/$f does not point to '$distccbinpath'. "
        echo "[ERROR] Kindly fix this!"
        exit 1
      fi
    else
      echo "[ERROR] $f either does not exist or is not a symlink inside"
      echo "[ERROR] '$distccsympath'. Kindly fix this! "
      exit 1
    fi
      
    #echo -n "Validating distcc $f compiler... "

    # This is a really small C program taken from autoconf tests
    #cat << EOF > $parenttmp/distcccheck-"$f".c
#int 
#main()
#{
#  ;
#  return 0;
#}
#EOF
      
    #"$distccsympath/$f" -o $parenttmp/distcccheck-"$f" $parenttmp/distcccheck-"$f".c
    #checkstatus="$?"

    # Discard the files once the validation passes/fails
    #if [[ $checkstatus = 0 ]] ; then
    #  echo "[OK]"
    #  rm $parenttmp/distcccheck-"$f"{,.c}
    #else
    #  echo "Something's up with distcc $f"
    #  rm $parenttmp/distcccheck-"$f"{,.c}
    #  exit 1
    #fi

  done

  # If distcc=0 is set in the package build file to disable distcc, remove the value of $distccsympath from 
  # $PATH otherwise export DISTCC_HOSTS and DISTCC_IO_TIMEOUT variables.
  if [[ $distcc = 0 ]] ; then
    PATH="${PATH//$distccsympath:/}"
  else
    # netcat hosts inside $DISTCC_HOSTS by checking for an open port
    echo "[INFO] Validating distcc hosts..."
    # Check if we have nc
    if [[ ! -x /bin/nc ]] ; then
      echo "[WARNING] nc does not exist! Ignoring this..."
    else 
      # Remove the common options along with the slash and the numbers after it
      hosts=$(echo "$DISTCC_HOSTS" | sed -e 's@/[a-z0-9]*@@g' -e 's@--randomize@@' -e 's@localhost@@' -e 's@,lzo@@g')
      for host in ${hosts[@]} ; do
        # We only run distccd on TCP port 3632
        if ! /bin/nc -z -w 1 "$host" 3632 > /dev/null 2>&1 ; then
          echo "[WARNING] Distcc host '$host' is OFFLINE"
          echo "[WARNING] Rewriting DISTCC_HOSTS"
          DISTCC_HOSTS=$(echo "$DISTCC_HOSTS" | sed "s@$host/[a-z0-9,]*@@")
        fi
      done
    fi

    export DISTCC_HOSTS DISTCC_IO_TIMEOUT
  fi
else
  # Remove $distccsympath
  PATH="${PATH//$distccsympath:/}"
fi

# Validate everything related to ccache if globalccache is set
if [[ $globalccache = 1 ]]; then
  if [[ ! -x $ccachebinpath ]] ; then
    echo "[ERROR] Oops! ccache binary was not found but building with it" 
    echo '[ERROR] was requested! Either ensure ccache is in your $PATH or'
    echo "[ERROR] disable this option in bldpkg.conf."
    exit 1
  fi

  if [[ ! $(echo "$PATH" | grep "$ccachesympath") ]] ; then
    echo "[ERROR] '"$ccachesympath"' directory not found in your env PATH"
    exit 1
  elif [[ ! -d $ccachesympath ]] ; then 
    echo "[ERROR] '$ccachesympath' directory containing symlinks to ccache"
    echo "[ERROR] does not exist! Kindly create it and create symlinks"
    echo "[ERROR] based on instructions in bldpkg.conf."
    exit 1
  fi

  for f in gcc g++ cc c++ ; do 
    if [[ -e $ccachesympath/$f ]] && [[ -L $ccachesympath/$f ]]; then
      # We use "realpath" to follow the $ccachesympath/$f symlink and act on the exit code
      if [[ $(realpath -e "$ccachesympath/$f") != "$ccachebinpath" ]] ; then
        echo "[ERROR] '$ccachesympath/$f' does not point to '$ccachebinpath'. "
        echo "[ERROR] Kindly fix this!"
        exit 1
      fi
    else
        echo "[ERROR] $f either does not exist or is not a symlink inside '$ccachesympath'" 
        echo "[ERROR] Kindly fix this!"
        exit 1
    fi

    #echo -n "Validating ccache $f compiler... "

    # This is a really small C program taken from autoconf tests
    #cat << EOF > $parenttmp/ccachecheck-"$f".c
#int 
#main()
#{
#  ;
#  return 0;
#}
#EOF
      
    #"$ccachesympath/$f" -o $parenttmp/ccachecheck-"$f" $parenttmp/ccachecheck-"$f".c
    #checkstatus="$?"

    # Discard the files once the validation passes/fails
    #if [[ $checkstatus = 0 ]] ; then
    #  echo "[OK]"
    #  rm $parenttmp/ccachecheck-"$f"{,.c}
    #else
    #  echo "Something's up with ccache $f"
    #  rm $parenttmp/ccachecheck-"$f"{,.c}
    #  exit 1
    #fi

  done

  # If ccache=0 is set in the package build file to disable ccache, remove the value of ccachesympath
  # from $PATH and export it again
  if [[ $ccache = 0 ]]; then
    PATH="${PATH//$ccachesympath:/}"
  fi
else
   # Remove $ccachesympath
    PATH="${PATH//$ccachesympath:/}"
fi

# Validate everything related to sccache if globalccache is set
if [[ $globalsccache = 1 ]]; then
  if [[ ! -x $sccachebinpath ]] ; then
    echo "[ERROR] Oops! sccache binary was not found but building with it" 
    echo "[ERROR] was requested! Either ensure sccache is in your "'$PATH'" or"
    echo "[ERROR] disable this option in bldpkg.conf."
    exit 1
  fi

  if [[ ! $(echo "$PATH" | grep "$sccachepath") ]] ; then
    echo "[ERROR] '"$sccachepath"' directory not found in your env PATH"
    exit 1
  elif [[ ! -d $sccachepath ]] ; then 
    echo "[ERROR] '$sccachepath' directory containing symlinks to ccache"
    echo "[ERROR] does not exist! Kindly create it and create symlinks"
    echo "[ERROR] based on instructions in bldpkg.conf."
    exit 1
  fi

  for f in gcc g++ cc c++ ; do 
    # A hard link is basically a copy of a file with the same inode number stored in a different location.
    # We are trying a bit hard to ascertain whether a binary is a hard link or not.
    # First get the inode number of the binary in the original location
    sccache_binary_inode_num=$(stat --printf '%i\n' $sccachebinpath)
    # Then get the inode number of the file inside the hard link path
    sccache_hardlink_file_inode_num=$(stat --printf '%i\n' $sccachepath/$f)

    if [[ ! -e $sccachepath/$f ]] ; then
        echo "[ERROR] '$f' either does not exist inside '"$sccachepath"'" 
        echo "[ERROR] Kindly fix this!"
        exit 1
    # If the hard link's inode number does not match the original binary's inode number, throw an error and exit
    elif [[ $sccache_hardlink_file_inode_num != "$sccache_binary_inode_num" ]] ; then
        echo "[ERROR] File '"$f"' inside '"$sccachepath"' is not a hard link!"
        echo "[ERROR] Kindly fix this!"
        exit 1
    fi

    #echo -n "Validating sccache $f compiler... "

    # This is a really small C program taken from autoconf tests
    #cat << EOF > $parenttmp/sccachecheck-"$f".c
#int 
#main()
#{
#  ;
#  return 0;
#}
#EOF
      
    #"$sccachepath/$f" -o $parenttmp/sccachecheck-"$f" $parenttmp/sccachecheck-"$f".c
    #checkstatus="$?"

    # Discard the files once the validation passes/fails
    #if [[ $checkstatus = 0 ]] ; then
    #  echo "[OK]"
    #  rm $parenttmp/sccachecheck-"$f"{,.c}
    #else
    #  echo "Something's up with sccache $f"
    #  rm $parenttmp/sccachecheck-"$f"{,.c}
    #  exit 1
    #fi
    
    # Useful for rust-specific builds. 
    RUSTC_WRAPPER="$sccachebinpath"
    export RUSTC_WRAPPER

  done

  # If sccache=0 is set in the package build file to disable sccache, remove the value of sccachepath
  # from $PATH and export it again
  if [[ $sccache = 0 ]]; then
    PATH=$(echo "$PATH" | sed "s@$sccachepath:@@g")
  fi
else
  # Remove $sccachepath
   PATH=$(echo "$PATH" | sed "s@$sccachepath:@@g")
fi

# Apply CPU-specific compiler variables defined inside bldpkg.conf
# https://github.com/sakaki-/gentoo-on-rpi-64bit/blob/master/reference/compile_run_benchmarks.sh
# https://www.raspberrypi.org/forums/viewtopic.php?t=11629
# noarch is set inside initfs, pkgtools, GTK themes and some other stuff.

# If $arch has not been exported by autobuild or not set in the individual build files that have
# arch=noarch, we set our own $HOSTTYPE is only set in the bash shell.
[[ -z $arch ]] && arch="$HOSTTYPE"

if [[ $arch = noarch ]]; then
  CFLAGS=""
  export CFLAGS

elif [[ $arch = aarch64 ]]; then  
  hostdist="$aarch64hostdist"
  builddist="$aarch64builddist"

  if [[ -n $debug ]]; then
    CFLAGS="$gccdebug $aarch64cflags"
  else
    CFLAGS="$aarch64cflags"
  fi

  CXXFLAGS="$CFLAGS"
  export hostdist builddist CFLAGS CXXFLAGS

elif [[ $arch = x86_64 ]]; then
  builddist="$x8664builddist"

  if [[ -n $debug ]]; then
    CFLAGS="$gccdebug $x8664cflags"
  else
    CFLAGS="$x8664cflags"
  fi

  CXXFLAGS="$CFLAGS"
  export builddist CFLAGS CXXFLAGS

else
  echo "[ERROR] Sorry! '$arch' CPU architecture not supported by SMLinux!"
  exit 1
fi
  

# If $noautoconfsite is unset in an individual package build file, export CONFIG_SITE variable into the build 
# environment for a package's configure script to pickup. Most autoconf-compatible configure scripts will 
# automatically pick up this variable from the environment and speed up the initial configure process. 
if [[ -z $noautoconfsite ]] ; then
  if [[ -n $configsite ]] && [[ -e $configsite ]]; then
    CONFIG_SITE="$configsite"
    export CONFIG_SITE
  fi
fi

# Condition to reuse the autobuildtemp file if set from autobuild or make a new temporary file
if [[ -n $autobuildtemp ]]; then
  tempfile="$autobuildtemp"
else
  tempfile=$(mktemp "$parenttmp/SMBUILD.XXXXXX")
fi

# Function to prevent a package from compiling on an unsupported architecture
compileonlyfor() {
  # usage: compileonlyfor <arch> 
  # will cause compilation to exit with 0 if uname -m does not match <arch>
  archname="$(uname -m)"
  archargument="$1"

  if [[ $archname != "$archargument" ]]; then
    echo ""
    echo "[INFO] '$app' not supported on '$archname' and hence not"
    echo "[INFO] not being built. Exiting."
    exit 0
  fi
}

# Function to remove old package directories and make new ones. 
# To be invoked inside a package build file.
mkandenterbuilddir() {
  # $pkgdest is set in bldpkg.conf. 
  pkgdocs="$pkg/share/doc/$app-$version"
  # Remove any old $pkg staging directory left by any previous build.
  rm -rf "$pkg"
     
  # Now create all essential build-related directories
  mkdir -p "$tmp" "$pkg/install" "$pkgdocs" "$pkgdest" 

  echo "[INFO] Leaving source directory $srcdir"
  echo "[INFO] Entering build directory $tmp"
  cd "$tmp"
}

# Function to fix permissions inside the build directory
# To be invoked inside a package build file.
fixbuilddirpermissions() {
  echo -n "[INFO] Fixing permissions in the newly-created build directory..."
  chown -R root.root .
  find -L .  -perm /111 -a \! -perm 755 -a -exec chmod 755 {} \+ -o \
   \! -perm /111 -a \! -perm 644 -a -exec chmod 644 {} \+ || true
  echo " done."
}

# Function to calculate elapsed build time. runtime takes the $SECONDS variable as an argument. $SECONDS is an
# environment variable set by bash to show the number of whole seconds the shell has been running.
runtime() {
  TIMER=$1
  [[ -z $TIMER ]] && return 1

  local DAY=$(( TIMER / 86400 ))
  local HOUR=$(( (TIMER - (DAY * 86400)) / 3600 ))
  local MINS=$(( (TIMER - (DAY * 86400) - (HOUR * 3600)) / 60 ))
  local SECS=$(( TIMER - (DAY * 86400) - (HOUR * 3600) - (MINS * 60) ))

  if [[ $DAY -gt 0 ]]; then
    echo -n "${DAY}d, ${HOUR}h ${MINS}m ${SECS}s"
  else
    echo -n "${HOUR}h, ${MINS}m ${SECS}s"
  fi

  return 0
}

# Function to prepare runit service directories for a particular daemon
# To be invoked inside a package build file.
preprunitservice() {
              	
  # usage: $ preprunitservice <chrony> <down> <finish>
  # Will create the chrony service directories with a down and a finish file to prevent auto-execution
  # at next boot and to provide a clean exit respectively. Use the first argument to define the service name
  rsname="$1"
  # Use the second argument to add the down file
  down="$2"
  # Use the third argument to add the finish file
  finish="$3"
   
  # Enter the staging directory
  cd "$pkg"

  # Create the service directories
  mkdir -p "etc/service/$1" var/service 

  # Copy the service run file from the source directory into etc/service/$1
  if [[ -f $srcdir/$1.run ]] ; then
    cp "$srcdir/$1.run" "etc/service/$1/run"
  else
    echo "[ERROR] $1.run does not exist!"
    exit 1
  fi

  # If the second argument is "down", or if the second argument is "finish", create that file inside etc/service/$1/
  if [[ $2 = down ]]; then
    touch "etc/service/$1/down"
  elif [[ $2 = finish ]]; then
    cp "$srcdir/$1.$2" "etc/service/$1/finish"
  fi

  # If the third argument is "finish", copy that file from the source directory into etc/service/$1/
  [[ -n $3 ]] && cp "$srcdir/$1.$3" "etc/service/$1/finish"

  # Create the symlinks between etc/service and var/service
  ln -s "../../etc/service/$1" "var/service/$1"

  # Finally set the executable permissions on the run file
  chmod 0755 "$pkg/etc/service/$1/run"
}

# Function to perform post-compile tasks.
# To be invoked inside a package build file.
mkfinalpkg() {
  
  # Now we attempt to split the total time we'll get when making the summary into two times: compile
  # time and packaging time. Here we store the value of $SECONDS variable the moment makefinalpkg is
  # invoked. We use this value as the compile time, because this is the next function that's called
  # by the build script the moment a successful compile make install  DESTDIR=$pkg or something similar. 

  # compiletimea will store the exact seconds
  compiletimea="$SECONDS"

  # compiletimeb will run the runtime function against compiletimea and store the resulting output
  compiletimeb=$(runtime $compiletimea)

  echo "[INFO] Leaving build directory $tmp"
  echo "[INFO] Entering staging directory $pkg"
  cd "$pkg"
  echo "[INFO] Performing packaging tasks..."
   
  # Check if /lib64 was created inside $pkg
  if [[ -d $pkg/lib64 ]] ; then
    echo "[ERROR] '$app' has /lib64 directory. Musl does not support multilib."
    echo "[ERROR] Please fix the build options and ensure the /lib64 is not created."
    exit 1
  fi
   
  # Check if /usr and /sbin were created inside $pkg only if allowusrdir=1 is not set in the package build file
  for directory in usr sbin ; do
    if [[ -d $pkg/$directory ]] ; then
      echo "[ERROR] '$app' has '$directory' directory which is a symlink to /bin on SMLinux."
      echo "[ERROR] Please fix the build options and ensure '$directory' is not created."
      exit 1
    fi
  done

  echo "[INFO] Copying post-install files..."

  [[ -e $srcdir/doinst.sh ]] && cp "$srcdir/doinst.sh" "$pkg/install/"

  # If /share/applications directory exists but there is no doinst.sh in the source directory, create one using cat
  if [[ -d $pkg/share/applications ]] && [[ ! -e $srcdir/doinst.sh ]] ; then
    echo "[INFO] Found /share/applications but couldn't find any doinst.sh in the source directory."
    echo "[INFO] Creating one automatically that refreshes GTK cache."
    cat << EOF >> "$pkg/install/doinst.sh"
[[ -x /etc/rc.d/rc.gtk ]] && /etc/rc.d/rc.gtk
EOF

  # Or if /share/applications directory exists, and there is a doinst.sh file in the source directory, but there is no mention of rc.gtk, then too create one using cat
  elif [[ -d $pkg/share/applications ]] && [[ -e $srcdir/doinst.sh ]] && ! grep -q 'rc.gtk' "$srcdir/doinst.sh" ; then
    echo "[INFO] Found /share/applications but couldn't find any rc.gtk lines inside doinst.sh in the source directory." 
    echo "[INFO] Creating one automatically that refreshes GTK cache."
    cat << EOF >> "$pkg/install/doinst.sh"
[[ -x /etc/rc.d/rc.gtk ]] && /etc/rc.d/rc.gtk
EOF
  fi

  # Compress and link manpages
  if [[ -d "$pkg/share/man" ]]; then
    echo "[INFO] Compressing and linking man pages..."
    ( cd "$pkg/share/man"
      for manpagedir in $(find . -type d -name "man*") ; do
        ( cd $manpagedir
          for eachpage in $( find . -type l -maxdepth 1) ; do
	    ln -s $( readlink $eachpage ).gz $eachpage.gz
	    rm $eachpage
          done
	  gzip -9 ./*.? >/dev/null 2>&1 || true
	)
      done
    )
  fi
   
  # Remove libtool archive files
  echo "[INFO] Discarding any libtool archive (.la) files..."
  find "$pkg" -type f -name "*.la" -exec rm -v {} \;

  # Provide a copy of the package build file as a source of reference for users
  if [[ -n $origbuildfile ]] && [[ -f $srcdir/$origbuildfile ]] ; then
    install -Dm 644 "$srcdir/$origbuildfile" "$pkgdocs/$app.SMBuild"
  else
    install -Dm 644 "$srcdir/$buildfile" "$pkgdocs/$app.SMBuild"
  fi


  # We don't want multiple directories for documentation. Detect if $pkg/share/doc/<app-name> was created. 
  # If it has been created, move its contents into $pkgdocs and discard the old doc directory.
  if [[ -d "$pkg/share/doc/$app" ]] ; then
    echo "[INFO] Found share/doc/$app documentation directory."
    echo "[INFO] Moving its contents into share/doc/$app-$version/"
    mv "$pkg/share/doc/$app/"* "$pkgdocs"/
    rmdir "$pkg/share/doc/$app"
  fi

  # We'd like pkgconfig files to only go into /lib; some packages have other ideas. So fix for them
  if [[ -d $pkg/share/pkgconfig ]] ; then
    echo "[WARNING] $app has created share/pkgconfig directory."
    echo "[WARNING] Moving this directory into /lib"
    mkdir -p "$pkg/lib/pkgconfig"
    mv "$pkg/share/pkgconfig"/* "$pkg/lib/pkgconfig/"
    rmdir "$pkg/share/pkgconfig"
  fi

  # Normally we'd expect some debug symbols in the newly-produced binaries. But that isn't always the
  # case with some packages whose build systems strip objects before hand
  if [[ $debug = 1 ]] ; then
    for file in \
    $( find "$pkg" -type f ) 
    do
      file -m /etc/file/magic/elf "$file" | \
        grep -E "executable|shared object" | \
        grep ", stripped" | cut -d: -f1 >> "$pkg/install/package.$app.debugfailedfilenames"
    done

    debugfilecount=$(wc -l < "$pkg/install/package.$app.debugfailedfilenames")
    if [[ $debugfilecount -ge 1 ]]; then
      debugwarning=1
    else 
      rm "$pkg/install/package.$app.debugfailedfilenames"
    fi
  fi
  
  # Now strip the binaries and shared libraries. --strip-unneeded is unnecessary for binutils 2.34+
  if [[ -z $debug ]] && [[ $debug != 1 ]]; then
    find "$pkg" -type f -print0 | xargs -0 file -m /etc/file/magic/elf | \
      grep -E "ELF.*(executable|shared object|statically linked)" |\
      cut -d: -f1 | xargs strip 2>/dev/null || true
  fi

  # If $preservestaticlibs is not set in the package build file, delete all static libraries
  if [[ -z $preservestaticlibs ]] ; then
    echo "[INFO] Discarding any static libraries..."
    find "$pkg" -type f -print0 | xargs -0 file -m /etc/file/magic/archive | \
    grep -E "current ar archive" | awk '{print $1}' | cut -d: -f1 | \
    xargs rm -v 2>/dev/null || true
  fi
  
  # Calculate total files, directories, symlinks and uncompressed staging directory size
  if [[ $showsummary = 1 ]] ; then
    totalfilecount=$(find "$pkg" -type f | wc -l)
    totaldircount=$(find "$pkg" -type d | wc -l)
    totalsymcount=$(find "$pkg" -type l | wc -l)
    packusize1=$(du -s "$pkg" | awk '{print $1}')
  fi

  # Here we ascertain the packaging time taken to actually prepare the final package. For this, we
  # must reset the SECONDS variable to ensure accuracy
  SECONDS=0

  # Store package location inside this variable:
  newpkglocation="$pkgdest/$app-$version-$arch-$build.$pkgext"
  
  # https://gist.github.com/ruario/9672717
  # Create the SMLinux package
  echo "[INFO] Generating SMLinux package..."

  # If $disablepkgsymlinks is not set in the package build file, change any symlinks into shell script code. An example is base/initfs wherein
  # we only want the doinst.sh file in the source directory to be copied, not manipulated.
  if [[ -z $disablepkgsymlinks ]] ; then
    if find . -type l | grep -qm1 .; then
      echo "[INFO] Found symlinks, preparing install/doinst.sh..."
      find . -type l -printf '( cd %h ; rm -rf %f )\n( cd %h ; ln -sf %l %f )\n' -delete > install/symlinks
      if [[ -f install/doinst.sh ]]; then
        printf '\n' | cat - install/doinst.sh >> install/symlinks
      fi
      mv install/symlinks install/doinst.sh
    fi
  fi

  tar cvf - . --format gnu \
    --xform 'sx^\./\(.\)x\1x' \
    --show-stored-names | "$compressor" "$compressopts" > "$newpkglocation"
  pkgstatus=$?

  echo ""
  echo "[INFO] SMLinux package '$app-$version-$arch-$build.$pkgext' successfully generated in $pkgdest."

  # Terminate auditd daemon
  terminateauditd

  echo "[INFO] Leaving staging directory $pkg"

  # cd back to $srcdir when preservepackagedir is set to 0 to prevent this error: 
  # shell-init: error retrieving current directory: getcwd: cannot access parent directories:
  # No such file or directory
  echo "[INFO] Re-entering source directory $srcdir"
  cd "$srcdir"


  if [[ $showsummary = 1 ]] || [[ $htmloutput  = 1 ]] ; then
    # With SECONDS reset, the shell will add in a fresh value, which we can now use to ascertain 
    # the packaging time, by again passing that value as an argument to the runtime function
    packagetimea="$SECONDS"
    packagetimeb=$(runtime "$packagetimea")
  fi


  if [[ $showsummary = 1 ]]; then
    # Determine size of SRCDIR aka source directory
    srcdirsize=$(du -s "$srcdir" | awk '{print $1}')
    # Determine size of tmp aka build directory size
    builddirsize=$(du -s "$tmp" | awk '{print $1}')

    # Calculate SSD write savings if TMPFS has been used
    if [[ $usetmpfs = 1 ]] && [[ $tmpfsenabledforthispackage = 1 ]] ; then

      # Determine size of staging directory
      pkgdirsize=$(du -s "$pkg" | awk '{print $1}')

      # Sum total of the above two variables is the amount of writes we saved
      tmpfssavingssum0="$((builddirsize + pkgdirsize))"

      # We'll get sum in kB. Convert that to MB.
      tmpfssavingssum1=$(echo "scale=2 ; "$tmpfssavingssum0" / 1024" | bc)

      # Store the result for build summary to pickup.
      tmpfssavingsize="and $tmpfssavingssum1"MB" writes to SSD saved"
    fi
  fi

  # Delete the build directory if preservebuilddir is set to 0
  if [[ $preservebuilddir = 0 ]] ; then
    if ! inarray "${tmp}" "${protecteddirectories[@]}" ; then
      rm -rf "$tmp"
    fi
  fi

  # Delete the package staging directory if preservepackagedir is set to 0
  if [[ $preservepackagedir = 0 ]] ; then
    if ! inarray "${pkg}" "${protecteddirectories[@]}" ; then
      rm -rf "$pkg"
    fi
  fi
}

buildfilecleanup() {
  # Discard all temporary files
  rm -f "$parenttmp/BUILDING" 

  # Autobuild discards $tempfile automatically, so only discard $tempfile if $autobuildtemp is unset
  if [[ -z $autobuildtemp ]] ; then
    rm "$tempfile"
  fi
}
  

prepbuildoutput() {

  # Get the build completion time and store it in a variable
  finishdate=$(date '+%a, %d %b %Y, %T')

  # If compiletimea is set, then do a sum total of compiletimea and packagetimea variables to get the
  # total time in seconds and use that as an argument for the runtime function. If compiletimea is not set,
  # invoke the runtime function alone on new reset $SECONDS
  if [[ -n $compiletimea ]] && [[ $pkgstatus = 0 ]] ; then
    finalcompiletime=$((compiletimea + packagetimea))
    totaltime=$(runtime $finalcompiletime)
  else
    totaltime=$(runtime $SECONDS)
  fi

  # Start of the showsummary if/else check
  if [[ $showsummary = 1 ]]; then

    # Stick to 8/16 colours, those are supported on most terminals
    if [[ $colours = 1 ]]; then
      colourscheck=$(tput colors 2>/dev/null)
      if [[ "$?" = 0 ]] && [[ $colourscheck -gt 2 ]] ; then
        # Red when the build fails
        colourr=$(printf '\e[41m')
        # Yellow when the build is interrupted
        coloury=$(printf '\e[93m')
        # Green when the build succeeds
        colourg=$(printf '\e[92m')
        # Cyan for the short questions
        colourc=$(printf '\e[96m')
        # App name/version colours
        colourv=$(printf '\e[92m')
        # Restore to default
        colourd=$(printf '\e[0m')
      fi
    fi

    # Determine the build type
    if [[ -n $autobuild ]]; then
      # We are using Tadgy's autobuild system
      buildsys="SSB Autobuild"
    else
      # We compiled the package manually
      buildsys="Manual"
    fi


    # Determine if distcc was used. If globaldistcc is enabled and  set to 1 and distcc is not declared in the
    # package build file, then set dstats in the build summary 
    if [[ $globaldistcc = 1 ]] && [[ -z $distcc ]]; then
      # If distcc was used, cut out --randomize and output rest of the DISTCC_HOSTS variable
      distccvar=$(echo "$DISTCC_HOSTS" | sed 's@--randomize@@')
      dstats="Yes,$distccvar"
    # Else if globaldistcc is enabled and set to 1 and distcc is set to 0 in the package build file, then set
    # dstats in the build summary
    elif [[ $globaldistcc = 1 ]] &&  [[ $distcc = 0 ]]; then
      dstats="Nope, disabled but global variable set"
    # Else If globaldistcc is unset, set dstats in the build summary
    elif [[ -z $globaldistcc ]] || [[ $globaldistcc = 0 ]]; then
      dstats="No, disabled globally"
    fi


    # Determine if ccache was used. Ccache caches compiler objects to disk, which speeds up subsequent compiles.
    # However, if we are compiling  inside tmpfs, we are not using ccache at all. So we set cstats accordingly
    # in the build summary
    if [[ $globalccache = 1 ]] && [[ $usetmpfs = 1 ]] && [[ $tmpfsenabledforthispackage = 1 ]] ; then
      cstats="Enabled globally but disabled due to tmpfs"

    elif [[ $globalccache = 1 ]] && [[ $usetmpfs = 0 ]] && [[ -z $ccache ]] ; then
      ccacheusedsize=$(ccache -s | grep "cache size" | head -n 1 | awk '{ $1=$2="" ; print $0}' | sed 's@ @@g')
      ccachetotalsize=$(ccache -s | grep "max cache size" | awk '{ $1=$2=$3="" ; print $0}' | sed 's@ @@g')
      cstats="Yes, $ccacheusedsize / $ccachetotalsize Allocated"

    elif [[ $globalccache = 1 ]] && [[ -z $usetmpfs ]] ; then
      cstats="Yes"

    elif [[ -z $globalccache ]] || [[ $globalccache = 0 ]] ; then
      cstats="No, disabled globally"
    fi


    # Determine the build type
    if [[ $debug = 1 ]] && [[ -z $debugwarning ]] ; then
      bldtype="*DEBUG* build"
    elif [[ $debug = 1 ]] && [[ $debugwarning = 1 ]]; then
      bldtype="*DEBUG* build[WARNING]"
    else
      bldtype="General build, no debug symbols"
    fi


    # Determine whether tmpfs was used
    if [[ $usetmpfs = 1 ]] && [[ $tmpfsenabledforthispackage = 1 ]] ; then
      tmpfsstate="Yes"

    elif [[ $usetmpfs = 1 ]] && [[ $tmpfsenabledforthispackage = 0 ]]; then
      tmpfsstate="*Not for this package* but enabled globally"

    elif [[ $usetmpfs = 1 ]] && [[ $tmpfscheckfailed = 1 ]]; then
      tmpfsstate="*NOPE, TMPFS DIRECTORY CHECK FAILED* but enabled globally"

    else
      tmpfsstate="No, disabled globally"
    fi


    if [[ -n $cputhreads ]]; then
      makeflags="$MAKEFLAGS, manually set"
    else
      makeflags="$MAKEFLAGS, auto-detected"
    fi
    
    # Determine if the build was successful or not
    if [[ $pkgstatus = 0 ]] ; then
      # Determine the compressed size
      packsize=$(du -bk "$newpkglocation" | awk '{print $1}')

      # Determine the uncompressed size
      packusize="$packusize1"

      # If the package was built successfully, output the installer sizes
      # Space saving = 1 - Compressed Size / Uncompressed size. 
      # Also, bc code taken from:
      # https://stackoverflow.com/questions/56945130/bash-echo-percentage-with-no-decimal-point-with-result-returned-from-bc-comman
      compressedsize=$(echo $(echo "scale=2 ; 1 - "$packsize" / "$packusize"" | bc ) | sed 's@.@@')

      bldstatus="$colourg Successful! :-D $colourd
                 Srce Size: $colourd Compressed: $srcdirsize"K", Uncompressed: $builddirsize"K"
                 Pkg Size:  $colourd Uncompressed: $packusize"K", Compressed: $packsize"K" ("$compressedsize'%'")
                 Pkg Has:   $colourd $totalfilecount files and $totalsymcount symlinks in $totaldircount dirs"

    elif [[ $wasinterrupted = 1 ]]; then
      bldstatus="$coloury ** INTERRUPTED ** :-/ $colourd" 

    else 
      bldstatus="$colourr !! FAILED !! :-( $colourd"

    fi

    # Finally prepare the summary

    # If we have $compiletimeb set, then assume the compile went well and output compile and packaging times into tempfile.
    if [[ -n $totaltime ]] && [[ -z $packagetimeb ]]; then
      ttime="$totaltime"
    elif [[ -n $totaltime ]] && [[ -n $packagetimeb ]]; then
      ttime="$totaltime ( $compiletimeb Compile ) + ( $packagetimeb Packaging )"
    fi

    # Output the section name if autobuildtemp is set. This means we are running an autobuild.
    if [[ -n $autobuildtemp ]]; then
      bsection="$section"
    else
      bsection="None"
    fi

    cat << EOF > "$tempfile"
$colourc ------------------------------------------------------------------------------- $colourd
$colourc BUILD SUMMARY FOR PACKAGE $colourv'$app'$colourc VERSION $colourv'$version'$colourc TAG $colourv'$build'
$colourc ------------------------------------------------------------------------------- $colourd
$colourc Build Status: $colourd $bldstatus
$colourc Build Section: $colourd $bsection
$colourc Total Time:   $colourd  $ttime
$colourc Started:  $colourd      $commencedate
$colourc Stopped:  $colourd      $finishdate
$colourc Ccache Used?   $colourd $cstats
$colourc Distcc Used?  $colourd  $dstats
$colourc TMPFS Used?  $colourd   $tmpfsstate $tmpfssavingsize
$colourc CPU Threads:  $colourd  ${makeflags/-j/}
$colourc CFLAGS Used:  $colourd  $CFLAGS
$colourc Compressor:  $colourd   $compressor ($compressopts)
$colourc Build Type:  $colourd   $buildsys & $bldtype
$colourc ------------------------------------------------------------------------------- $colourd
EOF

    # Output the build summary to the user on every build
    cat "$tempfile"

  fi
  # Completion of the showsummary if/else check

  # Discard temporary files
  buildfilecleanup

  # Show HTML output if enabled
  prephtmloutput

  # Prompt user for extracting/installing the package if the build has succeeded
  promptuser

}

prephtmloutput() {
  if [[ $htmloutput = 1 ]] ; then
    if [[ $pkgstatus = 0 ]] ; then
      cat << EOF >> "$parenttmp/BUILDMONITOR.html"
<tr><td><b><i><a href="/smlinux/pkgresults?pkg=$app&amp;smver=1.0&amp;arch=all&amp;resultnum=25">$app $version</a></i></b></td><td>$commencedate</td><td>$finishdate</td><td>$totaltime</td><td><b style="color:#00cc00;">SUCCEEDED</b></td></tr>
EOF
    elif [[ $wasinterrupted = 1 ]]; then
      cat << EOF >> "$parenttmp/BUILDMONITOR.html"
<tr><td><b><i><a href="/smlinux/pkgresults?pkg=$app&amp;smver=1.0&amp;arch=all&amp;resultnum=25">$app $version</a></i></b></td><td>$commencedate</td><td>$finishdate</td><td>$totaltime</td><td><b>INTERRUPTED</b></td></tr>
EOF
    else 
      cat << EOF >> "$parenttmp/BUILDMONITOR.html" 
<tr><td><b><i><a href="/smlinux/pkgresults?pkg=$app&amp;smver=1.0&amp;arch=all&amp;resultnum=25">$app $version</a></i></b></td><td>$commencedate</td><td>$finishdate</td><td>$totaltime</td><td><b style="color:#ff1a1a;">FAILED</b></td></tr>
EOF
    fi
  fi
}

promptuser() {
  
  # Extract package at the end of a build if autoextract is set to 1
  if [[ $autoextract = 1 ]] && [[ -z $autobuild ]] && [[ -n $newpkglocation ]] ; then
    echo "[INFO] Extracting package installer inside $srcdir/test..."
    mkdir -p "$srcdir/test"
    tar xvf "$newpkglocation" -C "$srcdir/test"
    echo ""
    echo "[INFO] '$app' package installer file successfully extracted"
  fi

  # Prompt the user at the end of a build whether to extract contents of a newly-built installer
  # into a subdirectory called "test" inside the package source directory the build was manually initiated from.
  # Has no effect on autobuilds since they are simply installed right away.
  if [[ $extractprompt = 1 ]] && [[ -z $autobuild ]] && [[ -n $newpkglocation ]] ; then
    while true ; do
      echo
      echo "[NOTIFY] '$app' has been built and extractprompt is enabled in"
      echo "[NOTIFY] bldpkg.conf file. Would you like to extract and examine contents"
      echo "[NOTIFY] of its package installer in a 'test' directory within the"
      echo "[NOTIFY] current source directory"
      echo "[NOTIFY] ($srcdir) ?"

      read -r -p "[NOTIFY] Old test directory, if it exists already, will be overwritten. (y/N) " yn

      case "$yn" in
        [Yy]* ) echo "[INFO] Wise choice :-) ";
               mkdir -p "$srcdir/test"
               tar xvf "$newpkglocation" -C "$srcdir/test"
               echo ""
               echo "[INFO] '"$app"' package installer file successfully extracted"
               break;;
             *) echo "[INFO] Nope? Alright." ; break ;;
      esac
    done
  fi

  # Prompt the user at the end of a successful build whether to install the newly created package. 
  # Has no effect on autobuilds because packages there are installed automatically.
  if [[ $installprompt = 1 ]] && [[ -z $autobuild ]] && [[ -n $newpkglocation ]] ; then
    while true ; do 
      echo
      echo "[NOTIFY] '$app' successfully built and installprompt is enabled in the bldpkg.conf file."

      read -r -p "[NOTIFY] Would you like to install/upgrade it? (y/N) " yn
      case "$yn" in 
        [Yy]* ) echo "[INFO] Wise choice :-) "
               upgradepkg --install-new "$newpkglocation"
               break;;
             *) echo "[INFO] Nope? Alright." ; exit 0 ;;
      esac
    done
  fi

  if [[ $pkgstatus = 0 ]]; then
    exit 0
  fi
}  

# This function will set the interrupt variable so prepbuildoutput can output the right build status 
# on receiving ctrl-c from the user during a manual build.
interruptoutput() {
  echo ""
  echo "[INFO] Caught Keyboard Interrupt"
  wasinterrupted="1"

  # Restore terminal colours
  echo -e "$colourd"

  # Terminate auditd daemon
  terminateauditd

  # If installprompt and extractprompt are set and the prompt is invoked after a successful build, hitting
  # ctrl-C will only set the above sm variable repeatedly and won't return user to the shell because
  # of the interrupt (SIGINT) trap set way below. Putting exit 0 is a decent way to get out of that prompt

  exit 0
}

# https://unix.stackexchange.com/questions/462392/bash-the-function-is-executed-twice
# https://stackoverflow.com/questions/9256644/identifying-received-signal-name-in-bash/9256709
# We use two traps to identify the signals, EXIT and INT. EXIT will invoke  'prepbuildoutput' function on any exit
# status >= 0. The script fail status  is determined by the above pkgstatus or any premature compile failure.
# The 'interruptoutput' function is invoked when the user sends CTRL-C aka SIGINT. The SIGINT trap does not work
# for auto builds, it has been added in the section build file too.
  
trap "prepbuildoutput" EXIT
trap "interruptoutput" INT

build

# End script
