]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - library-bundler
netradiant: strip 16-bit png to 8-bit, fix #153
[xonotic/netradiant.git] / library-bundler
index ea276cea7230f955dc50d3a2ff3d5252329cd77b..e9a7ccddf12d1eef487ed2cb93015a2ea0eb7310 100755 (executable)
@@ -1,7 +1,34 @@
 #! /usr/bin/env bash
 
+set -e
+
+export LANG='C.UTF-8'
+export LANGUAGE="${LANG}"
+
+_sed () {
+       case "${system_name}" in
+               'macos'|'freebsd')
+                       gsed "${@}"
+                       ;;
+               *)
+                       sed "${@}"
+                       ;;
+       esac
+}
+
+_cp () {
+       case "${system_name}" in
+               'macos'|'freebsd')
+                       gcp -R --preserve=timestamps -H -L "${1}" "${2}"
+                       ;;
+               *)
+                       cp -R --preserve=timestamps -H -L "${1}" "${2}"
+                       ;;
+       esac
+}
+
 Common::noOp () {
-       printf ''
+       true
 }
 
 Common::getPath () {
@@ -12,7 +39,7 @@ Common::getPath () {
                if [ "${file_path}" = '-' ]
                then
                        tr '\n' '\0' \
-                       | xargs -0 -n1 -P1 -I{} \
+                       | xargs -0 -P1 -I{} \
                                cygpath --unix '{}'
                else
                        cygpath --unix "${file_path}"
@@ -24,20 +51,36 @@ Common::getPath () {
                else
                        printf '%s\n' "${file_path}"
                fi
-       fi
+       fi \
+       | _sed -e 's|/*$||'
 }
 
 Common::grepLdd () {
-       egrep ' => '
+       case "${system_name}" in
+               'macos')
+                       egrep '^\t/'
+                       ;;
+               *)
+                       egrep ' => '
+                       ;;
+       esac
 }
 
 Common::stripLdd () {
-       sed -e 's/ (0x[0-9a-f]*)$//;s/^.* => //'
+       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')
+               '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.
@@ -46,11 +89,35 @@ Multi::excludeLdd () {
                        #   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
-                       egrep -v '/libc\.|/libstdc\+\+\.|/libdl\.|/libm\.|/libX|/libxcb|/libGL|/libICE\.|/libSM\.' \
-                       | egrep -v '/libatk|/libgdk|/libgtk|/libgio|/libglib|/libgmodule|/libgobject|/libpango|/libfontconfig|/libfreetype'
+                       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\\' \
+                       egrep -i '\.dll => [A-Z]:\\msys64\\'
+                       ;;
+               'macos')
+                       egrep -v '^\t/System/|^\t/usr/lib/'
                        ;;
        esac
 }
@@ -66,34 +133,42 @@ Multi::printLdd () {
        local exe_file="${1}"
 
        case "${system_name}" in
-               'linux')
+               'linux'|'freebsd')
                        ldd "${exe_file}"
                        ;;
                'windows')
                        ntldd --recursive "${exe_file}"
                        ;;
+               'macos')
+                       otool -L "${exe_file}"
        esac
 }
 
 Multi::getGtkThemeName () {
        case "${system_name}" in
-               'linux')
+               'linux'|'freebsd')
                        echo 'Adwaita'
                        ;;
                'windows')
                        echo 'MS-Windows'
                        ;;
+               *)
+                       echo 'Raleigh'
+                       ;;
        esac
 }
 
 Multi::getGtkLibName () {
        case "${system_name}" in
-               'linux')
+               '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
 }
 
@@ -102,12 +177,14 @@ Multi::getRootPrefix () {
 
        case "${system_name}" in
                'linux')
-                       echo "${lib_file}" \
-                       | cut -f2 -d'/'
+                       echo 'usr'
+                       ;;
+               'freebsd'|'macos')
+                       echo 'usr/local'
                        ;;
                'windows')
                        basename "${lib_file}" \
-                       | xargs -n1 -P 1 which \
+                       | xargs -n1 -P1 which \
                        | cut -f2 -d'/'
                        ;;
        esac
@@ -117,13 +194,16 @@ Multi::getLibPrefix () {
        local lib_file="${1}"
 
        case "${system_name}" in
-               'linux')
+               'linux'|'freebsd')
                        dirname "${lib_file}" \
                        | cut -f3- -d'/'
                        ;;
                'windows')
                        echo 'lib'
                        ;;
+               'macos')
+                       echo 'lib'
+                       ;;
        esac
 }
 
@@ -131,15 +211,30 @@ Multi::getGtkDeps () {
        local lib_prefix="${1}"
        local gtk_theme_name="${2}"
 
-       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
+       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')
+               'linux'|'freebsd')
                        cat <<-EOF
                        ${lib_prefix}/libatk-bridge-2.0.so.0
                        ${lib_prefix}/libcanberra-0.30
@@ -150,8 +245,29 @@ Multi::getGtkDeps () {
        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
 
        lib_basename="$(basename "${lib_file}")"
 
@@ -164,14 +280,18 @@ Multi::bundleGtkDepsFromFile () {
 
                for component_dir in $(Multi::getGtkDeps "${lib_prefix}" "${gtk_theme_name}")
                do
-                       bundle_component_dir="$(echo "${component_dir}" | sed -e 's|^'"${lib_prefix}"'|lib|')"
+                       bundle_component_dir="$(echo "${component_dir}" | _sed -e 's|^'"${lib_prefix}"'|lib|')"
                        if ! [ -e "${bundle_dir}/${bundle_component_dir}" ]
                        then
-                               mkdir --parents "${bundle_dir}/$(dirname "${bundle_component_dir}")"
+                               real_component_dir="$(realpath "/${root_prefix}/${component_dir}")"
 
-                               cp -H -r --preserve=timestamps \
-                                       "/${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
@@ -179,56 +299,144 @@ Multi::bundleGtkDepsFromFile () {
 
 Multi::bundleLibFromFile () {
        local exe_file="${1}"
+       local lib_file
 
        Multi::printLdd "${exe_file}" \
        | Multi::filterLib \
        | while read lib_file
        do
+               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}/${lib_basename}" ]
+               if [ -f "${lib_dir}/${lib_basename}" ]
                then
                        continue
                fi
 
-               cp --preserve=timestamps \
+               _cp \
                        "${lib_file}" \
                        "${lib_dir}/${lib_basename}"
 
                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 {} \;
+               -exec rm -f {} \;
 
        find "${bundle_dir}/lib" \
                -type f \
                -name '*.h' \
-               -exec rm {} \;
+               -exec rm -f {} \;
 
        find "${bundle_dir}/lib" \
-               -type d \
                -depth \
-               -exec rmdir --ignore-fail-on-non-empty {} \;
+               -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|^/||')"
+
+       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
+                               rpath_subdir="${rpath_subdir}/.."
+                               i="$((${i} + 1))"
+                       done
+                       printf '%s%s/lib\n' "${rpath_origin}" "${rpath_subdir}"
+               fi
+       fi
 }
 
 Linux::patchExe () {
        local exe_file="${1}"
 
-       patchelf --set-rpath "${rpath_string}" "${exe_file}"
+       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*' \
-               -exec patchelf --set-rpath "${rpath_string}" {} \;
+       | 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 () {
@@ -240,7 +448,7 @@ Windows::listLibForManifest () {
                -name '*.dll' \
                -exec basename {} \; \
        | tr '\n' '\0' \
-       | xargs -0 -n1 -P1 -I{} \
+       | xargs -0 -P1 -I{} \
                printf '  <file name="{}"/>\n'
 }
 
@@ -257,7 +465,11 @@ Windows::writeManifest () {
 
 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"
@@ -265,31 +477,33 @@ lib_dir="${bundle_dir}/lib"
 
 manifest_file="${lib_dir}/lib.manifest"
 
-rpath_string='@executable_path:lib'
-
 exe_action='Common::noOp'
 lib_action='Common::noOp'
 
 case "${system_name}" in
        'register')
-               mkdir --parents "${registry_dir}"
+               mkdir -p "${registry_dir}"
                Common::getPath "${exe_file}" > "${registry_dir}/$(uuidgen)"
                exit
                ;;
-       'linux')
+       'linux'|'freebsd')
                exe_action='Linux::patchExe'
                lib_action='Linux::patchLib'
                ;;
        'windows')
                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 --parents "${lib_dir}"
+mkdir -p "${lib_dir}"
 
 if [ -d "${registry_dir}" ]
 then