]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - library-bundler
bikeshedding: some touchup on library-bundler
[xonotic/netradiant.git] / library-bundler
1 #! /usr/bin/env bash
2
3 set -e
4
5 Common::noOp () {
6         true
7 }
8
9 Common::getPath () {
10         local file_path="${1}"
11
12         if command -v cygpath >/dev/null
13         then
14                 if [ "${file_path}" = '-' ]
15                 then
16                         tr '\n' '\0' \
17                         | xargs -0 -n1 -P1 -I{} \
18                                 cygpath --unix '{}'
19                 else
20                         cygpath --unix "${file_path}"
21                 fi
22         else
23                 if [ "${file_path}" = '-' ]
24                 then
25                         cat
26                 else
27                         printf '%s\n' "${file_path}"
28                 fi
29         fi \
30         | sed -e 's|/*$||'
31 }
32
33 Common::grepLdd () {
34         egrep ' => '
35 }
36
37 Common::stripLdd () {
38         sed -e 's/ (0x[0-9a-f]*)$//;s/^.* => //'
39 }
40
41 Multi::excludeLdd () {
42         case "${system_name}" in
43                 'linux')
44                         # - always rely on up-to-date x11 and gl libraries, bundling them will break on future distros
45                         # - gtk is not easily bundlable on linux because it looks for harcoded system path to optional
46                         #   shared libraries like image codecs, theme engines, sound notification system, etc.
47                         #   so expect user to install gtk first
48                         # - since we ask user to instal gtk, we can also ask them to install gtkglext,
49                         #   which is likely to pull gtk itself, x11 and gl dependencies
50                         # - old fontconfig does not work correctly if newer fontconfig configuration is installed
51                         # - if gtk and fontconfig is installed, pango and freetype are
52                         egrep -v '/libc\.|/libstdc\+\+\.|/libdl\.|/libm\.|/libX|/libxcb|/libGL|/libICE\.|/libSM\.|/libpthread\.' \
53                         | egrep -v '/libatk|/libgdk|/libgtk|/libgio|/libglib|/libgmodule|/libgobject|/libcairo|/libpango|/libfontconfig|/libfreetype'
54                         ;;
55                 'windows')
56                         egrep -i '\.dll => [A-Z]:\\msys64\\' \
57                         ;;
58         esac
59 }
60
61 Multi::filterLib () {
62         Common::grepLdd \
63         | Multi::excludeLdd \
64         | Common::stripLdd \
65         | Common::getPath -
66 }
67
68 Multi::printLdd () {
69         local exe_file="${1}"
70
71         case "${system_name}" in
72                 'linux')
73                         ldd "${exe_file}"
74                         ;;
75                 'windows')
76                         ntldd --recursive "${exe_file}"
77                         ;;
78         esac
79 }
80
81 Multi::getGtkThemeName () {
82         case "${system_name}" in
83                 'linux')
84                         echo 'Adwaita'
85                         ;;
86                 'windows')
87                         echo 'MS-Windows'
88                         ;;
89         esac
90 }
91
92 Multi::getGtkLibName () {
93         case "${system_name}" in
94                 'linux')
95                         echo 'libgtk-x11-2.0.so.0'
96                         ;;
97                 'windows')
98                         echo 'libgtk-win32-2.0-0.dll'
99                         ;;
100         esac
101 }
102
103 Multi::getRootPrefix () {
104         local lib_file="${1}"
105
106         case "${system_name}" in
107                 'linux')
108                         echo "${lib_file}" \
109                         | cut -f2 -d'/'
110                         ;;
111                 'windows')
112                         basename "${lib_file}" \
113                         | xargs -n1 -P1 which \
114                         | cut -f2 -d'/'
115                         ;;
116         esac
117 }
118
119 Multi::getLibPrefix () {
120         local lib_file="${1}"
121
122         case "${system_name}" in
123                 'linux')
124                         dirname "${lib_file}" \
125                         | cut -f3- -d'/'
126                         ;;
127                 'windows')
128                         echo 'lib'
129                         ;;
130         esac
131 }
132
133 Multi::getGtkDeps () {
134         local lib_prefix="${1}"
135         local gtk_theme_name="${2}"
136
137         cat <<-EOF
138         share/themes/${gtk_theme_name}/gtk-2.0
139         share/icons/hicolor
140         ${lib_prefix}/gdk-pixbuf-2.0
141         ${lib_prefix}/gtk-2.0
142         EOF
143
144         case "${system_name}" in
145                 'linux')
146                         cat <<-EOF
147                         ${lib_prefix}/libatk-bridge-2.0.so.0
148                         ${lib_prefix}/libcanberra-0.30
149                         ${lib_prefix}/libcanberra.so.0
150                         ${lib_prefix}/libcanberra-gtk.so.0
151                         EOF
152                         ;;
153         esac
154 }
155
156 Multi::bundleGtkDepsFromFile () {
157         local lib_file="${1}"
158
159         lib_basename="$(basename "${lib_file}")"
160
161         gtk_lib_name="$(Multi::getGtkLibName)"
162         if [ "${lib_basename}" = "${gtk_lib_name}" ]
163         then
164                 root_prefix="$(Multi::getRootPrefix "${lib_file}")"
165                 lib_prefix="$(Multi::getLibPrefix "${lib_file}")"
166                 gtk_theme_name="$(Multi::getGtkThemeName)"
167
168                 for component_dir in $(Multi::getGtkDeps "${lib_prefix}" "${gtk_theme_name}")
169                 do
170                         bundle_component_dir="$(echo "${component_dir}" | sed -e 's|^'"${lib_prefix}"'|lib|')"
171                         if ! [ -e "${bundle_dir}/${bundle_component_dir}" ]
172                         then
173                                 mkdir --parents "${bundle_dir}/$(dirname "${bundle_component_dir}")"
174
175                                 cp -H -r --preserve=timestamps \
176                                         "/${root_prefix}/${component_dir}" \
177                                         "${bundle_dir}/${bundle_component_dir}"
178                         fi
179                 done
180         fi
181 }
182
183 Multi::bundleLibFromFile () {
184         local exe_file="${1}"
185
186         Multi::printLdd "${exe_file}" \
187         | Multi::filterLib \
188         | while read lib_file
189         do
190                 lib_basename="$(basename "${lib_file}")"
191
192                 if [ -f "${lib_dir}/${lib_basename}" ]
193                 then
194                         continue
195                 fi
196
197                 cp --preserve=timestamps \
198                         "${lib_file}" \
199                         "${lib_dir}/${lib_basename}"
200
201                 Multi::bundleGtkDepsFromFile "${lib_file}"
202         done
203 }
204
205 Multi::cleanUp () {
206         find "${bundle_dir}/lib" \
207                 -type f \
208                 -name '*.a' \
209                 -exec rm {} \;
210
211         find "${bundle_dir}/lib" \
212                 -type f \
213                 -name '*.h' \
214                 -exec rm {} \;
215
216         find "${bundle_dir}/lib" \
217                 -depth \
218                 -type d \
219                 -exec rmdir --ignore-fail-on-non-empty {} \;
220 }
221
222 Linux::getRpath () {
223         local exe_file="${1}"
224
225         local exe_dir="$(dirname "${exe_file}")"
226         local path_start="$(printf '%s' "${bundle_dir}" | wc -c)"
227         path_start="$((${path_start} + 1))"
228
229         local exe_subdir="$(echo "${exe_dir}" | cut -c "${path_start}-" | sed -e 's|//*|/|;s|^/||')"
230
231         local rpath_origin='$ORIGIN'
232
233         if [ "${exe_subdir}" = '' ]
234         then
235                 printf '%s/lib\n' "${rpath_origin}"
236         else
237                 if [ "${exe_subdir}" = 'lib' ]
238                 then
239                         printf '%s\n' "${rpath_origin}"
240                 else
241                         local num_parent_dir="$(echo "${exe_subdir}" | tr '/' '\n' | wc -l)"
242                         local rpath_subdir
243                         local i=0
244                         while [ "${i}" -lt "${num_parent_dir}" ]
245                         do
246                                 rpath_subdir="${rpath_subdir}/.."
247                                 i="$((${i} + 1))"
248                         done
249                         printf '%s%s/lib\n' "${rpath_origin}" "${rpath_subdir}"
250                 fi
251         fi
252 }
253
254 Linux::patchExe () {
255         local exe_file="${1}"
256
257         local linux_rpath_string=$"$(Linux::getRpath "${exe_file}")"
258         patchelf --set-rpath "${linux_rpath_string}" "${exe_file}"
259 }
260
261 Linux::patchLib () {
262         local lib_dir="${1}"
263         local exe_file
264
265         find "${lib_dir}" \
266                 -type f \
267                 -name '*.so*' \
268         | while read exe_file
269         do
270                 Linux::patchExe "${exe_file}"
271         done
272 }
273
274 Windows::listLibForManifest () {
275         local lib_dir="${1}"
276
277         find "${lib_dir}" \
278                 -maxdepth 1 \
279                 -type f \
280                 -name '*.dll' \
281                 -exec basename {} \; \
282         | tr '\n' '\0' \
283         | xargs -0 -n1 -P1 -I{} \
284                 printf '  <file name="{}"/>\n'
285 }
286
287 Windows::writeManifest () {
288         local lib_dir="${1}"
289
290         cat > "${manifest_file}" <<-EOF
291         <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
292           <assemblyIdentity type="win32" name="lib" version="1.0.0.0"/>
293         $(Windows::listLibForManifest "${lib_dir}")
294         </assembly>
295         EOF
296 }
297
298 system_name="${1}"; shift
299 bundle_dir="${1}"; shift
300
301 if ! [ -z "${1}" ]
302 then
303         exe_file="${1}"; shift
304 fi
305
306 bundle_dir="$(Common::getPath "${bundle_dir}")"
307 registry_dir="${bundle_dir}/registry"
308 lib_dir="${bundle_dir}/lib"
309
310 manifest_file="${lib_dir}/lib.manifest"
311
312 exe_action='Common::noOp'
313 lib_action='Common::noOp'
314
315 case "${system_name}" in
316         'register')
317                 mkdir --parents "${registry_dir}"
318                 Common::getPath "${exe_file}" > "${registry_dir}/$(uuidgen)"
319                 exit
320                 ;;
321         'linux')
322                 exe_action='Linux::patchExe'
323                 lib_action='Linux::patchLib'
324                 ;;
325         'windows')
326                 lib_action='Windows::writeManifest'
327                 ;;
328         *)
329                 printf 'ERROR: unsupported system: %s\n' "${system_name}" >&2
330                 exit 1
331                 ;;
332 esac
333
334 mkdir --parents "${lib_dir}"
335
336 if [ -d "${registry_dir}" ]
337 then
338         for registry_entry in "${registry_dir}"/*
339         do
340                 exe_file="$(cat "${registry_entry}")"
341
342                 Multi::bundleLibFromFile "${exe_file}"
343
344                 "${exe_action}" "${exe_file}"
345
346                 rm "${registry_entry}"
347
348                 "${exe_action}" "${exe_file}"
349         done
350
351         rmdir "${registry_dir}"
352 fi
353
354 "${lib_action}" "${lib_dir}"
355
356 Multi::cleanUp