X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=library-bundler;h=b08b9c89c0e2b71cf7dbddb7c8ff89bcbbe5c0b1;hb=7abfd4e21a8a66400a4970b83f08d7856b67832e;hp=cf91dd35dd7a39fe2bcbccc4acf1462f0cafbc9d;hpb=470b397c60d1a60a489682d82a5a78eafecdf7a2;p=xonotic%2Fnetradiant.git diff --git a/library-bundler b/library-bundler old mode 100644 new mode 100755 index cf91dd35..b08b9c89 --- a/library-bundler +++ b/library-bundler @@ -1,108 +1,528 @@ #! /usr/bin/env bash -Windows::listLibForManifest () { - local lib_dir="${1}" +set -e - find "${lib_dir}" \ - -maxdepth 1 \ - -type f \ - -name '*.dll' \ - -exec basename {} \; \ - | xargs -I {} \ - printf ' \n' +export LANG='C.UTF-8' +export LANGUAGE="${LANG}" + +_sed () { + case "${system_name}" in + 'macos'|'freebsd') + gsed "${@}" + ;; + *) + sed "${@}" + ;; + esac } -Windows::printManifest () { - local lib_dir="${1}" +_cp () { + case "${system_name}" in + 'macos'|'freebsd') + gcp -R --preserve=timestamps -H -L "${1}" "${2}" + ;; + *) + cp -R --preserve=timestamps -H -L "${1}" "${2}" + ;; + esac +} - cat <<-EOF - - - $(Windows::listLibForManifest "${lib_dir}") - - EOF +Common::noOp () { + true +} + +Common::getPath () { + local file_path="${1}" + + if command -v cygpath >/dev/null + then + if [ "${file_path}" = '-' ] + then + tr '\n' '\0' \ + | xargs -0 -n1 -P1 -I{} \ + cygpath --unix '{}' + else + cygpath --unix "${file_path}" + fi + else + if [ "${file_path}" = '-' ] + then + cat + else + printf '%s\n' "${file_path}" + fi + fi \ + | _sed -e 's|/*$||' +} + +Common::grepLdd () { + case "${system_name}" in + 'macos') + egrep '^\t/' + ;; + *) + egrep ' => ' + ;; + esac +} + +Common::stripLdd () { + case "${system_name}" in + 'macos') + _sed -e 's/^\t\(.*\) (compatibility version .*/\1/' + ;; + *) + _sed -e 's/ (0x[0-9a-f]*)$//;s/^.* => //' + ;; + esac +} + +Multi::excludeLdd () { + case "${system_name}" in + 'linux'|'freebsd') + # - always bundle built-in libraries + # - always rely on up-to-date x11 and gl libraries, bundling them will break on future distros + # - gtk is not easily bundlable on linux because it looks for harcoded system path to optional + # shared libraries like image codecs, theme engines, sound notification system, etc. + # so expect user to install gtk first + # - since we ask user to instal gtk, we can also ask them to install gtkglext, + # which is likely to pull gtk itself, x11 and gl dependencies + # - old fontconfig does not work correctly if newer fontconfig configuration is installed + # - if gtk and fontconfig is installed, pango and freetype are + local ldd_line + while read ldd_line + do + if echo "${ldd_line}" | egrep '/builtins/' + then + echo "${ldd_line}" + elif echo "${ldd_line}" \ + | egrep -q '/libc\.|/libstdc\+\+\.|/libdl\.|/libm\.|/libX|/libxcb|/libGL|/libICE\.|/libSM\.|/libpthread\.' + then + Common::noOp + elif echo "${ldd_line}" \ + | egrep -q '/libatk|/libgdk|/libgtk|/libgio|/libglib|/libgmodule|/libgobject|/libcairo|/libpango|/libfontconfig|/libfreetype' + then + Common::noOp + # FreeBSD specific + elif echo "${ldd_line}" \ + | egrep -q '/libc++|/libgxxrt' + then + Common::noOp + else + echo "${ldd_line}" + fi + done + ;; + 'windows') + egrep -i '\.dll => [A-Z]:\\msys64\\' + ;; + 'macos') + egrep -v '^\t/System/|^\t/usr/lib/' + ;; + esac +} + +Multi::filterLib () { + Common::grepLdd \ + | Multi::excludeLdd \ + | Common::stripLdd \ + | Common::getPath - } -Windows::bundleLibFromFile () { +Multi::printLdd () { local exe_file="${1}" - exe_file="$(cygpath --unix "${exe_file}")" + case "${system_name}" in + 'linux'|'freebsd') + ldd "${exe_file}" + ;; + 'windows') + ntldd --recursive "${exe_file}" + ;; + 'macos') + otool -L "${exe_file}" + esac +} + +Multi::getGtkThemeName () { + case "${system_name}" in + 'linux'|'freebsd') + echo 'Adwaita' + ;; + 'windows') + echo 'MS-Windows' + ;; + *) + echo 'Raleigh' + ;; + esac +} + +Multi::getGtkLibName () { + case "${system_name}" in + 'linux'|'freebsd') + echo 'libgtk-x11-2.0.so.0' + ;; + 'windows') + echo 'libgtk-win32-2.0-0.dll' + ;; + 'macos') + echo 'libgtk-quartz-2.0.0.dylib' + ;; + esac +} + +Multi::getRootPrefix () { + local lib_file="${1}" + + case "${system_name}" in + 'linux'|'freebsd') + echo "${lib_file}" \ + | cut -f2 -d'/' + ;; + 'windows') + basename "${lib_file}" \ + | xargs -n1 -P1 which \ + | cut -f2 -d'/' + ;; + 'macos') + echo 'usr/local' + esac +} + +Multi::getLibPrefix () { + local lib_file="${1}" + + case "${system_name}" in + 'linux'|'freebsd') + dirname "${lib_file}" \ + | cut -f3- -d'/' + ;; + 'windows') + echo 'lib' + ;; + 'macos') + echo 'lib' + ;; + esac +} + +Multi::getGtkDeps () { + local lib_prefix="${1}" + local gtk_theme_name="${2}" + + case "${system_name}" in + 'linux'|'freebsd'|'windows') + cat <<-EOF + share/themes/${gtk_theme_name}/gtk-2.0 + share/icons/hicolor + ${lib_prefix}/gdk-pixbuf-2.0 + ${lib_prefix}/gtk-2.0 + EOF + ;; + 'macos') + cat <<-EOF + etc/fonts + share/themes/${gtk_theme_name}/gtk-2.0 + share/fontconfig + share/icons/hicolor + share/locale + ${lib_prefix}/gdk-pixbuf-2.0 + ${lib_prefix}/gtk-2.0 + EOF + ;; + esac + + case "${system_name}" in + 'linux'|'freebsd') + cat <<-EOF + ${lib_prefix}/libatk-bridge-2.0.so.0 + ${lib_prefix}/libcanberra-0.30 + ${lib_prefix}/libcanberra.so.0 + ${lib_prefix}/libcanberra-gtk.so.0 + EOF + ;; + esac +} + +Multi::rewriteLoadersCache () { + local bundle_component_path="${1}" + local cache_file + + find "${bundle_component_path}" \ + -type f \ + \( \ + -name 'loaders.cache' \ + -o -name 'immodules.cache' \ + \) \ + | while read cache_file + do + _sed \ + -e 's|^"/[^"]*/lib/|"lib/|;s| "/[^"]*/share/| "share/|;/^# ModulesPath = /d;/^# Created by /d;/^#$/d' \ + -i "${cache_file}" + done +} + +Multi::bundleGtkDepsFromFile () { + local lib_file="${1}" + local component_dir + local real_component_dir + local bundle_component_dir - ntldd --recursive "${exe_file}" \ - | egrep -i '\.dll => [A-Z]:\\msys64\\' \ - | sed -e 's/ (0x[0-9a-f]*)$//;s/^.* => //' \ - | cygpath --unix --file - \ - | while read dll_file + lib_basename="$(basename "${lib_file}")" + + gtk_lib_name="$(Multi::getGtkLibName)" + if [ "${lib_basename}" = "${gtk_lib_name}" ] + then + root_prefix="$(Multi::getRootPrefix "${lib_file}")" + lib_prefix="$(Multi::getLibPrefix "${lib_file}")" + gtk_theme_name="$(Multi::getGtkThemeName)" + + for component_dir in $(Multi::getGtkDeps "${lib_prefix}" "${gtk_theme_name}") + do + bundle_component_dir="$(echo "${component_dir}" | _sed -e 's|^'"${lib_prefix}"'|lib|')" + if ! [ -e "${bundle_dir}/${bundle_component_dir}" ] + then + real_component_dir="$(realpath "/${root_prefix}/${component_dir}")" + + mkdir -p "${bundle_dir}/$(dirname "${bundle_component_dir}")" + + _cp \ + "${real_component_dir}" \ + "${bundle_dir}/${bundle_component_dir}" + + Multi::rewriteLoadersCache "${bundle_dir}/${bundle_component_dir}" + fi + done + fi +} + +Multi::bundleLibFromFile () { + local exe_file="${1}" + local lib_file + + Multi::printLdd "${exe_file}" \ + | Multi::filterLib \ + | while read lib_file do - dll_basename="$(basename "${dll_file}")" + if [ "${lib_file}" = 'not found' ] + then + printf 'ERROR: library not found while bundling %s (but link worked)\n' "${exe_file}" >&2 + Multi::printLdd "${exe_file}" | grep 'not found' + exit 1 + fi + lib_basename="$(basename "${lib_file}")" - if [ -f "${bundle_dir}/${dll_basename}" ] + if [ -f "${lib_dir}/${lib_basename}" ] then continue fi - cp --preserve=timestamps "${dll_file}" "${lib_dir}/${dll_basename}" + _cp \ + "${lib_file}" \ + "${lib_dir}/${lib_basename}" - if [ "${dll_basename}" = 'libgtk-win32-2.0-0.dll' ] - then - mingw="$(which 'libgtk-win32-2.0-0.dll' | cut -f2 -d'/')" + Multi::bundleGtkDepsFromFile "${lib_file}" + + case "${system_name}" in + 'macos') + Multi::bundleLibFromFile "${lib_file}" + ;; + esac + done +} + +Multi::cleanUp () { + # Remove from bundle things that useless to be distributed, + # like headers or static libraries, also remove + # empty directories. + find "${bundle_dir}/lib" \ + -type f \ + -name '*.a' \ + -exec rm -f {} \; + + find "${bundle_dir}/lib" \ + -type f \ + -name '*.h' \ + -exec rm -f {} \; + + find "${bundle_dir}/lib" \ + -depth \ + -type d \ + -empty \ + -exec rmdir {} \; +} + +Linux::getRpath () { + local exe_file="${1}" + + local exe_dir="$(dirname "${exe_file}")" + local path_start="$(printf '%s' "${bundle_dir}" | wc -c)" + path_start="$((${path_start} + 1))" + + local exe_subdir="$(echo "${exe_dir}" | cut -c "${path_start}-" | _sed -e 's|//*|/|;s|^/||')" - for component_dir in \ - 'share/themes/MS-Windows' \ - 'share/icons/hicolor' \ - 'lib/gdk-pixbuf-2.0' \ - 'lib/gtk-2.0' + local rpath_origin='$ORIGIN' + + if [ "${exe_subdir}" = '' ] + then + printf '%s/lib\n' "${rpath_origin}" + else + if [ "${exe_subdir}" = 'lib' ] + then + printf '%s\n' "${rpath_origin}" + else + local num_parent_dir="$(echo "${exe_subdir}" | tr '/' '\n' | wc -l)" + local rpath_subdir + local i=0 + while [ "${i}" -lt "${num_parent_dir}" ] do - if ! [ -d "${bundle_dir}/${component_dir}" ] - then - mkdir --parents "${bundle_dir}/$(dirname "${component_dir}")" - cp -r --preserve=timestamps "/${mingw}/${component_dir}" \ - "${bundle_dir}/${component_dir}" - fi + rpath_subdir="${rpath_subdir}/.." + i="$((${i} + 1))" done - - find "${bundle_dir}/lib" -type f -name '*.a' -exec rm {} \; - find "${bundle_dir}/lib" -type f -name '*.h' -exec rm {} \; - find "${bundle_dir}/lib" -type d -exec rmdir --ignore-fail-on-non-empty {} \; + printf '%s%s/lib\n' "${rpath_origin}" "${rpath_subdir}" fi + fi +} + +Linux::patchExe () { + local exe_file="${1}" + + local linux_rpath_string=$"$(Linux::getRpath "${exe_file}")" + chmod u+w,go-w "${exe_file}" + patchelf --set-rpath "${linux_rpath_string}" "${exe_file}" +} + +Linux::patchLib () { + local lib_dir="${1}" + local exe_file + + find "${lib_dir}" \ + -type f \ + -name '*.so*' \ + | while read exe_file + do + Linux::patchExe "${exe_file}" + chmod ugo-x "${exe_file}" + done +} + +Darwin::patchExe () { + local exe_file="${1}" + + Multi::printLdd "${exe_file}" \ + | Multi::filterLib \ + | while read lib_file + do + new_path="$(echo "${lib_file}" | _sed -e 's|^/.*/lib/|@executable_path/lib/|')" + id_name="$(echo "${lib_file}" | _sed -e 's|.*/||g')" + chmod u+w,go-w "${exe_file}" + install_name_tool -change "${lib_file}" "${new_path}" "${exe_file}" + install_name_tool -id "${id_name}" "${exe_file}" + done +} + +Darwin::patchLib () { + local lib_dir="${1}" + local exe_file + + find "${lib_dir}" \ + -type f \ + \( \ + -name '*.dylib' \ + -o -name '*.so' \ + \) \ + | while read exe_file + do + Darwin::patchExe "${exe_file}" + chmod ugo-x "${exe_file}" done } +Windows::listLibForManifest () { + local lib_dir="${1}" + + find "${lib_dir}" \ + -maxdepth 1 \ + -type f \ + -name '*.dll' \ + -exec basename {} \; \ + | tr '\n' '\0' \ + | xargs -0 -n1 -P1 -I{} \ + printf ' \n' +} + +Windows::writeManifest () { + local lib_dir="${1}" + + cat > "${manifest_file}" <<-EOF + + + $(Windows::listLibForManifest "${lib_dir}") + + EOF +} + system_name="${1}"; shift bundle_dir="${1}"; shift -exe_file="${1}"; shift +if ! [ -z "${1}" ] +then + exe_file="${1}"; shift +fi + +bundle_dir="$(Common::getPath "${bundle_dir}")" registry_dir="${bundle_dir}/registry" +lib_dir="${bundle_dir}/lib" + +manifest_file="${lib_dir}/lib.manifest" + +exe_action='Common::noOp' +lib_action='Common::noOp' case "${system_name}" in 'register') - mkdir --parents "${registry_dir}" - printf '%s\n' "${exe_file}" > "${registry_dir}/$(uuidgen)" + mkdir -p "${registry_dir}" + Common::getPath "${exe_file}" > "${registry_dir}/$(uuidgen)" + exit + ;; + 'linux'|'freebsd') + exe_action='Linux::patchExe' + lib_action='Linux::patchLib' ;; 'windows') - bundle_dir="$(cygpath --unix "${bundle_dir}")" - - lib_dir="${bundle_dir}/lib" - mkdir --parents "${lib_dir}" - - if [ -d "${registry_dir}" ] - then - for registry_entry in "${registry_dir}"/* - do - exe_file="$(cat "${registry_entry}")" - - Windows::bundleLibFromFile "${exe_file}" - - rm "${registry_entry}" - rmdir --ignore-fail-on-non-empty "${registry_dir}" - done - - manifest_file="${lib_dir}/lib.manifest" - Windows::printManifest "${lib_dir}" > "${manifest_file}" - fi + lib_action='Windows::writeManifest' + ;; + 'macos') + exe_action='Darwin::patchExe' + lib_action='Darwin::patchLib' ;; *) printf 'ERROR: unsupported system: %s\n' "${system_name}" >&2 exit 1 ;; esac + +mkdir -p "${lib_dir}" + +if [ -d "${registry_dir}" ] +then + for registry_entry in "${registry_dir}"/* + do + exe_file="$(cat "${registry_entry}")" + + Multi::bundleLibFromFile "${exe_file}" + + "${exe_action}" "${exe_file}" + + rm "${registry_entry}" + + "${exe_action}" "${exe_file}" + done + + rmdir "${registry_dir}" +fi + +"${lib_action}" "${lib_dir}" + +Multi::cleanUp