#!/bin/bash

# Part of the SMLinux distribution
# http://git.pktsurf.in/smlinux
#
# /bin/bldpkg version 0.104
# Bash script to build SMLinux-specific packages
#
# Copyright (c) 2022-2023 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.
############

set -e

err() {
  printf "**ERROR**\n$@\n"
  exit 1
}

info() {
  printf "[INFO] $@\n"
}

warn() {
  printf "[WARNING]\n$@\n"
}

# Determine whether we are using bash version 4 and later. If not, exit.
if [[ -z $BASH_VERSINFO ]] ; then
  err "Is this really a GNU bash shell?"
elif ((BASH_VERSINFO[0] < 4)) ; then
  err "Bldpkg requires a minimum of GNU bash shell version 4 to run"
fi

getbuildfileanddir() {
  srcdir="$PWD"
  buildfile="${srcdir##*/}.SMBuild"
}

# Function to error out the build in absence of a build file
nopackagebuildfileerror() {
  err "No package build file to source from!
Was expecting '$buildfile' to be present inside this directory '$PWD'.
Try -f <build_file> if your build file has a different name (Not recommended)
If you want to autobuild packages, use -a"
}

startdate() {
  # 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')
}

htmloutput() {
  # 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 $autobuildmode ]] ; 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
}
  
# 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: 

  -a         Switch mode to autobuild. Default is to build individual packages. 
             Requires sections variable in bldpkg.conf to be defined

  -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, insert them into the package build file and exit

  -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

  -r         Resume the build by skipping all commands in prepbuilddir and execute 
             build() function. Presumes that the package was completely extracted
	     in the build directory, and patches, if any, were applied and executes 
	     stuff inside build().

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

  -t         Build inside tmpfs directory

  -v         Enable verbose compile

  -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
}

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

  if [[ ! -f $buildfile ]] ; then
    err "No build file to validate from!"
  else 
    source $buildfile
  fi

  # Check to prevent a package from being built on an unsupported CPU architecture
  if [[ -n $apparch ]] ; then
    local detectedarch="$(uname -m)"
    if [[ $apparch != $detectedarch ]] ; then
      info "'$app' not supported on your current CPU '$detectedarch'. Build aborted."
      buildskipped=1
      return 0
    fi
  fi
  
  if [[ -z $buildskipped ]] ; then
    for reqvars in app version build homepage desc requires ; do
      [[ ! ${!reqvars} ]] && err "Required variable '$reqvars' is not set in $buildfile!"
    done

    # Check if 'Maintainer' comment exists
    if ! grep -q "^# Maintainer" "$buildfile" ; then
      err "Please provide a Maintainer name and email as a comment at the top of the build file"

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

    # Validate $version 
    elif ! echo "$version" | grep -E -q '[a-z0-9.]+'; then
      err "Only lower case, numeric characters and a period allowed in the 'version' variable in the build file."
  
    # Validate $homepage
    elif ! echo "$homepage" | grep -E -q '(https|http|ftp)://[^"]+' ; then
      err "Invalid URL in the 'homepage' variable in the build file."

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

    # Check if prepbuilddir() function exists in the build file
    elif ! grep -q '^prepbuilddir() {' "$buildfile" ; then
      err "'prepbuilddir()' function does not exist in your build file."

    # Check if build() function exists in the build file
    elif ! grep -q '^build() {' "$buildfile" ; then
      err "'build()' function does not exist in your build file."
    fi

    # Validate the download variable separately because it's optional
    if [[ -n $download ]] ; then
      if ! echo "$download" | grep -E -q '(https|http|ftp)://[^"]+' ; then
        err "Invalid URL in the 'download' variable in the build file."
      fi
    fi

    # Check integrity of files defined in sha512sums variable which is expected 
    # in nearly every single package build file
    if [[ -z $sha512sums ]] ; then
      err "Please run 'bldpkg -g' to add sha512sums into '$buildfile'!"
    fi
  fi
}

verifychecksums()  {
  eval sums=\"\$sha512sums\"
  info "Verifying SHA512 checksums against source files..."
  IFS=$'\n'
  for src in $sums; do
    if ! echo $src | sha512sum -c ; then
      err "Checksums failed to match!"
    fi
  done
  unset IFS
}

# Function to validate MAKEFLAGS passed on by getopts
validatemakeflags() {
  local makeflags="$1"
  local makeflags0="${makeflags/-j/}"
  if ! echo "$makeflags0" | grep -E -q '^[0-9]+$' ; then
    err "Invalid MAKEFLAGS '$makeflags0'!"
  fi
}

# Function for applying patches to the build in a more efficient manner
# For use inside a package build file
applypatch() {
  # Take patch file name as the first argument
  local patchfile="$1"

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

  # Get relative path of the patch file
  local relativepath=${patchfile##*/}
  info "Applying patch '$relativepath'.."

  # We use if/else to determine if the patch applied successfully
  if ! patch -p1 < "$patchfile" ; then
    err "Failed to apply patch file '$patchfile'"
  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
}

# Function to validate compilers.
# Usage: compilertestfile "$CC" "$CFLAGS" cc-test.c cc-test
compilertestfile() {
  local compileraddonflags="-O0 -Werror -static"
  local compiler="$1"
  local compilerflags="$2"
  local compilerfile="$3"
  local compileroutput="$4"

  printf "[INFO] Validating compiler '$compiler'... "

  # Inside a function, validate the compilers that have been set as CC and CXX in bldpkg.conf
  # A small C/CXX program is being used to print a random number

  # Generate a random number using shuf. We'll output this same number inside the C/CXX program.
  # If the numbers match, the compiler looks sane, else error out.
  local randomnum="$(shuf -i 100-9999 -n1)"

  cat << EOF > "$parenttmp/$compilerfile"
#include <stdio.h>
int main() { printf("$randomnum\n"); }
EOF

  $compiler $compilerflags -o "$parenttmp/$compileroutput" $compileraddonflags "$parenttmp/$compilerfile"
  local finaloutput="$($parenttmp/$compileroutput)"

  # Error out if the outputs don't match
  if [[ $finaloutput != $randomnum ]] ;then
    err " failed!"
  else
    rm "$parenttmp/$compileroutput"*
    printf " done\n"
  fi
}

# Function to start/stop sccache. 
# Usage: sccache start/stop
sccacheprocess() {
  local sccacheopt="$1"
  if [[ $sccache = 1 ]]; then
    if [[ $sccacheopt = "start" ]] ; then
      # Check whether sccache is running on TCP port 4226 via nc
      if ! nc -z -w 1 localhost 4226 > /dev/null 2>&1 ; then
        info "Starting sccache"
        $sccachebinpath --start-server
      fi
    elif [[ $sccacheopt = "stop" ]] ; then
      if nc -z -w 1 localhost 4226 > /dev/null 2>&1 ; then
        info "Stopping sccache"
        $sccachebinpath --stop-server
      fi
    fi
  fi
}

# Function to remove old package directories and make new ones. 
# For use inside a package build file.
mkandenterbuilddir() {

  # Define $pkgdocs. Rest is defined in bldpkg.conf.
  pkgdocs="$pkg/share/doc/$app-$version"

  # Remove any old pkg staging directory left by any previous build having same application name
  rm -rf "$pkg"
  
  # Now create all essential build-related directories
  mkdir -p "$tmp" "$pkg/install" "$pkgdocs" "$pkgdest" 

  info "Entering build directory $tmp"
  cd "$tmp"
}

# Function to fix permissions inside the build directory
# For use inside a package build file.
fixbuilddirpermissions() {
  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

  # Place an extraction.complete file so that we know at which point to resume our build
  touch ".$app-$version.extraction.complete" 
}

# 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"

  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
}

# Function to generate and insert sha512sums into the build file
genchecksum() {
  getbuildfileanddir

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

  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
      info "Adding $file"
      sha512sum "$file" >> "$buildfile"
    fi
  done

  printf '"' >> "$buildfile"

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

  info "You may now run 'bldpkg' again"
  exit 0
}

# Function to generate and insert a runit service file into a staging directory.
# For use inside a package build file
# Usage: preprunitservice -s chrony -d -f 
preprunitservice() {
  unset OPTIND OPTARG
  while getopts ':dfs:' option; do
    case "$option" in
      d) downfile=1 ;; # Add a 'down' file 
      f) finishfile=1 ;; # Add a 'finish' file
      s) servicefile="$OPTARG" ;; # Add main runit service file
    esac
  done

  mkdir -p $pkg/var/service
  install -Dm 755 "$srcdir/$servicefile.run" "$pkg/etc/service/$servicefile/run"
  (
    cd $pkg
    ln -sf "../../etc/service/$servicefile" "var/service/$service"
  )
  
  [[ -n $downfile ]] && touch "$pkg/etc/service/$servicefile/down"
  [[ -n $finishfile ]] && touch "$pkg/etc/service/$servicefile/finish"

  return 0
}

# Function to validate symlinks. 
# Usage: checkcompilersymlink symlink symlink_target
checkcompilersymlink() {
  local symlinkdir="$1"
  local symlinktgt="$2"

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

# Prepare a nice HTML file that can be used to view status of the build process from a web browser
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
}

# Function to extract a successfully built package inside the source directory
extractinfo() {
  info "Extracting package installer inside $srcdir/test..."
  mkdir -p "$srcdir/test"
  tar xvf "$newpkglocation" -C "$srcdir/test"
  info "'$app' package installer file successfully extracted"
}

# Function to prompt a user to extract a successfully built package inside the
# the source directory
promptuser() {
  # Extract package at the end of a build if autoextract is set to 1
  if [[ $autoextract = 1 ]] && [[ -z $autobuildmode ]] && [[ -n $newpkglocation ]] ; then
    extractinfo
  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 $autobuildmode ]] && [[ -n $newpkglocation ]] ; then
    read -r -p "[NOTIFY] Would you like the package installer of '$app-$version' to be extracted in your current directory '$srcdir' for examination? (y/N) " yn
      case "$yn" in
        Y|y) info "Wise Choice :-)" 
             extractinfo ;;
          *) info "Nope? Alright " ;;
      esac
  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 $autobuildmode ]] && [[ -n $newpkglocation ]] ; then
      read -r -p "[NOTIFY] Would you like the package installer of '$app-$version' to be installed/upgraded onto your system? (y/N) " yn
      case "$yn" in 
        Y|y) info "Wise choice :-) " ;
             upgradepkg --install-new "$newpkglocation" ;;
          *) info "Nope? Alright." ;;
      esac
  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
  info "Caught Keyboard Interrupt"
  wasinterrupted="1"

  # Restore terminal colours
  echo -e "$colourd"

  # Terminate sccache
  sccacheprocess stop

  return 0
}

# Function to perform post-compile tasks.
# For use 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 mkfinalpkg 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)

  info "Leaving build directory $tmp"
  info "Entering staging directory $pkg"
  cd "$pkg"
  info "Performing packaging tasks..."
   
  # Check if /lib64 was created inside $pkg
  [[ -d lib64 ]] && err "Multilib directory '/lib64' created inside '$pkg' not supported by musl C library."
   
  # Check if /usr and /sbin were created inside staging directory $pkg 
  # of its build file. Ignore their existence if $ignoreusr is set to 1 in the pkg build file
  if [[ -z $ignoreusr ]] ; then
    for directory in usr sbin ; do
      [[ -d $directory ]] && err "$pkg/$directory is a symlink to '/bin'. Fix your build options and ensure such a directory is not created"
    done
  fi

  info "Copying post-install files..."

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

  # If /share/applications directory exists but there is no doinst.sh in the source directory, create one using cat
  if [[ -d share/applications ]] && [[ ! -e $srcdir/doinst.sh ]] ; then
    info "Found /share/applications but couldn't find any doinst.sh in the source directory.
Creating one automatically that refreshes GTK cache."
    cat << EOF >> "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 share/applications ]] && [[ -e $srcdir/doinst.sh ]] && ! grep -q 'rc.gtk' "$srcdir/doinst.sh" ; then
    info "Found /share/applications but couldn't find any rc.gtk lines inside doinst.sh in the source directory.
Creating one automatically that refreshes GTK cache."
    cat << EOF >> "install/doinst.sh"
[[ -x /etc/rc.d/rc.gtk ]] && /etc/rc.d/rc.gtk
EOF
  fi

  # Compress and link manpages
  if [[ -d share/man ]]; then
    info "Compressing and linking man pages..."
    ( 
    cd "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
   
  # 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

  # Remove libtool archive files
  if [[ -d lib ]] && [[ "$(findlibtoolfiles | wc -l)" -ge 1 ]] ; then
    info "Discarding libtool archive (.la) files..."
    findlibtoolfiles -exec rm -v {} \;
  fi

  # If $preservestaticlibs is not set in the package build file, delete all static libraries
  if [[ -d lib ]] && [[ -z $preservestaticlibs ]] ; then
    if [[ "$(findarchivefiles | wc -l)" -ge 1 ]] ; then
      info "Discarding static libraries..."
      findarchivefiles | xargs rm -v
    fi
  fi

  # Now strip the binaries and libraries.
  if [[ -z $debug ]] ; then
    if [[ "$(findelffiles | wc -l)" -ge 1 ]] ; then
      info "Stripping ELF files..."
      # scanelf will, if a static archive is found, return object files inside that archive, so play safe
      findelffiles | cut -d: -f1 | uniq | xargs strip --strip-unneeded
    fi
  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}')

    # 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
  fi

  # Store package location inside this variable:
  newpkglocation="$pkgdest/$app-$version-$arch-$build.$pkgext"
  
  # https://gist.github.com/ruario/9672717
  # Create the SMLinux package
  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
      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
      [[ -f install/doinst.sh ]] && printf '\n' | cat - install/doinst.sh >> install/symlinks
      mv install/symlinks install/doinst.sh
    fi
  fi

  # Discard charset.alias
  [[ -f lib/charset.alias ]] && rm lib/charset.alias

  # Also discard the lib and install directory if it's empty but don't error out
  rmdir lib &> /dev/null || true
  rmdir install &> /dev/null || true

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

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

  # Terminate sccache
  sccacheprocess stop

  info "Leaving staging directory $pkg"

  # 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
  if [[ $showsummary = 1 ]] || [[ $htmloutput  = 1 ]] ; then
    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 [[ $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

  cd "$srcdir"

  # Delete the build directory if preservebuilddir is set to 0
  [[ $preservebuilddir = 0 ]] && rm -rf "$tmp"

  # Delete the package staging directory if preservepackagedir is set to 0
  [[ $preservepackagedir = 0 ]] && rm -rf "$pkg"
  
  if [[ $pkgstatus = 0 ]] ; then
    return 0
  fi
}

findarchivefiles() {
  find "lib" -type f -name "*.a"
}

findlibtoolfiles() {
  find "lib" -type f -name "*.la" "$@"
}

findelffiles() {
  # Only scan for static archives if preservestaticlibs is set to 1
  [[ $preservestaticlibs = "1" ]] && staticelfflag=",ET_REL"
  scanelf --archives --nobanner --recursive --symlink --format "%F" --etype "ET_EXEC,ET_DYN$staticelfflag" .
}

buildfilecleanup() {
  # Discard all temporary files
  rm -f "$parenttmp/BUILDING" 
}
  
prepbuildoutput() {
  # Start of the showsummary if/else check
  if [[ $showsummary = 1 ]]; then

    # 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

    # 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 $autobuildmode ]]; then
      # We are using the autobuild system
      buildsys="Autobuild"
    else
      # We compiled the package manually
      buildsys="Manual"
    fi

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


    # Determine if ccache was used. If we are compiling inside tmpfs, we are not using ccache at all, so set 
    # ccachestats accordingly in the build summary
    if [[ $ccache = 1 ]] && [[ $tmpfsenabledforthispackage = 1 ]] ; then
      ccachestats="Set to 1 but disabled due to tmpfs"

    elif [[ $ccache = 1 ]] && [[ $tmpfs = 0 ]] ; 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')
      ccachestats="Yes, $ccacheusedsize / $ccachetotalsize Allocated"

    elif [[ $ccache = 1 ]] && [[ -z $tmpfs ]] ; then
      ccachestats="Yes"

    elif [[ -z $ccache ]] || [[ $ccache = 0 ]] ; then
      ccachestats="No, disabled in bldpkg.conf"
    fi

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

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

    elif [[ $tmpfs = 1 ]] && [[ $tmpfsenabledforthispackage = 0 ]]; then
      tmpfsstate="*Not for this package* but enabled in bldpkg.conf"

    elif [[ $tmpfs = 1 ]] && [[ $tmpfscheckfailed = 1 ]]; then
      tmpfsstate="*NOPE, TMPFS DIRECTORY CHECK FAILED* but enabled in bldpkg.conf"

    else
      tmpfsstate="No, disabled in bldpkg.conf"
    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 inside a variable
    if [[ -n $totaltime ]] && [[ -z $packagetimeb ]]; then
      ttime="$totaltime"
    elif [[ -n $totaltime ]] && [[ -n $packagetimeb ]]; then
      ttime="$totaltime ( $compiletimeb Compile ) + ( $packagetimeb Packaging )"
    fi

    [[ -z $section ]] && section="None"

    # Output the build summary to the user on every build
    cat << EOF
$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 $section
$colourc Total Time:   $colourd  $ttime
$colourc Started:  $colourd      $commencedate
$colourc Stopped:  $colourd      $finishdate
$colourc Ccache Used?   $colourd $ccachestats
$colourc Distcc Used?  $colourd  $distccstats
$colourc TMPFS Used?  $colourd   $tmpfsstate $tmpfssavingsize
$colourc MAKEFLAGS:  $colourd    $MAKEFLAGS
$colourc CFLAGS Used:  $colourd  $CFLAGS
$colourc Compressor:  $colourd   $compressor ($compressoropts)
$colourc Build Type:  $colourd   $buildsys & $bldtype
$colourc ------------------------------------------------------------------------------- $colourd
EOF

  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
}

stage0prep() {
  # Check if $parenttmp is set and is a directory and can be written into
  if [[ -z $parenttmp ]] ; then
    err "parenttmp variable not set in bldpkg.conf."
  elif [[ ! -d $parenttmp ]] ; then
    err "parenttmp variable set to '$parenttmp' in bldpkg.conf is not a directory."
  elif ! touch "$parenttmp/.parenttmpwritetest" ; then
    err "Parent temp directory '$parenttmp' is not writable!"
  else
    # Discard the test file
    rm "$parenttmp/.parenttmpwritetest"
  fi

  printf "[INFO] Validating compressor '$compressor'..."
  mkdir -p "$parenttmp/.tar.testdir"
  touch "$parenttmp/.tar.testdir/.tar.testfile"

  if ! tar -cf - -C "$parenttmp/.tar.testdir" . | "$compressor" "$compressoropts" > "$parenttmp/.tar.testdir.$pkgext" ; then
    err "Failed to validate '$compressor' options! Please check your bldpkg.conf"
  fi

  printf " done\n"
  rm -r "$parenttmp/.tar.testdir"*

  # 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 [[ $tmpfs = 1 ]] && [[ -n $tmpfsdir ]]; then
    if [[ ! -d $tmpfsdir ]] ||  ! touch "$tmpfsdir/.tmpfswritetest"  \
      || [[ $(findmnt -no TARGET $tmpfsdir) != $tmpfsdir ]] \
      || [[ $(findmnt -no FSTYPE $tmpfsdir) != tmpfs ]]; then
        tmpfscheckfailed=1
    fi

    # Discard the file used to test the tmp directory
    rm "$tmpfsdir/.tmpfswritetest"

    # Check the tmpfsdir for stale directories from previous pkg builds. If found, issue a warning.
    if [[ $(find "$tmpfsdir" -type d -maxdepth 1 -name "*.src" -o -name "package-*" | wc -l) -gt 0 ]]; then
      if [[ ! -d $tmpfsdir/package-$app ]] || [[ ! -d $tmpfsdir/$app.src ]] ; then
        warn "TMPFS directory '$tmpfsdir' has stale directories from previous builds!"
      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 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}')
      [[ $swapcheck -lt $swapsize ]] &&  err "Insufficient swap size to build packages, advise increasing it."
    fi
  fi

  # Set the build and package staging directories. This is where package files that get "installed" go into, 
  # for example 'make install DESTDIR=$pkg' or 'DESTDIR="$pkg" ninja install'.

  # If tmpfs is set to 1 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 [[ $tmpfs = 1 ]] && [[ -z $tmpfscheckfailed ]] ; then
   
    # If $app is in the TMPFS exception list inside 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

      info "'$app' is in tmpfs exception list. Falling back to non-tmpfs directory"

      # We DO NOT compile inside tmpfsdir
      tmpfsenabledforthispackage=0

      # In the absence of tmpfs, we use the normal directory 
      tmp="$nontmpfsdir/$app.src"
      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="$tmpfsdir/package-$app"

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

  if inarray "${pkg}" "${protecteddirectories[@]}" ; then
    echo "############ ATTENTION ############"
    err "'pkg' VARIABLE IS SET TO '$pkg' WHICH IS A PROTECTED DIRECTORY! EXITING!"
  fi

  # Also check tmp directory in advance
  if inarray "${tmp}" "${protecteddirectories[@]}" ; then
    echo "############ ATTENTION ############"
    err "'tmp' VARIABLE IS SET TO '$tmp' WHICH IS A PROTECTED DIRECTORY! EXITING!"
  fi

  # Run a for loop to find compile and build-related files
  for requiredfile in "${rqfiles[@]}"; do
    if [[ ! -x $(type -p "$requiredfile") ]] ; then
      err "Could not find required program '$requiredfile'!"
    fi
  done
}

stage1prep() {
  # Apply CPU-specific compiler variables defined inside bldpkg.conf
  # noarch is set inside initfs, pkgtools, GTK themes and some other stuff.

  # Only set $arch if it's not set in the build file. We take the help of $HOSTTYPE variable set by the bash shell.
  [[ -z $arch ]] && arch="$HOSTTYPE"

  for compilervariable in CC CXX CFLAGS ; do
    [[ -z $compilervariable ]] && err "$compilervariable not set in bldpkg.conf!"
  done

  # Clean out CFLAGS if $arch is set to noarch
  if [[ $arch = noarch ]]; then
    CFLAGS=""
  elif [[ -n $debug ]]; then
    CFLAGS="$gccdebug $CFLAGS"
  fi

  CXXFLAGS="$CFLAGS"
  export CC CXX CFLAGS CXXFLAGS MAKEFLAGS

  # Validate available compilers. Quotes are important while passing C/CXXFLAGS
  compilertestfile "$CC" "$CFLAGS" cc-test.c cc-test
  compilertestfile "$CXX" "$CXXFLAGS" cxx-test.cpp cxx-test

  # Validate everything related to ccache if ccache is set
  if [[ $ccache = 1 ]]; then
    [[ ! -x $ccachebinpath ]] && err "Ccache binary does not exist/is not an executable!"

    checkcompilersymlink $ccachesymdirpath $ccachebinpath
  
    # Prepare $PATH and export it only if ccache is unset in the pkg build file
    PATH="$ccachesymdirpath:$PATH"
    export PATH
  fi

  # Validate everything related to sccache if ccache is set
  if [[ $sccache = 1 ]]; then
    [[ ! -x $sccachebinpath ]] && err "$sccachebinpath was either not found or is not an executable!"

    # We expect the rustc wrapper to be defined in $HOME/.cargo/config.toml
    if ! grep -q 'rustc-wrapper\ \= "\/bin\/sccache"' $HOME/.cargo/config.toml ; then
      err 'sccache is not defined as a rustc wrapper!
Please add the following to your '"$HOME/"'.cargo/config.toml:
[build]
rustc-wrapper = "/bin/sccache"'
    fi

    # Useful for rust-specific builds. 
    RUSTC_WRAPPER="$sccachebinpath"
    export RUSTC_WRAPPER

    sccacheprocess start
  fi

  # Validate everything related to distcc if distcc is set
  if [[ $distcc = 1 ]] ; then
    # Check if distcc exists and is an executable
    [[ ! -x $distccbinpath ]] && err "Distcc binary was either not found or is not an executable"

    checkcompilersymlink $distccsymdirpath $distccbinpath

    # Prepare $PATH and export DISTCC_HOSTS and DISTCC_IO_TIMEOUT only if distcc is unset in the pkg build file
    # netcat hosts inside $DISTCC_HOSTS by checking for an open port
    info "Validating distcc hosts..."
    # Check if we have nc
    if [[ ! -x /bin/nc ]] ; then
      warn "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@@')
      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
          warn "Distcc host '$host' is OFFLINE! Rewriting DISTCC_HOSTS"
          DISTCC_HOSTS=$(echo "$DISTCC_HOSTS" | sed "s@$host/[a-z0-9,]*@@")
        fi
      done
    fi
    
    PATH="$distccsymdirpath:$PATH"
    export DISTCC_HOSTS DISTCC_IO_TIMEOUT PATH
  fi

  # If verbosebuild is set in bldpkg.conf or if verbosity is set to 1 from getopts, set V and
  # VERBOSE environment variables for make and cmake build systems to pick up.
  if [[ -z $disableverbosebuild ]] ; then
    if [[ $verbosebuild = 1 ]] || [[ $getoptsverbosebuild = 1 ]] ; then
      V=1 
      VERBOSE=1
    else
      V=0
      VERBOSE=0
    fi
    export V VERBOSE
  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

  # Do a preliminary package dependency check if $checkdependencies is set to 1 in bldpkg.conf
  if [[ $checkdependencies = 1 ]] ; then
    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
        info "Found dependency '$packagedep'"
      # If count is 0, we exit, because we are in trouble
      elif [[ $depcount = 0 ]] ; then
        err "Did not find dependency '$packagedep'"
      # If count is greater than or equal to 2, we are in slightly less trouble
      elif [[ $depcount -ge 2 ]] ; then
        warn "Found multiple versions of '$packagedep' !"
        sleep 0.5
      fi
    done
  fi
}

autobuild() {
  trap "interruptoutput" INT

  # We don't need colours and we set arch on purpose so that the packages land in the right directory
  colours=0
  arch="$HOSTTYPE"

  # For loop to recurse through sections defined inside bldpkg.conf
  for section in "${sections[@]}" ; do

    pkgdest="$parenttmp/sml/packages/$arch/$section"
    logdir="$parenttmp/sml/sml-buildlogs/$arch/$section"
    mkdir -p $logdir

    # If the defined section does not exist, abort
    if [[ ! -d $section ]] ; then
      err "Section directory '$section' not found!"
    elif [[ ! -f "$section/.buildlist.$section" ]] ; then
      err "Section build list file '$section/.buildlist.$section' not found!"
    fi

    cd $section

    # This check compares a list of source directories with the list of the
    # packages in the build list and warns of any missing package names 
    # in either of the two.

    dirtempfile=$(mktemp $parenttmp/DIRECTORYNAMES."$section".XXXXXX)
    dirfiletemppath="$dirtempfile"
    dirlist=$(find . -type d -maxdepth 1 -mindepth 1 | sed 's@./@@' | sort > $dirfiletemppath)

    packtempfile=$(mktemp $parenttmp/BUILDFILENAMES."$section".XXXXXX)
    packfiletemppath="$packtempfile"
    sort .buildlist.$section > $packfiletemppath
  
    directorycount="$( wc -l < $dirtempfile )"
    buildlistcount="$( wc -l < $packtempfile )"

    # Get number of total packages
    totalpkgnumber="$(wc -l < .buildlist.$section)"

    if diff -u "$dirfiletemppath" "$packfiletemppath" > /dev/null 2>&1 ; then
      diffstatus="0"
    else
      diffstatus="1"
    fi

    if [ "$diffstatus" != "0" ]; then 
      warn "In section '"$section"', the number of packages in the hidden file '.buildlist."$section"' is different to the number of package directories. Some packages may not have been added to this file/section directory. They are listed below:"

      diff -u "$dirfiletemppath" "$packfiletemppath" || true
      diff -u "$packfiletemppath" "$dirfiletemppath" || true

      warn "Building anyways :-) "
      sleep 2
    fi
    rm -f $packfiletemppath $dirfiletemppath

    while IFS="" read -r package || [[ -n $package ]] ; do
      (
      SECONDS=0
      currentpkgnumber="$(grep -Ewn "^$package" .buildlist.$section | cut -d: -f 1)"

      # We need this variable to switch back to the section directory inside the loop
      startdate
      currentpwd="$PWD"

      cd $package

      # Get the name of the buildfile and validate it
      getbuildfileanddir
      validatebldfile "$buildfile"

      if [[ -z $buildskipped ]] ; then

        # Check if package installer exists or is installed on the system
        # If the package installer does not exist in the set pkg destination, build it
        if [ ! -f $pkgdest/$app-$version-*-$build.* ] ; then
          info "Building package '$app' version '$version' ..."
          htmloutput
          stage0prep
          verifychecksums
          prepbuilddir
          stage1prep

          trap "prepbuildoutput" EXIT
          build

          # If the package is not installed in the system, install it
          if [ ! -f /var/log/packages/$app-$version-*-$build ] ; then
            upgradepkg --install-new $pkgdest/$app-$version-*-$build.*
          else
            info "'$app-$version' already installed, skipping it"
          fi
        elif [ ! -f /var/log/packages/$app-$version-*-$build ] ; then
          upgradepkg --install-new $pkgdest/$app-$version-*-$build.*
        else
          info "Already built '$app-$version', skipping it"
        fi
      fi

      cd $currentpwd
      )
    done < ".buildlist.$section"

    cd ..

  done
}

manualbuild() {
  startdate
  trap "interruptoutput" INT

  # Display the package and its version we are building or resuming
  if [[ -z $buildskipped ]] ; then
    if [[ -z $resumepkgbuild ]] ; then
      info "Building package '$app' version '$version' ..."
    else
      info "Resuming build of package '$app' version '$version' ..."
    fi
    
    htmloutput
    stage0prep

    # If $tmp exists, get the path to the $.app-$version.extraction.complete file
    [[ -d $tmp ]] && buildresumepath="$(find $tmp -type f -name .$app-$version.extraction.complete)"

    # Prompt the user 
    if [[ -f $buildresumepath ]] && [[ -z $resumepkgbuild ]] && [[ $autoresumepkgbuild = 1 ]] ; then
      read -r -p "[NOTIFY] Would you like to resume building? " yn
      case "$yn" in 
        N|n|No|no) info "Nope? Alright." ; 
                 unset resumepkgbuild ;;
              *) info "Wise choice :-) "
                 resumepkgbuild=1 ;;
      esac
    fi

    # If $resumepkgbuild variable is not set in getopts, only then execute prepbuilddir variable
    if [[ -z $resumepkgbuild ]] ; then
      verifychecksums
      prepbuilddir
    fi

    stage1prep
    trap "prepbuildoutput" EXIT
    build

    # If $resumepkgbuild is set either in getopts or from the user prompt above, execute mkandenterbuilddir 
    # function to enter the build directory. This is being done because mkandenterbuilddir is part of prepbuilddir 
    # function in the build file and ignoring prepbuilddir will not cause mkandenterbuilddir to be invoked 
    # separately unless the build system is told to.
    if [[ -n $resumepkgbuild ]] ; then
      mkandenterbuilddir

      # fixbuilddirpermissions places a file ".$app-$version.extraction.complete". Only resume the build if that file exists
      if [[ ! -f $buildresumepath ]] ; then
        err "Can't resume build of '"$app"'! Are you certain the source was extracted completely?" 
      else
        cd ${buildresumepath%/*}
      fi
    fi
  fi
}

# Source the main configuration file holding all necessary values
if [[ -f $HOME/.bldpkg.conf ]] ; then
  source "$HOME/.bldpkg.conf"
elif [[ -f /etc/bldpkg.conf ]] ; then
  source /etc/bldpkg.conf 
else 
  err "Bldpkg configuration file '/etc/bldpkg.conf' not found!"
fi

# Error out if ccache and sccache are set in the /etc/bldpkg.conf
if [[ $ccache = 1 ]] && [[ $sccache = 1 ]] ; then
  err "Please only enable either ccache or sccache in bldpkg.conf at the same time, not both."
fi

# While loop for providing handy arguments to users. Some will override bldpkg.conf.
while getopts ':adef:ghj:o:rstvx' option; do
  case "$option" in
    a) autobuildmode=1 ;;
    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 ;
       [[ -z $autobuildmode ]] && genchecksum ;;
    h) help ;;
    j) custommakeflags="$OPTARG" ;;
    o) origbuildfile="$OPTARG" ;;
    r) resumepkgbuild=1 ;;
    s) showsummary=1 ;; # Show build summary at the end of the build irrespective of the build status
    t) tmpfs=1 ;;
    v) getoptsverbosebuild=1 ;;
    x) set -x ;; # 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 then be validated and sourced.
if [[ $OPTIND = 1 ]] ; then
  getbuildfileanddir
  if [[ ! -f $buildfile ]] ; then
    nopackagebuildfileerror
  elif ! validatebldfile "$buildfile" ; then
    err "'$buildfile' build file validation failed!"
  else
    manualbuild
  fi

# If OPTIND is greater than 1, perform various checks and actions
elif [[ $OPTIND -gt 1 ]] ; then
    
  # If MAKEFLAGS and custommakeflags is set, let custommakeflags take precedence
  if [[ -n $custommakeflags ]] ; then
    validatemakeflags $custommakeflags
    MAKEFLAGS="-j$custommakeflags"

  elif [[ -n $MAKEFLAGS ]] ; then
    validatemakeflags $MAKEFLAGS

  elif [[ -z $MAKEFLAGS ]] ; then
    # Or fetch the number from nproc
    MAKEFLAGS="-j$(nproc --all)"
  fi

  if [[ $autobuildmode = 1 ]] ; then
    # Disallow certain variables that may be passed on by getopts
     if [[ -n $autoextract ]] ; then
       err "-e option not allowed in autobuild mode"
     elif [[ -n $genchecksum ]] ; then
       err "-g option not allowed in autobuild mode"
     elif [[ -n $origbuildfile ]] ; then
       err "-o option not allowed in autobuild mode"
     elif [[ -n $setbuildfile ]] ; then
       err "-f option not allowed in autobuild mode"
     elif [[ -n $resumepkgbuild ]] ; then
       err "-r option not allowed in autobuild mode"
     fi

    autobuild

  elif [[ -z $autobuildmode ]] ; then
    # Get the name of the default source file
    getbuildfileanddir

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

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

      elif ! validatebldfile "$origbuildfile" ; then
        err "Alternative build file validation failed!"
      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
        err "'$buildfile' validation failed!"
      else
        manualbuild
      fi

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

    # 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
        err "'$buildfile' validation failed!"
      else
        manualbuild
      fi

    # If even that file is not found, throw an error and exit
    else
      nopackagebuildfileerror
    fi
  fi
fi

# End of script
