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)
66 for path_element in path_list:
67 created_path = os.path.join(created_path, path_element)
68 if not os.path.exists(created_path):
69 os.mkdir(created_path)
71 def ship(filepath, desturl):
72 """Ship filepath via scp to desturl
74 if os.path.exists(filepath):
75 result = call(["/usr/bin/scp", filepath, desturl])
77 print >>sys.stderr, "%s doesn't exist" %( filepath )
80 # useful information about building dmgs
81 # http://developer.apple.com/documentation/Porting/Conceptual/PortingUnix
82 # http://developer.apple.com/documentation/developertools/Conceptual/SoftwareDistribution/Concepts/sd_disk_images.html
84 def makedmg(dirlist, volname):
85 """copy a list of directories into a dmg named volname
87 # need to detect what the real volume name is
88 mount_point = '/Volumes/%s' %(volname)
89 rwdmg = '%sw.dmg' %(volname)
90 dmg = '%s.dmg' %(volname)
91 call(['hdiutil','detach',mount_point])
92 if os.path.exists(mount_point):
93 print >>sys.stderr, "Something is in", mount_point
95 if os.path.exists(rwdmg):
97 if os.path.exists(dmg):
99 create=['hdiutil','create','-size','256m','-fs','HFS+','-volname',volname, rwdmg]
101 call(['hdiutil','attach',rwdmg])
106 tail = os.path.split(d)[-1]
107 if (os.path.isdir(d)):
108 copytree(d, os.path.join(mount_point, tail))
110 copy(d, os.path.join(mount_point, tail))
112 call(['hdiutil','detach',mount_point])
113 call(['hdiutil','convert',rwdmg,'-format','UDZO','-o',dmg])
114 #call('hdiutil','internet-enable','-yes',dmg)
117 def prelinkqt(app_name, bundle_dir, qt_lib_dir):
119 OS X's treatment of dynamic loading is annoying
120 properly prelink all the annoying qt components.
122 print >>sys.stderr, "Prelinking", app_name, "in", bundle_dir,
123 framework_subpath = os.path.join("%(framework)s.framework", "Versions", "4", "%(framework)s")
124 frameworks = ['QtCore', 'QtGui', 'QtOpenGL', 'QtAssistant', 'QtNetwork']
126 qt_framework=os.path.join(qt_lib_dir, framework_subpath)
127 app_binary=bundle_dir+"/Contents/MacOS/"+app_name
128 app_framework=os.path.join(bundle_dir, "Contents", "Frameworks", framework_subpath)
129 # install frameworks and update binary
130 for frame in frameworks:
131 qtframe = qt_framework % ({'framework': frame})
132 appframe = app_framework % ({'framework': frame})
133 exe_path = "@executable_path/../Frameworks/" + framework_subpath
134 exe_path %= ({'framework': frame})
135 mkdir(os.path.split(appframe)[0])
137 copy(qtframe, appframe)
138 call(['install_name_tool','-id',exe_path,appframe])
139 call(['install_name_tool','-change',qtframe,exe_path,app_binary])
140 # now that everything is in place change the frameworks
142 for frame2 in frameworks:
143 qtframe2 = qt_framework % ({'framework': frame2})
144 contents_exe_path = "@executable_path/../Frameworks/" + framework_subpath
145 contents_exe_path %= ({'framework': frame2})
146 call(['install_name_tool','-change',qtframe2,contents_exe_path,appframe])
149 def validate_bundle(bundle_path):
151 Go look through an OS X bundle for references to things that aren't in
152 /System/Library/Framework /usr/lib or @executable_path
155 """Is lib one of the allowed paths?"""
156 allowed_paths = [ re.compile("/usr/lib/"),
157 re.compile("/System/Library/Frameworks/.*"),
158 re.compile("\@executable_path/.*") ]
159 for allowed in allowed_paths:
160 if allowed.match(lib) is not None:
162 # if no pattern matched its not allowed
166 for curdir, subdirs, files in os.walk(bundle_path):
168 curpath = os.path.join(curdir, f)
171 for lib in obj.SharedLibraries:
172 if not isAllowed(lib):
173 print >>sys.stderr, "invalid library",lib,"in",curpath
178 qtroot = "/usr/local/qt/4.2.2"
186 opts, args = getopt.getopt(args, "a:b:d:n:q:h",
187 ["appbundle=", "build-num=", "destination=",
188 "name=", "prelink-only", "qt-root=", "help"])
190 for option, argument in opts:
191 if option in ("-a", "--appbundle"):
192 bundle_dir = argument
193 elif option in ("-b", "--build-num"):
195 elif option in ("-d", "--destination"):
197 elif option in ("-n", "--name"):
199 elif option in ("--prelink-only"):
201 elif option in ("-q", "--qt-root"):
203 elif option in ("-h", "--help"):
204 print "-a | --appbundle specify path to application bundle dir"
205 print "-b | --build-num specify build number, used to construct"
207 print "-n | --name specify application name and base volume name"
208 print "-q | --qtdir where is qt is installed"
209 print "-d | --destination scp destination for distribution"
210 print "-h | --help usage"
213 # compute bundle name/dir
214 if bundle_dir is None and app_name is None:
215 print >>sys.stderr, "I need a name or bundle path"
217 elif bundle_dir is None:
218 bundle_dir = app_name + ".app"
219 elif app_name is None:
220 # strip off trailing /
221 if bundle_dir[-1] == os.path.sep:
222 bundle_dir = bundle_dir[:-1]
223 path, file = os.path.split(bundle_dir)
224 app_name = os.path.splitext(file)[0]
227 dmg_name = app_name + "-" + build_num
231 bundle_dir = os.path.expanduser(bundle_dir)
233 if not os.path.exists(bundle_dir):
234 print >>sys.stderr, "couldn't find an app at %s" % (app_bundle)
237 qt_lib_dir = os.path.join(qtroot, 'lib')
239 prelinkqt(app_name, bundle_dir, qt_lib_dir)
240 if not validate_bundle(bundle_dir):
241 print >>sys.stderr, "Invalid libraries found"
247 dmgfile = makedmg([bundle_dir]+args, dmg_name)
248 if dmgfile and desturl:
249 print "Uploading",dmgfile,"to",desturl
250 ship(dmgfile, desturl)
252 if __name__ == "__main__":
253 sys.exit(main(sys.argv[1:]))