implemented. Why yet another implementation? It is feature full and has
better performance comparing to shell and Python implementation.
+It passes tests from dieweltistgarnichtso.net's redo-sh.tests and
+implementation-neutral from apenwarr/redo.
+
goredo is free software: see the file COPYING for copying conditions.
Home page: http://www.goredo.cypherpunks.ru/
$ git clone git://git.cypherpunks.ru/goredo.git
$ cd goredo
- $ git tag -v v0.5.0
+ $ git tag -v v0.6.0
$ git clone git://git.cypherpunks.ru/gorecfile.git
$ ( cd gorecfile ; git tag -v v0.3.0 )
$ echo "replace go.cypherpunks.ru/recfile => `pwd`/gorecfile" >> go.mod
$REDO_TOP_DIR environment variable, or by having .redo/top file in it
* target's completion messages are written after they finish
* executable .do is run as is, non-executable is run with /bin/sh -e[x]
-* tracing (-x) can be done only for non-executable .do
+* tracing (-x) can be obviously done only for non-executable .do
+* parallizable build is done only during redo-ifchange for human
+ convenience: you can globally enable REDO_JOBS, but still do for
+ example: redo htmls infos index upload
FEATURES *goredo-features*
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+apenwarr/wrapper.rc
\ No newline at end of file
--- /dev/null
+rm -f log
+redo fatal >/dev/null 2>&1 || true
+
+[ "$(cat log)" = "ok" ] || exit 5
--- /dev/null
+rm -f log *~ .*~
--- /dev/null
+rm -f log
+echo ok >>log
+this-should-cause-a-fatal-error
+echo fail >>log # this line should never run
--- /dev/null
+*.end
+*.start
+*.sub
+*.spin
+*.log
+*.x
--- /dev/null
+# We put the -j options at this toplevel to detect an earlier bug
+# where the sub-jobserver wasn't inherited by sub-sub-processes, which
+# accidentally reverted to the parent jobserver instead.
+
+redo -j 1 serialtest
--- /dev/null
+rm -f *~ .*~ *.log *.sub *.spin *.x *.start *.end \
+ first second parallel parallel2
--- /dev/null
+for d in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19; do
+ redo $2.$d.x
+done
+echo hello
--- /dev/null
+echo world
--- /dev/null
+# in case we're (erroneously) running in parallel, give second.do some
+# time to start but not finish.
+echo 'first sleep' >&2
+sleep 1
+
+# Because of --shuffle, we can't be sure if first or second ran first, but
+# because all.do uses -j1, we *should* expect that if second ran first, it
+# at least ran to completion before we ran at all.
+if [ -e second.start ]; then
+ echo 'first: second already started before we did...' >&2
+ [ -e second.end ] || exit 21
+ echo 'first: ...and it finished as it should.' >&2
+ # no sense continuing the test; can't test anything if second already
+ # ran.
+ exit 0
+fi
+echo 'first: second has not started yet, good.' >&2
+
+echo 'first spin' >&2
+redo 1.a.spin
+[ -e 1.a.spin ] || exit 11
+echo 'first spin complete' >&2
+
+! [ -e second.start ] || exit 22
--- /dev/null
+rm -f $1.start $1.end
+echo 'second start' >&2
+: >$1.start
+redo 2.x
+echo 'second sleep' >&2
+redo-ifchange first # wait until 'first' finishes, if it's running
+echo 'second end' >&2
+: >$1.end
--- /dev/null
+# Test that -j1 really serializes all sub-redo processes.
+rm -f *.sub *.spin *.x first second *.start *.end
+redo first second
--- /dev/null
+redo test.args test2.args passfailtest noargs/run
--- /dev/null
+rm -f passfail *~ .*~ */*~ */.*~ noargs/all
--- /dev/null
+[ "$1" = "test.args" ]
+[ "$2" = "test" ]
+[ "$3" != "test.args" ]
--- /dev/null
+echo RAN >$3
--- /dev/null
+rm -f all
+redo-ifchange # should not default to 'all' since not running from top level
+[ ! -e all ] || exit 11
--- /dev/null
+echo $$
+if [ -e pleasefail ]; then
+ exit 1
+else
+ exit 0
+fi
--- /dev/null
+rm -f pleasefail
+redo passfail
+[ -e passfail ] || exit 42
+PF1=$(cat passfail)
+touch pleasefail
+redo passfail 2>/dev/null && exit 43
+[ -e passfail ] || exit 44
+PF2=$(cat passfail)
+[ "$PF1" = "$PF2" ] || exit 45
+rm -f pleasefail
+redo passfail || exit 46
+PF3=$(cat passfail)
+[ "$PF1" != "$PF3" ] || exit 47
--- /dev/null
+[ "$1" = "test2.args" ]
+[ "$2" = "test2.args" ]
+[ "$3" != "test2.args" ]
--- /dev/null
+redo atime
--- /dev/null
+redo atime2
--- /dev/null
+# make sure redo doesn't think merely *reading* the old file counts as
+# modifying it in-place.
+cat $1 >/dev/null 2>/dev/null || true
+${PYTHON:-python} tick.py
+cat $1 >/dev/null 2>/dev/null || true
+echo hello
--- /dev/null
+rm -f atime2 *~ .*~
--- /dev/null
+import time
+t2 = int(time.time()) + 1.0
+while 1:
+ t = time.time()
+ if t >= t2: break
+ time.sleep(t2 - t + 0.01)
--- /dev/null
+touch1
+silence
+silence.do
--- /dev/null
+redo silencetest touchtest blank
--- /dev/null
+redo-ifchange
+redo-ifcreate
+redo
--- /dev/null
+rm -f touch1 touch1-ran *~ .*~ silence.do
--- /dev/null
+# This may have been leftover from a previous run, when switching
+# between "real" redo and minimal/do, so clean it up.
+rm -f silence
+
+echo 'echo hello' >silence.do
+redo silence
+[ -e silence ] || exit 55
+echo 'true' >silence.do
+redo silence
+[ ! -e silence ] || exit 66
+rm -f silence.do
--- /dev/null
+# This may have been leftover from a previous run, when switching
+# between "real" redo and minimal/do, so clean it up.
+rm -f touch1
+
+# simply create touch1
+echo 'echo hello' >touch1.do
+redo touch1
+[ -e touch1 ] || exit 55
+[ "$(cat touch1)" = "hello" ] || exit 56
+
+# ensure that 'redo touch1' always re-runs touch1.do even if we have
+# already built touch1 in this session, and even if touch1 already exists.
+echo 'echo hello2' >touch1.do
+redo touch1
+[ "$(cat touch1)" = "hello2" ] || exit 57
+
+# ensure that touch1 is rebuilt even if it got deleted after the last redo
+# inside the same session. Also ensure that we can produce a zero-byte
+# output file explicitly.
+rm -f touch1
+echo 'touch $3' >touch1.do
+redo touch1
+[ -e touch1 ] || exit 66
+[ -z "$(cat touch1)" ] || exit 67
+
+# Also test that zero bytes of output does not create the file at all, as
+# opposed to creating a zero-byte file.
+rm -f touch1
+echo 'touch touch1-ran' >touch1.do
+redo touch1
+[ -e touch1 ] && exit 75
+[ -e touch1-ran ] || exit 76
+rm -f touch1-ran
+
+# Make sure that redo-ifchange *won't* rebuild touch1 if we have already
+# built it, even if building it did not produce an output file.
+redo-ifchange touch1
+[ -e touch1 ] && exit 77
+[ -e touch1-ran ] && exit 78
+
+rm -f touch1.do
--- /dev/null
+redo unicode
--- /dev/null
+rm -rf *.tmp
--- /dev/null
+# Test that redo can handle a script whose path contains non-ASCII characters.
+# Note: the test directory is intentionally *not* a normalized unicode
+# string, ie. filesystems like macOS will convert it to a different string
+# at creation time. This tests weird normalization edge cases.
+#
+# Unfortunately, on macOS with APFS, it may helpfully normalize the path at
+# *create* time, but not on future *open* attempts. Thus, we let the shell
+# figure out what directory name actually got created, then pass that to redo.
+# Hence the weird wildcard expansion loop.
+rm -rf test-uni*.tmp
+mkdir "test-uniçøðë.tmp"
+for p in test-uni*.tmp; do
+ : >$p/test1.do
+ redo "$p/test1"
+done
+
--- /dev/null
+redo "space dir/test"
--- /dev/null
+redo "space dir/clean"
--- /dev/null
+/space file
+/space 2
--- /dev/null
+rm -f *~ .*~ "space 2"
--- /dev/null
+redo-ifchange "space 2"
--- /dev/null
+redo "space file"
+F1="$(cat "space 2")"
+redo "../space dir/space file"
+F2="$(cat "space 2")"
+[ "$F1" = "$F2" ] || exit 2
+[ -n "$F1" ] || exit 3
--- /dev/null
+/*.dyn
+/src
+/x
+/y
+
--- /dev/null
+rm -f src
+: >src
+
+for iter in 10 20; do
+ rm -rf y
+ rm -f x *.dyn static
+ mkdir y
+ : >y/static
+ ln -s . y/x
+
+ (
+ cd y/x/x/x/x/x
+ IFS=$(printf '\n')
+ redo-ifchange static x/x/x/static $PWD/static \
+ $(/bin/pwd)/static /etc/passwd
+ # goredo: that symlink path is not resolving even at OS level
+ # redo-ifchange $PWD/../static 2>/dev/null && exit 35
+ redo-ifchange 1.dyn x/x/x/2.dyn $PWD/3.dyn \
+ $PWD/../4.dyn $(/bin/pwd)/5.dyn
+ )
+ [ -e y/1.dyn ] || exit $((iter + 1))
+ [ -e y/2.dyn ] || exit $((iter + 2))
+ [ -e y/3.dyn ] || exit $((iter + 3))
+ # [ -e 4.dyn ] || exit $((iter + 4))
+ [ -e y/5.dyn ] || exit $((iter + 5))
+done
--- /dev/null
+rm -rf y
+rm -f src x *.dyn *~ .*~
--- /dev/null
+redo-ifchange src
+echo dynamic >$3
--- /dev/null
+CC
+LD
+[yb]ellow
+hello
+*.o
--- /dev/null
+exec >$3
+cat <<-EOF
+ cc -Wall -o /dev/fd/1 -c "\$1"
+EOF
+chmod a+x $3
--- /dev/null
+exec >$3
+cat <<-EOF
+ OUT="\$1"
+ shift
+ cc -Wall -o "\$OUT" "\$@"
+EOF
+chmod a+x $3
--- /dev/null
+if type cc >/dev/null 2>&1; then
+ redo-ifchange hello yellow bellow
+else
+ echo "$0: No C compiler installed; skipping this test." >&2
+ redo-ifcreate /usr/bin/cc
+fi
--- /dev/null
+redo-ifchange LD yellow.o
+./LD "$3" yellow.o
+../sleep 2
--- /dev/null
+rm -f hello [by]ellow *.o CC LD *~ .*~
+
+
--- /dev/null
+#include <stdio.h>
+
+int main()
+{
+ printf("hello, world!\n");
+ return 0;
+}
--- /dev/null
+redo-ifchange LD hello.o
+../sleep 1
+./LD "$3" hello.o
--- /dev/null
+# This test is meant to confirm some basic redo functionality
+# related to static files not in the build tree. But if your
+# system doesn't happen to have stdio.h in the usual location,
+# let's not explode just for that.
+stdio=/usr/include/stdio.h
+[ -e "$stdio" ] || stdio=
+
+redo-ifchange CC hello.c $stdio
+redo-ifcreate stdio.h
+../sleep 3
+./CC hello.c
--- /dev/null
+redo-ifchange LD yellow.o
+../sleep 1.5
+./LD "$3" yellow.o
--- /dev/null
+redo-ifchange CC hello.c
+../sleep 2
+cc -o $3 -c hello.c
--- /dev/null
+*.o
+CC
+hello
--- /dev/null
+redo-ifchange config.sh
+. ./config.sh
+exec >$3
+cat <<-EOF
+ redo-ifchange \$2.c
+ cc $CFLAGS -MD -MF \$3.deps -o \$3 -c \$2.c
+ read DEPS <\$3.deps
+ rm -f \$3.deps
+ redo-ifchange \${DEPS#*:}
+EOF
+chmod +x $3
--- /dev/null
+all:
+
+%: FORCE
+ +redo $@
+
+.PHONY: FORCE
--- /dev/null
+if type cc >/dev/null 2>&1; then
+ redo-ifchange hello
+else
+ echo "$0: No C compiler installed; skipping this test." >&2
+ redo-ifcreate /usr/bin/cc
+fi
--- /dev/null
+rm -f *.tmp *~ *.o hello CC
--- /dev/null
+CFLAGS="-Wall"
--- /dev/null
+redo-ifchange CC
+. ./CC "$@"
--- /dev/null
+DEPS="main.o
+mystr.o"
+redo-ifchange $DEPS
+cc -o $3 $DEPS
--- /dev/null
+#include <stdio.h>
+#include "mystr.h"
+
+int main()
+{
+ printf("%s\n", mystr);
+ return 0;
+}
--- /dev/null
+
+#include "mystr.h"
+
+char *mystr = "Hello, world!";
--- /dev/null
+#ifndef __MYSTR_H
+#define __MYSTR_H
+
+extern char *mystr;
+
+#endif
--- /dev/null
+c
+c.c
+c.c.c
+c.c.c.b
+c.c.c.b.b
+d
--- /dev/null
+redo-ifchange c d
--- /dev/null
+redo-ifchange $1.c
+echo c.do
+cat $1.c
--- /dev/null
+rm -f c c.c c.c.c c.c.c.b c.c.c.b.b d \
+ *~ .*~
--- /dev/null
+if [ -e "$1.a" -o -e "default${1#$2}.a" ]; then
+ redo-ifchange "$1.a"
+ echo a-to-b
+ cat "$1.a"
+else
+ redo-ifchange "$1.b"
+ echo b-to-b
+ cat "$1.b"
+fi
+../sleep 1.1
--- /dev/null
+redo-ifchange $1.b
+echo b-to-cc
+cat $1.b
+../sleep 1.2
--- /dev/null
+redo-ifchange $1.c
+echo c-to-c
+cat $1.c
+../sleep 1.3
--- /dev/null
+redo-ifchange c
+echo default-rule
+cat c
+../sleep 1.4
--- /dev/null
+/a/b/file
+/a/b/file.x.y.z
+/a/b/file.y.z
+/a/b/file.z
+/a/d/file
+/a/d/file.x.y.z
+/a/d/file.y.z
+/a/d/file.z
+/a/file
+/a/file.x.y.z
+/a/file.y.z
+/a/file.z
+/file.x.y.z
+/file.z
+/file
--- /dev/null
+echo default.y.z $2 ${1#$2}
--- /dev/null
+echo file $2 ${1#$2}
--- /dev/null
+echo default $2 ${1#$2}
--- /dev/null
+echo default.x.y.z $2 ${1#$2}
+
--- /dev/null
+echo default.z $2 ${1#$2}
--- /dev/null
+exec >&2
+find . -name '*~' -exec rm -f {} \;
+rm -f a/b/file a/b/file.x.y.z a/b/file.y.z a/b/file.z \
+ a/d/file a/d/file.x.y.z a/d/file.y.z a/d/file.z \
+ a/file a/file.x.y.z a/file.y.z a/file.z \
+ file.x.y.z file.z file
--- /dev/null
+echo root $2 ${1#$2} "$(dirname $3)"
--- /dev/null
+exec >&2
+redo-ifchange \
+ file.x.y.z file.z file \
+ a/b/file.x.y.z a/b/file.y.z a/b/file.z a/b/file \
+ a/d/file.x.y.z a/d/file.y.z a/d/file.z a/d/file
+
+(cd a/b && redo-ifchange ../file.x.y.z ../file.y.z ../file.z ../file)
+
+check()
+{
+ if [ "$(cat $1)" != "$2" ]; then
+ echo "$1 should contain '$2'"
+ echo " ...got '$(cat $1)'"
+ exit 44
+ fi
+}
+
+check file.x.y.z "root file.x.y.z ."
+check file.z "root file.z ."
+check file "root file ."
+
+check a/file.x.y.z "default.x.y.z file .x.y.z"
+check a/file.y.z "default.z file.y .z"
+check a/file.z "default.z file .z"
+check a/file "root a/file a"
+
+check a/b/file.x.y.z "file file.x.y.z"
+check a/b/file.y.z "default.y.z file .y.z"
+check a/b/file.z "default.z b/file .z"
+check a/b/file "root a/b/file a/b"
+
+check a/d/file.x.y.z "default file.x.y.z"
+check a/d/file.y.z "default file.y.z"
+check a/d/file.z "default file.z"
+check a/d/file "default file"
+
--- /dev/null
+rm -f x/shouldfail
+
+log=$PWD/$1.log
+
+expect_fail() {
+ local rv=$1
+ shift
+ if ("$@") >>$log 2>&1; then
+ cat "$log" >&2
+ echo "unexpected success:" "$@" >&2
+ return $rv
+ else
+ return 0
+ fi
+}
+
+# These should all fail because there is no matching .do file.
+# In previous versions of redo, it would accidentally try to use
+# $PWD/default.do even for ../path/file, which is incorrect. That
+# could cause it to return success accidentally.
+
+rm -f "$log"
+cd inner
+expect_fail 11 redo ../x/shouldfail
+expect_fail 12 redo-ifchange ../x/shouldfail
+
+rm -f "$log"
+cd ../inner2
+expect_fail 21 redo ../x/shouldfail2
+expect_fail 22 redo-ifchange ../x/shouldfail2
+
+exit 0
--- /dev/null
+rm -f *~ .*~ */*~ */.*~ *.tmp */*.tmp x/shouldfail *.log */*.log
+
--- /dev/null
+echo "inner/default.do: PWD=$PWD '$1' '$2' '$3'" >&2
+echo SUSPICIOUS
--- /dev/null
+echo "inner/default.do: PWD=$PWD '$1' '$2' '$3'" >&2
+# output file is left empty
--- /dev/null
+mode1
\ No newline at end of file
--- /dev/null
+umask 0022
+redo mode1
+MODE=$(${PYTHON:-python} -c \
+ 'import os; print(oct(os.stat("mode1").st_mode & 0o7777))')
+[ "$MODE" = "0644" -o "$MODE" = "0o644" ] || exit 78
--- /dev/null
+rm -f mode1 *~ .*~
--- /dev/null
+echo hello
--- /dev/null
+Those tests are taken from https://github.com/apenwarr/redo.
+
+* Only basic ([01]*) tests taken, because other ones are too apenwarr
+ implementation specific.
+* 140-shuffle and 141-keep-going removed, because apenwarr specific
+* flush-cache removed, as it works directly with apenwarr's database
+* redo/sh and redo/py targets replaced with /bin/sh and ${PYTHON}
+* skip-if-minimal-do.sh removed
+* 010-jobserver/parallel* removed, because goredo intentionally does not
+ parallelize targets building through plain redo invocation for human
+ convenience
+* one of 105-sympath tests commented out, because its symlink path can
+ not be resolved even at OS level
--- /dev/null
+rm -f 000-set-minus-e/log
+redo 000-set-minus-e/all
+if [ "$(cat 000-set-minus-e/log)" != "ok" ]; then
+ echo "FATAL! .do file not run with 'set -e' in effect!" >&2
+ exit 5
+fi
+
+/bin/ls 1[0-9][0-9]*/all.do | sed 's/\.do$//' | xargs redo
+110-compile/hello >&2
--- /dev/null
+/bin/ls [0-9s][0-9][0-9]*/clean.do |
+sed 's/\.do$//' |
+xargs redo
--- /dev/null
+PATH=/bin:/usr/bin
+if [ -n "$SLEEP" ]; then
+ exec sleep "$@"
+fi
--- /dev/null
+#!/bin/sh
+
+testname=`basename "$0"`
+testname=${testname#apenwarr-}
+testname=${testname%.t}
+testdir=`dirname $0`/apenwarr/$testname
+cd $testdir
+testdir=`pwd`
+test_description="none"
+. $SHARNESS_TEST_SRCDIR/sharness.sh
+cd $testdir
+test_expect_success itself redo
+test_done
-rm -f *.t
-[ -e redo-sh.tests ] || exit 0
-cd redo-sh.tests/
-redo-cleanup full >&2
-find . -type f ! -name test -delete
+redo apenwarr/clean redo-sh.tests/clean
+for d in apenwarr redo-sh.tests ; do ( cd $d ; redo-cleanup full >/dev/null ) ; done
+++ /dev/null
-tests=redo-sh.tests
-[ -e $tests ] || {
- cat >&2 <<EOF
-No $tests found. Provide them manually, for example with:
- $ git clone http://news.dieweltistgarnichtso.net/bin.git
- $ cd bin
- $ git checkout 6e45ad16ad0513d1a6a4a0539a9d01d39d3e7490
- $ ln -s `pwd`/redo-sh.tests /path/to/goredo/t/redo.sh.tests
-EOF
- exit 1
-}
-( cd $tests ; ls ) | while read testname ; do
- ln -s runner.rc ${testname}.t
-done
-
-echo Skipping parallel_2.t, that assumes --jobs option availability >&2
-rm parallel_2.t
--- /dev/null
+Those tests are taken from http://news.dieweltistgarnichtso.net/bin.git.
+Test parallel_2 removed, because goredo has no --jobs.
+Test always_rebuild_1 removed, because always-target is rebuilt once per run.
--- /dev/null
+for f in * ; do
+ [ -d $f ] || continue
+ find $f ! -name test -delete
+done
--- /dev/null
+#!/bin/sh -eu
+# A target built with a default dofile in the same directory must be rebuilt if a more specific dofile appears.
+
+test -e a.b.c && \
+ rm -- a.b.c
+
+>all.do cat <<EOF
+redo-ifchange a.b.c
+EOF
+
+dofiles="default.do default.c.do default.b.c.do a.b.c.do"
+
+for dofile in ${dofiles}; do
+ [ -e "${dofile}" ] && \
+ rm -- "${dofile}" || :
+done
+
+for dofile in ${dofiles}; do
+ >"${dofile}" cat <<EOF
+printf '${dofile}\n'
+EOF
+
+ redo
+ <a.b.c read -r text
+ if ! [ "${text}" = "${dofile}" ]; then
+ >&2 printf 'a.b.c was built with %s, should have been built with %s\n' \
+ "${text}" "${dofile}"
+ exit 1
+ fi
+done
--- /dev/null
+#!/bin/sh -eu
+# A target built with a default dofile in the parent directory must be rebuilt if a more specific dofile appears.
+
+[ -e dir ] \
+ || mkdir dir
+
+[ -e default.do ] \
+ && rm default.do
+
+[ -e dir/default.do ] \
+ && rm dir/default.do
+
+[ -e dir/a.do ] \
+ && rm dir/a.do
+
+>default.do cat <<EOF
+printf '1\n'
+EOF
+
+redo dir/a
+
+<dir/a read -r number_a1
+
+>dir/default.do cat <<EOF
+printf '2\n'
+EOF
+
+redo dir/a
+
+<dir/a read -r number_a2
+
+>dir/a.do cat <<EOF
+printf '3\n'
+EOF
+
+redo dir/a
+
+<dir/a read -r number_a3
+
+test 1 -eq "${number_a1}"
+test 2 -eq "${number_a2}"
+test 3 -eq "${number_a3}"
--- /dev/null
+#!/bin/sh -eu
+# A target must not be rebuilt when a non-existence dependency does not exist.
+
+>all.do printf 'redo-ifchange a\n'
+>a.do printf 'test -e b || redo-ifcreate b\ndate +%s\n'
+
+redo
+<a read -r timestamp_a1
+
+sleep 1
+
+redo
+<a read -r timestamp_a2
+
+test ${timestamp_a2} -eq ${timestamp_a1}
--- /dev/null
+#!/bin/sh -eu
+# A target must be rebuilt when a non-existence dependency exists.
+
+>all.do printf 'redo-ifchange a\n'
+>a.do printf 'test -e b || redo-ifcreate b\ndate +%s\n'
+
+redo
+<a read -r timestamp_a1
+
+>b :
+sleep 1
+
+redo
+<a read -r timestamp_a2
+
+test ${timestamp_a2} -gt ${timestamp_a1}
--- /dev/null
+#!/bin/sh -eu
+# A target must not be rebuilt when a dependency does not change.
+>all.do printf 'redo-ifchange a\n'
+>a.do printf 'redo-ifchange b\ndate +%s\n'
+>b printf '1'
+
+redo
+<a read -r timestamp_a1
+
+sleep 1
+>b printf '1'
+
+redo
+<a read -r timestamp_a2
+
+test ${timestamp_a2} -eq ${timestamp_a1}
--- /dev/null
+#!/bin/sh -eu
+# A target must not be rebuilt when a dependency of a dependency does not change.
+>all.do printf 'redo-ifchange a\n'
+>a.do printf 'redo-ifchange b\ndate +%s\n'
+>b.do printf 'redo-ifchange c\ndate +%s\n'
+>c printf '1'
+
+redo
+<a read -r timestamp_a1
+<b read -r timestamp_b1
+
+sleep 1
+>c printf '1'
+
+redo
+<a read -r timestamp_a2
+<b read -r timestamp_b2
+
+test ${timestamp_a2} -eq ${timestamp_a1}
+test ${timestamp_b2} -eq ${timestamp_b1}
--- /dev/null
+#!/bin/sh -eu
+# A target must not be rebuilt when a dependency of a dependency of a dependency does not change.
+>all.do printf 'redo-ifchange a\n'
+>a.do printf 'redo-ifchange b\ndate +%s\n'
+>b.do printf 'redo-ifchange c\ndate +%s\n'
+>c.do printf 'redo-ifchange d\ndate +%s\n'
+>d printf '1'
+
+redo
+<a read -r timestamp_a1
+<b read -r timestamp_b1
+<c read -r timestamp_c1
+
+sleep 1
+>d printf '1'
+
+redo
+<a read -r timestamp_a2
+<b read -r timestamp_b2
+<c read -r timestamp_c2
+
+test ${timestamp_a2} -eq ${timestamp_a1}
+test ${timestamp_b2} -eq ${timestamp_b1}
+test ${timestamp_c2} -eq ${timestamp_c1}
--- /dev/null
+#!/bin/sh -eu
+# A target must be rebuilt when a dependency changes.
+>all.do printf 'redo-ifchange a\n'
+>a.do printf 'redo-ifchange b\ndate +%s\n'
+>b printf '1'
+
+redo
+<a read -r timestamp_a1
+
+sleep 1
+>b printf '2'
+
+redo
+<a read -r timestamp_a2
+
+test ${timestamp_a2} -gt ${timestamp_a1}
--- /dev/null
+#!/bin/sh -eu
+# A target must be rebuilt when a dependency of a dependency changes.
+>all.do printf 'redo-ifchange a\n'
+>a.do printf 'redo-ifchange b\ndate +%s\n'
+>b.do printf 'redo-ifchange c\ndate +%s\n'
+>c printf '1'
+
+redo
+<a read -r timestamp_a1
+<b read -r timestamp_b1
+
+sleep 1
+>c printf '2'
+
+redo
+<a read -r timestamp_a2
+<b read -r timestamp_b2
+
+test ${timestamp_a2} -gt ${timestamp_a1}
+test ${timestamp_b2} -gt ${timestamp_b1}
--- /dev/null
+#!/bin/sh -eu
+# A target must be rebuilt when a dependency of a dependency of a dependency changes.
+>all.do printf 'redo-ifchange a\n'
+>a.do printf 'redo-ifchange b\ndate +%s\n'
+>b.do printf 'redo-ifchange c\ndate +%s\n'
+>c.do printf 'redo-ifchange d\ndate +%s\n'
+>d printf '1'
+
+redo
+<a read -r timestamp_a1
+<b read -r timestamp_b1
+<c read -r timestamp_c1
+
+sleep 1
+>d printf '2'
+
+redo
+<a read -r timestamp_a2
+<b read -r timestamp_b2
+<c read -r timestamp_c2
+
+test ${timestamp_a2} -gt ${timestamp_a1}
+test ${timestamp_b2} -gt ${timestamp_b1}
+test ${timestamp_c2} -gt ${timestamp_c1}
--- /dev/null
+#!/bin/sh -eu
+# A target built by a dofile built during the build must be built.
+
+test -e a.do && rm a.do
+test -e a && rm a
+
+>a.do.do cat <<DODO
+cat <<DO
+date +%s
+DO
+DODO
+
+redo a.do
+redo a
+
+test -e a.do
+test -e a
--- /dev/null
+#!/bin/sh -eu
+# A target built by a dofile built during the build by a dofile built during the build must be built.
+
+test -e a.do.do && rm a.do.do
+test -e a.do && rm a.do
+test -e a && rm a
+
+>a.do.do.do cat <<DODODO
+cat <<DODO
+cat <<DO
+date +%s
+DO
+DODO
+DODODO
+
+redo a.do.do
+redo a.do
+redo a
+
+test -e a.do.do
+test -e a.do
+test -e a
--- /dev/null
+#!/bin/sh -eu
+# Targets must be built in parallel if “-j” option is given.
+
+targets="a b c d e f g h "
+num_targets=$(( ${#targets} / 2 ))
+
+>all.do cat <<EOF
+redo-ifchange ${targets}
+EOF
+
+>default.do cat <<EOF
+redo-always
+sleep 1
+EOF
+
+for jobs in 4 8; do
+ timestamp_before=$(date +%s)
+ redo -j "${jobs}"
+ timestamp_after=$(date +%s)
+
+ duration_observed=$(( ${timestamp_after} - ${timestamp_before} ))
+ duration_expected=$(( ${num_targets} / ${jobs} + 1 ))
+
+ test ${duration_observed} -le ${duration_expected}
+done
--- /dev/null
+#!/bin/sh -eux
+# When invoked with an absolute path name as an argument, redo-whichdo
+# must output non-existent dofiles in the same directory until it
+# outputs one that exists.
+
+dofiles="default.do default.c.do default.b.c.do a.b.c.do"
+
+for dofile in ${dofiles}; do
+ if test -e "${dofile}"; then
+ rm -- "${dofile}"
+ fi
+done
+
+for dofile in ${dofiles}; do
+ >"${dofile}" cat <<EOF
+printf '${dofile}\n'
+EOF
+
+ whichdo_dofiles=$(
+ redo-whichdo "${PWD}/a.b.c" \
+ |tr '\0' '\n'
+ )
+
+ whichdo_dofiles_nonexistent=$(
+ printf '%s\n' "${whichdo_dofiles}" \
+ |sed '$d' # BusyBox v.1.19.3 has no “head -n-1”
+ )
+ for whichdo_dofile_nonexistent in ${whichdo_dofiles_nonexistent}; do
+ if test -e "${whichdo_dofile_nonexistent}"; then
+ >&2 printf 'redo-whichdo prints %s as nonexistent dofile, but it exists.\n' \
+ "${whichdo_dofile_nonexistent}"
+ exit 1
+ fi
+ done
+
+ whichdo_dofile_existent=$(
+ printf '%s\n' "${whichdo_dofiles}" \
+ |tail -n1
+ )
+ if ! test -e "${whichdo_dofile_existent}"; then
+ >&2 printf 'redo-whichdo prints %s as existent dofile, but it does not exist.\n' \
+ "${whichdo_dofile_existent}"
+ exit 1
+ fi
+
+done
--- /dev/null
+#!/bin/sh -eu
+# When invoked with an absolute path name as an argument, redo-whichdo
+# must output non-existent dofiles in the parent directory and the
+# same directory until it outputs one that exists.
+
+if ! test -d a; then
+ mkdir a
+fi
+
+dofiles="default.do default.c.do default.b.c.do a/default.do a/default.c.do a/default.b.c.do a/a.b.c.do"
+
+for dofile in ${dofiles}; do
+ if test -e "${dofile}"; then
+ rm -- "${dofile}"
+ fi
+done
+
+for dofile in ${dofiles}; do
+ >"${dofile}" cat <<EOF
+printf '${dofile}\n'
+EOF
+
+ whichdo_dofiles=$(
+ redo-whichdo "${PWD}/a.b.c" \
+ |tr '\0' '\n'
+ )
+
+ whichdo_dofiles_nonexistent=$(
+ printf '%s\n' "${whichdo_dofiles}" \
+ |sed '$d' # BusyBox v.1.19.3 has no “head -n-1”
+ )
+ for whichdo_dofile_nonexistent in ${whichdo_dofiles_nonexistent}; do
+ if test -e "${whichdo_dofile_nonexistent}"; then
+ >&2 printf 'redo-whichdo prints %s as nonexistent dofile, but it exists.\n' \
+ "${whichdo_dofile_nonexistent}"
+ exit 1
+ fi
+ done
+
+ whichdo_dofile_existent=$(
+ printf '%s\n' "${whichdo_dofiles}" \
+ |tail -n1
+ )
+ if ! test -e "${whichdo_dofile_existent}"; then
+ >&2 printf 'redo-whichdo prints %s as existent dofile, but it does not exist.\n' \
+ "${whichdo_dofile_existent}"
+ exit 1
+ fi
+
+done
--- /dev/null
+#!/bin/sh -eu
+# When invoked with an absolute path name with the prefix “default” as
+# an argument, redo-whichdo must not output any dofile filename twice.
+
+default_files="default default.a default.a.b default.do default.a.do default.a.b.do"
+
+for default_file in ${default_files}; do
+ whichdo_dofiles_duplicates=$(
+ redo-whichdo "${PWD}/${default_file}" \
+ |tr '\0' '\n' \
+ |sort \
+ |uniq -d
+ )
+ case "${whichdo_dofiles_duplicates}" in
+ '')
+ ;;
+ *)
+ >&2 printf 'redo-whichdo duplicate output: %s \n' ${whichdo_dofiles_duplicates}
+ exit 1
+ ;;
+ esac
+done
--- /dev/null
+#!/bin/sh -eu
+# When invoked with an absolute filename for the file “default.do” as
+# an argument, redo-whichdo must not output that filename as possible
+# dofile.
+
+default_dofile="${PWD}/default.do"
+
+whichdo_dofiles=$(
+ redo-whichdo "${default_dofile}" \
+ |tr '\0' '\n'
+)
+
+for whichdo_dofile in ${whichdo_dofiles}; do
+ case "${whichdo_dofile}" in
+ "${default_dofile}")
+ >&2 printf 'redo-whichdo outputs that this file is its own dofile: %s\n' \
+ "${whichdo_dofile}"
+ exit 1
+ ;;
+ esac
+done
--- /dev/null
+#!/bin/sh -eu
+# When invoked with a relative path name as an argument, redo-whichdo
+# must output non-existent dofiles in the same directory until it
+# outputs one that exists.
+
+dofiles="default.do default.c.do default.b.c.do a.b.c.do"
+
+for dofile in ${dofiles}; do
+ if test -e "${dofile}"; then
+ rm -- "${dofile}"
+ fi
+done
+
+for dofile in ${dofiles}; do
+ >"${dofile}" cat <<EOF
+printf '${dofile}\n'
+EOF
+
+ whichdo_dofiles=$(
+ redo-whichdo a.b.c \
+ |tr '\0' '\n'
+ )
+
+ whichdo_dofiles_nonexistent=$(
+ printf '%s\n' "${whichdo_dofiles}" \
+ |sed '$d' # BusyBox v.1.19.3 has no “head -n-1”
+ )
+ for whichdo_dofile_nonexistent in ${whichdo_dofiles_nonexistent}; do
+ if test -e "${whichdo_dofile_nonexistent}"; then
+ >&2 printf 'redo-whichdo prints %s as nonexistent dofile, but it exists.\n' \
+ "${whichdo_dofile_nonexistent}"
+ exit 1
+ fi
+ done
+
+ whichdo_dofile_existent=$(
+ printf '%s\n' "${whichdo_dofiles}" \
+ |tail -n1
+ )
+ if ! test -e "${whichdo_dofile_existent}"; then
+ >&2 printf 'redo-whichdo prints %s as existent dofile, but it does not exist.\n' \
+ "${whichdo_dofile_existent}"
+ exit 1
+ fi
+
+done
--- /dev/null
+#!/bin/sh -eu
+# When invoked with a relative path name as an argument, redo-whichdo
+# must output non-existent dofiles in the parent directory and the
+# same directory until it outputs one that exists.
+
+if ! test -d a; then
+ mkdir a
+fi
+
+dofiles="default.do default.c.do default.b.c.do a/default.do a/default.c.do a/default.b.c.do a/a.b.c.do"
+
+for dofile in ${dofiles}; do
+ if test -e "${dofile}"; then
+ rm -- "${dofile}"
+ fi
+done
+
+for dofile in ${dofiles}; do
+ >"${dofile}" cat <<EOF
+printf '${dofile}\n'
+EOF
+
+ whichdo_dofiles=$(
+ redo-whichdo a.b.c \
+ |tr '\0' '\n'
+ )
+
+ whichdo_dofiles_nonexistent=$(
+ printf '%s\n' "${whichdo_dofiles}" \
+ |sed '$d' # BusyBox v.1.19.3 has no “head -n-1”
+ )
+ for whichdo_dofile_nonexistent in ${whichdo_dofiles_nonexistent}; do
+ if test -e "${whichdo_dofile_nonexistent}"; then
+ >&2 printf 'redo-whichdo prints %s as nonexistent dofile, but it exists.\n' \
+ "${whichdo_dofile_nonexistent}"
+ exit 1
+ fi
+ done
+
+ whichdo_dofile_existent=$(
+ printf '%s\n' "${whichdo_dofiles}" \
+ |tail -n1
+ )
+ if ! test -e "${whichdo_dofile_existent}"; then
+ >&2 printf 'redo-whichdo prints %s as existent dofile, but it does not exist.\n' \
+ "${whichdo_dofile_existent}"
+ exit 1
+ fi
+
+done
--- /dev/null
+#!/bin/sh -eu
+# When invoked with a relative path name with the prefix “default” as
+# an argument, redo-whichdo must not output any dofile filename twice.
+
+default_files="default default.a default.a.b default.do default.a.do default.a.b.do"
+
+for default_file in ${default_files}; do
+ whichdo_dofiles_duplicates=$(
+ redo-whichdo "${default_file}" \
+ |tr '\0' '\n' \
+ |sort \
+ |uniq -d
+ )
+ case "${whichdo_dofiles_duplicates}" in
+ '')
+ ;;
+ *)
+ >&2 printf 'redo-whichdo duplicate output: %s \n' ${whichdo_dofiles_duplicates}
+ exit 1
+ ;;
+ esac
+done
--- /dev/null
+#!/bin/sh -eu
+# When invoked with a relative filename for the file “default.do” as
+# an argument, redo-whichdo must not output that filename as possible
+# dofile.
+
+default_dofile="default.do"
+
+whichdo_dofiles=$(
+ redo-whichdo "${default_dofile}" \
+ |tr '\0' '\n'
+)
+
+for whichdo_dofile in ${whichdo_dofiles}; do
+ case "${whichdo_dofile}" in
+ "${PWD}/${default_dofile}")
+ >&2 printf 'redo-whichdo outputs that this file is its own dofile: %s\n' \
+ "${whichdo_dofile}"
+ exit 1
+ ;;
+ esac
+done
#!/bin/sh
-testdir=`dirname $0`/redo-sh.tests/`basename "${0%.t}"`
+testname=`basename "$0"`
+testname=${testname#redosh-}
+testname=${testname%.t}
+testdir=`dirname $0`/redo-sh.tests/$testname
cd $testdir
testdir=`pwd`
test_description="`sed -n '2,/^$/p' test | sed 's/# //'`"
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
--- /dev/null
+redo-sh.tests/wrapper.rc
\ No newline at end of file
)
const (
- Version = "0.5.0"
+ Version = "0.6.0"
Warranty = `This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.
You can create them by running: goredo -symlinks.
* redo [options] [target ...]
- forcefully and sequentially build specified targets
+ forcefully and *sequentially* build specified targets
* redo-always
always build current target. Unusable outside .do
* redo-cleanup {full,log,tmp} [...]