4 bundlebuilder.py -- Tools to assemble MacOS X (application) bundles. 
   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. 
  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. 
  14 The module contains a main program that can be used in two ways: 
  16   % python bundlebuilder.py [options] build 
  17   % python buildapp.py [options] build 
  19 Where "buildapp.py" is a user-supplied setup.py-like script following 
  22   from bundlebuilder import buildapp 
  23   buildapp(<lots-of-keyword-args>) 
  28 __all__ 
= ["BundleBuilder", "BundleBuilderError", "AppBuilder", "buildapp"] 
  32 import os
, errno
, shutil
 
  35 from copy 
import deepcopy
 
  37 from plistlib 
import Plist
 
  38 from types 
import FunctionType 
as function
 
  40 class BundleBuilderError(Exception): pass 
  45     """Class attributes that don't start with an underscore and are 
  46     not functions or classmethods are (deep)copied to self.__dict__. 
  47     This allows for mutable default values. 
  50     def __init__(self
, **kwargs
): 
  51         defaults 
= self
._getDefaults
() 
  52         defaults
.update(kwargs
) 
  53         self
.__dict
__.update(defaults
) 
  55     def _getDefaults(cls
): 
  57         for base 
in cls
.__bases
__: 
  58             if hasattr(base
, "_getDefaults"): 
  59                 defaults
.update(base
._getDefaults
()) 
  60         for name
, value 
in cls
.__dict
__.items(): 
  61             if name
[0] != "_" and not isinstance(value
, 
  62                     (function
, classmethod)): 
  63                 defaults
[name
] = deepcopy(value
) 
  65     _getDefaults 
= classmethod(_getDefaults
) 
  68 class BundleBuilder(Defaults
): 
  70     """BundleBuilder is a barebones class for assembling bundles. It 
  71     knows nothing about executables or icons, it only copies files 
  72     and creates the PkgInfo and Info.plist files. 
  75     # (Note that Defaults.__init__ (deep)copies these values to 
  76     # instance variables. Mutable defaults are therefore safe.) 
  78     # Name of the bundle, with or without extension. 
  81     # The property list ("plist") 
  82     plist 
= Plist(CFBundleDevelopmentRegion 
= "English", 
  83                   CFBundleInfoDictionaryVersion 
= "6.0") 
  85     # The type of the bundle. 
  87     # The creator code of the bundle. 
  90     # the CFBundleIdentifier (this is used for the preferences file name) 
  93     # List of files that have to be copied to <bundle>/Contents/Resources. 
  96     # List of (src, dest) tuples; dest should be a path relative to the bundle 
  97     # (eg. "Contents/Resources/MyStuff/SomeFile.ext). 
 100     # List of shared libraries (dylibs, Frameworks) to bundle with the app 
 101     # will be placed in Contents/Frameworks 
 104     # Directory where the bundle will be assembled. 
 107     # Make symlinks instead copying files. This is handy during debugging, but 
 108     # makes the bundle non-distributable. 
 115         # XXX rethink self.name munging, this is brittle. 
 116         self
.name
, ext 
= os
.path
.splitext(self
.name
) 
 119         bundleextension 
= ext
 
 120         # misc (derived) attributes 
 121         self
.bundlepath 
= pathjoin(self
.builddir
, self
.name 
+ bundleextension
) 
 124         plist
.CFBundleName 
= self
.name
 
 125         plist
.CFBundlePackageType 
= self
.type 
 126         if self
.creator 
is None: 
 127             if hasattr(plist
, "CFBundleSignature"): 
 128                 self
.creator 
= plist
.CFBundleSignature
 
 130                 self
.creator 
= "????" 
 131         plist
.CFBundleSignature 
= self
.creator
 
 133             plist
.CFBundleIdentifier 
= self
.bundle_id
 
 134         elif not hasattr(plist
, "CFBundleIdentifier"): 
 135             plist
.CFBundleIdentifier 
= self
.name
 
 138         """Build the bundle.""" 
 139         builddir 
= self
.builddir
 
 140         if builddir 
and not os
.path
.exists(builddir
): 
 142         self
.message("Building %s" % repr(self
.bundlepath
), 1) 
 143         if os
.path
.exists(self
.bundlepath
): 
 144             shutil
.rmtree(self
.bundlepath
) 
 145         os
.mkdir(self
.bundlepath
) 
 150         self
.message("Done.", 1) 
 152     def preProcess(self
): 
 153         """Hook for subclasses.""" 
 155     def postProcess(self
): 
 156         """Hook for subclasses.""" 
 159     def _addMetaFiles(self
): 
 160         contents 
= pathjoin(self
.bundlepath
, "Contents") 
 163         # Write Contents/PkgInfo 
 164         assert len(self
.type) == len(self
.creator
) == 4, \
 
 165                 "type and creator must be 4-byte strings." 
 166         pkginfo 
= pathjoin(contents
, "PkgInfo") 
 167         f 
= open(pkginfo
, "wb") 
 168         f
.write(self
.type + self
.creator
) 
 171         # Write Contents/Info.plist 
 172         infoplist 
= pathjoin(contents
, "Info.plist") 
 173         self
.plist
.write(infoplist
) 
 175     def _copyFiles(self
): 
 176         files 
= self
.files
[:] 
 177         for path 
in self
.resources
: 
 178             files
.append((path
, pathjoin("Contents", "Resources", 
 179                 os
.path
.basename(path
)))) 
 180         for path 
in self
.libs
: 
 181             files
.append((path
, pathjoin("Contents", "Frameworks", 
 182                 os
.path
.basename(path
)))) 
 184             self
.message("Making symbolic links", 1) 
 185             msg 
= "Making symlink from" 
 187             self
.message("Copying files", 1) 
 190         for src
, dst 
in files
: 
 191             if os
.path
.isdir(src
): 
 192                 self
.message("%s %s/ to %s/" % (msg
, src
, dst
), 2) 
 194                 self
.message("%s %s to %s" % (msg
, src
, dst
), 2) 
 195             dst 
= pathjoin(self
.bundlepath
, dst
) 
 197                 symlink(src
, dst
, mkdirs
=1) 
 199                 copy(src
, dst
, mkdirs
=1) 
 201     def message(self
, msg
, level
=0): 
 202         if level 
<= self
.verbosity
: 
 205                 indent 
= (level 
- 1) * "  " 
 206             sys
.stderr
.write(indent 
+ msg 
+ "\n") 
 209         # XXX something decent 
 218 MAGIC 
= imp
.get_magic() 
 219 USE_ZIPIMPORT 
= "zipimport" in sys
.builtin_module_names
 
 221 # For standalone apps, we have our own minimal site.py. We don't need 
 222 # all the cruft of the real site.py. 
 225 if not %(semi_standalone)s: 
 226     del sys.path[1:]  # sys.path[0] is Contents/Resources/ 
 230     ZIP_ARCHIVE 
= "Modules.zip" 
 231     SITE_PY 
+= "sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
 
 232     def getPycData(fullname
, code
, ispkg
): 
 234             fullname 
+= ".__init__" 
 235         path 
= fullname
.replace(".", os
.sep
) + PYC_EXT
 
 236         return path
, MAGIC 
+ '\0\0\0\0' + marshal
.dumps(code
) 
 239 # Extension modules can't be in the modules zip archive, so a placeholder 
 240 # is added instead, that loads the extension from a specified location. 
 246         path = os.path.join(p, "%(filename)s") 
 247         if os.path.exists(path): 
 250         assert 0, "file not found: %(filename)s" 
 251     mod = imp.load_dynamic("%(name)s", path) 
 257 MAYMISS_MODULES 
= ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath', 
 258     'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize', 
 259     'org.python.core', 'riscos', 'riscosenviron', 'riscospath' 
 262 STRIP_EXEC 
= "/usr/bin/strip" 
 265 # We're using a stock interpreter to run the app, yet we need 
 266 # a way to pass the Python main program to the interpreter. The 
 267 # bootstrapping script fires up the interpreter with the right 
 268 # arguments. os.execve() is used as OSX doesn't like us to 
 269 # start a real new process. Also, the executable name must match 
 270 # the CFBundleExecutable value in the Info.plist, so we lie 
 271 # deliberately with argv[0]. The actual Python executable is 
 272 # passed in an environment variable so we can "repair" 
 273 # sys.executable later. 
 275 BOOTSTRAP_SCRIPT 
= """\ 
 279 execdir = os.path.dirname(sys.argv[0]) 
 280 executable = os.path.join(execdir, "%(executable)s") 
 281 resdir = os.path.join(os.path.dirname(execdir), "Resources") 
 282 libdir = os.path.join(os.path.dirname(execdir), "Frameworks") 
 283 mainprogram = os.path.join(resdir, "%(mainprogram)s") 
 285 sys.argv.insert(1, mainprogram) 
 286 if %(standalone)s or %(semi_standalone)s: 
 287     os.environ["PYTHONPATH"] = resdir 
 289         os.environ["PYTHONHOME"] = resdir 
 291     pypath = os.getenv("PYTHONPATH", "") 
 293         pypath = ":" + pypath 
 294     os.environ["PYTHONPATH"] = resdir + pypath 
 295 os.environ["PYTHONEXECUTABLE"] = executable 
 296 os.environ["DYLD_LIBRARY_PATH"] = libdir 
 297 os.environ["DYLD_FRAMEWORK_PATH"] = libdir 
 298 os.execve(executable, sys.argv, os.environ) 
 303 # Optional wrapper that converts "dropped files" into sys.argv values. 
 306 import argvemulator, os 
 308 argvemulator.ArgvCollector().mainloop() 
 309 execfile(os.path.join(os.path.split(__file__)[0], "%(realmainprogram)s")) 
 313 # When building a standalone app with Python.framework, we need to copy 
 314 # a subset from Python.framework to the bundle. The following list 
 315 # specifies exactly what items we'll copy. 
 317 PYTHONFRAMEWORKGOODIES 
= [ 
 318     "Python",  # the Python core library 
 319     "Resources/English.lproj", 
 320     "Resources/Info.plist", 
 321     "Resources/version.plist", 
 325     return sys
.exec_prefix
.find("Python.framework") > 0 
 328 LIB 
= os
.path
.join(sys
.prefix
, "lib", "python" + sys
.version
[:3]) 
 329 SITE_PACKAGES 
= os
.path
.join(LIB
, "site-packages") 
 332 class AppBuilder(BundleBuilder
): 
 334     # Override type of the bundle. 
 337     # platform, name of the subfolder of Contents that contains the executable. 
 340     # A Python main program. If this argument is given, the main 
 341     # executable in the bundle will be a small wrapper that invokes 
 342     # the main program. (XXX Discuss why.) 
 345     # The main executable. If a Python main program is specified 
 346     # the executable will be copied to Resources and be invoked 
 347     # by the wrapper program mentioned above. Otherwise it will 
 348     # simply be used as the main executable. 
 351     # The name of the main nib, for Cocoa apps. *Must* be specified 
 352     # when building a Cocoa app. 
 355     # The name of the icon file to be copied to Resources and used for 
 359     # Symlink the executable instead of copying it. 
 362     # If True, build standalone app. 
 365     # If True, build semi-standalone app (only includes third-party modules). 
 368     # If set, use this for #! lines in stead of sys.executable 
 371     # If True, add a real main program that emulates sys.argv before calling 
 375     # The following attributes are only used when building a standalone app. 
 377     # Exclude these modules. 
 380     # Include these modules. 
 383     # Include these packages. 
 386     # Strip binaries from debug info. 
 389     # Found Python modules: [(name, codeobject, ispkg), ...] 
 392     # Modules that modulefinder couldn't find: 
 394     maybeMissingModules 
= [] 
 397         if ((self
.standalone 
or self
.semi_standalone
) 
 398             and self
.mainprogram 
is None): 
 399             raise BundleBuilderError
, ("must specify 'mainprogram' when " 
 400                     "building a standalone application.") 
 401         if self
.mainprogram 
is None and self
.executable 
is None: 
 402             raise BundleBuilderError
, ("must specify either or both of " 
 403                     "'executable' and 'mainprogram'") 
 405         self
.execdir 
= pathjoin("Contents", self
.platform
) 
 407         if self
.name 
is not None: 
 409         elif self
.mainprogram 
is not None: 
 410             self
.name 
= os
.path
.splitext(os
.path
.basename(self
.mainprogram
))[0] 
 411         elif executable 
is not None: 
 412             self
.name 
= os
.path
.splitext(os
.path
.basename(self
.executable
))[0] 
 413         if self
.name
[-4:] != ".app": 
 416         if self
.executable 
is None: 
 417             if not self
.standalone 
and not isFramework(): 
 418                 self
.symlink_exec 
= 1 
 420                 self
.executable 
= self
.python
 
 422                 self
.executable 
= sys
.executable
 
 425             self
.plist
.NSMainNibFile 
= self
.nibname
 
 426             if not hasattr(self
.plist
, "NSPrincipalClass"): 
 427                 self
.plist
.NSPrincipalClass 
= "NSApplication" 
 429         if self
.standalone 
and isFramework(): 
 430             self
.addPythonFramework() 
 432         BundleBuilder
.setup(self
) 
 434         self
.plist
.CFBundleExecutable 
= self
.name
 
 436         if self
.standalone 
or self
.semi_standalone
: 
 437             self
.findDependencies() 
 439     def preProcess(self
): 
 440         resdir 
= "Contents/Resources" 
 441         if self
.executable 
is not None: 
 442             if self
.mainprogram 
is None: 
 445                 execname 
= os
.path
.basename(self
.executable
) 
 446             execpath 
= pathjoin(self
.execdir
, execname
) 
 447             if not self
.symlink_exec
: 
 448                 self
.files
.append((self
.executable
, execpath
)) 
 449             self
.execpath 
= execpath
 
 451         if self
.mainprogram 
is not None: 
 452             mainprogram 
= os
.path
.basename(self
.mainprogram
) 
 453             self
.files
.append((self
.mainprogram
, pathjoin(resdir
, mainprogram
))) 
 454             if self
.argv_emulation
: 
 455                 # Change the main program, and create the helper main program (which 
 456                 # does argv collection and then calls the real main). 
 457                 # Also update the included modules (if we're creating a standalone 
 458                 # program) and the plist 
 459                 realmainprogram 
= mainprogram
 
 460                 mainprogram 
= '__argvemulator_' + mainprogram
 
 461                 resdirpath 
= pathjoin(self
.bundlepath
, resdir
) 
 462                 mainprogrampath 
= pathjoin(resdirpath
, mainprogram
) 
 464                 open(mainprogrampath
, "w").write(ARGV_EMULATOR 
% locals()) 
 465                 if self
.standalone 
or self
.semi_standalone
: 
 466                     self
.includeModules
.append("argvemulator") 
 467                     self
.includeModules
.append("os") 
 468                 if not self
.plist
.has_key("CFBundleDocumentTypes"): 
 469                     self
.plist
["CFBundleDocumentTypes"] = [ 
 470                         { "CFBundleTypeOSTypes" : [ 
 474                           "CFBundleTypeRole": "Viewer"}] 
 475             # Write bootstrap script 
 476             executable 
= os
.path
.basename(self
.executable
) 
 477             execdir 
= pathjoin(self
.bundlepath
, self
.execdir
) 
 478             bootstrappath 
= pathjoin(execdir
, self
.name
) 
 480             if self
.standalone 
or self
.semi_standalone
: 
 481                 # XXX we're screwed when the end user has deleted 
 483                 hashbang 
= "/usr/bin/python" 
 485                 hashbang 
= self
.python
 
 487                 hashbang 
= os
.path
.realpath(sys
.executable
) 
 488             standalone 
= self
.standalone
 
 489             semi_standalone 
= self
.semi_standalone
 
 490             open(bootstrappath
, "w").write(BOOTSTRAP_SCRIPT 
% locals()) 
 491             os
.chmod(bootstrappath
, 0775) 
 493         if self
.iconfile 
is not None: 
 494             iconbase 
= os
.path
.basename(self
.iconfile
) 
 495             self
.plist
.CFBundleIconFile 
= iconbase
 
 496             self
.files
.append((self
.iconfile
, pathjoin(resdir
, iconbase
))) 
 498     def postProcess(self
): 
 499         if self
.standalone 
or self
.semi_standalone
: 
 500             self
.addPythonModules() 
 501         if self
.strip 
and not self
.symlink
: 
 504         if self
.symlink_exec 
and self
.executable
: 
 505             self
.message("Symlinking executable %s to %s" % (self
.executable
, 
 507             dst 
= pathjoin(self
.bundlepath
, self
.execpath
) 
 508             makedirs(os
.path
.dirname(dst
)) 
 509             os
.symlink(os
.path
.abspath(self
.executable
), dst
) 
 511         if self
.missingModules 
or self
.maybeMissingModules
: 
 514     def addPythonFramework(self
): 
 515         # If we're building a standalone app with Python.framework, 
 516         # include a minimal subset of Python.framework, *unless* 
 517         # Python.framework was specified manually in self.libs. 
 518         for lib 
in self
.libs
: 
 519             if os
.path
.basename(lib
) == "Python.framework": 
 520                 # a Python.framework was specified as a library 
 523         frameworkpath 
= sys
.exec_prefix
[:sys
.exec_prefix
.find( 
 524             "Python.framework") + len("Python.framework")] 
 526         version 
= sys
.version
[:3] 
 527         frameworkpath 
= pathjoin(frameworkpath
, "Versions", version
) 
 528         destbase 
= pathjoin("Contents", "Frameworks", "Python.framework", 
 530         for item 
in PYTHONFRAMEWORKGOODIES
: 
 531             src 
= pathjoin(frameworkpath
, item
) 
 532             dst 
= pathjoin(destbase
, item
) 
 533             self
.files
.append((src
, dst
)) 
 535     def _getSiteCode(self
): 
 536         return compile(SITE_PY 
% {"semi_standalone": self.semi_standalone}
, 
 537                      "<-bundlebuilder.py->", "exec") 
 539     def addPythonModules(self
): 
 540         self
.message("Adding Python modules", 1) 
 543             # Create a zip file containing all modules as pyc. 
 545             relpath 
= pathjoin("Contents", "Resources", ZIP_ARCHIVE
) 
 546             abspath 
= pathjoin(self
.bundlepath
, relpath
) 
 547             zf 
= zipfile
.ZipFile(abspath
, "w", zipfile
.ZIP_DEFLATED
) 
 548             for name
, code
, ispkg 
in self
.pymodules
: 
 549                 self
.message("Adding Python module %s" % name
, 2) 
 550                 path
, pyc 
= getPycData(name
, code
, ispkg
) 
 551                 zf
.writestr(path
, pyc
) 
 554             sitepath 
= pathjoin(self
.bundlepath
, "Contents", "Resources", 
 556             writePyc(self
._getSiteCode
(), sitepath
) 
 558             # Create individual .pyc files. 
 559             for name
, code
, ispkg 
in self
.pymodules
: 
 562                 path 
= name
.split(".") 
 563                 path 
= pathjoin("Contents", "Resources", *path
) + PYC_EXT
 
 566                     self
.message("Adding Python package %s" % path
, 2) 
 568                     self
.message("Adding Python module %s" % path
, 2) 
 570                 abspath 
= pathjoin(self
.bundlepath
, path
) 
 571                 makedirs(os
.path
.dirname(abspath
)) 
 572                 writePyc(code
, abspath
) 
 574     def stripBinaries(self
): 
 575         if not os
.path
.exists(STRIP_EXEC
): 
 576             self
.message("Error: can't strip binaries: no strip program at " 
 577                 "%s" % STRIP_EXEC
, 0) 
 580             self
.message("Stripping binaries", 1) 
 582                 for name 
in os
.listdir(top
): 
 583                     path 
= pathjoin(top
, name
) 
 584                     if os
.path
.islink(path
): 
 586                     if os
.path
.isdir(path
): 
 589                         mod 
= os
.stat(path
)[stat
.ST_MODE
] 
 592                         relpath 
= path
[len(self
.bundlepath
):] 
 593                         self
.message("Stripping %s" % relpath
, 2) 
 594                         inf
, outf 
= os
.popen4("%s -S \"%s\"" % 
 596                         output 
= outf
.read().strip() 
 598                             # usually not a real problem, like when we're 
 599                             # trying to strip a script 
 600                             self
.message("Problem stripping %s:" % relpath
, 3) 
 601                             self
.message(output
, 3) 
 602             walk(self
.bundlepath
) 
 604     def findDependencies(self
): 
 605         self
.message("Finding module dependencies", 1) 
 607         mf 
= modulefinder
.ModuleFinder(excludes
=self
.excludeModules
) 
 609             # zipimport imports zlib, must add it manually 
 610             mf
.import_hook("zlib") 
 611         # manually add our own site.py 
 612         site 
= mf
.add_module("site") 
 613         site
.__code
__ = self
._getSiteCode
() 
 614         mf
.scan_code(site
.__code
__, site
) 
 616         # warnings.py gets imported implicitly from C 
 617         mf
.import_hook("warnings") 
 619         includeModules 
= self
.includeModules
[:] 
 620         for name 
in self
.includePackages
: 
 621             includeModules
.extend(findPackageContents(name
).keys()) 
 622         for name 
in includeModules
: 
 626                 self
.missingModules
.append(name
) 
 628         mf
.run_script(self
.mainprogram
) 
 629         modules 
= mf
.modules
.items() 
 631         for name
, mod 
in modules
: 
 633             if path 
and self
.semi_standalone
: 
 634                 # skip the standard library 
 635                 if path
.startswith(LIB
) and not path
.startswith(SITE_PACKAGES
): 
 637             if path 
and mod
.__code
__ is None: 
 639                 filename 
= os
.path
.basename(path
) 
 640                 pathitems 
= name
.split(".")[:-1] + [filename
] 
 641                 dstpath 
= pathjoin(*pathitems
) 
 644                         # neatly pack all extension modules in a subdirectory, 
 645                         # except zlib, since it's neccesary for bootstrapping. 
 646                         dstpath 
= pathjoin("ExtensionModules", dstpath
) 
 647                     # Python modules are stored in a Zip archive, but put 
 648                     # extensions in Contents/Resources/. Add a tiny "loader" 
 649                     # program in the Zip archive. Due to Thomas Heller. 
 650                     source 
= EXT_LOADER 
% {"name": name, "filename": dstpath}
 
 651                     code 
= compile(source
, "<dynloader for %s>" % name
, "exec") 
 653                 self
.files
.append((path
, pathjoin("Contents", "Resources", dstpath
))) 
 654             if mod
.__code
__ is not None: 
 655                 ispkg 
= mod
.__path
__ is not None 
 656                 if not USE_ZIPIMPORT 
or name 
!= "site": 
 657                     # Our site.py is doing the bootstrapping, so we must 
 658                     # include a real .pyc file if USE_ZIPIMPORT is True. 
 659                     self
.pymodules
.append((name
, mod
.__code
__, ispkg
)) 
 661         if hasattr(mf
, "any_missing_maybe"): 
 662             missing
, maybe 
= mf
.any_missing_maybe() 
 664             missing 
= mf
.any_missing() 
 666         self
.missingModules
.extend(missing
) 
 667         self
.maybeMissingModules
.extend(maybe
) 
 669     def reportMissing(self
): 
 670         missing 
= [name 
for name 
in self
.missingModules
 
 671                 if name 
not in MAYMISS_MODULES
] 
 672         if self
.maybeMissingModules
: 
 673             maybe 
= self
.maybeMissingModules
 
 675             maybe 
= [name 
for name 
in missing 
if "." in name
] 
 676             missing 
= [name 
for name 
in missing 
if "." not in name
] 
 680             self
.message("Warning: couldn't find the following submodules:", 1) 
 681             self
.message("    (Note that these could be false alarms -- " 
 682                          "it's not always", 1) 
 683             self
.message("    possible to distinguish between \"from package " 
 684                          "import submodule\" ", 1) 
 685             self
.message("    and \"from package import name\")", 1) 
 687                 self
.message("  ? " + name
, 1) 
 689             self
.message("Warning: couldn't find the following modules:", 1) 
 691                 self
.message("  ? " + name
, 1) 
 694         # XXX something decent 
 696         pprint
.pprint(self
.__dict
__) 
 697         if self
.standalone 
or self
.semi_standalone
: 
 704 SUFFIXES 
= [_suf 
for _suf
, _mode
, _tp 
in imp
.get_suffixes()] 
 705 identifierRE 
= re
.compile(r
"[_a-zA-z][_a-zA-Z0-9]*$") 
 707 def findPackageContents(name
, searchpath
=None): 
 708     head 
= name
.split(".")[-1] 
 709     if identifierRE
.match(head
) is None: 
 712         fp
, path
, (ext
, mode
, tp
) = imp
.find_module(head
, searchpath
) 
 715     modules 
= {name: None}
 
 716     if tp 
== imp
.PKG_DIRECTORY 
and path
: 
 717         files 
= os
.listdir(path
) 
 719             sub
, ext 
= os
.path
.splitext(sub
) 
 720             fullname 
= name 
+ "." + sub
 
 721             if sub 
!= "__init__" and fullname 
not in modules
: 
 722                 modules
.update(findPackageContents(fullname
, [path
])) 
 725 def writePyc(code
, path
): 
 728     f
.write("\0" * 4)  # don't bother about a time stamp 
 729     marshal
.dump(code
, f
) 
 732 def copy(src
, dst
, mkdirs
=0): 
 733     """Copy a file or a directory.""" 
 735         makedirs(os
.path
.dirname(dst
)) 
 736     if os
.path
.isdir(src
): 
 737         shutil
.copytree(src
, dst
, symlinks
=1) 
 739         shutil
.copy2(src
, dst
) 
 741 def copytodir(src
, dstdir
): 
 742     """Copy a file or a directory to an existing directory.""" 
 743     dst 
= pathjoin(dstdir
, os
.path
.basename(src
)) 
 747     """Make all directories leading up to 'dir' including the leaf 
 748     directory. Don't moan if any path element already exists.""" 
 752         if why
.errno 
!= errno
.EEXIST
: 
 755 def symlink(src
, dst
, mkdirs
=0): 
 756     """Copy a file or a directory.""" 
 757     if not os
.path
.exists(src
): 
 758         raise IOError, "No such file or directory: '%s'" % src
 
 760         makedirs(os
.path
.dirname(dst
)) 
 761     os
.symlink(os
.path
.abspath(src
), dst
) 
 764     """Safe wrapper for os.path.join: asserts that all but the first 
 765     argument are relative paths.""" 
 768     return os
.path
.join(*args
) 
 773   python bundlebuilder.py [options] command 
 774   python mybuildscript.py [options] command 
 777   build      build the application 
 778   report     print a report 
 781   -b, --builddir=DIR     the build directory; defaults to "build" 
 782   -n, --name=NAME        application name 
 783   -r, --resource=FILE    extra file or folder to be copied to Resources 
 784   -f, --file=SRC:DST     extra file or folder to be copied into the bundle; 
 785                          DST must be a path relative to the bundle root 
 786   -e, --executable=FILE  the executable to be used 
 787   -m, --mainprogram=FILE the Python main program 
 788   -a, --argv             add a wrapper main program to create sys.argv 
 789   -p, --plist=FILE       .plist file (default: generate one) 
 790       --nib=NAME         main nib name 
 791   -c, --creator=CCCC     4-char creator code (default: '????') 
 792       --iconfile=FILE    filename of the icon (an .icns file) to be used 
 794       --bundle-id=ID     the CFBundleIdentifier, in reverse-dns format 
 795                          (eg. org.python.BuildApplet; this is used for 
 796                          the preferences file name) 
 797   -l, --link             symlink files/folder instead of copying them 
 798       --link-exec        symlink the executable instead of copying it 
 799       --standalone       build a standalone application, which is fully 
 800                          independent of a Python installation 
 801       --semi-standalone  build a standalone application, which depends on 
 802                          an installed Python, yet includes all third-party 
 804       --python=FILE      Python to use in #! line in stead of current Python 
 805       --lib=FILE         shared library or framework to be copied into 
 807   -x, --exclude=MODULE   exclude module (with --(semi-)standalone) 
 808   -i, --include=MODULE   include module (with --(semi-)standalone) 
 809       --package=PACKAGE  include a whole package (with --(semi-)standalone) 
 810       --strip            strip binaries (remove debug info) 
 811   -v, --verbose          increase verbosity level 
 812   -q, --quiet            decrease verbosity level 
 813   -h, --help             print this message 
 822 def main(builder
=None): 
 824         builder 
= AppBuilder(verbosity
=1) 
 826     shortopts 
= "b:n:r:f:e:m:c:p:lx:i:hvqa" 
 827     longopts 
= ("builddir=", "name=", "resource=", "file=", "executable=", 
 828         "mainprogram=", "creator=", "nib=", "plist=", "link", 
 829         "link-exec", "help", "verbose", "quiet", "argv", "standalone", 
 830         "exclude=", "include=", "package=", "strip", "iconfile=", 
 831         "lib=", "python=", "semi-standalone", "bundle-id=") 
 834         options
, args 
= getopt
.getopt(sys
.argv
[1:], shortopts
, longopts
) 
 838     for opt
, arg 
in options
: 
 839         if opt 
in ('-b', '--builddir'): 
 840             builder
.builddir 
= arg
 
 841         elif opt 
in ('-n', '--name'): 
 843         elif opt 
in ('-r', '--resource'): 
 844             builder
.resources
.append(os
.path
.normpath(arg
)) 
 845         elif opt 
in ('-f', '--file'): 
 846             srcdst 
= arg
.split(':') 
 848                 usage("-f or --file argument must be two paths, " 
 849                       "separated by a colon") 
 850             builder
.files
.append(srcdst
) 
 851         elif opt 
in ('-e', '--executable'): 
 852             builder
.executable 
= arg
 
 853         elif opt 
in ('-m', '--mainprogram'): 
 854             builder
.mainprogram 
= arg
 
 855         elif opt 
in ('-a', '--argv'): 
 856             builder
.argv_emulation 
= 1 
 857         elif opt 
in ('-c', '--creator'): 
 858             builder
.creator 
= arg
 
 859         elif opt 
== '--bundle-id': 
 860             builder
.bundle_id 
= arg
 
 861         elif opt 
== '--iconfile': 
 862             builder
.iconfile 
= arg
 
 864             builder
.libs
.append(os
.path
.normpath(arg
)) 
 866             builder
.nibname 
= arg
 
 867         elif opt 
in ('-p', '--plist'): 
 868             builder
.plist 
= Plist
.fromFile(arg
) 
 869         elif opt 
in ('-l', '--link'): 
 871         elif opt 
== '--link-exec': 
 872             builder
.symlink_exec 
= 1 
 873         elif opt 
in ('-h', '--help'): 
 875         elif opt 
in ('-v', '--verbose'): 
 876             builder
.verbosity 
+= 1 
 877         elif opt 
in ('-q', '--quiet'): 
 878             builder
.verbosity 
-= 1 
 879         elif opt 
== '--standalone': 
 880             builder
.standalone 
= 1 
 881         elif opt 
== '--semi-standalone': 
 882             builder
.semi_standalone 
= 1 
 883         elif opt 
== '--python': 
 885         elif opt 
in ('-x', '--exclude'): 
 886             builder
.excludeModules
.append(arg
) 
 887         elif opt 
in ('-i', '--include'): 
 888             builder
.includeModules
.append(arg
) 
 889         elif opt 
== '--package': 
 890             builder
.includePackages
.append(arg
) 
 891         elif opt 
== '--strip': 
 895         usage("Must specify one command ('build', 'report' or 'help')") 
 898     if command 
== "build": 
 901     elif command 
== "report": 
 904     elif command 
== "help": 
 907         usage("Unknown command '%s'" % command
) 
 910 def buildapp(**kwargs
): 
 911     builder 
= AppBuilder(**kwargs
) 
 915 if __name__ 
== "__main__":