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