mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-22 06:50:47 -04:00
426 lines
18 KiB
Python
426 lines
18 KiB
Python
#!/usr/bin/env python
|
|
__license__ = 'GPL v3'
|
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|
''' Create an OSX installer '''
|
|
|
|
import sys, re, os, shutil, subprocess, stat, glob, zipfile, plistlib
|
|
from setup import __version__ as VERSION, __appname__ as APPNAME, SRC, Command, \
|
|
scripts, basenames, functions as main_functions, modules as main_modules
|
|
|
|
try:
|
|
from setuptools import setup
|
|
setup
|
|
except:
|
|
class setup:
|
|
pass
|
|
|
|
try:
|
|
from py2app.build_app import py2app
|
|
from modulegraph.find_modules import find_modules
|
|
py2app
|
|
except ImportError:
|
|
py2app = object
|
|
|
|
PYTHON = '/Library/Frameworks/Python.framework/Versions/Current/bin/python'
|
|
|
|
info = warn = None
|
|
|
|
class OSX32_Freeze(Command):
|
|
|
|
description = 'Freeze OSX calibre installation'
|
|
|
|
def run(self, opts):
|
|
global info, warn
|
|
info, warn = self.info, self.warn
|
|
main()
|
|
|
|
|
|
class BuildAPP(py2app):
|
|
QT_PREFIX = '/Volumes/sw/qt'
|
|
LOADER_TEMPLATE = \
|
|
r'''#!/usr/bin/env python
|
|
import os, sys, glob
|
|
path = os.path.abspath(os.path.realpath(__file__))
|
|
dirpath = os.path.dirname(path)
|
|
name = os.path.basename(path)
|
|
base_dir = os.path.dirname(os.path.dirname(dirpath))
|
|
resources_dir = os.path.join(base_dir, 'Resources')
|
|
frameworks_dir = os.path.join(base_dir, 'Frameworks')
|
|
extensions_dir = os.path.join(frameworks_dir, 'plugins')
|
|
r_dir = os.path.join(resources_dir, 'resources')
|
|
base_name = os.path.splitext(name)[0]
|
|
python = os.path.join(base_dir, 'MacOS', 'python')
|
|
qt_plugins = os.path.join(os.path.realpath(base_dir), 'MacOS')
|
|
loader_path = os.path.join(dirpath, base_name+'.py')
|
|
loader = open(loader_path, 'w')
|
|
site_packages = glob.glob(resources_dir+'/lib/python*/site-packages.zip')[0]
|
|
devf = os.environ.get('CALIBRE_DEVELOP_FROM', None)
|
|
do_devf = devf and os.path.exists(devf)
|
|
if do_devf:
|
|
devf = os.path.abspath(devf)
|
|
print >>loader, 'import sys'
|
|
print >>loader, 'sys.argv[0] =', repr(os.path.basename(path))
|
|
print >>loader, 'if', repr(dirpath), 'in sys.path: sys.path.remove(', repr(dirpath), ')'
|
|
print >>loader, 'sys.path.append(', repr(site_packages), ')'
|
|
if do_devf:
|
|
print >>loader, 'sys.path.insert(0, '+repr(devf)+')'
|
|
print >>loader, 'sys.frozen = "macosx_app"'
|
|
print >>loader, 'sys.frameworks_dir =', repr(frameworks_dir)
|
|
print >>loader, 'sys.extensions_location =', repr(extensions_dir)
|
|
print >>loader, 'sys.resources_location =', repr(r_dir)
|
|
print >>loader, 'import os'
|
|
print >>loader, 'from %(module)s import %(function)s'
|
|
print >>loader, '%(function)s()'
|
|
loader.close()
|
|
os.chmod(loader_path, 0700)
|
|
os.environ['PYTHONHOME'] = resources_dir
|
|
os.environ['FONTCONFIG_PATH'] = os.path.join(resources_dir, 'fonts')
|
|
os.environ['MAGICK_HOME'] = os.path.join(frameworks_dir, 'ImageMagick')
|
|
os.environ['DYLD_LIBRARY_PATH'] = os.path.join(frameworks_dir, 'ImageMagick', 'lib')
|
|
os.environ['QT_PLUGIN_PATH'] = qt_plugins
|
|
args = [path, loader_path] + sys.argv[1:]
|
|
os.execv(python, args)
|
|
'''
|
|
|
|
def get_modulefinder(self):
|
|
if self.debug_modulegraph:
|
|
debug = 4
|
|
else:
|
|
debug = 0
|
|
return find_modules(
|
|
scripts=scripts['console'] + scripts['gui'],
|
|
includes=list(self.includes) + main_modules['console'],
|
|
packages=self.packages,
|
|
excludes=self.excludes,
|
|
debug=debug)
|
|
|
|
@classmethod
|
|
def makedmg(cls, d, volname,
|
|
destdir='dist',
|
|
internet_enable=True,
|
|
format='UDBZ'):
|
|
''' Copy a directory d into a dmg named volname '''
|
|
if not os.path.exists(destdir):
|
|
os.makedirs(destdir)
|
|
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
|
|
|
|
@classmethod
|
|
def qt_dependencies(cls, path):
|
|
pipe = subprocess.Popen('/usr/bin/otool -L '+path, shell=True, stdout=subprocess.PIPE).stdout
|
|
deps = []
|
|
for l in pipe.readlines():
|
|
match = re.search(r'(.*)\(', l)
|
|
if not match:
|
|
continue
|
|
lib = match.group(1).strip()
|
|
if lib.startswith(BuildAPP.QT_PREFIX):
|
|
deps.append(lib)
|
|
return deps
|
|
|
|
@classmethod
|
|
def fix_qt_dependencies(cls, path, deps):
|
|
fp = '@executable_path/../Frameworks/'
|
|
info('Fixing qt dependencies for:', os.path.basename(path))
|
|
for dep in deps:
|
|
match = re.search(r'(Qt\w+?)\.framework', dep)
|
|
if not match:
|
|
match = re.search(r'(phonon)\.framework', dep)
|
|
if not match:
|
|
warn(dep)
|
|
raise Exception('Unknown Qt dependency')
|
|
module = match.group(1)
|
|
newpath = fp + '%s.framework/Versions/Current/%s'%(module, module)
|
|
cmd = ' '.join(['/usr/bin/install_name_tool', '-change', dep, newpath, path])
|
|
subprocess.check_call(cmd, shell=True)
|
|
|
|
|
|
def add_qt_plugins(self):
|
|
macos_dir = os.path.join(self.dist_dir, APPNAME + '.app', 'Contents', 'MacOS')
|
|
for root, dirs, files in os.walk(BuildAPP.QT_PREFIX+'/plugins'):
|
|
for name in files:
|
|
if name.endswith('.dylib'):
|
|
path = os.path.join(root, name)
|
|
dir = os.path.basename(root)
|
|
dest_dir = os.path.join(macos_dir, dir)
|
|
if not os.path.exists(dest_dir):
|
|
os.mkdir(dest_dir)
|
|
target = os.path.join(dest_dir, name)
|
|
shutil.copyfile(path, target)
|
|
shutil.copymode(path, target)
|
|
deps = BuildAPP.qt_dependencies(target)
|
|
BuildAPP.fix_qt_dependencies(target, deps)
|
|
|
|
|
|
#deps = BuildAPP.qt_dependencies(path)
|
|
|
|
def fix_python_dependencies(self, files):
|
|
for f in files:
|
|
subprocess.check_call(['/usr/bin/install_name_tool', '-change', '/Library/Frameworks/Python.framework/Versions/2.6/Python', '@executable_path/../Frameworks/Python.framework/Versions/2.6/Python', f])
|
|
|
|
def fix_misc_dependencies(self, files):
|
|
for path in files:
|
|
frameworks_dir = os.path.join(self.dist_dir, APPNAME + '.app', 'Contents', 'Frameworks')
|
|
pipe = subprocess.Popen('/usr/bin/otool -L '+path, shell=True, stdout=subprocess.PIPE).stdout
|
|
for l in pipe.readlines():
|
|
match = re.search(r'\s+(.*?)\s+\(', l)
|
|
if match:
|
|
dep = match.group(1)
|
|
name = os.path.basename(dep)
|
|
if not name:
|
|
name = dep
|
|
bundle = os.path.join(frameworks_dir, name)
|
|
if os.path.exists(bundle):
|
|
subprocess.check_call(['/usr/bin/install_name_tool', '-change', dep,
|
|
'@executable_path/../Frameworks/'+name, path])
|
|
|
|
|
|
def add_plugins(self):
|
|
self.add_qt_plugins()
|
|
frameworks_dir = os.path.join(self.dist_dir, APPNAME + '.app', 'Contents', 'Frameworks')
|
|
plugins_dir = os.path.join(frameworks_dir, 'plugins')
|
|
if not os.path.exists(plugins_dir):
|
|
os.mkdir(plugins_dir)
|
|
|
|
maps = {}
|
|
for f in glob.glob('src/calibre/plugins/*'):
|
|
tgt = plugins_dir
|
|
if f.endswith('.dylib'):
|
|
tgt = frameworks_dir
|
|
maps[f] = os.path.join(tgt, os.path.basename(f))
|
|
deps = []
|
|
for src, dst in maps.items():
|
|
shutil.copyfile(src, dst)
|
|
self.fix_qt_dependencies(dst, self.qt_dependencies(dst))
|
|
deps.append(dst)
|
|
self.fix_python_dependencies(deps)
|
|
self.fix_misc_dependencies(deps)
|
|
|
|
def fix_image_magick_deps(self, root):
|
|
modules = []
|
|
frameworks_dir = os.path.dirname(root)
|
|
for x in os.walk(root):
|
|
for f in x[-1]:
|
|
if f.endswith('.so'):
|
|
modules.append(os.path.join(x[0], f))
|
|
for x in os.walk(os.path.join(frameworks_dir, 'plugins')):
|
|
for f in x[-1]:
|
|
if f.endswith('.so'):
|
|
modules.append(os.path.join(x[0], f))
|
|
|
|
deps = {}
|
|
for x in ('Core.1', 'Wand.1'):
|
|
modules.append(os.path.join(root, 'lib', 'libMagick%s.dylib'%x))
|
|
x = modules[-1]
|
|
deps[os.path.join('/Users/kovid/ImageMagick/lib',
|
|
os.path.basename(x))] = '@executable_path/../Frameworks/ImageMagick/lib/'+os.path.basename(x)
|
|
subprocess.check_call(['install_name_tool', '-id',
|
|
'@executable_path/../Frameworks/ImageMagick/lib/'+os.path.basename(x),
|
|
x])
|
|
for x in ('/usr/local/lib/libfreetype.6.dylib',
|
|
'/Volumes/sw/lib/libwmflite-0.2.7.dylib'):
|
|
deps[x] = '@executable_path/../Frameworks/'+ os.path.basename(x)
|
|
|
|
for x in modules:
|
|
print 'Fixing deps in', x
|
|
for f, t in deps.items():
|
|
subprocess.check_call(['install_name_tool', '-change', f, t, x])
|
|
|
|
|
|
def run(self):
|
|
py2app.run(self)
|
|
resource_dir = os.path.join(self.dist_dir,
|
|
APPNAME + '.app', 'Contents', 'Resources')
|
|
frameworks_dir = os.path.join(os.path.dirname(resource_dir), 'Frameworks')
|
|
all_scripts = scripts['console'] + scripts['gui']
|
|
all_names = basenames['console'] + basenames['gui']
|
|
all_modules = main_modules['console'] + main_modules['gui']
|
|
all_functions = main_functions['console'] + main_functions['gui']
|
|
|
|
info('\nAdding resources')
|
|
dest = os.path.join(resource_dir, 'resources')
|
|
if os.path.exists(dest):
|
|
shutil.rmtree(dest)
|
|
shutil.copytree(os.path.join(os.path.dirname(SRC), 'resources'), dest)
|
|
|
|
info('\nAdding PoDoFo')
|
|
pdf = glob.glob(os.path.expanduser('/Volumes/sw/podofo/libpodofo*.dylib'))[0]
|
|
shutil.copyfile(pdf, os.path.join(frameworks_dir, os.path.basename(pdf)))
|
|
|
|
info('\nAdding poppler')
|
|
popps = []
|
|
for x in ('bin/pdftohtml', 'lib/libpoppler.5.dylib'):
|
|
dest = os.path.join(frameworks_dir, os.path.basename(x))
|
|
popps.append(dest)
|
|
shutil.copy2(os.path.join('/Volumes/sw', x), dest)
|
|
subprocess.check_call(['install_name_tool', '-change',
|
|
'/usr/local/lib/libfontconfig.1.dylib',
|
|
'@executable_path/../Frameworks/libfontconfig.1.dylib',
|
|
os.path.join(frameworks_dir, 'pdftohtml')])
|
|
x ='libpng12.0.dylib'
|
|
shutil.copy2('/usr/local/lib/'+x, frameworks_dir)
|
|
subprocess.check_call(['install_name_tool', '-id',
|
|
'@executable_path/../Frameworks/'+x, os.path.join(frameworks_dir, x)])
|
|
self.fix_misc_dependencies(popps)
|
|
subprocess.check_call(['install_name_tool', '-change',
|
|
'/usr/local/lib/libfontconfig.1.dylib',
|
|
'@executable_path/../Frameworks/libfontconfig.1.dylib', popps[1]])
|
|
subprocess.check_call(['install_name_tool', '-id',
|
|
'@executable_path/../Frameworks/'+os.path.basename(popps[1]), popps[1]])
|
|
|
|
loader_path = os.path.join(resource_dir, 'loaders')
|
|
if not os.path.exists(loader_path):
|
|
os.mkdir(loader_path)
|
|
for name, module, function in zip(all_names, all_modules, all_functions):
|
|
path = os.path.join(loader_path, name)
|
|
info('Creating loader:', path)
|
|
f = open(path, 'w')
|
|
f.write(BuildAPP.LOADER_TEMPLATE % dict(module=module,
|
|
function=function))
|
|
f.close()
|
|
os.chmod(path, stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH|stat.S_IREAD\
|
|
|stat.S_IWUSR|stat.S_IROTH|stat.S_IRGRP)
|
|
|
|
|
|
info('Adding fontconfig')
|
|
for f in glob.glob(os.path.expanduser('~/fontconfig-bundled/*')):
|
|
dest = os.path.join(frameworks_dir, os.path.basename(f))
|
|
if os.path.exists(dest):
|
|
os.remove(dest)
|
|
shutil.copyfile(f, dest)
|
|
dst = os.path.join(resource_dir, 'fonts')
|
|
if os.path.exists(dst):
|
|
shutil.rmtree(dst)
|
|
shutil.copytree('/usr/local/etc/fonts', dst, symlinks=False)
|
|
|
|
self.add_plugins()
|
|
|
|
|
|
info('Adding IPython')
|
|
dst = os.path.join(resource_dir, 'lib', 'python2.6', 'IPython')
|
|
if os.path.exists(dst): shutil.rmtree(dst)
|
|
shutil.copytree(os.path.expanduser('~/build/ipython/IPython'), dst)
|
|
|
|
|
|
info('Adding ImageMagick')
|
|
libwmf = '/Volumes/sw/lib/libwmflite-0.2.7.dylib'
|
|
dest = os.path.join(frameworks_dir, os.path.basename(libwmf))
|
|
shutil.copy2(libwmf, frameworks_dir)
|
|
nid = '@executable_path/../Frameworks/'+os.path.basename(dest)
|
|
subprocess.check_call(['install_name_tool', '-id', nid, dest])
|
|
dest = os.path.join(frameworks_dir, 'ImageMagick')
|
|
if os.path.exists(dest):
|
|
shutil.rmtree(dest)
|
|
shutil.copytree(os.path.expanduser('~/ImageMagick'), dest, True)
|
|
shutil.rmtree(os.path.join(dest, 'include'))
|
|
shutil.rmtree(os.path.join(dest, 'share', 'doc'))
|
|
shutil.rmtree(os.path.join(dest, 'share', 'man'))
|
|
shutil.copyfile('/usr/local/lib/libpng12.0.dylib', os.path.join(dest, 'lib', 'libpng12.0.dylib'))
|
|
self.fix_image_magick_deps(dest)
|
|
|
|
|
|
info('Installing prescipt')
|
|
sf = [os.path.basename(s) for s in all_names]
|
|
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.*?\))', '%s'%(
|
|
'''
|
|
sys.frameworks_dir = os.path.join(os.path.dirname(os.environ['RESOURCEPATH']), 'Frameworks')
|
|
sys.resources_location = os.path.join(os.environ['RESOURCEPATH'], 'resources')
|
|
sys.extensions_location = os.path.join(sys.frameworks_dir, 'plugins')
|
|
devf = os.environ.get('CALIBRE_DEVELOP_FROM', None)
|
|
do_devf = devf and os.path.exists(devf)
|
|
if do_devf:
|
|
devf = os.path.abspath(devf)
|
|
sys.path.insert(0, devf)
|
|
''') + r'\n\1', src)
|
|
f = open(launcher_path, 'w')
|
|
print >>f, 'import sys, os'
|
|
f.write(src)
|
|
f.close()
|
|
|
|
info('\nAdding main scripts to site-packages')
|
|
f = zipfile.ZipFile(os.path.join(self.dist_dir, APPNAME+'.app', 'Contents', 'Resources', 'lib', 'python'+sys.version[:3], 'site-packages.zip'), 'a', zipfile.ZIP_DEFLATED)
|
|
for script in scripts['gui']+scripts['console']:
|
|
f.write(script, script.partition('/')[-1])
|
|
f.close()
|
|
|
|
info('\nCreating console.app')
|
|
contents_dir = os.path.dirname(resource_dir)
|
|
cc_dir = os.path.join(contents_dir, 'console.app', 'Contents')
|
|
os.makedirs(cc_dir)
|
|
for x in os.listdir(contents_dir):
|
|
if x == 'console.app':
|
|
continue
|
|
if x == 'Info.plist':
|
|
plist = plistlib.readPlist(os.path.join(contents_dir, x))
|
|
plist['LSUIElement'] = '1'
|
|
plistlib.writePlist(plist, os.path.join(cc_dir, x))
|
|
else:
|
|
os.symlink(os.path.join('../..', x),
|
|
os.path.join(cc_dir, x))
|
|
|
|
info('\nBuilding disk image')
|
|
BuildAPP.makedmg(os.path.join(self.dist_dir, APPNAME+'.app'), APPNAME+'-'+VERSION)
|
|
|
|
def main():
|
|
sys.argv[1:2] = ['py2app']
|
|
d = os.path.dirname
|
|
icon = os.path.abspath('icons/library.icns')
|
|
if not os.access(icon, os.R_OK):
|
|
raise Exception('No icon at '+icon)
|
|
setup(
|
|
name = APPNAME,
|
|
app = [scripts['gui'][0]],
|
|
cmdclass = { 'py2app' : BuildAPP },
|
|
options = { 'py2app' :
|
|
{
|
|
'optimize' : 2,
|
|
'dist_dir' : 'build/py2app',
|
|
'argv_emulation' : True,
|
|
'iconfile' : icon,
|
|
'frameworks': ['libusb.dylib', 'libunrar.dylib'],
|
|
'includes' : ['sip', 'pkg_resources', 'PyQt4.QtXml',
|
|
'PyQt4.QtSvg', 'PyQt4.QtWebKit', 'commands',
|
|
'mechanize', 'ClientForm', 'usbobserver',
|
|
'genshi', 'calibre.web.feeds.recipes.*',
|
|
'calibre.gui2.convert.*',
|
|
'PyQt4.QtNetwork',
|
|
'keyword', 'codeop', 'pydoc', 'readline',
|
|
'BeautifulSoup',
|
|
'dateutil', 'email.iterators',
|
|
'email.generator', 'sqlite3.dump',
|
|
'calibre.ebooks.metadata.amazon',
|
|
],
|
|
'packages' : ['PIL', 'Authorization', 'lxml', 'dns'],
|
|
'excludes' : ['IPython', 'PyQt4.uic.port_v3.proxy_base'],
|
|
'plist' : { 'CFBundleGetInfoString' : '''calibre, an E-book management application.'''
|
|
''' Visit http://calibre-ebook.com for details.''',
|
|
'CFBundleIdentifier':'net.kovidgoyal.calibre',
|
|
'CFBundleShortVersionString':VERSION,
|
|
'CFBundleVersion':APPNAME + ' ' + VERSION,
|
|
'LSMinimumSystemVersion':'10.4.3',
|
|
'LSMultipleInstancesProhibited':'true',
|
|
'NSHumanReadableCopyright':'Copyright 2008, Kovid Goyal',
|
|
'LSEnvironment':{
|
|
'FC_CONFIG_DIR':'@executable_path/../Resources/fonts',
|
|
'MAGICK_HOME':'@executable_path/../Frameworks/ImageMagick',
|
|
'DYLD_LIBRARY_PATH':'@executable_path/../Frameworks/ImageMagick/lib',
|
|
}
|
|
},
|
|
},
|
|
},
|
|
setup_requires = ['py2app'],
|
|
)
|
|
return 0
|
|
|