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