[gtk-mac-bundler] Provide example for using a Mach-O executable launcher for Python.



commit 547a5a198a37a0b06965ddb9f49764b599dfa3a5
Author: John Ralls <jralls ceridwen us>
Date:   Tue Aug 16 12:03:49 2016 -0700

    Provide example for using a Mach-O executable launcher for Python.
    
    Since OS-X 10.11 Apple has quietly made codesign a lot pickier about
    having script files of any sort in Contents/MacOS. This causes signing
    problems particularly with Python, so here we provide an alternative
    launching mechanism that links libpython, initializes the interpreter,
    and runs a python script designated in Info.plist to set up the
    environment and start the application.
    
    Details are provided in README.

 README                       |   56 ++++++++++++++++
 examples/Info-gtk-demo.plist |    3 +
 examples/gtk-demo.bundle     |   25 ++++---
 examples/gtk_launcher.py     |   48 +++++++++++++
 examples/python-launcher.c   |  150 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 271 insertions(+), 11 deletions(-)
---
diff --git a/README b/README
index b111a25..8835fa8 100644
--- a/README
+++ b/README
@@ -241,6 +241,62 @@ just put it in a data element and gtk-mac-integration (version 0.5.2
 and later) will copy it in *after* doing the *.l?a cleanup: <data>
 ${prefix}/lib/libfoo*.la </data>
 
+Code Signing
+------------
+
+Until mid-2016 gtk-mac-bundler depended upon a launcher shell script
+to configure the environment. You can still do this, but it causes
+problems with code-signing. Apple recommends that one not install
+scripts into the Contents/MacOS folder of a bundle, nor should one
+attempt to sign scripts. Doing so will produce signatures that are
+incompatible across different versions of MacOS.
+
+That means that for compiled-executable programs you need to launch
+first and then configure the environment before starting gettext,
+Gtk+, and any other libraries that read the environment for their
+configuration. There are a variety of ways to do this, including
+adding a module which reads configuration data from MacOS's 'defaults'
+preferences system or reading a YAML (a.k.a 'ini') file and exporting
+the results to the environment with setenv().
+
+For script-based programs one must create a small executable program
+which prepares the interpreter, launches it, and points it at a
+startup script which configutres the environment. Such a program,
+written in C, is provided in examples/python-launcher.c; a companion
+startup script, gtk_launcher.py, does the environment
+configuration. python-launcher.c should work as-is for most python
+programs; gtk_launcher.py will require a bit of specializing to work,
+in particular the import statement and startup call at the end of
+the file.
+
+To build python-launcher.c, start a jhbuild shell for your target and run:
+  gcc -L$PREFIX `python-config --cflags --ldflags` -o $PREFIX/bin/your-launcher \
+     path/to/gtk-mac-bundler/examples/python-launcher.c
+
+Remove the <launcher-script> element from your bundle file and change
+the main-binary element to:
+   <main-binary>
+       ${prefix}/bin/your-launcher
+   </main-binary>
+In this case leaving the name as "your-launcher" will actually work:
+The bundler will rename the file to the value of the
+CFBundleExecutable key in Info.plist.
+
+Copy gtk_launcher.py to the folder where your bundle file is, rename
+it to your liking (your_launcher.py from no on), and edit it as
+necessary.
+
+Add a data element for your_launcher.py to your bundle file:
+  <data dest=${bundle}/Contents/Resources>
+    ${project}/your_launcher.py
+  </data>
+
+Add the following key to the top dict in Info.plist for your project:
+    <key>GtkOSXLaunchScriptFile</key>
+    <string>your_launcher.py</string>
+
+
+
 Icon themes
 -----------
 
diff --git a/examples/Info-gtk-demo.plist b/examples/Info-gtk-demo.plist
index 80b04f4..966042c 100644
--- a/examples/Info-gtk-demo.plist
+++ b/examples/Info-gtk-demo.plist
@@ -26,5 +26,8 @@
     <string>Copyright 1997 - 2013 The GTK+ Team, GNU General Public License.</string>
     <key>LSMinimumSystemVersion</key>
     <string>10.4</string>
+    <key>GtkOSXLaunchScriptFile</key>
+    <string>gtk_launcher.py</string>
+
 </dict>
 </plist>
diff --git a/examples/gtk-demo.bundle b/examples/gtk-demo.bundle
index 422126e..fbed947 100644
--- a/examples/gtk-demo.bundle
+++ b/examples/gtk-demo.bundle
@@ -20,10 +20,6 @@
     -->
     <destination overwrite="yes">${env:HOME}/Desktop</destination>
 
-    <image>
-      <!-- Not implemented yet (DMG image). -->
-    </image>
-
     <!-- Comment this out to keep the install names in binaries -->
     <run-install-name-tool/>
  
@@ -31,14 +27,13 @@
          application sets up everything needed itself, like
          environment variable, linker paths, etc, a launcher script is
          not needed. If the source path is left out, the default
-         script will be used.
-    -->
-    <launcher-script>${project}/launcher.sh</launcher-script >
+        script will be used.
 
-    <!-- Not implemented: Optional runtime, could be python or mono
-         for example.
+         This example uses python-launcher.c (see the comment at
+         main-binary below), so there's no launcher script.
     -->
-    <!-- runtime copy="yes">/usr/bin/python</runtime -->
+    <!--launcher-script>${project}/launcher.sh</launcher-script -->
+
     <!-- Indicate the active gtk version to use. This is needed only
          for gtk+-3.0 projects. -->
     <gtk>gtk+-2.0</gtk>
@@ -49,8 +44,16 @@
        identifier are taken from the plist file.
   -->
   <plist>${project}/Info-gtk-demo.plist</plist>
+  <!-- Build gtk-demo-launcher with:
+          gcc -L$PREFIX `python-config --cflags --ldflags` \
+              -o $PREFIX/bin/gramps-launcher \
+             path/to/gtk-mac-bundler/examples/python-launcher.c
+       with the obvious substitution.
+  -->
 
-  <main-binary>${prefix}/bin/gtk-demo</main-binary>
+  <main-binary>
+    ${prefix}/bin/gtk-demo-launcher
+  </main-binary>
 
   <!-- Copy in GTK+ modules.  Note the ${gtkdir} macro, which expands
        to the correct library subdirectory for the specified gtk
diff --git a/examples/gtk_launcher.py b/examples/gtk_launcher.py
new file mode 100644
index 0000000..2f00e9e
--- /dev/null
+++ b/examples/gtk_launcher.py
@@ -0,0 +1,48 @@
+from os.path import join, dirname, abspath, normpath
+import sys, os
+import platform
+
+
+bundlepath = sys.argv[0]
+
+bundle_contents = join(bundlepath, 'Contents')
+bundle_res = join(bundle_contents, 'Resources')
+
+bundle_lib = join(bundle_res, 'lib')
+bundle_bin = join(bundle_res, 'bin')
+bundle_data = join(bundle_res, 'share')
+bundle_etc = join(bundle_res, 'etc')
+
+os.environ['XDG_DATA_DIRS'] = bundle_data
+os.environ['DYLD_LIBRARY_PATH'] = bundle_lib
+os.environ['LD_LIBRARY_PATH'] = bundle_lib
+os.environ['GTK_DATA_PREFIX'] = bundle_res
+os.environ['GTK_EXE_PREFIX'] = bundle_res
+os.environ['GTK_PATH'] = bundle_res
+
+os.environ['PANGO_RC_FILE'] = join(bundle_etc, 'pango', 'pangorc')
+os.environ['PANGO_SYSCONFDIR'] = bundle_etc
+os.environ['PANGO_LIBDIR'] = bundle_lib
+os.environ['GDK_PIXBUF_MODULE_FILE'] = join(bundle_lib, 'gdk-pixbuf-2.0',
+                                                '2.10.0', 'loaders.cache')
+if int(platform.release().split('.')[0]) > 10:
+    os.environ['GTK_IM_MODULE_FILE'] = join(bundle_etc, 'gtk-3.0',
+                                            'gtk.immodules')
+
+os.environ['GI_TYPELIB_PATH'] = join(bundle_lib, 'girepository-1.0')
+os.environ['GVBINDIR'] = join(bundle_lib, 'graphviz')
+os.environ['ENCHANT_MODULE_PATH'] = join(bundle_lib, 'enchant')
+
+#Set $PYTHON to point inside the bundle
+PYVER = 'python3.4'
+sys.path.append(bundle_res)
+
+os.environ['GRAMPSDIR'] = join (bundle_lib, PYVER, 'site-packages', 'gramps')
+os.environ['GRAMPSI18N'] = join(bundle_data, 'locale')
+os.environ['GRAMPS_RESOURCES'] = bundle_data
+os.environ['USERPROFILE'] = os.environ['HOME']
+os.environ['APPDATA'] = join(os.environ['HOME'], 'Library', 'Application Support')
+print('System Path:\n','\n'.join(sys.path)) 
+import gramps.grampsapp as app
+app.main()
+
diff --git a/examples/python-launcher.c b/examples/python-launcher.c
new file mode 100644
index 0000000..c8bcca9
--- /dev/null
+++ b/examples/python-launcher.c
@@ -0,0 +1,150 @@
+#include <Python.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <sys/syslimits.h>
+
+static wchar_t*
+widen_cfstring(CFStringRef str)
+{
+    int i;
+    CFRange range = {0, 0};
+    long len = CFStringGetLength(str);
+    size_t size = sizeof(wchar_t) * (len + 1);
+    size_t unisize = sizeof(UniChar) * (len + 1);
+    wchar_t *buf = malloc(size);
+    UniChar *unibuf = malloc(unisize);
+    if (buf == NULL || unibuf == NULL)
+    {
+       if (unibuf != NULL)
+           free(unibuf);
+       if (buf != NULL)
+           free(buf);
+       return NULL;
+    }
+    memset(buf, 0, size);
+    memset(unibuf, 0, unisize);
+    range.length = len;
+    CFStringGetCharacters(str, range, unibuf);
+    for (i = 0; i < len; ++i)
+       buf[i] = unibuf[i];
+    free(unibuf);
+    return buf;
+}
+
+static CFStringRef
+make_filesystem_string(CFURLRef url)
+{
+    unsigned char cbuf[PATH_MAX + 1];
+    CFStringRef str;
+    memset(cbuf, 0, PATH_MAX + 1);
+    if (!CFURLGetFileSystemRepresentation(url, 1, cbuf, PATH_MAX))
+    {
+       return NULL;
+    }
+    str = CFStringCreateWithBytes(NULL, cbuf, strlen((char*)cbuf),
+                                 kCFStringEncodingUTF8, 0);
+    return str;
+}
+
+static wchar_t*
+get_bundle_dir(void)
+{
+    CFBundleRef bundle = CFBundleGetMainBundle();
+    CFURLRef bundle_url = CFBundleCopyBundleURL(bundle);
+    CFStringRef str = make_filesystem_string(bundle_url);
+    wchar_t *retval = widen_cfstring(str);
+    CFRelease(bundle_url);
+    CFRelease(str);
+    return retval;
+}
+
+static void
+set_python_path(void)
+{
+    CFBundleRef bundle = CFBundleGetMainBundle();
+    CFURLRef bundle_url = CFBundleCopyResourcesDirectoryURL(bundle);
+    CFMutableStringRef mstr;
+    wchar_t *path;
+    CFStringRef str = make_filesystem_string(bundle_url);
+    CFRelease(bundle_url);
+    mstr = CFStringCreateMutableCopy(NULL, 5 * PATH_MAX, str);
+    CFStringAppendCString(mstr, "/lib/python34.zip:", kCFStringEncodingUTF8);
+    CFStringAppend(mstr, str);
+    CFStringAppendCString(mstr, "/lib/python3.4:",
+                         kCFStringEncodingUTF8);
+    CFStringAppend(mstr, str);
+    CFStringAppendCString(mstr, "/lib/python3.4/plat-darwin:",
+                         kCFStringEncodingUTF8);
+    CFStringAppend(mstr, str);
+    CFStringAppendCString(mstr, "/lib/python3.4/lib-dynload:",
+                         kCFStringEncodingUTF8);
+    CFStringAppend(mstr, str);
+    CFStringAppendCString(mstr, "/lib/python3.4/site-packages",
+                         kCFStringEncodingUTF8);
+    CFRelease(str);
+    path = widen_cfstring(mstr);
+    CFRelease(mstr);
+    Py_SetPath(path);
+}
+
+static wchar_t*
+widen_c_string(char* string)
+{
+    CFStringRef str;
+    wchar_t *retval;
+    if (string == NULL) return NULL;
+    str = CFStringCreateWithCString(NULL, string, kCFStringEncodingUTF8);
+    retval = widen_cfstring(str);
+    CFRelease(str);
+    return retval;
+}
+
+static FILE*
+open_scriptfile(void)
+{
+    FILE *fd = NULL;
+    char full_path[PATH_MAX + 1];
+    CFBundleRef bundle = CFBundleGetMainBundle();
+    CFURLRef bundle_url = CFBundleCopyResourcesDirectoryURL(bundle);
+    CFStringRef key = CFStringCreateWithCString(NULL, "GtkOSXLaunchScriptFile",
+                                               kCFStringEncodingUTF8);
+    CFStringRef str = make_filesystem_string(bundle_url);
+    CFMutableStringRef mstr = CFStringCreateMutableCopy(NULL, PATH_MAX, str);
+    CFStringRef filename = CFBundleGetValueForInfoDictionaryKey(bundle, key);
+    CFStringAppendCString(mstr, "/", kCFStringEncodingUTF8);
+    CFStringAppend(mstr, filename);
+    CFRelease(key);
+    if (CFStringGetCString(mstr, full_path, PATH_MAX, kCFStringEncodingUTF8))
+       fd = fopen(full_path, "r");
+    if (fd == NULL)
+       printf("Failed to open script file %s\n", full_path);
+    CFRelease(bundle_url);
+    CFRelease(str);
+    return fd;
+}
+
+int
+main(int argc, char *argv[])
+{
+    int retval = 0, i;
+    wchar_t **wargv = malloc(sizeof(wchar_t*) * argc);
+    FILE *fd = open_scriptfile();
+    if (fd == NULL)
+    {
+       return -1;
+    }
+    set_python_path();
+    Py_Initialize();
+    wargv[0] = get_bundle_dir();
+    for (i = 1; i < argc; ++i)
+       wargv[i] = widen_c_string(argv[i]);
+    PySys_SetArgvEx(argc, wargv, 0);
+    retval = PyRun_SimpleFile(fd, "");
+    if (retval != 0)
+       printf ("Run Simple File returned %d\n", retval);
+    Py_Finalize();
+    fclose(fd);
+    for (i = 0; i < argc; ++i)
+       free(wargv[i]);
+    free(wargv);
+    return 0;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]