1#!/bin/bash
2
3## Copyright (C) 2018 Robert Krawitz
4##
5## This program is free software; you can redistribute it and/or modify
6## it under the terms of the GNU General Public License as published by
7## the Free Software Foundation; either version 2, or (at your option)
8## any later version.
9##
10## This program is distributed in the hope that it will be useful,
11## but WITHOUT ANY WARRANTY; without even the implied warranty of
12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13## GNU General Public License for more details.
14##
15## You should have received a copy of the GNU General Public License
16## along with this program.  If not, see <https://www.gnu.org/licenses/>.
17##
18## Build release tarball
19
20# Note that the shebang line is explicit here, not indirected through
21# autoconf.  This allows the script to be run in a non-initialized workspace.
22# We also require this script to be run in the root of a workspace so that
23# it can find the script directory to get the version without having to be
24# autotool-processed.
25
26function xtput() {
27    if [[ -z ${STP_TEST_RECURSIVE:-} && -n ${TERM:-} && ${TERM:-} != dumb ]] ; then
28	tput "$@"
29    fi
30}
31
32set -u
33
34# shellcheck disable=SC2155
35{
36declare -r tbold="$(xtput bold)"
37declare -r tred="$(xtput setaf 1)$tbold"
38declare -r tyellow="$(xtput setaf 3)$tbold"
39declare -r tgreen="$(xtput setaf 2)$tbold"
40declare -r tpurple="$(xtput setaf 5)$tbold"
41declare -r tblue=$(xtput setaf 4)
42declare -r tcyan=$(xtput setaf 6)
43declare -r treset=$(xtput sgr0)
44}
45declare -i exitstatus=1
46declare -i stepstatus=-1
47declare currentmodule=
48declare -a failedmodules=()
49declare top_log
50function fatal() {
51    echo "${tred}FATAL: $*${treset}"
52    exit 1
53}
54
55# shellcheck disable=SC2155
56declare GIT=$(type -p git)
57[[ -n $GIT ]] || fatal "Can't find git"
58
59"$GIT" rev-parse --show-toplevel >/dev/null 2>&1 || fatal "Current directory is not a git workspace"
60declare rootdir
61rootdir="$("$GIT" rev-parse --show-toplevel 2>/dev/null)"
62[[ -n $rootdir ]] || fatal "Can't find workspace root"
63cd "$rootdir" || fatal "Can't cd to workspace root ($rootdir)"
64[[ -s ChangeLog.pre-5.2.11 ]] || fatal "$rootdir does not appear to be a Gutenprint tree"
65
66# shellcheck disable=SC2155
67declare MAKE=$(type -p make)
68[[ -n $MAKE ]] || fatal "Can't find make"
69
70# shellcheck disable=SC2155
71declare -r ROOT=$(pwd)
72declare tmpfile=/dev/null
73export STP_PARALLEL=${STP_PARALLEL:-$("$ROOT/scripts/count-cpus")}
74declare counter=1
75declare git_dirty=
76declare build_type=release
77[[ -n ${STP_BUILD_SNAPSHOT:-} ]] && build_type=snapshot
78
79# This can't simply be a constant because scripts/gversion might
80# not exist (or may be incorrect) prior to autogen being run.
81function pkg_version() {
82    "$ROOT/scripts/gversion" pkg
83}
84
85function pkg_tag() {
86    # shellcheck disable=SC2155
87    local version=$(pkg_version)
88    echo "gutenprint-${version//./_}"
89}
90
91# Clean up any trailing whitespace.
92
93function preflight() {
94    # shellcheck disable=SC2155
95    local trailing_ws="$("$GIT" grep -Il '[	 ]$')"
96    if [[ -n $trailing_ws ]] ; then
97	console_log "*** ERROR: The following files have trailing whitespace:"
98	console_log "$trailing_ws"
99	return 2
100    fi
101    return 0
102}
103
104# Git pre-checks (not version-specific)
105
106function check_git() {
107    "$GIT" fetch
108    local gstatus=0
109
110    # Check for uncommitted files.
111    if [[ -n $("$GIT" status -uno --porcelain) ]] ; then
112	console_log "*** ERROR: Uncommitted changes in repository:"
113	"$GIT" status -uno --porcelain | console_log
114	gstatus=2
115    fi
116
117    # Ensure that the workspace is up to date (git status -uno
118    # --porcelain -b |grep -v ahead is empty -- it's OK to be ahead,
119    # but not behind) and that we don't need to rebase (no merges.
120    # Also check that we haven't diverged.
121
122    # shellcheck disable=SC2155
123    local ahead=$("$GIT" rev-list '@{u}..@')
124    # shellcheck disable=SC2155
125    local behind=$("$GIT" rev-list '@..@{u}')
126    if [[ -n $ahead && -n $behind ]] ; then
127	# Oops!  Both ahead *and* behind remote.  Really bad news!
128	console_log "*** ERROR: HEAD and remote have diverged!"
129	console_log "***        Please merge and rebase all changes!"
130	return 2
131    elif [[ -n $behind ]] ; then
132	# We're behind.  Not good.
133	console_log "*** ERROR: Behind remote by $(wc -w <<< "$behind") commits."
134	return 2
135    elif [[ -n $ahead ]] ; then
136	# We're ahead.  That's OK as long as there are no merge commits.
137	local merges=0
138	for h in $ahead ; do
139	    (( $("$GIT" rev-parse "$h^@" |wc -w) > 1 )) && merges=$((merges + 1))
140	done
141	console_log "*** Warning: Ahead of remote."
142	if (( merges > 0 )) ; then
143	    (( merges != 1 )) && pl=s
144	    console_log "*** ERROR: $merges merge${pl:-} between HEAD and remote"
145	    return 2
146	fi
147    fi
148    return $gstatus
149}
150
151# Run autogen.sh to ensure that we're using default build settings
152# Everything else depends on this.
153
154function run_target() {
155    make "$1" && return 0
156    local lstatus=$?
157    console_log "*** ERROR: ${3:+$3 }make $1 failed"
158    (( lstatus >= 127 )) && return $lstatus
159    return "${2:-2}"
160}
161
162function run_maintainer_clean() {
163    if [[ -f Makefile ]] ; then
164	run_target maintainer-clean
165    else
166	return 0
167    fi
168}
169
170function run_autogen() {
171    # shellcheck disable=SC2086
172    ./autogen.sh ${STP_CONFIG_ARGS:-} && return 0
173    console_log "*** FATAL: autogen failed!"
174    return 1
175}
176
177function colorize() {
178    sed \
179	-e "s/\*\*\* \(FATAL\|ERROR\):\(.*\)/${tred}*** \1:\2${treset}/" \
180	-e "s/\*\*\* Warning:\(.*\)/${tyellow}*** Warning:\1${treset}/"
181}
182
183function run_clean() {
184    run_target clean
185}
186
187function run_build() {
188    run_target "${STP_PARALLEL:+-j$STP_PARALLEL}" 1
189}
190
191# Same as above, without make clean if we know we're in a clean
192# environment (e. g. CI)
193function run_build_fresh() {
194    # shellcheck disable=SC2086
195    ./autogen.sh ${STP_CONFIG_ARGS:-} && make "${STP_PARALLEL:+-j$STP_PARALLEL}" && return 0
196    console_log "*** FATAL preliminary build failed!"
197    return 1
198}
199
200# Git check tag.  This can't be run until after the build, because we
201# don't have the version available until autogen.
202
203function check_git_tag() {
204    # Make sure that the tag that we're going to want to apply isn't
205    # already present.
206    [[ $build_type != release ]] && return 0
207    if [[ -n $("$GIT" show-ref "refs/tags/$(pkg_tag)") ]] ; then
208	console_log "*** ERROR: Tag named $(pkg_tag) is already present"
209	return 2
210    fi
211}
212
213function _cleanup_test_repo() {
214    if [[ -d $TESTREPO ]] ; then
215	rm -rf -- "$TESTREPO"
216    fi
217}
218
219# Check that we can build a clone of this workspace
220function _check_git_builds() {
221    # shellcheck disable=SC2155
222    export TESTREPO=$(mktemp -d "/tmp/stpbuild.XXXXXXXX")
223    trap _cleanup_test_repo EXIT SIGHUP SIGINT SIGQUIT SIGTERM
224    # shellcheck disable=SC2155
225    local rev=$("$GIT" rev-parse @)
226    cwd=$(pwd -P)
227    [[ -n $cwd ]] || {
228	console_log "*** ERROR: Can't find directory!"
229	return 2
230    }
231    cd "$TESTREPO" || {
232	console_log "*** ERROR: Can't cd to test repo directory $cwd!"
233	(( $? >= 127 )) && return 127
234	return 2
235    }
236    "$GIT" clone "$cwd" . || {
237    	console_log "*** ERROR: Unable to clone repo"
238	(( $? >= 127 )) && return 127
239	return 2
240    }
241    "$GIT" checkout "$rev" || {
242    	console_log "*** ERROR: Unable to check out rev $rev"
243	(( $? >= 127 )) && return 127
244	return 2
245    }
246    STP_TEST_RECURSIVE=$((${STP_TEST_RECURSIVE?-0}+1)) STP_LOG_NO_SUBDIR=1 STP_LOG_DIR=$STP_TEST_LOG_PREFIX scripts/build-release preflight run_autogen run_build run_distcheck_minimal || {
247	console_log "*** ERROR: Repo build failed!"
248	(( $? >= 127 )) && return 127
249	return 2
250    }
251}
252
253function check_git_builds() {
254    (_check_git_builds)
255}
256
257# Run make valgrind-minimal.
258#
259#    This does a *very* limited set of valgrind checks, running
260#    testpattern and rastertogutenprint on 9 (currently) selected
261#    printers.  It takes about 30 seconds on my laptop.  Smoketest and
262#    all.
263
264function run_valgrind_minimal() {
265    run_target check-valgrind-minimal
266}
267
268function run_valgrind_fast() {
269    run_target check-valgrind-fast
270}
271
272function run_check_minimal() {
273    run_target check-minimal
274}
275
276function run_check_fast() {
277    run_target check-fast
278}
279
280# Run make distcheck-fast.
281#
282#    This actually builds the tarball, unpacks the tarball, builds it
283#    out of tree, runs a short set of tests against it, does a local
284#    make install, followed by make uninstall, and makes sure no
285#    debris is left around.  This runs configure with all default
286#    arguments, so it is testing dynamically linked executables.
287#
288#    The particular tests it runs are:
289#
290#    - Conformance tests all non-translated non-simplified PPD files
291#      and distinct global ones.
292#
293#    - Runs test-rastertogutenprint on distinct printers, with fast
294#      options (minimum paper size, lowest resolution, very fast
295#      dithering).
296#
297#    - Runs run-testpattern-2:
298#
299#      + Distinct printers, fast options
300#
301#      + Selected printers, with cross product of input mode (and bit
302#        depth), color correction, ink type, and use gloss.
303#
304#    It also has the property of maybe updating the .po files.  These
305#    will later need to be committed and included in the tag.  So we
306#    have to do our check for uncommitted bits prior to this.
307#
308#    It has not escaped me that this could be part of a CI testing
309#    process.  I don't know if Sourceforge has the necessary gittage
310#    (as GitHub does) to allow a merge bot to run something like this
311#    and only merge to the main repository if this suite passes.
312#
313#    The reason for the distcheck-fast is so that if something stupid
314#    goes wrong it gets caught quickly.  It takes about 270 seconmds
315#    on my laptop.  It would be Kind Of Annoying to spend hours
316#    testing only to find out that something's not handling destdir
317#    correctly or make clean isn't removing something.
318#
319#    Note that this can't be combined with valgrind, since this builds
320#    dynamic executables which can't conveniently be valground since
321#    they're actually shell scripts.
322#
323#    There's now an even faster check, distcheck-minimal, that only
324#    tests a handful of printers.  It takes about 50 seconds to run.
325#    But that's really most useful for testing the distcheck
326#    apparatus.
327#
328# So far we're at just over 5 minutes on a Skylake Xeon E3-1505Mv5,
329# which isn't too bad for a prerelease smoke test.  The rest of this
330# takes a lot longer.
331
332function run_distcheck_fast() {
333    run_target distcheck-fast
334}
335
336# Run make check-valgrind
337#
338# This is slow.  It tests only unique printers, and a lot of extra
339# combinations with a few printers, all using fast options.  It
340# uses both CUPS and run-testpattern-2 testing.  However, it's
341# essentially embarrassingly parallel.
342#
343# I'd like not to go too long without running it, as it's easy for
344# things to make their way in.  For CI purposes, if we ever go
345# there, like to find a happy medium.
346
347function run_valgrind() {
348    run_target check-valgrind
349}
350
351# Run make check-full
352#
353#    This one I'm not sure of; do we need this or is this well enough
354#    covered by the combination of distcheck-fast and check-valgrind?
355#    It does take a while, but I haven't benchmarked it lately.
356#
357#    - Conformance test all PPD files
358#
359#    - Run test-rastertogutenprint on all printers, with default options
360#
361#    - Runs run-testpattern-2:
362#
363#      + Distinct printers, default options
364#
365#      + All printers, fast options
366#
367#      + Distinct printers, fast options, with cross product of input
368#        mode (and bit depth), color correction, ink type, and use
369#        gloss.
370#
371#    IIRC this takes 60-90 minutes on my laptop, but again, it
372#    parallelizes very well.
373
374function run_full() {
375    run_target check-full
376}
377
378# Run make checksums-release to generate a new regression file.
379#
380# The problem here is what do we require for the release build.  Do
381# we require a clean regression run (other than added
382# printers/modes)?  There are legitimate reasons for changing, and
383# having to rerun the procedure because the release engineer forgot a
384# command line option is a bit harsh.  Something better might be to
385# simply record changes unless there's an outright failure here, and
386# let those be reviewed.
387#
388# For CI purposes, the default might be to require no changes, with
389# human intervention if there are.
390#
391# This takes about 30 minutes on my laptop.  This is extremely
392# scalable.  Give us a really big machine instance to run it on, this
393# will run really fast.
394
395function run_checksums() {
396    make checksums
397    local lstatus=$?
398    if (( lstatus == 0 )) ; then
399	# shellcheck disable=SC2155
400	local csum_file="src/testpattern/Checksums/sums.$(pkg_version).zpaq"
401	if [[ ! -f $csum_file ]] ; then
402	    console_log "*** ERROR: Can't find new checksums file $csum_file"
403	    (( lstatus > 127 )) && return $lstatus
404	    return 2
405	fi
406	cp -p "$csum_file" "$ARTIFACTDIR"
407	return 0
408    fi
409    console_log "*** ERROR: make checksums failed"
410    (( lstatus > 127 )) && return $lstatus
411    return 2
412}
413
414# Prep the release
415
416function git_prep_release() {
417    # .po files might have changed; nothing else should have!
418    # Add any of those changed files.
419    if [[ $build_type == release ]] ; then
420	"$GIT" add -u || return 1
421	# Add the checksums file.
422	# TBD whether to do this for snapshots.  The file's not very big,
423	# but it's completely incompressible!
424	"$GIT" add -f "src/testpattern/Checksums/sums.$(pkg_version).zpaq" || return 1
425	# Commit this change
426	"$GIT" commit -m"Gutenprint $(pkg_version) release" || return 1
427    else
428	# Don't update the .po files for every snapshot.
429	echo "Cleaning up .po files"
430	"$GIT" checkout -- po
431    fi
432    # Shouldn't have anything left after this.
433    if "$GIT" status -uno --porcelain |grep -q -E -v 'po/.*\.po' ; then
434	console_log "*** ERROR: Unexpected untracked files:"
435	"$GIT" status -uno --porcelain |grep -E -v 'po/.*\.po' | console_log
436	return 1
437    fi
438    # Apply the tag.  Ideally we should sign the tag too.
439    # But don't tag snapshot builds.
440    [[ $build_type != release ]] && return 0
441    "$GIT" tag -a "$(pkg_tag)" -m "Gutenprint $(pkg_version) release" || return 1
442}
443
444# make distcheck-minimal
445#
446# We have to rebuild the tarball in any event here, so that we pick up
447# the tag (to get a correct change log) and updated .po files.
448# A minimal distcheck only takes about a minute; we might as well
449# do a final sanity check.
450
451function run_distcheck_minimal() {
452    run_target distcheck-minimal 1 Final
453}
454
455function run_check_minimal() {
456    run_target check-minimal
457}
458
459#  Save away build
460function save_build_artifacts() {
461    # shellcheck disable=SC2155
462    local tarball="$(pkg_version).tar.xz"
463    if [[ -s $tarball ]] ; then
464	cp -p "$tarball" "$ARTIFACTDIR"
465    else
466	echo "Cannot find $tarball"
467	return 1
468    fi
469}
470
471# Final release prep
472
473function finis() {
474    local extra_verbiage=
475    [[ $build_type == release ]] && {
476	STP_DATA_PATH=src/xml test/gen-printer-list > "printer-list.$(pkg_version)" || return 1
477	extra_verbiage=$(cat <<EOF
478
479  * Update the web site
480
481  * Merge the updated printer list in ${tcyan}printer-list.$(pkg_version)${tpurple}
482    into p_Supported_Printers.php and upload that to the web site
483EOF
484)
485}
486    console_log <<EOF
487${tpurple}
488================================================================
489Remainder to be done manually:
490
491  * git push
492
493  * Upload the tarball (${tcyan}gutenprint-$(pkg_version).tar.xz${tpurple})
494$extra_verbiage
495================================================================
496$treset
497EOF
498    return 0
499}
500
501################################################################
502#
503# Runtime
504#
505################################################################
506
507# Note that if we change the format of this timestamp we have to
508# change console_log if the width changes.
509#
510# Unfortunately the shell built-in printf can't specify UTC.
511function stamp() {
512    printf '%(%Y-%m-%dT%H:%M:%S%z)T' -1
513}
514
515function date_sec() {
516    printf '%(%s)T' -1
517}
518
519function report_progress() {
520    local outst="RUNNING[$1]: "
521    shift
522    # shellcheck disable=SC2046
523    echo $([[ -z ${BUILD_VERBOSE:-} ]] && echo '' -n) ">>> $(stamp) $outst $*"
524}
525
526# This allows us to log to multiple outputs, including stdout and
527# (where available) file descriptors.  Ideally we'd be able to build a
528# pipeline and eval it, but it's not clear that that's possible.
529function log1() {
530    if [[ $# -eq 0 || ($# == 1 && $1 == -) ]] ; then
531	exec cat
532    else
533	local dest="$1"
534	shift
535	# stdout needs to come last, because we just want to send data
536	# to stdout rather than teeing off or explicitly going to a file.
537	if [[ $dest == - && $# -gt 0 ]] ; then
538	    # Protect against someone inadvertently specifying '-' twice!
539	    # shellcheck disable=SC2046
540	    log1 "$@" $([[ $1 == - ]] || echo -)
541	elif [[ $dest == -* ]] ; then
542	    dest=${dest:1}
543	    destdir=${dest%/*}
544	    [[ -n $destdir && ! -d $destdir ]] && mkdir -p "$destdir"
545	    if (( $# == 0 )) ; then
546		exec cat > "$dest"
547	    elif [[ $* == - ]] ; then
548		exec tee "$dest"
549	    else
550		exec tee "$dest" | log1 "$@"
551	    fi
552	else
553	    destdir=${dest%/*}
554	    [[ -n $destdir && ! -d $destdir ]] && mkdir -p "$destdir"
555	    if (( $# == 0 )) ; then
556		exec cat >> "$dest"
557	    elif [[ $* == - ]] ; then
558		exec tee -a "$dest"
559	    else
560		exec tee -a "$dest" | log1 "$@"
561	    fi
562	fi
563    fi
564}
565
566function log() {
567    (log1 "$@" ${BUILD_VERBOSE:+-})
568}
569
570function log_top1() {
571    log "$top_log" -
572}
573
574function red() {
575    sed -e "s/^/${tred}/" -e "s/$/${treset}/"
576}
577
578function green() {
579    sed -e "s/^/${tgreen}/" -e "s/$/${treset}/"
580}
581
582function log_top() {
583    if [[ -n "$*" ]] ; then
584	log_top1 <<< "$*"
585    else
586	log_top1
587    fi
588}
589
590# Log the output to the console as well as the master log file and the
591# per-operation log file.
592#
593# fd#4 (/dev/fd/4 -- let's hope we're building the package on
594#      a system that supports /dev/fd, but linux does)
595#      in the operation gets tied to stderr
596#
597# Note that fd#3 is used by lower levels
598#
599# Then we timestamp the data and send it to the top-level log (which
600# is not normally timestamped).
601#
602# Finally, we remove the existing timestamp (which relies upon the timestamp
603# format, ugh) and send it to stdout where it gets picked up and timestamped
604# again.
605function console_log1() {
606    if [[ -n ${STP_TEST_RECURSIVE:-} ]] ; then
607	tee >(exec cat 1>&4) >(timestamp | log_top | cut -c26-)
608    else
609	timestamp | log_top - | cut -c26-
610    fi
611}
612
613function console_log_immediate1() {
614    case "${STP_TEST_RECURSIVE:-0}" in
615	2)
616	    tee >(exec cat 1>&6) >(exec cat 1>&5) >(exec cat 1>&4) >(timestamp | log_top | cut -c26-)
617	    ;;
618	1)
619	    tee >(exec cat 1>&5) >(exec cat 1>&4) >(timestamp | log_top | cut -c26-)
620	    ;;
621	*)
622	    tee >(exec cat 1>&2) >(timestamp | log_top | cut -c26-)
623	    ;;
624    esac
625}
626
627function console_log() {
628    if [[ -n "$*" ]] ; then
629	console_log1 <<< "$*"
630    else
631	console_log1
632    fi
633}
634
635function console_log_immediate() {
636    if [[ -n "$*" ]] ; then
637	console_log_immediate1 <<< "$*"
638    else
639	console_log_immediate1
640    fi
641}
642
643function time_delta() {
644    local -i i=$((${2:-0} - ${1:-0}))
645    printf "%d:%02d:%02d" $((i / 3600)) $(((i % 3600) / 60)) $((i % 60))
646}
647
648function report_step_status() {
649    (( stepstatus == -1 )) && return
650    [[ -z ${BUILD_VERBOSE:-} ]] && {
651	local ststatus=OK
652	local stcolor="${tgreen}"
653	if (( stepstatus)) ; then
654	    ststatus=FAILED
655	    stcolor="${tred}"
656	    if (( stepstatus == 126 )) ; then
657		ststatus="NOT FOUND"
658		stcolor="${tpurple}"
659	    elif (( stepstatus >= 127 )) ; then
660		ststatus=INTERRUPTED
661	    fi
662	fi
663	printf "$stcolor%$((longest_op - ${#op} ))s $ststatus$treset\n"
664    }
665    [[ -n $currentmodule && $stepstatus -ne 0 ]] && failedmodules+=("$currentmodule")
666    currentmodule=
667    if [[ -f $tmpfile ]] ; then
668	[[ -s $tmpfile ]] && colorize < "$tmpfile"
669	rm -f -- "$tmpfile"
670    fi
671    stepstatus=-1
672}
673
674# shellcheck disable=SC2155
675function finish() {
676    local status=$exitstatus
677    local etime=$(date_sec)
678    local estamp=$(stamp)
679    report_step_status
680    if [[ $status != 0 || -n ${failedmodules[*]:-} ]] ; then
681	log_top "The following modules failed:"
682	log_top "$(printf "    %s\n" "${failedmodules[@]:-}")"
683	log_top "*** Gutenprint $build_type build FAILED at $estamp ($(time_delta "$stime" "$etime"))" |red
684    else
685	log_top "*** Gutenprint $build_type build completed at $estamp ($(time_delta "$stime" "$etime"))" |green
686    fi
687    log "$top_log" <<< "================================================================"
688    if [[ -n ${TRAVIS_MODE:-} ]] ; then
689	# We really don't want the termination message from the deadman
690	exec 3>&2 2>/dev/null
691	kill %travis_deadman
692	wait %travis_deadman
693	exec 2>&3 3>&-
694    fi
695    trap - EXIT
696    exit "$status"
697}
698
699# Travis times out if there's no output for 10 minutes, but some things
700# go silent for quite a while
701function travis_deadman() {
702    while : ; do sleep 60; echo -e "\n${tblue}Mark $(uptime)${treset}" | log -; done
703}
704
705function timestamp() {
706    while read -r ; do echo "$(stamp) $REPLY"; done
707}
708
709# Run one operation.
710# shellcheck disable=SC2155
711function runit() {
712    local cmdname=$1
713    local cmd="$*"
714    local fcounter=$(printf "%02d" "$counter")
715    local local_logdir="$logdir/$fcounter.${cmd// /_/}"
716    mkdir -p "$local_logdir"
717    local logfile="$local_logdir/Master"
718    [[ -n ${DONTRUN_OP:-} ]] && logfile=/dev/null
719    local sstime=$(date_sec)
720    local ssstamp=$(stamp)
721    local status=0
722    local msg=completed
723    log "-$logfile" "$top_log" <<< "----------------------------------------------------------------"
724    if [[ -z ${DONTRUN_OP:-} ]] ; then
725	echo "$cmdname started at $ssstamp" | log "$logfile" "$top_log"
726	echo "Command: $cmd" | log "$logfile" "$top_log"
727	echo "Log file: ${logfile#${logdir}/}" | log "$top_log"
728    else
729	echo "$cmdname SKIPPED" | log "$top_log"
730    fi
731    report_progress "$fcounter" "$cmdname"
732
733    if [[ -z ${DONTRUN_OP:-} ]] ; then
734	#
735	stepstatus=127
736	currentmodule="$cmdname"
737	# Run the command, capturing console output as well as logged output.
738	if [[ -z $(type -t "$cmdname") ]] ; then
739	    msg="NOT FOUND"
740	    # 126 is also used for "permission denied", which amounts
741	    # to the same thing -- it's something the user should not
742	    # have tried to run.
743	    stepstatus=126
744	else
745	    if [[ -n ${STP_TEST_RECURSIVE:-} ]] ; then
746		STP_TEST_RECURSIVE=2 STP_TEST_LOG_PREFIX="$local_logdir/" $cmd </dev/null 4>"$tmpfile" 6>&5 5>&2 3>&1 2>&1 | timestamp | log "$logfile"
747	    else
748		STP_TEST_RECURSIVE=1 STP_TEST_LOG_PREFIX="$local_logdir/" $cmd </dev/null 4>"$tmpfile" 5>&2 3>&1 2>&1 | timestamp | log "$logfile"
749	    fi
750	    stepstatus=${PIPESTATUS[0]}
751	    (( stepstatus > 0 )) && msg=FAILED
752	fi
753	local -a emptyfiles=()
754	for f in "$local_logdir"/* ; do
755	    [[ -f $f && ! -s $f ]] && emptyfiles+=("$f")
756	done
757	[[ -n "${emptyfiles[*]:-}" ]] && rm -f -- "${emptyfiles[@]}"
758    else
759	msg='(SKIPPED)'
760    fi
761    local setime=$(date_sec)
762    local sestamp=$(stamp)
763    if [[ -z ${DONTRUN_OP:-} ]] ; then
764	echo "$cmd $msg at $sestamp ($(time_delta "$sstime" "$setime"))" | log "$logfile" "$top_log"
765	echo "----------------------------------------------------------------" | log "$logfile" "$top_log"
766    fi
767    counter=$((counter+1))
768}
769
770declare -a OPERATIONS=(preflight
771		       check_git
772		       check_git_tag
773		       run_maintainer_clean
774		       run_autogen
775		       run_clean
776		       run_build
777		       check_git_builds
778		       run_valgrind_minimal
779		       run_distcheck_fast
780		       run_valgrind
781		       run_full
782		       run_checksums
783		       run_distcheck_minimal
784		       git_prep_release
785		       save_build_artifacts
786		       finis)
787
788function get_longest_op() {
789    local longest_op=0
790    local op
791    for op in "${OPERATIONS[@]}" ; do
792	(( ${#op} > longest_op )) && longest_op=${#op}
793    done
794    echo $((longest_op + 2))
795}
796
797# shellcheck disable=SC2206
798[[ -n ${STP_BUILD_OPERATIONS:-} ]] && OPERATIONS=($STP_BUILD_OPERATIONS)
799[[ -n "$*" ]] && OPERATIONS=("$@")
800
801trap finish EXIT SIGHUP SIGINT SIGQUIT SIGTERM
802
803# shellcheck disable=SC2155
804{
805declare sstamp=$(stamp)
806declare stime=$(date_sec)
807}
808declare toplogdir=${STP_LOG_DIR:-"$ROOT/BuildLogs"}
809declare logdir="$toplogdir/Log.${sstamp// /_}"
810[[ -n ${STP_LOG_NO_SUBDIR:-} ]] && logdir=$toplogdir
811top_log="$logdir/00.Master"
812mkdir -p "$logdir"
813if [[ -z ${STP_LOG_NO_SUBDIR:-} ]] ; then
814     if [[ -L $toplogdir/Current ]] ; then
815	 rm -f -- "$toplogdir/Previous"
816	 mv "$toplogdir/Current" "$toplogdir/Previous"
817     fi
818    ln -s "${logdir##*/}" "$toplogdir/Current"
819fi
820export ARTIFACTDIR="$logdir/Artifacts"
821mkdir -p "$ARTIFACTDIR"
822
823[[ -n $("$GIT" status --porcelain -uno) ]] && git_dirty=' (dirty)'
824log "-$top_log" <<< "================================================================"
825
826log_top <<EOF
827${tbold}*** Gutenprint $build_type build started at $sstamp on $(uname -n)${treset}
828
829Directory:   $ROOT
830Logs:        ${logdir#${ROOT}/}
831Kernel:      $(uname -o) $(uname -rv)
832Revision:    $("$GIT" rev-parse @)$git_dirty
833Parallel:    $STP_PARALLEL
834EOF
835
836[[ -n ${STP_TEST_ROTOR_CIRCUMFERENCE:-} && -n ${STP_TEST_ROTOR:-} ]] &&
837    log_top "Rotor:       $STP_TEST_ROTOR / $STP_TEST_ROTOR_CIRCUMFERENCE"
838
839# Or we could just do "env" here, but having them sorted makes it
840# easier to read.  Variables containing '%%' are functions
841log_top <<EOF
842
843Environment:
844$(while read -r var ; do
845    eval "echo '    '$var=\${$var@Q}"
846done <<< "$(compgen -e)")
847
848CPU information:
849$(lscpu -e)
850$(lscpu)
851
852Memory information:
853$(free)
854
855Running operations:
856$(printf "    %s\n" "${OPERATIONS[@]}")
857EOF
858
859if [[ -n ${TRAVIS_MODE:-} ]] ; then
860    export BUILD_VERBOSE=1
861    travis_deadman&
862fi
863
864declare -A SKIP_OPS
865[[ -n ${STP_BUILD_SKIP:-} ]] &&
866    for o in ${STP_BUILD_SKIP//,/ } ; do SKIP_OPS[$o]=1; done
867declare -i runstatus=0
868# shellcheck disable=SC2155
869declare -i longest_op=$(get_longest_op)
870declare -i lstatus=0
871for op in "${OPERATIONS[@]}" ; do
872    [[ -z ${BUILD_VERBOSE:-} ]] && tmpfile=$(mktemp "/tmp/stpconsole.XXXXXXXX")
873    DONTRUN_OP="${DONTRUN:-}${SKIP_OPS[$op]:-}" runit "$op"
874    lstatus=$stepstatus
875    report_step_status
876    if (( lstatus > 0 )) ; then
877	runstatus=1
878	(( lstatus != 2 )) && break
879    fi
880done
881exitstatus="$runstatus"
882