mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-10-31 18:47:02 -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
 | |
| 
 |