#!/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)
- while len(head) > 0:
+ while head != os.path.sep and len(head) > 0:
head, tail = os.path.split(head)
path_list.insert(0, tail)
+ # FIXME: For some reason the above os.path.split lost the root '/'
+ if path[0] == os.path.sep and path_list[0] != os.path.sep:
+ path_list.insert(0, os.path.sep)
created_path = ""
for path_element in path_list:
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
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 64m -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))
+ if (os.path.isdir(d)):
+ copytree(d, os.path.join(mount_point, tail))
+ else:
+ copy(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):
"""
OS X's treatment of dynamic loading is annoying
properly prelink all the annoying qt components.
"""
- framework_subpath = os.path.join("%(framework)s.framework", "Versions", "4.0", "%(framework)s")
- frameworks = ['QtCore', 'QtGui', 'QtOpenGL']
+ print >>sys.stderr, "Prelinking", app_name, "in", bundle_dir,
+ framework_subpath = os.path.join("%(framework)s.framework", "Versions", "4", "%(framework)s")
+ frameworks = ['QtCore', 'QtGui', 'QtOpenGL', 'QtAssistant', 'QtNetwork']
qt_framework=os.path.join(qt_lib_dir, framework_subpath)
app_binary=bundle_dir+"/Contents/MacOS/"+app_name
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])
+ print "."
+
+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.1"
- bundle_dir = None
+ qtroot = "/usr/local/qt/4.2.3"
app_name = None
+ build_num = None
+ bundle_dir = None
+ dmgfile = None
+ desturl = None
+ prelink_only = False
- 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=", "prelink-only", "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 ("--prelink-only"):
+ prelink_only = True
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
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
+
+ bundle_dir = os.path.expanduser(bundle_dir)
+
if not os.path.exists(bundle_dir):
print >>sys.stderr, "couldn't find an app at %s" % (app_bundle)
return 1
qt_lib_dir = os.path.join(qtroot, 'lib')
prelinkqt(app_name, bundle_dir, qt_lib_dir)
- makedmg([bundle_dir]+args, app_name)
+ if not validate_bundle(bundle_dir):
+ print >>sys.stderr, "Invalid libraries found"
+ return 1
+
+ if prelink_only:
+ return 0
+
+ dmgfile = makedmg([bundle_dir]+args, dmg_name)
+ 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:]))