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}