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}