From 690e60cd74c44ed1e2d21b27e3152856845ead28 Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch
Date: Sat, 8 Nov 2025 18:47:51 +0100 Subject: Build an application bundle on macOS This is far from done, but nonetheless constitutes a big improvement. macOS application bundles are more or less necessary for: - showing a nice icon; - having spawned off instances actually be brought to the foreground; - file associations (yet files currently do not open properly); - having a reasonable method of distribution. Also resolving a bunch of minor issues: - The context menu had duplicate items, and might needlessly end up with (null) labels. --- macos-install.sh | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100755 macos-install.sh (limited to 'macos-install.sh') diff --git a/macos-install.sh b/macos-install.sh new file mode 100755 index 0000000..884e1b1 --- /dev/null +++ b/macos-install.sh @@ -0,0 +1,115 @@ +#!/bin/sh -e +export LC_ALL=C +cd "$MESON_INSTALL_DESTDIR_PREFIX" + +# Input: Half-baked application bundle linked against Homebrew. +# Output: Portable application bundle. +source=/opt/homebrew +bindir=Contents/MacOS +libdir=Contents/Resources/lib +datadir=Contents/Resources/share + +mkdir -p "$datadir"/glib-2.0/schemas +cp -p "$source"/share/glib-2.0/schemas/org.gtk.Settings.* \ + "$datadir"/glib-2.0/schemas +mkdir -p "$datadir"/icons +cp -pRL "$source"/share/icons/Adwaita "$datadir/"icons +mkdir -p "$datadir"/icons/hicolor +cp -p "$source"/share/icons/hicolor/index.theme "$datadir"/icons/hicolor +mkdir -p "$datadir/mime" +# GIO doesn't use the database on macOS, this subset is for us. +find "$source"/share/mime/ -maxdepth 1 -type f -exec cp -p {} "$datadir"/mime \; + +# Copy binaries we directly or indirectly depend on. +# +# Homebrew is a bit chaotic in that some libraries are linked against locations +# in /opt/homebrew/Cellar, and some against /opt/homebrew/opt symlinks. +# We'll process things in such a way that it does not matter. +# +# As a side note, libraries in /usr/lib are now actually being served from +# a shared cache by the dynamic linker and aren't visible on the filesystem. +# There is an alternative to "otool -L" which can see them but it isn't +# particularly nicer to parse: "dyld_info -dependents/-linked_dylibs". +rm -rf "$libdir" +mkdir -p "$libdir" + +pixbufdir=$libdir/gdk-pixbuf-2.0 +loadersdir=$pixbufdir/loaders +cp -RL "$source"/lib/gdk-pixbuf-2.0/* "$pixbufdir" + +# Fix a piece of crap loader that needs to be special. +svg=$loadersdir/libpixbufloader_svg.so +rm -f "$loadersdir"/libpixbufloader_svg.dylib +otool -L "$svg" | grep -o '@rpath/[^ ]*' | while IFS= read -r bad +do install_name_tool -change "$bad" "$source/lib/$(basename "$bad")" "$svg" +done + +GDK_PIXBUF_MODULEDIR=$loadersdir gdk-pixbuf-query-loaders \ + | sed "s,$libdir,@rpath," > "$pixbufdir/loaders.cache" + +gtkdir=$libdir/gtk-3.0 +printbackendsdir=$gtkdir/printbackends +cp -RL "$source"/lib/gtk-3.0/* "$gtkdir" + +# TODO: Figure out how to make gtk-query-immodules-3.0 pick up exactly +# what it needs to. So far I'm not sure if this is at all even useful. +rm -rf "$gtkdir"/immodules* + +find "$bindir" "$loadersdir" "$printbackendsdir" -type f -maxdepth 1 | awk ' + function collect(binary, command, line) { + if (seen[binary]++) + return + + command = "otool -L \"" binary "\"" + while ((command | getline line) > 0) + if (match(line, /^\t\/opt\/.+ \(/)) + collect(substr(line, RSTART + 1, RLENGTH - 3)) + close(command) + } { + collect($0) + delete seen[$0] + } END { + for (library in seen) + print library + } +' | while IFS= read -r binary +do test -f "$libdir/$(basename "$binary")" || cp "$binary" "$libdir" +done + +# Now redirect all binaries to internal linking. +# A good overview of how this works is "man dyld" and: +# https://itwenty.me/posts/01-understanding-rpath/ +rewrite() { + otool -L "$1" | sed -n 's,^\t\(.*\) (.*,\1,p' | grep '^/opt/' \ + | while IFS= read -r lib + do install_name_tool -change "$lib" "@rpath/$(basename "$lib")" "$1" + done +} + +find "$bindir" -type f -maxdepth 1 | while IFS= read -r binary +do + install_name_tool -add_rpath @executable_path/../Resources/lib "$binary" + rewrite "$binary" +done + +find "$libdir" -type f \( -name '*.so' -o -name '*.dylib' \) \ + | while IFS= read -r binary +do + chmod 644 "$binary" + install_name_tool -id "@rpath/${binary#$libdir/}" "$binary" + rewrite "$binary" + + # Discard pointless @loader_path/../lib and absolute Homebrew paths. + otool -l "$binary" | awk ' + $1 == "cmd" { command = $2 } + command == "LC_RPATH" && $1 == "path" { print $2 } + ' | xargs -R 1 -I % install_name_tool -delete_rpath % "$binary" + + # Replace freshly invalidated code signatures with ad-hoc ones. + codesign --force --sign - "$binary" +done + +glib-compile-schemas "$datadir"/glib-2.0/schemas + +# This may speed up program start-up a little bit. +gtk-update-icon-cache "$datadir"/icons/Adwaita -- cgit v1.2.3-70-g09d2