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 name
, value 
in cls
.__dict
__.items(): 
  58                         if name
[0] != "_" and not isinstance(value
, 
  59                                         (function
, classmethod)): 
  60                                 defaults
[name
] = deepcopy(value
) 
  61                 for base 
in cls
.__bases
__: 
  62                         if hasattr(base
, "_getDefaults"): 
  63                                 defaults
.update(base
._getDefaults
()) 
  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         # List of files that have to be copied to <bundle>/Contents/Resources. 
  93         # List of (src, dest) tuples; dest should be a path relative to the bundle 
  94         # (eg. "Contents/Resources/MyStuff/SomeFile.ext). 
  97         # List of shared libraries (dylibs, Frameworks) to bundle with the app 
  98         # will be placed in Contents/Frameworks 
 101         # Directory where the bundle will be assembled. 
 104         # Make symlinks instead copying files. This is handy during debugging, but 
 105         # makes the bundle non-distributable. 
 112                 # XXX rethink self.name munging, this is brittle. 
 113                 self
.name
, ext 
= os
.path
.splitext(self
.name
) 
 116                 bundleextension 
= ext
 
 117                 # misc (derived) attributes 
 118                 self
.bundlepath 
= pathjoin(self
.builddir
, self
.name 
+ bundleextension
) 
 121                 plist
.CFBundleName 
= self
.name
 
 122                 plist
.CFBundlePackageType 
= self
.type 
 123                 if self
.creator 
is None: 
 124                         if hasattr(plist
, "CFBundleSignature"): 
 125                                 self
.creator 
= plist
.CFBundleSignature
 
 127                                 self
.creator 
= "????" 
 128                 plist
.CFBundleSignature 
= self
.creator
 
 129                 if not hasattr(plist
, "CFBundleIdentifier"): 
 130                         plist
.CFBundleIdentifier 
= self
.name
 
 133                 """Build the bundle.""" 
 134                 builddir 
= self
.builddir
 
 135                 if builddir 
and not os
.path
.exists(builddir
): 
 137                 self
.message("Building %s" % repr(self
.bundlepath
), 1) 
 138                 if os
.path
.exists(self
.bundlepath
): 
 139                         shutil
.rmtree(self
.bundlepath
) 
 140                 os
.mkdir(self
.bundlepath
) 
 145                 self
.message("Done.", 1) 
 147         def preProcess(self
): 
 148                 """Hook for subclasses.""" 
 150         def postProcess(self
): 
 151                 """Hook for subclasses.""" 
 154         def _addMetaFiles(self
): 
 155                 contents 
= pathjoin(self
.bundlepath
, "Contents") 
 158                 # Write Contents/PkgInfo 
 159                 assert len(self
.type) == len(self
.creator
) == 4, \
 
 160                                 "type and creator must be 4-byte strings." 
 161                 pkginfo 
= pathjoin(contents
, "PkgInfo") 
 162                 f 
= open(pkginfo
, "wb") 
 163                 f
.write(self
.type + self
.creator
) 
 166                 # Write Contents/Info.plist 
 167                 infoplist 
= pathjoin(contents
, "Info.plist") 
 168                 self
.plist
.write(infoplist
) 
 170         def _copyFiles(self
): 
 171                 files 
= self
.files
[:] 
 172                 for path 
in self
.resources
: 
 173                         files
.append((path
, pathjoin("Contents", "Resources", 
 174                                 os
.path
.basename(path
)))) 
 175                 for path 
in self
.libs
: 
 176                         files
.append((path
, pathjoin("Contents", "Frameworks", 
 177                                 os
.path
.basename(path
)))) 
 179                         self
.message("Making symbolic links", 1) 
 180                         msg 
= "Making symlink from" 
 182                         self
.message("Copying files", 1) 
 185                 for src
, dst 
in files
: 
 186                         if os
.path
.isdir(src
): 
 187                                 self
.message("%s %s/ to %s/" % (msg
, src
, dst
), 2) 
 189                                 self
.message("%s %s to %s" % (msg
, src
, dst
), 2) 
 190                         dst 
= pathjoin(self
.bundlepath
, dst
) 
 192                                 symlink(src
, dst
, mkdirs
=1) 
 194                                 copy(src
, dst
, mkdirs
=1) 
 196         def message(self
, msg
, level
=0): 
 197                 if level 
<= self
.verbosity
: 
 200                                 indent 
= (level 
- 1) * "  " 
 201                         sys
.stderr
.write(indent 
+ msg 
+ "\n") 
 204                 # XXX something decent 
 213 MAGIC 
= imp
.get_magic() 
 214 USE_ZIPIMPORT 
= "zipimport" in sys
.builtin_module_names
 
 216 # For standalone apps, we have our own minimal site.py. We don't need 
 217 # all the cruft of the real site.py. 
 220 del sys.path[1:]  # sys.path[0] is Contents/Resources/ 
 224         ZIP_ARCHIVE 
= "Modules.zip" 
 225         SITE_PY 
+= "sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
 
 226         def getPycData(fullname
, code
, ispkg
): 
 228                         fullname 
+= ".__init__" 
 229                 path 
= fullname
.replace(".", os
.sep
) + PYC_EXT
 
 230                 return path
, MAGIC 
+ '\0\0\0\0' + marshal
.dumps(code
) 
 232 SITE_CO 
= compile(SITE_PY
, "<-bundlebuilder.py->", "exec") 
 235 # Extension modules can't be in the modules zip archive, so a placeholder 
 236 # is added instead, that loads the extension from a specified location. 
 242                 path = os.path.join(p, "%(filename)s") 
 243                 if os.path.exists(path): 
 246                 assert 0, "file not found: %(filename)s" 
 247         mod = imp.load_dynamic("%(name)s", path) 
 253 MAYMISS_MODULES 
= ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath', 
 254         'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize', 
 255         'org.python.core', 'riscos', 'riscosenviron', 'riscospath' 
 258 STRIP_EXEC 
= "/usr/bin/strip" 
 261 # We're using a stock interpreter to run the app, yet we need 
 262 # a way to pass the Python main program to the interpreter. The 
 263 # bootstrapping script fires up the interpreter with the right 
 264 # arguments. os.execve() is used as OSX doesn't like us to 
 265 # start a real new process. Also, the executable name must match 
 266 # the CFBundleExecutable value in the Info.plist, so we lie 
 267 # deliberately with argv[0]. The actual Python executable is 
 268 # passed in an environment variable so we can "repair" 
 269 # sys.executable later. 
 271 BOOTSTRAP_SCRIPT 
= """\ 
 275 execdir = os.path.dirname(sys.argv[0]) 
 276 executable = os.path.join(execdir, "%(executable)s") 
 277 resdir = os.path.join(os.path.dirname(execdir), "Resources") 
 278 libdir = os.path.join(os.path.dirname(execdir), "Frameworks") 
 279 mainprogram = os.path.join(resdir, "%(mainprogram)s") 
 281 sys.argv.insert(1, mainprogram) 
 282 os.environ["PYTHONPATH"] = resdir 
 284 os.environ["PYTHONEXECUTABLE"] = executable 
 285 os.environ["DYLD_LIBRARY_PATH"] = libdir 
 286 os.execve(executable, sys.argv, os.environ) 
 291 # Optional wrapper that converts "dropped files" into sys.argv values. 
 294 import argvemulator, os 
 296 argvemulator.ArgvCollector().mainloop() 
 297 execfile(os.path.join(os.path.split(__file__)[0], "%(realmainprogram)s")) 
 301 class AppBuilder(BundleBuilder
): 
 303         # Override type of the bundle. 
 306         # platform, name of the subfolder of Contents that contains the executable. 
 309         # A Python main program. If this argument is given, the main 
 310         # executable in the bundle will be a small wrapper that invokes 
 311         # the main program. (XXX Discuss why.) 
 314         # The main executable. If a Python main program is specified 
 315         # the executable will be copied to Resources and be invoked 
 316         # by the wrapper program mentioned above. Otherwise it will 
 317         # simply be used as the main executable. 
 320         # The name of the main nib, for Cocoa apps. *Must* be specified 
 321         # when building a Cocoa app. 
 324         # The name of the icon file to be copied to Resources and used for 
 328         # Symlink the executable instead of copying it. 
 331         # If True, build standalone app. 
 334         # If True, add a real main program that emulates sys.argv before calling 
 338         # The following attributes are only used when building a standalone app. 
 340         # Exclude these modules. 
 343         # Include these modules. 
 346         # Include these packages. 
 352         # Found Python modules: [(name, codeobject, ispkg), ...] 
 355         # Modules that modulefinder couldn't find: 
 357         maybeMissingModules 
= [] 
 359         # List of all binaries (executables or shared libs), for stripping purposes 
 363                 if self
.standalone 
and self
.mainprogram 
is None: 
 364                         raise BundleBuilderError
, ("must specify 'mainprogram' when " 
 365                                         "building a standalone application.") 
 366                 if self
.mainprogram 
is None and self
.executable 
is None: 
 367                         raise BundleBuilderError
, ("must specify either or both of " 
 368                                         "'executable' and 'mainprogram'") 
 370                 self
.execdir 
= pathjoin("Contents", self
.platform
) 
 372                 if self
.name 
is not None: 
 374                 elif self
.mainprogram 
is not None: 
 375                         self
.name 
= os
.path
.splitext(os
.path
.basename(self
.mainprogram
))[0] 
 376                 elif executable 
is not None: 
 377                         self
.name 
= os
.path
.splitext(os
.path
.basename(self
.executable
))[0] 
 378                 if self
.name
[-4:] != ".app": 
 381                 if self
.executable 
is None: 
 382                         if not self
.standalone
: 
 383                                 self
.symlink_exec 
= 1 
 384                         self
.executable 
= sys
.executable
 
 387                         self
.plist
.NSMainNibFile 
= self
.nibname
 
 388                         if not hasattr(self
.plist
, "NSPrincipalClass"): 
 389                                 self
.plist
.NSPrincipalClass 
= "NSApplication" 
 391                 BundleBuilder
.setup(self
) 
 393                 self
.plist
.CFBundleExecutable 
= self
.name
 
 396                         self
.findDependencies() 
 398         def preProcess(self
): 
 399                 resdir 
= "Contents/Resources" 
 400                 if self
.executable 
is not None: 
 401                         if self
.mainprogram 
is None: 
 404                                 execname 
= os
.path
.basename(self
.executable
) 
 405                         execpath 
= pathjoin(self
.execdir
, execname
) 
 406                         if not self
.symlink_exec
: 
 407                                 self
.files
.append((self
.executable
, execpath
)) 
 408                                 self
.binaries
.append(execpath
) 
 409                         self
.execpath 
= execpath
 
 411                 if self
.mainprogram 
is not None: 
 412                         mainprogram 
= os
.path
.basename(self
.mainprogram
) 
 413                         self
.files
.append((self
.mainprogram
, pathjoin(resdir
, mainprogram
))) 
 414                         if self
.argv_emulation
: 
 415                                 # Change the main program, and create the helper main program (which 
 416                                 # does argv collection and then calls the real main). 
 417                                 # Also update the included modules (if we're creating a standalone 
 418                                 # program) and the plist 
 419                                 realmainprogram 
= mainprogram
 
 420                                 mainprogram 
= '__argvemulator_' + mainprogram
 
 421                                 resdirpath 
= pathjoin(self
.bundlepath
, resdir
) 
 422                                 mainprogrampath 
= pathjoin(resdirpath
, mainprogram
) 
 424                                 open(mainprogrampath
, "w").write(ARGV_EMULATOR 
% locals()) 
 426                                         self
.includeModules
.append("argvemulator") 
 427                                         self
.includeModules
.append("os") 
 428                                 if not self
.plist
.has_key("CFBundleDocumentTypes"): 
 429                                         self
.plist
["CFBundleDocumentTypes"] = [ 
 430                                                 { "CFBundleTypeOSTypes" : [ 
 434                                                   "CFBundleTypeRole": "Viewer"}] 
 435                         # Write bootstrap script 
 436                         executable 
= os
.path
.basename(self
.executable
) 
 437                         execdir 
= pathjoin(self
.bundlepath
, self
.execdir
) 
 438                         bootstrappath 
= pathjoin(execdir
, self
.name
) 
 441                                 # XXX we're screwed when the end user has deleted 
 443                                 hashbang 
= "/usr/bin/python" 
 444                                 pythonhome 
= 'os.environ["PYTHONHOME"] = resdir' 
 446                                 hashbang 
= sys
.executable
 
 447                                 while os
.path
.islink(hashbang
): 
 448                                         hashbang 
= os
.readlink(hashbang
) 
 450                         open(bootstrappath
, "w").write(BOOTSTRAP_SCRIPT 
% locals()) 
 451                         os
.chmod(bootstrappath
, 0775) 
 453                 if self
.iconfile 
is not None: 
 454                         iconbase 
= os
.path
.basename(self
.iconfile
) 
 455                         self
.plist
.CFBundleIconFile 
= iconbase
 
 456                         self
.files
.append((self
.iconfile
, pathjoin(resdir
, iconbase
))) 
 458         def postProcess(self
): 
 460                         self
.addPythonModules() 
 461                 if self
.strip 
and not self
.symlink
: 
 464                 if self
.symlink_exec 
and self
.executable
: 
 465                         self
.message("Symlinking executable %s to %s" % (self
.executable
, 
 467                         dst 
= pathjoin(self
.bundlepath
, self
.execpath
) 
 468                         makedirs(os
.path
.dirname(dst
)) 
 469                         os
.symlink(os
.path
.abspath(self
.executable
), dst
) 
 471                 if self
.missingModules 
or self
.maybeMissingModules
: 
 474         def addPythonModules(self
): 
 475                 self
.message("Adding Python modules", 1) 
 478                         # Create a zip file containing all modules as pyc. 
 480                         relpath 
= pathjoin("Contents", "Resources", ZIP_ARCHIVE
) 
 481                         abspath 
= pathjoin(self
.bundlepath
, relpath
) 
 482                         zf 
= zipfile
.ZipFile(abspath
, "w", zipfile
.ZIP_DEFLATED
) 
 483                         for name
, code
, ispkg 
in self
.pymodules
: 
 484                                 self
.message("Adding Python module %s" % name
, 2) 
 485                                 path
, pyc 
= getPycData(name
, code
, ispkg
) 
 486                                 zf
.writestr(path
, pyc
) 
 489                         sitepath 
= pathjoin(self
.bundlepath
, "Contents", "Resources", 
 491                         writePyc(SITE_CO
, sitepath
) 
 493                         # Create individual .pyc files. 
 494                         for name
, code
, ispkg 
in self
.pymodules
: 
 497                                 path 
= name
.split(".") 
 498                                 path 
= pathjoin("Contents", "Resources", *path
) + PYC_EXT
 
 501                                         self
.message("Adding Python package %s" % path
, 2) 
 503                                         self
.message("Adding Python module %s" % path
, 2) 
 505                                 abspath 
= pathjoin(self
.bundlepath
, path
) 
 506                                 makedirs(os
.path
.dirname(abspath
)) 
 507                                 writePyc(code
, abspath
) 
 509         def stripBinaries(self
): 
 510                 if not os
.path
.exists(STRIP_EXEC
): 
 511                         self
.message("Error: can't strip binaries: no strip program at " 
 512                                 "%s" % STRIP_EXEC
, 0) 
 514                         self
.message("Stripping binaries", 1) 
 515                         for relpath 
in self
.binaries
: 
 516                                 self
.message("Stripping %s" % relpath
, 2) 
 517                                 abspath 
= pathjoin(self
.bundlepath
, relpath
) 
 518                                 assert not os
.path
.islink(abspath
) 
 519                                 rv 
= os
.system("%s -S \"%s\"" % (STRIP_EXEC
, abspath
)) 
 521         def findDependencies(self
): 
 522                 self
.message("Finding module dependencies", 1) 
 524                 mf 
= modulefinder
.ModuleFinder(excludes
=self
.excludeModules
) 
 526                         # zipimport imports zlib, must add it manually 
 527                         mf
.import_hook("zlib") 
 528                 # manually add our own site.py 
 529                 site 
= mf
.add_module("site") 
 530                 site
.__code
__ = SITE_CO
 
 531                 mf
.scan_code(SITE_CO
, site
) 
 533                 # warnings.py gets imported implicitly from C 
 534                 mf
.import_hook("warnings") 
 536                 includeModules 
= self
.includeModules
[:] 
 537                 for name 
in self
.includePackages
: 
 538                         includeModules
.extend(findPackageContents(name
).keys()) 
 539                 for name 
in includeModules
: 
 543                                 self
.missingModules
.append(name
) 
 545                 mf
.run_script(self
.mainprogram
) 
 546                 modules 
= mf
.modules
.items() 
 548                 for name
, mod 
in modules
: 
 549                         if mod
.__file
__ and mod
.__code
__ is None: 
 552                                 filename 
= os
.path
.basename(path
) 
 554                                         # Python modules are stored in a Zip archive, but put 
 555                                         # extensions in Contents/Resources/.a and add a tiny "loader" 
 556                                         # program in the Zip archive. Due to Thomas Heller. 
 557                                         dstpath 
= pathjoin("Contents", "Resources", filename
) 
 558                                         source 
= EXT_LOADER 
% {"name": name, "filename": filename}
 
 559                                         code 
= compile(source
, "<dynloader for %s>" % name
, "exec") 
 563                                         dstpath 
= name
.split(".")[:-1] + [filename
] 
 564                                         dstpath 
= pathjoin("Contents", "Resources", *dstpath
) 
 565                                 self
.files
.append((path
, dstpath
)) 
 566                                 self
.binaries
.append(dstpath
) 
 567                         if mod
.__code
__ is not None: 
 568                                 ispkg 
= mod
.__path
__ is not None 
 569                                 if not USE_ZIPIMPORT 
or name 
!= "site": 
 570                                         # Our site.py is doing the bootstrapping, so we must 
 571                                         # include a real .pyc file if USE_ZIPIMPORT is True. 
 572                                         self
.pymodules
.append((name
, mod
.__code
__, ispkg
)) 
 574                 if hasattr(mf
, "any_missing_maybe"): 
 575                         missing
, maybe 
= mf
.any_missing_maybe() 
 577                         missing 
= mf
.any_missing() 
 579                 self
.missingModules
.extend(missing
) 
 580                 self
.maybeMissingModules
.extend(maybe
) 
 582         def reportMissing(self
): 
 583                 missing 
= [name 
for name 
in self
.missingModules
 
 584                                 if name 
not in MAYMISS_MODULES
] 
 585                 if self
.maybeMissingModules
: 
 586                         maybe 
= self
.maybeMissingModules
 
 588                         maybe 
= [name 
for name 
in missing 
if "." in name
] 
 589                         missing 
= [name 
for name 
in missing 
if "." not in name
] 
 593                         self
.message("Warning: couldn't find the following submodules:", 1) 
 594                         self
.message("    (Note that these could be false alarms -- " 
 595                                      "it's not always", 1) 
 596                         self
.message("    possible to distinguish between \"from package " 
 597                                      "import submodule\" ", 1) 
 598                         self
.message("    and \"from package import name\")", 1) 
 600                                 self
.message("  ? " + name
, 1) 
 602                         self
.message("Warning: couldn't find the following modules:", 1) 
 604                                 self
.message("  ? " + name
, 1) 
 607                 # XXX something decent 
 609                 pprint
.pprint(self
.__dict
__) 
 617 SUFFIXES 
= [_suf 
for _suf
, _mode
, _tp 
in imp
.get_suffixes()] 
 618 identifierRE 
= re
.compile(r
"[_a-zA-z][_a-zA-Z0-9]*$") 
 620 def findPackageContents(name
, searchpath
=None): 
 621         head 
= name
.split(".")[-1] 
 622         if identifierRE
.match(head
) is None: 
 625                 fp
, path
, (ext
, mode
, tp
) = imp
.find_module(head
, searchpath
) 
 628         modules 
= {name: None}
 
 629         if tp 
== imp
.PKG_DIRECTORY 
and path
: 
 630                 files 
= os
.listdir(path
) 
 632                         sub
, ext 
= os
.path
.splitext(sub
) 
 633                         fullname 
= name 
+ "." + sub
 
 634                         if sub 
!= "__init__" and fullname 
not in modules
: 
 635                                 modules
.update(findPackageContents(fullname
, [path
])) 
 638 def writePyc(code
, path
): 
 641         f
.write("\0" * 4)  # don't bother about a time stamp 
 642         marshal
.dump(code
, f
) 
 645 def copy(src
, dst
, mkdirs
=0): 
 646         """Copy a file or a directory.""" 
 648                 makedirs(os
.path
.dirname(dst
)) 
 649         if os
.path
.isdir(src
): 
 650                 shutil
.copytree(src
, dst
) 
 652                 shutil
.copy2(src
, dst
) 
 654 def copytodir(src
, dstdir
): 
 655         """Copy a file or a directory to an existing directory.""" 
 656         dst 
= pathjoin(dstdir
, os
.path
.basename(src
)) 
 660         """Make all directories leading up to 'dir' including the leaf 
 661         directory. Don't moan if any path element already exists.""" 
 665                 if why
.errno 
!= errno
.EEXIST
: 
 668 def symlink(src
, dst
, mkdirs
=0): 
 669         """Copy a file or a directory.""" 
 670         if not os
.path
.exists(src
): 
 671                 raise IOError, "No such file or directory: '%s'" % src
 
 673                 makedirs(os
.path
.dirname(dst
)) 
 674         os
.symlink(os
.path
.abspath(src
), dst
) 
 677         """Safe wrapper for os.path.join: asserts that all but the first 
 678         argument are relative paths.""" 
 681         return os
.path
.join(*args
) 
 686   python bundlebuilder.py [options] command 
 687   python mybuildscript.py [options] command 
 690   build      build the application 
 691   report     print a report 
 694   -b, --builddir=DIR     the build directory; defaults to "build" 
 695   -n, --name=NAME        application name 
 696   -r, --resource=FILE    extra file or folder to be copied to Resources 
 697   -f, --file=SRC:DST     extra file or folder to be copied into the bundle; 
 698                          DST must be a path relative to the bundle root 
 699   -e, --executable=FILE  the executable to be used 
 700   -m, --mainprogram=FILE the Python main program 
 701   -a, --argv             add a wrapper main program to create sys.argv 
 702   -p, --plist=FILE       .plist file (default: generate one) 
 703       --nib=NAME         main nib name 
 704   -c, --creator=CCCC     4-char creator code (default: '????') 
 705       --iconfile=FILE    filename of the icon (an .icns file) to be used 
 707   -l, --link             symlink files/folder instead of copying them 
 708       --link-exec        symlink the executable instead of copying it 
 709       --standalone       build a standalone application, which is fully 
 710                          independent of a Python installation 
 711       --lib=FILE         shared library or framework to be copied into 
 713   -x, --exclude=MODULE   exclude module (with --standalone) 
 714   -i, --include=MODULE   include module (with --standalone) 
 715       --package=PACKAGE  include a whole package (with --standalone) 
 716       --strip            strip binaries (remove debug info) 
 717   -v, --verbose          increase verbosity level 
 718   -q, --quiet            decrease verbosity level 
 719   -h, --help             print this message 
 728 def main(builder
=None): 
 730                 builder 
= AppBuilder(verbosity
=1) 
 732         shortopts 
= "b:n:r:f:e:m:c:p:lx:i:hvqa" 
 733         longopts 
= ("builddir=", "name=", "resource=", "file=", "executable=", 
 734                 "mainprogram=", "creator=", "nib=", "plist=", "link", 
 735                 "link-exec", "help", "verbose", "quiet", "argv", "standalone", 
 736                 "exclude=", "include=", "package=", "strip", "iconfile=", 
 740                 options
, args 
= getopt
.getopt(sys
.argv
[1:], shortopts
, longopts
) 
 744         for opt
, arg 
in options
: 
 745                 if opt 
in ('-b', '--builddir'): 
 746                         builder
.builddir 
= arg
 
 747                 elif opt 
in ('-n', '--name'): 
 749                 elif opt 
in ('-r', '--resource'): 
 750                         builder
.resources
.append(arg
) 
 751                 elif opt 
in ('-f', '--file'): 
 752                         srcdst 
= arg
.split(':') 
 754                                 usage("-f or --file argument must be two paths, " 
 755                                       "separated by a colon") 
 756                         builder
.files
.append(srcdst
) 
 757                 elif opt 
in ('-e', '--executable'): 
 758                         builder
.executable 
= arg
 
 759                 elif opt 
in ('-m', '--mainprogram'): 
 760                         builder
.mainprogram 
= arg
 
 761                 elif opt 
in ('-a', '--argv'): 
 762                         builder
.argv_emulation 
= 1 
 763                 elif opt 
in ('-c', '--creator'): 
 764                         builder
.creator 
= arg
 
 765                 elif opt 
== '--iconfile': 
 766                         builder
.iconfile 
= arg
 
 768                         builder
.libs
.append(arg
) 
 770                         builder
.nibname 
= arg
 
 771                 elif opt 
in ('-p', '--plist'): 
 772                         builder
.plist 
= Plist
.fromFile(arg
) 
 773                 elif opt 
in ('-l', '--link'): 
 775                 elif opt 
== '--link-exec': 
 776                         builder
.symlink_exec 
= 1 
 777                 elif opt 
in ('-h', '--help'): 
 779                 elif opt 
in ('-v', '--verbose'): 
 780                         builder
.verbosity 
+= 1 
 781                 elif opt 
in ('-q', '--quiet'): 
 782                         builder
.verbosity 
-= 1 
 783                 elif opt 
== '--standalone': 
 784                         builder
.standalone 
= 1 
 785                 elif opt 
in ('-x', '--exclude'): 
 786                         builder
.excludeModules
.append(arg
) 
 787                 elif opt 
in ('-i', '--include'): 
 788                         builder
.includeModules
.append(arg
) 
 789                 elif opt 
== '--package': 
 790                         builder
.includePackages
.append(arg
) 
 791                 elif opt 
== '--strip': 
 795                 usage("Must specify one command ('build', 'report' or 'help')") 
 798         if command 
== "build": 
 801         elif command 
== "report": 
 804         elif command 
== "help": 
 807                 usage("Unknown command '%s'" % command
) 
 810 def buildapp(**kwargs
): 
 811         builder 
= AppBuilder(**kwargs
) 
 815 if __name__ 
== "__main__":