#!/bin/bash #------------------------------------------------------------------------------ set -u #============================================================================== # Internal Functions #------------------------------------------------------------------------------ # Function msg ($message) # # Input: # Arg 1, $message, message to display. # # Sample Usage: # msg "This is a good message." #------------------------------------------------------------------------------ function msg () { echo -e "$*"; } #------------------------------------------------------------------------------ # Function bail ($message, [$exitCode]) # # Input: # Arg 1, $message, error message to display. # Arg 2, $exitCode, exit status. Default is 1. # # Sample Usage: # bail "Something went wrong" #------------------------------------------------------------------------------ function bail () { msg "Error: ${1:-Unknown Error}\n" msg "Aborting after $(($SECONDS/3600)) hours $(($SECONDS%3600/60)) minutes $(($SECONDS%60)) seconds.\n" exit ${2:-1} } #------------------------------------------------------------------------------ # Function cmd ($command, [$desc], $[log], [$showLog]) # # Input: # Arg 1, $command, the command to run. # Arg 2, $description, optional description to show before running the # command # Arg 3, $log, name of file to log to, e.g. 'make.log'. Can be relative # or absolute path. # Arg 4, $showLog: Determines whether to cat the output of $log. Values: # 0=never, 1=always, 2=on non-zero exit of command only. # The default is 1 (always). # # Usage Samples: # cmd "ls" "Listing directory" || bail "Failed to list dir." # cmd "make test" "Building and Testing" make_test.log 2 #------------------------------------------------------------------------------ function cmd () { declare command="${1:-echo}" declare desc="${2:-}" declare log="${3:-}" declare showLog=${4:-1} declare -i exitCode [[ -n "${desc:-}" ]] && msg "$desc" if [[ $NO_OP -eq 0 ]]; then if [[ -n "$log" ]]; then msg "Executing: $command, logging to $log." $command > $log 2>&1 exitCode=$? if [[ $showLog -ne 0 ]]; then if [[ $showLog -eq 1 ]]; then msg "Output of $log:\n" cat $log elif [[ $exitCode -ne 0 ]]; then msg "Command failed. Output of $log:\n" cat $log fi fi else msg "Executing: $command" $command exitCode=$? fi else msg "NO_OP: Would run: $command" exitCode=0 fi return $exitCode } #------------------------------------------------------------------------------ # Function: terminate function terminate { # Disable signal trapping. trap - EXIT SIGINT SIGTERM # Stop logging. [[ "${LogFile}" == off ]] || msg "LogFile is $LogFile\n${H1}\n" # With the trap removed, exit. exit $OverallReturnStatus } #------------------------------------------------------------------------------ # Function: usage (required function) # # Input: # $1 - style, either -h (for short form) or -man (for man-page like format). # The default is -h. # # $2 - error message (optional). Specify this if usage() is called due to # user error, in which case the given message displayed first, followed by the # standard usage message (short or long depending on $1). If displaying an # errror, usually $1 should be -h so that the longer usage message doesn't # obsure the error message. # # Sample Usage: # usage # usage -h # usage -man # usage -h "Incorrect command line usage." #------------------------------------------------------------------------------ function usage { declare style=${1:--h} declare errorMessage=${2:-Unset} if [[ $errorMessage != Unset ]]; then echo -e "\n\nUsage Error:\n\n$errorMessage\n\n" fi echo "USAGE for $ThisScript v$Version: $ThisScript [-e <perl_root> | -r <perl_root> [-p <perl_rel>] [-d <perl_config_defs>]] [-a] [-b <p4perl_branch>] [-a <api_rel>] [-C] [-s] [-L <log>] [-n] [-D] or $ThisScript [-h|-man] " if [[ $style == -man ]]; then echo -e " DESCRIPTION: This script downloads Perl source code, the Perforce C++ API in binary form, and P4Perl source code. It builds, tests, and installs Perl, and then builds, tests, and installs P4Perl. Optionally, if the '-e /path/to/my/perl' flag is used, an existing Perl installation is updated with P4Perl. In that case, P4Perl source code and the Perforce C++ API in binary form are downloaded, and only P4Perl is built and installed. OPTIONS: -e <path_to_existing_perl_root> Specify the path to the install root of an existing existing Perl installation. This directory will typically include bin, man, lib, and possibly other directories. The '-e' option cannot be used with '-r' or '-p'. -r Specify the Perl install root. The default is $InstallRoot. The user running this script may require sudo priviledges depending on the install root used. Install will be attempted without sudo first. If the install fails, it will be attempted a second time with sudo (unless already running as root). Cannot be used with '-e'. -p Specify the Perl version, e.g. 5.12.4. The default is $PerlRel. Cannot be used with '-e'. -d Specify Perl configure definitions to pass to the Configure utility. If there are more than one, the should be a quoted, space-delimited list. Sample value: -d '-Dusethreads'. This would translate into this Perl Configure command: ./Configure -des -Dprefix=/usr/local/perl -Dusethreads -a Specify the Perforce API to use. The default is $P4APIRel. -b Specify the branch of P4Perl to acquire source from, e.g. '-b r16.1' to build the 2016.1 release, or '-b main' to build from the Perl mainline. -C Specify -C to remove the working directory, including the downloads directory. This can be used if ensure a clean start point of multiple iterations are needed to achieve a successful build. -f Force thru, ignoring failed tests. By default, if test suites for Perl or P4Perl fail, this script aborts, making it harder (by design) to accidentally ignore the test failures. In some cases, test failures may be deemed safely ignorable; this flag is for those scenarios. -s Specify '-s' to build and install Perl, but skip P4Perl installation. -L <log> Specify the path to a log file, or the special value 'off' to disable logging. By default, all output (stdout and stderr) goes to: ${LogFile}. -n No-Op. Prints commands instead of running them. -D Set extreme debugging verbosity. HELP OPTIONS: -h Display short help message -man Display man-style help message REQUIREMENTS: This script requires a public internet connection with 'http:' access to ftp.perforce.com and www.cpan.org. It requires the 'curl' utility, the g++ compiler, and 'make'. EXAMPLES: Example 1: Typical usage, building a local Perl/P4Perl in /home/perforce/perl, using Perl v$PerlRel, building P4Perl from the $P4PerlBranch branch in The Workshop using the $P4APIRel release of the Perforce C++ API: $ThisScript -r /home/perforce/perl Example 2: Build only Perl, v5.12.4, for testing, skipping P4Perl build: $ThisScript -r /tmp/test_perl_5.12.4 -p 5.12.4 -s Example 3: Add P4Perl from the mainline branch in The Workshop to the just-built existing Perl from the prior example: $ThisScript -e /tmp/test_perl_5.12.4 -b main Example 4: Add P4Perl from the $P4PerlRel branch in The Workshop to an existing Perl in your environment $ThisScript -e /devtools/perl5/5.18.0 " fi exit 1 } #============================================================================== # Declarations declare InstallRoot="/usr/local/perl" declare WorkingDir="/tmp/p4perl" declare DownloadsDir= declare PerlRel="5.24.1" declare PerlTarFile= declare PerlBuildDir= declare PerlURL= declare P4APIRel="r16.1" declare Platform= declare P4APIURL= declare P4APITarFile="p4api.tgz" declare P4APIDir= declare P4PerlBranch="r16.1" declare P4PerlSourceTarFile="p4perl.src.tgz" declare P4PerlSourceURL= declare P4PerlBuildDir= declare ThisScript=${0##*/} declare CommandLine="$ThisScript $*" declare LogFile="/tmp/$ThisScript.$(date +'%Y%m%d-%H%M%S').log" declare H1="===============================================================================" declare H2="-------------------------------------------------------------------------------" declare PerlConfigDefs= declare -i IgnoreFailedTests=0 declare -i UseExistingPerl=0 declare -i SkipP4Perl=0 declare -i CleanWorkingDir=0 export NO_OP=0 declare Version="1.1.1" #============================================================================== # Command Line Processing declare -i shiftArgs=0 set +u while [[ $# -gt 0 ]]; do case $1 in (-r) InstallRoot="$2"; shiftArgs=1;; (-e) InstallRoot="$2"; UseExistingPerl=1; shiftArgs=1;; (-p) PerlRel="$2"; shiftArgs=1;; (-d) PerlConfigDefs="$2"; shiftArgs=1;; (-f) IgnoreFailedTests=1;; (-a) P4APIRel="$2"; shiftArgs=1;; (-b) P4PerlBranch="$2"; shiftArgs=1;; (-C) CleanWorkingDir=1;; (-s) SkipP4Perl=1;; (-h) usage -h;; (-man) usage -man;; (-n) export NO_OP=1;; (-D) set -x;; # Debug; use 'set -x' mode. (*) usage -h "Unknown arg ($1).";; esac # Shift (modify $#) the appropriate number of times. shift; while [[ $shiftArgs -gt 0 ]]; do [[ $# -eq 0 ]] && usage -h "Incorrect number of arguments." shiftArgs=$shiftArgs-1 shift done done set -u #============================================================================== # Main Program trap terminate EXIT SIGINT SIGTERM declare -i OverallReturnStatus=0 if [[ "${LogFile}" != off ]]; then touch ${LogFile} || bail "Couldn't touch log file [${LogFile}]." # Redirect stdout and stderr to a log file. exec > >(tee ${LogFile}) exec 2>&1 fi msg "${H1}\nStarting ${0##*/} v$Version at $(date).\n" msg "Started with this command line:\n\t$CommandLine\n" if [[ $UseExistingPerl -eq 1 ]]; then PerlTarFile= PerlURL= else PerlTarFile="perl-${PerlRel}.tar.gz" PerlURL="http://www.cpan.org/src/5.0/$PerlTarFile" fi if [[ $SkipP4Perl -eq 0 ]]; then case "$(uname)" in (Linux) Platform="linux26x86" [[ "$(uname -m)" == *"_64" ]] && Platform="${Platform}_64" ;; (Darwin) Platform="darwin90x86" [[ "$(uname -m)" == *"_64" ]] && Platform="${Platform}_64" ;; (*) bail "Current OS/platform is not supported: $(uname -a)." ;; esac msg "Target platform is: $Platform." P4APIURL="http://ftp.perforce.com/perforce/$P4APIRel/bin.$Platform/$P4APITarFile" P4PerlSourceURL="https://swarm.workshop.perforce.com/downloads/p4perl-bld/$P4PerlBranch/downloads/$P4PerlSourceTarFile" else P4APITarFile= P4PerlSourceTarFile= fi DownloadsDir="${WorkingDir}/downloads" if [[ -d "$WorkingDir" ]]; then if [[ $CleanWorkingDir -eq 0 ]]; then msg "Using existing working dir: $WorkingDir" else cmd "/bin/rm -rf $WorkingDir" "Removing existing working dir due to -C." ||\ bail "Failed to remove working dir $WorkingDir." fi else mkdir -p "$WorkingDir" || bail "Failed to create working dir $WorkingDir" fi if [[ ! -d "$DownloadsDir" ]]; then mkdir -p "$DownloadsDir" || bail "Failed to create downloads dir $DownloadsDir." fi cd "$DownloadsDir" || bail "Could not cd to downloads dir $DownloadsDir." msg "${H1}\nOperating in downloads dir [$DownloadsDir]." if [[ $UseExistingPerl -eq 0 ]]; then cmd "curl -s -O $PerlURL" "Downloading Perl source code." ||\ bail "Failed to download Perl from URL $PerlURL." fi if [[ $SkipP4Perl -eq 0 ]]; then cmd "curl -s -O $P4APIURL" "Downloading Perforce C++ API." ||\ bail "Failed to download Perforce C++ API from URL $P4APIURL." cmd "curl -k -s -O $P4PerlSourceURL" "Downloading P4Perl Source." ||\ bail "Failed to download P4Perl Source fromm URL $P4PerlSourceURL" fi cd "$WorkingDir" || bail "Could not cd to working dir $WorkingDir." msg "${H1}\nOperating in working dir [$WorkingDir]." for tarfile in $PerlTarFile $P4APITarFile $P4PerlSourceTarFile; do cmd "tar -xzf $DownloadsDir/$tarfile" "Extracting $tarfile." ||\ bail "Failed to extract tar file $tarfile." done if [[ $UseExistingPerl -eq 0 ]]; then PerlBuildDir="$PWD/perl-$PerlRel" cd $PerlBuildDir || bail "Could not cd to Perl build dir $PerlBuildDir." msg "${H1}\nOperating in Perl build dir [$PerlBuildDir]." cmd "./Configure -des -Dprefix=$InstallRoot $PerlConfigDefs" "${H2}\nConfiguring Perl." configure.log ||\ bail "Failed to configure Perl." cmd "make" "${H2}\nMaking Perl" make.log || bail "Failed to build Perl." cmd "make test" "${H2}\nTesting Perl" make_test.log if [[ $? -eq 0 ]]; then msg "Verified: Perl Test Suite passed." else if [[ $IgnoreFailedTests -eq 1 ]]; then msg "Warning: Perl Test Suite was not entirely successful. Ignoring due to -f." else bail "Perl Test Suite failed." fi fi cmd "make install" "${H2}\nInstalling Perl." make_install.log if [[ $? -ne 0 ]]; then if [[ $(id -u) -eq 0 ]]; then bail "Install for Perl failed running as root." else msg "Install for Perl failed as $(whoami). Trying again with sudo." cmd "sudo make install" "\nInstalling Perl using sudo." make_install_sudo.log ||\ bail "Failed to install Perl with sudo to $InstallRoot." fi fi fi if [[ $SkipP4Perl -eq 0 ]]; then P4APIDir="$(ls -d $WorkingDir/p4api-*)" P4PerlBuildDir="$(ls -d $WorkingDir/p4perl-*)" cd $P4PerlBuildDir || "Could not cd to P4Perl build dir $P4PerlBuildDir." msg "${H1}\nOperating in P4Perl build dir [$P4PerlBuildDir]." cmd "$InstallRoot/bin/perl Makefile.PL --apidir $P4APIDir" \ "Generating Makefile for P4Perl." gen_makefile.log ||\ bail "Failed to generate Makefile for P4Perl." cmd "make" "${H2}\nBuilding P4Perl" make.log ||\ bail "Failed to build P4Perl." cmd "make test" "${H2}\nTesting P4Perl" make_test.log if [[ $? -eq 0 ]]; then msg "Verified: P4Perl Test Suite passed." else if [[ $IgnoreFailedTests -eq 1 ]]; then msg "Warning: P4Perl Test Suite was not entirely successful. Ignoring due to -f." else bail "P4Perl Test Suite failed." fi fi cmd "make install" "${H2}\nInstalling P4Perl." make_install.log if [[ $? -ne 0 ]]; then if [[ $(id -u) -eq 0 ]]; then bail "Install for P4Perl failed running as root." else msg "Install for P4Perl failed as $(whoami). Trying again with sudo." cmd "sudo make install" "\nInstalling P4Perl using sudo." make_install_sudo.log ||\ bail "Failed to install P4Perl with sudo to $InstallRoot." fi fi if [[ $NO_OP -eq 0 ]]; then msg "Smoke Testing P4Perl with command:\n$InstallRoot/bin/perl -MP4 -e \"print P4::Identify()\"" $InstallRoot/bin/perl -MP4 -e "print P4::Identify()" if [[ $? -eq 0 ]]; then msg "\nSmoke test was successful!\n\nPerl scripts should use this shebang line:\n#!$InstallRoot/bin/perl -w\n\nOptionally, prepend PATH with $InstallRoot/bin, and prepend MANPATH with $InstallRoot/man\n" else OverallReturnStatus=1 fi fi fi if [[ $OverallReturnStatus -eq 0 ]]; then msg "${H1}\nAll processing completed successfully.\n" else msg "${H1}\nProcessing completed, but with errors. Scan above output carefully.\n" fi # Illustrate using $SECONDS to display runtime of a script. msg "That took $(($SECONDS/3600)) hours $(($SECONDS%3600/60)) minutes $(($SECONDS%60)) seconds.\n"