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