calibre/osx_installer.py
Kovid Goyal 2a779db7bd rtf2lrf
2007-07-02 19:07:32 +00:00

249 lines
9.9 KiB
Python

#!/usr/bin/env python
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
''' Create an OSX installer '''
import sys, re, os, shutil, subprocess, stat
from setup import VERSION, APPNAME, scripts, main_modules, basenames, main_functions
from setuptools import setup
sys.argv[1:2] = ['py2app']
from py2app.build_app import py2app
from modulegraph.find_modules import find_modules
class BuildAPP(py2app):
QT_PREFIX = '/Users/kovid/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(dirpath)
frameworks_dir = os.path.join(base_dir, 'Frameworks')
base_name = os.path.splitext(name)[0]
python = os.path.join(base_dir, 'MacOS', 'python')
loader_path = os.path.join(dirpath, base_name+'.py')
loader = open(loader_path, 'w')
site_packages = glob.glob(dirpath+'/*/*/site-packages.zip')[0]
print >>loader, '#!'+python
print >>loader, 'import sys'
print >>loader, 'sys.path.append(', repr(site_packages), ')'
print >>loader, 'sys.frozen = "macosx_app"'
print >>loader, 'sys.frameworks_dir =', repr(frameworks_dir)
print >>loader, 'import os'
print >>loader, 'base =', repr(dirpath)
print >>loader, 'from %(module)s import %(function)s'
print >>loader, '%(function)s()'
loader.close()
os.chmod(loader_path, 0700)
os.environ['PYTHONHOME'] = dirpath
os.execv(loader_path, sys.argv)
'''
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
os.symlink(s, l)
"""
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, 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(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
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 '''
dmg = os.path.join(destdir, volname+'.dmg')
if os.path.exists(dmg):
os.unlink(dmg)
subprocess.check_call(['hdiutil', 'create', '-srcfolder', os.path.abspath(d),
'-volname', volname, '-format', format, dmg])
if internet_enable:
subprocess.check_call(['hdiutil', 'internet-enable', '-yes', dmg])
return dmg
@classmethod
def qt_dependencies(cls, path):
pipe = subprocess.Popen('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/'
print 'Fixing qt dependencies for:', os.path.basename(path)
for dep in deps:
module = re.search(r'(Qt\w+?)\.framework', dep).group(1)
newpath = fp + '%s.framework/Versions/Current/%s'%(module, module)
cmd = ' '.join(['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 run(self):
py2app.run(self)
self.add_qt_plugins()
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']
print
for name, module, function in zip(all_names, all_modules, all_functions):
path = os.path.join(resource_dir, name)
print '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)
print
print 'Adding clit'
os.link(os.path.expanduser('~/clit'), os.path.join(frameworks_dir, 'clit'))
print
print 'Adding unrtf'
os.link(os.path.expanduser('~/unrtf'), os.path.join(frameworks_dir, 'unrtf'))
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 = re.sub('(_run\s*\(.*?.py.*?\))', cs+'%s'%(
'''
sys.frameworks_dir = os.path.join(os.path.dirname(os.environ['RESOURCEPATH']), 'Frameworks')
''') + r'\n\1', src)
f = open(launcher_path, 'w')
print >>f, 'import sys, os'
f.write(src)
f.close()
print
print 'Building disk image'
BuildAPP.makedmg(os.path.join(self.dist_dir, APPNAME+'.app'), APPNAME+'-'+VERSION)
setup(
name = APPNAME,
app = [scripts['gui'][0]],
cmdclass = { 'py2app' : BuildAPP },
options = { 'py2app' :
{
'optimize' : 2,
'dist_dir' : 'build/py2app',
'argv_emulation' : True,
'iconfile' : 'icons/library.icns',
'frameworks': ['libusb.dylib', 'libunrar.dylib'],
'includes' : ['sip', 'pkg_resources', 'PyQt4.QtSvg'],
'packages' : ['PIL', 'Authorization',],
'excludes' : ['pydoc'],
'plist' : { 'CFBundleGetInfoString' : '''libprs500, an E-book management application.'''
''' Visit http://libprs500.kovidgoyal.net for details.''',
'CFBundleIdentifier':'net.kovidgoyal.librs500',
'CFBundleShortVersionString':VERSION,
'CFBundleVersion':APPNAME + ' ' + VERSION,
'LSMinimumSystemVersion':'10.4.3',
'LSMultipleInstancesProhibited':'true',
'NSHumanReadableCopyright':'Copyright 2006, Kovid Goyal',
},
},
},
setup_requires = ['py2app'],
)