#!/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>]] [-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'. -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. -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 LogFile="/tmp/$ThisScript.$(date +'%Y%m%d-%H%M%S').log" declare H1="===============================================================================" declare H2="-------------------------------------------------------------------------------" declare -i UseExistingPerl=0 declare -i SkipP4Perl=0 declare -i CleanWorkingDir=0 export NO_OP=0 declare Version="1.0.7" #============================================================================== # 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;; (-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" 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" "${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 || bail "Perl test suite failed." 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 || bail "P4Perl Test Suite failed." 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 "Smoke 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"