1003 lines
32 KiB
C

#include <unistd.h>
#include <sys/stat.h>
#include <pwd.h>
#include <dlfcn.h>
#include <mach-o/dyld.h>
#include <CoreFoundation/CoreFoundation.h>
#include <ApplicationServices/ApplicationServices.h>
/*
Typedefs
*/
typedef int PyObject;
typedef void (*Py_DecRefPtr)(PyObject *);
typedef void (*Py_SetProgramNamePtr)(const char *);
typedef void (*Py_InitializePtr)(void);
typedef int (*PyRun_SimpleFilePtr)(FILE *, const char *);
typedef void (*Py_FinalizePtr)(void);
typedef PyObject *(*PySys_GetObjectPtr)(const char *);
typedef int *(*PySys_SetArgvPtr)(int argc, char **argv);
typedef PyObject *(*PyObject_StrPtr)(PyObject *);
typedef const char *(*PyString_AsStringPtr)(PyObject *);
typedef PyObject *(*PyObject_GetAttrStringPtr)(PyObject *, const char *);
static void DefaultDecRef(PyObject *op) {
if (op != NULL) --(*op);
}
typedef CFTypeRef id;
typedef const char *SEL;
typedef signed char BOOL;
#define NSAlertAlternateReturn 0
/*
Forward declarations
*/
static int report_error(const char *);
static CFTypeRef getKey(const char *key);
/*
Strings
*/
static const char *ERR_REALLYBADTITLE = "The application could not be launched.";
static const char *ERR_TITLEFORMAT = "%@ has encountered a fatal error, and will now terminate.";
static const char *ERR_NONAME = "The Info.plist file must have values for the CFBundleName or CFBundleExecutable strings.";
static const char *ERR_PYRUNTIMELOCATIONS = "The Info.plist file must have a PyRuntimeLocations array containing string values for preferred Python runtime locations. These strings should be \"otool -L\" style mach ids; \"@executable_stub\" and \"~\" prefixes will be translated accordingly.";
static const char *ERR_NOPYTHONRUNTIME = "A Python runtime could be located. You may need to install a framework build of Python, or edit the PyRuntimeLocations array in this application's Info.plist file.";
static const char *ERR_NOPYTHONSCRIPT = "A main script could not be located in the Resources folder.;";
static const char *ERR_LINKERRFMT = "An internal error occurred while attempting to link:\r\r%s\r\r";
static const char *ERR_UNKNOWNPYTHONEXCEPTION = "An uncaught exception was raised during execution of the main script, but its class or name could not be determined";
static const char *ERR_PYTHONEXCEPTION = "An uncaught exception was raised during execution of the main script:\r\r%@: %@\r\rThis may mean that an unexpected error has occurred, or that you do not have all of the dependencies for this application.\r\rSee the Console for a detailed traceback.";
static const char *ERR_COLONPATH = "Python applications can not currently run from paths containing a '/' (or ':' from the Terminal).";
static const char *ERR_DEFAULTURLTITLE = "Visit Website";
static const char *ERR_CONSOLEAPP = "Console.app";
static const char *ERR_CONSOLEAPPTITLE = "Open Console";
static const char *ERR_TERMINATE = "Terminate";
/*
Constants
*/
#define PYMACAPP_DYLD_FLAGS RTLD_LAZY|RTLD_GLOBAL
/*
Globals
*/
static CFMutableArrayRef pool;
#define USES(NAME) static __typeof__(&NAME) x ## NAME
/* ApplicationServices */
USES(LSOpenFSRef);
USES(LSFindApplicationForInfo);
USES(GetCurrentProcess);
USES(SetFrontProcess);
/* CoreFoundation */
USES(CFArrayRemoveValueAtIndex);
USES(CFStringCreateFromExternalRepresentation);
USES(CFStringAppendCString);
USES(CFStringCreateMutable);
USES(kCFTypeArrayCallBacks);
USES(CFArrayCreateMutable);
USES(CFRetain);
USES(CFRelease);
USES(CFBundleGetMainBundle);
USES(CFBundleGetValueForInfoDictionaryKey);
USES(CFArrayGetCount);
USES(CFStringCreateWithCString);
USES(CFArrayGetValueAtIndex);
USES(CFArrayAppendValue);
USES(CFStringFind);
USES(CFBundleCopyPrivateFrameworksURL);
USES(CFURLCreateWithFileSystemPathRelativeToBase);
USES(CFStringCreateWithSubstring);
USES(CFStringGetLength);
USES(CFURLGetFileSystemRepresentation);
USES(CFURLCreateWithFileSystemPath);
USES(CFShow);
USES(CFBundleCopyResourcesDirectoryURL);
USES(CFURLCreateFromFileSystemRepresentation);
USES(CFURLCreateFromFileSystemRepresentationRelativeToBase);
USES(CFStringGetCharacterAtIndex);
USES(CFURLCreateWithString);
USES(CFStringGetCString);
USES(CFStringCreateByCombiningStrings);
USES(CFDictionaryGetValue);
USES(CFBooleanGetValue);
USES(CFStringCreateArrayBySeparatingStrings);
USES(CFArrayAppendArray);
USES(CFStringCreateByCombiningStrings);
USES(CFStringCreateWithFormat);
USES(CFBundleCopyResourceURL);
USES(CFBundleCopyAuxiliaryExecutableURL);
USES(CFURLCreateCopyDeletingLastPathComponent);
USES(CFURLCreateCopyAppendingPathComponent);
USES(CFURLCopyLastPathComponent);
USES(CFStringGetMaximumSizeForEncoding);
#undef USES
/*
objc
*/
#define CLS(name) xobjc_getClass(name)
#define MSG(receiver, selName, ...) \
xobjc_msgSend(receiver, xsel_getUid(selName), ## __VA_ARGS__)
static id (*xobjc_getClass)(const char *name);
static SEL (*xsel_getUid)(const char *str);
static id (*xobjc_msgSend)(id self, SEL op, ...);
/*
Cocoa
*/
static void (*xNSLog)(CFStringRef format, ...);
static BOOL (*xNSApplicationLoad)(void);
static int (*xNSRunAlertPanel)(CFStringRef title, CFStringRef msg, CFStringRef defaultButton, CFStringRef alternateButton, CFStringRef otherButton, ...);
/*
Functions
*/
static int bind_objc_Cocoa_ApplicationServices(void) {
static Boolean bound = false;
if (bound) return 0;
bound = true;
void *cf_dylib;
cf_dylib = dlopen("/usr/lib/libobjc.dylib", PYMACAPP_DYLD_FLAGS);
if (cf_dylib == NULL) return -1;
#define LOOKUP(NAME) do { \
void *tmpSymbol = dlsym( \
cf_dylib, #NAME); \
if (tmpSymbol == NULL) return -1; \
x ## NAME = (__typeof__(x ## NAME))(tmpSymbol); \
} while (0)
LOOKUP(objc_getClass);
LOOKUP(sel_getUid);
LOOKUP(objc_msgSend);
cf_dylib = dlopen(
"/System/Library/Frameworks/Cocoa.framework/Cocoa",
PYMACAPP_DYLD_FLAGS);
if (cf_dylib == NULL) return -1;
LOOKUP(NSLog);
LOOKUP(NSApplicationLoad);
LOOKUP(NSRunAlertPanel);
#undef LOOKUP
cf_dylib = dlopen(
"/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices",
PYMACAPP_DYLD_FLAGS);
if (cf_dylib == NULL) return -1;
#define LOOKUP(NAME) do { \
void *tmpSymbol = dlsym( \
cf_dylib, #NAME); \
if (tmpSymbol == NULL) return -1; \
x ## NAME = (__typeof__(&NAME))(tmpSymbol); \
} while (0)
LOOKUP(GetCurrentProcess);
LOOKUP(SetFrontProcess);
LOOKUP(LSOpenFSRef);
LOOKUP(LSFindApplicationForInfo);
#undef LOOKUP
return 0;
}
static int bind_CoreFoundation(void) {
static Boolean bound = false;
void *cf_dylib;
if (bound) return 0;
bound = true;
cf_dylib = dlopen(
"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation",
PYMACAPP_DYLD_FLAGS);
if (cf_dylib == NULL) return -1;
#define LOOKUP(NAME) do { \
void *tmpSymbol = dlsym( \
cf_dylib, #NAME); \
if (tmpSymbol == NULL) return -1; \
x ## NAME = (__typeof__(&NAME))(tmpSymbol); \
} while (0)
LOOKUP(CFArrayRemoveValueAtIndex);
LOOKUP(CFStringCreateFromExternalRepresentation);
LOOKUP(CFStringAppendCString);
LOOKUP(CFStringCreateMutable);
LOOKUP(kCFTypeArrayCallBacks);
LOOKUP(CFArrayCreateMutable);
LOOKUP(CFRetain);
LOOKUP(CFRelease);
LOOKUP(CFBundleGetMainBundle);
LOOKUP(CFBundleGetValueForInfoDictionaryKey);
LOOKUP(CFArrayGetCount);
LOOKUP(CFStringCreateWithCString);
LOOKUP(CFArrayGetValueAtIndex);
LOOKUP(CFArrayAppendValue);
LOOKUP(CFStringFind);
LOOKUP(CFBundleCopyPrivateFrameworksURL);
LOOKUP(CFURLCreateWithFileSystemPathRelativeToBase);
LOOKUP(CFStringCreateWithSubstring);
LOOKUP(CFStringGetLength);
LOOKUP(CFURLGetFileSystemRepresentation);
LOOKUP(CFURLCreateWithFileSystemPath);
LOOKUP(CFShow);
LOOKUP(CFBundleCopyResourcesDirectoryURL);
LOOKUP(CFURLCreateFromFileSystemRepresentation);
LOOKUP(CFURLCreateFromFileSystemRepresentationRelativeToBase);
LOOKUP(CFStringGetCharacterAtIndex);
LOOKUP(CFURLCreateWithString);
LOOKUP(CFStringGetCString);
LOOKUP(CFStringCreateByCombiningStrings);
LOOKUP(CFDictionaryGetValue);
LOOKUP(CFBooleanGetValue);
LOOKUP(CFStringCreateArrayBySeparatingStrings);
LOOKUP(CFArrayAppendArray);
LOOKUP(CFStringCreateByCombiningStrings);
LOOKUP(CFStringCreateWithFormat);
LOOKUP(CFBundleCopyResourceURL);
LOOKUP(CFBundleCopyAuxiliaryExecutableURL);
LOOKUP(CFURLCreateCopyDeletingLastPathComponent);
LOOKUP(CFURLCreateCopyAppendingPathComponent);
LOOKUP(CFURLCopyLastPathComponent);
LOOKUP(CFStringGetMaximumSizeForEncoding);
#undef LOOKUP
return 0;
}
#define AUTORELEASE(obj) ((obj == NULL) ? NULL : ( \
xCFArrayAppendValue(pool, (const void *)obj), \
xCFRelease(obj), \
obj))
#define xCFSTR(s) AUTORELEASE( \
xCFStringCreateWithCString(NULL, s, kCFStringEncodingUTF8))
static int openConsole(void) {
OSStatus err;
FSRef consoleRef;
err = xLSFindApplicationForInfo(
kLSUnknownCreator,
NULL,
xCFSTR(ERR_CONSOLEAPP),
&consoleRef,
NULL);
if (err != noErr) return err;
return xLSOpenFSRef((const FSRef *)&consoleRef, NULL);
}
static CFTypeRef getKey(const char *key) {
CFTypeRef rval;
CFStringRef cfKey = xCFStringCreateWithCString(NULL,
key, kCFStringEncodingUTF8);
if (!cfKey) return NULL;
rval = xCFBundleGetValueForInfoDictionaryKey(
xCFBundleGetMainBundle(),
cfKey);
xCFRelease(cfKey);
return rval;
}
static CFStringRef getApplicationName(void) {
static CFStringRef name = NULL;
if (name) return name;
name = (CFStringRef)getKey("CFBundleName");
if (!name) name = (CFStringRef)getKey("CFBundleExecutable");
return AUTORELEASE(name);
}
static CFStringRef getErrorTitle(CFStringRef applicationName) {
CFStringRef res;
if (!applicationName) return xCFSTR(ERR_REALLYBADTITLE);
res = xCFStringCreateWithFormat(
NULL, NULL, xCFSTR(ERR_TITLEFORMAT), applicationName);
AUTORELEASE(res);
return res;
}
static void ensureGUI(void) {
ProcessSerialNumber psn;
id app = MSG(CLS("NSApplication"), "sharedApplication");
xNSApplicationLoad();
MSG(app, "activateIgnoringOtherApps:", (BOOL)1);
if (xGetCurrentProcess(&psn) == noErr) {
xSetFrontProcess(&psn);
}
}
static int report_error(const char *error) {
int choice;
id releasePool;
if (bind_objc_Cocoa_ApplicationServices()) {
fprintf(stderr, "%s\n", error);
return -1;
}
releasePool = MSG(MSG(CLS("NSAutoreleasePool"), "alloc"), "init");
xNSLog(xCFSTR("%@"), xCFSTR(error));
if (!xNSApplicationLoad()) {
xNSLog(xCFSTR("NSApplicationLoad() failed"));
} else {
ensureGUI();
choice = xNSRunAlertPanel(
getErrorTitle(getApplicationName()),
xCFSTR("%@"),
xCFSTR(ERR_TERMINATE),
xCFSTR(ERR_CONSOLEAPPTITLE),
NULL,
xCFSTR(error));
if (choice == NSAlertAlternateReturn) openConsole();
}
MSG(releasePool, "release");
return -1;
}
static CFStringRef pathFromURL(CFURLRef anURL) {
UInt8 buf[PATH_MAX];
xCFURLGetFileSystemRepresentation(anURL, true, buf, sizeof(buf));
return xCFStringCreateWithCString(NULL, (char *)buf, kCFStringEncodingUTF8);
}
static CFStringRef pyStandardizePath(CFStringRef pyLocation) {
CFRange foundRange;
CFURLRef fmwkURL;
CFURLRef locURL;
CFStringRef subpath;
static CFStringRef prefix = NULL;
if (!prefix) prefix = xCFSTR("@executable_path/");
foundRange = xCFStringFind(pyLocation, prefix, 0);
if (foundRange.location == kCFNotFound || foundRange.length == 0) {
return NULL;
}
fmwkURL = xCFBundleCopyPrivateFrameworksURL(xCFBundleGetMainBundle());
foundRange.location = foundRange.length;
foundRange.length = xCFStringGetLength(pyLocation) - foundRange.length;
subpath = xCFStringCreateWithSubstring(NULL, pyLocation, foundRange);
locURL = xCFURLCreateWithFileSystemPathRelativeToBase(
NULL,
subpath,
kCFURLPOSIXPathStyle,
false,
fmwkURL);
xCFRelease(subpath);
xCFRelease(fmwkURL);
subpath = pathFromURL(locURL);
xCFRelease(locURL);
return subpath;
}
static Boolean doesPathExist(CFStringRef path) {
struct stat st;
CFURLRef locURL;
UInt8 buf[PATH_MAX];
locURL = xCFURLCreateWithFileSystemPath(
NULL, path, kCFURLPOSIXPathStyle, false);
xCFURLGetFileSystemRepresentation(locURL, true, buf, sizeof(buf));
xCFRelease(locURL);
return (stat((const char *)buf, &st) == -1 ? false : true);
}
static CFStringRef findPyLocation(CFArrayRef pyLocations) {
int i;
int cnt = xCFArrayGetCount(pyLocations);
for (i = 0; i < cnt; i++) {
CFStringRef newLoc;
CFStringRef pyLocation = xCFArrayGetValueAtIndex(pyLocations, i);
newLoc = pyStandardizePath(pyLocation);
if (!newLoc) newLoc = pyLocation;
if (doesPathExist(newLoc)) {
if (newLoc == pyLocation) xCFRetain(newLoc);
return newLoc;
}
if (newLoc) xCFRelease(newLoc);
}
return NULL;
}
static CFStringRef tildeExpand(CFStringRef path) {
CFURLRef pathURL;
char buf[PATH_MAX];
CFURLRef fullPathURL;
struct passwd *pwnam;
char tmp;
char *dir = NULL;
xCFStringGetCString(path, buf, sizeof(buf), kCFStringEncodingUTF8);
int i;
if (buf[0] != '~') {
return xCFStringCreateWithCString(
NULL, buf, kCFStringEncodingUTF8);
}
/* user in path */
i = 1;
while (buf[i] != '\0' && buf[i] != '/') {
i++;
}
if (i == 1) {
dir = getenv("HOME");
} else {
tmp = buf[i];
buf[i] = '\0';
pwnam = getpwnam((const char *)&buf[1]);
if (pwnam) dir = pwnam->pw_dir;
buf[i] = tmp;
}
if (!dir) {
return xCFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
}
pathURL = xCFURLCreateFromFileSystemRepresentation(
NULL, (const UInt8*)dir, strlen(dir), false);
fullPathURL = xCFURLCreateFromFileSystemRepresentationRelativeToBase(
NULL, (const UInt8*)&buf[i + 1], strlen(&buf[i + 1]), false, pathURL);
xCFRelease(pathURL);
path = pathFromURL(fullPathURL);
xCFRelease(fullPathURL);
return path;
}
static void setcfenv(char *name, CFStringRef value) {
char buf[PATH_MAX];
xCFStringGetCString(value, buf, sizeof(buf), kCFStringEncodingUTF8);
setenv(name, buf, 1);
}
static void setPythonPath(void) {
CFMutableArrayRef paths;
CFURLRef resDir;
CFStringRef resPath;
CFArrayRef resPackages;
CFDictionaryRef options;
paths = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks);
resDir = xCFBundleCopyResourcesDirectoryURL(xCFBundleGetMainBundle());
resPath = pathFromURL(resDir);
xCFArrayAppendValue(paths, resPath);
xCFRelease(resPath);
resPackages = getKey("PyResourcePackages");
if (resPackages) {
int i;
int cnt = xCFArrayGetCount(resPackages);
for (i = 0; i < cnt; i++) {
resPath = tildeExpand(xCFArrayGetValueAtIndex(resPackages, i));
if (xCFStringGetLength(resPath)) {
if (xCFStringGetCharacterAtIndex(resPath, 0) != '/') {
CFURLRef absURL = xCFURLCreateWithString(
NULL, resPath, resDir);
xCFRelease(resPath);
resPath = pathFromURL(absURL);
xCFRelease(absURL);
}
xCFArrayAppendValue(paths, resPath);
}
xCFRelease(resPath);
}
}
xCFRelease(resDir);
options = getKey("PyOptions");
if (options) {
CFBooleanRef use_pythonpath;
use_pythonpath = xCFDictionaryGetValue(
options, xCFSTR("use_pythonpath"));
if (use_pythonpath && xCFBooleanGetValue(use_pythonpath)) {
char *ppath = getenv("PYTHONPATH");
if (ppath) {
CFArrayRef oldPath;
oldPath = xCFStringCreateArrayBySeparatingStrings(
NULL, xCFSTR(ppath), xCFSTR(":"));
if (oldPath) {
CFRange rng;
rng.location = 0;
rng.length = xCFArrayGetCount(oldPath);
xCFArrayAppendArray(paths, oldPath, rng);
xCFRelease(oldPath);
}
}
}
}
if (xCFArrayGetCount(paths)) {
resPath = xCFStringCreateByCombiningStrings(NULL, paths, xCFSTR(":"));
setcfenv("PYTHONPATH", resPath);
xCFRelease(resPath);
}
xCFRelease(paths);
}
static void setResourcePath(void) {
CFURLRef resDir;
CFStringRef resPath;
resDir = xCFBundleCopyResourcesDirectoryURL(xCFBundleGetMainBundle());
resPath = pathFromURL(resDir);
xCFRelease(resDir);
setcfenv("RESOURCEPATH", resPath);
xCFRelease(resPath);
}
static void setExecutablePath(void) {
char executable_path[PATH_MAX];
uint32_t bufsize = PATH_MAX;
if (!_NSGetExecutablePath(executable_path, &bufsize)) {
executable_path[bufsize] = '\0';
setenv("EXECUTABLEPATH", executable_path, 1);
}
}
static CFStringRef getMainScript(void) {
CFMutableArrayRef possibleMains;
CFBundleRef bndl;
CFStringRef e_py, e_pyc, e_pyo, path;
int i, cnt;
possibleMains = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks);
CFArrayRef firstMains = getKey("PyMainFileNames");
if (firstMains) {
CFRange rng;
rng.location = 0;
rng.length = xCFArrayGetCount(firstMains);
xCFArrayAppendArray(possibleMains, firstMains, rng);
}
xCFArrayAppendValue(possibleMains, xCFSTR("__main__"));
xCFArrayAppendValue(possibleMains, xCFSTR("__realmain__"));
xCFArrayAppendValue(possibleMains, xCFSTR("launcher"));
e_py = xCFSTR("py");
e_pyc = xCFSTR("pyc");
e_pyo = xCFSTR("pyo");
cnt = xCFArrayGetCount(possibleMains);
bndl = xCFBundleGetMainBundle();
path = NULL;
for (i = 0; i < cnt; i++) {
CFStringRef base;
CFURLRef resURL;
base = xCFArrayGetValueAtIndex(possibleMains, i);
resURL = xCFBundleCopyResourceURL(bndl, base, e_py, NULL);
if (resURL == NULL) {
resURL = xCFBundleCopyResourceURL(bndl, base, e_pyc, NULL);
}
if (resURL == NULL) {
resURL = xCFBundleCopyResourceURL(bndl, base, e_pyo, NULL);
}
if (resURL != NULL) {
path = pathFromURL(resURL);
xCFRelease(resURL);
break;
}
}
xCFRelease(possibleMains);
return path;
}
static int report_linkEdit_error(void) {
CFStringRef errString;
const char *errorString;
char *buf;
errorString = dlerror();
fprintf(stderr, errorString);
errString = xCFStringCreateWithFormat(
NULL, NULL, xCFSTR(ERR_LINKERRFMT), errString);
buf = alloca(xCFStringGetMaximumSizeForEncoding(
xCFStringGetLength(errString), kCFStringEncodingUTF8));
xCFStringGetCString(errString, buf, sizeof(buf), kCFStringEncodingUTF8);
xCFRelease(errString);
return report_error(buf);
}
static CFStringRef getPythonInterpreter(CFStringRef pyLocation) {
CFBundleRef bndl;
CFStringRef auxName;
CFURLRef auxURL;
CFStringRef path;
auxName = getKey("PyExecutableName");
if (!auxName) auxName = xCFSTR("python");
bndl = xCFBundleGetMainBundle();
auxURL = xCFBundleCopyAuxiliaryExecutableURL(bndl, auxName);
if (auxURL) {
path = pathFromURL(auxURL);
xCFRelease(auxURL);
return path;
}
return NULL;
}
static CFStringRef getErrorScript(void) {
CFMutableArrayRef errorScripts;
CFBundleRef bndl;
CFStringRef path;
int i, cnt;
errorScripts = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks);
CFArrayRef firstErrorScripts = getKey("PyErrorScripts");
if (firstErrorScripts) {
CFRange rng;
rng.location = 0;
rng.length = xCFArrayGetCount(firstErrorScripts);
xCFArrayAppendArray(errorScripts, firstErrorScripts, rng);
}
xCFArrayAppendValue(errorScripts, xCFSTR("__error__"));
xCFArrayAppendValue(errorScripts, xCFSTR("__error__.py"));
xCFArrayAppendValue(errorScripts, xCFSTR("__error__.pyc"));
xCFArrayAppendValue(errorScripts, xCFSTR("__error__.pyo"));
xCFArrayAppendValue(errorScripts, xCFSTR("__error__.sh"));
cnt = xCFArrayGetCount(errorScripts);
bndl = xCFBundleGetMainBundle();
path = NULL;
for (i = 0; i < cnt; i++) {
CFStringRef base;
CFURLRef resURL;
base = xCFArrayGetValueAtIndex(errorScripts, i);
resURL = xCFBundleCopyResourceURL(bndl, base, NULL, NULL);
if (resURL) {
path = pathFromURL(resURL);
xCFRelease(resURL);
break;
}
}
xCFRelease(errorScripts);
return path;
}
static CFMutableArrayRef get_trimmed_lines(CFStringRef output) {
CFMutableArrayRef lines;
CFArrayRef tmp;
CFRange rng;
lines = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks);
tmp = xCFStringCreateArrayBySeparatingStrings(
NULL, output, xCFSTR("\n"));
rng.location = 0;
rng.length = xCFArrayGetCount(tmp);
xCFArrayAppendArray(lines, tmp, rng);
while (true) {
CFIndex cnt = xCFArrayGetCount(lines);
CFStringRef last;
/* Nothing on stdout means pass silently */
if (cnt <= 0) {
xCFRelease(lines);
return NULL;
}
last = xCFArrayGetValueAtIndex(lines, cnt - 1);
if (xCFStringGetLength(last) > 0) break;
xCFArrayRemoveValueAtIndex(lines, cnt - 1);
}
return lines;
}
static int report_script_error(const char *msg, CFStringRef cls, CFStringRef name) {
CFStringRef errorScript;
CFMutableArrayRef lines;
CFRange foundRange;
CFStringRef lastLine;
CFStringRef output = NULL;
CFIndex lineCount;
CFURLRef buttonURL = NULL;
CFStringRef buttonString = NULL;
CFStringRef title = NULL;
CFStringRef errmsg = NULL;
id releasePool;
int errBinding;
int status = 0;
char *buf;
if (cls && name) {
CFStringRef errString = xCFStringCreateWithFormat(
NULL, NULL, xCFSTR(msg), cls, name);
buf = alloca(xCFStringGetMaximumSizeForEncoding(
xCFStringGetLength(errString), kCFStringEncodingUTF8));
xCFStringGetCString(
errString, buf, sizeof(buf), kCFStringEncodingUTF8);
xCFRelease(errString);
} else {
buf = (char *)msg;
}
errorScript = getErrorScript();
if (!errorScript) return report_error(buf);
errBinding = bind_objc_Cocoa_ApplicationServices();
if (!errBinding) {
id task, stdoutPipe, taskData;
CFMutableArrayRef argv;
releasePool = MSG(MSG(CLS("NSAutoreleasePool"), "alloc"), "init");
task = MSG(MSG(CLS("NSTask"), "alloc"), "init");
stdoutPipe = MSG(CLS("NSPipe"), "pipe");
MSG(task, "setLaunchPath:", xCFSTR("/bin/sh"));
MSG(task, "setStandardOutput:", stdoutPipe);
argv = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks);
xCFArrayAppendValue(argv, errorScript);
xCFArrayAppendValue(argv, getApplicationName());
if (cls && name) {
xCFArrayAppendValue(argv, cls);
xCFArrayAppendValue(argv, name);
}
MSG(task, "setArguments:", argv);
/* This could throw, in theory, but /bin/sh should prevent that */
MSG(task, "launch");
MSG(task, "waitUntilExit");
taskData = MSG(
MSG(stdoutPipe, "fileHandleForReading"),
"readDataToEndOfFile");
xCFRelease(argv);
status = (int)MSG(task, "terminationStatus");
xCFRelease(task);
if (!status && taskData) {
output = xCFStringCreateFromExternalRepresentation(
NULL, taskData, kCFStringEncodingUTF8);
}
MSG(releasePool, "release");
}
xCFRelease(errorScript);
if (status || !output) return report_error(buf);
lines = get_trimmed_lines(output);
xCFRelease(output);
/* Nothing on stdout means pass silently */
if (!lines) return -1;
lineCount = xCFArrayGetCount(lines);
lastLine = xCFArrayGetValueAtIndex(lines, lineCount - 1);
foundRange = xCFStringFind(lastLine, xCFSTR("ERRORURL: "), 0);
if (foundRange.location != kCFNotFound && foundRange.length != 0) {
CFMutableArrayRef buttonArr;
CFArrayRef tmp;
CFRange rng;
buttonArr = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks);
tmp = xCFStringCreateArrayBySeparatingStrings(
NULL, lastLine, xCFSTR(" "));
lineCount -= 1;
xCFArrayRemoveValueAtIndex(lines, lineCount);
rng.location = 1;
rng.length = xCFArrayGetCount(tmp) - 1;
xCFArrayAppendArray(buttonArr, tmp, rng);
xCFRelease(tmp);
while (true) {
CFStringRef tmpstr;
if (xCFArrayGetCount(buttonArr) <= 0) break;
tmpstr = xCFArrayGetValueAtIndex(buttonArr, 0);
if (xCFStringGetLength(tmpstr) == 0) {
xCFArrayRemoveValueAtIndex(buttonArr, 0);
} else {
break;
}
}
buttonURL = xCFURLCreateWithString(
NULL, xCFArrayGetValueAtIndex(buttonArr, 0), NULL);
if (buttonURL) {
xCFArrayRemoveValueAtIndex(buttonArr, 0);
while (true) {
CFStringRef tmpstr;
if (xCFArrayGetCount(buttonArr) <= 0) break;
tmpstr = xCFArrayGetValueAtIndex(buttonArr, 0);
if (xCFStringGetLength(tmpstr) == 0) {
xCFArrayRemoveValueAtIndex(buttonArr, 0);
} else {
break;
}
}
if (xCFArrayGetCount(buttonArr) > 0) {
buttonString = xCFStringCreateByCombiningStrings(
NULL, buttonArr, xCFSTR(" "));
}
if (!buttonString) buttonString = xCFSTR(ERR_DEFAULTURLTITLE);
}
xCFRelease(buttonArr);
}
if (lineCount <= 0 || errBinding) {
xCFRelease(lines);
return report_error(buf);
}
releasePool = MSG(MSG(CLS("NSAutoreleasePool"), "alloc"), "init");
title = xCFArrayGetValueAtIndex(lines, 0);
xCFRetain(title);
AUTORELEASE(title);
lineCount -= 1;
xCFArrayRemoveValueAtIndex(lines, lineCount);
xNSLog(xCFSTR("%@"), title);
if (lineCount > 0) {
CFStringRef showerr;
errmsg = xCFStringCreateByCombiningStrings(
NULL, lines, xCFSTR("\r"));
AUTORELEASE(errmsg);
showerr = MSG(
MSG(errmsg, "componentsSeparatedByString:", xCFSTR("\r")),
"componentsJoinedByString:", xCFSTR("\n"));
xNSLog(xCFSTR("%@"), showerr);
} else {
errmsg = xCFSTR("");
}
ensureGUI();
if (!buttonURL) {
int choice = xNSRunAlertPanel(
title, xCFSTR("%@"), xCFSTR(ERR_TERMINATE),
xCFSTR(ERR_CONSOLEAPPTITLE), NULL, errmsg);
if (choice == NSAlertAlternateReturn) openConsole();
} else {
int choice = xNSRunAlertPanel(
title, xCFSTR("%@"), xCFSTR(ERR_TERMINATE),
buttonString, NULL, errmsg);
if (choice == NSAlertAlternateReturn) {
id ws = MSG(CLS("NSWorkspace"), "sharedWorkspace");
MSG(ws, "openURL:", buttonURL);
}
}
MSG(releasePool, "release");
xCFRelease(lines);
return -1;
}
static int py2app_main(int argc, char * const *argv, char * const *envp) {
CFArrayRef pyLocations;
CFStringRef pyLocation;
CFStringRef mainScript;
CFStringRef pythonInterpreter;
char *resource_path;
char buf[PATH_MAX];
char c_pythonInterpreter[PATH_MAX];
char c_mainScript[PATH_MAX];
char **argv_new;
struct stat sb;
void *py_dylib;
void *tmpSymbol;
int rval;
FILE *mainScriptFile;
if (!getApplicationName()) return report_error(ERR_NONAME);
pyLocations = (CFArrayRef)getKey("PyRuntimeLocations");
if (!pyLocations) return report_error(ERR_PYRUNTIMELOCATIONS);
pyLocation = findPyLocation(pyLocations);
if (!pyLocation) return report_error(ERR_NOPYTHONRUNTIME);
setExecutablePath();
setResourcePath();
/* check for ':' in path, not compatible with Python due to Py_GetPath */
/* XXX: Could work-around by creating something in /tmp I guess */
resource_path = getenv("RESOURCEPATH");
if ((resource_path == NULL) || (strchr(resource_path, ':') != NULL)) {
return report_error(ERR_COLONPATH);
}
setPythonPath();
setenv("ARGVZERO", argv[0], 1);
mainScript = getMainScript();
if (!mainScript) return report_error(ERR_NOPYTHONSCRIPT);
pythonInterpreter = getPythonInterpreter(pyLocation);
xCFStringGetCString(
pythonInterpreter, c_pythonInterpreter,
sizeof(c_pythonInterpreter), kCFStringEncodingUTF8);
xCFRelease(pythonInterpreter);
if (lstat(c_pythonInterpreter, &sb) == 0) {
if (!((sb.st_mode & S_IFLNK) == S_IFLNK)) {
setenv("PYTHONHOME", resource_path, 1);
}
}
xCFStringGetCString(pyLocation, buf, sizeof(buf), kCFStringEncodingUTF8);
py_dylib = dlopen(buf, PYMACAPP_DYLD_FLAGS);
if (py_dylib == NULL) return report_linkEdit_error();
#define LOOKUP_SYMBOL(NAME) \
tmpSymbol = dlsym(py_dylib, # NAME)
#define LOOKUP_DEFINEADDRESS(NAME, ADDRESS) \
NAME ## Ptr NAME = (NAME ## Ptr)ADDRESS
#define LOOKUP_DEFINE(NAME) \
LOOKUP_DEFINEADDRESS(NAME, (tmpSymbol))
#define LOOKUP(NAME) \
LOOKUP_SYMBOL(NAME); \
if ( tmpSymbol == NULL) \
return report_linkEdit_error(); \
LOOKUP_DEFINE(NAME)
LOOKUP_SYMBOL(Py_DecRef);
LOOKUP_DEFINEADDRESS(Py_DecRef, (tmpSymbol ? (tmpSymbol) : &DefaultDecRef));
LOOKUP(Py_SetProgramName);
LOOKUP(Py_Initialize);
LOOKUP(PyRun_SimpleFile);
LOOKUP(Py_Finalize);
LOOKUP(PySys_GetObject);
LOOKUP(PySys_SetArgv);
LOOKUP(PyObject_Str);
LOOKUP(PyString_AsString);
LOOKUP(PyObject_GetAttrString);
#undef LOOKUP
#undef LOOKUP_DEFINE
#undef LOOKUP_DEFINEADDRESS
#undef LOOKUP_SYMBOL
Py_SetProgramName(c_pythonInterpreter);
Py_Initialize();
xCFStringGetCString(
mainScript, c_mainScript,
sizeof(c_mainScript), kCFStringEncodingUTF8);
xCFRelease(mainScript);
argv_new = alloca((argc + 1) * sizeof(char *));
argv_new[argc] = NULL;
argv_new[0] = c_mainScript;
memcpy(&argv_new[1], &argv[1], (argc - 1) * sizeof(char *));
PySys_SetArgv(argc, argv_new);
mainScriptFile = fopen(c_mainScript, "r");
rval = PyRun_SimpleFile(mainScriptFile, c_mainScript);
fclose(mainScriptFile);
while (rval) {
PyObject *exc, *exceptionClassName, *v, *exceptionName;
CFStringRef clsName, excName;
exc = PySys_GetObject("last_type");
if ( !exc ) {
rval = report_error(ERR_UNKNOWNPYTHONEXCEPTION);
break;
}
exceptionClassName = PyObject_GetAttrString(exc, "__name__");
if (!exceptionClassName) {
rval = report_error(ERR_UNKNOWNPYTHONEXCEPTION);
break;
}
v = PySys_GetObject("last_value");
exceptionName = (v ? PyObject_Str(v) : NULL);
clsName = xCFSTR(PyString_AsString(exceptionClassName));
Py_DecRef(exceptionClassName);
if (exceptionName) {
excName = xCFSTR(PyString_AsString(exceptionName));
Py_DecRef(exceptionName);
} else {
excName = xCFSTR("");
}
rval = report_script_error(ERR_PYTHONEXCEPTION, clsName, excName);
break;
}
Py_Finalize();
return rval;
}
int main(int argc, char * const *argv, char * const *envp)
{
int rval;
if (bind_CoreFoundation()) {
fprintf(stderr, "CoreFoundation not found or functions missing\n");
return -1;
}
if (!xCFBundleGetMainBundle()) {
fprintf(stderr, "Not bundled, exiting\n");
return -1;
}
pool = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks);
if (!pool) {
fprintf(stderr, "Couldn't create global pool\n");
return -1;
}
rval = py2app_main(argc, argv, envp);
xCFRelease(pool);
return rval;
}