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