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"]
31 from warnings
import warnpy3k
32 warnpy3k("In 3.x, the bundlebuilder module is removed.", stacklevel
=2)
35 import os
, errno
, shutil
38 from copy
import deepcopy
40 from plistlib
import Plist
41 from types
import FunctionType
as function
43 class BundleBuilderError(Exception): pass
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.
53 def __init__(self
, **kwargs
):
54 defaults
= self
._getDefaults
()
55 defaults
.update(kwargs
)
56 self
.__dict
__.update(defaults
)
58 def _getDefaults(cls
):
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
)
68 _getDefaults
= classmethod(_getDefaults
)
71 class BundleBuilder(Defaults
):
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.
78 # (Note that Defaults.__init__ (deep)copies these values to
79 # instance variables. Mutable defaults are therefore safe.)
81 # Name of the bundle, with or without extension.
84 # The property list ("plist")
85 plist
= Plist(CFBundleDevelopmentRegion
= "English",
86 CFBundleInfoDictionaryVersion
= "6.0")
88 # The type of the bundle.
90 # The creator code of the bundle.
93 # the CFBundleIdentifier (this is used for the preferences file name)
96 # List of files that have to be copied to <bundle>/Contents/Resources.
99 # List of (src, dest) tuples; dest should be a path relative to the bundle
100 # (eg. "Contents/Resources/MyStuff/SomeFile.ext).
103 # List of shared libraries (dylibs, Frameworks) to bundle with the app
104 # will be placed in Contents/Frameworks
107 # Directory where the bundle will be assembled.
110 # Make symlinks instead copying files. This is handy during debugging, but
111 # makes the bundle non-distributable.
117 # Destination root directory
121 # XXX rethink self.name munging, this is brittle.
122 self
.name
, ext
= os
.path
.splitext(self
.name
)
125 bundleextension
= ext
126 # misc (derived) attributes
127 self
.bundlepath
= pathjoin(self
.builddir
, self
.name
+ bundleextension
)
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
136 self
.creator
= "????"
137 plist
.CFBundleSignature
= self
.creator
139 plist
.CFBundleIdentifier
= self
.bundle_id
140 elif not hasattr(plist
, "CFBundleIdentifier"):
141 plist
.CFBundleIdentifier
= self
.name
144 """Build the bundle."""
145 builddir
= self
.builddir
146 if builddir
and not os
.path
.exists(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
+ '~')
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
+ '~'
161 os
.mkdir(self
.bundlepath
)
166 os
.rename(self
.bundlepath
, bp
)
169 self
.message("Done.", 1)
171 def preProcess(self
):
172 """Hook for subclasses."""
174 def postProcess(self
):
175 """Hook for subclasses."""
178 def _addMetaFiles(self
):
179 contents
= pathjoin(self
.bundlepath
, "Contents")
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
)
190 # Write Contents/Info.plist
191 infoplist
= pathjoin(contents
, "Info.plist")
192 self
.plist
.write(infoplist
)
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
))))
203 self
.message("Making symbolic links", 1)
204 msg
= "Making symlink from"
206 self
.message("Copying files", 1)
209 for src
, dst
in files
:
210 if os
.path
.isdir(src
):
211 self
.message("%s %s/ to %s/" % (msg
, src
, dst
), 2)
213 self
.message("%s %s to %s" % (msg
, src
, dst
), 2)
214 dst
= pathjoin(self
.bundlepath
, dst
)
216 symlink(src
, dst
, mkdirs
=1)
218 copy(src
, dst
, mkdirs
=1)
220 def message(self
, msg
, level
=0):
221 if level
<= self
.verbosity
:
224 indent
= (level
- 1) * " "
225 sys
.stderr
.write(indent
+ msg
+ "\n")
228 # XXX something decent
237 MAGIC
= imp
.get_magic()
238 USE_ZIPIMPORT
= "zipimport" in sys
.builtin_module_names
240 # For standalone apps, we have our own minimal site.py. We don't need
241 # all the cruft of the real site.py.
244 if not %(semi_standalone)s:
245 del sys.path[1:] # sys.path[0] is Contents/Resources/
248 ZIP_ARCHIVE
= "Modules.zip"
249 SITE_PY_ZIP
= SITE_PY
+ ("sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
)
251 def getPycData(fullname
, code
, ispkg
):
253 fullname
+= ".__init__"
254 path
= fullname
.replace(".", os
.sep
) + PYC_EXT
255 return path
, MAGIC
+ '\0\0\0\0' + marshal
.dumps(code
)
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.
265 path = os.path.join(p, "%(filename)s")
266 if os.path.exists(path):
269 assert 0, "file not found: %(filename)s"
270 mod = imp.load_dynamic("%(name)s", path)
276 MAYMISS_MODULES
= ['os2', 'nt', 'ntpath', 'dos', 'dospath',
277 'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize',
278 'org.python.core', 'riscos', 'riscosenviron', 'riscospath'
281 STRIP_EXEC
= "/usr/bin/strip"
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.
294 BOOTSTRAP_SCRIPT
= """\
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")
305 sys.argv.insert(1, '-O')
307 sys.argv.insert(1, mainprogram)
308 if %(standalone)s or %(semi_standalone)s:
309 os.environ["PYTHONPATH"] = resdir
311 os.environ["PYTHONHOME"] = resdir
313 pypath = os.getenv("PYTHONPATH", "")
315 pypath = ":" + pypath
316 os.environ["PYTHONPATH"] = resdir + pypath
318 os.environ["PYTHONEXECUTABLE"] = executable
319 os.environ["DYLD_LIBRARY_PATH"] = libdir
320 os.environ["DYLD_FRAMEWORK_PATH"] = libdir
321 os.execve(executable, sys.argv, os.environ)
326 # Optional wrapper that converts "dropped files" into sys.argv values.
329 import argvemulator, os
331 argvemulator.ArgvCollector().mainloop()
332 execfile(os.path.join(os.path.split(__file__)[0], "%(realmainprogram)s"))
336 # When building a standalone app with Python.framework, we need to copy
337 # a subset from Python.framework to the bundle. The following list
338 # specifies exactly what items we'll copy.
340 PYTHONFRAMEWORKGOODIES
= [
341 "Python", # the Python core library
342 "Resources/English.lproj",
343 "Resources/Info.plist",
347 return sys
.exec_prefix
.find("Python.framework") > 0
350 LIB
= os
.path
.join(sys
.prefix
, "lib", "python" + sys
.version
[:3])
351 SITE_PACKAGES
= os
.path
.join(LIB
, "site-packages")
354 class AppBuilder(BundleBuilder
):
356 use_zipimport
= USE_ZIPIMPORT
358 # Override type of the bundle.
361 # platform, name of the subfolder of Contents that contains the executable.
364 # A Python main program. If this argument is given, the main
365 # executable in the bundle will be a small wrapper that invokes
366 # the main program. (XXX Discuss why.)
369 # The main executable. If a Python main program is specified
370 # the executable will be copied to Resources and be invoked
371 # by the wrapper program mentioned above. Otherwise it will
372 # simply be used as the main executable.
375 # The name of the main nib, for Cocoa apps. *Must* be specified
376 # when building a Cocoa app.
379 # The name of the icon file to be copied to Resources and used for
383 # Symlink the executable instead of copying it.
386 # If True, build standalone app.
389 # If True, build semi-standalone app (only includes third-party modules).
392 # If set, use this for #! lines in stead of sys.executable
395 # If True, add a real main program that emulates sys.argv before calling
399 # The following attributes are only used when building a standalone app.
401 # Exclude these modules.
404 # Include these modules.
407 # Include these packages.
410 # Strip binaries from debug info.
413 # Found Python modules: [(name, codeobject, ispkg), ...]
416 # Modules that modulefinder couldn't find:
418 maybeMissingModules
= []
421 if ((self
.standalone
or self
.semi_standalone
)
422 and self
.mainprogram
is None):
423 raise BundleBuilderError
, ("must specify 'mainprogram' when "
424 "building a standalone application.")
425 if self
.mainprogram
is None and self
.executable
is None:
426 raise BundleBuilderError
, ("must specify either or both of "
427 "'executable' and 'mainprogram'")
429 self
.execdir
= pathjoin("Contents", self
.platform
)
431 if self
.name
is not None:
433 elif self
.mainprogram
is not None:
434 self
.name
= os
.path
.splitext(os
.path
.basename(self
.mainprogram
))[0]
435 elif self
.executable
is not None:
436 self
.name
= os
.path
.splitext(os
.path
.basename(self
.executable
))[0]
437 if self
.name
[-4:] != ".app":
440 if self
.executable
is None:
441 if not self
.standalone
and not isFramework():
442 self
.symlink_exec
= 1
444 self
.executable
= self
.python
446 self
.executable
= sys
.executable
449 self
.plist
.NSMainNibFile
= self
.nibname
450 if not hasattr(self
.plist
, "NSPrincipalClass"):
451 self
.plist
.NSPrincipalClass
= "NSApplication"
453 if self
.standalone
and isFramework():
454 self
.addPythonFramework()
456 BundleBuilder
.setup(self
)
458 self
.plist
.CFBundleExecutable
= self
.name
460 if self
.standalone
or self
.semi_standalone
:
461 self
.findDependencies()
463 def preProcess(self
):
464 resdir
= "Contents/Resources"
465 if self
.executable
is not None:
466 if self
.mainprogram
is None:
469 execname
= os
.path
.basename(self
.executable
)
470 execpath
= pathjoin(self
.execdir
, execname
)
471 if not self
.symlink_exec
:
472 self
.files
.append((self
.destroot
+ self
.executable
, execpath
))
473 self
.execpath
= execpath
475 if self
.mainprogram
is not None:
476 mainprogram
= os
.path
.basename(self
.mainprogram
)
477 self
.files
.append((self
.mainprogram
, pathjoin(resdir
, mainprogram
)))
478 if self
.argv_emulation
:
479 # Change the main program, and create the helper main program (which
480 # does argv collection and then calls the real main).
481 # Also update the included modules (if we're creating a standalone
482 # program) and the plist
483 realmainprogram
= mainprogram
484 mainprogram
= '__argvemulator_' + mainprogram
485 resdirpath
= pathjoin(self
.bundlepath
, resdir
)
486 mainprogrampath
= pathjoin(resdirpath
, mainprogram
)
488 open(mainprogrampath
, "w").write(ARGV_EMULATOR
% locals())
489 if self
.standalone
or self
.semi_standalone
:
490 self
.includeModules
.append("argvemulator")
491 self
.includeModules
.append("os")
492 if "CFBundleDocumentTypes" not in self
.plist
:
493 self
.plist
["CFBundleDocumentTypes"] = [
494 { "CFBundleTypeOSTypes" : [
498 "CFBundleTypeRole": "Viewer"}]
499 # Write bootstrap script
500 executable
= os
.path
.basename(self
.executable
)
501 execdir
= pathjoin(self
.bundlepath
, self
.execdir
)
502 bootstrappath
= pathjoin(execdir
, self
.name
)
504 if self
.standalone
or self
.semi_standalone
:
505 # XXX we're screwed when the end user has deleted
507 hashbang
= "/usr/bin/python"
509 hashbang
= self
.python
511 hashbang
= os
.path
.realpath(sys
.executable
)
512 standalone
= self
.standalone
513 semi_standalone
= self
.semi_standalone
514 optimize
= sys
.flags
.optimize
515 open(bootstrappath
, "w").write(BOOTSTRAP_SCRIPT
% locals())
516 os
.chmod(bootstrappath
, 0775)
518 if self
.iconfile
is not None:
519 iconbase
= os
.path
.basename(self
.iconfile
)
520 self
.plist
.CFBundleIconFile
= iconbase
521 self
.files
.append((self
.iconfile
, pathjoin(resdir
, iconbase
)))
523 def postProcess(self
):
524 if self
.standalone
or self
.semi_standalone
:
525 self
.addPythonModules()
526 if self
.strip
and not self
.symlink
:
529 if self
.symlink_exec
and self
.executable
:
530 self
.message("Symlinking executable %s to %s" % (self
.executable
,
532 dst
= pathjoin(self
.bundlepath
, self
.execpath
)
533 makedirs(os
.path
.dirname(dst
))
534 os
.symlink(os
.path
.abspath(self
.executable
), dst
)
536 if self
.missingModules
or self
.maybeMissingModules
:
539 def addPythonFramework(self
):
540 # If we're building a standalone app with Python.framework,
541 # include a minimal subset of Python.framework, *unless*
542 # Python.framework was specified manually in self.libs.
543 for lib
in self
.libs
:
544 if os
.path
.basename(lib
) == "Python.framework":
545 # a Python.framework was specified as a library
548 frameworkpath
= sys
.exec_prefix
[:sys
.exec_prefix
.find(
549 "Python.framework") + len("Python.framework")]
551 version
= sys
.version
[:3]
552 frameworkpath
= pathjoin(frameworkpath
, "Versions", version
)
553 destbase
= pathjoin("Contents", "Frameworks", "Python.framework",
555 for item
in PYTHONFRAMEWORKGOODIES
:
556 src
= pathjoin(frameworkpath
, item
)
557 dst
= pathjoin(destbase
, item
)
558 self
.files
.append((src
, dst
))
560 def _getSiteCode(self
):
561 if self
.use_zipimport
:
562 return compile(SITE_PY
% {"semi_standalone": self
.semi_standalone
},
563 "<-bundlebuilder.py->", "exec")
565 def addPythonModules(self
):
566 self
.message("Adding Python modules", 1)
568 if self
.use_zipimport
:
569 # Create a zip file containing all modules as pyc.
571 relpath
= pathjoin("Contents", "Resources", ZIP_ARCHIVE
)
572 abspath
= pathjoin(self
.bundlepath
, relpath
)
573 zf
= zipfile
.ZipFile(abspath
, "w", zipfile
.ZIP_DEFLATED
)
574 for name
, code
, ispkg
in self
.pymodules
:
575 self
.message("Adding Python module %s" % name
, 2)
576 path
, pyc
= getPycData(name
, code
, ispkg
)
577 zf
.writestr(path
, pyc
)
580 sitepath
= pathjoin(self
.bundlepath
, "Contents", "Resources",
582 writePyc(self
._getSiteCode
(), sitepath
)
584 # Create individual .pyc files.
585 for name
, code
, ispkg
in self
.pymodules
:
588 path
= name
.split(".")
589 path
= pathjoin("Contents", "Resources", *path
) + PYC_EXT
592 self
.message("Adding Python package %s" % path
, 2)
594 self
.message("Adding Python module %s" % path
, 2)
596 abspath
= pathjoin(self
.bundlepath
, path
)
597 makedirs(os
.path
.dirname(abspath
))
598 writePyc(code
, abspath
)
600 def stripBinaries(self
):
601 if not os
.path
.exists(STRIP_EXEC
):
602 self
.message("Error: can't strip binaries: no strip program at "
603 "%s" % STRIP_EXEC
, 0)
606 self
.message("Stripping binaries", 1)
608 for name
in os
.listdir(top
):
609 path
= pathjoin(top
, name
)
610 if os
.path
.islink(path
):
612 if os
.path
.isdir(path
):
615 mod
= os
.stat(path
)[stat
.ST_MODE
]
618 relpath
= path
[len(self
.bundlepath
):]
619 self
.message("Stripping %s" % relpath
, 2)
620 inf
, outf
= os
.popen4("%s -S \"%s\"" %
622 output
= outf
.read().strip()
624 # usually not a real problem, like when we're
625 # trying to strip a script
626 self
.message("Problem stripping %s:" % relpath
, 3)
627 self
.message(output
, 3)
628 walk(self
.bundlepath
)
630 def findDependencies(self
):
631 self
.message("Finding module dependencies", 1)
633 mf
= modulefinder
.ModuleFinder(excludes
=self
.excludeModules
)
634 if self
.use_zipimport
:
635 # zipimport imports zlib, must add it manually
636 mf
.import_hook("zlib")
637 # manually add our own site.py
638 site
= mf
.add_module("site")
639 site
.__code
__ = self
._getSiteCode
()
640 mf
.scan_code(site
.__code
__, site
)
642 # warnings.py gets imported implicitly from C
643 mf
.import_hook("warnings")
645 includeModules
= self
.includeModules
[:]
646 for name
in self
.includePackages
:
647 includeModules
.extend(findPackageContents(name
).keys())
648 for name
in includeModules
:
652 self
.missingModules
.append(name
)
654 mf
.run_script(self
.mainprogram
)
655 modules
= mf
.modules
.items()
657 for name
, mod
in modules
:
659 if path
and self
.semi_standalone
:
660 # skip the standard library
661 if path
.startswith(LIB
) and not path
.startswith(SITE_PACKAGES
):
663 if path
and mod
.__code
__ is None:
665 filename
= os
.path
.basename(path
)
666 pathitems
= name
.split(".")[:-1] + [filename
]
667 dstpath
= pathjoin(*pathitems
)
668 if self
.use_zipimport
:
670 # neatly pack all extension modules in a subdirectory,
671 # except zlib, since it's necessary for bootstrapping.
672 dstpath
= pathjoin("ExtensionModules", dstpath
)
673 # Python modules are stored in a Zip archive, but put
674 # extensions in Contents/Resources/. Add a tiny "loader"
675 # program in the Zip archive. Due to Thomas Heller.
676 source
= EXT_LOADER
% {"name": name
, "filename": dstpath
}
677 code
= compile(source
, "<dynloader for %s>" % name
, "exec")
679 self
.files
.append((path
, pathjoin("Contents", "Resources", dstpath
)))
680 if mod
.__code
__ is not None:
681 ispkg
= mod
.__path
__ is not None
682 if not self
.use_zipimport
or name
!= "site":
683 # Our site.py is doing the bootstrapping, so we must
684 # include a real .pyc file if self.use_zipimport is True.
685 self
.pymodules
.append((name
, mod
.__code
__, ispkg
))
687 if hasattr(mf
, "any_missing_maybe"):
688 missing
, maybe
= mf
.any_missing_maybe()
690 missing
= mf
.any_missing()
692 self
.missingModules
.extend(missing
)
693 self
.maybeMissingModules
.extend(maybe
)
695 def reportMissing(self
):
696 missing
= [name
for name
in self
.missingModules
697 if name
not in MAYMISS_MODULES
]
698 if self
.maybeMissingModules
:
699 maybe
= self
.maybeMissingModules
701 maybe
= [name
for name
in missing
if "." in name
]
702 missing
= [name
for name
in missing
if "." not in name
]
706 self
.message("Warning: couldn't find the following submodules:", 1)
707 self
.message(" (Note that these could be false alarms -- "
708 "it's not always", 1)
709 self
.message(" possible to distinguish between \"from package "
710 "import submodule\" ", 1)
711 self
.message(" and \"from package import name\")", 1)
713 self
.message(" ? " + name
, 1)
715 self
.message("Warning: couldn't find the following modules:", 1)
717 self
.message(" ? " + name
, 1)
720 # XXX something decent
722 pprint
.pprint(self
.__dict
__)
723 if self
.standalone
or self
.semi_standalone
:
730 SUFFIXES
= [_suf
for _suf
, _mode
, _tp
in imp
.get_suffixes()]
731 identifierRE
= re
.compile(r
"[_a-zA-z][_a-zA-Z0-9]*$")
733 def findPackageContents(name
, searchpath
=None):
734 head
= name
.split(".")[-1]
735 if identifierRE
.match(head
) is None:
738 fp
, path
, (ext
, mode
, tp
) = imp
.find_module(head
, searchpath
)
741 modules
= {name
: None}
742 if tp
== imp
.PKG_DIRECTORY
and path
:
743 files
= os
.listdir(path
)
745 sub
, ext
= os
.path
.splitext(sub
)
746 fullname
= name
+ "." + sub
747 if sub
!= "__init__" and fullname
not in modules
:
748 modules
.update(findPackageContents(fullname
, [path
]))
751 def writePyc(code
, path
):
754 f
.write("\0" * 4) # don't bother about a time stamp
755 marshal
.dump(code
, f
)
758 def copy(src
, dst
, mkdirs
=0):
759 """Copy a file or a directory."""
761 makedirs(os
.path
.dirname(dst
))
762 if os
.path
.isdir(src
):
763 shutil
.copytree(src
, dst
, symlinks
=1)
765 shutil
.copy2(src
, dst
)
767 def copytodir(src
, dstdir
):
768 """Copy a file or a directory to an existing directory."""
769 dst
= pathjoin(dstdir
, os
.path
.basename(src
))
773 """Make all directories leading up to 'dir' including the leaf
774 directory. Don't moan if any path element already exists."""
778 if why
.errno
!= errno
.EEXIST
:
781 def symlink(src
, dst
, mkdirs
=0):
782 """Copy a file or a directory."""
783 if not os
.path
.exists(src
):
784 raise IOError, "No such file or directory: '%s'" % src
786 makedirs(os
.path
.dirname(dst
))
787 os
.symlink(os
.path
.abspath(src
), dst
)
790 """Safe wrapper for os.path.join: asserts that all but the first
791 argument are relative paths."""
794 return os
.path
.join(*args
)
799 python bundlebuilder.py [options] command
800 python mybuildscript.py [options] command
803 build build the application
804 report print a report
807 -b, --builddir=DIR the build directory; defaults to "build"
808 -n, --name=NAME application name
809 -r, --resource=FILE extra file or folder to be copied to Resources
810 -f, --file=SRC:DST extra file or folder to be copied into the bundle;
811 DST must be a path relative to the bundle root
812 -e, --executable=FILE the executable to be used
813 -m, --mainprogram=FILE the Python main program
814 -a, --argv add a wrapper main program to create sys.argv
815 -p, --plist=FILE .plist file (default: generate one)
816 --nib=NAME main nib name
817 -c, --creator=CCCC 4-char creator code (default: '????')
818 --iconfile=FILE filename of the icon (an .icns file) to be used
820 --bundle-id=ID the CFBundleIdentifier, in reverse-dns format
821 (eg. org.python.BuildApplet; this is used for
822 the preferences file name)
823 -l, --link symlink files/folder instead of copying them
824 --link-exec symlink the executable instead of copying it
825 --standalone build a standalone application, which is fully
826 independent of a Python installation
827 --semi-standalone build a standalone application, which depends on
828 an installed Python, yet includes all third-party
830 --no-zipimport Do not copy code into a zip file
831 --python=FILE Python to use in #! line in stead of current Python
832 --lib=FILE shared library or framework to be copied into
834 -x, --exclude=MODULE exclude module (with --(semi-)standalone)
835 -i, --include=MODULE include module (with --(semi-)standalone)
836 --package=PACKAGE include a whole package (with --(semi-)standalone)
837 --strip strip binaries (remove debug info)
838 -v, --verbose increase verbosity level
839 -q, --quiet decrease verbosity level
840 -h, --help print this message
849 def main(builder
=None):
851 builder
= AppBuilder(verbosity
=1)
853 shortopts
= "b:n:r:f:e:m:c:p:lx:i:hvqa"
854 longopts
= ("builddir=", "name=", "resource=", "file=", "executable=",
855 "mainprogram=", "creator=", "nib=", "plist=", "link",
856 "link-exec", "help", "verbose", "quiet", "argv", "standalone",
857 "exclude=", "include=", "package=", "strip", "iconfile=",
858 "lib=", "python=", "semi-standalone", "bundle-id=", "destroot="
863 options
, args
= getopt
.getopt(sys
.argv
[1:], shortopts
, longopts
)
867 for opt
, arg
in options
:
868 if opt
in ('-b', '--builddir'):
869 builder
.builddir
= arg
870 elif opt
in ('-n', '--name'):
872 elif opt
in ('-r', '--resource'):
873 builder
.resources
.append(os
.path
.normpath(arg
))
874 elif opt
in ('-f', '--file'):
875 srcdst
= arg
.split(':')
877 usage("-f or --file argument must be two paths, "
878 "separated by a colon")
879 builder
.files
.append(srcdst
)
880 elif opt
in ('-e', '--executable'):
881 builder
.executable
= arg
882 elif opt
in ('-m', '--mainprogram'):
883 builder
.mainprogram
= arg
884 elif opt
in ('-a', '--argv'):
885 builder
.argv_emulation
= 1
886 elif opt
in ('-c', '--creator'):
887 builder
.creator
= arg
888 elif opt
== '--bundle-id':
889 builder
.bundle_id
= arg
890 elif opt
== '--iconfile':
891 builder
.iconfile
= arg
893 builder
.libs
.append(os
.path
.normpath(arg
))
895 builder
.nibname
= arg
896 elif opt
in ('-p', '--plist'):
897 builder
.plist
= Plist
.fromFile(arg
)
898 elif opt
in ('-l', '--link'):
900 elif opt
== '--link-exec':
901 builder
.symlink_exec
= 1
902 elif opt
in ('-h', '--help'):
904 elif opt
in ('-v', '--verbose'):
905 builder
.verbosity
+= 1
906 elif opt
in ('-q', '--quiet'):
907 builder
.verbosity
-= 1
908 elif opt
== '--standalone':
909 builder
.standalone
= 1
910 elif opt
== '--semi-standalone':
911 builder
.semi_standalone
= 1
912 elif opt
== '--python':
914 elif opt
in ('-x', '--exclude'):
915 builder
.excludeModules
.append(arg
)
916 elif opt
in ('-i', '--include'):
917 builder
.includeModules
.append(arg
)
918 elif opt
== '--package':
919 builder
.includePackages
.append(arg
)
920 elif opt
== '--strip':
922 elif opt
== '--destroot':
923 builder
.destroot
= arg
924 elif opt
== '--no-zipimport':
925 builder
.use_zipimport
= False
928 usage("Must specify one command ('build', 'report' or 'help')")
931 if command
== "build":
934 elif command
== "report":
937 elif command
== "help":
940 usage("Unknown command '%s'" % command
)
943 def buildapp(**kwargs
):
944 builder
= AppBuilder(**kwargs
)
948 if __name__
== "__main__":