Update mussa to build on ubuntu 10.04 with qt 4.6.2 +boost 1.40.0.1
[mussa.git] / makelib / osxdist.py
1 #!/usr/bin/env python
2
3 import os
4 import re
5 import sys
6 import getopt
7
8 try:
9   from subprocess import call, Popen, PIPE
10 except ImportError, e:
11   print >>sys.stderr, "Need to install subprocess or use python >= 2.4"
12   print >>sys.stderr, "See http://www.lysator.liu.se/~astrand/popen5/"
13   raise e
14
15 from shutil import copy, copytree
16
17 class otool(object):
18   """Gather information from an object file using otool"""
19   def __init__(self, object_path):
20     self.otool = "/usr/bin/otool"
21     self.object_path = object_path
22     if not os.path.exists(self.object_path):
23       raise RuntimeError("path not found")
24     
25   def __run_command(self, option):
26     p = Popen([self.otool, option, self.object_path],
27               bufsize=1, stdin=PIPE, stdout=PIPE, close_fds=True)
28     os.waitpid(p.pid, 0)
29     return p
30   
31   def _is_object(self):
32     """"Check to see if object_path is an object-file"""
33     p = self.__run_command("-h")
34     header = p.stdout.read()
35     if re.search("Mach header", header):
36       return True
37     else:
38       return False
39   isObject = property(_is_object)
40                           
41   def _get_shared_libraries(self):
42     """Return list of shared libraries"""
43     if not self.isObject:
44       raise RuntimeError("Not object")
45     p = self.__run_command("-L")
46     libraries = []
47     output_lines = p.stdout.readlines()
48     # ignore the header, or perhaps we should test it
49     # to see if it matches self.object_path
50     for line in output_lines[1:]:
51       if len(line) > 0:
52         libraries.append(line.split()[0].strip())
53     return libraries
54   SharedLibraries = property(_get_shared_libraries)  
55
56 def mkdir(path):
57   """make all the components of a specified path
58   """
59   path_list = []
60   head, tail = os.path.split(path)
61   path_list.insert(0, tail)
62   while head != os.path.sep and len(head) > 0:
63     head, tail = os.path.split(head)
64     path_list.insert(0, tail)
65   # FIXME: For some reason the above os.path.split lost the root '/'
66   if path[0] == os.path.sep and path_list[0] != os.path.sep:
67     path_list.insert(0, os.path.sep)
68   created_path = ""
69   for path_element in path_list:
70     created_path = os.path.join(created_path, path_element)
71     if not os.path.exists(created_path):
72       os.mkdir(created_path)
73       
74 def ship(filepath, desturl):
75   """Ship filepath via scp to desturl
76   """
77   if os.path.exists(filepath):
78     result = call(["/usr/bin/scp", filepath, desturl])
79   else:
80     print >>sys.stderr, "%s doesn't exist" %( filepath )
81   return None
82   
83 # useful information about building dmgs
84 # http://developer.apple.com/documentation/Porting/Conceptual/PortingUnix
85 # http://developer.apple.com/documentation/developertools/Conceptual/SoftwareDistribution/Concepts/sd_disk_images.html
86
87 def makedmg(dirlist, volname):
88   """copy a list of directories into a dmg named volname
89   """
90   # need to detect what the real volume name is
91   mount_point = '/Volumes/%s' %(volname)
92   rwdmg = '%sw.dmg' %(volname)
93   dmg = '%s.dmg' %(volname)
94   call(['hdiutil','detach',mount_point])
95   if os.path.exists(mount_point):
96     print >>sys.stderr, "Something is in", mount_point
97     return 
98   if os.path.exists(rwdmg):
99     os.unlink(rwdmg)
100   if os.path.exists(dmg):
101     os.unlink(dmg)
102   create=['hdiutil','create','-size','256m','-fs','HFS+','-volname',volname, rwdmg]
103   call(create)
104   call(['hdiutil','attach',rwdmg])
105   # copy files
106   for d in dirlist:
107     if d[-1] == '/':
108       d = d[:-1]
109     tail = os.path.split(d)[-1]
110     if (os.path.isdir(d)):
111       copytree(d, os.path.join(mount_point, tail))
112     else:
113       copy(d, os.path.join(mount_point, tail))
114
115   call(['hdiutil','detach',mount_point])
116   call(['hdiutil','convert',rwdmg,'-format','UDZO','-o',dmg])
117   #call('hdiutil','internet-enable','-yes',dmg)
118   return dmg
119
120 def prelinkqt(app_name, bundle_dir, qt_lib_dir):
121   """
122   OS X's treatment of dynamic loading is annoying
123   properly prelink all the annoying qt components.
124   """
125   print >>sys.stderr, "Prelinking", app_name, "in", bundle_dir,
126   framework_subpath = os.path.join("%(framework)s.framework", "Versions", "4", "%(framework)s")
127   frameworks = ['QtCore', 'QtGui', 'QtOpenGL', 'QtAssistant', 'QtNetwork']
128
129   qt_framework=os.path.join(qt_lib_dir, framework_subpath)
130   app_binary=bundle_dir+"/Contents/MacOS/"+app_name
131   app_framework=os.path.join(bundle_dir, "Contents", "Frameworks", framework_subpath)
132   # install frameworks and update binary
133   for frame in frameworks:
134     qtframe = qt_framework % ({'framework': frame})
135     appframe = app_framework % ({'framework': frame})
136     exe_path = "@executable_path/../Frameworks/" + framework_subpath
137     exe_path %= ({'framework': frame})
138     mkdir(os.path.split(appframe)[0])
139     # update binary
140     copy(qtframe, appframe)
141     call(['install_name_tool','-id',exe_path,appframe])
142     call(['install_name_tool','-change',qtframe,exe_path,app_binary])
143     # now that everything is in place change the frameworks 
144     # references
145     for frame2 in frameworks:
146       qtframe2 = qt_framework % ({'framework': frame2})
147       contents_exe_path = "@executable_path/../Frameworks/" + framework_subpath
148       contents_exe_path %= ({'framework': frame2})
149       call(['install_name_tool','-change',qtframe2,contents_exe_path,appframe])
150   print "."
151       
152 def validate_bundle(bundle_path):
153   """
154   Go look through an OS X bundle for references to things that aren't in
155   /System/Library/Framework /usr/lib or @executable_path
156   """
157   def isAllowed(lib):
158     """Is lib one of the allowed paths?"""
159     allowed_paths = [ re.compile("/usr/lib/"), 
160                       re.compile("/System/Library/Frameworks/.*"),
161                       re.compile("\@executable_path/.*") ]
162     for allowed in allowed_paths:
163       if allowed.match(lib) is not None:
164         return True
165     # if no pattern matched its not allowed
166     return False
167   
168   valid = True
169   for curdir, subdirs, files in os.walk(bundle_path):
170     for f in files:
171       curpath = os.path.join(curdir, f)
172       obj = otool(curpath)
173       if obj.isObject: 
174         for lib in obj.SharedLibraries:
175           if not isAllowed(lib):
176             print >>sys.stderr, "invalid library",lib,"in",curpath
177             valid = False
178   return valid
179   
180 def main(args):
181   qtroot = "/opt/local/libexec/qt4-mac/"
182   app_name = None
183   build_num = None
184   bundle_dir = None
185   dmgfile = None
186   desturl = None
187   prelink_only = False
188   
189   opts, args = getopt.getopt(args, "a:b:d:n:q:h", 
190                              ["appbundle=", "build-num=", "destination=",
191                               "name=", "prelink-only", "qt-root=", "help"])
192
193   for option, argument in opts:
194     if option in ("-a", "--appbundle"):
195       bundle_dir = argument
196     elif option in ("-b", "--build-num"):
197       build_num = argument
198     elif option in ("-d", "--destination"):
199       desturl = argument
200     elif option in ("-n", "--name"):
201       app_name = argument
202     elif option in ("--prelink-only"):
203       prelink_only = True
204     elif option in ("-q", "--qt-root"):
205       qtroot = argument
206     elif option in ("-h", "--help"):
207       print "-a | --appbundle    specify path to application bundle dir"
208       print "-b | --build-num    specify build number, used to construct"
209       print "                    volume name"      
210       print "-n | --name         specify application name and base volume name"
211       print "-q | --qtdir        where is qt is installed"
212       print "-d | --destination  scp destination for distribution"
213       print "-h | --help         usage"
214       return 0
215
216   # compute bundle name/dir
217   if bundle_dir is None and app_name is None:
218     print >>sys.stderr, "I need a name or bundle path"
219     return 1
220   elif bundle_dir is None:
221     bundle_dir = app_name + ".app"
222   elif app_name is None:
223     # strip off trailing /
224     if bundle_dir[-1] == os.path.sep:
225       bundle_dir = bundle_dir[:-1]
226     path, file = os.path.split(bundle_dir)
227     app_name = os.path.splitext(file)[0]
228
229   if build_num:
230     dmg_name = app_name + "-" + build_num
231   else:
232     dmg_name = app_name
233     
234   bundle_dir = os.path.expanduser(bundle_dir)
235   
236   if not os.path.exists(bundle_dir):
237     print >>sys.stderr, "couldn't find an app at %s" % (app_bundle)
238     return 1
239   
240   qt_lib_dir = os.path.join(qtroot, 'lib')
241
242   prelinkqt(app_name, bundle_dir, qt_lib_dir)
243   if not validate_bundle(bundle_dir):
244     print >>sys.stderr, "Invalid libraries found"
245     return 1
246
247   if prelink_only:
248     return 0
249   
250   dmgfile = makedmg([bundle_dir]+args, dmg_name)
251   if dmgfile and desturl:
252     print "Uploading",dmgfile,"to",desturl
253     ship(dmgfile, desturl)
254
255 if __name__ == "__main__":
256   sys.exit(main(sys.argv[1:]))