#!/usr/bin/env bash # Author: Lilian BESSON, (C) 2015-oo # Email: Lilian.BESSON[AT]ens-cachan[DOT]fr # Date: 10-06-2018. # Web: https://bitbucket.org/lbesson/bin/src/master/mymake.sh # # A top-recursive 'make' command, with two awesome behaviors. # # Usage: mymake.sh ARG1 ARG2 ... # - A better make command, which goes up in the folder, until it finds a valid Makefile file. # - Additionally, it keeps looking as long as no rule has been found. # - An optional --FreeSMS option can be given, in which case a text message is sent when a job failed or succeed (like the notification) # - If the file ~/.use_FreeSMS_for_mymake is present, the option is turned on by default # - An optional --SlackWrap option can be given, in which case a text message is sent when a job failed or succeed (like the notification) # - If the file ~/.use_SlackWrap_for_mymake is present, the option is turned on by default # # Licence: MIT Licence (http://lbesson.mit-license.org). # version="1.0.1" returncode="0" # If success, return 0 datestarting="$(date "+%T - %D")" # More details at http://redsymbol.net/articles/unofficial-bash-strict-mode/ set -o pipefail # Use https://bitbucket.org/lbesson/bin/src/master/.color.sh to add colors in Bash scripts [ -f ~/.color.sh ] && . ~/.color.sh # Try to detect automatically the location of the make binary makePath="$(type make | awk '{ print $3 }')" # But if it failed, use the default one makePath="${makePath:-/usr/bin/make}" # options JustVersion="false" JustBashCompletion="false" Use_FreeSMS="false" if [ -f ~/.use_FreeSMS_for_mymake ]; then echo -e "The file '${black}~/.use_FreeSMS_for_mymake${white}' has been found, turning on the option ${cyan}--FreeSMS${white} ..." Use_FreeSMS="true" fi Use_SlackWrap="false" if [ -f ~/.use_SlackWrap_for_mymake ]; then echo -e "The file '${black}~/.use_SlackWrap_for_mymake${white}' has been found, turning on the option ${cyan}--SlackWrap${white} ..." Use_SlackWrap="true" fi # read all arguments for i in "$@"; do case "$i" in --noansi | --noANSI ) . ~/.nocolor.sh shift ;; --version ) JustVersion="true" shift ;; -npq ) JustBashCompletion="true" ;; --FreeSMS ) Use_FreeSMS="true" shift ;; --noFreeSMS ) Use_FreeSMS="false" shift ;; --SlackWrap ) Use_SlackWrap="true" shift ;; --noSlackWrap ) Use_SlackWrap="false" shift ;; esac done # Copyrights echo -e "${cyan}$(basename "$0") v${version} : copyright (C) 2016-2018 Lilian Besson${white}" if [ "X${JustVersion}" = "Xtrue" ]; then echo -e " ${black}You can find it online (https://bitbucket.org/lbesson/bin/src/master/mymake.sh)${white}" echo -e " ${black}This is free software, and you are welcome to redistribute it under certain conditions.${white}" echo -e " ${black}This program comes with ABSOLUTELY NO WARRANTY; for details see http://lbesson.mit-license.org${white}" "${makePath}" --version # exit 1 # don't exit with code 0 if --version? XXX why? fi # Do we need to turn down the FreeSMS plugin ? if [ "X${Use_FreeSMS}" = "Xtrue" ]; then if type FreeSMS.py &>/dev/null; then echo -e "${cyan}Option '--FreeSMS' was found, and the program 'FreeSMS.py' is present : OK ...${white}" else Use_FreeSMS="false" echo -e "${red}Option '--FreeSMS' was found, but the program 'FreeSMS.py' is not present : I am disabling the FreeSMS option ...${white}" fi fi # Do we need to turn down the SlackWrap plugin ? if [ "X${Use_SlackWrap}" = "Xtrue" ]; then if type slack-wrap &>/dev/null; then echo -e "${cyan}Option '--SlackWrap' was found, and the program 'slack-wrap' is present : OK ...${white}" else Use_SlackWrap="false" echo -e "${red}Option '--SlackWrap' was found, but the program 'slack-wrap' is not present : I am disabling the SlackWrap option ...${white}" echo -e "You can install it from 'https://github.com/EricCrosson/slack-wrap/' ..." fi fi if [ "X${JustBashCompletion}" = "Xtrue" ]; then Use_FreeSMS="false" Use_SlackWrap="false" fi # Working variables LogFile="$(tempfile)" mv -f -- "${LogFile}" "${LogFile}_mymake.log" LogFile="${LogFile}_mymake.log" if [ "X${Use_SlackWrap}" = "Xtrue" ]; then wrap="slack-wrap" fi OriginalPath="$(pwd)/" # The big loop: as long as we fail because there is no valid rule, go up FailBecauseNoValidRule="true" while [ "X$FailBecauseNoValidRule" = "Xtrue" ]; do FailBecauseNoValidRule="false" # Second loop: a long as there is no valid 'Makefile' file, go up old="$(pwd)/" xtitle "make $* - (in ${old})" # set terminal title echo -e "Looking for a valid ${magenta}Makefile${white} from ${blue}${old}${white} :" c="" while [ ! -f "${old}${c}Makefile" ]; do # No file echo -e "${red}${old}${c}Makefile${white} is not there, going up in the parent folder ... Current directory: $(pwd)" c="../${c}" cd .. # DEBUG avoid infinite loops! [ "$(pwd)" = "/" -o "$(pwd)" = "${HOME}" ] && break done # We found a valid Makefile or we already called 'break'... NameOfMakefile="$(readlink -f "${old}${c}Makefile")" # Find his real name, e.g. simplies fake/bar/foo/../.. to fake/ # Now using it to execute make if [ -f "${old}${c}Makefile" ]; then echo -e "${green}${NameOfMakefile}${white} is there, I'm using it :" echo -e "Calling... ${black}' time ${wrap:-} ${makePath} -w --file=${NameOfMakefile} $@ | tee ${LogFile} '${white}..." if [ "X${JustBashCompletion}" != "Xtrue" ]; then time ${wrap:-} "${makePath}" -w --file="${NameOfMakefile}" "$@" \ 3>&1 1>&2 2>&3 | tee "${LogFile}" # FIXED this pipe disabled color on stdout of programs that detect pipes (sphinx, my ansicolortags script, grep etc...) # This trick '3>&1 1>&2 2>&3' swaps stdout and stderr: only stderr is piped to |tee so colors are still in stdout (from https://serverfault.com/a/63708) returncode="$?" if [ "X${returncode}" = "X0" -a -f "${LogFile}" ]; then if grep '\*\*\*.* Error [0-9]\+' "${LogFile}" >/dev/null; then returncode="$(grep -o '\*\*\*.* Error [0-9]\+' "${LogFile}" | awk '{print $2}')" fi fi # echo -e "1. Return code from this make rule: ${returncode}..." # DEBUG else time ${wrap:-} "${makePath}" -w --file="${NameOfMakefile}" "$@" \ 2>&1 | tee "${LogFile}" returncode="$?" if [ "X${returncode}" = "X0" -a -f "${LogFile}" ]; then if grep '\*\*\*.* Error [0-9]\+' "${LogFile}" >/dev/null; then returncode="$(grep -o '\*\*\*.* Error [0-9]\+' "${LogFile}" | awk '{print $2}')" fi fi # echo -e "2. Return code from this make rule: ${returncode}..." # DEBUG fi datefinished="$(date "+%T - %D")" # XXX This is very specific, maybe there is a better way to detect it ? # Not with the returned error code of /usr/bin/make at least... grep 'make: \*\*\* Aucune règle pour fabriquer la cible' "${LogFile}" || grep 'make: \*\*\* No rule to make target' "${LogFile}" &>/dev/null grepReturnCode="$?" if [[ "X${grepReturnCode}" = "X0" ]]; then echo -e "${red}Failed because there is no rule${white} to make the target '${yellow}$*${white}' in the current Makefile (${green}${NameOfMakefile}${white}) ..." FailBecauseNoValidRule="true" returncode="4" [ "$(pwd)" = "/" -o "$(pwd)" = "${HOME}" ] && break # avoid infinite loops! c="../${c}" cd .. # FIXED We should just go up once. echo -e "${magenta}Going up in the parent folder...${white} Current directory: $(pwd)" # [ "$(dirname $(pwd))" = "/" ] && break # DEBUG avoid infinite loops! else # No notifications if make was called by the bash auto-completion function (options '-npq -C'...) or if it failed because no rule if [ "X${JustBashCompletion}" != "Xtrue" ]; then runningfolder="$(readlink -f "${old}${c}")" runningfolder="${runningfolder#$HOME/}/" displayfolder="$(basename "${runningfolder}")" if [ "X${returncode}" = "X0" ]; then notify-send --icon=terminal "$(basename "$0") v${version}" "make on '$*' worked, in the folder '${displayfolder}' from '$(readlink -f "${OriginalPath}")' :-)" if [ "X${Use_FreeSMS}" = "Xtrue" ]; then FreeSMS.py "[SUCCESS] make on '$*' *worked*, in the folder '${displayfolder}' :-)\\n- Job started at '${datestarting}', finished at '${datefinished}'.\\n- Sent by $(basename "$0") v${version}, using FreeSMS.py by Lilian Besson." fi else notify-send --icon=error "$(basename "$0") v${version}" "make on '$*' failed, in the folder '${displayfolder}' from '$(readlink -f "${OriginalPath}")' ..." if [ "X${Use_FreeSMS}" = "Xtrue" ]; then # Search for the last modified log file, either *.log or *log.txt, in . or any sub dir of depth 1 # Then print is last modified time stamp in decimal seconds, sort it, take last one, remove date lastlogfile="$(find . -maxdepth 2 -type f \( -iname '*'.log -o -iname '*'log.txt \) -printf '%T@ : %p\n' | sort -n | tail -n1 | awk '{ print $3 }')" errormsg="" if [ -f "${lastlogfile}" ]; then # Reading the last modified log file, to txt, remove empty lines, keep 5 lines errormsg="\\n- Error:\n$(tail -n20 "${lastlogfile}" | ansi2txt | grep -v '^$' | tail -n5)" fi FreeSMS.py "[FAILURE] make on '$*' *failed*, in the folder '${displayfolder}' :-(\\n- Job started at '${datestarting}', finished at '${datefinished}'.${errormsg}\\n- Sent by $(basename "$0") v${version}, using FreeSMS.py by Lilian Besson." fi fi fi fi else # If we did not find any valid Makefile echo -e "${red}${NameOfMakefile} is not there and I'm in '/' or '${HOME}'... I cannot go up anymore${white}" exit 3 fi done exit "${returncode}" # End of mymake.sh