[gtk-mac-bundler] Provide example for using a Mach-O executable launcher for Python.
- From: John Ralls <jralls src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk-mac-bundler] Provide example for using a Mach-O executable launcher for Python.
- Date: Tue, 16 Aug 2016 19:09:23 +0000 (UTC)
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]