symbian-qemu-0.9.1-12/python-2.6.1/Lib/plat-mac/bundlebuilder.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 #! /usr/bin/env python
       
     2 
       
     3 """\
       
     4 bundlebuilder.py -- Tools to assemble MacOS X (application) bundles.
       
     5 
       
     6 This module contains two classes to build so called "bundles" for
       
     7 MacOS X. BundleBuilder is a general tool, AppBuilder is a subclass
       
     8 specialized in building application bundles.
       
     9 
       
    10 [Bundle|App]Builder objects are instantiated with a bunch of keyword
       
    11 arguments, and have a build() method that will do all the work. See
       
    12 the class doc strings for a description of the constructor arguments.
       
    13 
       
    14 The module contains a main program that can be used in two ways:
       
    15 
       
    16   % python bundlebuilder.py [options] build
       
    17   % python buildapp.py [options] build
       
    18 
       
    19 Where "buildapp.py" is a user-supplied setup.py-like script following
       
    20 this model:
       
    21 
       
    22   from bundlebuilder import buildapp
       
    23   buildapp(<lots-of-keyword-args>)
       
    24 
       
    25 """
       
    26 
       
    27 
       
    28 __all__ = ["BundleBuilder", "BundleBuilderError", "AppBuilder", "buildapp"]
       
    29 
       
    30 
       
    31 from warnings import warnpy3k
       
    32 warnpy3k("In 3.x, the bundlebuilder module is removed.", stacklevel=2)
       
    33 
       
    34 import sys
       
    35 import os, errno, shutil
       
    36 import imp, marshal
       
    37 import re
       
    38 from copy import deepcopy
       
    39 import getopt
       
    40 from plistlib import Plist
       
    41 from types import FunctionType as function
       
    42 
       
    43 class BundleBuilderError(Exception): pass
       
    44 
       
    45 
       
    46 class Defaults:
       
    47 
       
    48     """Class attributes that don't start with an underscore and are
       
    49     not functions or classmethods are (deep)copied to self.__dict__.
       
    50     This allows for mutable default values.
       
    51     """
       
    52 
       
    53     def __init__(self, **kwargs):
       
    54         defaults = self._getDefaults()
       
    55         defaults.update(kwargs)
       
    56         self.__dict__.update(defaults)
       
    57 
       
    58     def _getDefaults(cls):
       
    59         defaults = {}
       
    60         for base in cls.__bases__:
       
    61             if hasattr(base, "_getDefaults"):
       
    62                 defaults.update(base._getDefaults())
       
    63         for name, value in cls.__dict__.items():
       
    64             if name[0] != "_" and not isinstance(value,
       
    65                     (function, classmethod)):
       
    66                 defaults[name] = deepcopy(value)
       
    67         return defaults
       
    68     _getDefaults = classmethod(_getDefaults)
       
    69 
       
    70 
       
    71 class BundleBuilder(Defaults):
       
    72 
       
    73     """BundleBuilder is a barebones class for assembling bundles. It
       
    74     knows nothing about executables or icons, it only copies files
       
    75     and creates the PkgInfo and Info.plist files.
       
    76     """
       
    77 
       
    78     # (Note that Defaults.__init__ (deep)copies these values to
       
    79     # instance variables. Mutable defaults are therefore safe.)
       
    80 
       
    81     # Name of the bundle, with or without extension.
       
    82     name = None
       
    83 
       
    84     # The property list ("plist")
       
    85     plist = Plist(CFBundleDevelopmentRegion = "English",
       
    86                   CFBundleInfoDictionaryVersion = "6.0")
       
    87 
       
    88     # The type of the bundle.
       
    89     type = "BNDL"
       
    90     # The creator code of the bundle.
       
    91     creator = None
       
    92 
       
    93     # the CFBundleIdentifier (this is used for the preferences file name)
       
    94     bundle_id = None
       
    95 
       
    96     # List of files that have to be copied to <bundle>/Contents/Resources.
       
    97     resources = []
       
    98 
       
    99     # List of (src, dest) tuples; dest should be a path relative to the bundle
       
   100     # (eg. "Contents/Resources/MyStuff/SomeFile.ext).
       
   101     files = []
       
   102 
       
   103     # List of shared libraries (dylibs, Frameworks) to bundle with the app
       
   104     # will be placed in Contents/Frameworks
       
   105     libs = []
       
   106 
       
   107     # Directory where the bundle will be assembled.
       
   108     builddir = "build"
       
   109 
       
   110     # Make symlinks instead copying files. This is handy during debugging, but
       
   111     # makes the bundle non-distributable.
       
   112     symlink = 0
       
   113 
       
   114     # Verbosity level.
       
   115     verbosity = 1
       
   116 
       
   117     # Destination root directory
       
   118     destroot = ""
       
   119 
       
   120     def setup(self):
       
   121         # XXX rethink self.name munging, this is brittle.
       
   122         self.name, ext = os.path.splitext(self.name)
       
   123         if not ext:
       
   124             ext = ".bundle"
       
   125         bundleextension = ext
       
   126         # misc (derived) attributes
       
   127         self.bundlepath = pathjoin(self.builddir, self.name + bundleextension)
       
   128 
       
   129         plist = self.plist
       
   130         plist.CFBundleName = self.name
       
   131         plist.CFBundlePackageType = self.type
       
   132         if self.creator is None:
       
   133             if hasattr(plist, "CFBundleSignature"):
       
   134                 self.creator = plist.CFBundleSignature
       
   135             else:
       
   136                 self.creator = "????"
       
   137         plist.CFBundleSignature = self.creator
       
   138         if self.bundle_id:
       
   139             plist.CFBundleIdentifier = self.bundle_id
       
   140         elif not hasattr(plist, "CFBundleIdentifier"):
       
   141             plist.CFBundleIdentifier = self.name
       
   142 
       
   143     def build(self):
       
   144         """Build the bundle."""
       
   145         builddir = self.builddir
       
   146         if builddir and not os.path.exists(builddir):
       
   147             os.mkdir(builddir)
       
   148         self.message("Building %s" % repr(self.bundlepath), 1)
       
   149         if os.path.exists(self.bundlepath):
       
   150             shutil.rmtree(self.bundlepath)
       
   151         if os.path.exists(self.bundlepath + '~'):
       
   152             shutil.rmtree(self.bundlepath + '~')
       
   153         bp = self.bundlepath
       
   154 
       
   155         # Create the app bundle in a temporary location and then
       
   156         # rename the completed bundle. This way the Finder will
       
   157         # never see an incomplete bundle (where it might pick up
       
   158         # and cache the wrong meta data)
       
   159         self.bundlepath = bp + '~'
       
   160         try:
       
   161             os.mkdir(self.bundlepath)
       
   162             self.preProcess()
       
   163             self._copyFiles()
       
   164             self._addMetaFiles()
       
   165             self.postProcess()
       
   166             os.rename(self.bundlepath, bp)
       
   167         finally:
       
   168             self.bundlepath = bp
       
   169         self.message("Done.", 1)
       
   170 
       
   171     def preProcess(self):
       
   172         """Hook for subclasses."""
       
   173         pass
       
   174     def postProcess(self):
       
   175         """Hook for subclasses."""
       
   176         pass
       
   177 
       
   178     def _addMetaFiles(self):
       
   179         contents = pathjoin(self.bundlepath, "Contents")
       
   180         makedirs(contents)
       
   181         #
       
   182         # Write Contents/PkgInfo
       
   183         assert len(self.type) == len(self.creator) == 4, \
       
   184                 "type and creator must be 4-byte strings."
       
   185         pkginfo = pathjoin(contents, "PkgInfo")
       
   186         f = open(pkginfo, "wb")
       
   187         f.write(self.type + self.creator)
       
   188         f.close()
       
   189         #
       
   190         # Write Contents/Info.plist
       
   191         infoplist = pathjoin(contents, "Info.plist")
       
   192         self.plist.write(infoplist)
       
   193 
       
   194     def _copyFiles(self):
       
   195         files = self.files[:]
       
   196         for path in self.resources:
       
   197             files.append((path, pathjoin("Contents", "Resources",
       
   198                 os.path.basename(path))))
       
   199         for path in self.libs:
       
   200             files.append((path, pathjoin("Contents", "Frameworks",
       
   201                 os.path.basename(path))))
       
   202         if self.symlink:
       
   203             self.message("Making symbolic links", 1)
       
   204             msg = "Making symlink from"
       
   205         else:
       
   206             self.message("Copying files", 1)
       
   207             msg = "Copying"
       
   208         files.sort()
       
   209         for src, dst in files:
       
   210             if os.path.isdir(src):
       
   211                 self.message("%s %s/ to %s/" % (msg, src, dst), 2)
       
   212             else:
       
   213                 self.message("%s %s to %s" % (msg, src, dst), 2)
       
   214             dst = pathjoin(self.bundlepath, dst)
       
   215             if self.symlink:
       
   216                 symlink(src, dst, mkdirs=1)
       
   217             else:
       
   218                 copy(src, dst, mkdirs=1)
       
   219 
       
   220     def message(self, msg, level=0):
       
   221         if level <= self.verbosity:
       
   222             indent = ""
       
   223             if level > 1:
       
   224                 indent = (level - 1) * "  "
       
   225             sys.stderr.write(indent + msg + "\n")
       
   226 
       
   227     def report(self):
       
   228         # XXX something decent
       
   229         pass
       
   230 
       
   231 
       
   232 if __debug__:
       
   233     PYC_EXT = ".pyc"
       
   234 else:
       
   235     PYC_EXT = ".pyo"
       
   236 
       
   237 MAGIC = imp.get_magic()
       
   238 USE_ZIPIMPORT = "zipimport" in sys.builtin_module_names
       
   239 
       
   240 # For standalone apps, we have our own minimal site.py. We don't need
       
   241 # all the cruft of the real site.py.
       
   242 SITE_PY = """\
       
   243 import sys
       
   244 if not %(semi_standalone)s:
       
   245     del sys.path[1:]  # sys.path[0] is Contents/Resources/
       
   246 """
       
   247 
       
   248 if USE_ZIPIMPORT:
       
   249     ZIP_ARCHIVE = "Modules.zip"
       
   250     SITE_PY += "sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
       
   251     def getPycData(fullname, code, ispkg):
       
   252         if ispkg:
       
   253             fullname += ".__init__"
       
   254         path = fullname.replace(".", os.sep) + PYC_EXT
       
   255         return path, MAGIC + '\0\0\0\0' + marshal.dumps(code)
       
   256 
       
   257 #
       
   258 # Extension modules can't be in the modules zip archive, so a placeholder
       
   259 # is added instead, that loads the extension from a specified location.
       
   260 #
       
   261 EXT_LOADER = """\
       
   262 def __load():
       
   263     import imp, sys, os
       
   264     for p in sys.path:
       
   265         path = os.path.join(p, "%(filename)s")
       
   266         if os.path.exists(path):
       
   267             break
       
   268     else:
       
   269         assert 0, "file not found: %(filename)s"
       
   270     mod = imp.load_dynamic("%(name)s", path)
       
   271 
       
   272 __load()
       
   273 del __load
       
   274 """
       
   275 
       
   276 MAYMISS_MODULES = ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath',
       
   277     'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize',
       
   278     'org.python.core', 'riscos', 'riscosenviron', 'riscospath'
       
   279 ]
       
   280 
       
   281 STRIP_EXEC = "/usr/bin/strip"
       
   282 
       
   283 #
       
   284 # We're using a stock interpreter to run the app, yet we need
       
   285 # a way to pass the Python main program to the interpreter. The
       
   286 # bootstrapping script fires up the interpreter with the right
       
   287 # arguments. os.execve() is used as OSX doesn't like us to
       
   288 # start a real new process. Also, the executable name must match
       
   289 # the CFBundleExecutable value in the Info.plist, so we lie
       
   290 # deliberately with argv[0]. The actual Python executable is
       
   291 # passed in an environment variable so we can "repair"
       
   292 # sys.executable later.
       
   293 #
       
   294 BOOTSTRAP_SCRIPT = """\
       
   295 #!%(hashbang)s
       
   296 
       
   297 import sys, os
       
   298 execdir = os.path.dirname(sys.argv[0])
       
   299 executable = os.path.join(execdir, "%(executable)s")
       
   300 resdir = os.path.join(os.path.dirname(execdir), "Resources")
       
   301 libdir = os.path.join(os.path.dirname(execdir), "Frameworks")
       
   302 mainprogram = os.path.join(resdir, "%(mainprogram)s")
       
   303 
       
   304 sys.argv.insert(1, mainprogram)
       
   305 if %(standalone)s or %(semi_standalone)s:
       
   306     os.environ["PYTHONPATH"] = resdir
       
   307     if %(standalone)s:
       
   308         os.environ["PYTHONHOME"] = resdir
       
   309 else:
       
   310     pypath = os.getenv("PYTHONPATH", "")
       
   311     if pypath:
       
   312         pypath = ":" + pypath
       
   313     os.environ["PYTHONPATH"] = resdir + pypath
       
   314 os.environ["PYTHONEXECUTABLE"] = executable
       
   315 os.environ["DYLD_LIBRARY_PATH"] = libdir
       
   316 os.environ["DYLD_FRAMEWORK_PATH"] = libdir
       
   317 os.execve(executable, sys.argv, os.environ)
       
   318 """
       
   319 
       
   320 
       
   321 #
       
   322 # Optional wrapper that converts "dropped files" into sys.argv values.
       
   323 #
       
   324 ARGV_EMULATOR = """\
       
   325 import argvemulator, os
       
   326 
       
   327 argvemulator.ArgvCollector().mainloop()
       
   328 execfile(os.path.join(os.path.split(__file__)[0], "%(realmainprogram)s"))
       
   329 """
       
   330 
       
   331 #
       
   332 # When building a standalone app with Python.framework, we need to copy
       
   333 # a subset from Python.framework to the bundle. The following list
       
   334 # specifies exactly what items we'll copy.
       
   335 #
       
   336 PYTHONFRAMEWORKGOODIES = [
       
   337     "Python",  # the Python core library
       
   338     "Resources/English.lproj",
       
   339     "Resources/Info.plist",
       
   340     "Resources/version.plist",
       
   341 ]
       
   342 
       
   343 def isFramework():
       
   344     return sys.exec_prefix.find("Python.framework") > 0
       
   345 
       
   346 
       
   347 LIB = os.path.join(sys.prefix, "lib", "python" + sys.version[:3])
       
   348 SITE_PACKAGES = os.path.join(LIB, "site-packages")
       
   349 
       
   350 
       
   351 class AppBuilder(BundleBuilder):
       
   352 
       
   353     # Override type of the bundle.
       
   354     type = "APPL"
       
   355 
       
   356     # platform, name of the subfolder of Contents that contains the executable.
       
   357     platform = "MacOS"
       
   358 
       
   359     # A Python main program. If this argument is given, the main
       
   360     # executable in the bundle will be a small wrapper that invokes
       
   361     # the main program. (XXX Discuss why.)
       
   362     mainprogram = None
       
   363 
       
   364     # The main executable. If a Python main program is specified
       
   365     # the executable will be copied to Resources and be invoked
       
   366     # by the wrapper program mentioned above. Otherwise it will
       
   367     # simply be used as the main executable.
       
   368     executable = None
       
   369 
       
   370     # The name of the main nib, for Cocoa apps. *Must* be specified
       
   371     # when building a Cocoa app.
       
   372     nibname = None
       
   373 
       
   374     # The name of the icon file to be copied to Resources and used for
       
   375     # the Finder icon.
       
   376     iconfile = None
       
   377 
       
   378     # Symlink the executable instead of copying it.
       
   379     symlink_exec = 0
       
   380 
       
   381     # If True, build standalone app.
       
   382     standalone = 0
       
   383 
       
   384     # If True, build semi-standalone app (only includes third-party modules).
       
   385     semi_standalone = 0
       
   386 
       
   387     # If set, use this for #! lines in stead of sys.executable
       
   388     python = None
       
   389 
       
   390     # If True, add a real main program that emulates sys.argv before calling
       
   391     # mainprogram
       
   392     argv_emulation = 0
       
   393 
       
   394     # The following attributes are only used when building a standalone app.
       
   395 
       
   396     # Exclude these modules.
       
   397     excludeModules = []
       
   398 
       
   399     # Include these modules.
       
   400     includeModules = []
       
   401 
       
   402     # Include these packages.
       
   403     includePackages = []
       
   404 
       
   405     # Strip binaries from debug info.
       
   406     strip = 0
       
   407 
       
   408     # Found Python modules: [(name, codeobject, ispkg), ...]
       
   409     pymodules = []
       
   410 
       
   411     # Modules that modulefinder couldn't find:
       
   412     missingModules = []
       
   413     maybeMissingModules = []
       
   414 
       
   415     def setup(self):
       
   416         if ((self.standalone or self.semi_standalone)
       
   417             and self.mainprogram is None):
       
   418             raise BundleBuilderError, ("must specify 'mainprogram' when "
       
   419                     "building a standalone application.")
       
   420         if self.mainprogram is None and self.executable is None:
       
   421             raise BundleBuilderError, ("must specify either or both of "
       
   422                     "'executable' and 'mainprogram'")
       
   423 
       
   424         self.execdir = pathjoin("Contents", self.platform)
       
   425 
       
   426         if self.name is not None:
       
   427             pass
       
   428         elif self.mainprogram is not None:
       
   429             self.name = os.path.splitext(os.path.basename(self.mainprogram))[0]
       
   430         elif executable is not None:
       
   431             self.name = os.path.splitext(os.path.basename(self.executable))[0]
       
   432         if self.name[-4:] != ".app":
       
   433             self.name += ".app"
       
   434 
       
   435         if self.executable is None:
       
   436             if not self.standalone and not isFramework():
       
   437                 self.symlink_exec = 1
       
   438             if self.python:
       
   439                 self.executable = self.python
       
   440             else:
       
   441                 self.executable = sys.executable
       
   442 
       
   443         if self.nibname:
       
   444             self.plist.NSMainNibFile = self.nibname
       
   445             if not hasattr(self.plist, "NSPrincipalClass"):
       
   446                 self.plist.NSPrincipalClass = "NSApplication"
       
   447 
       
   448         if self.standalone and isFramework():
       
   449             self.addPythonFramework()
       
   450 
       
   451         BundleBuilder.setup(self)
       
   452 
       
   453         self.plist.CFBundleExecutable = self.name
       
   454 
       
   455         if self.standalone or self.semi_standalone:
       
   456             self.findDependencies()
       
   457 
       
   458     def preProcess(self):
       
   459         resdir = "Contents/Resources"
       
   460         if self.executable is not None:
       
   461             if self.mainprogram is None:
       
   462                 execname = self.name
       
   463             else:
       
   464                 execname = os.path.basename(self.executable)
       
   465             execpath = pathjoin(self.execdir, execname)
       
   466             if not self.symlink_exec:
       
   467                 self.files.append((self.destroot + self.executable, execpath))
       
   468             self.execpath = execpath
       
   469 
       
   470         if self.mainprogram is not None:
       
   471             mainprogram = os.path.basename(self.mainprogram)
       
   472             self.files.append((self.mainprogram, pathjoin(resdir, mainprogram)))
       
   473             if self.argv_emulation:
       
   474                 # Change the main program, and create the helper main program (which
       
   475                 # does argv collection and then calls the real main).
       
   476                 # Also update the included modules (if we're creating a standalone
       
   477                 # program) and the plist
       
   478                 realmainprogram = mainprogram
       
   479                 mainprogram = '__argvemulator_' + mainprogram
       
   480                 resdirpath = pathjoin(self.bundlepath, resdir)
       
   481                 mainprogrampath = pathjoin(resdirpath, mainprogram)
       
   482                 makedirs(resdirpath)
       
   483                 open(mainprogrampath, "w").write(ARGV_EMULATOR % locals())
       
   484                 if self.standalone or self.semi_standalone:
       
   485                     self.includeModules.append("argvemulator")
       
   486                     self.includeModules.append("os")
       
   487                 if not self.plist.has_key("CFBundleDocumentTypes"):
       
   488                     self.plist["CFBundleDocumentTypes"] = [
       
   489                         { "CFBundleTypeOSTypes" : [
       
   490                             "****",
       
   491                             "fold",
       
   492                             "disk"],
       
   493                           "CFBundleTypeRole": "Viewer"}]
       
   494             # Write bootstrap script
       
   495             executable = os.path.basename(self.executable)
       
   496             execdir = pathjoin(self.bundlepath, self.execdir)
       
   497             bootstrappath = pathjoin(execdir, self.name)
       
   498             makedirs(execdir)
       
   499             if self.standalone or self.semi_standalone:
       
   500                 # XXX we're screwed when the end user has deleted
       
   501                 # /usr/bin/python
       
   502                 hashbang = "/usr/bin/python"
       
   503             elif self.python:
       
   504                 hashbang = self.python
       
   505             else:
       
   506                 hashbang = os.path.realpath(sys.executable)
       
   507             standalone = self.standalone
       
   508             semi_standalone = self.semi_standalone
       
   509             open(bootstrappath, "w").write(BOOTSTRAP_SCRIPT % locals())
       
   510             os.chmod(bootstrappath, 0775)
       
   511 
       
   512         if self.iconfile is not None:
       
   513             iconbase = os.path.basename(self.iconfile)
       
   514             self.plist.CFBundleIconFile = iconbase
       
   515             self.files.append((self.iconfile, pathjoin(resdir, iconbase)))
       
   516 
       
   517     def postProcess(self):
       
   518         if self.standalone or self.semi_standalone:
       
   519             self.addPythonModules()
       
   520         if self.strip and not self.symlink:
       
   521             self.stripBinaries()
       
   522 
       
   523         if self.symlink_exec and self.executable:
       
   524             self.message("Symlinking executable %s to %s" % (self.executable,
       
   525                     self.execpath), 2)
       
   526             dst = pathjoin(self.bundlepath, self.execpath)
       
   527             makedirs(os.path.dirname(dst))
       
   528             os.symlink(os.path.abspath(self.executable), dst)
       
   529 
       
   530         if self.missingModules or self.maybeMissingModules:
       
   531             self.reportMissing()
       
   532 
       
   533     def addPythonFramework(self):
       
   534         # If we're building a standalone app with Python.framework,
       
   535         # include a minimal subset of Python.framework, *unless*
       
   536         # Python.framework was specified manually in self.libs.
       
   537         for lib in self.libs:
       
   538             if os.path.basename(lib) == "Python.framework":
       
   539                 # a Python.framework was specified as a library
       
   540                 return
       
   541 
       
   542         frameworkpath = sys.exec_prefix[:sys.exec_prefix.find(
       
   543             "Python.framework") + len("Python.framework")]
       
   544 
       
   545         version = sys.version[:3]
       
   546         frameworkpath = pathjoin(frameworkpath, "Versions", version)
       
   547         destbase = pathjoin("Contents", "Frameworks", "Python.framework",
       
   548                             "Versions", version)
       
   549         for item in PYTHONFRAMEWORKGOODIES:
       
   550             src = pathjoin(frameworkpath, item)
       
   551             dst = pathjoin(destbase, item)
       
   552             self.files.append((src, dst))
       
   553 
       
   554     def _getSiteCode(self):
       
   555         return compile(SITE_PY % {"semi_standalone": self.semi_standalone},
       
   556                      "<-bundlebuilder.py->", "exec")
       
   557 
       
   558     def addPythonModules(self):
       
   559         self.message("Adding Python modules", 1)
       
   560 
       
   561         if USE_ZIPIMPORT:
       
   562             # Create a zip file containing all modules as pyc.
       
   563             import zipfile
       
   564             relpath = pathjoin("Contents", "Resources", ZIP_ARCHIVE)
       
   565             abspath = pathjoin(self.bundlepath, relpath)
       
   566             zf = zipfile.ZipFile(abspath, "w", zipfile.ZIP_DEFLATED)
       
   567             for name, code, ispkg in self.pymodules:
       
   568                 self.message("Adding Python module %s" % name, 2)
       
   569                 path, pyc = getPycData(name, code, ispkg)
       
   570                 zf.writestr(path, pyc)
       
   571             zf.close()
       
   572             # add site.pyc
       
   573             sitepath = pathjoin(self.bundlepath, "Contents", "Resources",
       
   574                     "site" + PYC_EXT)
       
   575             writePyc(self._getSiteCode(), sitepath)
       
   576         else:
       
   577             # Create individual .pyc files.
       
   578             for name, code, ispkg in self.pymodules:
       
   579                 if ispkg:
       
   580                     name += ".__init__"
       
   581                 path = name.split(".")
       
   582                 path = pathjoin("Contents", "Resources", *path) + PYC_EXT
       
   583 
       
   584                 if ispkg:
       
   585                     self.message("Adding Python package %s" % path, 2)
       
   586                 else:
       
   587                     self.message("Adding Python module %s" % path, 2)
       
   588 
       
   589                 abspath = pathjoin(self.bundlepath, path)
       
   590                 makedirs(os.path.dirname(abspath))
       
   591                 writePyc(code, abspath)
       
   592 
       
   593     def stripBinaries(self):
       
   594         if not os.path.exists(STRIP_EXEC):
       
   595             self.message("Error: can't strip binaries: no strip program at "
       
   596                 "%s" % STRIP_EXEC, 0)
       
   597         else:
       
   598             import stat
       
   599             self.message("Stripping binaries", 1)
       
   600             def walk(top):
       
   601                 for name in os.listdir(top):
       
   602                     path = pathjoin(top, name)
       
   603                     if os.path.islink(path):
       
   604                         continue
       
   605                     if os.path.isdir(path):
       
   606                         walk(path)
       
   607                     else:
       
   608                         mod = os.stat(path)[stat.ST_MODE]
       
   609                         if not (mod & 0100):
       
   610                             continue
       
   611                         relpath = path[len(self.bundlepath):]
       
   612                         self.message("Stripping %s" % relpath, 2)
       
   613                         inf, outf = os.popen4("%s -S \"%s\"" %
       
   614                                               (STRIP_EXEC, path))
       
   615                         output = outf.read().strip()
       
   616                         if output:
       
   617                             # usually not a real problem, like when we're
       
   618                             # trying to strip a script
       
   619                             self.message("Problem stripping %s:" % relpath, 3)
       
   620                             self.message(output, 3)
       
   621             walk(self.bundlepath)
       
   622 
       
   623     def findDependencies(self):
       
   624         self.message("Finding module dependencies", 1)
       
   625         import modulefinder
       
   626         mf = modulefinder.ModuleFinder(excludes=self.excludeModules)
       
   627         if USE_ZIPIMPORT:
       
   628             # zipimport imports zlib, must add it manually
       
   629             mf.import_hook("zlib")
       
   630         # manually add our own site.py
       
   631         site = mf.add_module("site")
       
   632         site.__code__ = self._getSiteCode()
       
   633         mf.scan_code(site.__code__, site)
       
   634 
       
   635         # warnings.py gets imported implicitly from C
       
   636         mf.import_hook("warnings")
       
   637 
       
   638         includeModules = self.includeModules[:]
       
   639         for name in self.includePackages:
       
   640             includeModules.extend(findPackageContents(name).keys())
       
   641         for name in includeModules:
       
   642             try:
       
   643                 mf.import_hook(name)
       
   644             except ImportError:
       
   645                 self.missingModules.append(name)
       
   646 
       
   647         mf.run_script(self.mainprogram)
       
   648         modules = mf.modules.items()
       
   649         modules.sort()
       
   650         for name, mod in modules:
       
   651             path = mod.__file__
       
   652             if path and self.semi_standalone:
       
   653                 # skip the standard library
       
   654                 if path.startswith(LIB) and not path.startswith(SITE_PACKAGES):
       
   655                     continue
       
   656             if path and mod.__code__ is None:
       
   657                 # C extension
       
   658                 filename = os.path.basename(path)
       
   659                 pathitems = name.split(".")[:-1] + [filename]
       
   660                 dstpath = pathjoin(*pathitems)
       
   661                 if USE_ZIPIMPORT:
       
   662                     if name != "zlib":
       
   663                         # neatly pack all extension modules in a subdirectory,
       
   664                         # except zlib, since it's neccesary for bootstrapping.
       
   665                         dstpath = pathjoin("ExtensionModules", dstpath)
       
   666                     # Python modules are stored in a Zip archive, but put
       
   667                     # extensions in Contents/Resources/. Add a tiny "loader"
       
   668                     # program in the Zip archive. Due to Thomas Heller.
       
   669                     source = EXT_LOADER % {"name": name, "filename": dstpath}
       
   670                     code = compile(source, "<dynloader for %s>" % name, "exec")
       
   671                     mod.__code__ = code
       
   672                 self.files.append((path, pathjoin("Contents", "Resources", dstpath)))
       
   673             if mod.__code__ is not None:
       
   674                 ispkg = mod.__path__ is not None
       
   675                 if not USE_ZIPIMPORT or name != "site":
       
   676                     # Our site.py is doing the bootstrapping, so we must
       
   677                     # include a real .pyc file if USE_ZIPIMPORT is True.
       
   678                     self.pymodules.append((name, mod.__code__, ispkg))
       
   679 
       
   680         if hasattr(mf, "any_missing_maybe"):
       
   681             missing, maybe = mf.any_missing_maybe()
       
   682         else:
       
   683             missing = mf.any_missing()
       
   684             maybe = []
       
   685         self.missingModules.extend(missing)
       
   686         self.maybeMissingModules.extend(maybe)
       
   687 
       
   688     def reportMissing(self):
       
   689         missing = [name for name in self.missingModules
       
   690                 if name not in MAYMISS_MODULES]
       
   691         if self.maybeMissingModules:
       
   692             maybe = self.maybeMissingModules
       
   693         else:
       
   694             maybe = [name for name in missing if "." in name]
       
   695             missing = [name for name in missing if "." not in name]
       
   696         missing.sort()
       
   697         maybe.sort()
       
   698         if maybe:
       
   699             self.message("Warning: couldn't find the following submodules:", 1)
       
   700             self.message("    (Note that these could be false alarms -- "
       
   701                          "it's not always", 1)
       
   702             self.message("    possible to distinguish between \"from package "
       
   703                          "import submodule\" ", 1)
       
   704             self.message("    and \"from package import name\")", 1)
       
   705             for name in maybe:
       
   706                 self.message("  ? " + name, 1)
       
   707         if missing:
       
   708             self.message("Warning: couldn't find the following modules:", 1)
       
   709             for name in missing:
       
   710                 self.message("  ? " + name, 1)
       
   711 
       
   712     def report(self):
       
   713         # XXX something decent
       
   714         import pprint
       
   715         pprint.pprint(self.__dict__)
       
   716         if self.standalone or self.semi_standalone:
       
   717             self.reportMissing()
       
   718 
       
   719 #
       
   720 # Utilities.
       
   721 #
       
   722 
       
   723 SUFFIXES = [_suf for _suf, _mode, _tp in imp.get_suffixes()]
       
   724 identifierRE = re.compile(r"[_a-zA-z][_a-zA-Z0-9]*$")
       
   725 
       
   726 def findPackageContents(name, searchpath=None):
       
   727     head = name.split(".")[-1]
       
   728     if identifierRE.match(head) is None:
       
   729         return {}
       
   730     try:
       
   731         fp, path, (ext, mode, tp) = imp.find_module(head, searchpath)
       
   732     except ImportError:
       
   733         return {}
       
   734     modules = {name: None}
       
   735     if tp == imp.PKG_DIRECTORY and path:
       
   736         files = os.listdir(path)
       
   737         for sub in files:
       
   738             sub, ext = os.path.splitext(sub)
       
   739             fullname = name + "." + sub
       
   740             if sub != "__init__" and fullname not in modules:
       
   741                 modules.update(findPackageContents(fullname, [path]))
       
   742     return modules
       
   743 
       
   744 def writePyc(code, path):
       
   745     f = open(path, "wb")
       
   746     f.write(MAGIC)
       
   747     f.write("\0" * 4)  # don't bother about a time stamp
       
   748     marshal.dump(code, f)
       
   749     f.close()
       
   750 
       
   751 def copy(src, dst, mkdirs=0):
       
   752     """Copy a file or a directory."""
       
   753     if mkdirs:
       
   754         makedirs(os.path.dirname(dst))
       
   755     if os.path.isdir(src):
       
   756         shutil.copytree(src, dst, symlinks=1)
       
   757     else:
       
   758         shutil.copy2(src, dst)
       
   759 
       
   760 def copytodir(src, dstdir):
       
   761     """Copy a file or a directory to an existing directory."""
       
   762     dst = pathjoin(dstdir, os.path.basename(src))
       
   763     copy(src, dst)
       
   764 
       
   765 def makedirs(dir):
       
   766     """Make all directories leading up to 'dir' including the leaf
       
   767     directory. Don't moan if any path element already exists."""
       
   768     try:
       
   769         os.makedirs(dir)
       
   770     except OSError, why:
       
   771         if why.errno != errno.EEXIST:
       
   772             raise
       
   773 
       
   774 def symlink(src, dst, mkdirs=0):
       
   775     """Copy a file or a directory."""
       
   776     if not os.path.exists(src):
       
   777         raise IOError, "No such file or directory: '%s'" % src
       
   778     if mkdirs:
       
   779         makedirs(os.path.dirname(dst))
       
   780     os.symlink(os.path.abspath(src), dst)
       
   781 
       
   782 def pathjoin(*args):
       
   783     """Safe wrapper for os.path.join: asserts that all but the first
       
   784     argument are relative paths."""
       
   785     for seg in args[1:]:
       
   786         assert seg[0] != "/"
       
   787     return os.path.join(*args)
       
   788 
       
   789 
       
   790 cmdline_doc = """\
       
   791 Usage:
       
   792   python bundlebuilder.py [options] command
       
   793   python mybuildscript.py [options] command
       
   794 
       
   795 Commands:
       
   796   build      build the application
       
   797   report     print a report
       
   798 
       
   799 Options:
       
   800   -b, --builddir=DIR     the build directory; defaults to "build"
       
   801   -n, --name=NAME        application name
       
   802   -r, --resource=FILE    extra file or folder to be copied to Resources
       
   803   -f, --file=SRC:DST     extra file or folder to be copied into the bundle;
       
   804                          DST must be a path relative to the bundle root
       
   805   -e, --executable=FILE  the executable to be used
       
   806   -m, --mainprogram=FILE the Python main program
       
   807   -a, --argv             add a wrapper main program to create sys.argv
       
   808   -p, --plist=FILE       .plist file (default: generate one)
       
   809       --nib=NAME         main nib name
       
   810   -c, --creator=CCCC     4-char creator code (default: '????')
       
   811       --iconfile=FILE    filename of the icon (an .icns file) to be used
       
   812                          as the Finder icon
       
   813       --bundle-id=ID     the CFBundleIdentifier, in reverse-dns format
       
   814                          (eg. org.python.BuildApplet; this is used for
       
   815                          the preferences file name)
       
   816   -l, --link             symlink files/folder instead of copying them
       
   817       --link-exec        symlink the executable instead of copying it
       
   818       --standalone       build a standalone application, which is fully
       
   819                          independent of a Python installation
       
   820       --semi-standalone  build a standalone application, which depends on
       
   821                          an installed Python, yet includes all third-party
       
   822                          modules.
       
   823       --python=FILE      Python to use in #! line in stead of current Python
       
   824       --lib=FILE         shared library or framework to be copied into
       
   825                          the bundle
       
   826   -x, --exclude=MODULE   exclude module (with --(semi-)standalone)
       
   827   -i, --include=MODULE   include module (with --(semi-)standalone)
       
   828       --package=PACKAGE  include a whole package (with --(semi-)standalone)
       
   829       --strip            strip binaries (remove debug info)
       
   830   -v, --verbose          increase verbosity level
       
   831   -q, --quiet            decrease verbosity level
       
   832   -h, --help             print this message
       
   833 """
       
   834 
       
   835 def usage(msg=None):
       
   836     if msg:
       
   837         print msg
       
   838     print cmdline_doc
       
   839     sys.exit(1)
       
   840 
       
   841 def main(builder=None):
       
   842     if builder is None:
       
   843         builder = AppBuilder(verbosity=1)
       
   844 
       
   845     shortopts = "b:n:r:f:e:m:c:p:lx:i:hvqa"
       
   846     longopts = ("builddir=", "name=", "resource=", "file=", "executable=",
       
   847         "mainprogram=", "creator=", "nib=", "plist=", "link",
       
   848         "link-exec", "help", "verbose", "quiet", "argv", "standalone",
       
   849         "exclude=", "include=", "package=", "strip", "iconfile=",
       
   850         "lib=", "python=", "semi-standalone", "bundle-id=", "destroot=")
       
   851 
       
   852     try:
       
   853         options, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
       
   854     except getopt.error:
       
   855         usage()
       
   856 
       
   857     for opt, arg in options:
       
   858         if opt in ('-b', '--builddir'):
       
   859             builder.builddir = arg
       
   860         elif opt in ('-n', '--name'):
       
   861             builder.name = arg
       
   862         elif opt in ('-r', '--resource'):
       
   863             builder.resources.append(os.path.normpath(arg))
       
   864         elif opt in ('-f', '--file'):
       
   865             srcdst = arg.split(':')
       
   866             if len(srcdst) != 2:
       
   867                 usage("-f or --file argument must be two paths, "
       
   868                       "separated by a colon")
       
   869             builder.files.append(srcdst)
       
   870         elif opt in ('-e', '--executable'):
       
   871             builder.executable = arg
       
   872         elif opt in ('-m', '--mainprogram'):
       
   873             builder.mainprogram = arg
       
   874         elif opt in ('-a', '--argv'):
       
   875             builder.argv_emulation = 1
       
   876         elif opt in ('-c', '--creator'):
       
   877             builder.creator = arg
       
   878         elif opt == '--bundle-id':
       
   879             builder.bundle_id = arg
       
   880         elif opt == '--iconfile':
       
   881             builder.iconfile = arg
       
   882         elif opt == "--lib":
       
   883             builder.libs.append(os.path.normpath(arg))
       
   884         elif opt == "--nib":
       
   885             builder.nibname = arg
       
   886         elif opt in ('-p', '--plist'):
       
   887             builder.plist = Plist.fromFile(arg)
       
   888         elif opt in ('-l', '--link'):
       
   889             builder.symlink = 1
       
   890         elif opt == '--link-exec':
       
   891             builder.symlink_exec = 1
       
   892         elif opt in ('-h', '--help'):
       
   893             usage()
       
   894         elif opt in ('-v', '--verbose'):
       
   895             builder.verbosity += 1
       
   896         elif opt in ('-q', '--quiet'):
       
   897             builder.verbosity -= 1
       
   898         elif opt == '--standalone':
       
   899             builder.standalone = 1
       
   900         elif opt == '--semi-standalone':
       
   901             builder.semi_standalone = 1
       
   902         elif opt == '--python':
       
   903             builder.python = arg
       
   904         elif opt in ('-x', '--exclude'):
       
   905             builder.excludeModules.append(arg)
       
   906         elif opt in ('-i', '--include'):
       
   907             builder.includeModules.append(arg)
       
   908         elif opt == '--package':
       
   909             builder.includePackages.append(arg)
       
   910         elif opt == '--strip':
       
   911             builder.strip = 1
       
   912         elif opt == '--destroot':
       
   913             builder.destroot = arg
       
   914 
       
   915     if len(args) != 1:
       
   916         usage("Must specify one command ('build', 'report' or 'help')")
       
   917     command = args[0]
       
   918 
       
   919     if command == "build":
       
   920         builder.setup()
       
   921         builder.build()
       
   922     elif command == "report":
       
   923         builder.setup()
       
   924         builder.report()
       
   925     elif command == "help":
       
   926         usage()
       
   927     else:
       
   928         usage("Unknown command '%s'" % command)
       
   929 
       
   930 
       
   931 def buildapp(**kwargs):
       
   932     builder = AppBuilder(**kwargs)
       
   933     main(builder)
       
   934 
       
   935 
       
   936 if __name__ == "__main__":
       
   937     main()