Move setting of ioready 'wait' earlier in call chain, to
[python/dscho.git] / Lib / plat-mac / bundlebuilder.py
blob9e4fc910be1f0f6b3e8718d9e882d6da5c543d49
1 #! /usr/bin/env python
3 """\
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
20 this model:
22 from bundlebuilder import buildapp
23 buildapp(<lots-of-keyword-args>)
25 """
28 __all__ = ["BundleBuilder", "BundleBuilderError", "AppBuilder", "buildapp"]
31 import sys
32 import os, errno, shutil
33 import imp, marshal
34 import re
35 from copy import deepcopy
36 import getopt
37 from plistlib import Plist
38 from types import FunctionType as function
40 class BundleBuilderError(Exception): pass
43 class Defaults:
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.
48 """
50 def __init__(self, **kwargs):
51 defaults = self._getDefaults()
52 defaults.update(kwargs)
53 self.__dict__.update(defaults)
55 def _getDefaults(cls):
56 defaults = {}
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())
64 return defaults
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.
73 """
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.
79 name = None
81 # The property list ("plist")
82 plist = Plist(CFBundleDevelopmentRegion = "English",
83 CFBundleInfoDictionaryVersion = "6.0")
85 # The type of the bundle.
86 type = "BNDL"
87 # The creator code of the bundle.
88 creator = None
90 # List of files that have to be copied to <bundle>/Contents/Resources.
91 resources = []
93 # List of (src, dest) tuples; dest should be a path relative to the bundle
94 # (eg. "Contents/Resources/MyStuff/SomeFile.ext).
95 files = []
97 # Directory where the bundle will be assembled.
98 builddir = "build"
100 # Make symlinks instead copying files. This is handy during debugging, but
101 # makes the bundle non-distributable.
102 symlink = 0
104 # Verbosity level.
105 verbosity = 1
107 def setup(self):
108 # XXX rethink self.name munging, this is brittle.
109 self.name, ext = os.path.splitext(self.name)
110 if not ext:
111 ext = ".bundle"
112 bundleextension = ext
113 # misc (derived) attributes
114 self.bundlepath = pathjoin(self.builddir, self.name + bundleextension)
116 plist = self.plist
117 plist.CFBundleName = self.name
118 plist.CFBundlePackageType = self.type
119 if self.creator is None:
120 if hasattr(plist, "CFBundleSignature"):
121 self.creator = plist.CFBundleSignature
122 else:
123 self.creator = "????"
124 plist.CFBundleSignature = self.creator
125 if not hasattr(plist, "CFBundleIdentifier"):
126 plist.CFBundleIdentifier = self.name
128 def build(self):
129 """Build the bundle."""
130 builddir = self.builddir
131 if builddir and not os.path.exists(builddir):
132 os.mkdir(builddir)
133 self.message("Building %s" % repr(self.bundlepath), 1)
134 if os.path.exists(self.bundlepath):
135 shutil.rmtree(self.bundlepath)
136 os.mkdir(self.bundlepath)
137 self.preProcess()
138 self._copyFiles()
139 self._addMetaFiles()
140 self.postProcess()
141 self.message("Done.", 1)
143 def preProcess(self):
144 """Hook for subclasses."""
145 pass
146 def postProcess(self):
147 """Hook for subclasses."""
148 pass
150 def _addMetaFiles(self):
151 contents = pathjoin(self.bundlepath, "Contents")
152 makedirs(contents)
154 # Write Contents/PkgInfo
155 assert len(self.type) == len(self.creator) == 4, \
156 "type and creator must be 4-byte strings."
157 pkginfo = pathjoin(contents, "PkgInfo")
158 f = open(pkginfo, "wb")
159 f.write(self.type + self.creator)
160 f.close()
162 # Write Contents/Info.plist
163 infoplist = pathjoin(contents, "Info.plist")
164 self.plist.write(infoplist)
166 def _copyFiles(self):
167 files = self.files[:]
168 for path in self.resources:
169 files.append((path, pathjoin("Contents", "Resources",
170 os.path.basename(path))))
171 if self.symlink:
172 self.message("Making symbolic links", 1)
173 msg = "Making symlink from"
174 else:
175 self.message("Copying files", 1)
176 msg = "Copying"
177 files.sort()
178 for src, dst in files:
179 if os.path.isdir(src):
180 self.message("%s %s/ to %s/" % (msg, src, dst), 2)
181 else:
182 self.message("%s %s to %s" % (msg, src, dst), 2)
183 dst = pathjoin(self.bundlepath, dst)
184 if self.symlink:
185 symlink(src, dst, mkdirs=1)
186 else:
187 copy(src, dst, mkdirs=1)
189 def message(self, msg, level=0):
190 if level <= self.verbosity:
191 indent = ""
192 if level > 1:
193 indent = (level - 1) * " "
194 sys.stderr.write(indent + msg + "\n")
196 def report(self):
197 # XXX something decent
198 pass
201 if __debug__:
202 PYC_EXT = ".pyc"
203 else:
204 PYC_EXT = ".pyo"
206 MAGIC = imp.get_magic()
207 USE_ZIPIMPORT = "zipimport" in sys.builtin_module_names
209 # For standalone apps, we have our own minimal site.py. We don't need
210 # all the cruft of the real site.py.
211 SITE_PY = """\
212 import sys
213 del sys.path[1:] # sys.path[0] is Contents/Resources/
216 if USE_ZIPIMPORT:
217 ZIP_ARCHIVE = "Modules.zip"
218 SITE_PY += "sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
219 def getPycData(fullname, code, ispkg):
220 if ispkg:
221 fullname += ".__init__"
222 path = fullname.replace(".", os.sep) + PYC_EXT
223 return path, MAGIC + '\0\0\0\0' + marshal.dumps(code)
225 SITE_CO = compile(SITE_PY, "<-bundlebuilder.py->", "exec")
228 # Extension modules can't be in the modules zip archive, so a placeholder
229 # is added instead, that loads the extension from a specified location.
231 EXT_LOADER = """\
232 def __load():
233 import imp, sys, os
234 for p in sys.path:
235 path = os.path.join(p, "%(filename)s")
236 if os.path.exists(path):
237 break
238 else:
239 assert 0, "file not found: %(filename)s"
240 mod = imp.load_dynamic("%(name)s", path)
242 __load()
243 del __load
246 MAYMISS_MODULES = ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath',
247 'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize',
248 'org.python.core', 'riscos', 'riscosenviron', 'riscospath'
251 STRIP_EXEC = "/usr/bin/strip"
254 # We're using a stock interpreter to run the app, yet we need
255 # a way to pass the Python main program to the interpreter. The
256 # bootstrapping script fires up the interpreter with the right
257 # arguments. os.execve() is used as OSX doesn't like us to
258 # start a real new process. Also, the executable name must match
259 # the CFBundleExecutable value in the Info.plist, so we lie
260 # deliberately with argv[0]. The actual Python executable is
261 # passed in an environment variable so we can "repair"
262 # sys.executable later.
264 BOOTSTRAP_SCRIPT = """\
265 #!%(hashbang)s
267 import sys, os
268 execdir = os.path.dirname(sys.argv[0])
269 executable = os.path.join(execdir, "%(executable)s")
270 resdir = os.path.join(os.path.dirname(execdir), "Resources")
271 mainprogram = os.path.join(resdir, "%(mainprogram)s")
273 sys.argv.insert(1, mainprogram)
274 os.environ["PYTHONPATH"] = resdir
275 os.environ["PYTHONEXECUTABLE"] = executable
276 os.execve(executable, sys.argv, os.environ)
281 # Optional wrapper that converts "dropped files" into sys.argv values.
283 ARGV_EMULATOR = """\
284 import argvemulator, os
286 argvemulator.ArgvCollector().mainloop()
287 execfile(os.path.join(os.path.split(__file__)[0], "%(realmainprogram)s"))
291 class AppBuilder(BundleBuilder):
293 # Override type of the bundle.
294 type = "APPL"
296 # platform, name of the subfolder of Contents that contains the executable.
297 platform = "MacOS"
299 # A Python main program. If this argument is given, the main
300 # executable in the bundle will be a small wrapper that invokes
301 # the main program. (XXX Discuss why.)
302 mainprogram = None
304 # The main executable. If a Python main program is specified
305 # the executable will be copied to Resources and be invoked
306 # by the wrapper program mentioned above. Otherwise it will
307 # simply be used as the main executable.
308 executable = None
310 # The name of the main nib, for Cocoa apps. *Must* be specified
311 # when building a Cocoa app.
312 nibname = None
314 # The name of the icon file to be copied to Resources and used for
315 # the Finder icon.
316 iconfile = None
318 # Symlink the executable instead of copying it.
319 symlink_exec = 0
321 # If True, build standalone app.
322 standalone = 0
324 # If True, add a real main program that emulates sys.argv before calling
325 # mainprogram
326 argv_emulation = 0
328 # The following attributes are only used when building a standalone app.
330 # Exclude these modules.
331 excludeModules = []
333 # Include these modules.
334 includeModules = []
336 # Include these packages.
337 includePackages = []
339 # Strip binaries.
340 strip = 0
342 # Found Python modules: [(name, codeobject, ispkg), ...]
343 pymodules = []
345 # Modules that modulefinder couldn't find:
346 missingModules = []
347 maybeMissingModules = []
349 # List of all binaries (executables or shared libs), for stripping purposes
350 binaries = []
352 def setup(self):
353 if self.standalone and self.mainprogram is None:
354 raise BundleBuilderError, ("must specify 'mainprogram' when "
355 "building a standalone application.")
356 if self.mainprogram is None and self.executable is None:
357 raise BundleBuilderError, ("must specify either or both of "
358 "'executable' and 'mainprogram'")
360 self.execdir = pathjoin("Contents", self.platform)
362 if self.name is not None:
363 pass
364 elif self.mainprogram is not None:
365 self.name = os.path.splitext(os.path.basename(self.mainprogram))[0]
366 elif executable is not None:
367 self.name = os.path.splitext(os.path.basename(self.executable))[0]
368 if self.name[-4:] != ".app":
369 self.name += ".app"
371 if self.executable is None:
372 if not self.standalone:
373 self.symlink_exec = 1
374 self.executable = sys.executable
376 if self.nibname:
377 self.plist.NSMainNibFile = self.nibname
378 if not hasattr(self.plist, "NSPrincipalClass"):
379 self.plist.NSPrincipalClass = "NSApplication"
381 BundleBuilder.setup(self)
383 self.plist.CFBundleExecutable = self.name
385 if self.standalone:
386 self.findDependencies()
388 def preProcess(self):
389 resdir = "Contents/Resources"
390 if self.executable is not None:
391 if self.mainprogram is None:
392 execname = self.name
393 else:
394 execname = os.path.basename(self.executable)
395 execpath = pathjoin(self.execdir, execname)
396 if not self.symlink_exec:
397 self.files.append((self.executable, execpath))
398 self.binaries.append(execpath)
399 self.execpath = execpath
401 if self.mainprogram is not None:
402 mainprogram = os.path.basename(self.mainprogram)
403 self.files.append((self.mainprogram, pathjoin(resdir, mainprogram)))
404 if self.argv_emulation:
405 # Change the main program, and create the helper main program (which
406 # does argv collection and then calls the real main).
407 # Also update the included modules (if we're creating a standalone
408 # program) and the plist
409 realmainprogram = mainprogram
410 mainprogram = '__argvemulator_' + mainprogram
411 resdirpath = pathjoin(self.bundlepath, resdir)
412 mainprogrampath = pathjoin(resdirpath, mainprogram)
413 makedirs(resdirpath)
414 open(mainprogrampath, "w").write(ARGV_EMULATOR % locals())
415 if self.standalone:
416 self.includeModules.append("argvemulator")
417 self.includeModules.append("os")
418 if not self.plist.has_key("CFBundleDocumentTypes"):
419 self.plist["CFBundleDocumentTypes"] = [
420 { "CFBundleTypeOSTypes" : [
421 "****",
422 "fold",
423 "disk"],
424 "CFBundleTypeRole": "Viewer"}]
425 # Write bootstrap script
426 executable = os.path.basename(self.executable)
427 execdir = pathjoin(self.bundlepath, self.execdir)
428 bootstrappath = pathjoin(execdir, self.name)
429 makedirs(execdir)
430 if self.standalone:
431 # XXX we're screwed when the end user has deleted
432 # /usr/bin/python
433 hashbang = "/usr/bin/python"
434 else:
435 hashbang = sys.executable
436 while os.path.islink(hashbang):
437 hashbang = os.readlink(hashbang)
438 open(bootstrappath, "w").write(BOOTSTRAP_SCRIPT % locals())
439 os.chmod(bootstrappath, 0775)
441 if self.iconfile is not None:
442 iconbase = os.path.basename(self.iconfile)
443 self.plist.CFBundleIconFile = iconbase
444 self.files.append((self.iconfile, pathjoin(resdir, iconbase)))
446 def postProcess(self):
447 if self.standalone:
448 self.addPythonModules()
449 if self.strip and not self.symlink:
450 self.stripBinaries()
452 if self.symlink_exec and self.executable:
453 self.message("Symlinking executable %s to %s" % (self.executable,
454 self.execpath), 2)
455 dst = pathjoin(self.bundlepath, self.execpath)
456 makedirs(os.path.dirname(dst))
457 os.symlink(os.path.abspath(self.executable), dst)
459 if self.missingModules or self.maybeMissingModules:
460 self.reportMissing()
462 def addPythonModules(self):
463 self.message("Adding Python modules", 1)
465 if USE_ZIPIMPORT:
466 # Create a zip file containing all modules as pyc.
467 import zipfile
468 relpath = pathjoin("Contents", "Resources", ZIP_ARCHIVE)
469 abspath = pathjoin(self.bundlepath, relpath)
470 zf = zipfile.ZipFile(abspath, "w", zipfile.ZIP_DEFLATED)
471 for name, code, ispkg in self.pymodules:
472 self.message("Adding Python module %s" % name, 2)
473 path, pyc = getPycData(name, code, ispkg)
474 zf.writestr(path, pyc)
475 zf.close()
476 # add site.pyc
477 sitepath = pathjoin(self.bundlepath, "Contents", "Resources",
478 "site" + PYC_EXT)
479 writePyc(SITE_CO, sitepath)
480 else:
481 # Create individual .pyc files.
482 for name, code, ispkg in self.pymodules:
483 if ispkg:
484 name += ".__init__"
485 path = name.split(".")
486 path = pathjoin("Contents", "Resources", *path) + PYC_EXT
488 if ispkg:
489 self.message("Adding Python package %s" % path, 2)
490 else:
491 self.message("Adding Python module %s" % path, 2)
493 abspath = pathjoin(self.bundlepath, path)
494 makedirs(os.path.dirname(abspath))
495 writePyc(code, abspath)
497 def stripBinaries(self):
498 if not os.path.exists(STRIP_EXEC):
499 self.message("Error: can't strip binaries: no strip program at "
500 "%s" % STRIP_EXEC, 0)
501 else:
502 self.message("Stripping binaries", 1)
503 for relpath in self.binaries:
504 self.message("Stripping %s" % relpath, 2)
505 abspath = pathjoin(self.bundlepath, relpath)
506 assert not os.path.islink(abspath)
507 rv = os.system("%s -S \"%s\"" % (STRIP_EXEC, abspath))
509 def findDependencies(self):
510 self.message("Finding module dependencies", 1)
511 import modulefinder
512 mf = modulefinder.ModuleFinder(excludes=self.excludeModules)
513 if USE_ZIPIMPORT:
514 # zipimport imports zlib, must add it manually
515 mf.import_hook("zlib")
516 # manually add our own site.py
517 site = mf.add_module("site")
518 site.__code__ = SITE_CO
519 mf.scan_code(SITE_CO, site)
521 # warnings.py gets imported implicitly from C
522 mf.import_hook("warnings")
524 includeModules = self.includeModules[:]
525 for name in self.includePackages:
526 includeModules.extend(findPackageContents(name).keys())
527 for name in includeModules:
528 try:
529 mf.import_hook(name)
530 except ImportError:
531 self.missingModules.append(name)
533 mf.run_script(self.mainprogram)
534 modules = mf.modules.items()
535 modules.sort()
536 for name, mod in modules:
537 if mod.__file__ and mod.__code__ is None:
538 # C extension
539 path = mod.__file__
540 filename = os.path.basename(path)
541 if USE_ZIPIMPORT:
542 # Python modules are stored in a Zip archive, but put
543 # extensions in Contents/Resources/.a and add a tiny "loader"
544 # program in the Zip archive. Due to Thomas Heller.
545 dstpath = pathjoin("Contents", "Resources", filename)
546 source = EXT_LOADER % {"name": name, "filename": filename}
547 code = compile(source, "<dynloader for %s>" % name, "exec")
548 mod.__code__ = code
549 else:
550 # just copy the file
551 dstpath = name.split(".")[:-1] + [filename]
552 dstpath = pathjoin("Contents", "Resources", *dstpath)
553 self.files.append((path, dstpath))
554 self.binaries.append(dstpath)
555 if mod.__code__ is not None:
556 ispkg = mod.__path__ is not None
557 if not USE_ZIPIMPORT or name != "site":
558 # Our site.py is doing the bootstrapping, so we must
559 # include a real .pyc file if USE_ZIPIMPORT is True.
560 self.pymodules.append((name, mod.__code__, ispkg))
562 if hasattr(mf, "any_missing_maybe"):
563 missing, maybe = mf.any_missing_maybe()
564 else:
565 missing = mf.any_missing()
566 maybe = []
567 self.missingModules.extend(missing)
568 self.maybeMissingModules.extend(maybe)
570 def reportMissing(self):
571 missing = [name for name in self.missingModules
572 if name not in MAYMISS_MODULES]
573 if self.maybeMissingModules:
574 maybe = self.maybeMissingModules
575 else:
576 maybe = [name for name in missing if "." in name]
577 missing = [name for name in missing if "." not in name]
578 missing.sort()
579 maybe.sort()
580 if maybe:
581 self.message("Warning: couldn't find the following submodules:", 1)
582 self.message(" (Note that these could be false alarms -- "
583 "it's not always", 1)
584 self.message(" possible to distinguish between \"from package "
585 "import submodule\" ", 1)
586 self.message(" and \"from package import name\")", 1)
587 for name in maybe:
588 self.message(" ? " + name, 1)
589 if missing:
590 self.message("Warning: couldn't find the following modules:", 1)
591 for name in missing:
592 self.message(" ? " + name, 1)
594 def report(self):
595 # XXX something decent
596 import pprint
597 pprint.pprint(self.__dict__)
598 if self.standalone:
599 self.reportMissing()
602 # Utilities.
605 SUFFIXES = [_suf for _suf, _mode, _tp in imp.get_suffixes()]
606 identifierRE = re.compile(r"[_a-zA-z][_a-zA-Z0-9]*$")
608 def findPackageContents(name, searchpath=None):
609 head = name.split(".")[-1]
610 if identifierRE.match(head) is None:
611 return {}
612 try:
613 fp, path, (ext, mode, tp) = imp.find_module(head, searchpath)
614 except ImportError:
615 return {}
616 modules = {name: None}
617 if tp == imp.PKG_DIRECTORY and path:
618 files = os.listdir(path)
619 for sub in files:
620 sub, ext = os.path.splitext(sub)
621 fullname = name + "." + sub
622 if sub != "__init__" and fullname not in modules:
623 modules.update(findPackageContents(fullname, [path]))
624 return modules
626 def writePyc(code, path):
627 f = open(path, "wb")
628 f.write(MAGIC)
629 f.write("\0" * 4) # don't bother about a time stamp
630 marshal.dump(code, f)
631 f.close()
633 def copy(src, dst, mkdirs=0):
634 """Copy a file or a directory."""
635 if mkdirs:
636 makedirs(os.path.dirname(dst))
637 if os.path.isdir(src):
638 shutil.copytree(src, dst)
639 else:
640 shutil.copy2(src, dst)
642 def copytodir(src, dstdir):
643 """Copy a file or a directory to an existing directory."""
644 dst = pathjoin(dstdir, os.path.basename(src))
645 copy(src, dst)
647 def makedirs(dir):
648 """Make all directories leading up to 'dir' including the leaf
649 directory. Don't moan if any path element already exists."""
650 try:
651 os.makedirs(dir)
652 except OSError, why:
653 if why.errno != errno.EEXIST:
654 raise
656 def symlink(src, dst, mkdirs=0):
657 """Copy a file or a directory."""
658 if not os.path.exists(src):
659 raise IOError, "No such file or directory: '%s'" % src
660 if mkdirs:
661 makedirs(os.path.dirname(dst))
662 os.symlink(os.path.abspath(src), dst)
664 def pathjoin(*args):
665 """Safe wrapper for os.path.join: asserts that all but the first
666 argument are relative paths."""
667 for seg in args[1:]:
668 assert seg[0] != "/"
669 return os.path.join(*args)
672 cmdline_doc = """\
673 Usage:
674 python bundlebuilder.py [options] command
675 python mybuildscript.py [options] command
677 Commands:
678 build build the application
679 report print a report
681 Options:
682 -b, --builddir=DIR the build directory; defaults to "build"
683 -n, --name=NAME application name
684 -r, --resource=FILE extra file or folder to be copied to Resources
685 -f, --file=SRC:DST extra file or folder to be copied into the bundle;
686 DST must be a path relative to the bundle root
687 -e, --executable=FILE the executable to be used
688 -m, --mainprogram=FILE the Python main program
689 -a, --argv add a wrapper main program to create sys.argv
690 -p, --plist=FILE .plist file (default: generate one)
691 --nib=NAME main nib name
692 -c, --creator=CCCC 4-char creator code (default: '????')
693 --iconfile=FILE filename of the icon (an .icns file) to be used
694 as the Finder icon
695 -l, --link symlink files/folder instead of copying them
696 --link-exec symlink the executable instead of copying it
697 --standalone build a standalone application, which is fully
698 independent of a Python installation
699 -x, --exclude=MODULE exclude module (with --standalone)
700 -i, --include=MODULE include module (with --standalone)
701 --package=PACKAGE include a whole package (with --standalone)
702 --strip strip binaries (remove debug info)
703 -v, --verbose increase verbosity level
704 -q, --quiet decrease verbosity level
705 -h, --help print this message
708 def usage(msg=None):
709 if msg:
710 print msg
711 print cmdline_doc
712 sys.exit(1)
714 def main(builder=None):
715 if builder is None:
716 builder = AppBuilder(verbosity=1)
718 shortopts = "b:n:r:f:e:m:c:p:lx:i:hvqa"
719 longopts = ("builddir=", "name=", "resource=", "file=", "executable=",
720 "mainprogram=", "creator=", "nib=", "plist=", "link",
721 "link-exec", "help", "verbose", "quiet", "argv", "standalone",
722 "exclude=", "include=", "package=", "strip", "iconfile=")
724 try:
725 options, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
726 except getopt.error:
727 usage()
729 for opt, arg in options:
730 if opt in ('-b', '--builddir'):
731 builder.builddir = arg
732 elif opt in ('-n', '--name'):
733 builder.name = arg
734 elif opt in ('-r', '--resource'):
735 builder.resources.append(arg)
736 elif opt in ('-f', '--file'):
737 srcdst = arg.split(':')
738 if len(srcdst) != 2:
739 usage("-f or --file argument must be two paths, "
740 "separated by a colon")
741 builder.files.append(srcdst)
742 elif opt in ('-e', '--executable'):
743 builder.executable = arg
744 elif opt in ('-m', '--mainprogram'):
745 builder.mainprogram = arg
746 elif opt in ('-a', '--argv'):
747 builder.argv_emulation = 1
748 elif opt in ('-c', '--creator'):
749 builder.creator = arg
750 elif opt == '--iconfile':
751 builder.iconfile = arg
752 elif opt == "--nib":
753 builder.nibname = arg
754 elif opt in ('-p', '--plist'):
755 builder.plist = Plist.fromFile(arg)
756 elif opt in ('-l', '--link'):
757 builder.symlink = 1
758 elif opt == '--link-exec':
759 builder.symlink_exec = 1
760 elif opt in ('-h', '--help'):
761 usage()
762 elif opt in ('-v', '--verbose'):
763 builder.verbosity += 1
764 elif opt in ('-q', '--quiet'):
765 builder.verbosity -= 1
766 elif opt == '--standalone':
767 builder.standalone = 1
768 elif opt in ('-x', '--exclude'):
769 builder.excludeModules.append(arg)
770 elif opt in ('-i', '--include'):
771 builder.includeModules.append(arg)
772 elif opt == '--package':
773 builder.includePackages.append(arg)
774 elif opt == '--strip':
775 builder.strip = 1
777 if len(args) != 1:
778 usage("Must specify one command ('build', 'report' or 'help')")
779 command = args[0]
781 if command == "build":
782 builder.setup()
783 builder.build()
784 elif command == "report":
785 builder.setup()
786 builder.report()
787 elif command == "help":
788 usage()
789 else:
790 usage("Unknown command '%s'" % command)
793 def buildapp(**kwargs):
794 builder = AppBuilder(**kwargs)
795 main(builder)
798 if __name__ == "__main__":
799 main()