From: Diane Trout Date: Tue, 17 Oct 2006 01:36:10 +0000 (+0000) Subject: validate app bundle before shipping X-Git-Url: http://woldlab.caltech.edu/gitweb/?p=mussa.git;a=commitdiff_plain;h=30d36af04be35d43bcaded6b688754e4c9ba63c1 validate app bundle before shipping ticket:180 This path adds some support for parsing otool results so I can test to make sure that object files are only linked against /usr/lib, /System/Library or @executable_path entries. I changed it to use subprocess (which required installation on OS X because of python2.3) and it'll also handle the uploading of packages now. --- diff --git a/makelib/osxdist.py b/makelib/osxdist.py index 02ba255..37fe3b6 100644 --- a/makelib/osxdist.py +++ b/makelib/osxdist.py @@ -1,16 +1,61 @@ #!/usr/bin/env python import os +import re import sys import getopt +try: + from subprocess import call, Popen, PIPE +except ImportError, e: + print >>sys.stderr, "Need to install subprocess or use python >= 2.4" + print >>sys.stderr, "See http://www.lysator.liu.se/~astrand/popen5/" + raise e + from shutil import copy, copytree -def system(cmdline): - #print >>sys.stderr, cmdline - return os.system(cmdline) +class otool(object): + """Gather information from an object file using otool""" + def __init__(self, object_path): + self.otool = "/usr/bin/otool" + self.object_path = object_path + if not os.path.exists(self.object_path): + raise RuntimeError("path not found") + + def __run_command(self, option): + p = Popen([self.otool, option, self.object_path], + bufsize=1, stdin=PIPE, stdout=PIPE, close_fds=True) + os.waitpid(p.pid, 0) + return p + + def _is_object(self): + """"Check to see if object_path is an object-file""" + p = self.__run_command("-h") + header = p.stdout.read() + if re.search("Mach header", header): + return True + else: + return False + isObject = property(_is_object) + + def _get_shared_libraries(self): + """Return list of shared libraries""" + if not self.isObject: + raise RuntimeError("Not object") + p = self.__run_command("-L") + libraries = [] + output_lines = p.stdout.readlines() + # ignore the header, or perhaps we should test it + # to see if it matches self.object_path + for line in output_lines[1:]: + if len(line) > 0: + libraries.append(line.split()[0].strip()) + return libraries + SharedLibraries = property(_get_shared_libraries) def mkdir(path): + """make all the components of a specified path + """ path_list = [] head, tail = os.path.split(path) path_list.insert(0, tail) @@ -22,7 +67,16 @@ def mkdir(path): created_path = os.path.join(created_path, path_element) if not os.path.exists(created_path): os.mkdir(created_path) - + +def ship(filepath, desturl): + """Ship filepath via scp to desturl + """ + if os.path.exists(filepath): + result = call(["/usr/bin/scp", filepath, desturl]) + else: + print >>sys.stderr, "%s doesn't exist" %( filepath ) + return None + # useful information about building dmgs # http://developer.apple.com/documentation/Porting/Conceptual/PortingUnix # http://developer.apple.com/documentation/developertools/Conceptual/SoftwareDistribution/Concepts/sd_disk_images.html @@ -30,31 +84,32 @@ def mkdir(path): def makedmg(dirlist, volname): """copy a list of directories into a dmg named volname """ - # need to detect what the real volume name is - mussa_mount = '/Volumes/%s' %(volname) - mussarw_dmg = '%sw.dmg' %(volname) - mussa_dmg = '%s.dmg' %(volname) - system('hdiutil detach '+mussa_mount) - if os.path.exists(mussa_mount): - print >>sys.stderr, "Something is in", mussa_mount + mount_point = '/Volumes/%s' %(volname) + rwdmg = '%sw.dmg' %(volname) + dmg = '%s.dmg' %(volname) + call(['hdiutil','detach',mount_point]) + if os.path.exists(mount_point): + print >>sys.stderr, "Something is in", mount_point return - if os.path.exists(mussarw_dmg): - os.unlink(mussarw_dmg) - if os.path.exists(mussa_dmg): - os.unlink(mussa_dmg) - system('hdiutil create -size 256m -fs HFS+ -volname "%s" %s'%(volname, mussarw_dmg)) - system('hdiutil attach '+mussarw_dmg) + if os.path.exists(rwdmg): + os.unlink(rwdmg) + if os.path.exists(dmg): + os.unlink(dmg) + create=['hdiutil','create','-size','256m','-fs','HFS+','-volname',volname, rwdmg] + call(create) + call(['hdiutil','attach',rwdmg]) # copy files for d in dirlist: if d[-1] == '/': d = d[:-1] tail = os.path.split(d)[-1] - copytree(d, os.path.join(mussa_mount, tail)) + copytree(d, os.path.join(mount_point, tail)) - system('hdiutil detach '+mussa_mount) - system('hdiutil convert '+mussarw_dmg +' -format UDZO -o '+mussa_dmg) - #system('hdiutil internet-enable -yes '+mussa_dmg) + call(['hdiutil','detach',mount_point]) + call(['hdiutil','convert',rwdmg,'-format','UDZO','-o',dmg]) + #call('hdiutil','internet-enable','-yes',dmg) + return dmg def prelinkqt(app_name, bundle_dir, qt_lib_dir): """ @@ -76,36 +131,74 @@ def prelinkqt(app_name, bundle_dir, qt_lib_dir): mkdir(os.path.split(appframe)[0]) # update binary copy(qtframe, appframe) - system("install_name_tool -id "+exe_path+" "+appframe) - system("install_name_tool -change "+qtframe+" "+exe_path+" "+app_binary) + call(['install_name_tool','-id',exe_path,appframe]) + call(['install_name_tool','-change',qtframe,exe_path,app_binary]) # now that everything is in place change the frameworks # references for frame2 in frameworks: qtframe2 = qt_framework % ({'framework': frame2}) contents_exe_path = "@executable_path/../Frameworks/" + framework_subpath contents_exe_path %= ({'framework': frame2}) - system("install_name_tool -change "+qtframe2+" "+contents_exe_path+" "+ - appframe) - + call(['install_name_tool','-change',qtframe2,contents_exe_path,appframe]) + +def validate_bundle(bundle_path): + """ + Go look through an OS X bundle for references to things that aren't in + /System/Library/Framework /usr/lib or @executable_path + """ + def isAllowed(lib): + """Is lib one of the allowed paths?""" + allowed_paths = [ re.compile("/usr/lib/"), + re.compile("/System/Library/Frameworks/.*"), + re.compile("\@executable_path/.*") ] + for allowed in allowed_paths: + if allowed.match(lib) is not None: + return True + # if no pattern matched its not allowed + return False + + valid = True + for curdir, subdirs, files in os.walk(bundle_path): + for f in files: + curpath = os.path.join(curdir, f) + obj = otool(curpath) + if obj.isObject: + for lib in obj.SharedLibraries: + if not isAllowed(lib): + print >>sys.stderr, "invalid library",lib,"in",curpath + valid = False + return valid + def main(args): qtroot = "/usr/local/qt/4.1.4" - bundle_dir = None app_name = None + build_num = None + bundle_dir = None + dmgfile = None + desturl = None - opts, args = getopt.getopt(args, "b:n:q:h", - ["bundle=", "name=", "qt-root=", "help"]) + opts, args = getopt.getopt(args, "a:b:d:n:q:h", + ["appbundle=", "build-num=", "destination=", + "name=", "qt-root=", "help"]) for option, argument in opts: - if option in ("-b", "--bundle"): + if option in ("-a", "--appbundle"): bundle_dir = argument + elif option in ("-b", "--build-num"): + build_num = argument + elif option in ("-d", "--destination"): + desturl = argument elif option in ("-n", "--name"): app_name = argument elif option in ("-q", "--qt-root"): qtroot = argument elif option in ("-h", "--help"): - print "-b | --bundle specify path to application bundle dir" - print "-n | --name specify application name (and volume name)" - print "-q | --qtdir where is qt is installed" - print "-h | --help how to use" + print "-a | --appbundle specify path to application bundle dir" + print "-b | --build-num specify build number, used to construct" + print " volume name" + print "-n | --name specify application name and base volume name" + print "-q | --qtdir where is qt is installed" + print "-d | --destination scp destination for distribution" + print "-h | --help usage" return 0 # compute bundle name/dir @@ -121,6 +214,11 @@ def main(args): path, file = os.path.split(bundle_dir) app_name = os.path.splitext(file)[0] + if build_num: + dmg_name = app_name + "-" + build_num + else: + dmg_name = app_name + if not os.path.exists(bundle_dir): print >>sys.stderr, "couldn't find an app at %s" % (app_bundle) return 1 @@ -128,7 +226,15 @@ def main(args): qt_lib_dir = os.path.join(qtroot, 'lib') prelinkqt(app_name, bundle_dir, qt_lib_dir) - makedmg([bundle_dir]+args, app_name) + if validate_bundle(bundle_dir): + dmgfile = makedmg([bundle_dir]+args, dmg_name) + else: + print >>sys.stderr, "Invalid libraries found" + return 1 + + if dmgfile and desturl: + print "Uploading",dmgfile,"to",desturl + ship(dmgfile, desturl) if __name__ == "__main__": - main(sys.argv[1:]) + sys.exit(main(sys.argv[1:]))