#!/bin/bash # Part of the SMLinux distribution # http://git.pktsurf.in/smlinux # Copyright (c) 2022 PktSurf # # 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 # Find a better way to communicate to the build monitor, by, for example, # "catting" important build info into a unique build file and then # the build monitor sources from that file # Exit on any error set -e # All variable names are to be in upper-case, function names in lower-xase. # 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')" # Store the source directory path the build was initiated from srcdir="$PWD" # Get relative directory name from SRCDIR srcdirpath="$(basename $srcdir)" # Generate sha512sums in the build file genchecksum() { echo "Discarding old sha512sums from srcdirpath.SMBuild" sed -E -i \ -e '/^sha512sums=".*"$/d' \ -e '/^sha512sums="/,/"$/d' \ -e "/^sha512sums='.*'\$/d" \ -e "/^SHA512sums='/,/'\$/d" \ "$srcdirpath".SMBuild echo "Adding new sha512sums in $srcdirpath.SMBuild..." printf 'sha512sums="\n' >> "$srcdirpath".SMBuild # File types files=( *.tar.* *.zip *.t?z *.patch *.diff *.c *.h ) # Checksum digest to be used along with arguments checksumbinary="sha512sum" for buildfile in ${files[@]} ; do if [ -f "$buildfile" ] ; then $checksumbinary $buildfile >> "$srcdirpath".SMBuild fi done printf '"' >> "$srcdirpath".SMBuild echo "You may now run bldpkg again without any arguments" exit 0 } # Unset standard variables before sourcing from the build file unset app version build homepage download requires desc arch CFLAGS CXXFLAGS # If the first argument is "genchecksum", invoke the genchecksum function. # Else if the first argument is filename, then get the build extension of that file and source it, else throw an error # Else if no argument is given, get the basename of the directory and look for a matching package build file name. # If a package build file is found, source that file and that will initiate the build. if [ -n "$1" ] && [ "$1" = "genchecksum" ] ; then genchecksum elif [ -n "$1" ] ; then extension="${1##*.}" if [ -f "$1" ] && [ "$extension" == "SMBuild" ] ; then source "$1" else echo "Invalid file!" exit 1 fi elif [ -z $1 ] && [ -f "$srcdirpath".SMBuild ]; then source "$srcdirpath".SMBuild else echo "Please provide a build file as an argument" exit 1 fi # Sanitise the build file, get what we need. # If any of the following variables are not set in the build file, abort. for buildvariables in app version build homepage desc requires ; do if [[ ! "${!buildvariables}" ]] ; then echo "Variable \"${buildvariables}\" is not set. Please check your build file. Aborting!" exit 1 fi done # Check if build() function exists in the build file if [[ ! "$(grep '^build()' "$srcdirpath".SMBuild)" ]] ; then echo "build() function does not exist in your build file. Aborting!" exit 1 fi # Only verify source checksums if skipchecksum is not set in the build file if [ -z "$skipchecksum" ] ; then if [ -z "$sha512sums" ] ; then echo "sha512 checksums don't exist in srcdirpath.SMBuild !" echo "Please run bldpkg genchecksum" exit 1 fi eval sums=\"\$sha512sums\" echo "Verifying Checksums..." IFS=$'\n' for src in $sums; do echo "$src" | sha512sum -c done unset IFS echo "Looks good..." fi # Then source the configuration file holding all values if [ -f /etc/bldpkg.conf ] ; then source /etc/bldpkg.conf else echo "/etc/bldpkg.conf not found! Aborting!" exit 1 fi # Do a preliminary package dependency check if checkdependencies is set to 1 in bldpkg.conf if [ "$checkdependencies" == "1" ] ; then echo "Parsing $app 's dependency list..." for packagedep in $requires; do depcount="$(find /share/doc -name $packagedep.SMBuild | wc -l)" # If count is 1, we are ok if [ "$depcount" == "1" ] ; then echo "Found dependency $packagedep" # If count is 0, we exit, because we are in trouble elif [ "$depcount" == "0" ] ; then echo "Did not find dependency $packagedep ! Aborting!" 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 } # Display the package and its version we are building echo "[INFO] Building package $app version $version ..." sleep 0.5 # Check if $parenttmp is set and is a directory if [ -z "$parenttmp" ] ; then echo "parenttmp variable not set in /etc/bldpkg.conf. Aborting!" exit 1 elif [ ! -d "$parenttmp" ] ; then echo "parenttmp variable set to '"$tmpfsdir"' in /etc/bldpkg.conf is not a directory. Aborting!" exit 1 fi # Create the $parenttmp directory. This directory is used for everything related to the build process outside #the source directory $srcdir # First check if $parenttmp is a directory. If it does, check if it is writable. If not, create one. if [ -d "$parenttmp" ] ; then if ! touch "$parenttmp"/.smlinuxwritetest ; then echo "$parenttmp is not writable. Aborting!" exit 1 fi # Discard the test file rm -f "$parenttmp"/.smlinuxwritetest fi # Determine if $tmpfsdir is listed inside $protecteddirectories array if inarray "${parenttmp}" "${protecteddirectories[@]}" ; then echo "############ ATTENTION ############" echo "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 $commencedate | Building package # $currentpkgnumber / $totalpkgnumber: $app $version EOF else cat << EOF >> $parenttmp/BUILDMONITOR $commencedate | Building package $app $version 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" echo "[ERROR] installer file! Aborting!" exit 1 fi # Figure out the compression tool to be used based on the $pkgext variable set in bldpkg.conf. At the same time, # export the compressor options set for makepkg to import from the build environment. case "$pkgext" in tgz) compressor=gzip compressopts="$gzipopts" export compressopts ;; tbz) compressor=bzip2 compressopts="$bzipopts" export compressopts ;; tlz) compressor=lzip compressopts="$lzipopts" export compressopts ;; txz) compressor=xz compressopts="$xzopts" export compressopts ;; esac # Borrowed from slackware's installpkg utility echo -n "Validating $compressor...." if ! $compressor --help > /dev/null 2>&1 ; then echo "[FAILED]" exit 1 else echo "[OK]" fi # Validate the TMPFS directory. If usetmpfs is set to 1 and tmpfsdir variable is set, then check for # genuineness of the TMPFS directory. If it fails, declare a variable for the build summary. if [ "$usetmpfs" = "1" ] && [ -n "$tmpfsdir" ]; then if [ ! -d "$tmpfsdir" ] || [ ! -w "$tmpfsdir" ] \ || [ "$(findmnt -no TARGET $tmpfsdir)" != "$tmpfsdir" ] \ || [ "$(findmnt -no FSTYPE $tmpfsdir)" != "tmpfs" ]; then tmpfscheckfailed=1 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. Aborting!" 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 preservpackagedir to remove both build and package directories preservebuilddir=0 preservepackagedir=0 # Get the directory from the tmpfsdir variable for extracting the source 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 # 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. bldpkg.conf file" echo "[ERROR] is located in $buildvars ! Aborting!" exit 1 fi # Check if the symlinks are right if [ ! "$(echo "$PATH" | grep "$distccsympath")" ] ; then echo "[ERROR] $distccsympath not found in "'$PATH'"! Fix it please." 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! Aborting!" 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! Aborting!" exit 1 fi else echo "[ERROR] $f either does not exist or is not a symlink inside" echo "[ERROR] $distccsympath. Kindly fix this! Aborting!" 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="$(echo "$PATH" | sed "s@:$distccsympath@@g")" export PATH else export DISTCC_HOSTS DISTCC_IO_TIMEOUT fi else # Remove $distccsympath PATH="$(echo "$PATH" | sed "s@:$_distccsympath@@g")" export PATH 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. buildvars.conf" echo "[ERROR] file is located in $buildVARS . Aborting!" exit 1 fi if [ ! "$(echo $PATH | grep "$ccachesympath")" ] ; then echo "[ERROR] $ccachesympath not found in "'$PATH!'" Fix it please." 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 buildvars.conf. Aborting!" 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! Aborting!" exit 1 fi else echo "[ERROR] $f either does not exist or is not a symlink inside $ccachesympath" echo "[ERROR] Kindly fix this! Aborting!" 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="$(echo "$PATH" | sed "s@$ccachesympath:@@g")" export PATH fi else # Remove $ccachesympath PATH="$(echo "$PATH" | sed "s@$ccachesympath:@@g")" export PATH fi # Validate everything related to sccache if globalccache is set if [ "$globalsccache" = "1" ]; then if [ ! -x "$scacchebinpath" ] ; 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 buildvars.conf. buildvars.conf" echo "[ERROR] file is located in $buildvars . Aborting!" exit 1 fi if [ ! "$(echo $PATH | grep "$scacchepath")" ] ; then echo "[ERROR] $scacchepath not found in "'$PATH!'" Fix it please." exit 1 elif [ ! -d "$scacchepath" ] ; then echo "[ERROR] $scacchepath directory containing symlinks to ccache" echo "[ERROR] does not exist! Kindly create it and create symlinks" echo "[ERROR] based on instructions in bldpkg.conf. Aborting!" 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! Aborting!" 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! Aborting!" 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 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")" export PATH fi else # Remove $sccachepath PATH="$(echo "$PATH" | sed "s@$sccachepath:@@g")" export PATH 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="$(echo $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="$(echo $gccdebug $x8664cflags)" else CFLAGS="$x8664cflags" fi CXXFLAGS="$CFLAGS" export builddist CFLAGS CXXFLAGS else echo "[ERROR] Sorry! '$arch' CPU architecture not supported by SMLinux! Aborting!" 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 # will cause compilation to exit with 0 if uname -m does not match 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() { # $tmp, $pkg and $pkgdest are set in buildvars.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" "$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() { [ -z "$1" ] && return 1 local D=$(( $1 / 86400 )) local H=$(( ($1 - ($D * 86400)) / 3600 )) local M=$(( ($1 - ($D * 86400) - ($H * 3600)) / 60 )) local S=$(( $1 - ($D * 86400) - ($H * 3600) - ($M * 60) )) if [ "$D" -gt "0" ]; then echo -n "${D}d, ${H}h ${M}m ${S}s" else echo -n "${H}h, ${M}m ${S}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 # 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 "$1.run does not exist! Exiting" 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 remove static libraries for use inside build scripts # To be invoked inside a package build file. removestaticlibs() { echo "[INFO] Discarding static libraries..." find "$pkg" -name "*.a" -exec rm -fv {} \; } # 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] Just a min..." # Check if /lib64 was created inside $pkg if [ -d "$pkg/lib64" ] ; then echo "$app has /lib64 directory. Musl does not support multilib." echo "Please fix the build options and ensure the /lib64 is not created". echo "Aborting." exit 1 fi # Check if /usr and /sbin were created inside $pkg for directory in usr sbin ; do if [ -d "$pkg/$directory" ] ; then echo "$app has $directory directory which is a symlink to /bin on SMLinux." echo "Please fix the build options and ensure $directory is not created. Aborting!" exit 1 fi done if [ -d "$pkg/usr/var" ]; then echo "$app has created an incorrect post-install /usr/var directory for storing logs and variable information." echo "This stuff should go only inside /var. Kindly fix this. Aborting!" exit 1 fi echo "[INFO] Copying post-install files..." mkdir -p "$pkg/install" [ -e "$srcdir/doinst.sh" ] && cp "$srcdir/doinst.sh" "$pkg/install/" # 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 .la files similar to what slackware devs are doing in slackware-current, but in a more efficient manner :) echo "[INFO] Discarding any libtool archive (.la) files..." find "$pkg" ! -type d -name "*.la" -exec rm -v {} \; # Provide a copy of the package build file so that users know the build options that went into compiling the package install -Dm 644 "$srcdir/$app.SMBuild" "$pkgdocs/$app.SMBuild" # Now strip the binaries and shared libraries if [ -z "$debug" ] && [ "$debug" != "1" ]; then find "$pkg" -print0 | xargs -0 file -m /etc/file/magic/elf | \ grep -E "executable|shared object|statically linked" | grep "ELF" | \ cut -d: -f1 | xargs strip --strip-unneeded 2>/dev/null || true fi # And static libraries separately unconditionally find "$pkg" -print0 | xargs -0 file -m /etc/file/magic/archive | \ grep -E "current ar archive" | awk '{print $1}' | cut -d: -f1 | \ xargs strip --strip-unneeded 2>/dev/null || true # 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 # Finally create the package /bin/makepkg -l y -c n "$pkgdest/$app-$version-$arch-$build.$pkgext" pkgstatus=$? # Store package location inside this variable: packlocation="$pkgdest/$app-$version-$arch-$build.$pkgext" echo "[INFO] Leaving staging directory $pkg" # cd back to $srcdir when preservpackagedir 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" ]; 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 )" # 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="$(echo 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 build directory if preservpackagedir is set to 0 if [ "$preservepackagedir" = "0" ] ; then if ! inarray "${pkg}" "${protecteddirectories[@]}" ; then rm -rf "$pkg" fi fi } prepbuildsummary() { # Get the build completion time and store it in a variable finishdate="$(date '+%a, %d %b %Y, %T')" # 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="\e[41m" # Yellow when the build is interrupted coloury="\e[93m" # Green when the build succeeds colourg="\e[92m" # Cyan for the short questions colourc="\e[96m" # appNAME/version Colours colourv="\e[92m" # Restore to default colourd="\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 dstats="Yes" # 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" ] && \ [ -n "$usetmpfs" ] && [ "$usetmpfs" = "1" ] && \ [ -n "$tmpfsenabledforthispackage" ] && \ [ "$tmpfsenabledforthispackage" = "1" ] ; then cstats="Enabled globally but disabled due to tmpfs" elif [ -n "$globalccache" ] && [ "$globalccache" = "1" ] && \ [ -n "$usetmpfs" ] && [ "$usetmpfs" = "0" ] ; then cstats="Yes" 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" ] ; then bldtype="*DEBUG* build" 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 # 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 # Determine if the build was successful or not if [ "$pkgstatus" = "0" ] ; then bldstatus="$(echo -e "$colourg"'Successful! :-D' "$colourd")" if [ "$htmloutput" = "1" ] ; then cat << EOF >> $parenttmp/BUILDMONITOR.html $app $version$commencedate$finishdate$totaltimeSUCCEEDED EOF rm -f $parenttmp/BUILDING fi # Determine the compressed size packsize="$(du -bk "$packlocation" | awk '{print $1}')" # Determine the uncompressed size packusize="$(echo $packusize1)" elif [ "$wasinterrupted" = "1" ]; then bldstatus="$(echo -e "$coloury"'** INTERRUPTED ** :-/'"$colourd")" if [ "$htmloutput" = "1" ] ; then cat << EOF >> $parenttmp/BUILDMONITOR.html $app $version$commencedate$finishdate$totaltimeINTERRUPTED EOF rm -f $parenttmp/BUILDING fi else bldstatus="$(echo -e "$colourr"'!! FAILED !! :-('"$colourd")" if [ "$htmloutput" = "1" ] ; then cat << EOF >> $parenttmp/BUILDMONITOR.html $app $version$commencedate$finishdate$totaltimeFAILED EOF rm -f $parenttmp/BUILDING fi fi # Finally prepare the summary echo "" >> "$tempfile" echo -e ""$colourc"-------------------------------------------------------------------------------" >> "$tempfile" echo -e " BUILD SUMMARY FOR PACKAGE "$colourd"'"$colourv"\ "$app""$colourd"'"$colourc" VERSION "$colourd"'"$colourv""$version"'" "$colourc"TAG"$colourv" "'$build'"$colourd"" >> "$tempfile" echo -e ""$colourc"-------------------------------------------------------------------------------"$colourd"" >> "$tempfile" echo -e ""$colourc" Build Status:"$colourd" $bldstatus" >> "$tempfile" # Output the section name if autobuildtemp is set. This means we are running an autobuild. if [ -n "$autobuildtemp" ]; then echo -e ""$colourc" Build Section:"$colourd" $SECTION" >> "$tempfile" fi # 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 echo -e ""$colourc" Total Time: "$colourd" $totaltime" >> "$tempfile" elif [ -n "$totaltime" ] && [ -n "$packagetimeb" ]; then echo -e ""$colourc" Total Time: "$colourd" $totaltime ( $compiletimeb Compile ) + ( $packagetimeb Packaging )" >> "$tempfile" fi echo -e ""$colourc" Started:"$colourd" $commencedate" >> "$tempfile" echo -e ""$colourc" Stopped:"$colourd" $finishdate" >> "$tempfile" # If the package was built successfully, output the installer sizes if [ "$pkgstatus" = "0" ]; then #compressedsize="$(echo $(($packsize * 100 / $packusize)))" # 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@.@@')" echo -e ""$colourc" Source Size: "$colourd" Compressed: $srcdirsize"K", Uncompressed: $builddirsize"K"" >> "$tempfile" echo -e ""$colourc" Package Size: "$colourd" Uncompressed: $packusize"K", Compressed: $packsize"K" ("$compressedsize'%'")" >> "$tempfile" echo -e ""$colourc" Package Has: "$colourd" $totalfilecount files and $totalsymcount symlinks in $totaldircount directories" >> "$tempfile" fi # If ccache was used, output the current cache used size and max allocated size if [ "$globalccache" = "1" ] && [ "$ccache" != "0" ] && [ "$cstats" = "Yes" ]; then ccacheusedsize="$(ccache -s | grep "cache size" | head -n 1 | \ awk '{ $1=$2="" ; print $0}')" ccachetotalsize="$(ccache -s | grep "max cache size" | \ awk '{ $1=$2=$3="" ; print $0}')" echo -e ""$colourc" Ccache Used?"$colourd" "$cstats","$ccacheusedsize" /"$ccachetotalsize" Allocated" >> "$tempfile" else echo -e ""$colourc" Ccache Used?"$colourd" "$cstats"" >> "$tempfile" fi echo -e ""$colourc" Distcc Used?"$colourd" $dstats" >> "$tempfile" # If distcc was used, cut out --randomize and output rest of the DISTCC_HOSTS variable if [ "$globaldistcc" = "1" ] && [ "$distcc" != "0" ]; then echo -e ""$colourc" Distcc Args: "$colourd" $(echo "$DISTCC_HOSTS" | sed 's@--randomize@@')" >> "$tempfile" fi echo -e ""$colourc" TMPFS Used? "$colourd" "$tmpfsstate" $tmpfssavingsize" >> "$tempfile" echo -e ""$colourc" CPU Threads: "$colourd" $(echo $makeflags | sed 's@-j@@')" >> "$tempfile" echo -e ""$colourc" CFLAGS Used: "$colourd" $CFLAGS" >> "$tempfile" echo -e ""$colourc" Compressor: "$colourd" $compressor ($compressopts)" >> "$tempfile" echo -e ""$colourc" Build Type:" $colourd" $buildsys & $bldtype" >> "$tempfile" echo -e ""$colourc"-------------------------------------------------------------------------------"$colourd"" >> "$tempfile" # Output the build summary to the user on every build cat $tempfile # Once the output is shown, discard all temporary files rm -f $tempfile if [ -z "$autobuild" ] ; then rm -f $parenttmp/$app.{app,build,version} fi fi # Completion of the showsummary if/else check promptuser } promptuser() { # 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 "$packlocation" ] ; 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 "$packlocation" -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 "$packlocation" ] ; 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 "$packlocation" 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 prepbuildsummary can output the right build status on receiving # ctrl-c from the user during a manual build. interruptsummary() { echo "Caught Keyboard Interrupt..." wasinterrupted="1" # 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 'prepbuildsummary' function on any exit # status >= 0. The script fail status is determined by the above pkgstatus or any premature compile failure. # The 'interrruptsummary' 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 "prepbuildsummary" EXIT trap "interruptsummary" INT build