Pages

Saturday, July 1, 2017

GNU - Build preparation


Warning
OUT OF DATE
It's no doubt very convenient and trustworthy to install new software through IPS or even the legacy SVr4 packages. But when those options are not available fortunately there's still a chance for the GNU build automation, which although not as convenient can also perform a good job.

The well-known essentials of GNU build automation end-user operation work-flow was the subject of another post, in which I hopefully confirmed to work reasonably with a few adjustments under Solaris 11.3 and in which I successfully integrated some well-known ZFS essential advantages, such as source-tree snapshots.

In this post I hope to somewhat complement what's been discussed by presenting a suggestion of Shell script to hopefully simplify and avoid some common mistakes in the preparation of the GNU build automation work-flow environment.

For instance, assume that an application source code (tarball app-1.0.0.tar.gz) is to be built for both 32-bit and 64-bit targets using GNU tools and that the build operation is to take place at a local staff /software/build with the results installed locally under /software/prototype.

NOTE
I assume that the source code tarball is usually named under the following convention: name-version.tar.compression.

But there are cases this convention varies, for instance. node.js prepends a v before version, which slight variation, in this particular case, is a minor annoyance and fortunately won't break the following script. After the script is run, if desired, one can rename the datasets accordingly in order to normalize things, by removing the v before version number.

But there are other cases such as maven (apache-maven-3.5.3-src.tar.gz) where the tarball name would break the script. I could attempt fixing it, but as there are so many unpredictable possibilities this would certainly become a unmanageable.
 
The general solution is to conform the tarballs' names to my original assumption, usually with a simple manual procedure (as root) of extracting the archive and then storing everything back under a conformant archive name. For instance:
 
# cd /tmp
# gtar xf .../maven/3.5.3/apache-maven-3.5.3-src.tar.gz
# gtar czf maven-3.5.3.tar.gz maven-3.5.3
# cp maven-3.5.3.tar.gz .../maven/3.5.3/

 
As an alternative while recreating the tarball, one can attempt higher compression rates with j (for a .bz2) or J (for a .xz) instead of just z (for a more common .gz compressed file). On this particular example the sizes become: 2.6M (.gz), 2.2M (.bz2) and 1.8M (.xz).

A possible script (gnu-build-preparation) could be:

#!/bin/bash -

################################################################

if [[ -z "$1" ]];
then

  echo
  echo "Usage: $0 <tarball>"

  echo
  exit 1
else
  if ! [[ -f "$1" ]];
  then

    echo
    echo "$1 not found."

    echo
    exit 1
  fi
fi

 
################################################################

DS_BASE=rpool

DS_SOFTWARE=${DS_BASE:+$DS_BASE/}software
DS_BUILD=build
DS_PROTOTYPE=prototype


################################################################
 

TAR=$(realpath "$1")

A=$(basename $1 |sed 's/\(.*\)-.*/\1/')
V=$(basename $1 |sed 's/.*-\(.*\)\.tar.*/\1/')

echo

echo Processing $TAR
echo
echo ------------------------------------
echo "App: $A"
echo "Ver: $V"
echo ------------------------------------

echo
echo In the process, the following ZFS datasets will be created:

echo
echo "  $DS_
SOFTWARE/$DS_BUILD/$A"
echo "  $DS_
SOFTWARE/$DS_BUILD/$A/$A-$V"
echo "  $DS_
SOFTWARE/$DS_BUILD/$A/$A-$V-gnu32"
echo "  $DS_
SOFTWARE/$DS_BUILD/$A/$A-$V-gnu64"
echo
echo "  $DS_
SOFTWARE/$DS_PROTOTYPE/$A"
echo "  $DS_
SOFTWARE/$DS_PROTOTYPE/$A/$A-$V"
echo "  $DS_
SOFTWARE/$DS_PROTOTYPE/$A/$A-$V/gnu32"
echo "  $DS_
SOFTWARE/$DS_PROTOTYPE/$A/$A-$V/gnu64"
echo
read -p "Enter \"y\" to proceed: " -r
if ! [[ "$REPLY" = "y" ]] && ! [[ "$REPLY" = "Y" ]];
then
  echo "Exiting..."
  exit 1
fi

################################################################

function dataset-exists()
{
  zfs list -H "$1" 2>/dev/null
  local rv=$?
  echo $rv
}

################################################################

function dataset-get-mountpoint()
{
  echo $(zfs get -H -o value mountpoint "$1" 2>/dev/null)
}

################################################################

function dataset-assert-mounted()
{
  if [[ -z $(dataset-get-mountpoint "$1") ]]; then
    echo "Unexpected: \"$1\" doesn't seem to be mounted."
    exit 1
  fi
}

dataset-assert-mounted "$DS_
SOFTWARE"
dataset-assert-mounted "$DS_
SOFTWARE/$DS_BUILD"
dataset-assert-mounted "$DS_
SOFTWARE/$DS_PROTOTYPE"

################################################################

function dataset-app-version()
{
  if [[ $(dataset-exists "$1") -eq 1 ]]; then
    echo "Creating $1..."
    zfs create "$1"
  else
    local MP=$(dataset-get-mountpoint "$1")
    if [[ $(basename "$MP") == "$A" ]]; then
      echo "No need to create $1."
    else
      echo
      echo "$1 mounted as $MP."
      echo "No further action can be safely taken."
      echo "Exiting..."
      exit 1
    fi
  fi
}

echo

  
dataset-app-version "$DS_
SOFTWARE/$DS_BUILD/$A"
dataset-app-version "$DS_
SOFTWARE/$DS_BUILD/$A/$A-$V"

################################################################

echo

DS_APPLICATION="$DS_
SOFTWARE/$DS_BUILD/$A"
DS_APPLICATION_VERSION="$DS_APPLICATION/$A-$V"
MP=$(dataset-get-mountpoint "$DS_APPLICATION_VERSION")

if [[ $(ls -A "$MP" 2>/dev/null) ]]; then
  echo
  echo "Directory $MP not empty!"
  echo "No further action can be safely taken."
  echo "Exiting..."
  exit 1
fi

(cd "$MP/.."; gtar xf "$TAR")

if ! [[ $(ls -A "$MP" 2>/dev/null) ]]; then
  echo
  echo "Directory $MP shouldn't be empty!"
  echo "No further action can be safely taken."
  echo "Exiting..."
  exit 1
fi

zfs snapshot "$DS_APPLICATION_VERSION@source"
zfs set readonly=on "$DS_APPLICATION_VERSION"

################################################################

function set-target()
{
  local T=$1

  # This may be inappropriate in some cases.
  # Some "packages" require/prefer just an empty dataset.
  zfs clone "$DS_APPLICATION_VERSION"@source \

            "$DS_APPLICATION_VERSION-$T"
 
  # This may be too soon.
  # The "package" may need some manual fix before @start
  zfs snapshot "$DS_APPLICATION_VERSION-$T"@start
}

set-target gnu32
set-target gnu64

################################################################

zfs list -H -r -t all -o name "$DS_APPLICATION"


################################################################
 
#
# Create the final resting place of the results.
# Use an independent ZFS pool/dataset, not rpool/VARSHARE.
# At end consider snapshoting, just in case.
# Could be a staging area for packaging.
#

 
DS_PROTOTYPE="$DS_
SOFTWARE/$DS_PROTOTYPE/$A"
DS_PROTOTYPE_VERSION="$DS_PROTOTYPE/$A-$V"

 
echo
echo "Creating "$DS_PROTOTYPE" subtree."
echo

function create-staging-area()
{
  if [[ $(dataset-exists "$1") -eq 1 ]]; then
    zfs create -p "$1"
  fi
}

create-staging-area "$DS_PROTOTYPE_VERSION"
create-staging-area "$DS_PROTOTYPE_VERSION/gnu32"
create-staging-area "$DS_PROTOTYPE_VERSION/gnu64"

zfs list -H -r -t all -o name "$DS_PROTOTYPE"


################################################################
  
echo
echo "Creating pre-configuration script."

 
cat > "$MP/../setenv" <<EOF
#
# HOSTTYPE=$HOSTTYPE
# OSTYPE=$OSTYPE
# MACHTYPE=$MACHTYPE
#
# Up to Solaris 11.3 the default is 32-bits.
# MACHTYPE is typically i386-pc-solaris2.11
#
# Since Solaris 11.4 Beta the default is 64-bits.
# MACHTYPE is x86_64-pc-solaris2.11

#
# It's advisable not to override the above env vars
# buy you experiment with the --build or --target options
# to the standard GNU's configuration script: configure.
# (they are important to properly organize the built artifacts) 

# Bottom line it seems that the -m option to the flags
# CFLAGS, CXXFLAGS and LDFLAGS always prevail after all.
# (in terms of the actual bitness of the built artifacts)
#

if [[ -z "\$1" ]];
then
  echo "Usage: source setenv <bit-target>"
  return 1
else
  if [[ "\$1" -ne 32 ]] && [[ "\$1" -ne 64 ]];
  then
    echo "Valid bit-targets: 32 or 64."
    return 1
  fi
fi

# Despite setting bitness as shown next
# DON'T override HOSTTYPE and MACHTYPE

# Just remind the proper --build option

BITS=\$1


echo
case $BITS in
32)
  echo --prefix=/software/prototype/.../gnu32
  echo --build=i386-pc-solaris2.11
  ;;
64)
  echo --prefix=/software/prototype/.../gnu64
  echo --build=x86_64-pc-solaris2.11
  ;;
esac

function add-path()
{
  local COMPONENT="\$1"

  if [[ -d "\$COMPONENT" ]] ;
  then
    ! [[ "\$PATH" =~ "\$COMPONENT" ]] && \
      export PATH="\$COMPONENT:\$PATH"
  fi
}

function add-pkgconfig-path()
{
  local COMPONENT="\$1"
  local TAIL="\${PKG_CONFIG_PATH:+:\$PKG_CONFIG_PATH}"

  if [[ -d "\$COMPONENT" ]] ;
  then
    ! [[ "\$TAIL" =~ "\$COMPONENT" ]] && \
      export PKG_CONFIG_PATH="\$COMPONENT\$TAIL"
  fi
}

function extend-env()
{
  local BASE="\$1/gnu\$BITS"

  add-path "\$BASE/bin"
  add-pkgconfig-path "\$BASE/lib/pkgconfig"
}

add-path /usr/gnu/bin

#
# Other PATH and PKG_CONFIG_PATH settings.
# Put entries in reverse order of dependency.
# (following my PATH building suggestion)
# The samples below are pre-Solaris 11.4 Beta.
#

# ...
# extend-env /opt/tcl-8.5.19
# extend-env /opt/automake-1.15
# extend-env /opt/autoconf-2.69
# extend-env /opt/m4-1.4.18
# extend-env /opt/libtool-2.4.6

echo

# In general, not a good idea; evaluate.
# export CONFIG_SHELL=/bin/bash
echo CONFIG_SHELL=\$CONFIG_SHELL

echo
 

#
# The following compilers and linker overrides
# may be troublesome, specially the -m and -std options
# as it may be very source dependent; evaluate.
# The bare -std minimums should be gnu89 and gnu+03;
# since Solaris 11.4 Beta gnu11 and gnu++11 seem Ok.
#

FLAGS="-m\$BITS -march=core2"

export CC=/usr/bin/gcc
export CFLAGS="\$FLAGS -std=gnu89"
echo CC=\$CC CFLAGS=\$CFLAGS

echo

export CXX=/usr/bin/g++
export CXXFLAGS="\$FLAGS -std=gnu++03"
echo CXX=\$CXX CXXFLAGS=\$CXXFLAGS

echo

export LD=/usr/bin/ld

export LDFLAGS="\$FLAGS"
echo LD=\$LD LDFLAGS=\$LDFLAGS 

echo

echo PATH=\$PATH

echo

echo PKG_CONFIG_PATH=\$PKG_CONFIG_PATH

unset CF

EOF


cat > "$MP/../unsetenv" <<EOF

function cleanup-env()
{
  unset CONFIG_SHELL

  unset CC
  unset CFLAGS

  unset CXX
  unset CXXFLAGS


  unset LD
  unset LDFLAGS

  unset PKG_CONFIG_PATH
  export PATH=/usr/bin:/usr/sbin

  unset -f add-path
  unset -f add-pkgconfig-path
  unset -f extend-env
  unset -f cleanup-env
}

cleanup-env
 

EOF

touch "$MP/../NOTES"

For instance, assuming that a non-priviledged user has been delegated appropriate ZFS permissions on rpool/software/build and rpool/software/prototype in addition to the ownership and appropriate mode for the respective mountpoints, the output of the above script could be as follows:

$ pwd
/software/build

$./gnu-build-preparation ../source/.../ruby-2.5.0.tar.gz

Processing /software/source/.../ruby-2.5.0.tar.gz


------------------------------------
App: ruby
Ver: 2.5.0
------------------------------------


In the process, the following ZFS datasets will be created:

 
 
rpool/software/build/ruby
 
rpool/software/build/ruby/ruby-2.5.0
 
rpool/software/build/ruby/ruby-2.5.0-gnu32
 
rpool/software/build/ruby/ruby-2.5.0-gnu64
 
 
rpool/software/prototype/ruby
 
rpool/software/prototype/ruby/ruby-2.5.0
 
rpool/software/prototype/ruby/ruby-2.5.0/gnu32
 
rpool/software/prototype/ruby/ruby-2.5.0/gnu64

Enter "y" to proceed: y

Creating rpool/
software/build/ruby...
Creating rpool/
software/build/ruby/ruby-2.5.0...
  
rpool/software/build/ruby
rpool/software/build/ruby/ruby-2.5.0
rpool/software/build/ruby/ruby-2.5.0@source 
rpool/software/build/ruby/ruby-2.5.0-gnu32 
rpool/software/build/ruby/ruby-2.5.0-gnu32@start
rpool/software/build/ruby/ruby-2.5.0-gnu64 
rpool/software/build/ruby/ruby-2.5.0-gnu64@start

Creating
rpool/software/prototype/ruby subtree.
 
rpool/software/prototype/ruby 
rpool/software/prototype/ruby/ruby-2.5.0
rpool/software/prototype/ruby/ruby-2.5.0/gnu32
rpool/software/prototype/ruby/ruby-2.5.0/gnu64

Creating pre-configuration script.