Partial reorganization of Mac OS bundeling script.
[quassel.git] / scripts / build / macosx_DeployApp.py
1 #!/usr/bin/python
2 # -*- coding: iso-8859-1 -*-
3
4 ################################################################################
5 #                                                                              #
6 # 2008 June 27th by Marcus 'EgS' Eggenberger <egs@quassel-irc.org>             #
7 #                                                                              #
8 # The author disclaims copyright to this source code.                          #
9 # This Python Script is in the PUBLIC DOMAIN.                                  #
10 #                                                                              #
11 ################################################################################
12
13 # ==============================
14 #  Imports
15 # ==============================
16 import sys
17 import os
18
19 from subprocess import Popen, PIPE
20
21 class InstallQt(object):
22     def __init__(self, appdir, bundle = True):
23         self.appDir = appdir
24         self.bundle = bundle
25         self.executableDir = self.appDir
26         if bundle:
27             self.executableDir += "/MacOS"
28
29         if bundle:
30             self.frameworkDir = self.appDir + "/Frameworks"
31         else:
32             self.frameworkDir = self.executableDir + "/Frameworks"
33
34         self.installedFrameworks = set()
35
36         self.findFrameworkPath()
37
38         executables = [self.executableDir + "/" + executable for executable in os.listdir(self.executableDir)]
39         for executable in executables:
40             self.resolveDependancies(executable)
41
42     def findFrameworkPath(self):
43         qmakeProcess = Popen('qmake -query QT_INSTALL_LIBS', shell=True, stdout=PIPE, stderr=PIPE)
44         self.sourceFrameworkPath = qmakeProcess.stdout.read().strip()
45         qmakeProcess.stdout.close()
46         qmakeProcess.wait()
47
48     def resolveDependancies(self, obj):
49         # obj must be either an application binary or a framework library
50         for framework, lib in self.determineDependancies(obj):
51             self.installFramework(framework)
52             self.changeDylPath(obj, lib)
53
54     def installFramework(self, framework):
55         # skip if framework is already installed.
56         if framework in self.installedFrameworks:
57             return
58
59         self.installedFrameworks.add(framework)
60
61         # ensure that the framework directory exists
62         try:
63             os.mkdir(self.frameworkDir)
64         except:
65             pass
66
67         if not framework.startswith('/'):
68             framework = "%s/%s" % (self.sourceFrameworkPath, framework)
69
70         # Copy Framework
71         os.system('cp -R "%s" "%s"' % (framework, self.frameworkDir))
72
73         frameworkname = framework.split('/')[-1]
74         localframework = self.frameworkDir + "/" + frameworkname
75
76         # De-Myllify
77         os.system('find "%s" -name *debug* -exec rm -f {} \;' % localframework)
78         os.system('find "%s" -name Headers -exec rm -rf {} \; 2>/dev/null' % localframework)
79
80         # Install new Lib ID and Change Path to Frameworks for the Dynamic linker
81         for lib in os.listdir(localframework + "/Versions/Current"):
82             lib = "%s/Versions/Current/%s" % (localframework, lib)
83             otoolProcess = Popen('otool -D "%s"' % lib, shell=True, stdout=PIPE, stderr=PIPE)
84             try:
85                 libname = [line for line in otoolProcess.stdout][1].strip()
86             except:
87                 libname = ''
88             otoolProcess.stdout.close()
89             if otoolProcess.wait() == 1: # we found some Resource dir or similar -> skip
90                 continue
91             frameworkpath, libpath = libname.split(frameworkname)
92             if self.bundle:
93                 newlibname = "@executable_path/../%s%s" % (frameworkname, libpath)
94             else:
95                 newlibname = "@executable_path/%s%s" % (frameworkname, libpath)
96             #print 'install_name_tool -id "%s" "%s"' % (newlibname, lib)
97             os.system('install_name_tool -id "%s" "%s"' % (newlibname, lib))
98
99             self.resolveDependancies(lib)
100
101     def determineDependancies(self, app):
102         otoolPipe = Popen('otool -L "%s"' % app, shell=True, stdout=PIPE).stdout
103         otoolOutput = [line for line in otoolPipe]
104         otoolPipe.close()
105         libs = [line.split()[0] for line in otoolOutput[1:] if ("Qt" in line
106                                                                or "phonon" in line)
107                                                                and not "@executable_path" in line]
108         frameworks = [lib[:lib.find(".framework")+len(".framework")] for lib in libs]
109         return zip(frameworks, libs)
110
111
112     def changeDylPath(self, obj, lib):
113         if self.bundle:
114             newlibname = "@executable_path/../Frameworks/%s" % lib
115         else:
116             newlibname = "@executable_path/Frameworks/%s" % lib
117
118         #print 'install_name_tool -change "%s" "%s" "%s"' % (lib, newlibname, obj)
119         os.system('install_name_tool -change "%s" "%s" "%s"' % (lib, newlibname, obj))
120
121 if __name__ == "__main__":
122     if len(sys.argv) < 2:
123         print "Wrong Argument Count (Syntax: %s [--nobundle] $TARGET_APP)" % sys.argv[0]
124         sys.exit(1)
125     else:
126         bundle = True
127         offset = 1
128
129         if sys.argv[1].startswith("--"):
130             offset = 2
131             if sys.argv[1] == "--nobundle":
132                 bundle = False
133
134         targetDir = sys.argv[offset]
135         if bundle:
136             targetDir += "/Contents"
137
138         InstallQt(targetDir, bundle)