#!/bin/bash
# -*-mode: sh; coding: latin-1-unix; fill-column: 79-*-
#
# maketool -- Run make on all available CPUs, tee'd
#
# "jobserver unavailable" warnings:
#     Most likely result from a nested call to make, when the parent make did
#     not realize that the child process it was invoking was actually a GNU
#     make program.  In the parent makefile the sub-make must be started with a
#     "+" prefix. See the GNU make manual for more information.
#
# Installation:
#    This is a portable shell script that works on any UN*X and Windows
#    (Cygwin) system.  Simply copy this script to your path, e.g.  to
#    "$HOME/bin".
#
# Prerequisites:
#     sh, GNU make, egrep, wc, tee, sed
#
# $Compile: ./maketool$
# $Maintained at: http://www.visualco.de$
# $Author: Andreas Spindler <info@andreasspindler.de>$
# $Writestamp: 2016-04-20 13:07:02$

###############################################################################
# Environment
#
script=`basename $0`
MAKE=`which make` || Panic 'make: not found'
SED=`which sed`   || Panic 'sed: not found'
TEE=`which tee`

###############################################################################
# Options
#
opts="-k -n" optlog=make.log optjobctl=1

function Usage {
    cat <<EOF 1>&2

USAGE
    $script [MAKE-COMMANDS]...

SYNOPSIS
    $ $script -k all

DESCRIPTION
    Use  "$script" instead  of "make"  to run  GNU make.  "$script" is  a small
    shell-script that runs make with  automatic job-control. All output will be
    tee'd to "$optlog".

Automatic job-control:
    The "--jobs=N" parameter allows GNU make to run commands simultaneously. On
    a  multiprocessor machine  dependencies  are  then dissolved  significantly
    faster, and targets are build parallely.

    "$script"  will determine  the number  of available  CPUs on  Linux, MacOS,
    Solaris (SunOS) and  BDS. Note that to run make  efficiently (i.e. with the
    maximum number  of jobs) the number  of available CPUs must  be determined.
    There's no portable way to get this important ratio, and GNU make also does
    not know the max. number of jobs.

Multiple jobs:

    - In recursive make invocations the parent make and all sub-makes will
      communicate to ensure that there are only N jobs running at the same time
      between them all. The sub-make shall be started with a "+" prefix.

    - When commands are not serially executed messages from different commands
      may be interspersed.

    - When more than one process reads from standard input this can break
      pipes.

    - If make terminates for any reason (including a signal) with child
      processes running, it waits for them to finish before actually exiting.

    - Consider specifying --max-load=LIMIT. GNU make then checks the current
      system load average; make waits until it goes below the limit, or until
      all jobs finish. "$script" will not set this option by itself, but passes
      it to make like all arguments.

EOF
    exit 0
}

function Panic {
    cat <<EOF >&2
Panic! in shell $$, pipe $?, terminal `tty`: $*
$script exits with -1.
EOF
    exit -1
}

while [ $# -gt 0 ]; do
    case "$1" in
        -h|--help) Usage;;
        -j|--jobs) optjobctl=;;
    esac
    opts="$opts $1"
    shift
done

########################################################################
# Main code
#

# Try to evaluate $ncpus
#
# Resources:
#   <http://ndevilla.free.fr/threads/index.html>
#   <http://ndevilla.free.fr/threads/butenhof.txt>)
#   <sys/cpuvars.h>

if [ -n "$optjobctl" ]; then
    case `uname -s` in
        Darwin)
            ncpus=`sysctl -n hw.activecpu`;;
        *bsd)
            ncpus=`sysctl -n hw.ncpu`;;
        CYGWIN*)
            [ -f /proc/cpuinfo ] && ncpus=`grep -i '^processor[[:space:]]*:' /proc/cpuinfo|wc -l`;;
        Linux)
            [ -f /proc/cpuinfo ] && ncpus=`grep -i '^processor[[:space:]]*:' /proc/cpuinfo|wc -l`;;
        SunOS)
            ncpus=`/usr/sbin/psrinfo|wc -l` ;;
    esac
fi

echo "$script: Found $ncpus CPUs"

if [ -n "$ncpus" ]; then
    if [ $ncpus -gt 1 ]; then
        opts="$opts -j $ncpus"
    fi
fi

# Run make under tee. Reduce blank sequences in $opts into single blanks. Fail
# for sub-makes, since $optlog then is already tee'd and the jobserver is busy.

MK="$MAKE $opts"
MK=`echo -n $MK|$SED -e 's/[ \t\r\n]+/ /g'`

echo "$sciprt: logfile $optlog"
echo "$script: $MK"

if [ -n "$MAKELEVEL" ]; then
    if [ $MAKELEVEL -gt 0 ]; then # "0" for top-level make
        Panic "cannot run as sub-make (MAKELEVEL=$MAKELEVEL)"
    fi
fi

if [ -n "$TEE" ]; then
    $MK $* 2>&1 | $TEE $optlog
else
    $MK $* 2>&1 > $optlog
fi

# Local Variables:
# coding: iso-8859-1-unix
# fill-column: 80
# End: