Skip to content

Commit

Permalink
Update the bundle_gtk script to work with the arm64 homebrew install …
Browse files Browse the repository at this point in the history
…prefix
  • Loading branch information
cameronwhite committed Dec 31, 2024
1 parent 6b466be commit 4ee9e14
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 32 deletions.
2 changes: 1 addition & 1 deletion installer/macos/build_installer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ cp pinta.icns ${MAC_APP_DIR}/Contents/Resources

# Install the GTK dependencies.
echo "Bundling GTK..."
./bundle_gtk.py --resource_dir ${MAC_APP_RESOURCE_DIR}
./bundle_gtk.py --runtime $runtimeid --resource_dir ${MAC_APP_RESOURCE_DIR}
# Add the GTK lib dir to the library search path (for dlopen()), as an alternative to $DYLD_LIBRARY_PATH.
install_name_tool -add_rpath "@executable_path/../Resources/lib" ${MAC_APP_BIN_DIR}/Pinta

Expand Down
75 changes: 44 additions & 31 deletions installer/macos/bundle_gtk.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,18 @@
import subprocess
from stat import S_IREAD, S_IRGRP, S_IROTH, S_IWUSR

PREFIX = "/usr/local"

# Grab all dependencies of libadwaita / libgtk, plus pixbuf loader plugins.
GTK_LIB = "/usr/local/lib/libadwaita-1.0.dylib"
RSVG_LIB = "/usr/local/lib/librsvg-2.2.dylib"
TIFF_LIB = "/usr/local/lib/libtiff.6.dylib"
WEBP_DEMUX_LIB = "/usr/local/lib/libwebpdemux.2.dylib"
WEBP_MUX_LIB = "/usr/local/lib/libwebpmux.3.dylib"
GTK_LIB = "lib/libadwaita-1.0.dylib"
RSVG_LIB = "lib/librsvg-2.2.dylib"
TIFF_LIB = "lib/libtiff.6.dylib"
WEBP_DEMUX_LIB = "lib/libwebpdemux.2.dylib"
WEBP_MUX_LIB = "lib/libwebpmux.3.dylib"
ROOT_LIBS = [GTK_LIB, RSVG_LIB, TIFF_LIB, WEBP_DEMUX_LIB, WEBP_MUX_LIB]

ADWAITA_THEME = "/usr/local/share/icons/Adwaita/index.theme"
ADWAITA_THEME = "share/icons/Adwaita/index.theme"
PIXBUF_LOADERS = "lib/gdk-pixbuf-2.0/2.10.0"
GLIB_SCHEMAS = "share/glib-2.0/schemas"

# Match against non-system libraries
OTOOL_LIB_REGEX = re.compile(r"(/usr/local/.*\.dylib)")
# Match against relative paths (webp and related libraries)
OTOOL_REL_LIB_REGEX = re.compile(r"@rpath/(lib.*\.dylib)")


def run_install_name_tool(lib, deps, lib_install_dir):
# Make writable by user.
Expand All @@ -40,49 +33,49 @@ def run_install_name_tool(lib, deps, lib_install_dir):
dep_lib = "@executable_path/../Resources/lib/" + dep_lib_name
cmd = ['install_name_tool',
'-change', dep_path, dep_lib,
'-change', f"@rpath/{dep_path_basename}", dep_lib, # For libraries like webp
'-change', f"@rpath/{dep_path_basename}", dep_lib, # For libraries like webp
lib]
subprocess.check_output(cmd)


def collect_libs(src_lib, lib_deps):
def collect_libs(src_lib, lib_deps, otool_lib_regex, otool_rel_lib_regex):
"""
Use otool -L to collect the library dependencies.
"""
cmd = ['otool', '-L', src_lib]
output = subprocess.check_output(cmd).decode('utf-8')
referenced_paths = re.findall(OTOOL_LIB_REGEX, output)
referenced_paths = re.findall(otool_lib_regex, output)

folder = os.path.dirname(src_lib)
referenced_paths.extend([os.path.join(folder, lib)
for lib in re.findall(OTOOL_REL_LIB_REGEX, output)])
for lib in re.findall(otool_rel_lib_regex, output)])

real_lib_paths = set([os.path.realpath(lib) for lib in referenced_paths])

lib_deps[src_lib] = referenced_paths

for lib in real_lib_paths:
if lib not in lib_deps:
collect_libs(lib, lib_deps)
collect_libs(lib, lib_deps, otool_lib_regex, otool_rel_lib_regex)


def copy_resources(res_path):
def copy_resources(src_prefix, res_path):
"""
Copy a folder from ${PREFIX}/${res_path} to Contents/Resources/${res_path}.
"""
dest_folder = os.path.join(args.resource_dir, res_path)
shutil.copytree(os.path.join(PREFIX, res_path),
shutil.copytree(os.path.join(src_prefix, res_path),
dest_folder,
dirs_exist_ok=True)


def copy_plugins(res_path, lib_install_dir):
def copy_plugins(src_prefix, res_path, lib_install_dir, otool_lib_regex, otool_rel_lib_regex):
"""
Copy a folder of plugins from ${PREFIX}/${res_path} to
Contents/Resources/${res_path} and update the library references.
"""

copy_resources(res_path)
copy_resources(src_prefix, res_path)

# Update paths to the main GTK libs.
lib_install_dir = os.path.join(args.resource_dir, 'lib')
Expand All @@ -94,17 +87,18 @@ def copy_plugins(res_path, lib_install_dir):

lib_path = os.path.join(root, lib)
lib_deps = {}
collect_libs(lib_path, lib_deps)
collect_libs(lib_path, lib_deps, otool_lib_regex,
otool_rel_lib_regex)
run_install_name_tool(lib_path, lib_deps[lib_path],
lib_install_dir)


def install_plugin_cache(cache_path, resource_dir):
def install_plugin_cache(src_prefix, cache_path, resource_dir):
"""
Copy a file such as immodules.cache, and update the library paths to be
paths inside the .app bundle.
"""
src_cache = os.path.join(PREFIX, cache_path)
src_cache = os.path.join(src_prefix, cache_path)
dest_cache = os.path.join(resource_dir, cache_path)

with open(src_cache, 'r') as src_f:
Expand All @@ -117,15 +111,31 @@ def install_plugin_cache(cache_path, resource_dir):


parser = argparse.ArgumentParser(description='Bundle the GTK libraries.')
parser.add_argument('--runtime', type=str, required=True,
help='The dotnet runtime id, e.g. osx-x64 or osx-arm64')
parser.add_argument('--resource_dir',
type=pathlib.Path,
required=True,
help='Directory to copy extra resources to.')
args = parser.parse_args()

src_prefix = ""
if args.runtime == "osx-x64":
src_prefix = "/usr/local"
elif args.runtime == "osx-arm64":
src_prefix = "/opt/homebrew"
else:
raise RuntimeError("Invalid runtime id")

# Match against non-system libraries
otool_lib_regex = re.compile(fr"({src_prefix}/.*\.dylib)")
# Match against relative paths (webp and related libraries)
otool_rel_lib_regex = re.compile(r"@rpath/(lib.*\.dylib)")

lib_deps = {}
for root_lib in ROOT_LIBS:
collect_libs(os.path.realpath(root_lib), lib_deps)
lib_path = os.path.realpath(os.path.join(src_prefix, root_lib))
collect_libs(lib_path, lib_deps, otool_lib_regex, otool_rel_lib_regex)

lib_install_dir = os.path.join(args.resource_dir, 'lib')
os.makedirs(lib_install_dir)
Expand All @@ -139,18 +149,21 @@ def install_plugin_cache(cache_path, resource_dir):
os.path.join(lib_install_dir, "libgdk_pixbuf-2.0.dylib"))

# Copy translations and icons.
gtk_root = os.path.join(os.path.dirname(os.path.realpath(GTK_LIB)), "..")
gtk_root = os.path.join(os.path.dirname(
os.path.realpath(os.path.join(src_prefix, GTK_LIB))), "..")
shutil.copytree(os.path.join(gtk_root, 'share/locale'),
os.path.join(args.resource_dir, 'share/locale'),
dirs_exist_ok=True)
# TODO - could probably trim the number of installed icons.
adwaita_icons = os.path.join(os.path.dirname(os.path.realpath(ADWAITA_THEME)), "..")
adwaita_icons = os.path.join(os.path.dirname(
os.path.realpath(os.path.join(src_prefix, ADWAITA_THEME))), "..")
shutil.copytree(adwaita_icons,
os.path.join(args.resource_dir, 'share/icons'),
dirs_exist_ok=True)

copy_plugins(PIXBUF_LOADERS, lib_install_dir)
install_plugin_cache(os.path.join(PIXBUF_LOADERS, "loaders.cache"),
copy_plugins(src_prefix, PIXBUF_LOADERS, lib_install_dir,
otool_lib_regex, otool_rel_lib_regex)
install_plugin_cache(src_prefix, os.path.join(PIXBUF_LOADERS, "loaders.cache"),
args.resource_dir)

copy_resources(GLIB_SCHEMAS)
copy_resources(src_prefix, GLIB_SCHEMAS)

0 comments on commit 4ee9e14

Please sign in to comment.