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 copytree(d, os.path.join(mount_point, tail))
109 call(['hdiutil','detach',mount_point])
110 call(['hdiutil','convert',rwdmg,'-format','UDZO','-o',dmg])
111 #call('hdiutil','internet-enable','-yes',dmg)
114 def prelinkqt(app_name, bundle_dir, qt_lib_dir):
116 OS X's treatment of dynamic loading is annoying
117 properly prelink all the annoying qt components.
119 framework_subpath = os.path.join("%(framework)s.framework", "Versions", "4.0", "%(framework)s")
120 frameworks = ['QtCore', 'QtGui', 'QtOpenGL', 'QtNetwork']
122 qt_framework=os.path.join(qt_lib_dir, framework_subpath)
123 app_binary=bundle_dir+"/Contents/MacOS/"+app_name
124 app_framework=os.path.join(bundle_dir, "Contents", "Frameworks", framework_subpath)
125 # install frameworks and update binary
126 for frame in frameworks:
127 qtframe = qt_framework % ({'framework': frame})
128 appframe = app_framework % ({'framework': frame})
129 exe_path = "@executable_path/../Frameworks/" + framework_subpath
130 exe_path %= ({'framework': frame})
131 mkdir(os.path.split(appframe)[0])
133 copy(qtframe, appframe)
134 call(['install_name_tool','-id',exe_path,appframe])
135 call(['install_name_tool','-change',qtframe,exe_path,app_binary])
136 # now that everything is in place change the frameworks
138 for frame2 in frameworks:
139 qtframe2 = qt_framework % ({'framework': frame2})
140 contents_exe_path = "@executable_path/../Frameworks/" + framework_subpath
141 contents_exe_path %= ({'framework': frame2})
142 call(['install_name_tool','-change',qtframe2,contents_exe_path,appframe])
144 def validate_bundle(bundle_path):
146 Go look through an OS X bundle for references to things that aren't in
147 /System/Library/Framework /usr/lib or @executable_path
150 """Is lib one of the allowed paths?"""
151 allowed_paths = [ re.compile("/usr/lib/"),
152 re.compile("/System/Library/Frameworks/.*"),
153 re.compile("\@executable_path/.*") ]
154 for allowed in allowed_paths:
155 if allowed.match(lib) is not None:
157 # if no pattern matched its not allowed
161 for curdir, subdirs, files in os.walk(bundle_path):
163 curpath = os.path.join(curdir, f)
166 for lib in obj.SharedLibraries:
167 if not isAllowed(lib):
168 print >>sys.stderr, "invalid library",lib,"in",curpath
173 qtroot = "/usr/local/qt/4.1.4"
180 opts, args = getopt.getopt(args, "a:b:d:n:q:h",
181 ["appbundle=", "build-num=", "destination=",
182 "name=", "qt-root=", "help"])
183 for option, argument in opts:
184 if option in ("-a", "--appbundle"):
185 bundle_dir = argument
186 elif option in ("-b", "--build-num"):
188 elif option in ("-d", "--destination"):
190 elif option in ("-n", "--name"):
192 elif option in ("-q", "--qt-root"):
194 elif option in ("-h", "--help"):
195 print "-a | --appbundle specify path to application bundle dir"
196 print "-b | --build-num specify build number, used to construct"
198 print "-n | --name specify application name and base volume name"
199 print "-q | --qtdir where is qt is installed"
200 print "-d | --destination scp destination for distribution"
201 print "-h | --help usage"
204 # compute bundle name/dir
205 if bundle_dir is None and app_name is None:
206 print >>sys.stderr, "I need a name or bundle path"
208 elif bundle_dir is None:
209 bundle_dir = app_name + ".app"
210 elif app_name is None:
211 # strip off trailing /
212 if bundle_dir[-1] == os.path.sep:
213 bundle_dir = bundle_dir[:-1]
214 path, file = os.path.split(bundle_dir)
215 app_name = os.path.splitext(file)[0]
218 dmg_name = app_name + "-" + build_num
222 if not os.path.exists(bundle_dir):
223 print >>sys.stderr, "couldn't find an app at %s" % (app_bundle)
226 qt_lib_dir = os.path.join(qtroot, 'lib')
228 prelinkqt(app_name, bundle_dir, qt_lib_dir)
229 if validate_bundle(bundle_dir):
230 dmgfile = makedmg([bundle_dir]+args, dmg_name)
232 print >>sys.stderr, "Invalid libraries found"
235 if dmgfile and desturl:
236 print "Uploading",dmgfile,"to",desturl
237 ship(dmgfile, desturl)
239 if __name__ == "__main__":
240 sys.exit(main(sys.argv[1:]))