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.
114 # Destination root directory
118 # XXX rethink self.name munging, this is brittle.
119 self
.name
, ext
= os
.path
.splitext(self
.name
)
122 bundleextension
= ext
123 # misc (derived) attributes
124 self
.bundlepath
= pathjoin(self
.builddir
, self
.name
+ bundleextension
)
127 plist
.CFBundleName
= self
.name
128 plist
.CFBundlePackageType
= self
.type
129 if self
.creator
is None:
130 if hasattr(plist
, "CFBundleSignature"):
131 self
.creator
= plist
.CFBundleSignature
133 self
.creator
= "????"
134 plist
.CFBundleSignature
= self
.creator
136 plist
.CFBundleIdentifier
= self
.bundle_id
137 elif not hasattr(plist
, "CFBundleIdentifier"):
138 plist
.CFBundleIdentifier
= self
.name
141 """Build the bundle."""
142 builddir
= self
.builddir
143 if builddir
and not os
.path
.exists(builddir
):
145 self
.message("Building %s" % repr(self
.bundlepath
), 1)
146 if os
.path
.exists(self
.bundlepath
):
147 shutil
.rmtree(self
.bundlepath
)
148 os
.mkdir(self
.bundlepath
)
153 self
.message("Done.", 1)
155 def preProcess(self
):
156 """Hook for subclasses."""
158 def postProcess(self
):
159 """Hook for subclasses."""
162 def _addMetaFiles(self
):
163 contents
= pathjoin(self
.bundlepath
, "Contents")
166 # Write Contents/PkgInfo
167 assert len(self
.type) == len(self
.creator
) == 4, \
168 "type and creator must be 4-byte strings."
169 pkginfo
= pathjoin(contents
, "PkgInfo")
170 f
= open(pkginfo
, "wb")
171 f
.write(self
.type + self
.creator
)
174 # Write Contents/Info.plist
175 infoplist
= pathjoin(contents
, "Info.plist")
176 self
.plist
.write(infoplist
)
178 def _copyFiles(self
):
179 files
= self
.files
[:]
180 for path
in self
.resources
:
181 files
.append((path
, pathjoin("Contents", "Resources",
182 os
.path
.basename(path
))))
183 for path
in self
.libs
:
184 files
.append((path
, pathjoin("Contents", "Frameworks",
185 os
.path
.basename(path
))))
187 self
.message("Making symbolic links", 1)
188 msg
= "Making symlink from"
190 self
.message("Copying files", 1)
193 for src
, dst
in files
:
194 if os
.path
.isdir(src
):
195 self
.message("%s %s/ to %s/" % (msg
, src
, dst
), 2)
197 self
.message("%s %s to %s" % (msg
, src
, dst
), 2)
198 dst
= pathjoin(self
.bundlepath
, dst
)
200 symlink(src
, dst
, mkdirs
=1)
202 copy(src
, dst
, mkdirs
=1)
204 def message(self
, msg
, level
=0):
205 if level
<= self
.verbosity
:
208 indent
= (level
- 1) * " "
209 sys
.stderr
.write(indent
+ msg
+ "\n")
212 # XXX something decent
221 MAGIC
= imp
.get_magic()
222 USE_ZIPIMPORT
= "zipimport" in sys
.builtin_module_names
224 # For standalone apps, we have our own minimal site.py. We don't need
225 # all the cruft of the real site.py.
228 if not %(semi_standalone)s:
229 del sys.path[1:] # sys.path[0] is Contents/Resources/
233 ZIP_ARCHIVE
= "Modules.zip"
234 SITE_PY
+= "sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
235 def getPycData(fullname
, code
, ispkg
):
237 fullname
+= ".__init__"
238 path
= fullname
.replace(".", os
.sep
) + PYC_EXT
239 return path
, MAGIC
+ '\0\0\0\0' + marshal
.dumps(code
)
242 # Extension modules can't be in the modules zip archive, so a placeholder
243 # is added instead, that loads the extension from a specified location.
249 path = os.path.join(p, "%(filename)s")
250 if os.path.exists(path):
253 assert 0, "file not found: %(filename)s"
254 mod = imp.load_dynamic("%(name)s", path)
260 MAYMISS_MODULES
= ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath',
261 'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize',
262 'org.python.core', 'riscos', 'riscosenviron', 'riscospath'
265 STRIP_EXEC
= "/usr/bin/strip"
268 # We're using a stock interpreter to run the app, yet we need
269 # a way to pass the Python main program to the interpreter. The
270 # bootstrapping script fires up the interpreter with the right
271 # arguments. os.execve() is used as OSX doesn't like us to
272 # start a real new process. Also, the executable name must match
273 # the CFBundleExecutable value in the Info.plist, so we lie
274 # deliberately with argv[0]. The actual Python executable is
275 # passed in an environment variable so we can "repair"
276 # sys.executable later.
278 BOOTSTRAP_SCRIPT
= """\
282 execdir = os.path.dirname(sys.argv[0])
283 executable = os.path.join(execdir, "%(executable)s")
284 resdir = os.path.join(os.path.dirname(execdir), "Resources")
285 libdir = os.path.join(os.path.dirname(execdir), "Frameworks")
286 mainprogram = os.path.join(resdir, "%(mainprogram)s")
288 sys.argv.insert(1, mainprogram)
289 if %(standalone)s or %(semi_standalone)s:
290 os.environ["PYTHONPATH"] = resdir
292 os.environ["PYTHONHOME"] = resdir
294 pypath = os.getenv("PYTHONPATH", "")
296 pypath = ":" + pypath
297 os.environ["PYTHONPATH"] = resdir + pypath
298 os.environ["PYTHONEXECUTABLE"] = executable
299 os.environ["DYLD_LIBRARY_PATH"] = libdir
300 os.environ["DYLD_FRAMEWORK_PATH"] = libdir
301 os.execve(executable, sys.argv, os.environ)
306 # Optional wrapper that converts "dropped files" into sys.argv values.
309 import argvemulator, os
311 argvemulator.ArgvCollector().mainloop()
312 execfile(os.path.join(os.path.split(__file__)[0], "%(realmainprogram)s"))
316 # When building a standalone app with Python.framework, we need to copy
317 # a subset from Python.framework to the bundle. The following list
318 # specifies exactly what items we'll copy.
320 PYTHONFRAMEWORKGOODIES
= [
321 "Python", # the Python core library
322 "Resources/English.lproj",
323 "Resources/Info.plist",
324 "Resources/version.plist",
328 return sys
.exec_prefix
.find("Python.framework") > 0
331 LIB
= os
.path
.join(sys
.prefix
, "lib", "python" + sys
.version
[:3])
332 SITE_PACKAGES
= os
.path
.join(LIB
, "site-packages")
335 class AppBuilder(BundleBuilder
):
337 # Override type of the bundle.
340 # platform, name of the subfolder of Contents that contains the executable.
343 # A Python main program. If this argument is given, the main
344 # executable in the bundle will be a small wrapper that invokes
345 # the main program. (XXX Discuss why.)
348 # The main executable. If a Python main program is specified
349 # the executable will be copied to Resources and be invoked
350 # by the wrapper program mentioned above. Otherwise it will
351 # simply be used as the main executable.
354 # The name of the main nib, for Cocoa apps. *Must* be specified
355 # when building a Cocoa app.
358 # The name of the icon file to be copied to Resources and used for
362 # Symlink the executable instead of copying it.
365 # If True, build standalone app.
368 # If True, build semi-standalone app (only includes third-party modules).
371 # If set, use this for #! lines in stead of sys.executable
374 # If True, add a real main program that emulates sys.argv before calling
378 # The following attributes are only used when building a standalone app.
380 # Exclude these modules.
383 # Include these modules.
386 # Include these packages.
389 # Strip binaries from debug info.
392 # Found Python modules: [(name, codeobject, ispkg), ...]
395 # Modules that modulefinder couldn't find:
397 maybeMissingModules
= []
400 if ((self
.standalone
or self
.semi_standalone
)
401 and self
.mainprogram
is None):
402 raise BundleBuilderError
, ("must specify 'mainprogram' when "
403 "building a standalone application.")
404 if self
.mainprogram
is None and self
.executable
is None:
405 raise BundleBuilderError
, ("must specify either or both of "
406 "'executable' and 'mainprogram'")
408 self
.execdir
= pathjoin("Contents", self
.platform
)
410 if self
.name
is not None:
412 elif self
.mainprogram
is not None:
413 self
.name
= os
.path
.splitext(os
.path
.basename(self
.mainprogram
))[0]
414 elif executable
is not None:
415 self
.name
= os
.path
.splitext(os
.path
.basename(self
.executable
))[0]
416 if self
.name
[-4:] != ".app":
419 if self
.executable
is None:
420 if not self
.standalone
and not isFramework():
421 self
.symlink_exec
= 1
423 self
.executable
= self
.python
425 self
.executable
= sys
.executable
428 self
.plist
.NSMainNibFile
= self
.nibname
429 if not hasattr(self
.plist
, "NSPrincipalClass"):
430 self
.plist
.NSPrincipalClass
= "NSApplication"
432 if self
.standalone
and isFramework():
433 self
.addPythonFramework()
435 BundleBuilder
.setup(self
)
437 self
.plist
.CFBundleExecutable
= self
.name
439 if self
.standalone
or self
.semi_standalone
:
440 self
.findDependencies()
442 def preProcess(self
):
443 resdir
= "Contents/Resources"
444 if self
.executable
is not None:
445 if self
.mainprogram
is None:
448 execname
= os
.path
.basename(self
.executable
)
449 execpath
= pathjoin(self
.execdir
, execname
)
450 if not self
.symlink_exec
:
451 self
.files
.append((self
.destroot
+ self
.executable
, execpath
))
452 self
.execpath
= execpath
454 if self
.mainprogram
is not None:
455 mainprogram
= os
.path
.basename(self
.mainprogram
)
456 self
.files
.append((self
.mainprogram
, pathjoin(resdir
, mainprogram
)))
457 if self
.argv_emulation
:
458 # Change the main program, and create the helper main program (which
459 # does argv collection and then calls the real main).
460 # Also update the included modules (if we're creating a standalone
461 # program) and the plist
462 realmainprogram
= mainprogram
463 mainprogram
= '__argvemulator_' + mainprogram
464 resdirpath
= pathjoin(self
.bundlepath
, resdir
)
465 mainprogrampath
= pathjoin(resdirpath
, mainprogram
)
467 open(mainprogrampath
, "w").write(ARGV_EMULATOR
% locals())
468 if self
.standalone
or self
.semi_standalone
:
469 self
.includeModules
.append("argvemulator")
470 self
.includeModules
.append("os")
471 if not self
.plist
.has_key("CFBundleDocumentTypes"):
472 self
.plist
["CFBundleDocumentTypes"] = [
473 { "CFBundleTypeOSTypes" : [
477 "CFBundleTypeRole": "Viewer"}]
478 # Write bootstrap script
479 executable
= os
.path
.basename(self
.executable
)
480 execdir
= pathjoin(self
.bundlepath
, self
.execdir
)
481 bootstrappath
= pathjoin(execdir
, self
.name
)
483 if self
.standalone
or self
.semi_standalone
:
484 # XXX we're screwed when the end user has deleted
486 hashbang
= "/usr/bin/python"
488 hashbang
= self
.python
490 hashbang
= os
.path
.realpath(sys
.executable
)
491 standalone
= self
.standalone
492 semi_standalone
= self
.semi_standalone
493 open(bootstrappath
, "w").write(BOOTSTRAP_SCRIPT
% locals())
494 os
.chmod(bootstrappath
, 0775)
496 if self
.iconfile
is not None:
497 iconbase
= os
.path
.basename(self
.iconfile
)
498 self
.plist
.CFBundleIconFile
= iconbase
499 self
.files
.append((self
.iconfile
, pathjoin(resdir
, iconbase
)))
501 def postProcess(self
):
502 if self
.standalone
or self
.semi_standalone
:
503 self
.addPythonModules()
504 if self
.strip
and not self
.symlink
:
507 if self
.symlink_exec
and self
.executable
:
508 self
.message("Symlinking executable %s to %s" % (self
.executable
,
510 dst
= pathjoin(self
.bundlepath
, self
.execpath
)
511 makedirs(os
.path
.dirname(dst
))
512 os
.symlink(os
.path
.abspath(self
.executable
), dst
)
514 if self
.missingModules
or self
.maybeMissingModules
:
517 def addPythonFramework(self
):
518 # If we're building a standalone app with Python.framework,
519 # include a minimal subset of Python.framework, *unless*
520 # Python.framework was specified manually in self.libs.
521 for lib
in self
.libs
:
522 if os
.path
.basename(lib
) == "Python.framework":
523 # a Python.framework was specified as a library
526 frameworkpath
= sys
.exec_prefix
[:sys
.exec_prefix
.find(
527 "Python.framework") + len("Python.framework")]
529 version
= sys
.version
[:3]
530 frameworkpath
= pathjoin(frameworkpath
, "Versions", version
)
531 destbase
= pathjoin("Contents", "Frameworks", "Python.framework",
533 for item
in PYTHONFRAMEWORKGOODIES
:
534 src
= pathjoin(frameworkpath
, item
)
535 dst
= pathjoin(destbase
, item
)
536 self
.files
.append((src
, dst
))
538 def _getSiteCode(self
):
539 return compile(SITE_PY
% {"semi_standalone": self
.semi_standalone
},
540 "<-bundlebuilder.py->", "exec")
542 def addPythonModules(self
):
543 self
.message("Adding Python modules", 1)
546 # Create a zip file containing all modules as pyc.
548 relpath
= pathjoin("Contents", "Resources", ZIP_ARCHIVE
)
549 abspath
= pathjoin(self
.bundlepath
, relpath
)
550 zf
= zipfile
.ZipFile(abspath
, "w", zipfile
.ZIP_DEFLATED
)
551 for name
, code
, ispkg
in self
.pymodules
:
552 self
.message("Adding Python module %s" % name
, 2)
553 path
, pyc
= getPycData(name
, code
, ispkg
)
554 zf
.writestr(path
, pyc
)
557 sitepath
= pathjoin(self
.bundlepath
, "Contents", "Resources",
559 writePyc(self
._getSiteCode
(), sitepath
)
561 # Create individual .pyc files.
562 for name
, code
, ispkg
in self
.pymodules
:
565 path
= name
.split(".")
566 path
= pathjoin("Contents", "Resources", *path
) + PYC_EXT
569 self
.message("Adding Python package %s" % path
, 2)
571 self
.message("Adding Python module %s" % path
, 2)
573 abspath
= pathjoin(self
.bundlepath
, path
)
574 makedirs(os
.path
.dirname(abspath
))
575 writePyc(code
, abspath
)
577 def stripBinaries(self
):
578 if not os
.path
.exists(STRIP_EXEC
):
579 self
.message("Error: can't strip binaries: no strip program at "
580 "%s" % STRIP_EXEC
, 0)
583 self
.message("Stripping binaries", 1)
585 for name
in os
.listdir(top
):
586 path
= pathjoin(top
, name
)
587 if os
.path
.islink(path
):
589 if os
.path
.isdir(path
):
592 mod
= os
.stat(path
)[stat
.ST_MODE
]
595 relpath
= path
[len(self
.bundlepath
):]
596 self
.message("Stripping %s" % relpath
, 2)
597 inf
, outf
= os
.popen4("%s -S \"%s\"" %
599 output
= outf
.read().strip()
601 # usually not a real problem, like when we're
602 # trying to strip a script
603 self
.message("Problem stripping %s:" % relpath
, 3)
604 self
.message(output
, 3)
605 walk(self
.bundlepath
)
607 def findDependencies(self
):
608 self
.message("Finding module dependencies", 1)
610 mf
= modulefinder
.ModuleFinder(excludes
=self
.excludeModules
)
612 # zipimport imports zlib, must add it manually
613 mf
.import_hook("zlib")
614 # manually add our own site.py
615 site
= mf
.add_module("site")
616 site
.__code
__ = self
._getSiteCode
()
617 mf
.scan_code(site
.__code
__, site
)
619 # warnings.py gets imported implicitly from C
620 mf
.import_hook("warnings")
622 includeModules
= self
.includeModules
[:]
623 for name
in self
.includePackages
:
624 includeModules
.extend(findPackageContents(name
).keys())
625 for name
in includeModules
:
629 self
.missingModules
.append(name
)
631 mf
.run_script(self
.mainprogram
)
632 modules
= mf
.modules
.items()
634 for name
, mod
in modules
:
636 if path
and self
.semi_standalone
:
637 # skip the standard library
638 if path
.startswith(LIB
) and not path
.startswith(SITE_PACKAGES
):
640 if path
and mod
.__code
__ is None:
642 filename
= os
.path
.basename(path
)
643 pathitems
= name
.split(".")[:-1] + [filename
]
644 dstpath
= pathjoin(*pathitems
)
647 # neatly pack all extension modules in a subdirectory,
648 # except zlib, since it's neccesary for bootstrapping.
649 dstpath
= pathjoin("ExtensionModules", dstpath
)
650 # Python modules are stored in a Zip archive, but put
651 # extensions in Contents/Resources/. Add a tiny "loader"
652 # program in the Zip archive. Due to Thomas Heller.
653 source
= EXT_LOADER
% {"name": name
, "filename": dstpath
}
654 code
= compile(source
, "<dynloader for %s>" % name
, "exec")
656 self
.files
.append((path
, pathjoin("Contents", "Resources", dstpath
)))
657 if mod
.__code
__ is not None:
658 ispkg
= mod
.__path
__ is not None
659 if not USE_ZIPIMPORT
or name
!= "site":
660 # Our site.py is doing the bootstrapping, so we must
661 # include a real .pyc file if USE_ZIPIMPORT is True.
662 self
.pymodules
.append((name
, mod
.__code
__, ispkg
))
664 if hasattr(mf
, "any_missing_maybe"):
665 missing
, maybe
= mf
.any_missing_maybe()
667 missing
= mf
.any_missing()
669 self
.missingModules
.extend(missing
)
670 self
.maybeMissingModules
.extend(maybe
)
672 def reportMissing(self
):
673 missing
= [name
for name
in self
.missingModules
674 if name
not in MAYMISS_MODULES
]
675 if self
.maybeMissingModules
:
676 maybe
= self
.maybeMissingModules
678 maybe
= [name
for name
in missing
if "." in name
]
679 missing
= [name
for name
in missing
if "." not in name
]
683 self
.message("Warning: couldn't find the following submodules:", 1)
684 self
.message(" (Note that these could be false alarms -- "
685 "it's not always", 1)
686 self
.message(" possible to distinguish between \"from package "
687 "import submodule\" ", 1)
688 self
.message(" and \"from package import name\")", 1)
690 self
.message(" ? " + name
, 1)
692 self
.message("Warning: couldn't find the following modules:", 1)
694 self
.message(" ? " + name
, 1)
697 # XXX something decent
699 pprint
.pprint(self
.__dict
__)
700 if self
.standalone
or self
.semi_standalone
:
707 SUFFIXES
= [_suf
for _suf
, _mode
, _tp
in imp
.get_suffixes()]
708 identifierRE
= re
.compile(r
"[_a-zA-z][_a-zA-Z0-9]*$")
710 def findPackageContents(name
, searchpath
=None):
711 head
= name
.split(".")[-1]
712 if identifierRE
.match(head
) is None:
715 fp
, path
, (ext
, mode
, tp
) = imp
.find_module(head
, searchpath
)
718 modules
= {name
: None}
719 if tp
== imp
.PKG_DIRECTORY
and path
:
720 files
= os
.listdir(path
)
722 sub
, ext
= os
.path
.splitext(sub
)
723 fullname
= name
+ "." + sub
724 if sub
!= "__init__" and fullname
not in modules
:
725 modules
.update(findPackageContents(fullname
, [path
]))
728 def writePyc(code
, path
):
731 f
.write("\0" * 4) # don't bother about a time stamp
732 marshal
.dump(code
, f
)
735 def copy(src
, dst
, mkdirs
=0):
736 """Copy a file or a directory."""
738 makedirs(os
.path
.dirname(dst
))
739 if os
.path
.isdir(src
):
740 shutil
.copytree(src
, dst
, symlinks
=1)
742 shutil
.copy2(src
, dst
)
744 def copytodir(src
, dstdir
):
745 """Copy a file or a directory to an existing directory."""
746 dst
= pathjoin(dstdir
, os
.path
.basename(src
))
750 """Make all directories leading up to 'dir' including the leaf
751 directory. Don't moan if any path element already exists."""
755 if why
.errno
!= errno
.EEXIST
:
758 def symlink(src
, dst
, mkdirs
=0):
759 """Copy a file or a directory."""
760 if not os
.path
.exists(src
):
761 raise IOError, "No such file or directory: '%s'" % src
763 makedirs(os
.path
.dirname(dst
))
764 os
.symlink(os
.path
.abspath(src
), dst
)
767 """Safe wrapper for os.path.join: asserts that all but the first
768 argument are relative paths."""
771 return os
.path
.join(*args
)
776 python bundlebuilder.py [options] command
777 python mybuildscript.py [options] command
780 build build the application
781 report print a report
784 -b, --builddir=DIR the build directory; defaults to "build"
785 -n, --name=NAME application name
786 -r, --resource=FILE extra file or folder to be copied to Resources
787 -f, --file=SRC:DST extra file or folder to be copied into the bundle;
788 DST must be a path relative to the bundle root
789 -e, --executable=FILE the executable to be used
790 -m, --mainprogram=FILE the Python main program
791 -a, --argv add a wrapper main program to create sys.argv
792 -p, --plist=FILE .plist file (default: generate one)
793 --nib=NAME main nib name
794 -c, --creator=CCCC 4-char creator code (default: '????')
795 --iconfile=FILE filename of the icon (an .icns file) to be used
797 --bundle-id=ID the CFBundleIdentifier, in reverse-dns format
798 (eg. org.python.BuildApplet; this is used for
799 the preferences file name)
800 -l, --link symlink files/folder instead of copying them
801 --link-exec symlink the executable instead of copying it
802 --standalone build a standalone application, which is fully
803 independent of a Python installation
804 --semi-standalone build a standalone application, which depends on
805 an installed Python, yet includes all third-party
807 --python=FILE Python to use in #! line in stead of current Python
808 --lib=FILE shared library or framework to be copied into
810 -x, --exclude=MODULE exclude module (with --(semi-)standalone)
811 -i, --include=MODULE include module (with --(semi-)standalone)
812 --package=PACKAGE include a whole package (with --(semi-)standalone)
813 --strip strip binaries (remove debug info)
814 -v, --verbose increase verbosity level
815 -q, --quiet decrease verbosity level
816 -h, --help print this message
825 def main(builder
=None):
827 builder
= AppBuilder(verbosity
=1)
829 shortopts
= "b:n:r:f:e:m:c:p:lx:i:hvqa"
830 longopts
= ("builddir=", "name=", "resource=", "file=", "executable=",
831 "mainprogram=", "creator=", "nib=", "plist=", "link",
832 "link-exec", "help", "verbose", "quiet", "argv", "standalone",
833 "exclude=", "include=", "package=", "strip", "iconfile=",
834 "lib=", "python=", "semi-standalone", "bundle-id=", "destroot=")
837 options
, args
= getopt
.getopt(sys
.argv
[1:], shortopts
, longopts
)
841 for opt
, arg
in options
:
842 if opt
in ('-b', '--builddir'):
843 builder
.builddir
= arg
844 elif opt
in ('-n', '--name'):
846 elif opt
in ('-r', '--resource'):
847 builder
.resources
.append(os
.path
.normpath(arg
))
848 elif opt
in ('-f', '--file'):
849 srcdst
= arg
.split(':')
851 usage("-f or --file argument must be two paths, "
852 "separated by a colon")
853 builder
.files
.append(srcdst
)
854 elif opt
in ('-e', '--executable'):
855 builder
.executable
= arg
856 elif opt
in ('-m', '--mainprogram'):
857 builder
.mainprogram
= arg
858 elif opt
in ('-a', '--argv'):
859 builder
.argv_emulation
= 1
860 elif opt
in ('-c', '--creator'):
861 builder
.creator
= arg
862 elif opt
== '--bundle-id':
863 builder
.bundle_id
= arg
864 elif opt
== '--iconfile':
865 builder
.iconfile
= arg
867 builder
.libs
.append(os
.path
.normpath(arg
))
869 builder
.nibname
= arg
870 elif opt
in ('-p', '--plist'):
871 builder
.plist
= Plist
.fromFile(arg
)
872 elif opt
in ('-l', '--link'):
874 elif opt
== '--link-exec':
875 builder
.symlink_exec
= 1
876 elif opt
in ('-h', '--help'):
878 elif opt
in ('-v', '--verbose'):
879 builder
.verbosity
+= 1
880 elif opt
in ('-q', '--quiet'):
881 builder
.verbosity
-= 1
882 elif opt
== '--standalone':
883 builder
.standalone
= 1
884 elif opt
== '--semi-standalone':
885 builder
.semi_standalone
= 1
886 elif opt
== '--python':
888 elif opt
in ('-x', '--exclude'):
889 builder
.excludeModules
.append(arg
)
890 elif opt
in ('-i', '--include'):
891 builder
.includeModules
.append(arg
)
892 elif opt
== '--package':
893 builder
.includePackages
.append(arg
)
894 elif opt
== '--strip':
896 elif opt
== '--destroot':
897 builder
.destroot
= arg
900 usage("Must specify one command ('build', 'report' or 'help')")
903 if command
== "build":
906 elif command
== "report":
909 elif command
== "help":
912 usage("Unknown command '%s'" % command
)
915 def buildapp(**kwargs
):
916 builder
= AppBuilder(**kwargs
)
920 if __name__
== "__main__":