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/"
15 from shutil import copy, copytree
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")
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)
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):
39 isObject = property(_is_object)
41 def _get_shared_libraries(self):
42 """Return list of shared libraries"""
44 raise RuntimeError("Not object")
45 p = self.__run_command("-L")
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:]:
52 libraries.append(line.split()[0].strip())
54 SharedLibraries = property(_get_shared_libraries)
57 """make all the components of a specified path
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)
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)
74 def ship(filepath, desturl):
75 """Ship filepath via scp to desturl
77 if os.path.exists(filepath):
78 result = call(["/usr/bin/scp", filepath, desturl])
80 print >>sys.stderr, "%s doesn't exist" %( filepath )
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
87 def makedmg(dirlist, volname):
88 """copy a list of directories into a dmg named volname
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
98 if os.path.exists(rwdmg):
100 if os.path.exists(dmg):
102 create=['hdiutil','create','-size','256m','-fs','HFS+','-volname',volname, rwdmg]
104 call(['hdiutil','attach',rwdmg])
109 tail = os.path.split(d)[-1]
110 if (os.path.isdir(d)):
111 copytree(d, os.path.join(mount_point, tail))
113 copy(d, os.path.join(mount_point, tail))
115 call(['hdiutil','detach',mount_point])
116 call(['hdiutil','convert',rwdmg,'-format','UDZO','-o',dmg])
117 #call('hdiutil','internet-enable','-yes',dmg)
120 def prelinkqt(app_name, bundle_dir, qt_lib_dir):
122 OS X's treatment of dynamic loading is annoying
123 properly prelink all the annoying qt components.
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']
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])
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
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])
152 def validate_bundle(bundle_path):
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
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:
165 # if no pattern matched its not allowed
169 for curdir, subdirs, files in os.walk(bundle_path):
171 curpath = os.path.join(curdir, f)
174 for lib in obj.SharedLibraries:
175 if not isAllowed(lib):
176 print >>sys.stderr, "invalid library",lib,"in",curpath
181 qtroot = "/opt/local/libexec/qt4-mac/"
189 opts, args = getopt.getopt(args, "a:b:d:n:q:h",
190 ["appbundle=", "build-num=", "destination=",
191 "name=", "prelink-only", "qt-root=", "help"])
193 for option, argument in opts:
194 if option in ("-a", "--appbundle"):
195 bundle_dir = argument
196 elif option in ("-b", "--build-num"):
198 elif option in ("-d", "--destination"):
200 elif option in ("-n", "--name"):
202 elif option in ("--prelink-only"):
204 elif option in ("-q", "--qt-root"):
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"
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"
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"
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]
230 dmg_name = app_name + "-" + build_num
234 bundle_dir = os.path.expanduser(bundle_dir)
236 if not os.path.exists(bundle_dir):
237 print >>sys.stderr, "couldn't find an app at %s" % (app_bundle)
240 qt_lib_dir = os.path.join(qtroot, 'lib')
242 prelinkqt(app_name, bundle_dir, qt_lib_dir)
243 if not validate_bundle(bundle_dir):
244 print >>sys.stderr, "Invalid libraries found"
250 dmgfile = makedmg([bundle_dir]+args, dmg_name)
251 if dmgfile and desturl:
252 print "Uploading",dmgfile,"to",desturl
253 ship(dmgfile, desturl)
255 if __name__ == "__main__":
256 sys.exit(main(sys.argv[1:]))