Update mussa to build on ubuntu 10.04 with qt 4.6.2 +boost 1.40.0.1
[mussa.git] / makelib / osxdist.py
index 500cdd5b729b0f3616d5103e7746e2cea7d48aa3..63da570184492ff60687467f7143e50743524709 100644 (file)
 #!/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:
-    copytree(d, os.path.join(mussa_mount, d))
+    if d[-1] == '/':
+      d = d[:-1]
+    tail = os.path.split(d)[-1]
+    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 main(args):
-  framework_subpath = os.path.join("%(framework)s.framework", "Versions", "4.0", "%(framework)s")
-  frameworks = ['QtCore', 'QtGui', 'QtOpenGL']
+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.
+  """
+  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_lib_dir="/usr/local/qt-4.1.1/lib"
   qt_framework=os.path.join(qt_lib_dir, framework_subpath)
-
-  app_name="mussagl"
-  app_dir=app_name+".app"
-  app_binary=app_dir+"/Contents/MacOS/"+app_name
-  app_framework=os.path.join(app_dir, "Contents", "Frameworks", framework_subpath)
-  if not os.path.exists(app_dir):
-    print >>sys.stderr, "please build mussagl first"
-    return 1
-
+  app_binary=bundle_dir+"/Contents/MacOS/"+app_name
+  app_framework=os.path.join(bundle_dir, "Contents", "Frameworks", framework_subpath)
   # install frameworks and update binary
   for frame in frameworks:
     qtframe = qt_framework % ({'framework': frame})
@@ -72,17 +138,119 @@ def main(args):
     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)
-  makedmg([app_dir], 'mussa')
+      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 = "/opt/local/libexec/qt4-mac/"
+  app_name = None
+  build_num = None
+  bundle_dir = None
+  dmgfile = None
+  desturl = None
+  prelink_only = False
+  
+  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 ("-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 "-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
+  if bundle_dir is None and app_name is None:
+    print >>sys.stderr, "I need a name or bundle path"
+    return 1
+  elif bundle_dir is None:
+    bundle_dir = app_name + ".app"
+  elif app_name is None:
+    # strip off trailing /
+    if bundle_dir[-1] == os.path.sep:
+      bundle_dir = bundle_dir[:-1]
+    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)
+  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:]))