diff options
Diffstat (limited to '')
-rwxr-xr-x | packaging/macosx/osx-app.sh.in | 717 |
1 files changed, 717 insertions, 0 deletions
diff --git a/packaging/macosx/osx-app.sh.in b/packaging/macosx/osx-app.sh.in new file mode 100755 index 00000000..0bd0b932 --- /dev/null +++ b/packaging/macosx/osx-app.sh.in @@ -0,0 +1,717 @@ +#!/bin/bash +# +# USAGE +# osx-app [-s] [-l /path/to/libraries] +# +# This script attempts to build an application bundle for macOS, resolving +# dynamic libraries, etc. +# It strips the executable and libraries if '-s' is given. +# +# AUTHORS +# Kees Cook <kees@outflux.net> +# Michael Wybrow <mjwybrow@users.sourceforge.net> +# Jean-Olivier Irisson <jo.irisson@gmail.com> +# +# Copyright (C) 2005 Kees Cook +# Copyright (C) 2005-2007 Michael Wybrow +# Copyright (C) 2007 Jean-Olivier Irisson +# +# Released under GNU GPL, read the file 'COPYING' for more information +# +# Thanks to GNUnet's "build_app" script for help with library dep resolution. +# https://gnunet.org/svn/GNUnet/contrib/OSX/build_app +# +# NB: +# This originally came from Inkscape; Inkscape's configure script has an +# "--enable-osxapp", which causes some of Inkscape's installation data +# files to have macOS-ish paths under Contents/Resources of the bundle +# or under /Library/Application Support. We don't have such an option; +# we just put them in "bin", "etc", "lib", and "share" directories +# under Contents/Resources, rather than in the "bin", "etc", "lib", +# and "share" directories under the installation directory. +# + +# XXX We could probably replace a lot of this with https://github.com/auriamg/macdylibbundler + +shopt -s extglob + +# Defaults +strip=false +install_exclude_prefixes="/System/|/Library/|/usr/lib/|/usr/X11/|/opt/X11/|@executable_path" + +# Bundle always has the same name. Version information is stored in +# the Info.plist file which is filled in by the configure script. +bundle="Wireshark.app" + +# Location for libraries (tools/macos-setup.sh defaults to whatever the +# various support libraries use as their standard installation location, +# which is /usr/local) +if [ -z "$LIBPREFIX" ]; then + LIBPREFIX="/usr/local" +fi + + +# Help message +#---------------------------------------------------------- +help() +{ +echo -e " +Create an app bundle for macOS + +USAGE + $0 [-s] [-l /path/to/libraries] + +OPTIONS + -h,--help + Display this help message. + -b,--bundle + The application bundle name. Default is Wireshark.app. + -s + Strip the libraries and executables from debugging symbols. + -l,--libraries + Specify the path to the libraries the application depends + on (typically /sw or /opt/local). By default it is + /usr/local. + +EXAMPLE + $0 -b Logray.app -s -l /opt/local +" +} + + +# Parse command line arguments +#---------------------------------------------------------- +while [ "$1" != "" ] +do + case $1 in + -b|--bundle) + bundle="$2" + shift 1 ;; + -s) + strip=true ;; + -l|--libraries) + LIBPREFIX="$2" + shift 1 ;; + -h|--help) + help + exit 0 ;; + *) + echo "Invalid command line option: $1" + exit 2 ;; + esac + shift 1 +done + +# Safety tests +if [ ! -e "$LIBPREFIX" ]; then + echo "Cannot find the directory containing the libraries: $LIBPREFIX" >&2 + exit 1 +fi + +if [ ! -d "$bundle" ] ; then + echo "$bundle not found" >&2 + exit 1 +fi + +qt_frameworks_dir=$( "@QT_QMAKE_EXECUTABLE@" -query QT_INSTALL_LIBS ) +if [ ! -d "$qt_frameworks_dir" ] ; then + echo "Can't find the Qt frameworks directory" >&2 + exit 1 +fi + +sparkle_version="@SPARKLE_VERSION@" +sparkle_frameworks_dir="@SPARKLE_LIBRARY@" + +# +# Define the signing identities, or use self-signed ("-") +# if the identity is not provided. +if [ -n "$CODE_SIGN_IDENTITY" ] ; then + codesign_dev_app_identity="Developer ID Application: $CODE_SIGN_IDENTITY" + codesign_dev_install_identity="Developer ID Installer: $CODE_SIGN_IDENTITY" +else + codesign_dev_app_identity="-" + codesign_dev_install_identity="-" +fi + +# +# Leave the Qt frameworks out of the special processing. +# +install_exclude_prefixes="$install_exclude_prefixes|$qt_frameworks_dir" + +app_name=${bundle%%.app} +app_lower=$(echo "$app_name" | tr '[:upper:]' '[:lower:]') + +# Package paths +pkgexec="$bundle/Contents/MacOS" +#pkgres="$bundle/Contents/Resources" +pkglib="$bundle/Contents/Frameworks" +pkgplugin="$bundle/Contents/PlugIns/$app_lower/@PLUGIN_PATH_ID@" + +# Set the 'macosx' directory, usually the current directory. +#resdir=$( pwd ) + +# +# Get a list of all binaries in the bundle. +# Treat all plain files with read and execute permissions for all as +# binaries. +# +secondary_binary_list=() +while read -r binary ; do + secondary_binary_list+=("$binary") +done < <( find "$pkgexec" \! -name "$app_name" -type f -perm -0555 -print | sort ) +plugin_library_list=() +while read -r library ; do + plugin_library_list+=("$library") +done < <( find "$pkgplugin" -name "*.so" -type f -perm -0555 -print | sort ) +bundle_binary_list=("$pkgexec/$app_name" "${secondary_binary_list[@]}" "${plugin_library_list[@]}") + +echo -e "\\nFixing up $bundle..." + +# Start with a clean Frameworks slate. +if [ -d "$pkglib" ] ; then + printf "Removing %s\n" "$pkglib" + rm -v -r -f "$pkglib" +fi +mkdir -v -m u=rwx,go=rx "$pkglib" + +echo -e "\\nPrepopulating our libraries" + +# Copy only <library>.<SOVERSION>.dylib. +cp -v +([^.]).+([[:digit:]]).dylib "$pkglib" + +# Fetch a unique list of LC_RPATHs from our executables, which will be used +# for our dependency search below. +bundle_binary_rpaths=("/usr/local/lib") +rpaths=() + +# macdeployqt handles our Qt dependencies. We handle our Sparkle and +# internal dependencies. +skip_pats="Qt|Sparkle|build/run" + +for binary in "${bundle_binary_list[@]}" "$pkglib"/*.dylib ; do + while read -r rpath ; do + bundle_binary_rpaths+=("$rpath") + done < <( otool -l "$binary" | grep -A2 LC_RPATH | awk '$1=="path" && $2 !~ /^@/ {print $2}' | grep -E -v "$skip_pats" ) +done + +while read -r rpath ; do + rpaths+=("$rpath") +done < <( printf '%s\n' "${bundle_binary_rpaths[@]}" | sort -u) + +printf "\nSearching the following LC_RPATHs for dependencies:\n" +printf '%s\n' "${rpaths[@]}" + +# Find out libs we need from Fink, MacPorts, or from a custom install +# (i.e. $LIBPREFIX), then loop until no changes. +a=1 +nfiles=0 +endl=true +while $endl; do + echo -e "\\nLooking for dependencies. Round $a" + # + # To find dependencies, we: + # + # run otool -L on all the binaries in the bundle, and on all + # the shared libraries in the $pkglib directory, to find all + # the libraries they depend on (we don't bother with the + # frameworks, as the only frameworks we ship are the Qt + # frameworks, which don't depend on any libraries that + # don't ship with the OS, and as it's hard to find the + # framework libraries under $pkglib without getting + # non-framework files); + # + # filter out all lines that don't contain "compatibility" to + # remove lines of output that don't correspond to dependencies; + # + # use cut to extract the library name from the output; + + # replace "\tlibbrotli" with "\t/usr/local/lib/libbrotli" so that + # it isn't excluded from subsequent filtering. + # libbrotli 1.09 and earlier doesn't have a path prefix in its + # "install name" when built by tools/macos-setup.sh: + # https://github.com/google/brotli/pull/976; + # + # replace "@loader_path/libbrotli" with "/usr/local/lib/libbrotli" so that + # it isn't excluded from subsequent filtering; + # + # strip out system libraries, as we don't bundle them with + # Wireshark; + # + # eliminate duplicates. + # + # We might want to let dyld do some of the work for us, e.g. by + # parsing the output of + # + # `DYLD_PRINT_LIBRARIES=1 $bundle_binary` + # + # instead, or just use CMake's fixup_bundle: + # https://cmake.org/cmake/help/latest/module/BundleUtilities.html + libs=() + while read -r lib ; do + libs+=("$lib") + done < <( + otool -L "${bundle_binary_list[@]}" "$pkglib"/*.dylib 2>/dev/null \ + | grep -F compatibility \ + | grep -v @rpath \ + | cut -d\( -f1 \ + | sed '1,$s;^ libbrotli; /usr/local/lib/libbrotli;' \ + | sed '1,$s;^ @loader_path/libbrotli; /usr/local/lib/libbrotli;' \ + | grep -E -v "$install_exclude_prefixes" \ + | sort \ + | uniq \ + ) + while read -r rpath_lib _ ; do + suffix=${rpath_lib/@rpath\/} + for rpath in "${rpaths[@]}" ; do + if [ -f "$rpath/$suffix" ] ; then + printf "Found @rpath/%s in %s\n" "$suffix" "$rpath" + libs+=("$rpath/$suffix") + fi + done + done < <( otool -L "${bundle_binary_list[@]}" "$pkglib"/*.dylib \ + | grep @rpath \ + | grep -E -v "$skip_pats" \ + | sort -u \ + ) + install -m 644 -C -v "${libs[@]}" "$pkglib" + (( a++ )) + # shellcheck disable=SC2012 + nnfiles=$( ls "$pkglib" | wc -l ) + if (( nnfiles == nfiles )); then + endl=false + else + nfiles=$nnfiles + fi +done + +# Strip libraries and executables if requested +#---------------------------------------------------------- +if [ "$strip" = "true" ]; then + echo -e "\\nStripping debugging symbols...\\n" + strip -x "$pkglib"/*.dylib + strip -ur "${bundle_binary_list[@]}" +fi + +"@QT_MACDEPLOYQT_EXECUTABLE@" "$bundle" -no-strip -verbose=2 || exit 1 + +# +# The build process added to the Wireshark/Logray binary an rpath +# entry pointing to the directory containing the Qt frameworks; remove +# that entry from the binary in the package. +# +/usr/bin/install_name_tool -delete_rpath "$qt_frameworks_dir" "$pkgexec/$app_name" + +if [ -d "$sparkle_frameworks_dir" ] ; then + cp -R "$sparkle_frameworks_dir" "$pkglib" || exit 1 + # Remove these if we ever start sandboxing. + rm -f "$pkglib/Sparkle.framework/XPCServices" || exit 1 + rm -rf "$pkglib/Sparkle.framework/Versions/B/XPCServices" || exit 1 +fi + +# NOTE: we must rpathify *all* files, *including* Qt libraries etc., +# +rpathify_file () { + local rpathify_exclude_prefixes="$install_exclude_prefixes|@rpath" + + # Fix a given executable, library, or plugin to be relocatable + if [ ! -f "$1" ]; then + return 0; + fi + + # + # OK, what type of file is this? + # + if ! filetype=$( otool -hv "$1" | grep -E MH_MAGIC | awk '{print $5}' ; exit "${PIPESTATUS[0]}" ) ; then + echo "Unable to rpathify $1 in $( pwd ): file type failed." + exit 1 + fi + + case "$filetype" in + + EXECUTE|DYLIB|BUNDLE) + # + # Executable, library, or plugin. (Plugins + # can be either DYLIB or BUNDLE; shared + # libraries are DYLIB.) + # + # For DYLIB and BUNDLE, fix the shared + # library identification. + # + if [[ "$filetype" = "DYLIB" || "$filetype" = "BUNDLE" ]]; then + echo "Changing shared library identification of $1" + base=$( echo "$1" | awk -F/ '{print $NF}' ) + # + # The library will end up in a directory in + # the rpath; this is what we should change its + # ID to. + # + to=@rpath/$base + /usr/bin/install_name_tool -id "$to" "$1" + + # + # If we're a library and we depend on something in + # @executable_path/../Frameworks, replace that with + # @rpath. + # + while read -r dep_lib ; do + base=$( echo "$dep_lib" | awk -F/ '{print $NF}' ) + to="@rpath/$base" + echo "Changing reference to $dep_lib to $to in $1" + /usr/bin/install_name_tool -change "$dep_lib" "$to" "$1" + done < <( otool -L "$1" | grep @executable_path/../Frameworks | awk '{print $1}' ) + + # + # Try to work around brotli's lack of a full path + # https://github.com/google/brotli/issues/934 + # + while read -r base ; do + to="@rpath/$base" + echo "Changing reference to $base to $to in $1" + /usr/bin/install_name_tool -change "$base" "$to" "$1" + done < <( otool -L "$1" | grep '^ libbrotli' | awk '{print $1}' ) + fi + + # + # Find our local rpaths and remove them. + # + otool -l "$1" | grep -A2 LC_RPATH \ + | awk '$1=="path" && $2 !~ /^@/ {print $2}' \ + | grep -E -v "$rpathify_exclude_prefixes" | \ + while read -r lc_rpath ; do + echo "Stripping LC_RPATH $lc_rpath from $1" + install_name_tool -delete_rpath "$lc_rpath" "$1" + done + + # + # Add -Wl,-rpath,@executable_path/../Frameworks + # to the rpath, so it'll find the bundled + # frameworks and libraries if they're referred + # to by @rpath/, rather than having a wrapper + # script tweak DYLD_LIBRARY_PATH. + # + if [[ "$filetype" = "EXECUTE" ]]; then + if [ -d ../Frameworks ] ; then + framework_path=../Frameworks + elif [ -d ../../Frameworks ] ; then + framework_path=../../Frameworks + else + echo "Unable to find relative path to Frameworks for $1 from $( pwd )" + exit 1 + fi + + echo "Adding @executable_path/$framework_path to rpath of $1" + /usr/bin/install_name_tool -add_rpath @executable_path/$framework_path "$1" + fi + + # + # Show the minimum supported version of macOS + # for each executable or library + # + if [[ "$filetype" = "EXECUTE" || "$filetype" = "DYLIB" ]] ; then + echo "Minimum macOS version for $1:" + otool -l "$1" | grep -A3 LC_VERSION_MIN_MACOSX + fi + + # + # Get the list of dynamic libraries on which this + # file depends, and select only the libraries that + # are in $LIBPREFIX, as those are the only ones + # that we'll be shipping in the app bundle; the + # other libraries are system-supplied or supplied + # as part of X11, will be expected to be on the + # system on which the bundle will be installed, + # and should be referred to by their full pathnames. + # + local libs=() + while read -r lib ; do + libs+=("$lib") + done < <( otool -L "$1" \ + | grep -F compatibility \ + | cut -d\( -f1 \ + | grep -E -v "$rpathify_exclude_prefixes" \ + | sort \ + | uniq \ + ) + + for lib in "${libs[@]}"; do + # + # Get the file name of the library. + # + base=$( echo "$lib" | awk -F/ '{print $NF}' ) + # + # The library will end up in a directory in + # the rpath; this is what we should change its + # file name to. + # + to=@rpath/$base + # + # Change the reference to that library. + # + echo "Changing reference to $lib to $to in $1" + /usr/bin/install_name_tool -change "$lib" "$to" "$1" + done + ;; + esac +} + +rpathify_dir () { + # + # Make sure we *have* that directory + # + if [ -d "$1" ]; then + (cd "$1" || exit 1 + echo "rpathifying $1" + # + # Make sure we *have* files to fix + # + # shellcheck disable=SC2086 + files=$( ls $2 2>/dev/null ) + if [ -n "$files" ]; then + for file in $files; do + rpathify_file "$file" "$( pwd )" + done + else + echo "no files found in $1" + fi + ) + rf_ret=$? + if [ $rf_ret -ne 0 ] ; then exit $rf_ret ; fi + fi +} + +rpathify_files () { + # + # Fix bundle deps + # + rpathify_dir "$pkglib" "*.dylib" + rpathify_dir "$pkgexec" "*" + for plugindir in "$pkgplugin"/* + do + rpathify_dir "$plugindir" "*" + done + + rpathify_dir "$pkgexec/extcap" "*" +} + +if [ ${#LIBPREFIX} -ge "6" ]; then + # If the LIBPREFIX path is long enough to allow + # path rewriting, then do this. + # 6 is the length of @rpath, which replaces LIBPREFIX. + rpathify_files +else + echo "Could not rewrite dylib paths for bundled libraries. This requires" >&2 + echo "the support libraries to be installed in a PREFIX of at least 6 characters in length." >&2 + echo "" >&2 + exit 1 + +fi + +# QtNetwork might be linked with brotli. +rpathify_file "$pkglib/QtNetwork.framework/Versions/Current/QtNetwork" + +bundle_dsym="${bundle%%.app}.dSYM" + +frameworks=() +for framework in "$pkglib"/*.framework/Versions/*/* ; do + if [ -f "$framework" ];then + frameworks+=("$framework") + fi +done + +echo "Dsymifying binaries to $bundle_dsym:" +# shellcheck disable=SC2086 +dsymutil --minimize --out "$bundle_dsym" \ + "${bundle_binary_list[@]}" \ + "${frameworks[@]}" \ + "$pkglib"/*.dylib + +# echo "Stripping binaries:" +# # shellcheck disable=SC2086 +# strip -S \ +# "${bundle_binary_list[@]}" \ +# "${frameworks[@]}" \ +# "$pkglib"/*.dylib \ +# "$pkgplugin"/*/*.so + +# XXX What's the proper directory layout here? +# dsymify_file () { +# # out_dsym="${1/#$bundle/$bundle_dsym}.dSYM" +# echo " $1" +# dsymutil --minimize --out "$bundle_dsym" "$1" +# strip "$1" +# } + +# echo "Dsymifying and stripping executables:" +# if [ -z "${bundle_binary_list[@]}" ] ; then +# echo "No executables specified for dsymifying." +# exit 1 +# fi +# for binary in "${bundle_binary_list[@]}" ; do +# if [ -e "$binary" ];then +# dsymify_file "$binary" +# fi +# done + +# echo "Dsymifying and stripping frameworks:" +# for framework in "$pkglib"/*.framework/Versions/*/* ; do +# if [ -f "$framework" ];then +# dsymify_file "$framework" +# fi +# done + +# echo "Dsymifying and stripping libraries:" +# for library in "$pkglib"/*.dylib ; do +# # +# # Squelch warnings, in case the .o files from building +# # support libraries aren't around any more. +# # +# dsymify_file "$library" | grep -E -v 'unable to open object file' +# done + +# echo "Dsymifying and stripping plugins:" +# for plugin in "$pkgplugin"/*/*.so ; do +# dsymify_file "$plugin" +# done + +codesign_file () { + # https://developer.apple.com/forums/thread/128166 + # https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html + # https://developer.apple.com/library/archive/technotes/tn2206/_index.html + # https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution/resolving_common_notarization_issues?language=objc + # + # XXX Do we need to add the com.apple.security.cs.allow-unsigned-executable-memory + # entitlement for Lua? + # https://developer.apple.com/documentation/security/hardened_runtime_entitlements?language=objc + + codesign \ + --sign "$codesign_dev_app_identity" \ + --prefix "org.wireshark." \ + --force \ + --options runtime \ + --entitlements "@CMAKE_SOURCE_DIR@/packaging/macosx/entitlements.plist" \ + --timestamp \ + --verbose \ + "$1" || exit 1 +} + +# XXX We could do this via the productbuild calls in the {,un}install_*_pkg +# targets in CMakeLists.txt instead. +productsign_pkg () { + mv "$1" "$1.unsigned" || exit 1 + productsign \ + --sign "$codesign_dev_install_identity" \ + --timestamp \ + "$1.unsigned" "$1" || exit 1 + rm -f "$1.unsigned" || exit 1 +} + +if [ -n "$CODE_SIGN_IDENTITY" ] ; then + security find-identity -v -s "$CODE_SIGN_IDENTITY" -p codesigning + + # The Code Signing Guide says: + # + # "While you use the --deep option for verification to mimic what Gatekeeper does, + # it is not recommended for signing. During signing, if you have nested code, and + # if you are signing manually, you sign nested code in stages (as Xcode does + # automatically), starting with the most deeply embedded components first. You + # then sign code at the next level of hierarchy, and so on. You work your way + # outward, finally signing the top level entity that contains all the others. + # Signing all the components in one shot with --deep is for emergency repairs and + # temporary adjustments only. Note that signing with the combination --deep + # --force will forcibly re-sign all code in a bundle." + + # We need to force-sign Sparkle and its Updater.app. + # https://sparkle-project.org/documentation/#4-distributing-your-app + # https://sparkle-project.org/documentation/sandboxing/#code-signing + + if [ "$sparkle_version" == "2" ] ; then + echo "Signing Sparkle's assets" + codesign \ + --sign "$codesign_dev_app_identity" \ + --force \ + --options runtime \ + --verbose \ + "$pkglib/Sparkle.framework/Versions/B/AutoUpdate" \ + "$pkglib/Sparkle.framework/Versions/B/Updater.app" \ + "$pkglib/Sparkle.framework" \ + || exit 1 + # Uncomment if we ever start sandboxing. + # "$pkglib/Sparkle.framework/Versions/B/XPCServices/org.sparkle-project.InstallerLauncher.xpc" + # codesign \ + # --sign "$codesign_dev_app_identity" \ + # --force \ + # --options runtime \ + # --entitlements "$sparkle_frameworks_dir/../Entitlements/org.sparkle-project.Downloader.entitlements" \ + # --verbose \ + # "$pkglib/Sparkle.framework/Versions/B/XPCServices/org.sparkle-project.Downloader.xpc" \ + # || exit 1 + else + echo "Signing Sparkle's AutoUpdate.app" + codesign \ + --sign "$codesign_dev_app_identity" \ + --force \ + --timestamp \ + --options runtime \ + --verbose \ + "$pkglib/Sparkle.framework/Versions/A/Resources/AutoUpdate.app" \ + || exit 1 + fi + + echo "Signing frameworks" + for framework in "$pkglib"/*.framework/Versions/* ; do + if [ -L "$framework" ] ; then + # Skip "Current" + continue + fi + codesign_file "$framework" + done + + echo "Signing libraries" + for library in "$pkglib"/*.dylib ; do + codesign_file "$library" + done + + plugin_list=$( find "$bundle/Contents/PlugIns" -type f -name "*.dylib" -o -name "*.so" ) + echo "Signing plugins" + for plugin in $plugin_list ; do + codesign_file "$plugin" + done + + echo "Signing extra packages" + find "$bundle/Contents/Resources/Extras" -type f -name "*.pkg" | \ + while read -r extra_pkg ; do + productsign_pkg "$extra_pkg" + done + + echo "Signing secondary executables" + if (( ! ${#secondary_binary_list[@]} )) ; then + echo "No executables specified for code signing." + exit 1 + fi + for binary in "${secondary_binary_list[@]}" ; do + if [ -e "$binary" ];then + codesign_file "$binary" + fi + done + + echo "Signing primary executable" + codesign_file "$pkgexec/$app_name" + + echo "Signing $bundle" + codesign_file "$bundle" + + # Code Signing Guide, "Testing Conformance with Command Line Tools" + codesign --verify --deep --strict --verbose=2 "$bundle" || exit 1 + spctl --assess --type exec --verbose=2 "$bundle" || exit 1 +else + echo "Code signing not performed (no identity)" +fi + +# File permission sanity check. +if badperms=$( find "$bundle" ! -perm -0444 -exec ls -l "{}" + | grep . ) ; then + echo "Found files with restrictive permissions:" + echo "$badperms" + exit 1 +fi + +exit 0 |