Shipping GTK+ apps for macOS with Xcode
Asked Answered
F

3

9

My setup:

  • macOS Mojave
  • Xcode 10.3

I'm looking into the possibility of shipping apps on macOS using GTK+.

Unfortunately, the whole process seems daunting to me according to GTK+'s macOS build/bundle/integration guide:

This guide showed various pitfalls of the gigantic script and its environment tweaks, which could mess up the entire OS. The suggested approach of opening a new user account seems not very practical, either.

This all-in-one script approach, excluding any talks about Xcode, is quite scary as it makes me wonder how fast it could keep up with Apple's movement, as Xcode with its toolchain upgrades every few months. Xcode is the only environment that I feel comfortable when it comes to project organization and app bundling.

For one, would it be possible to use Apple's more mature dynamic linking system, i.e., Frameworks, instead of the .dylib approach? I couldn't find any resources or discussions about linking GTK+ through Frameworks, or even using Xcode build system for the job.

Any tips would be appreciated.

UPDATE

My first attempt of bootstrapping the macOS build system for GTK+ just failed at a cmake dependency step, and the script does not even offer an "abort" option. I had to Ctrl-C it: Another negative experience.

UPDATE 2

Progress: I managed to create a Hello-World command-line program in Xcode 10.3 and make it run. Basically I steered clear of the jhbuild route that GNOME recommends.

Things I did:

  1. Install gtk+3 and its Python binding from homebrew: brew install pygobject3 gtk+3
  2. In Xcode, create a C++ command-line project.
  3. Find out header search paths: pkg-config --cflags gtk+-3.0, and add the result to Other C Flags of Xcode target's Build Settings.
  4. Find out about libs: pkg-config --libs gtk+-3.0, and add the result to Other Linker Flags.
  5. Build and Run.

This is so much easier than the GNOME recommendation. Mind you that its latest edits were done in 2019, so they likely still recommend it.

I'm going to try bundling today.

Furnishing answered 17/8, 2019 at 11:37 Comment(1)
I never got the jhbuild thing eitherWellfounded
F
6

So after messing around with GTK+ and Xcode 10 toolchain for a whole day, I finally got more confidence in the idea of shipping macOS app built with GTK+. The hardest part for me was to figure out how to make a self-contained app bundle, especially how to deal with the .dylib hell on macOS (Mojave) with Xcode toolchain (10.3). The online resources are incredibly inconsistent on this topic. I was tipped by many tutorials to fix things here and there. However, each of them has a slightly skewed portion that's just enough to throw me off track one minute later. But I'm glad that things are a lot easier than the GNOME WIKI described, with Xcode handling a lot of heavy-lifting.

Here are the things I did to make a hello-world GNOME app based on their getting-started example and make it run on another Mac without any pre-installed dependency, with brief remarks:

Install GTK+

  • Install gtk+3 and its Python binding from homebrew: brew install pygobject3 gtk+3;

Setup Xcode project and finishing coding

  • In Xcode, create a Cocoa macOS project using Objective-C.
  • Write your GTK+ app code in main.m (I actually made it main.mm for possible C++ mix-in). Instead of returning the result of NSApplicationMain(). You return that of g_application_run().

  • Keep all the other supporting source files the way they are.

  • Set a proper macOS Deployment Target: I make it macOS 10.10 for reasonable compatibility. The other test machine I have runs High Sierra.

Here is my main.mm.

//
//  main.mm
//  hello_gtk
//
//  Created by Me on 2019-08-11.
//  Copyright © 2019 Me. All rights reserved.
//

#include <iostream>
#import <Cocoa/Cocoa.h>
#include <gtk/gtk.h>


// callback function which is called when button is clicked
static void on_button_clicked(GtkButton *btn, gpointer data) {
    // change button label when it's clicked
    gtk_button_set_label(btn, "Hello World");
}


// callback function which is called when application is first started
static void on_app_activate(GApplication *app, gpointer data) {
    // create a new application window for the application
    // GtkApplication is sub-class of GApplication
    // downcast GApplication* to GtkApplication* with GTK_APPLICATION() macro
    GtkWidget *window = gtk_application_window_new(GTK_APPLICATION(app));
    // a simple push button
    GtkWidget *btn = gtk_button_new_with_label("Click Me!");
    // connect the event-handler for "clicked" signal of button
    g_signal_connect(btn, "clicked", G_CALLBACK(on_button_clicked), NULL);
    // add the button to the window
    gtk_container_add(GTK_CONTAINER(window), btn);
    // display the window
    gtk_widget_show_all(GTK_WIDGET(window));
}


int main(int argc, char *argv[]) {
    // create new GtkApplication with an unique application ID
    GtkApplication *app = gtk_application_new(
                                              "org.gtkmm.example.HelloApp",
                                              G_APPLICATION_FLAGS_NONE
                                              );
    // connect the event-handler for "activate" signal of GApplication
    // G_CALLBACK() macro is used to cast the callback function pointer
    // to generic void pointer
    g_signal_connect(app, "activate", G_CALLBACK(on_app_activate), NULL);
    // start the application, terminate by closing the window
    // GtkApplication* is upcast to GApplication* with G_APPLICATION() macro
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    // deallocate the application object
    g_object_unref(app);
//    return status;
    return status;
}


Configure Xcode project to compile and link

Here you must link against the Homebrew GTK+. Don't do your .dylib tweaks just yet.

  • Set up compiler flags. Find out about header search paths: pkg-config --cflags gtk+-3.0, and add the result to Other C Flags of Xcode target's Build Settings.
  • Set up linker flags. Find out about the flags needed: pkg-config --libs gtk+-3.0, and add the result to Other Linker Flags.
  • Add required system Frameworks for the example to the Link Binary with Libraries Build Phase. In my case: Carbon(!), Foundation, CoreGraphics, Cocoa.

Fix embedded paths in .dylibs

This is a topic much discussed about. People even made tools (including the Jhbuild, and this one) to do the job. Unfortunately, none of these tools or tips worked for me out of the box. My guess is that it's just too hard for hobbyist bloggers to keep up with Apple who has a gift to deprecate things invented a year ago, and the gurus are busy with more interesting and important missions. All the tips got the theories right though, so I finally got it working by following the idea of this one. Here comes my take:

  • Add a Run Script Build Phase right after Link Binary with Libraries. I use a separate script file, and only do sh +x ${PROJECT_DIR}/${PRODUCT_NAME}/deploy_libs.sh for maintainability.

Here is the actual script I wrote:

#! /bin/sh

LibTargetDir="${BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/lib"
Exec=${BUILD_DIR}/${EXECUTABLE_PATH}
RelLibDir="@executable_path/../Frameworks/lib"
pwd
mkdir -p "${LibTargetDir}" 2>&1 > /dev/null

#
# Copy Homebrew libs to hello_world.app/Contents/Frameworks/lib/
#

# Direct dependencies of the executable found out about by otool.

cp /usr/local/opt/gtk+3/lib/libgtk-3.0.dylib "${LibTargetDir}"
cp /usr/local/opt/gtk+3/lib/libgdk-3.0.dylib "${LibTargetDir}"
cp /usr/local/opt/pango/lib/libpangocairo-1.0.0.dylib "${LibTargetDir}"
cp /usr/local/opt/pango/lib/libpango-1.0.0.dylib "${LibTargetDir}"
cp /usr/local/opt/harfbuzz/lib/libharfbuzz.0.dylib "${LibTargetDir}"
cp /usr/local/opt/atk/lib/libatk-1.0.0.dylib "${LibTargetDir}"
cp /usr/local/opt/cairo/lib/libcairo-gobject.2.dylib "${LibTargetDir}"
cp /usr/local/opt/cairo/lib/libcairo.2.dylib "${LibTargetDir}"
cp /usr/local/opt/gdk-pixbuf/lib/libgdk_pixbuf-2.0.0.dylib "${LibTargetDir}"
cp /usr/local/opt/glib/lib/libgio-2.0.0.dylib "${LibTargetDir}"
cp /usr/local/opt/glib/lib/libgobject-2.0.0.dylib "${LibTargetDir}"
cp /usr/local/opt/glib/lib/libglib-2.0.0.dylib "${LibTargetDir}"
cp /usr/local/opt/gettext/lib/libintl.8.dylib "${LibTargetDir}"

# 2nd-order dependencies by the above libs.

cp /usr/local/opt/libepoxy/lib/libepoxy.0.dylib "${LibTargetDir}"
cp /usr/local/opt/fontconfig/lib/libfontconfig.1.dylib "${LibTargetDir}"
cp /usr/local/opt/freetype/lib/libfreetype.6.dylib "${LibTargetDir}"
cp /usr/local//Cellar/libffi/3.2.1/lib/libffi.6.dylib "${LibTargetDir}"
cp /usr/local/opt/fribidi/lib/libfribidi.0.dylib "${LibTargetDir}"
cp /usr/local/opt/glib/lib/libgmodule-2.0.0.dylib "${LibTargetDir}"
cp /usr/local/opt/graphite2/lib/libgraphite2.3.dylib "${LibTargetDir}"
cp /usr/local/Cellar/pango/1.44.5/lib/libpangoft2-1.0.0.dylib "${LibTargetDir}"
cp /usr/local/opt/pixman/lib/libpixman-1.0.dylib "${LibTargetDir}"
cp /usr/local/opt/libpng/lib/libpng16.16.dylib "${LibTargetDir}"


#
# Make them writable for the fixes. The libs are read-only fresh out of Homebrew .
#

chmod -R +w "${LibTargetDir}"/*


#
# Fix .dylib execution paths for the well-known reason: Paths are hardcoded.
#

# Fix executable dependencies.

install_name_tool -change "/usr/local/opt/gtk+3/lib/libgtk-3.0.dylib" "${RelLibDir}/libgtk-3.0.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/gtk+3/lib/libgdk-3.0.dylib" "${RelLibDir}/libgdk-3.0.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/pango/lib/libpangocairo-1.0.0.dylib" "${RelLibDir}/libpangocairo-1.0.0.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/pango/lib/libpango-1.0.0.dylib" "${RelLibDir}/libpango-1.0.0.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/harfbuzz/lib/libharfbuzz.0.dylib" "${RelLibDir}/libharfbuzz.0.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/atk/lib/libatk-1.0.0.dylib" "${RelLibDir}/libatk-1.0.0.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/cairo/lib/libcairo-gobject.2.dylib" "${RelLibDir}/libcairo-gobject.2.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/cairo/lib/libcairo.2.dylib" "${RelLibDir}/libcairo.2.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/gdk-pixbuf/lib/libgdk_pixbuf-2.0.0.dylib" "${RelLibDir}/libgdk_pixbuf-2.0.0.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/glib/lib/libgio-2.0.0.dylib" "${RelLibDir}/libgio-2.0.0.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/glib/lib/libgobject-2.0.0.dylib" "${RelLibDir}/libgobject-2.0.0.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${Exec}"
install_name_tool -change "/usr/local/opt/gettext/lib/libintl.8.dylib" "${RelLibDir}/libintl.8.dylib" "${Exec}"

# Fix dependencies of dependencies

install_name_tool -change "/usr/local/Cellar/gtk+3/3.24.10/lib/libgdk-3.0.dylib" "${RelLibDir}/libgdk-3.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgmodule-2.0.0.dylib" "${RelLibDir}/libgmodule-2.0.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/gettext/lib/libintl.8.dylib" "${RelLibDir}/libintl.8.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgobject-2.0.0.dylib" "${RelLibDir}/libgobject-2.0.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgio-2.0.0.dylib" "${RelLibDir}/libgio-2.0.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/pango/lib/libpangocairo-1.0.0.dylib" "${RelLibDir}/libpangocairo-1.0.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/pango/lib/libpango-1.0.0.dylib" "${RelLibDir}/libpango-1.0.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/harfbuzz/lib/libharfbuzz.0.dylib" "${RelLibDir}/libharfbuzz.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/cairo/lib/libcairo.2.dylib" "${RelLibDir}/libcairo.2.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/pango/lib/libpangoft2-1.0.0.dylib" "${RelLibDir}/libpangoft2-1.0.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/freetype/lib/libfreetype.6.dylib" "${RelLibDir}/libfreetype.6.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/fribidi/lib/libfribidi.0.dylib" "${RelLibDir}/libfribidi.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/cairo/lib/libcairo-gobject.2.dylib" "${RelLibDir}/libcairo-gobject.2.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/gdk-pixbuf/lib/libgdk_pixbuf-2.0.0.dylib" "${RelLibDir}/libgdk_pixbuf-2.0.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/atk/lib/libatk-1.0.0.dylib" "${RelLibDir}/libatk-1.0.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib
install_name_tool -change "/usr/local/opt/libepoxy/lib/libepoxy.0.dylib" "${RelLibDir}/libepoxy.0.dylib" "${LibTargetDir}"/libgtk-3.0.dylib


install_name_tool -change "/usr/local/opt/gdk-pixbuf/lib/libgdk_pixbuf-2.0.0.dylib" "${RelLibDir}/libgdk_pixbuf-2.0.0.dylib" "${LibTargetDir}"/libgdk-3.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgobject-2.0.0.dylib" "${RelLibDir}/libgobject-2.0.0.dylib" "${LibTargetDir}"/libgdk-3.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libgdk-3.0.dylib
install_name_tool -change "/usr/local/opt/gettext/lib/libintl.8.dylib" "${RelLibDir}/libintl.8.dylib" "${LibTargetDir}"/libgdk-3.0.dylib
install_name_tool -change "/usr/local/opt/cairo/lib/libcairo-gobject.2.dylib" "${RelLibDir}/libcairo-gobject.2.dylib" "${LibTargetDir}"/libgdk-3.0.dylib
install_name_tool -change "/usr/local/opt/pango/lib/libpango-1.0.0.dylib" "${RelLibDir}/libpango-1.0.0.dylib" "${LibTargetDir}"/libgdk-3.0.dylib
install_name_tool -change "/usr/local/opt/fribidi/lib/libfribidi.0.dylib" "${RelLibDir}/libfribidi.0.dylib" "${LibTargetDir}"/libgdk-3.0.dylib
install_name_tool -change "/usr/local/opt/cairo/lib/libcairo-gobject.2.dylib" "${RelLibDir}/libcairo-gobject.2.dylib" "${LibTargetDir}"/libgdk-3.0.dylib
install_name_tool -change "/usr/local/opt/libepoxy/lib/libepoxy.0.dylib" "${RelLibDir}/libepoxy.0.dylib" "${LibTargetDir}"/libgdk-3.0.dylib
install_name_tool -change "/usr/local/opt/pango/lib/libpangocairo-1.0.0.dylib" "${RelLibDir}/libpangocairo-1.0.0.dylib" "${LibTargetDir}"/libgdk-3.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgio-2.0.0.dylib" "${RelLibDir}/libgio-2.0.0.dylib" "${LibTargetDir}"/libgdk-3.0.dylib


install_name_tool -change "/usr/local/Cellar/pango/1.44.5/lib/libpango-1.0.0.dylib" "${RelLibDir}/libpango-1.0.0.dylib" "${LibTargetDir}"/libpangocairo-1.0.0.dylib
install_name_tool -change "/usr/local/Cellar/pango/1.44.5/lib/libpangoft2-1.0.0.dylib" "${RelLibDir}/libpangoft2-1.0.0.dylib" "${LibTargetDir}"/libpangocairo-1.0.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libpangocairo-1.0.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgobject-2.0.0.dylib" "${RelLibDir}/libgobject-2.0.0.dylib" "${LibTargetDir}"/libpangocairo-1.0.0.dylib
install_name_tool -change "/usr/local/opt/harfbuzz/lib/libharfbuzz.0.dylib" "${RelLibDir}/libharfbuzz.0.dylib" "${LibTargetDir}"/libpangocairo-1.0.0.dylib
install_name_tool -change "/usr/local/opt/fontconfig/lib/libfontconfig.1.dylib" "${RelLibDir}/libfontconfig.1.dylib" "${LibTargetDir}"/libpangocairo-1.0.0.dylib
install_name_tool -change "/usr/local/opt/cairo/lib/libcairo.2.dylib" "${RelLibDir}/libcairo.2.dylib" "${LibTargetDir}"/libpangocairo-1.0.0.dylib


install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libpango-1.0.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgobject-2.0.0.dylib" "${RelLibDir}/libgobject-2.0.0.dylib" "${LibTargetDir}"/libpango-1.0.0.dylib
install_name_tool -change "/usr/local/opt/fribidi/lib/libfribidi.0.dylib" "${RelLibDir}/libfribidi.0.dylib" "${LibTargetDir}"/libpango-1.0.0.dylib
install_name_tool -change "/usr/local/opt/harfbuzz/lib/libharfbuzz.0.dylib" "${RelLibDir}/libharfbuzz.0.dylib" "${LibTargetDir}"/libpango-1.0.0.dylib


install_name_tool -change "/usr/local/opt/harfbuzz/lib/libharfbuzz.0.dylib" "${RelLibDir}/libharfbuzz.0.dylib" "${LibTargetDir}"/libharfbuzz.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libharfbuzz.0.dylib
install_name_tool -change "/usr/local/opt/gettext/lib/libintl.8.dylib" "${RelLibDir}/libintl.8.dylib" "${LibTargetDir}"/libharfbuzz.0.dylib
install_name_tool -change "/usr/local/opt/freetype/lib/libfreetype.6.dylib" "${RelLibDir}/libfreetype.6.dylib" "${LibTargetDir}"/libharfbuzz.0.dylib
install_name_tool -change "/usr/local/opt/graphite2/lib/libgraphite2.3.dylib" "${RelLibDir}/libgraphite2.3.dylib" "${LibTargetDir}"/libharfbuzz.0.dylib


install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libatk-1.0.0.dylib
install_name_tool -change "/usr/local/opt/gettext/lib/libintl.8.dylib" "${RelLibDir}/libintl.8.dylib"${LibTargetDir}/libatk-1.0.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgobject-2.0.0.dylib" "${RelLibDir}/libgobject-2.0.0.dylib" "${LibTargetDir}"/libatk-1.0.0.dylib


install_name_tool -change "/usr/local/opt/cairo/lib/libcairo.2.dylib" "${RelLibDir}/libcairo.2.dylib" "${LibTargetDir}"/libcairo-gobject.2.dylib
install_name_tool -change "/usr/local/opt/pixman/lib/libpixman-1.0.dylib" "${RelLibDir}/libpixman-1.0.dylib" "${LibTargetDir}"/libcairo-gobject.2.dylib
install_name_tool -change "/usr/local/opt/fontconfig/lib/libfontconfig.1.dylib" "${RelLibDir}/libfontconfig.1.dylib" "${LibTargetDir}"/libcairo-gobject.2.dylib
install_name_tool -change "/usr/local/opt/freetype/lib/libfreetype.6.dylib" "${RelLibDir}/libfreetype.6.dylib" "${LibTargetDir}"/libcairo-gobject.2.dylib
install_name_tool -change "/usr/local/opt/libpng/lib/libpng16.16.dylib" "${RelLibDir}/libpng16.16.dylib" "${LibTargetDir}"/libcairo-gobject.2.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgobject-2.0.0.dylib" "${RelLibDir}/libgobject-2.0.0.dylib" "${LibTargetDir}"/libcairo-gobject.2.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libcairo-gobject.2.dylib
install_name_tool -change "/usr/local/opt/gettext/lib/libintl.8.dylib" "${RelLibDir}/libintl.8.dylib" "${LibTargetDir}"/libcairo-gobject.2.dylib


install_name_tool -change "/usr/local/opt/pixman/lib/libpixman-1.0.dylib" "${RelLibDir}/libpixman-1.0.dylib" "${LibTargetDir}"/libcairo.2.dylib
install_name_tool -change "/usr/local/opt/fontconfig/lib/libfontconfig.1.dylib" "${RelLibDir}/libfontconfig.1.dylib" "${LibTargetDir}"/libcairo.2.dylib
install_name_tool -change "/usr/local/opt/freetype/lib/libfreetype.6.dylib" "${RelLibDir}/libfreetype.6.dylib" "${LibTargetDir}"/libcairo.2.dylib
install_name_tool -change "/usr/local/opt/libpng/lib/libpng16.16.dylib" "${RelLibDir}/libpng16.16.dylib" "${LibTargetDir}"/libcairo.2.dylib


install_name_tool -change "/usr/local/opt/glib/lib/libgobject-2.0.0.dylib" "${RelLibDir}/libgobject-2.0.0.dylib" "${LibTargetDir}"/libgdk_pixbuf-2.0.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libgdk_pixbuf-2.0.0.dylib
install_name_tool -change "/usr/local/opt/gettext/lib/libintl.8.dylib" "${RelLibDir}/libintl.8.dylib" "${LibTargetDir}"/libgdk_pixbuf-2.0.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgmodule-2.0.0.dylib" "${RelLibDir}/libgmodule-2.0.0.dylib" "${LibTargetDir}"/libgdk_pixbuf-2.0.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libgio-2.0.0.dylib" "${RelLibDir}/libgio-2.0.0.dylib" "${LibTargetDir}"/libgdk_pixbuf-2.0.0.dylib


install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libgobject-2.0.0.dylib
install_name_tool -change "/usr/local/opt/libffi/lib/libffi.6.dylib" "${RelLibDir}/libffi.6.dylib" "${LibTargetDir}"/libgobject-2.0.0.dylib


install_name_tool -change "/usr/local/opt/pcre/lib/libpcre.1.dylib" "${RelLibDir}/libpcre.1.dylib" "${LibTargetDir}"/libglib-2.0.0.dylib
install_name_tool -change "/usr/local/opt/gettext/lib/libintl.8.dylib" "${RelLibDir}/libintl.8.dylib" "${LibTargetDir}"/libglib-2.0.0.dylib


install_name_tool -change "/usr/local/Cellar/glib/2.60.6/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libgmodule-2.0.0.dylib


install_name_tool -change "/usr/local/Cellar/glib/2.60.6/lib/libgobject-2.0.0.dylib" "${RelLibDir}/libgobject-2.0.0.dylib" "${LibTargetDir}"/libgio-2.0.0.dylib
install_name_tool -change "/usr/local/Cellar/glib/2.60.6/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libgio-2.0.0.dylib
install_name_tool -change "/usr/local/opt/gettext/lib/libintl.8.dylib" "${RelLibDir}/libintl.8.dylib" "${LibTargetDir}"/libgio-2.0.0.dylib
install_name_tool -change "/usr/local/Cellar/glib/2.60.6/lib/libgmodule-2.0.0.dylib" "${RelLibDir}/libgmodule-2.0.0.dylib" "${LibTargetDir}"/libgio-2.0.0.dylib


install_name_tool -change "/usr/local/opt/harfbuzz/lib/libharfbuzz.0.dylib" "${RelLibDir}/libharfbuzz.0.dylib" "${LibTargetDir}"/libpangoft2-1.0.0.dylib
install_name_tool -change "/usr/local/Cellar/pango/1.44.5/lib/libpango-1.0.0.dylib" "${RelLibDir}/libpango-1.0.0.dylib" "${LibTargetDir}"/libpangoft2-1.0.0.dylib
install_name_tool -change "/usr/local/opt/glib/lib/libglib-2.0.0.dylib" "${RelLibDir}/libglib-2.0.0.dylib" "${LibTargetDir}"/libpangoft2-1.0.0.dylib
install_name_tool -change "/usr/local/opt/gettext/lib/libintl.8.dylib" "${RelLibDir}/libintl.8.dylib" "${LibTargetDir}"/libpangoft2-1.0.0.dylib
install_name_tool -change "/usr/local/opt/freetype/lib/libfreetype.6.dylib" "${RelLibDir}/libfreetype.6.dylib" "${LibTargetDir}"/libpangoft2-1.0.0.dylib
install_name_tool -change "/usr/local/opt/fontconfig/lib/libfontconfig.1.dylib" "${RelLibDir}/libfontconfig.1.dylib" "${LibTargetDir}"/libpangoft2-1.0.0.dylib


install_name_tool -change "/usr/local/opt/libpng/lib/libpng16.16.dylib" "${RelLibDir}/libpng16.16.dylib" "${LibTargetDir}"/libfreetype.6.dylib


install_name_tool -change "/usr/local/opt/freetype/lib/libfreetype.6.dylib" "${RelLibDir}/libfreetype.6.dylib" "${LibTargetDir}"/libfontconfig.1.dylib


echo "DONE"

Sorry for not writing for-loops with arrays, partly because you literally need to pinpoint every single dependency along the dependency tree until you reach a leaf lib, such as libfreetype, which only depends on system libs, i.e., things under /usr/lib/ and /System/Library/. Your only truthful friend is otool -L. You'll stumble upon quirks such as:

  • symlink and versioned libs: Some specific dependencies are recorded as symlinks under /usr/local/opt, others their source paths under /usr/local/Cellar. You'll likely hit one or two DYLB-not-found error after shipping the bundle to another machine if you took symlinks by mistake. You want the physical libs for your install_name_tool commands.

Obviously, although the lib itself is always the first line in the otool dependency report, you don't need to install_name_tool -change it.

For bundling the libs, although Xcode Build Phases comes with a Copy step, that is not what you need to copy your libs to the Framework folder because they require explicitly importing .dylib files into your project, which would make it hard to distinguish between Debug/Release configurations later.

I beg to have a tool to automate this mess without luck. Guess I'll just have to write one, otherwise this brute-force approach will not scale.

Code-signing the binaries

All dylibs must be code-signed as submodules along with the app.

  • Add --deep to Other Code Signing Flags, in the Xcode project Build Settings;
  • Set Code Signing Identity to your team developer account.
  • For this example, I don't need to tweak Info.plist at all.

Build, Run, and Deploy to other machines

  • Just build and run. The example should already be working on the dev machine.

  • Copy the entire app bundle hello_world.app to another Mac machine, which has no Homebrew or other bags. The app should run successfully as well.

  • Build .dmg for deployment. It's the standard procedure so I'll skip it for now.

Hope this could help people who might be as clueless as I was at first. My next challenge would be to discover the gist of shipping PyGObject apps on top of GTK+. The worry-free procedure seems to be also buried in a huge desert.

Furnishing answered 19/8, 2019 at 15:10 Comment(2)
Very detail, that's save my a lot of research time :D.Naca
There's a problem with using homebrew built libraries beyond what you've mentioned here, which is that they are built only for the OS version that you are on. Even though you've set the macOS Deployment Target this won't be built into your homebrew libraries and your app will likely fail on an OS older than the one you are on.Berstine
S
1

My project uses Homebrew's GTK via GObject Introspection and the above approach with dylibs patching didn't work. It kept loading some dylibs from /usr/local/Cellar whatever I tried.

I've made the bundle another way. The idea is to run a program and collect its dylibs and typelibs loaded from /usr/local/Cellar, then copy the dylibs (along with the links pointing to those dylibs, very important!) to PROG_DIR/lib and the typelibs to PROG_DIR/lib/girepository-1.0. The last step is to create a bash/zsh wrapper script that sets DYLD_LIBRARY_PATH and GI_TYPELIB_PATH and then runs the program. Some more details below.

Run your program and save its PID

% ./your_prog &
% PROG_PID=$!

Collect dylibs and typelibs loaded from /usr/local/Cellar

% TMP=$(mktemp)
% lsof -p $PROG_PID > $TMP
% cat $TMP | egrep '/usr/local/Cellar/.*\.dylib'   | awk '{ print $9 }' | sort -u > dylibs.txt
% cat $TMP | egrep '/usr/local/Cellar/.*\.typelib' | awk '{ print $9 }' | sort -u > typelibs.txt
% rm $MP

Copy the dylibs and the links pointing to them to PROG_DIR/lib and the typelibs to PROG_DIR/lib/girepository-1.0. See example https://github.com/ten0s/velisp/blob/master/macos/find-homebrew-deps.sh#L36-L59

Create the wrapper script:

your_prog.sh:

#!/usr/bin/env bash

BASE_DIR=$(dirname $0)

DYLD_LIBRARY_PATH=$BASE_DIR/lib
GI_TYPELIB_PATH=$BASE_DIR/lib/girepository-1.0

export DYLD_LIBRARY_PATH
export GI_TYPELIB_PATH

exec $BASE_DIR/your_prog "$@"

Run the wrapper script:

% ./your_prog.sh

As was correctly pointed above, the bundle should be built on an older MacOS to make it possible to run on newer ones, i.e. make the bundle on Catalina and it should run on Catalina, Big Sur, Monterey, etc. The bundle should also be signed.

See complete examples:

Spotty answered 7/12, 2022 at 9:35 Comment(0)
B
0

I know this is dated but... Following on your lead I wrote a script to accomplish the job of moving the libraries needed and rewriting paths. The script works well, but I found that there were still errors in running the executable despite all paths (otool -L) being redirected to the new lib directory. 'strings xxx.dylib | grep "/usr/local" shows that there are still hard coded paths in some of the libraries. This possibly leads to the problem. The executable does not crash just throws errors about ambiguous library placements. This is on Mac Monterey OS.

Beebread answered 6/12, 2021 at 16:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.