at 24.11-pre 225 lines 9.3 kB view raw
1# Assert that FILE exists and is executable 2# 3# assertExecutable FILE 4assertExecutable() { 5 local file="$1" 6 [[ -f "$file" && -x "$file" ]] || \ 7 die "Cannot wrap '$file' because it is not an executable file" 8} 9 10# construct an executable file that wraps the actual executable 11# makeWrapper EXECUTABLE OUT_PATH ARGS 12 13# ARGS: 14# --argv0 NAME : set the name of the executed process to NAME 15# (if unset or empty, defaults to EXECUTABLE) 16# --inherit-argv0 : the executable inherits argv0 from the wrapper. 17# (use instead of --argv0 '$0') 18# --resolve-argv0 : if argv0 doesn't include a / character, resolve it against PATH 19# --set VAR VAL : add VAR with value VAL to the executable's environment 20# --set-default VAR VAL : like --set, but only adds VAR if not already set in 21# the environment 22# --unset VAR : remove VAR from the environment 23# --chdir DIR : change working directory (use instead of --run "cd DIR") 24# --run COMMAND : run command before the executable 25# --add-flags ARGS : prepend ARGS to the invocation of the executable 26# (that is, *before* any arguments passed on the command line) 27# --append-flags ARGS : append ARGS to the invocation of the executable 28# (that is, *after* any arguments passed on the command line) 29 30# --prefix ENV SEP VAL : suffix/prefix ENV with VAL, separated by SEP 31# --suffix 32# --prefix-each ENV SEP VALS : like --prefix, but VALS is a list 33# --suffix-each ENV SEP VALS : like --suffix, but VALS is a list 34# --prefix-contents ENV SEP FILES : like --suffix-each, but contents of FILES 35# are read first and used as VALS 36# --suffix-contents 37makeWrapper() { makeShellWrapper "$@"; } 38makeShellWrapper() { 39 local original="$1" 40 local wrapper="$2" 41 local params varName value command separator n fileNames 42 local argv0 flagsBefore flagsAfter flags 43 44 assertExecutable "$original" 45 46 # Write wrapper code which adds `value` to the beginning or end of 47 # the list variable named by `varName`, depending on the `mode` 48 # specified. 49 # 50 # A value which is already part of the list will not be added 51 # again. If this is the case and the `suffix` mode is used, the 52 # list won't be touched at all. The `prefix` mode will however 53 # move the last matching instance of the value to the beginning 54 # of the list. Any remaining duplicates of the value will be left 55 # as-is. 56 addValue() { 57 local mode="$1" # `prefix` or `suffix` to add to the beginning or end respectively 58 local varName="$2" # name of list variable to add to 59 local separator="$3" # character used to separate elements of list 60 local value="$4" # one value, or multiple values separated by `separator`, to add to list 61 62 # Disable file globbing, since bash will otherwise try to find 63 # filenames matching the the value to be prefixed/suffixed if 64 # it contains characters considered wildcards, such as `?` and 65 # `*`. We want the value as is, except we also want to split 66 # it on on the separator; hence we can't quote it. 67 local reenableGlob=0 68 if [[ ! -o noglob ]]; then 69 reenableGlob=1 70 fi 71 set -o noglob 72 73 if [[ -n "$value" ]]; then 74 local old_ifs=$IFS 75 IFS=$separator 76 77 if [[ "$mode" == '--prefix'* ]]; then 78 # Keep the order of the components as written when 79 # prefixing; normally, they would be added in the 80 # reverse order. 81 local tmp= 82 for v in $value; do 83 tmp=$v${tmp:+$separator}$tmp 84 done 85 value="$tmp" 86 fi 87 for v in $value; do 88 { 89 echo "$varName=\${$varName:+${separator@Q}\$$varName${separator@Q}}" # add separators on both ends unless empty 90 if [[ "$mode" == '--prefix'* ]]; then # -- in prefix mode -- 91 echo "$varName=\${$varName/${separator@Q}${v@Q}${separator@Q}/${separator@Q}}" # remove the first instance of the value (if any) 92 echo "$varName=${v@Q}\$$varName" # prepend the value 93 elif [[ "$mode" == '--suffix'* ]]; then # -- in suffix mode -- 94 echo "if [[ \$$varName != *${separator@Q}${v@Q}${separator@Q}* ]]; then" # if the value isn't already in the list 95 echo " $varName=\$$varName${v@Q}" # append the value 96 echo "fi" 97 else 98 echo "unknown mode $mode!" 1>&2 99 exit 1 100 fi 101 echo "$varName=\${$varName#${separator@Q}}" # remove leading separator 102 echo "$varName=\${$varName%${separator@Q}}" # remove trailing separator 103 echo "export $varName" 104 } >> "$wrapper" 105 done 106 IFS=$old_ifs 107 fi 108 109 if (( reenableGlob )); then 110 set +o noglob 111 fi 112 } 113 114 mkdir -p "$(dirname "$wrapper")" 115 116 echo "#! @shell@ -e" > "$wrapper" 117 118 params=("$@") 119 for ((n = 2; n < ${#params[*]}; n += 1)); do 120 p="${params[$n]}" 121 122 if [[ "$p" == "--set" ]]; then 123 varName="${params[$((n + 1))]}" 124 value="${params[$((n + 2))]}" 125 n=$((n + 2)) 126 echo "export $varName=${value@Q}" >> "$wrapper" 127 elif [[ "$p" == "--set-default" ]]; then 128 varName="${params[$((n + 1))]}" 129 value="${params[$((n + 2))]}" 130 n=$((n + 2)) 131 echo "export $varName=\${$varName-${value@Q}}" >> "$wrapper" 132 elif [[ "$p" == "--unset" ]]; then 133 varName="${params[$((n + 1))]}" 134 n=$((n + 1)) 135 echo "unset $varName" >> "$wrapper" 136 elif [[ "$p" == "--chdir" ]]; then 137 dir="${params[$((n + 1))]}" 138 n=$((n + 1)) 139 echo "cd ${dir@Q}" >> "$wrapper" 140 elif [[ "$p" == "--run" ]]; then 141 command="${params[$((n + 1))]}" 142 n=$((n + 1)) 143 echo "$command" >> "$wrapper" 144 elif [[ ("$p" == "--suffix") || ("$p" == "--prefix") ]]; then 145 varName="${params[$((n + 1))]}" 146 separator="${params[$((n + 2))]}" 147 value="${params[$((n + 3))]}" 148 n=$((n + 3)) 149 addValue "$p" "$varName" "$separator" "$value" 150 elif [[ ("$p" == "--suffix-each") || ("$p" == "--prefix-each") ]]; then 151 varName="${params[$((n + 1))]}" 152 separator="${params[$((n + 2))]}" 153 values="${params[$((n + 3))]}" 154 n=$((n + 3)) 155 for value in $values; do 156 addValue "$p" "$varName" "$separator" "$value" 157 done 158 elif [[ ("$p" == "--suffix-contents") || ("$p" == "--prefix-contents") ]]; then 159 varName="${params[$((n + 1))]}" 160 separator="${params[$((n + 2))]}" 161 fileNames="${params[$((n + 3))]}" 162 n=$((n + 3)) 163 for fileName in $fileNames; do 164 contents="$(cat "$fileName")" 165 addValue "$p" "$varName" "$separator" "$contents" 166 done 167 elif [[ "$p" == "--add-flags" ]]; then 168 flags="${params[$((n + 1))]}" 169 n=$((n + 1)) 170 flagsBefore="${flagsBefore-} $flags" 171 elif [[ "$p" == "--append-flags" ]]; then 172 flags="${params[$((n + 1))]}" 173 n=$((n + 1)) 174 flagsAfter="${flagsAfter-} $flags" 175 elif [[ "$p" == "--argv0" ]]; then 176 argv0="${params[$((n + 1))]}" 177 n=$((n + 1)) 178 elif [[ "$p" == "--inherit-argv0" ]]; then 179 # Whichever comes last of --argv0 and --inherit-argv0 wins 180 argv0='$0' 181 elif [[ "$p" == "--resolve-argv0" ]]; then 182 # this is noop in shell wrappers, since bash will always resolve $0 183 resolve_argv0=1 184 else 185 die "makeWrapper doesn't understand the arg $p" 186 fi 187 done 188 189 echo exec ${argv0:+-a \"$argv0\"} \""$original"\" \ 190 "${flagsBefore-}" '"$@"' "${flagsAfter-}" >> "$wrapper" 191 192 chmod +x "$wrapper" 193} 194 195addSuffix() { 196 suffix="$1" 197 shift 198 for name in "$@"; do 199 echo "$name$suffix" 200 done 201} 202 203filterExisting() { 204 for fn in "$@"; do 205 if test -e "$fn"; then 206 echo "$fn" 207 fi 208 done 209} 210 211# Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...> 212wrapProgram() { wrapProgramShell "$@"; } 213wrapProgramShell() { 214 local prog="$1" 215 local hidden 216 217 assertExecutable "$prog" 218 219 hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped 220 while [ -e "$hidden" ]; do 221 hidden="${hidden}_" 222 done 223 mv "$prog" "$hidden" 224 makeShellWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}" 225}