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)
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 framework_subpath = os.path.join("%(framework)s.framework", "Versions", "4", "%(framework)s")
123 frameworks = ['QtCore', 'QtGui', 'QtOpenGL', 'QtAssistantClient', 'QtNetwork']
125 qt_framework=os.path.join(qt_lib_dir, framework_subpath)
126 app_binary=bundle_dir+"/Contents/MacOS/"+app_name
127 app_framework=os.path.join(bundle_dir, "Contents", "Frameworks", framework_subpath)
128 # install frameworks and update binary
129 for frame in frameworks:
130 qtframe = qt_framework % ({'framework': frame})
131 appframe = app_framework % ({'framework': frame})
132 exe_path = "@executable_path/../Frameworks/" + framework_subpath
133 exe_path %= ({'framework': frame})
134 mkdir(os.path.split(appframe)[0])
136 copy(qtframe, appframe)
137 call(['install_name_tool','-id',exe_path,appframe])
138 call(['install_name_tool','-change',qtframe,exe_path,app_binary])
139 # now that everything is in place change the frameworks
141 for frame2 in frameworks:
142 qtframe2 = qt_framework % ({'framework': frame2})
143 contents_exe_path = "@executable_path/../Frameworks/" + framework_subpath
144 contents_exe_path %= ({'framework': frame2})
145 call(['install_name_tool','-change',qtframe2,contents_exe_path,appframe])
147 def validate_bundle(bundle_path):
149 Go look through an OS X bundle for references to things that aren't in
150 /System/Library/Framework /usr/lib or @executable_path
153 """Is lib one of the allowed paths?"""
154 allowed_paths = [ re.compile("/usr/lib/"),
155 re.compile("/System/Library/Frameworks/.*"),
156 re.compile("\@executable_path/.*") ]
157 for allowed in allowed_paths:
158 if allowed.match(lib) is not None:
160 # if no pattern matched its not allowed
164 for curdir, subdirs, files in os.walk(bundle_path):
166 curpath = os.path.join(curdir, f)
169 for lib in obj.SharedLibraries:
170 if not isAllowed(lib):
171 print >>sys.stderr, "invalid library",lib,"in",curpath
176 qtroot = "/usr/local/qt/4.2.1"
183 opts, args = getopt.getopt(args, "a:b:d:n:q:h",
184 ["appbundle=", "build-num=", "destination=",
185 "name=", "qt-root=", "help"])
186 for option, argument in opts:
187 if option in ("-a", "--appbundle"):
188 bundle_dir = argument
189 elif option in ("-b", "--build-num"):
191 elif option in ("-d", "--destination"):
193 elif option in ("-n", "--name"):
195 elif option in ("-q", "--qt-root"):
197 elif option in ("-h", "--help"):
198 print "-a | --appbundle specify path to application bundle dir"
199 print "-b | --build-num specify build number, used to construct"
201 print "-n | --name specify application name and base volume name"
202 print "-q | --qtdir where is qt is installed"
203 print "-d | --destination scp destination for distribution"
204 print "-h | --help usage"
207 # compute bundle name/dir
208 if bundle_dir is None and app_name is None:
209 print >>sys.stderr, "I need a name or bundle path"
211 elif bundle_dir is None:
212 bundle_dir = app_name + ".app"
213 elif app_name is None:
214 # strip off trailing /
215 if bundle_dir[-1] == os.path.sep:
216 bundle_dir = bundle_dir[:-1]
217 path, file = os.path.split(bundle_dir)
218 app_name = os.path.splitext(file)[0]
221 dmg_name = app_name + "-" + build_num
225 if not os.path.exists(bundle_dir):
226 print >>sys.stderr, "couldn't find an app at %s" % (app_bundle)
229 qt_lib_dir = os.path.join(qtroot, 'lib')
231 prelinkqt(app_name, bundle_dir, qt_lib_dir)
232 if validate_bundle(bundle_dir):
233 dmgfile = makedmg([bundle_dir]+args, dmg_name)
235 print >>sys.stderr, "Invalid libraries found"
238 if dmgfile and desturl:
239 print "Uploading",dmgfile,"to",desturl
240 ship(dmgfile, desturl)
242 if __name__ == "__main__":
243 sys.exit(main(sys.argv[1:]))