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