2 # redo-ifchange – bourne shell implementation of DJB redo
3 # Copyright © 2014 Nils Dagsson Moskopp (erlehmann)
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # Dieses Programm hat das Ziel, die Medienkompetenz der Leser zu
11 # steigern. Gelegentlich packe ich sogar einen handfesten Buffer
12 # Overflow oder eine Format String Vulnerability zwischen die anderen
13 # Codezeilen und schreibe das auch nicht dran.
17 local base; _dirsplit "$target"
18 [ -d "$REDO_DIR/$dir" ] || LANG=C mkdir -p "$REDO_DIR/$dir"
19 LANG=C stat -c%Y "$target" >"$REDO_DIR/$target".ctime
20 LANG=C md5sum <"$target" >"$REDO_DIR/$target".md5sum
26 # Do not record circular dependencies.
27 [ "$parent" = "$dependency" ] && exit 1
28 local base; _dirsplit "$parent"
29 [ -d "$REDO_DIR/$dir" ] || LANG=C mkdir -p "$REDO_DIR/$dir"
30 ctime="$(LANG=C stat -c%Y "$dependency")"
31 md5sum="$(LANG=C md5sum < "$dependency")"
32 printf '%s\t%s\t%s\n' "$dependency" "$ctime" "$md5sum" >> \
33 "$REDO_DIR"/"$parent".dependencies.tmp
36 _add_dependency_ne() {
39 # Do not record circular dependencies.
40 [ "$parent" = "$dependency_ne" ] && exit 1
41 # Do not record existing files as non-existence dependencies.
42 [ -e "$dependency_ne" ] && return
43 local base; _dirsplit "$parent"
44 [ -d "$REDO_DIR/$dir" ] || mkdir -p "$REDO_DIR/$dir"
45 printf '%s\n' "$dependency_ne" >> "$REDO_DIR/$parent".dependencies_ne.tmp
48 _dependencies_ne_uptodate() {
50 # If no non-existence dependencies exist, they are by definition up to date.
51 if [ ! -s "$REDO_DIR/$target".dependencies_ne ]; then
52 _echo_debug_message "$target has no non-existence dependencies."
55 _echo_debug_message "$target non-existence dependency check:"
56 while read dependency_ne; do
57 _echo_debug_message "\t$dependency_ne should not exist."
58 # If a non-existence dependency exists, it is out of date.
59 # Dependencies, e.g. on default.do files may also be out of date.
60 # Naive implementation: Pretend target is not up to date and rebuild.
61 if [ -e "$dependency_ne" ]; then
62 _echo_debug_message "\t$dependency_ne does exist."
65 _echo_debug_message "\t$dependency_ne does not exist."
66 done < "$REDO_DIR/$target".dependencies_ne
67 _echo_debug_message "$target non-existence dependencies uptodate."
73 # If a target is a top-level target, it is not up to date.
74 if [ -z "$REDO_TARGET" ]; then
77 # If a target does not exist, it is not up to date.
78 if [ ! -e "$target" ]; then
79 _echo_debug_message "$target does not exist."
82 # If an .always file exists, the target is out of date.
83 if [ -e "$REDO_DIR/$target".always ]; then
84 _echo_debug_message "$target is always out of date."
87 # If .stamp file exists, the target is out of date.
88 if [ -e "$REDO_DIR/$target".stamp ]; then
89 _echo_debug_message "$target is out of date due to redo-stamp."
92 if [ -e "$REDO_DIR/$target".ctime ]; then
93 read ctime_stored <"$REDO_DIR/$target".ctime
94 ctime_actual="$(LANG=C stat -c%Y "$target")"
95 _echo_debug_message "$target stored ctime $ctime_stored"
96 _echo_debug_message "$target actual ctime $ctime_actual"
97 if [ "$ctime_stored" = "$ctime_actual" ]; then
98 _echo_debug_message "$target up to date."
100 elif [ -e "$REDO_DIR/$target".md5sum ]; then
101 read md5sum_stored <"$REDO_DIR/$target".md5sum
102 md5sum_actual="$(LANG=C md5sum < "$target")"
103 _echo_debug_message "$target stored md5sum $md5sum_stored"
104 _echo_debug_message "$target actual md5sum $md5sum_actual"
105 if [ "$md5sum_stored" = "$md5sum_actual" ]; then
106 # If stored md5sum of target matches actual md5sum, but stored
107 # ctime does not, redo needs to update stored ctime of target.
108 LANG=C stat -c%Y "$target" >"$REDO_DIR/$target".md5sum
111 _echo_debug_message "$target out of date."
117 _dependencies_uptodate() {
119 # If no dependencies exist, they are by definition up to date.
120 if [ ! -e "$REDO_DIR/$target".dependencies ]; then
121 _echo_debug_message "$target has no dependencies."
124 if [ "$REDO_SHUFFLE" = "1" ]; then
125 shuf "$REDO_DIR/$target".dependencies -o "$REDO_DIR/$target".dependencies \
128 _echo_debug_message "$target dependency check:"
129 # If any dependency does not exist, the target is out of date.
130 LANG=C stat -c%Y $(LANG=C cut -f1 "$REDO_DIR/$target".dependencies) > \
131 "$REDO_DIR/".dependencies.ctimes 2>&- || return 1
132 exec 3< "$REDO_DIR/".dependencies.ctimes
133 exec 4< "$REDO_DIR/$target".dependencies
134 while read ctime_actual <&3 && read dependency ctime_stored md5sum_stored <&4; do
135 # If a .always file exists, the dependency is out of date.
136 if [ -e "$REDO_DIR/$dependency".always ]; then
137 _echo_debug_message "\t$dependency is always out of date."
140 # If a .stamp file exists, the dependency is out of date.
141 if [ -e "$REDO_DIR/$dependency".stamp ]; then
142 _echo_debug_message "\t$dependency is always out of date."
145 # If a dependency of a dependency is out of date, the dependency is out of date.
146 if ( ! _dependencies_uptodate "$dependency" ); then
149 # If the ctime of a dependency did not change, the dependency is up to date.
150 _echo_debug_message "\t$dependency stored ctime $ctime_stored"
151 _echo_debug_message "\t$dependency actual ctime $ctime_actual"
152 if [ "$ctime_stored" = "$ctime_actual" ]; then
153 _echo_debug_message "\t$dependency up to date."
156 # If the md5sum of a dependency did not change, the dependency is up to date.
157 md5sum_actual="$(LANG=C md5sum < "$dependency")"
158 _echo_debug_message "\t$dependency stored md5sum $md5sum_stored"
159 _echo_debug_message "\t$dependency actual md5sum $md5sum_actual"
160 if [ "$md5sum_stored" = "$md5sum_actual" ]; then
161 _echo_debug_message "\t$dependency up to date."
164 # if both ctime and md5sum did change, the dependency is out of date.
165 _echo_debug_message "\t$dependency out of date."
171 _echo_debug_message "$target dependencies up to date."
172 # If a non-existence dependency is out of date, the target is out of date.
173 if ( ! _dependencies_ne_uptodate "$target" ); then
185 local dir="$1" target="$2" tmp="$3"
186 target_abspath="$PWD/$target"
187 target_relpath="${target_abspath##$REDO_BASE/}"
188 # If target is not up to date or its dependencies are not up to date, build it.
190 ! _target_uptodate "$target_abspath" || \
191 ! _dependencies_uptodate "$target_abspath"
196 [ -e "$target.do" ] || _find_dofile "$target"
197 if [ ! -e "$dofile" ]; then
198 # If .do file does not exist and target exists, it is a source file.
199 if [ -e "$target_abspath" ]; then
200 _add_dependency "$REDO_TARGET" "$target_abspath"
201 _add_ctime_md5sum "$target_abspath"
203 # If .do file does not exist and target does not exist, stop.
205 echo "redo: $target: no .do file" >&2
209 printf '%sredo %s%s%s%s%s\n' \
210 "$green" "$REDO_DEPTH" "$bold" "$target_relpath" "$plain" >&2
211 ( _run_dofile "$target" "$base" "$tmp.tmp" )
213 # Add non existing .do file to non-existence dependencies so
214 # target is built when .do file in question is created.
215 [ -e "$target.do" ] || _add_dependency_ne "$target_abspath" "$PWD/$target.do"
216 # Add .do file to dependencies so target is built when .do file changes.
217 _add_dependency "$target_abspath" "$PWD/$dofile"
218 if [ $rv != 0 ]; then
219 rm -f "$tmp.tmp" "$tmp.tmp2"
220 # Exit code 123 conveys that target was considered uptodate at runtime.
221 if [ $rv != 123 ]; then
222 printf "%sredo: %s%s: got exit code %s.%s\n" \
223 "$red" "$REDO_DEPTH" "$target" "$rv" "$plain" >&2
227 mv "$tmp.tmp" "$target" 2>&- ||
228 ! test -s "$tmp.tmp2" ||
229 mv "$tmp.tmp2" "$target" 2>&-
231 # After build is finished, update dependencies.
232 touch "$REDO_DIR/$target_abspath".dependencies.tmp
233 touch "$REDO_DIR/$target_abspath".dependencies_ne.tmp
234 mv "$REDO_DIR/$target_abspath".dependencies.tmp \
235 "$REDO_DIR/$target_abspath".dependencies >&2
236 mv "$REDO_DIR/$target_abspath".dependencies_ne.tmp \
237 "$REDO_DIR/$target_abspath".dependencies_ne >&2
239 # Some do files (like all.do) do not usually generate output.
240 if [ -e "$target_abspath" ]; then
241 # Record dependency on parent target.
242 _add_dependency "$REDO_TARGET" "$target_abspath"
243 _add_ctime_md5sum "$target_abspath"
247 _echo_debug_message() {
248 [ "$REDO_DEBUG" = "1" ] && echo "$@" >&2
254 _find_dofile_pwd "$1"
255 [ -e "$dofile" ] && break
256 [ "$PWD" = "/" ] && break
257 target=${PWD##*/}/"$target"
258 tmp=${PWD##*/}/"$tmp"
259 prefix=${PWD##*/}/"$prefix"
262 base="$prefix""$base"
266 dofile=default."$1".do
268 dofile=default.${dofile#default.*.}
269 [ -e "$dofile" -o "$dofile" = default.do ] && break
271 ext=${dofile#default}
277 export REDO_DEPTH="$REDO_DEPTH "
278 export REDO_TARGET="$PWD"/"$target"
281 read line1 <"$PWD/$dofile" || true
283 # If the first line of a do file does not have a hashbang (#!), use /bin/sh.
284 if [ "$cmd" = "$line1" ] || [ "$cmd" = "/bin/sh" ]; then
285 if [ "$REDO_XTRACE" = "1" ]; then
291 $cmd "$PWD/$dofile" "$@" >"$tmp.tmp2"
297 [ "$REDO_SHUFFLE" = "1" ] && targets=$(LANG=C shuf -e "$@")
298 for target in $targets; do
299 # If relative path to target is given, convert to absolute absolute path.
302 *) target="$PWD"/"$target" >&2;;
305 ( cd "$dir" && _do "$dir" "$base" "$base" )
306 [ "$?" = 0 ] || exit 1