diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py index 7791668a09..a6fd365869 100644 --- a/installer/osx/freeze.py +++ b/installer/osx/freeze.py @@ -54,54 +54,7 @@ os.environ['QT_PLUGIN_PATH'] = qt_plugins args = [path, loader_path] + sys.argv[1:] os.execv(python, args) ''' - CHECK_SYMLINKS_PRESCRIPT = \ -r''' -def _check_symlinks_prescript(): - import os, tempfile, traceback, sys - from Authorization import Authorization, kAuthorizationFlagDestroyRights - AUTHTOOL="""#!%(sp)s -import os -scripts = %(sp)s -links = %(sp)s -os.setuid(0) -for s, l in zip(scripts, links): - if os.path.lexists(l): - os.remove(l) - print 'Creating link:', l, '->', s - omask = os.umask(022) - os.symlink(s, l) - os.umask(omask) -""" - - dest_path = %(dest_path)s - resources_path = os.environ['RESOURCEPATH'] - scripts = %(scripts)s - links = [os.path.join(dest_path, i) for i in scripts] - scripts = [os.path.join(resources_path, 'loaders', i) for i in scripts] - - bad = False - for s, l in zip(scripts, links): - if os.path.exists(l) and os.path.exists(os.path.realpath(l)): - continue - bad = True - break - if bad: - auth = Authorization(destroyflags=(kAuthorizationFlagDestroyRights,)) - fd, name = tempfile.mkstemp('.py') - os.write(fd, AUTHTOOL %(pp)s (sys.executable, repr(scripts), repr(links))) - os.close(fd) - os.chmod(name, 0700) - try: - pipe = auth.executeWithPrivileges(sys.executable, name) - sys.stdout.write(pipe.read()) - pipe.close() - except: - traceback.print_exc() - finally: - os.unlink(name) -_check_symlinks_prescript() -''' def get_modulefinder(self): if self.debug_modulegraph: debug = 4 @@ -286,15 +239,12 @@ _check_symlinks_prescript() print print 'Installing prescipt' sf = [os.path.basename(s) for s in all_names] - cs = BuildAPP.CHECK_SYMLINKS_PRESCRIPT % dict(dest_path=repr('/usr/bin'), - scripts=repr(sf), - sp='%s', pp='%') launcher_path = os.path.join(resource_dir, '__boot__.py') f = open(launcher_path, 'r') src = f.read() f.close() src = src.replace('import Image', 'from PIL import Image') - src = re.sub('(_run\s*\(.*?.py.*?\))', cs+'%s'%( + src = re.sub('(_run\s*\(.*?.py.*?\))', '%s'%( ''' sys.frameworks_dir = os.path.join(os.path.dirname(os.environ['RESOURCEPATH']), 'Frameworks') ''') + r'\n\1', src) diff --git a/installer/osx/py2app/__init__.py b/installer/osx/py2app/__init__.py new file mode 100644 index 0000000000..3d1a86922e --- /dev/null +++ b/installer/osx/py2app/__init__.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + + diff --git a/installer/osx/py2app/launcher.py b/installer/osx/py2app/launcher.py new file mode 100644 index 0000000000..dcd14668f7 --- /dev/null +++ b/installer/osx/py2app/launcher.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +def _disable_linecache(): + import linecache + def fake_getline(*args, **kwargs): + return '' + linecache.orig_getline = linecache.getline + linecache.getline = fake_getline +_disable_linecache() + +def _recipes_pil_prescript(plugins): + from PIL import Image + import sys + def init(): + if Image._initialized >= 2: + return + for plugin in plugins: + try: + __import__(plugin, globals(), locals(), []) + except ImportError: + if Image.DEBUG: + print 'Image: failed to import' + print plugin, ':', sys.exc_info()[1] + if Image.OPEN or Image.SAVE: + Image._initialized = 2 + Image.init = init + + +_recipes_pil_prescript(['Hdf5StubImagePlugin', 'FitsStubImagePlugin', 'SunImagePlugin', 'GbrImagePlugin', 'PngImagePlugin', 'MicImagePlugin', 'FpxImagePlugin', 'PcxImagePlugin', 'ImImagePlugin', 'SpiderImagePlugin', 'PsdImagePlugin', 'BufrStubImagePlugin', 'SgiImagePlugin', 'McIdasImagePlugin', 'XpmImagePlugin', 'BmpImagePlugin', 'TgaImagePlugin', 'PalmImagePlugin', 'XVThumbImagePlugin', 'GribStubImagePlugin', 'ArgImagePlugin', 'PdfImagePlugin', 'ImtImagePlugin', 'GifImagePlugin', 'CurImagePlugin', 'WmfImagePlugin', 'MpegImagePlugin', 'IcoImagePlugin', 'TiffImagePlugin', 'PpmImagePlugin', 'MspImagePlugin', 'EpsImagePlugin', 'JpegImagePlugin', 'PixarImagePlugin', 'PcdImagePlugin', 'IptcImagePlugin', 'XbmImagePlugin', 'DcxImagePlugin', 'IcnsImagePlugin', 'FliImagePlugin']) + +def _run(): + global __file__ + import os, sys, site + sys.frozen = 'macosx_app' + base = os.environ['RESOURCEPATH'] + sys.frameworks_dir = os.path.join(os.path.dirname(base, 'Frameworks')) + site.addsitedir(base) + site.addsitedir(os.path.join(base, 'Python', 'site-packages')) + exe = os.environ.get('CALIBRE_LAUNCH_MODULE', 'calibre.gui2.main') + exe = os.path.join(base, 'Python', 'site-packages', *exe.split('.')) + sys.argv[0] = __file__ = exe + execfile(exe, globals(), globals()) + +_run() diff --git a/installer/osx/py2app/main.c b/installer/osx/py2app/main.c new file mode 100644 index 0000000000..01abe6d3a7 --- /dev/null +++ b/installer/osx/py2app/main.c @@ -0,0 +1,1002 @@ +#include +#include +#include +#include +#include +#include +#include + +/* + 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; +} diff --git a/installer/osx/py2app/main.py b/installer/osx/py2app/main.py new file mode 100644 index 0000000000..42f9c4361f --- /dev/null +++ b/installer/osx/py2app/main.py @@ -0,0 +1,459 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import sys, os, shutil, plistlib, subprocess, glob, zipfile, tempfile, \ + py_compile, stat, operator +abspath, join, basename = os.path.abspath, os.path.join, os.path.basename + +l = {} +exec open('setup.py').read() in l +VERSION = l['VERSION'] +APPNAME = l['APPNAME'] +scripts = l['scripts'] +basenames = l['basenames'] +main_functions = l['main_functions'] +main_modules = l['main_modules'] +LICENSE = open('LICENSE', 'rb').read() +ENV = dict( + FC_CONFIG_DIR='@executable_path/../Resources/fonts', + MAGICK_HOME='@executable_path/../Frameworks/ImageMagick', + PYTHONDONTWRITEBYTECODE='1', + PYTHONIOENCODING='utf-8:replace', + PYTHONPATH='@executable_path/../Resources/Python/site-packages', + PYTHONHOME='@executable_path/../Resources/Python', + ) + +SW = os.environ.get('SW') + +def flipwritable(fn, mode=None): + """ + Flip the writability of a file and return the old mode. Returns None + if the file is already writable. + """ + if os.access(fn, os.W_OK): + return None + old_mode = os.stat(fn).st_mode + os.chmod(fn, stat.S_IWRITE | old_mode) + return old_mode + +STRIPCMD = ['/usr/bin/strip', '-x', '-S', '-'] +def strip_files(files, argv_max=(256 * 1024)): + """ + Strip a list of files + """ + tostrip = [(fn, flipwritable(fn)) for fn in files] + while tostrip: + cmd = list(STRIPCMD) + flips = [] + pathlen = reduce(operator.add, [len(s) + 1 for s in cmd]) + while pathlen < argv_max: + if not tostrip: + break + added, flip = tostrip.pop() + pathlen += len(added) + 1 + cmd.append(added) + flips.append((added, flip)) + else: + cmd.pop() + tostrip.append(flips.pop()) + os.spawnv(os.P_WAIT, cmd[0], cmd) + for args in flips: + flipwritable(*args) + +class Py2App(object): + + FID = '@executable_path/../Frameworks' + + def __init__(self, build_dir): + self.build_dir = build_dir + self.contents_dir = join(self.build_dir, 'Contents') + self.resources_dir = join(self.contents_dir, 'Resources') + self.frameworks_dir = join(self.contents_dir, 'Frameworks') + self.to_strip = [] + + def run(self): + self.create_skeleton() + self.create_plist() + + self.add_python_framework() + self.add_qt_frameworks() + self.add_calibre_plugins() + self.add_podofo() + self.add_poppler() + self.add_libjpeg() + self.add_libpng() + self.add_fontconfig() + self.add_imagemagick() + self.add_plugins() + self.add_qt_plugins() + self.add_ipython() + self.add_misc_libraries() + + self.add_site_packages() + self.add_stdlib() + self.compile_py_modules() + + self.create_console_app() + + self.copy_launcher_and_site() + self.create_exe() + self.strip_files() + + return self.makedmg(self.builddir, APPNAME+'-'+VERSION+'-x86_64') + + def strip_files(self): + strip_files(self.to_strip) + + def create_exe(self): + gcc = os.environ.get('GCC', 'gcc') + base = os.path.dirname(__file__) + out = join(self.contents_dir, 'MacOS', 'calibre') + subprocess.check_call([gcc, '-Wall', '-arch x86_64', join(base, + 'main.c'), '-o', out]) + self.to_strip(out) + + def set_id(self, path_to_lib, new_id): + old_mode = flipwritable(path_to_lib) + subprocess.check_call(['install_name_tool', '-id', new_id, path_to_lib]) + if old_mode is not None: + flipwritable(path_to_lib, old_mode) + + def get_dependencies(self, path_to_lib): + raw = subprocess.Popen(['otool', '-L', path_to_lib], + stdout=subprocess.PIPE).stdout.read() + for line in raw.splitlines(): + if 'compatibility' not in line: + continue + idx = line.find('(') + path = line[:idx].strip() + bname = os.path.basename(path).partition('.')[0] + if bname in path_to_lib: + continue + yield path + + def get_local_dependencies(self, path_to_lib): + for x in self.get_dependencies(path_to_lib): + for y in (SW+'/lib/', '/usr/local/lib/', SW+'/qt/lib/', + SW+'/python/'): + if x.startswith(y): + yield x, x[len(y):] + break + + def change_dep(self, old_dep, new_dep, path_to_lib): + print '\tResolving dependency %s to'%old_dep, new_dep + subprocess.check_call(['install_name_tool', '-change', old_dep, new_dep, + path_to_lib]) + + def fix_dependencies_in_lib(self, path_to_lib): + print '\nFixing dependecies in', path_to_lib + self.to_strip.append(path_to_lib) + old_mode = flipwritable(path_to_lib) + for dep, bname in self.get_local_dependencies(path_to_lib): + ndep = self.FID+'/'+bname + self.change_dep(dep, ndep, path_to_lib) + if list(self.get_local_dependencies(path_to_lib)): + raise Exception('Failed to resolve deps in: '+path_to_lib) + if old_mode is not None: + flipwritable(path_to_lib, old_mode) + + def add_python_framework(self): + src = join(SW, 'python', 'Python.framework') + x = join(self.frameworks_dir, 'Python.framework') + curr = os.path.realpath(join(src, 'Versions', 'Current')) + currd = join(x, 'Versions', basename(curr)) + rd = join(currd, 'Resources') + os.makedirs(rd) + shutil.copy2(join(curr, 'Resources', 'Info.plist'), rd) + shutil.copy2(join(curr, 'Python'), currd) + self.set_id(join(currd, 'Python'), + self.FID+'/Python.framework/Versions/%s/Python'%basename(curr)) + + def add_qt_frameworks(self): + for f in ('QtCore', 'QtGui', 'QtXml', 'QtNetwork', 'QtSvg', 'QtWebkit', + 'phonon'): + self.add_qt_framework(f) + for d in glob.glob(join(SW, 'qt', 'plugins', '*')): + shutil.copytree(d, join(self.contents_dir, 'MacOS', basename(d))) + for l in glob.glob(join(self.contents_dir, 'MacOS', '*/*.dylib')): + self.fix_dependencies_in_lib(l) + x = os.path.relpath(l, join(self.contents_dir, 'MacOS')) + self.set_id(l, '@executable_path/'+x) + + def add_qt_framework(self, f): + libname = f + f = f+'.framework' + src = join(SW, 'qt', 'lib', f) + ignore = shutil.ignore_patterns('Headers', '*.h', 'Headers/*') + dest = join(self.frameworks_dir, f) + shutil.copytree(src, dest, symlinks=True, + ignore=ignore) + lib = os.path.realpath(join(dest, libname)) + rpath = os.path.relpath(lib, self.frameworks_dir) + self.set_id(lib, self.FID+'/'+rpath) + self.fix_dependencies_in_lib(lib) + + def create_skeleton(self): + c = join(self.build_dir, 'Contents') + for x in ('Frameworks', 'MacOS', 'Resources'): + os.makedirs(join(c, x)) + x = 'library.icns' + shutil.copyfile(join('icons', x), join(self.resources_dir, x)) + + def add_calibre_plugins(self): + dest = join(self.frameworks_dir, 'plugins') + os.mkdir(dest) + for f in glob.glob('src/calibre/plugins/*.so'): + shutil.copy2(f, dest) + self.fix_dependencies_in_lib(join(dest, basename(f))) + + + def create_plist(self): + pl = dict( + CFBundleDevelopmentRegion='English', + CFBundleDisplayName=APPNAME, + CFBundleName=APPNAME, + CFBundleIdentifier='net.kovidgoyal.calibre', + CFBundleVersion=VERSION, + CFBundlePackageType='APPL', + CFBundleSignature='????', + CFBundleExecutable='calibre', + LSMinimumSystemVersion='10.5.2', + PyRuntimeLocations=[self.FID+'/Python.framework/Versions/Current/Python'], + LSRequiresNativeExecution=True, + NSAppleScriptEnabled=False, + NSHumanReadableCopyright='Copyright 2008, Kovid Goyal', + CFBundleGetInfoString=('calibre, an E-book management ' + 'application. Visit http://calibre.kovidgoyal.net for details.'), + CFBundleIconFile='library.icns', + LSMultipleInstancesProhibited=True, + LSEnvironment=ENV + ) + plistlib.writePlist(pl, join(self.contents_dir, 'Info.plist')) + + def install_dylib(self, path, set_id=True): + shutil.copy2(path, self.frameworks_dir) + if set_id: + self.set_id(join(self.frameworks_dir, basename(path)), + self.FID+'/'+basename(path)) + self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path))) + + def add_podofo(self): + print '\nAdding PoDoFo' + pdf = join(SW, 'lib', 'libpodofo.0.6.99.dylib') + self.install_dylib(pdf) + + def add_poppler(self): + print '\nAdding poppler' + for x in ('libpoppler.4.dylib', 'libpoppler-qt4.3.dylib'): + self.install_dylib(os.path.join(SW, 'lib', x)) + self.install_dylib(os.path.join(SW, 'bin', 'pdftohtml'), False) + + def add_libjpeg(self): + print '\nAdding libjpeg' + self.install_dylib(os.path.join(SW, 'lib', 'libjpeg.7.dylib')) + + def add_libpng(self): + print '\nAdding libpng' + self.install_dylib(os.path.join(SW, 'lib', 'libpng12.0.dylib')) + + def add_fontconfig(self): + print '\nAdding fontconfig' + for x in ('fontconfig.1', 'freetype.6', 'expat.1'): + src = os.path.join(SW, 'lib', 'lib'+x+'.dylib') + self.install_dylib(src) + dst = os.path.join(self.resource_dir, 'fonts') + if os.path.exists(dst): + shutil.rmtree(dst) + src = os.path.join(SW, 'etc', 'fonts') + shutil.copytree(src, dst, symlinks=False) + fc = os.path.join(dst, 'fonts.conf') + raw = open(fc, 'rb').read() + raw = raw.replace('/usr/share/fonts', '''\ + /Library/Fonts + /Network/Library/Fonts + /System/Library/Fonts + /usr/X11R6/lib/X11/fonts + /usr/share/fonts + /var/root/Library/Fonts + /usr/share/fonts + ''') + open(fc, 'wb').write(raw) + + def add_imagemagick(self): + print '\nAdding ImageMagick' + for x in ('Wand', 'Core'): + self.install_dylib(os.path.join(SW, 'lib', 'libMagick%s.2.dylib'%x)) + idir = glob.glob(os.path.join(SW, 'lib', 'ImageMagick-*'))[-1] + dest = os.path.join(self.frameworks_dir, 'ImageMagick', 'lib') + if not os.path.exists(dest): + os.makedirs(dest) + dest = os.path.join(dest, os.path.basename(idir)) + if os.path.exists(dest): + shutil.rmtree(dest) + shutil.copytree(idir, dest, True) + + def add_misc_libraries(self): + for x in ('usb', 'unrar'): + print '\nAdding', x + shutil.copy2(join(SW, 'lib', 'lib%s.dylib'%x), self.frameworks_dir) + + def add_site_packages(self): + print '\nAdding site-packages' + self.site_packages = join(self.resources_dir, 'Python', 'site-packages') + os.makedirs(self.site_packages) + paths = reversed(map(abspath, [x for x in sys.path if x.startswith('/')])) + upaths = [] + for x in paths: + if x not in upaths: + upaths.append(x) + for x in upaths: + if x.endswith('/PIL') or 'site-packages' not in x: + continue + tdir = None + try: + if not os.path.isdir(x): + try: + zf = zipfile.ZipFile(x) + except: + print "WARNING:", x, 'is neither a directory nor a zipfile' + continue + tdir = tempfile.mkdtemp() + zf.extract_all(tdir) + x = tdir + self.add_modules_from_dir(x) + self.add_packages_from_dir(x) + finally: + if tdir is not None: + shutil.rmtree(tdir) + self.remove_bytecode(join(self.resources_dir, 'Python', 'site-packages')) + + def add_modules_from_dir(self, src): + for x in glob.glob(join(src, '*.py'))+glob.glob(join(src, '*.so')): + dest = join(self.site_packages, basename(x)) + shutil.copy2(x, self.site_packages) + if x.endswith('.so'): + self.fix_dependencies_in_lib(x) + + def add_packages_from_dir(self, src): + for x in os.listdir(src): + x = join(src, x) + if os.path.isdir(x) and os.path.exists(join(x, '__init__.py')): + if self.filter_package(basename(x)): + continue + self.add_package_dir(x) + + def add_package_dir(self, x, dest=None): + def ignore(root, files): + ans = [] + for y in files: + if os.path.splitext(y) in ('.py', '.so'): + continue + ans.append(y) + return ans + if dest is None: + dest = self.site_packages + dest = join(dest, basename(x)) + shutil.copytree(x, dest, symlinks=True, ignore=ignore) + self.postprocess_package(x, dest) + + def filter_package(self, name): + return name in ('Cython', 'modulegraph', 'macholib', 'py2app', + 'bdist_mpkg', 'altgraph') + + def postprocess_package(self, src_path, dest_path): + pass + + def add_stdlib(self): + print '\nAdding python stdlib' + src = join(SW, '/python/Python.framework/Versions/Current/lib/python') + src += '.'.join(sys.version_info[:2]) + dest = join(self.resources_dir, 'Python', 'lib', 'python') + dest += '.'.join(sys.version_info[:2]) + for x in os.listdir(src): + if x in ('site-packages', 'config', 'test', 'lib2to3', 'lib-tk', + 'lib-old', 'idlelib', 'plat-mac', 'plat-darwin', 'site.py'): + continue + if os.path.isdir(x): + self.add_package_dir(join(src, x), dest) + elif os.path.splitext(x) in ('.so', '.py'): + shutil.copy2(join(src, x), dest) + dest = join(dest, basename(x)) + if dest.endswith('.so'): + self.fix_dependencies_in_lib(dest) + self.remove_bytecode(join(self.resources_dir, 'Python', 'lib')) + + def remove_bytecode(self, dest): + for x in os.walk(dest): + root = x[0] + for f in x[-1]: + if os.path.splitext(f) in ('.pyc', '.pyo'): + os.remove(join(root, f)) + + def compile_py_modules(self): + print '\nCompiling Python modules' + base = join(self.resources_dir, 'Python') + for x in os.walk(base): + root = x[0] + for f in x[-1]: + if f.endswith('.py'): + y = join(root, f) + rel = os.path.relpath(y, base) + try: + py_compile.compile(y, dfile=rel, doraise=True) + os.remove(y) + except: + print 'WARNING: Failed to byte-compile', y + + def create_console_app(self): + print '\nCreating console.app' + cc_dir = os.path.join(self.contents_dir, 'console.app', 'Contents') + os.makedirs(cc_dir) + for x in os.listdir(self.contents_dir): + if x == 'console.app': + continue + if x == 'Info.plist': + plist = plistlib.readPlist(join(self.contents_dir, x)) + plist['LSUIElement'] = '1' + plistlib.writePlist(plist, join(cc_dir, x)) + else: + os.symlink(join('../..', x), + join(cc_dir, x)) + + def copy_launcher_and_site(self): + base = os.path.dirname(__file__) + for x in ('launcher', 'site'): + shutil.copy2(join(base, x+'.py'), self.resources_dir) + + def makedmg(self, d, volname, + destdir='dist', + internet_enable=True, + format='UDBZ'): + ''' Copy a directory d into a dmg named volname ''' + dmg = os.path.join(destdir, volname+'.dmg') + if os.path.exists(dmg): + os.unlink(dmg) + subprocess.check_call(['/usr/bin/hdiutil', 'create', '-srcfolder', os.path.abspath(d), + '-volname', volname, '-format', format, dmg]) + if internet_enable: + subprocess.check_call(['/usr/bin/hdiutil', 'internet-enable', '-yes', dmg]) + return dmg + + + +def main(): + build_dir = abspath(join('build', APPNAME+'.app')) + if os.path.exists(build_dir): + shutil.rmtree(build_dir) + os.makedirs(build_dir) + py2app = Py2App(build_dir) + py2app.run() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/installer/osx/py2app/site.py b/installer/osx/py2app/site.py new file mode 100644 index 0000000000..4c98a871ca --- /dev/null +++ b/installer/osx/py2app/site.py @@ -0,0 +1,106 @@ +""" +Append module search paths for third-party packages to sys.path. + +This is stripped down and customized for use in py2app applications +""" + +import sys +import os + +def makepath(*paths): + dir = os.path.abspath(os.path.join(*paths)) + return dir, os.path.normcase(dir) + +for m in sys.modules.values(): + f = getattr(m, '__file__', None) + if isinstance(f, basestring) and os.path.exists(f): + m.__file__ = os.path.abspath(m.__file__) +del m + +# This ensures that the initial path provided by the interpreter contains +# only absolute pathnames, even if we're running from the build directory. +L = [] +_dirs_in_sys_path = {} +dir = dircase = None # sys.path may be empty at this point +for dir in sys.path: + # Filter out duplicate paths (on case-insensitive file systems also + # if they only differ in case); turn relative paths into absolute + # paths. + dir, dircase = makepath(dir) + if not dircase in _dirs_in_sys_path: + L.append(dir) + _dirs_in_sys_path[dircase] = 1 +sys.path[:] = L +del dir, dircase, L +_dirs_in_sys_path = None + +def _init_pathinfo(): + global _dirs_in_sys_path + _dirs_in_sys_path = d = {} + for dir in sys.path: + if dir and not os.path.isdir(dir): + continue + dir, dircase = makepath(dir) + d[dircase] = 1 + +def addsitedir(sitedir): + global _dirs_in_sys_path + if _dirs_in_sys_path is None: + _init_pathinfo() + reset = 1 + else: + reset = 0 + sitedir, sitedircase = makepath(sitedir) + if not sitedircase in _dirs_in_sys_path: + sys.path.append(sitedir) # Add path component + try: + names = os.listdir(sitedir) + except os.error: + return + names.sort() + for name in names: + if name[-4:] == os.extsep + "pth": + addpackage(sitedir, name) + if reset: + _dirs_in_sys_path = None + +def addpackage(sitedir, name): + global _dirs_in_sys_path + if _dirs_in_sys_path is None: + _init_pathinfo() + reset = 1 + else: + reset = 0 + fullname = os.path.join(sitedir, name) + try: + f = open(fullname) + except IOError: + return + while 1: + dir = f.readline() + if not dir: + break + if dir[0] == '#': + continue + if dir.startswith("import"): + exec dir + continue + if dir[-1] == '\n': + dir = dir[:-1] + dir, dircase = makepath(sitedir, dir) + if not dircase in _dirs_in_sys_path and os.path.exists(dir): + sys.path.append(dir) + _dirs_in_sys_path[dircase] = 1 + if reset: + _dirs_in_sys_path = None + + +sys.setdefaultencoding('utf-8') + +# +# Remove sys.setdefaultencoding() so that users cannot change the +# encoding after initialization. The test for presence is needed when +# this module is run as a script, because this code is executed twice. +# +if hasattr(sys, "setdefaultencoding"): + del sys.setdefaultencoding diff --git a/pyqtdistutils.py b/pyqtdistutils.py index 0150cd8684..550bf46da7 100644 --- a/pyqtdistutils.py +++ b/pyqtdistutils.py @@ -23,13 +23,24 @@ elif find_executable('qmake'): QMAKE = find_executable('qmake') QMAKE = os.environ.get('QMAKE', QMAKE) WINDOWS_PYTHON = ['C:/Python26/libs'] -OSX_SDK = '/Developer/SDKs/MacOSX10.4u.sdk' +OSX_SDK = '/Developer/SDKs/MacOSX10.5.sdk' +if not os.path.exists(OSX_SDK): + OSX_SDK = '/Developer/SDKs/MacOSX10.4u.sdk' + +leopard_build = '10.5' in OSX_SDK def replace_suffix(path, new_suffix): return os.path.splitext(path)[0] + new_suffix class Extension(_Extension): - pass + + def __init__(self, *args, **kwargs): + if leopard_build: + prev = kwargs.get('extra_compile_args', []) + prev.extend(['-arch', 'ppc64', '-arch', 'x86_64']) + kwargs['extra_compile_args'] = prev + _Extension.__init__(self, *args, **kwargs) + if iswindows: from distutils import msvc9compiler @@ -38,7 +49,6 @@ if iswindows: nmake = msvc.find_exe('nmake.exe') rc = msvc.find_exe('rc.exe') - class PyQtExtension(Extension): def __init__(self, name, sources, sip_sources, **kw): @@ -66,6 +76,7 @@ class build_ext(_build_ext): cwd = os.getcwd() sources = map(os.path.abspath, ext.sources) os.chdir(bdir) + archs = 'x86_64 ppc64' if leopard_build else 'x86 ppc' try: headers = set([f for f in sources if f.endswith('.h')]) sources = set(sources) - headers @@ -76,10 +87,13 @@ TEMPLATE = lib HEADERS = %s SOURCES = %s VERSION = 1.0.0 -CONFIG += x86 ppc -'''%(name, ' '.join(headers), ' '.join(sources)) +CONFIG += %s +'''%(name, ' '.join(headers), ' '.join(sources), archs) open(name+'.pro', 'wb').write(pro) self.spawn([QMAKE, '-o', 'Makefile.qt', name+'.pro']) + if leopard_build: + raw = open('Makefile.qt', 'rb').read() + open('Makefile.qt', 'wb').write(raw.replace('ppc64', 'x86_64')) self.make('Makefile.qt') pat = 'release\\*.obj' if iswindows else '*.o' return map(os.path.abspath, glob.glob(pat)) @@ -116,6 +130,13 @@ CONFIG += x86 ppc makefile.generate() cwd = os.getcwd() os.chdir(bdir) + if leopard_build: + mf = 'Makefile.pyqt' + raw = open(mf, 'rb').read() + raw = raw.replace('ppc64 x86_64', 'x86_64') + for x in ('ppc64', 'ppc', 'i386'): + raw = raw.replace(x, 'x86_64') + open(mf, 'wb').write(raw) try: self.make('Makefile.pyqt') finally: diff --git a/setup.py b/setup.py index ed372920de..27adc64016 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,9 @@ if __name__ == '__main__': tag_release, upload_demo, build_linux, build_windows, \ build_osx, upload_installers, upload_user_manual, \ upload_to_pypi, stage3, stage2, stage1, upload, \ - upload_rss, betas, build_linux32, build_linux64 + upload_rss, betas, build_linux32, build_linux64, \ + build_osx64 + resources.SCRIPTS = list(basenames['console']+basenames['gui']) entry_points['console_scripts'].append( 'calibre_postinstall = calibre.linux:post_install') @@ -147,14 +149,19 @@ if __name__ == '__main__': r'C:\cygwin\home\kovid\fontconfig\lib' if iswindows else \ '/Users/kovid/fontconfig/lib' - + fc_inc = os.environ.get('FC_INC_DIR', fc_inc) + fc_lib = os.environ.get('FC_LIB_DIR', fc_lib) + if not os.path.exists(os.path.join(fc_inc, 'fontconfig.h')): + print 'ERROR: fontconfig not found on your system.', + print 'Use the FC_INC_DIR and FC_LIB_DIR environment variables.' + raise SystemExit(1) ext_modules = optional + [ Extension('calibre.plugins.fontconfig', sources = ['src/calibre/utils/fonts/fontconfig.c'], - include_dirs = [os.environ.get('FC_INC_DIR', fc_inc)], + include_dirs = [fc_inc], libraries=['fontconfig'], - library_dirs=[os.environ.get('FC_LIB_DIR', fc_lib)]), + library_dirs=[fc_lib]), Extension('calibre.plugins.lzx', sources=['src/calibre/utils/lzx/lzxmodule.c', @@ -262,6 +269,7 @@ if __name__ == '__main__': 'build_linux64' : build_linux64, 'build_windows' : build_windows, 'build_osx' : build_osx, + 'build_osx64' : build_osx64, 'upload_installers': upload_installers, 'upload_user_manual': upload_user_manual, 'upload_to_pypi': upload_to_pypi, diff --git a/src/calibre/devices/usbobserver/usbobserver.c b/src/calibre/devices/usbobserver/usbobserver.c index 051901f849..a6238f1005 100644 --- a/src/calibre/devices/usbobserver/usbobserver.c +++ b/src/calibre/devices/usbobserver/usbobserver.c @@ -26,7 +26,7 @@ #include #include - +#include static PyObject * usbobserver_get_usb_devices(PyObject *self, PyObject *args) { @@ -67,7 +67,7 @@ usbobserver_get_usb_devices(PyObject *self, PyObject *args) { return NULL; } - while (usbDevice = IOIteratorNext(iter)) { + while ((usbDevice = IOIteratorNext(iter))) { plugInInterface = NULL; dev = NULL; //Create an intermediate plugin kr = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score); diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py index 6cc77725e8..df1ed65025 100644 --- a/src/calibre/gui2/dialogs/config/__init__.py +++ b/src/calibre/gui2/dialogs/config/__init__.py @@ -10,7 +10,7 @@ from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \ QDialogButtonBox, QTabWidget, QBrush, QLineEdit, \ QProgressDialog -from calibre.constants import islinux, iswindows +from calibre.constants import islinux, iswindows, isosx from calibre.gui2.dialogs.config.config_ui import Ui_Dialog from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \ ALL_COLUMNS, NONE, info_dialog, choose_files, \ @@ -450,6 +450,9 @@ class ConfigDialog(QDialog, Ui_Dialog): self.connect(self.remove_plugin, SIGNAL('clicked()'), lambda : self.modify_plugin(op='remove')) self.connect(self.button_plugin_browse, SIGNAL('clicked()'), self.find_plugin) self.connect(self.button_plugin_add, SIGNAL('clicked()'), self.add_plugin) + self.connect(self.button_osx_symlinks, SIGNAL('clicked()'), + self.create_symlinks) + self.button_osx_symlinks.setVisible(isosx) self.separate_cover_flow.setChecked(config['separate_cover_flow']) self.setup_email_page() self.category_view.setCurrentIndex(self.category_view.model().index(0)) @@ -458,6 +461,13 @@ class ConfigDialog(QDialog, Ui_Dialog): self.delete_news.setEnabled) self.setup_conversion_options() + def create_symlinks(self): + from calibre.utils.osx_symlinks import create_symlinks + loc, paths = create_symlinks() + info_dialog(self, _('Command line tools installed'), + _('Command line tools installed in')+' '+loc, + det_msg=paths, show=True) + def setup_conversion_options(self): self.conversion_options = ConfigTabs(self) self.stackedWidget.insertWidget(2, self.conversion_options) diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui index e38b16cdf5..62979c8a5c 100644 --- a/src/calibre/gui2/dialogs/config/config.ui +++ b/src/calibre/gui2/dialogs/config/config.ui @@ -677,28 +677,27 @@ - - + - + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - + + + Qt::Horizontal + + + + 154 + 20 + + + + + + @@ -710,20 +709,27 @@ - - - Qt::Horizontal + + + &Install command line tools - - - 40 - 20 - - - + + + + + Qt::Horizontal + + + + 153 + 20 + + + + diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index c3914bdb81..ea0241d46e 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -107,6 +107,11 @@ Now you should be able to access your books on your iPhone by opening Stanza and Replace ``192.168.1.2`` with the local IP address of the computer running |app|. If you have changed the port the |app| content server is running on, you will have to change ``8080`` as well to the new port. The local IP address is the IP address you computer is assigned on your home network. A quick Google search will tell you how to find out your local IP address. +I get the error message "Failed to start content server: Port 8080 not free on '0.0.0.0'"? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The most likely cause of this is your antivirus program. Try temporarily disabling it and see if it does the trick. + Why is my device not detected in linux? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -216,7 +221,7 @@ Your antivirus program is wrong. |app| is a completely open source product. You How do I use purchased EPUB books with |app|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Most purchased EPUB books have `DRM `_. This prevents |app| from opening them. You can still use |app| to store and transfer them to your SONY Reader. First, you must authorize your reader on a windows machine with Adobe DIgital Editions. Once this is done, EPUB books transferred with |app| will work fine on your reader. Sometimes, the EPUB file itself is corrupted, in which case you should notify the e-book vendor. +Most purchased EPUB books have `DRM `_. This prevents |app| from opening them. You can still use |app| to store and transfer them to your SONY Reader. First, you must authorize your reader on a windows machine with Adobe Digital Editions. Once this is done, EPUB books transferred with |app| will work fine on your reader. Sometimes, the EPUB file itself is corrupted, in which case you should notify the e-book vendor. I want some feature added to |app|. What can I do? diff --git a/src/calibre/trac/plugins/download.py b/src/calibre/trac/plugins/download.py index 61cf330a78..28c947d06c 100644 --- a/src/calibre/trac/plugins/download.py +++ b/src/calibre/trac/plugins/download.py @@ -185,7 +185,7 @@ else: note=Markup(\ u'''
    -
  1. Before trying to use the command line tools, you must run the app at least once. This will ask you for you password and then setup the symbolic links for the command line tools.
  2. +
  3. To install the command line tools, go to Preferences->Advanced
  4. The app cannot be run from within the dmg. You must drag it to a folder on your filesystem (The Desktop, Applications, wherever).
  5. In order for localization of the user interface in your language, select your language in the preferences (by pressing u\2318+P) and select your language.
diff --git a/src/calibre/utils/PythonMagickWand.py b/src/calibre/utils/PythonMagickWand.py index f650d6dc4e..6a3fbd654c 100644 --- a/src/calibre/utils/PythonMagickWand.py +++ b/src/calibre/utils/PythonMagickWand.py @@ -73,8 +73,13 @@ isosx = 'darwin' in sys.platform isfrozen = getattr(sys, 'frozen', False) if isosx: - _lib = os.path.join(getattr(sys, 'frameworks_dir'), 'ImageMagick', 'libMagickWand.dylib') \ - if isfrozen else util.find_library('Wand') + if isfrozen: + fd = getattr(sys, 'frameworks_dir') + _lib = os.path.join(fd, 'libMagickWand.2.dylib') + if not os.path.exists(_lib): + _lib = os.path.join(fd, 'ImageMagick', 'libMagickWand.dylib') + else: + _lib = util.find_library('Wand') elif iswindows: _lib = os.path.join(os.path.dirname(sys.executable), 'CORE_RL_wand_.dll') \ if isfrozen else 'CORE_RL_wand_' diff --git a/src/calibre/utils/fonts/fontconfig.c b/src/calibre/utils/fonts/fontconfig.c index 54ff22d915..d8f7190798 100644 --- a/src/calibre/utils/fonts/fontconfig.c +++ b/src/calibre/utils/fonts/fontconfig.c @@ -18,7 +18,7 @@ static PyObject * fontconfig_initialize(PyObject *self, PyObject *args) { - char *path; + FcChar8 *path; FcBool ok; FcConfig *config; PyThreadState *_save; @@ -44,7 +44,7 @@ fontconfig_initialize(PyObject *self, PyObject *args) { Py_RETURN_FALSE; } -static +static void fontconfig_cleanup_find(FcPattern *p, FcObjectSet *oset, FcFontSet *fs) { if (p != NULL) FcPatternDestroy(p); if (oset != NULL) FcObjectSetDestroy(oset); @@ -105,13 +105,13 @@ fontconfig_find_font_families(PyObject *self, PyObject *args) { ext = PyBytes_AS_STRING(PySequence_ITEM(exts, j)); extlen = PyBytes_GET_SIZE(PySequence_ITEM(exts, j)); ok = flen > extlen && extlen > 0 && - PyOS_strnicmp(ext, v.u.s + (flen - extlen), extlen) == 0; + PyOS_strnicmp(ext, ((char *)v.u.s) + (flen - extlen), extlen) == 0; } if (ok) { if (FcPatternGet(temp, FC_FAMILY, 0, &w) != FcResultMatch) continue; if (w.type != FcTypeString) continue; - t = PyString_FromString(w.u.s); + t = PyString_FromString((char *)w.u.s); if (t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } if (PyList_Append(ans, t) != 0) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } @@ -169,16 +169,16 @@ fontconfig_files_for_family(PyObject *self, PyObject *args) { temp = PyTuple_New(6); if(temp == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } - t = PyBytes_FromString(fullname.u.s); + t = PyBytes_FromString((char *)fullname.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 0, t); - t = PyBytes_FromString(file.u.s); + t = PyBytes_FromString((char *)file.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 1, t); - t = PyBytes_FromString(style.u.s); + t = PyBytes_FromString((char *)style.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 2, t); - t = PyBytes_FromString(family2.u.s); + t = PyBytes_FromString((char *)family2.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 3, t); t = PyInt_FromLong((long)weight.u.i); @@ -197,7 +197,7 @@ fontconfig_files_for_family(PyObject *self, PyObject *args) { static PyObject * fontconfig_match(PyObject *self, PyObject *args) { - char *namespec; int i; + FcChar8 *namespec; int i; FcPattern *pat, *tp; FcObjectSet *oset; FcFontSet *fs, *fs2; @@ -255,16 +255,16 @@ fontconfig_match(PyObject *self, PyObject *args) { temp = PyTuple_New(6); if(temp == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } - t = PyBytes_FromString(fullname.u.s); + t = PyBytes_FromString((char *)fullname.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 0, t); - t = PyBytes_FromString(file.u.s); + t = PyBytes_FromString((char *)file.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 1, t); - t = PyBytes_FromString(style.u.s); + t = PyBytes_FromString((char *)style.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 2, t); - t = PyBytes_FromString(family.u.s); + t = PyBytes_FromString((char *)family.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 3, t); t = PyInt_FromLong((long)weight.u.i); diff --git a/src/calibre/utils/osx_symlinks.py b/src/calibre/utils/osx_symlinks.py new file mode 100644 index 0000000000..36fa0e7b45 --- /dev/null +++ b/src/calibre/utils/osx_symlinks.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +AUTHTOOL="""#!%s +import os +scripts = %s +links = %s +os.setuid(0) +for s, l in zip(scripts, links): + if os.path.lexists(l): + os.remove(l) + print 'Creating link:', l, '->', s + omask = os.umask(022) + os.symlink(s, l) + os.umask(omask) +""" + +DEST_PATH = '/usr/bin' + +def create_symlinks(): + import os, tempfile, traceback, sys + from Authorization import Authorization, kAuthorizationFlagDestroyRights + from calibre.resources import scripts + + + resources_path = os.environ['RESOURCEPATH'] + links = [os.path.join(DEST_PATH, i) for i in scripts] + scripts = [os.path.join(resources_path, 'loaders', i) for i in scripts] + + bad = False + for s, l in zip(scripts, links): + if os.path.exists(l) and os.path.exists(os.path.realpath(l)): + continue + bad = True + break + if bad: + auth = Authorization(destroyflags=(kAuthorizationFlagDestroyRights,)) + fd, name = tempfile.mkstemp('.py') + os.write(fd, AUTHTOOL % (sys.executable, repr(scripts), repr(links))) + os.close(fd) + os.chmod(name, 0700) + try: + pipe = auth.executeWithPrivileges(sys.executable, name) + sys.stdout.write(pipe.read()) + pipe.close() + except: + traceback.print_exc() + finally: + os.unlink(name) + + return DEST_PATH, links + diff --git a/src/calibre/utils/podofo/podofo.cpp b/src/calibre/utils/podofo/podofo.cpp index 188ed0c5dc..8766b709c9 100644 --- a/src/calibre/utils/podofo/podofo.cpp +++ b/src/calibre/utils/podofo/podofo.cpp @@ -144,7 +144,6 @@ podofo_PDFDoc_version_getter(podofo_PDFDoc *self, void *closure) { static PyObject * podofo_PDFDoc_extract_first_page(podofo_PDFDoc *self, PyObject *args, PyObject *kwargs) { - int i, num_pages; try { while (self->doc->GetPageCount() > 1) self->doc->GetPagesTree()->DeletePage(1); } catch(const PdfError & err) { diff --git a/upload.py b/upload.py index a7146008de..6e3c5fa099 100644 --- a/upload.py +++ b/upload.py @@ -167,7 +167,7 @@ class resources(OptionlessCommand): translations_found = True break if not translations_found: - print 'WARNING: Could not find Qt transations' + print 'WARNING: Could not find Qt translations' return data def get_static_resources(self): @@ -216,7 +216,8 @@ class resources(OptionlessCommand): static, smax = self.get_static_resources() recipes, rmax = self.get_recipes() hyphenate, hmax = self.get_hyphenate() - amax = max(rmax, smax, hmax, os.stat(__file__).st_mtime) + lmax = os.stat(os.path.join('src', 'calibre', 'linux.py')).st_mtime + amax = max(rmax, smax, hmax, lmax, os.stat(__file__).st_mtime) if newer([dest], RESOURCES.values()) or os.stat(dest).st_mtime < amax: print 'Compiling resources...' with open(dest, 'wb') as f: @@ -226,6 +227,7 @@ class resources(OptionlessCommand): f.write('server_resources = %s\n\n'%repr(static)) f.write('recipes = %s\n\n'%repr(recipes)) f.write('hyphenate = %s\n\n'%repr(hyphenate)) + f.write('scripts = %r\n\n'%self.SCRIPTS) f.write('build_time = "%s"\n\n'%time.strftime('%d %m %Y %H%M%S')) else: print 'Resources are up to date' @@ -504,6 +506,8 @@ class VMInstaller(OptionlessCommand): user_options = [('dont-shutdown', 'd', 'Dont shutdown VM after build')] boolean_options = ['dont-shutdown'] EXTRA_SLEEP = 5 + BUILD_CMD = 'ssh -t %s bash build-calibre' + INIT_CMD = '' def initialize_options(self): self.dont_shutdown = False @@ -511,6 +515,7 @@ class VMInstaller(OptionlessCommand): BUILD_SCRIPT = textwrap.dedent('''\ #!/bin/bash export CALIBRE_BUILDBOT=1 + %%s cd ~/build && \ rsync -avz --exclude src/calibre/plugins \ --exclude calibre/src/calibre.egg-info --exclude docs \ @@ -519,12 +524,13 @@ class VMInstaller(OptionlessCommand): rsync://%(host)s/work/%(project)s . && \ cd %(project)s && \ %%s && \ - rm -rf build/* dist/* && \ + rm -rf build/* dist/* src/calibre/plugins/* && \ %%s %%s '''%dict(host=HOST, project=__appname__)) def get_build_script(self, subs): - return self.BUILD_SCRIPT%subs + subs = [self.INIT_CMD]+list(subs) + return self.BUILD_SCRIPT%tuple(subs) def vmware_started(self): return 'started' in subprocess.Popen('/etc/init.d/vmware status', shell=True, stdout=subprocess.PIPE).stdout.read() @@ -562,16 +568,7 @@ class VMInstaller(OptionlessCommand): time.sleep(self.EXTRA_SLEEP) print 'Trying to SSH into VM' check_call(('scp', t.name, ssh_host+':build-calibre')) - check_call('ssh -t %s bash build-calibre'%ssh_host, shell=True) - -class KVMInstaller(VMInstaller): - - def run_vm(self): - self.stop_vmware() - check_call('sudo modprobe kvm-intel', shell=True) - self.__p = Popen(self.VM) - - + check_call(self.BUILD_CMD%ssh_host, shell=True) class build_linux32(VMInstaller): @@ -582,7 +579,7 @@ class build_linux32(VMInstaller): installer = installer_name('tar.bz2').replace('x86_64', 'i686') self.start_vm('gentoo32_build', ('sudo python setup.py develop && sudo chown -R kovid:users *', 'python', 'installer/linux/freeze.py')) - check_call(('scp', 'gentoo32_build:build/calibre/dist/*.dmg', 'dist')) + check_call(('scp', 'gentoo32_build:build/calibre/dist/*.bz2', 'dist')) if not os.path.exists(installer): raise Exception('Failed to build installer '+installer) if not self.dont_shutdown: @@ -624,22 +621,44 @@ class build_windows(VMInstaller): class build_osx(VMInstaller): description = 'Build OS X app bundle' VM = '/vmware/bin/tiger_build' + FREEZE_SCRIPT = 'installer/osx/freeze.py' + VM_NAME = 'tiger_build' + PYTHON = '/Library/Frameworks/Python.framework/Versions/Current/bin/python' + DEVELOP = 'sudo %s setup.py develop' def get_build_script(self, subs): - return (self.BUILD_SCRIPT%subs).replace('rm ', 'sudo rm ') + return VMInstaller.get_build_script(self, subs).replace('rm ', 'sudo rm ') + + def installer_name(self): + return installer_name('dmg') def run(self): - installer = installer_name('dmg') - python = '/Library/Frameworks/Python.framework/Versions/Current/bin/python' - self.start_vm('tiger_build', ('sudo %s setup.py develop'%python, python, - 'installer/osx/freeze.py')) - check_call(('scp', 'tiger_build:build/calibre/dist/*.dmg', 'dist')) + installer = self.installer_name() + python = self.PYTHON + self.start_vm(self.VM_NAME, (self.DEVELOP%python, python, + self.FREEZE_SCRIPT)) + check_call(('scp', self.VM_NAME+':build/calibre/dist/*.dmg', 'dist')) if not os.path.exists(installer): raise Exception('Failed to build installer '+installer) if not self.dont_shutdown: - Popen(('ssh', 'tiger_build', 'sudo', '/sbin/shutdown', '-h', 'now')) + Popen(('ssh', self.VM_NAME, 'sudo', '/sbin/shutdown', '-h', 'now')) return os.path.basename(installer) +class build_osx64(build_osx): + + description = 'Build OS X 64-bit app bundle' + VM = '/vmware/bin/leopard_build' + FREEZE_SCRIPT = 'installer/osx/py2app/main.py' + VM_NAME = 'leopard_build' + PYTHON = '/sw/bin/python -OO' + DEVELOP = '%s setup.py develop' + BUILD_CMD = 'ssh -t %s bash --login build-calibre' + INIT_CMD = 'source ~/.profile' + + def installer_name(self): + return installer_name('dmg').replace('.dmg', '-x86_64.dmg') + + class upload_installers(OptionlessCommand): description = 'Upload any installers present in dist/' def curl_list_dir(self, url=MOBILEREAD, listonly=1):