After playing with "continuable" mode for a while, it seems to me that what most people want will be incremental builds by default (albeit without any dependency checking to rebuild a target if sources change). This lets you run minimal/do several times in a row to incrementally build up your project while you're playing around. If you want to start over, use -c. As a result, in test.do we now recommend using 'minimal/do -c test' since minimal/do now strays even further from proper redo semantics otherwise. While we're here, update the usage message to describe what all the new options mean.
218 lines
4.7 KiB
Bash
Executable file
218 lines
4.7 KiB
Bash
Executable file
#!/bin/sh
|
|
#
|
|
# A minimal alternative to djb redo that doesn't support incremental builds.
|
|
# For the full version, visit http://github.com/apenwarr/redo
|
|
#
|
|
# The author disclaims copyright to this source file and hereby places it in
|
|
# the public domain. (2010 12 14; updated 2018 10 31)
|
|
#
|
|
USAGE="
|
|
usage: $0 [-d] [-x] [-v] [-c] <targets...>
|
|
-d print extra debug messages (mostly about dependency checks)
|
|
-v run .do files with 'set -v'
|
|
-x run .do files with 'set -x'
|
|
-c clean up all old targets before starting
|
|
|
|
Note: $0 is an implementation of redo that does *not* check dependencies.
|
|
It will never rebuild a target it has already built, unless you use -c.
|
|
"
|
|
|
|
# By default, no output coloring.
|
|
green=""
|
|
bold=""
|
|
plain=""
|
|
|
|
if [ -n "$TERM" -a "$TERM" != "dumb" ] && tty <&2 >/dev/null 2>&1; then
|
|
green="$(printf '\033[32m')"
|
|
bold="$(printf '\033[1m')"
|
|
plain="$(printf '\033[m')"
|
|
fi
|
|
|
|
_dirsplit()
|
|
{
|
|
base=${1##*/}
|
|
dir=${1%$base}
|
|
}
|
|
|
|
dirname()
|
|
(
|
|
_dirsplit "$1"
|
|
dir=${dir%/}
|
|
echo "${dir:-.}"
|
|
)
|
|
|
|
_dirsplit "$0"
|
|
export REDO=$(cd "${dir:-.}" && echo "$PWD/$base")
|
|
if [ "$base" = "redo-ifchange" ]; then ifchange=1; else ifchange=; fi
|
|
|
|
DO_TOP=
|
|
if [ -z "$DO_BUILT" ]; then
|
|
export _do_opt_debug=
|
|
export _do_opt_exec=
|
|
export _do_opt_verbose=
|
|
export _do_opt_clean=
|
|
fi
|
|
while getopts 'dxvch?' _opt; do
|
|
case $_opt in
|
|
d) _do_opt_debug=1 ;;
|
|
x) _do_opt_exec=x ;;
|
|
v) _do_opt_verbose=v ;;
|
|
c) _do_opt_clean=1 ;;
|
|
\?|h|*) printf "%s" "$USAGE" >&2
|
|
exit 99
|
|
;;
|
|
esac
|
|
done
|
|
shift "$((OPTIND - 1))"
|
|
_debug() {
|
|
[ -z "$_do_opt_debug" ] || echo "$@" >&2
|
|
}
|
|
|
|
if [ -z "$DO_BUILT" ]; then
|
|
DO_TOP=1
|
|
[ "$#" -gt 0 ] || set all # only toplevel redo has a default target
|
|
export DO_BUILT=$PWD/.do_built
|
|
: >>"$DO_BUILT"
|
|
sort -u "$DO_BUILT" >"$DO_BUILT.new"
|
|
if [ -n "$_do_opt_clean" ]; then
|
|
echo "Removing previously built files..." >&2
|
|
while read f; do printf "%s\0%s.did\0" "$f" "$f"; done <"$DO_BUILT.new" |
|
|
xargs -0 rm -f 2>/dev/null
|
|
fi
|
|
mv "$DO_BUILT.new" "$DO_BUILT"
|
|
DO_PATH=$DO_BUILT.dir
|
|
export PATH=$DO_PATH:$PATH
|
|
rm -rf "$DO_PATH"
|
|
mkdir "$DO_PATH"
|
|
for d in redo redo-ifchange; do
|
|
ln -s "$REDO" "$DO_PATH/$d"
|
|
done
|
|
[ -e /bin/true ] && TRUE=/bin/true || TRUE=/usr/bin/true
|
|
for d in redo-ifcreate redo-stamp redo-always; do
|
|
ln -s $TRUE "$DO_PATH/$d"
|
|
done
|
|
fi
|
|
|
|
|
|
_find_dofile_pwd()
|
|
{
|
|
dofile=default.$1.do
|
|
while :; do
|
|
dofile=default.${dofile#default.*.}
|
|
[ -e "$dofile" -o "$dofile" = default.do ] && break
|
|
done
|
|
ext=${dofile#default}
|
|
ext=${ext%.do}
|
|
base=${1%$ext}
|
|
}
|
|
|
|
|
|
_find_dofile()
|
|
{
|
|
local prefix=
|
|
while :; do
|
|
_find_dofile_pwd "$1"
|
|
[ -e "$dofile" ] && break
|
|
[ "$PWD" = "/" ] && break
|
|
target=${PWD##*/}/$target
|
|
tmp=${PWD##*/}/$tmp
|
|
prefix=${PWD##*/}/$prefix
|
|
cd ..
|
|
done
|
|
base=$prefix$base
|
|
}
|
|
|
|
|
|
_run_dofile()
|
|
{
|
|
export DO_DEPTH="$DO_DEPTH "
|
|
export REDO_TARGET="$PWD/$target"
|
|
local line1
|
|
set -e
|
|
read line1 <"$PWD/$dofile" || true
|
|
cmd=${line1#"#!/"}
|
|
if [ "$cmd" != "$line1" ]; then
|
|
set -$_do_opt_verbose$_do_opt_exec
|
|
/$cmd "$PWD/$dofile" "$@" >"$tmp.tmp2"
|
|
else
|
|
set -$_do_opt_verbose$_do_opt_exec
|
|
:; . "$PWD/$dofile" >"$tmp.tmp2"
|
|
fi
|
|
}
|
|
|
|
|
|
_do()
|
|
{
|
|
local dir="$1" target="$2" tmp="$3"
|
|
if [ -z "$ifchange" ] ||
|
|
( [ ! -e "$target" -o -d "$target" ] &&
|
|
[ ! -e "$target.did" ] ); then
|
|
printf '%sdo %s%s%s%s\n' \
|
|
"$green" "$DO_DEPTH" "$bold" "$dir$target" "$plain" >&2
|
|
echo "$PWD/$target" >>"$DO_BUILT"
|
|
dofile=$target.do
|
|
base=$target
|
|
ext=
|
|
[ -e "$target.do" ] || _find_dofile "$target"
|
|
if [ ! -e "$dofile" ]; then
|
|
echo "do: $target: no .do file" >&2
|
|
return 1
|
|
fi
|
|
[ ! -e "$DO_BUILT" ] || [ ! -d "$(dirname "$target")" ] ||
|
|
: >>"$target.did"
|
|
( _run_dofile "$target" "$base" "$tmp.tmp" )
|
|
rv=$?
|
|
if [ $rv != 0 ]; then
|
|
printf "do: %s%s\n" "$DO_DEPTH" \
|
|
"$dir$target: got exit code $rv" >&2
|
|
rm -f "$tmp.tmp" "$tmp.tmp2"
|
|
return $rv
|
|
fi
|
|
mv "$tmp.tmp" "$target" 2>/dev/null ||
|
|
! test -s "$tmp.tmp2" ||
|
|
mv "$tmp.tmp2" "$target" 2>/dev/null
|
|
rm -f "$tmp.tmp2"
|
|
else
|
|
_debug "do $DO_DEPTH$target exists." >&2
|
|
fi
|
|
}
|
|
|
|
|
|
# Make corrections for directories that don't actually exist yet.
|
|
_dir_shovel()
|
|
{
|
|
local dir base
|
|
xdir=$1 xbase=$2 xbasetmp=$2
|
|
while [ ! -d "$xdir" -a -n "$xdir" ]; do
|
|
_dirsplit "${xdir%/}"
|
|
xbasetmp=${base}__$xbase
|
|
xdir=$dir xbase=$base/$xbase
|
|
_debug "xbasetmp='$xbasetmp'" >&2
|
|
done
|
|
}
|
|
|
|
|
|
_redo()
|
|
{
|
|
set +e
|
|
for i in "$@"; do
|
|
_dirsplit "$i"
|
|
_dir_shovel "$dir" "$base"
|
|
dir=$xdir base=$xbase basetmp=$xbasetmp
|
|
( cd "$dir" && _do "$dir" "$base" "$basetmp" )
|
|
[ "$?" = 0 ] || return 1
|
|
done
|
|
}
|
|
|
|
|
|
_redo "$@"
|
|
[ "$?" = 0 ] || exit 1
|
|
|
|
if [ -n "$DO_TOP" ]; then
|
|
if [ -n "$_do_opt_clean" ]; then
|
|
echo "Removing stamp files..." >&2
|
|
[ ! -e "$DO_BUILT" ] ||
|
|
while read f; do printf "%s.did\0" "$f"; done <"$DO_BUILT" |
|
|
xargs -0 rm -f 2>/dev/null
|
|
fi
|
|
fi
|