4 # osx-app [-s] [-l /path/to/libraries]
6 # This script attempts to build an application bundle for macOS, resolving
7 # dynamic libraries, etc.
8 # It strips the executable and libraries if '-s' is given.
11 # Kees Cook <kees@outflux.net>
12 # Michael Wybrow <mjwybrow@users.sourceforge.net>
13 # Jean-Olivier Irisson <jo.irisson@gmail.com>
15 # Copyright (C) 2005 Kees Cook
16 # Copyright (C) 2005-2007 Michael Wybrow
17 # Copyright (C) 2007 Jean-Olivier Irisson
19 # Released under GNU GPL, read the file 'COPYING' for more information
21 # Thanks to GNUnet's "build_app" script for help with library dep resolution.
22 # https://gnunet.org/svn/GNUnet/contrib/OSX/build_app
25 # This originally came from Inkscape; Inkscape's configure script has an
26 # "--enable-osxapp", which causes some of Inkscape's installation data
27 # files to have macOS-ish paths under Contents/Resources of the bundle
28 # or under /Library/Application Support. We don't have such an option;
29 # we just put them in "bin", "etc", "lib", and "share" directories
30 # under Contents/Resources, rather than in the "bin", "etc", "lib",
31 # and "share" directories under the installation directory.
34 # XXX We could probably replace a lot of this with https://github.com/auriamg/macdylibbundler
40 install_exclude_prefixes
="/System/|/Library/|/usr/lib/|/usr/X11/|/opt/X11/|@executable_path"
42 # Bundle always has the same name. Version information is stored in
43 # the Info.plist file which is filled in by the configure script.
44 bundle
="Wireshark.app"
46 # Location for libraries (tools/macos-setup.sh defaults to whatever the
47 # various support libraries use as their standard installation location,
48 # which is /usr/local)
49 if [ -z "$LIBPREFIX" ]; then
50 LIBPREFIX
="/usr/local"
55 #----------------------------------------------------------
59 Create an app bundle for macOS
62 $0 [-s] [-l /path/to/libraries]
66 Display this help message.
68 The application bundle name. Default is Wireshark.app.
70 Strip the libraries and executables from debugging symbols.
72 Specify the path to the libraries the application depends
73 on (typically /sw or /opt/local). By default it is
77 $0 -b Stratoshark.app -s -l /opt/local
82 # Parse command line arguments
83 #----------------------------------------------------------
99 echo "Invalid command line option: $1"
106 if [ ! -e "$LIBPREFIX" ]; then
107 echo "Cannot find the directory containing the libraries: $LIBPREFIX" >&2
111 if [ ! -d "$bundle" ] ; then
112 echo "$bundle not found" >&2
116 qt_frameworks_dir
=$
( "@QT_QMAKE_EXECUTABLE@" -query QT_INSTALL_LIBS
)
117 if [ ! -d "$qt_frameworks_dir" ] ; then
118 echo "Can't find the Qt frameworks directory" >&2
122 sparkle_version
="@SPARKLE_VERSION@"
123 sparkle_frameworks_dir
="@SPARKLE_LIBRARY@"
126 # Define the signing identities, or use self-signed ("-")
127 # if the identity is not provided.
128 if [ -n "$CODE_SIGN_IDENTITY" ] ; then
129 codesign_dev_app_identity
="Developer ID Application: $CODE_SIGN_IDENTITY"
130 codesign_dev_install_identity
="Developer ID Installer: $CODE_SIGN_IDENTITY"
132 codesign_dev_app_identity
="-"
133 codesign_dev_install_identity
="-"
137 # Leave the Qt frameworks out of the special processing.
139 install_exclude_prefixes
="$install_exclude_prefixes|$qt_frameworks_dir"
141 app_name
=${bundle%%.app}
142 app_lower
=$
(echo "$app_name" |
tr '[:upper:]' '[:lower:]')
145 pkgexec
="$bundle/Contents/MacOS"
146 #pkgres="$bundle/Contents/Resources"
147 pkglib
="$bundle/Contents/Frameworks"
148 pkgplugin
="$bundle/Contents/PlugIns/$app_lower/@PLUGIN_PATH_ID@"
150 # Set the 'macosx' directory, usually the current directory.
154 # Get a list of all binaries in the bundle.
155 # Treat all plain files with read and execute permissions for all as
158 secondary_binary_list
=()
159 while read -r binary
; do
160 secondary_binary_list
+=("$binary")
161 done < <( find "$pkgexec" \
! -name "$app_name" -type f
-perm -0555 -print |
sort )
162 plugin_library_list
=()
163 while read -r library
; do
164 plugin_library_list
+=("$library")
165 done < <( find "$pkgplugin" -name "*.so" -type f
-perm -0555 -print |
sort )
166 bundle_binary_list
=("$pkgexec/$app_name" "${secondary_binary_list[@]}" "${plugin_library_list[@]}")
168 echo -e "\\nFixing up $bundle..."
170 # Start with a clean Frameworks slate.
171 if [ -d "$pkglib" ] ; then
172 printf "Removing %s\n" "$pkglib"
173 rm -v -r -f "$pkglib"
175 mkdir
-v -m u
=rwx
,go
=rx
"$pkglib"
177 echo -e "\\nPrepopulating our libraries"
179 # Copy only <library>.<SOVERSION>.dylib.
180 cp -v +([^.
]).
+([[:digit
:]]).dylib
"$pkglib"
182 # Fetch a unique list of LC_RPATHs from our executables, which will be used
183 # for our dependency search below.
184 bundle_binary_rpaths
=("/usr/local/lib")
187 # macdeployqt handles our Qt dependencies. We handle our Sparkle and
188 # internal dependencies.
189 skip_pats
="Qt|Sparkle|build/run"
191 for binary
in "${bundle_binary_list[@]}" "$pkglib"/*.dylib
; do
192 while read -r rpath
; do
193 bundle_binary_rpaths
+=("$rpath")
194 done < <( otool
-l "$binary" |
grep -A2 LC_RPATH |
awk '$1=="path" && $2 !~ /^@/ {print $2}' |
grep -E -v "$skip_pats" )
197 while read -r rpath
; do
199 done < <( printf '%s\n' "${bundle_binary_rpaths[@]}" |
sort -u)
201 printf "\nSearching the following LC_RPATHs for dependencies:\n"
202 printf '%s\n' "${rpaths[@]}"
204 # Find out libs we need from Fink, MacPorts, or from a custom install
205 # (i.e. $LIBPREFIX), then loop until no changes.
210 echo -e "\\nLooking for dependencies. Round $a"
212 # To find dependencies, we:
214 # run otool -L on all the binaries in the bundle, and on all
215 # the shared libraries in the $pkglib directory, to find all
216 # the libraries they depend on (we don't bother with the
217 # frameworks, as the only frameworks we ship are the Qt
218 # frameworks, which don't depend on any libraries that
219 # don't ship with the OS, and as it's hard to find the
220 # framework libraries under $pkglib without getting
221 # non-framework files);
223 # filter out all lines that don't contain "compatibility" to
224 # remove lines of output that don't correspond to dependencies;
226 # use cut to extract the library name from the output;
228 # replace "\tlibbrotli" with "\t/usr/local/lib/libbrotli" so that
229 # it isn't excluded from subsequent filtering.
230 # libbrotli 1.09 and earlier doesn't have a path prefix in its
231 # "install name" when built by tools/macos-setup.sh:
232 # https://github.com/google/brotli/pull/976;
234 # replace "@loader_path/libbrotli" with "/usr/local/lib/libbrotli" so that
235 # it isn't excluded from subsequent filtering;
237 # strip out system libraries, as we don't bundle them with
240 # eliminate duplicates.
242 # We might want to let dyld do some of the work for us, e.g. by
243 # parsing the output of
245 # `DYLD_PRINT_LIBRARIES=1 $bundle_binary`
247 # instead, or just use CMake's fixup_bundle:
248 # https://cmake.org/cmake/help/latest/module/BundleUtilities.html
250 while read -r lib
; do
253 otool
-L "${bundle_binary_list[@]}" "$pkglib"/*.dylib
2>/dev
/null \
254 |
grep -F compatibility \
257 |
sed '1,$s;^ libbrotli; /usr/local/lib/libbrotli;' \
258 |
sed '1,$s;^ @loader_path/libbrotli; /usr/local/lib/libbrotli;' \
259 |
grep -E -v "$install_exclude_prefixes" \
263 while read -r rpath_lib _
; do
264 suffix
=${rpath_lib/@rpath\/}
265 for rpath
in "${rpaths[@]}" ; do
266 if [ -f "$rpath/$suffix" ] ; then
267 printf "Found @rpath/%s in %s\n" "$suffix" "$rpath"
268 libs
+=("$rpath/$suffix")
271 done < <( otool
-L "${bundle_binary_list[@]}" "$pkglib"/*.dylib \
273 |
grep -E -v "$skip_pats" \
276 install -m 644 -C -v "${libs[@]}" "$pkglib"
278 # shellcheck disable=SC2012
279 nnfiles
=$
( ls "$pkglib" |
wc -l )
280 if (( nnfiles
== nfiles
)); then
287 # Strip libraries and executables if requested
288 #----------------------------------------------------------
289 if [ "$strip" = "true" ]; then
290 echo -e "\\nStripping debugging symbols...\\n"
291 strip
-x "$pkglib"/*.dylib
292 strip
-ur "${bundle_binary_list[@]}"
295 "@QT_MACDEPLOYQT_EXECUTABLE@" "$bundle" -no-strip -verbose=2 ||
exit 1
298 # The build process added to the Wireshark/Stratoshark binary an rpath
299 # entry pointing to the directory containing the Qt frameworks; remove
300 # that entry from the binary in the package.
302 /usr
/bin
/install_name_tool
-delete_rpath "$qt_frameworks_dir" "$pkgexec/$app_name"
304 if [ -d "$sparkle_frameworks_dir" ] ; then
305 cp -R "$sparkle_frameworks_dir" "$pkglib" ||
exit 1
306 # Remove these if we ever start sandboxing.
307 rm -f "$pkglib/Sparkle.framework/XPCServices" ||
exit 1
308 rm -rf "$pkglib/Sparkle.framework/Versions/B/XPCServices" ||
exit 1
311 # NOTE: we must rpathify *all* files, *including* Qt libraries etc.,
314 local rpathify_exclude_prefixes
="$install_exclude_prefixes|@rpath"
316 # Fix a given executable, library, or plugin to be relocatable
317 if [ ! -f "$1" ]; then
322 # OK, what type of file is this?
324 if ! filetype
=$
( otool
-hv "$1" |
grep -E MH_MAGIC |
awk '{print $5}' ; exit "${PIPESTATUS[0]}" ) ; then
325 echo "Unable to rpathify $1 in $( pwd ): file type failed."
331 EXECUTE|DYLIB|BUNDLE
)
333 # Executable, library, or plugin. (Plugins
334 # can be either DYLIB or BUNDLE; shared
335 # libraries are DYLIB.)
337 # For DYLIB and BUNDLE, fix the shared
338 # library identification.
340 if [[ "$filetype" = "DYLIB" ||
"$filetype" = "BUNDLE" ]]; then
341 echo "Changing shared library identification of $1"
342 base
=$
( echo "$1" |
awk -F/ '{print $NF}' )
344 # The library will end up in a directory in
345 # the rpath; this is what we should change its
349 /usr
/bin
/install_name_tool
-id "$to" "$1"
352 # If we're a library and we depend on something in
353 # @executable_path/../Frameworks, replace that with
356 while read -r dep_lib
; do
357 base
=$
( echo "$dep_lib" |
awk -F/ '{print $NF}' )
359 echo "Changing reference to $dep_lib to $to in $1"
360 /usr
/bin
/install_name_tool
-change "$dep_lib" "$to" "$1"
361 done < <( otool
-L "$1" |
grep @executable_path
/..
/Frameworks |
awk '{print $1}' )
364 # Try to work around brotli's lack of a full path
365 # https://github.com/google/brotli/issues/934
367 while read -r base
; do
369 echo "Changing reference to $base to $to in $1"
370 /usr
/bin
/install_name_tool
-change "$base" "$to" "$1"
371 done < <( otool
-L "$1" |
grep '^ libbrotli' |
awk '{print $1}' )
375 # Find our local rpaths and remove them.
377 otool
-l "$1" |
grep -A2 LC_RPATH \
378 |
awk '$1=="path" && $2 !~ /^@/ {print $2}' \
379 |
grep -E -v "$rpathify_exclude_prefixes" | \
380 while read -r lc_rpath
; do
381 echo "Stripping LC_RPATH $lc_rpath from $1"
382 install_name_tool
-delete_rpath "$lc_rpath" "$1"
386 # Add -Wl,-rpath,@executable_path/../Frameworks
387 # to the rpath, so it'll find the bundled
388 # frameworks and libraries if they're referred
389 # to by @rpath/, rather than having a wrapper
390 # script tweak DYLD_LIBRARY_PATH.
392 if [[ "$filetype" = "EXECUTE" ]]; then
393 if [ -d ..
/Frameworks
] ; then
394 framework_path
=..
/Frameworks
395 elif [ -d ..
/..
/Frameworks
] ; then
396 framework_path
=..
/..
/Frameworks
398 echo "Unable to find relative path to Frameworks for $1 from $( pwd )"
402 echo "Adding @executable_path/$framework_path to rpath of $1"
403 /usr
/bin
/install_name_tool
-add_rpath @executable_path
/$framework_path "$1"
407 # Show the minimum supported version of macOS
408 # for each executable or library
410 if [[ "$filetype" = "EXECUTE" ||
"$filetype" = "DYLIB" ]] ; then
411 echo "Minimum macOS version for $1:"
412 otool
-l "$1" |
grep -A3 LC_VERSION_MIN_MACOSX
416 # Get the list of dynamic libraries on which this
417 # file depends, and select only the libraries that
418 # are in $LIBPREFIX, as those are the only ones
419 # that we'll be shipping in the app bundle; the
420 # other libraries are system-supplied or supplied
421 # as part of X11, will be expected to be on the
422 # system on which the bundle will be installed,
423 # and should be referred to by their full pathnames.
426 while read -r lib
; do
428 done < <( otool
-L "$1" \
429 |
grep -F compatibility \
431 |
grep -E -v "$rpathify_exclude_prefixes" \
436 for lib
in "${libs[@]}"; do
438 # Get the file name of the library.
440 base
=$
( echo "$lib" |
awk -F/ '{print $NF}' )
442 # The library will end up in a directory in
443 # the rpath; this is what we should change its
448 # Change the reference to that library.
450 echo "Changing reference to $lib to $to in $1"
451 /usr
/bin
/install_name_tool
-change "$lib" "$to" "$1"
459 # Make sure we *have* that directory
463 echo "rpathifying $1"
465 # Make sure we *have* files to fix
467 # shellcheck disable=SC2086
468 files
=$
( ls $2 2>/dev
/null
)
469 if [ -n "$files" ]; then
470 for file in $files; do
471 rpathify_file
"$file" "$( pwd )"
474 echo "no files found in $1"
478 if [ $rf_ret -ne 0 ] ; then exit $rf_ret ; fi
486 rpathify_dir
"$pkglib" "*.dylib"
487 rpathify_dir
"$pkgexec" "*"
488 for plugindir
in "$pkgplugin"/*
490 rpathify_dir
"$plugindir" "*"
493 rpathify_dir
"$pkgexec/extcap" "*"
496 if [ ${#LIBPREFIX} -ge "6" ]; then
497 # If the LIBPREFIX path is long enough to allow
498 # path rewriting, then do this.
499 # 6 is the length of @rpath, which replaces LIBPREFIX.
502 echo "Could not rewrite dylib paths for bundled libraries. This requires" >&2
503 echo "the support libraries to be installed in a PREFIX of at least 6 characters in length." >&2
509 # QtNetwork might be linked with brotli.
510 rpathify_file
"$pkglib/QtNetwork.framework/Versions/Current/QtNetwork"
512 bundle_dsym
="${bundle%%.app}.dSYM"
515 for framework
in "$pkglib"/*.framework
/Versions
/*/* ; do
516 if [ -f "$framework" ];then
517 frameworks
+=("$framework")
521 echo "Dsymifying binaries to $bundle_dsym:"
522 # shellcheck disable=SC2086
523 dsymutil
--minimize --out "$bundle_dsym" \
524 "${bundle_binary_list[@]}" \
528 # echo "Stripping binaries:"
529 # # shellcheck disable=SC2086
531 # "${bundle_binary_list[@]}" \
532 # "${frameworks[@]}" \
533 # "$pkglib"/*.dylib \
534 # "$pkgplugin"/*/*.so
536 # XXX What's the proper directory layout here?
538 # # out_dsym="${1/#$bundle/$bundle_dsym}.dSYM"
540 # dsymutil --minimize --out "$bundle_dsym" "$1"
544 # echo "Dsymifying and stripping executables:"
545 # if [ -z "${bundle_binary_list[@]}" ] ; then
546 # echo "No executables specified for dsymifying."
549 # for binary in "${bundle_binary_list[@]}" ; do
550 # if [ -e "$binary" ];then
551 # dsymify_file "$binary"
555 # echo "Dsymifying and stripping frameworks:"
556 # for framework in "$pkglib"/*.framework/Versions/*/* ; do
557 # if [ -f "$framework" ];then
558 # dsymify_file "$framework"
562 # echo "Dsymifying and stripping libraries:"
563 # for library in "$pkglib"/*.dylib ; do
565 # # Squelch warnings, in case the .o files from building
566 # # support libraries aren't around any more.
568 # dsymify_file "$library" | grep -E -v 'unable to open object file'
571 # echo "Dsymifying and stripping plugins:"
572 # for plugin in "$pkgplugin"/*/*.so ; do
573 # dsymify_file "$plugin"
577 # https://developer.apple.com/forums/thread/128166
578 # https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html
579 # https://developer.apple.com/library/archive/technotes/tn2206/_index.html
580 # https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution/resolving_common_notarization_issues?language=objc
582 # XXX Do we need to add the com.apple.security.cs.allow-unsigned-executable-memory
583 # entitlement for Lua?
584 # https://developer.apple.com/documentation/security/hardened_runtime_entitlements?language=objc
587 --sign "$codesign_dev_app_identity" \
588 --prefix "org.wireshark." \
591 --entitlements "@CMAKE_SOURCE_DIR@/packaging/macosx/entitlements.plist" \
597 # XXX We could do this via the productbuild calls in the {,un}install_*_pkg
598 # targets in CMakeLists.txt instead.
600 mv "$1" "$1.unsigned" ||
exit 1
602 --sign "$codesign_dev_install_identity" \
604 "$1.unsigned" "$1" ||
exit 1
605 rm -f "$1.unsigned" ||
exit 1
608 if [ -n "$CODE_SIGN_IDENTITY" ] ; then
609 security find-identity
-v -s "$CODE_SIGN_IDENTITY" -p codesigning
611 # The Code Signing Guide says:
613 # "While you use the --deep option for verification to mimic what Gatekeeper does,
614 # it is not recommended for signing. During signing, if you have nested code, and
615 # if you are signing manually, you sign nested code in stages (as Xcode does
616 # automatically), starting with the most deeply embedded components first. You
617 # then sign code at the next level of hierarchy, and so on. You work your way
618 # outward, finally signing the top level entity that contains all the others.
619 # Signing all the components in one shot with --deep is for emergency repairs and
620 # temporary adjustments only. Note that signing with the combination --deep
621 # --force will forcibly re-sign all code in a bundle."
623 # We need to force-sign Sparkle and its Updater.app.
624 # https://sparkle-project.org/documentation/#4-distributing-your-app
625 # https://sparkle-project.org/documentation/sandboxing/#code-signing
627 if [ "$sparkle_version" == "2" ] ; then
628 echo "Signing Sparkle's assets"
630 --sign "$codesign_dev_app_identity" \
634 "$pkglib/Sparkle.framework/Versions/B/AutoUpdate" \
635 "$pkglib/Sparkle.framework/Versions/B/Updater.app" \
636 "$pkglib/Sparkle.framework" \
638 # Uncomment if we ever start sandboxing.
639 # "$pkglib/Sparkle.framework/Versions/B/XPCServices/org.sparkle-project.InstallerLauncher.xpc"
641 # --sign "$codesign_dev_app_identity" \
643 # --options runtime \
644 # --entitlements "$sparkle_frameworks_dir/../Entitlements/org.sparkle-project.Downloader.entitlements" \
646 # "$pkglib/Sparkle.framework/Versions/B/XPCServices/org.sparkle-project.Downloader.xpc" \
649 echo "Signing Sparkle's AutoUpdate.app"
651 --sign "$codesign_dev_app_identity" \
656 "$pkglib/Sparkle.framework/Versions/A/Resources/AutoUpdate.app" \
660 echo "Signing frameworks"
661 for framework
in "$pkglib"/*.framework
/Versions
/* ; do
662 if [ -L "$framework" ] ; then
666 codesign_file
"$framework"
669 echo "Signing libraries"
670 for library
in "$pkglib"/*.dylib
; do
671 codesign_file
"$library"
674 plugin_list
=$
( find "$bundle/Contents/PlugIns" -type f
-name "*.dylib" -o -name "*.so" )
675 echo "Signing plugins"
676 for plugin
in $plugin_list ; do
677 codesign_file
"$plugin"
680 echo "Signing extra packages"
681 find "$bundle/Contents/Resources/Extras" -type f
-name "*.pkg" | \
682 while read -r extra_pkg
; do
683 productsign_pkg
"$extra_pkg"
686 echo "Signing secondary executables"
687 if (( ! ${#secondary_binary_list[@]} )) ; then
688 echo "No executables specified for code signing."
691 for binary
in "${secondary_binary_list[@]}" ; do
692 if [ -e "$binary" ];then
693 codesign_file
"$binary"
697 echo "Signing primary executable"
698 codesign_file
"$pkgexec/$app_name"
700 echo "Signing $bundle"
701 codesign_file
"$bundle"
703 # Code Signing Guide, "Testing Conformance with Command Line Tools"
704 codesign
--verify --deep --strict --verbose=2 "$bundle" ||
exit 1
705 spctl
--assess --type exec --verbose=2 "$bundle" ||
exit 1
707 echo "Code signing not performed (no identity)"
710 # File permission sanity check.
711 if badperms
=$
( find "$bundle" ! -perm -0444 -exec ls -l "{}" + |
grep .
) ; then
712 echo "Found files with restrictive permissions:"