X-Git-Url: https://plomlompom.com/repos/?a=blobdiff_plain;f=build%2Fredo_scripts%2Fredo-ifchange;h=902ed16d5cef6b58c38d5ee6c306aef65f67c8a6;hb=2b4005f1f4b4ca64d86af9ac35a92d60f6e7bc48;hp=d345a39694f007e3516e7945045ef138f4cd4c77;hpb=5fd4be90f125e318a2fcaa0b92329511cb178f5a;p=plomrogue diff --git a/build/redo_scripts/redo-ifchange b/build/redo_scripts/redo-ifchange index d345a39..902ed16 100755 --- a/build/redo_scripts/redo-ifchange +++ b/build/redo_scripts/redo-ifchange @@ -12,17 +12,25 @@ # Overflow oder eine Format String Vulnerability zwischen die anderen # Codezeilen und schreibe das auch nicht dran. +_add_ctime_md5sum() { + target=$1 + local base; _dirsplit "$target" + [ -d "$REDO_DIR/$dir" ] || LANG=C mkdir -p "$REDO_DIR/$dir" + LANG=C stat -c%Y "$target" >"$REDO_DIR/$target".ctime + LANG=C md5sum <"$target" >"$REDO_DIR/$target".md5sum +} + _add_dependency() { - parent="$1" - dependency="$2" + parent=$1 + dependency=$2 # Do not record circular dependencies. [ "$parent" = "$dependency" ] && exit 1 local base; _dirsplit "$parent" - [ -d "$REDO_DIR/$dir" ] || mkdir -p "$REDO_DIR/$dir" - # Naive implementation: Append dependency, then remove double dependencies. - dependencies=$( (echo "$dependency"; cat "$REDO_DIR/$parent".dependencies 2>/dev/null ) | sort -u) - echo "$dependencies" > "$REDO_DIR/$parent".dependencies - # FIXME: Remove added dependency from non-existence dependencies. + [ -d "$REDO_DIR/$dir" ] || LANG=C mkdir -p "$REDO_DIR/$dir" + ctime="$(LANG=C stat -c%Y "$dependency")" + md5sum="$(LANG=C md5sum < "$dependency")" + printf '%s\t%s\t%s\n' "$dependency" "$ctime" "$md5sum" >> \ + "$REDO_DIR"/"$parent".dependencies.tmp } _add_dependency_ne() { @@ -34,93 +42,137 @@ _add_dependency_ne() { [ -e "$dependency_ne" ] && return local base; _dirsplit "$parent" [ -d "$REDO_DIR/$dir" ] || mkdir -p "$REDO_DIR/$dir" - # Naive implementation: Append dependency, then remove double dependencies. - dependencies_ne=$( (echo "$dependency_ne"; cat "$REDO_DIR/$parent".dependencies_ne 2>/dev/null ) | sort -u) - echo "$dependencies_ne" > "$REDO_DIR/$parent".dependencies_ne - # FIXME: Remove added non-existence dependency from dependencies. + printf '%s\n' "$dependency_ne" >> "$REDO_DIR/$parent".dependencies_ne.tmp } -_add_target() { - local target="$1" - # Exit if called without a target. - [ -z "$target" ] && exit 1 - local base; _dirsplit "$target" - [ -d "$REDO_DIR/$dir" ] || mkdir -p "$REDO_DIR/$dir" - # If target file exists, record ctime and md5sum. - if [ -e "$target" ]; then - stat -c%Y "$target" > "$REDO_DIR/$target".ctime - md5sum < "$target" > "$REDO_DIR/$target".md5sum - fi -} - -_dependencies_uptodate() { - target="$1" - target_relpath=${target##$REDO_BASE/} - # If no dependencies exist, they are by definition up to date. - if [ ! -e "$REDO_DIR/$target".dependencies ]; then - _echo_debug_message "$REDO_DEPTH$dir$target_relpath has no dependencies." +_dependencies_ne_uptodate() { + target=$1 + # If no non-existence dependencies exist, they are by definition up to date. + if [ ! -s "$REDO_DIR/$target".dependencies_ne ]; then + _echo_debug_message "$target has no non-existence dependencies." return 0 fi - _echo_debug_message "$REDO_DEPTH$dir$target_relpath non-existant dependency check:" - dependencies_ne=$(cat "$REDO_DIR/$target".dependencies_ne 2>/dev/null || :) - for dependency_ne in $dependencies_ne; do - dependency_ne_relpath=${dependency_ne##$REDO_BASE/} - _echo_debug_message "$REDO_DEPTH$dir$target_relpath depends on non-existence of $dependency_ne_relpath" + _echo_debug_message "$target non-existence dependency check:" + while read dependency_ne; do + _echo_debug_message "\t$dependency_ne should not exist." # If a non-existence dependency exists, it is out of date. # Dependencies, e.g. on default.do files may also be out of date. - # Naive implementation: Delete dependency information and rebuild. + # Naive implementation: Pretend target is not up to date and rebuild. if [ -e "$dependency_ne" ]; then - rm "$REDO_DIR/$target".dependencies - rm "$REDO_DIR/$target".dependencies_ne + _echo_debug_message "\t$dependency_ne does exist." return 1 - # If a non-existence dependency does not exist, but is also a - # dependency, it existed in the past. Naive implementation: Delete - # dependency information and pretend the (actually up-to-date) - # dependency is out of date to generate dependency information. - # TODO: Only remove non-existence dependency from dependencies, - # and do no return at this point; maybe escape filename properly. - elif (grep -q '^'"$dependency_ne"'$' "$REDO_DIR/$target".dependencies \ - 2>/dev/null); then - rm "$REDO_DIR/$target".dependencies + fi + _echo_debug_message "\t$dependency_ne does not exist." + done < "$REDO_DIR/$target".dependencies_ne + _echo_debug_message "$target non-existence dependencies uptodate." + return 0 +} + +_target_uptodate() { + target=$1 + # If a target is a top-level target, it is not up to date. + if [ -z "$REDO_TARGET" ]; then + return 1 + fi + # If a target does not exist, it is not up to date. + if [ ! -e "$target" ]; then + _echo_debug_message "$target does not exist." + return 1 + fi + # If an .always file exists, the target is out of date. + if [ -e "$REDO_DIR/$target".always ]; then + _echo_debug_message "$target is always out of date." + return 1 + fi + # If .stamp file exists, the target is out of date. + if [ -e "$REDO_DIR/$target".stamp ]; then + _echo_debug_message "$target is out of date due to redo-stamp." + return 1 + fi + if [ -e "$REDO_DIR/$target".ctime ]; then + read ctime_stored <"$REDO_DIR/$target".ctime + ctime_actual="$(LANG=C stat -c%Y "$target")" + _echo_debug_message "$target stored ctime $ctime_stored" + _echo_debug_message "$target actual ctime $ctime_actual" + if [ "$ctime_stored" = "$ctime_actual" ]; then + _echo_debug_message "$target up to date." + return 0 + elif [ -e "$REDO_DIR/$target".md5sum ]; then + read md5sum_stored <"$REDO_DIR/$target".md5sum + md5sum_actual="$(LANG=C md5sum < "$target")" + _echo_debug_message "$target stored md5sum $md5sum_stored" + _echo_debug_message "$target actual md5sum $md5sum_actual" + if [ "$md5sum_stored" = "$md5sum_actual" ]; then + # If stored md5sum of target matches actual md5sum, but stored + # ctime does not, redo needs to update stored ctime of target. + LANG=C stat -c%Y "$target" >"$REDO_DIR/$target".md5sum + return 0 + fi + _echo_debug_message "$target out of date." return 1 fi - done - _echo_debug_message "$REDO_DEPTH$dir$target_relpath non-existence dependencies uptodate." - _echo_debug_message "$REDO_DEPTH$dir$target_relpath dependency check:" + fi +} + +_dependencies_uptodate() { + target=$1 + # If no dependencies exist, they are by definition up to date. + if [ ! -e "$REDO_DIR/$target".dependencies ]; then + _echo_debug_message "$target has no dependencies." + return 0 + fi if [ "$REDO_SHUFFLE" = "1" ]; then - dependencies=$(shuf "$REDO_DIR/$target".dependencies 2>/dev/null || :) - else - dependencies=$(cat "$REDO_DIR/$target".dependencies 2>/dev/null || :) + shuf "$REDO_DIR/$target".dependencies -o "$REDO_DIR/$target".dependencies \ + 2>&- fi - for dependency in $dependencies; do - dependency_relpath=${dependency##$REDO_BASE/} - dir='' - _echo_debug_message "$REDO_DEPTH$dir$target_relpath depends on $dependency_relpath" - if ( ! _is_uptodate "$dependency" ); then - _echo_debug_message \ - "$REDO_DEPTH$dir$target_relpath dependency $dependency_relpath not uptodate." + _echo_debug_message "$target dependency check:" + # If any dependency does not exist, the target is out of date. + LANG=C stat -c%Y $(LANG=C cut -f1 "$REDO_DIR/$target".dependencies) > \ + "$REDO_DIR/$target".dependencies.ctimes 2>&- || return 1 + exec 3< "$REDO_DIR/$target".dependencies.ctimes + exec 4< "$REDO_DIR/$target".dependencies + while read ctime_actual <&3 && read dependency ctime_stored md5sum_stored <&4; do + # If a .always file exists, the dependency is out of date. + if [ -e "$REDO_DIR/$dependency".always ]; then + _echo_debug_message "\t$dependency is always out of date." return 1 fi - if ( ! _dependencies_uptodate "$dependency"); then - _echo_debug_message \ - "$REDO_DEPTH$dir$target_relpath dependency $dependency_relpath dependencies not uptodate." + # If a .stamp file exists, the dependency is out of date. + if [ -e "$REDO_DIR/$dependency".stamp ]; then + _echo_debug_message "\t$dependency is always out of date." return 1 fi - # In the case that two targets depend on the same target and are - # built after another, when the second target is built, redo finds - # its dependencies to be up-to-date. This implies that the second - # target would not be built. If a dependency build timestamp is - # greater than or equal to a targets build timestamp, assume the - # dependency has been rebuilt and the target should be rebuilt. - target_ts="$(cat "$REDO_DIR/$target_abspath.buildtime" 2>/dev/null || echo 0)" - dependency_ts="$(cat "$REDO_DIR/$dependency.buildtime" 2>/dev/null || echo 0)" - if [ "$dependency_ts" -ge "$target_ts" ]; then - _echo_debug_message \ - "$REDO_DEPTH$dir$target_relpath dependency $dependency_relpath uptodate, but built later than $target_relpath." + # If a dependency of a dependency is out of date, the dependency is out of date. + if ( ! _dependencies_uptodate "$dependency" ); then + return 1 + fi + # If the ctime of a dependency did not change, the dependency is up to date. + _echo_debug_message "\t$dependency stored ctime $ctime_stored" + _echo_debug_message "\t$dependency actual ctime $ctime_actual" + if [ "$ctime_stored" = "$ctime_actual" ]; then + _echo_debug_message "\t$dependency up to date." + continue + fi + # If the md5sum of a dependency did not change, the dependency is up to date. + md5sum_actual="$(LANG=C md5sum < "$dependency")" + _echo_debug_message "\t$dependency stored md5sum $md5sum_stored" + _echo_debug_message "\t$dependency actual md5sum $md5sum_actual" + if [ "$md5sum_stored" = "$md5sum_actual" ]; then + _echo_debug_message "\t$dependency up to date." + continue + else + # if both ctime and md5sum did change, the dependency is out of date. + _echo_debug_message "\t$dependency out of date." return 1 fi done - _echo_debug_message "$REDO_DEPTH$dir$target_relpath dependencies uptodate." + exec 4>&- + exec 3>&- + _echo_debug_message "$target dependencies up to date." + # If a non-existence dependency is out of date, the target is out of date. + if ( ! _dependencies_ne_uptodate "$target" ); then + return 1 + fi return 0 } @@ -129,54 +181,40 @@ _dirsplit() { dir=${1%"$base"} } -_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" - echo "xbasetmp='$xbasetmp'" >&2 - done -} - _do() { local dir="$1" target="$2" tmp="$3" - # Add target to parent targets dependencies. target_abspath="$PWD/$target" - if [ -n "$REDO_TARGET" ]; then - _add_dependency "$REDO_TARGET" "$target_abspath" - fi target_relpath="${target_abspath##$REDO_BASE/}" - if ( ! _is_uptodate "$target_abspath" || ! _dependencies_uptodate "$target_abspath" ); then + # If target is not up to date or its dependencies are not up to date, build it. + if ( + ! _target_uptodate "$target_abspath" || \ + ! _dependencies_uptodate "$target_abspath" + ); then dofile="$target".do base="$target" ext= [ -e "$target.do" ] || _find_dofile "$target" - # Add non existing .do file to non-existence dependencies so target is built when - # .do file in question is created. - _add_dependency_ne "$(readlink -f "$target")" "$PWD/$target.do" if [ ! -e "$dofile" ]; then # If .do file does not exist and target exists, it is a source file. - if [ -e "$target" ]; then - _add_target "$(readlink -f $target)" - # Portable timestamps for systems supporting GNU date format '%N'. - date +%s%N | tr -d '%N' > "$REDO_DIR/$target_abspath".buildtime + if [ -e "$target_abspath" ]; then + _add_dependency "$REDO_TARGET" "$target_abspath" + _add_ctime_md5sum "$target_abspath" return 0 # If .do file does not exist and target does not exist, stop. else echo "redo: $target: no .do file" >&2 exit 1 fi - # Add .do file to dependencies so target is built when .do file changes. - else - _add_dependency "$(readlink -f "$target")" "$PWD/$dofile" - _add_target "$PWD/$dofile" fi printf '%sredo %s%s%s%s%s\n' \ "$green" "$REDO_DEPTH" "$bold" "$target_relpath" "$plain" >&2 ( _run_dofile "$target" "$base" "$tmp.tmp" ) rv="$?" + # Add non existing .do file to non-existence dependencies so + # target is built when .do file in question is created. + [ -e "$target.do" ] || _add_dependency_ne "$target_abspath" "$PWD/$target.do" + # Add .do file to dependencies so target is built when .do file changes. + _add_dependency "$target_abspath" "$PWD/$dofile" if [ $rv != 0 ]; then rm -f "$tmp.tmp" "$tmp.tmp2" # Exit code 123 conveys that target was considered uptodate at runtime. @@ -186,16 +224,24 @@ _do() { exit 1 fi fi - mv "$tmp.tmp" "$target" 2>/dev/null || + mv "$tmp.tmp" "$target" 2>&- || ! test -s "$tmp.tmp2" || - mv "$tmp.tmp2" "$target" 2>/dev/null + mv "$tmp.tmp2" "$target" 2>&- rm -f "$tmp.tmp2" - # Portable timestamps for systems supporting GNU date format '%N'. - date +%s%N | tr -d '%N' > "$REDO_DIR/$target_abspath".buildtime - else - _echo_debug_message "$REDO_DEPTH$dir$target is up to date." + # After build is finished, update dependencies. + touch "$REDO_DIR/$target_abspath".dependencies.tmp + touch "$REDO_DIR/$target_abspath".dependencies_ne.tmp + mv "$REDO_DIR/$target_abspath".dependencies.tmp \ + "$REDO_DIR/$target_abspath".dependencies >&2 + mv "$REDO_DIR/$target_abspath".dependencies_ne.tmp \ + "$REDO_DIR/$target_abspath".dependencies_ne >&2 + fi + # Some do files (like all.do) do not usually generate output. + if [ -e "$target_abspath" ]; then + # Record dependency on parent target. + _add_dependency "$REDO_TARGET" "$target_abspath" + _add_ctime_md5sum "$target_abspath" fi - _add_target "$(readlink -f $target)" } _echo_debug_message() { @@ -227,60 +273,6 @@ _find_dofile_pwd() { base=${1%$ext} } -_is_uptodate() { - target="$1" - target_relpath=${target##$REDO_BASE/} - # If a target does not exist, it is by definition out of date. - if [ ! -e "$target" ]; then - _echo_debug_message "$REDO_DEPTH$dir$target_relpath does not exist." - return 1 - else - # If a file exists, but has no build date, it is by a previously unseen - # source file. A previously unseen source file is by definition up to date. - if [ ! -e "$REDO_DIR/$target".ctime ]; then - _echo_debug_message "$REDO_DEPTH$dir$target_relpath.ctime does not exist." - # Add source file to dependencies so target is built when source file changes. - _add_target "$(readlink -f $target)" - return 0 - else - _echo_debug_message "$REDO_DEPTH$dir$target_relpath.ctime exists." - # If a file exists and has an entry in the always database, it is never - # up to date. - if [ -e "$REDO_DIR/$target".always ]; then - _echo_debug_message "$REDO_DEPTH$dir$target_relpath.always exists." - return 1 - fi - # If a file exists and has an entry in the stamp database, it might not - # be up to date. redo-stamp decides if the file is up to date at runtime. - if [ -e "$REDO_DIR/$target".stamp ]; then - _echo_debug_message "$REDO_DEPTH$dir$target_relpath.stamp exists." - return 1 - fi - # If a file exists and has an entry in the dependency database and ctime - # is the same as in the entry in the ctime database, it is up to date. - if [ "$(cat "$REDO_DIR/$target".ctime 2>/dev/null || :)" = \ - "$(stat -c%Y "$target")" ]; then - _echo_debug_message "$REDO_DEPTH$dir$target_relpath.ctime match." - return 0 - else - _echo_debug_message "$REDO_DEPTH$dir$target_relpath.ctime mismatch." - # If a file exists and has an entry in the dependency database and - # ctime is different from the entry in the ctime database, but md5sum - # is the same as md5sum in the md5sum database, it is up to date. - if [ "$(cat "$REDO_DIR/$target".md5sum 2>/dev/null || :)" = \ - "$(md5sum < "$target")" ]; then - _echo_debug_message "$REDO_DEPTH$dir$target_relpath.md5sum match." - return 0 - else - _echo_debug_message "$REDO_DEPTH$dir$target_relpath.md5sum mismatch." - return 1 - fi - fi - fi - fi - return 1 -} - _run_dofile() { export REDO_DEPTH="$REDO_DEPTH " export REDO_TARGET="$PWD"/"$target" @@ -301,11 +293,16 @@ _run_dofile() { set +e if [ -n "$1" ]; then - for target; do + targets="$@" + [ "$REDO_SHUFFLE" = "1" ] && targets=$(LANG=C shuf -e "$@") + for target in $targets; do + # If relative path to target is given, convert to absolute absolute path. + case "$target" in + /*) ;; + *) target="$PWD"/"$target" >&2;; + esac _dirsplit "$target" - _dir_shovel "$dir" "$base" - dir="$xdir" base="$xbase" basetmp="$xbasetmp" - ( cd "$dir" && _do "$dir" "$base" "$basetmp" ) + ( cd "$dir" && _do "$dir" "$base" "$base" ) [ "$?" = 0 ] || exit 1 done fi