Issue #7632: Fix a serious wrong output bug for string -> float conversion.
[python.git] / Lib / plat-mac / bundlebuilder.py
blob7a710510f33fd8c0fab00cd010210f418e8d97ff
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 from warnings import warnpy3k
32 warnpy3k("In 3.x, the bundlebuilder module is removed.", stacklevel=2)
34 import sys
35 import os, errno, shutil
36 import imp, marshal
37 import re
38 from copy import deepcopy
39 import getopt
40 from plistlib import Plist
41 from types import FunctionType as function
43 class BundleBuilderError(Exception): pass
46 class Defaults:
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.
51 """
53 def __init__(self, **kwargs):
54 defaults = self._getDefaults()
55 defaults.update(kwargs)
56 self.__dict__.update(defaults)
58 def _getDefaults(cls):
59 defaults = {}
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)
67 return defaults
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.
76 """
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.
82 name = None
84 # The property list ("plist")
85 plist = Plist(CFBundleDevelopmentRegion = "English",
86 CFBundleInfoDictionaryVersion = "6.0")
88 # The type of the bundle.
89 type = "BNDL"
90 # The creator code of the bundle.
91 creator = None
93 # the CFBundleIdentifier (this is used for the preferences file name)
94 bundle_id = None
96 # List of files that have to be copied to <bundle>/Contents/Resources.
97 resources = []
99 # List of (src, dest) tuples; dest should be a path relative to the bundle
100 # (eg. "Contents/Resources/MyStuff/SomeFile.ext).
101 files = []
103 # List of shared libraries (dylibs, Frameworks) to bundle with the app
104 # will be placed in Contents/Frameworks
105 libs = []
107 # Directory where the bundle will be assembled.
108 builddir = "build"
110 # Make symlinks instead copying files. This is handy during debugging, but
111 # makes the bundle non-distributable.
112 symlink = 0
114 # Verbosity level.
115 verbosity = 1
117 # Destination root directory
118 destroot = ""
120 def setup(self):
121 # XXX rethink self.name munging, this is brittle.
122 self.name, ext = os.path.splitext(self.name)
123 if not ext:
124 ext = ".bundle"
125 bundleextension = ext
126 # misc (derived) attributes
127 self.bundlepath = pathjoin(self.builddir, self.name + bundleextension)
129 plist = self.plist
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
135 else:
136 self.creator = "????"
137 plist.CFBundleSignature = self.creator
138 if self.bundle_id:
139 plist.CFBundleIdentifier = self.bundle_id
140 elif not hasattr(plist, "CFBundleIdentifier"):
141 plist.CFBundleIdentifier = self.name
143 def build(self):
144 """Build the bundle."""
145 builddir = self.builddir
146 if builddir and not os.path.exists(builddir):
147 os.mkdir(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 + '~')
153 bp = 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 + '~'
160 try:
161 os.mkdir(self.bundlepath)
162 self.preProcess()
163 self._copyFiles()
164 self._addMetaFiles()
165 self.postProcess()
166 os.rename(self.bundlepath, bp)
167 finally:
168 self.bundlepath = bp
169 self.message("Done.", 1)
171 def preProcess(self):
172 """Hook for subclasses."""
173 pass
174 def postProcess(self):
175 """Hook for subclasses."""
176 pass
178 def _addMetaFiles(self):
179 contents = pathjoin(self.bundlepath, "Contents")
180 makedirs(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)
188 f.close()
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))))
202 if self.symlink:
203 self.message("Making symbolic links", 1)
204 msg = "Making symlink from"
205 else:
206 self.message("Copying files", 1)
207 msg = "Copying"
208 files.sort()
209 for src, dst in files:
210 if os.path.isdir(src):
211 self.message("%s %s/ to %s/" % (msg, src, dst), 2)
212 else:
213 self.message("%s %s to %s" % (msg, src, dst), 2)
214 dst = pathjoin(self.bundlepath, dst)
215 if self.symlink:
216 symlink(src, dst, mkdirs=1)
217 else:
218 copy(src, dst, mkdirs=1)
220 def message(self, msg, level=0):
221 if level <= self.verbosity:
222 indent = ""
223 if level > 1:
224 indent = (level - 1) * " "
225 sys.stderr.write(indent + msg + "\n")
227 def report(self):
228 # XXX something decent
229 pass
232 if __debug__:
233 PYC_EXT = ".pyc"
234 else:
235 PYC_EXT = ".pyo"
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.
242 SITE_PY = """\
243 import sys
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):
252 if 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.
261 EXT_LOADER = """\
262 def __load():
263 import imp, sys, os
264 for p in sys.path:
265 path = os.path.join(p, "%(filename)s")
266 if os.path.exists(path):
267 break
268 else:
269 assert 0, "file not found: %(filename)s"
270 mod = imp.load_dynamic("%(name)s", path)
272 __load()
273 del __load
276 MAYMISS_MODULES = ['mac', '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 = """\
295 #!%(hashbang)s
297 import sys, os
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")
304 if %(optimize)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
310 if %(standalone)s:
311 os.environ["PYTHONHOME"] = resdir
312 else:
313 pypath = os.getenv("PYTHONPATH", "")
314 if pypath:
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.
328 ARGV_EMULATOR = """\
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",
346 def isFramework():
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.
359 type = "APPL"
361 # platform, name of the subfolder of Contents that contains the executable.
362 platform = "MacOS"
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.)
367 mainprogram = None
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.
373 executable = None
375 # The name of the main nib, for Cocoa apps. *Must* be specified
376 # when building a Cocoa app.
377 nibname = None
379 # The name of the icon file to be copied to Resources and used for
380 # the Finder icon.
381 iconfile = None
383 # Symlink the executable instead of copying it.
384 symlink_exec = 0
386 # If True, build standalone app.
387 standalone = 0
389 # If True, build semi-standalone app (only includes third-party modules).
390 semi_standalone = 0
392 # If set, use this for #! lines in stead of sys.executable
393 python = None
395 # If True, add a real main program that emulates sys.argv before calling
396 # mainprogram
397 argv_emulation = 0
399 # The following attributes are only used when building a standalone app.
401 # Exclude these modules.
402 excludeModules = []
404 # Include these modules.
405 includeModules = []
407 # Include these packages.
408 includePackages = []
410 # Strip binaries from debug info.
411 strip = 0
413 # Found Python modules: [(name, codeobject, ispkg), ...]
414 pymodules = []
416 # Modules that modulefinder couldn't find:
417 missingModules = []
418 maybeMissingModules = []
420 def setup(self):
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:
432 pass
433 elif self.mainprogram is not None:
434 self.name = os.path.splitext(os.path.basename(self.mainprogram))[0]
435 elif executable is not None:
436 self.name = os.path.splitext(os.path.basename(self.executable))[0]
437 if self.name[-4:] != ".app":
438 self.name += ".app"
440 if self.executable is None:
441 if not self.standalone and not isFramework():
442 self.symlink_exec = 1
443 if self.python:
444 self.executable = self.python
445 else:
446 self.executable = sys.executable
448 if self.nibname:
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:
467 execname = self.name
468 else:
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)
487 makedirs(resdirpath)
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 not self.plist.has_key("CFBundleDocumentTypes"):
493 self.plist["CFBundleDocumentTypes"] = [
494 { "CFBundleTypeOSTypes" : [
495 "****",
496 "fold",
497 "disk"],
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)
503 makedirs(execdir)
504 if self.standalone or self.semi_standalone:
505 # XXX we're screwed when the end user has deleted
506 # /usr/bin/python
507 hashbang = "/usr/bin/python"
508 elif self.python:
509 hashbang = self.python
510 else:
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:
527 self.stripBinaries()
529 if self.symlink_exec and self.executable:
530 self.message("Symlinking executable %s to %s" % (self.executable,
531 self.execpath), 2)
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:
537 self.reportMissing()
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
546 return
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",
554 "Versions", version)
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.
570 import zipfile
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)
578 zf.close()
579 # add site.pyc
580 sitepath = pathjoin(self.bundlepath, "Contents", "Resources",
581 "site" + PYC_EXT)
582 writePyc(self._getSiteCode(), sitepath)
583 else:
584 # Create individual .pyc files.
585 for name, code, ispkg in self.pymodules:
586 if ispkg:
587 name += ".__init__"
588 path = name.split(".")
589 path = pathjoin("Contents", "Resources", *path) + PYC_EXT
591 if ispkg:
592 self.message("Adding Python package %s" % path, 2)
593 else:
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)
604 else:
605 import stat
606 self.message("Stripping binaries", 1)
607 def walk(top):
608 for name in os.listdir(top):
609 path = pathjoin(top, name)
610 if os.path.islink(path):
611 continue
612 if os.path.isdir(path):
613 walk(path)
614 else:
615 mod = os.stat(path)[stat.ST_MODE]
616 if not (mod & 0100):
617 continue
618 relpath = path[len(self.bundlepath):]
619 self.message("Stripping %s" % relpath, 2)
620 inf, outf = os.popen4("%s -S \"%s\"" %
621 (STRIP_EXEC, path))
622 output = outf.read().strip()
623 if output:
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)
632 import modulefinder
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:
649 try:
650 mf.import_hook(name)
651 except ImportError:
652 self.missingModules.append(name)
654 mf.run_script(self.mainprogram)
655 modules = mf.modules.items()
656 modules.sort()
657 for name, mod in modules:
658 path = mod.__file__
659 if path and self.semi_standalone:
660 # skip the standard library
661 if path.startswith(LIB) and not path.startswith(SITE_PACKAGES):
662 continue
663 if path and mod.__code__ is None:
664 # C extension
665 filename = os.path.basename(path)
666 pathitems = name.split(".")[:-1] + [filename]
667 dstpath = pathjoin(*pathitems)
668 if self.use_zipimport:
669 if name != "zlib":
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")
678 mod.__code__ = code
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()
689 else:
690 missing = mf.any_missing()
691 maybe = []
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
700 else:
701 maybe = [name for name in missing if "." in name]
702 missing = [name for name in missing if "." not in name]
703 missing.sort()
704 maybe.sort()
705 if maybe:
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)
712 for name in maybe:
713 self.message(" ? " + name, 1)
714 if missing:
715 self.message("Warning: couldn't find the following modules:", 1)
716 for name in missing:
717 self.message(" ? " + name, 1)
719 def report(self):
720 # XXX something decent
721 import pprint
722 pprint.pprint(self.__dict__)
723 if self.standalone or self.semi_standalone:
724 self.reportMissing()
727 # Utilities.
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:
736 return {}
737 try:
738 fp, path, (ext, mode, tp) = imp.find_module(head, searchpath)
739 except ImportError:
740 return {}
741 modules = {name: None}
742 if tp == imp.PKG_DIRECTORY and path:
743 files = os.listdir(path)
744 for sub in files:
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]))
749 return modules
751 def writePyc(code, path):
752 f = open(path, "wb")
753 f.write(MAGIC)
754 f.write("\0" * 4) # don't bother about a time stamp
755 marshal.dump(code, f)
756 f.close()
758 def copy(src, dst, mkdirs=0):
759 """Copy a file or a directory."""
760 if mkdirs:
761 makedirs(os.path.dirname(dst))
762 if os.path.isdir(src):
763 shutil.copytree(src, dst, symlinks=1)
764 else:
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))
770 copy(src, dst)
772 def makedirs(dir):
773 """Make all directories leading up to 'dir' including the leaf
774 directory. Don't moan if any path element already exists."""
775 try:
776 os.makedirs(dir)
777 except OSError, why:
778 if why.errno != errno.EEXIST:
779 raise
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
785 if mkdirs:
786 makedirs(os.path.dirname(dst))
787 os.symlink(os.path.abspath(src), dst)
789 def pathjoin(*args):
790 """Safe wrapper for os.path.join: asserts that all but the first
791 argument are relative paths."""
792 for seg in args[1:]:
793 assert seg[0] != "/"
794 return os.path.join(*args)
797 cmdline_doc = """\
798 Usage:
799 python bundlebuilder.py [options] command
800 python mybuildscript.py [options] command
802 Commands:
803 build build the application
804 report print a report
806 Options:
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
819 as the Finder icon
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
829 modules.
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
833 the bundle
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
843 def usage(msg=None):
844 if msg:
845 print msg
846 print cmdline_doc
847 sys.exit(1)
849 def main(builder=None):
850 if builder is 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="
859 "no-zipimport"
862 try:
863 options, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
864 except getopt.error:
865 usage()
867 for opt, arg in options:
868 if opt in ('-b', '--builddir'):
869 builder.builddir = arg
870 elif opt in ('-n', '--name'):
871 builder.name = arg
872 elif opt in ('-r', '--resource'):
873 builder.resources.append(os.path.normpath(arg))
874 elif opt in ('-f', '--file'):
875 srcdst = arg.split(':')
876 if len(srcdst) != 2:
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
892 elif opt == "--lib":
893 builder.libs.append(os.path.normpath(arg))
894 elif opt == "--nib":
895 builder.nibname = arg
896 elif opt in ('-p', '--plist'):
897 builder.plist = Plist.fromFile(arg)
898 elif opt in ('-l', '--link'):
899 builder.symlink = 1
900 elif opt == '--link-exec':
901 builder.symlink_exec = 1
902 elif opt in ('-h', '--help'):
903 usage()
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':
913 builder.python = arg
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':
921 builder.strip = 1
922 elif opt == '--destroot':
923 builder.destroot = arg
924 elif opt == '--no-zipimport':
925 builder.use_zipimport = False
927 if len(args) != 1:
928 usage("Must specify one command ('build', 'report' or 'help')")
929 command = args[0]
931 if command == "build":
932 builder.setup()
933 builder.build()
934 elif command == "report":
935 builder.setup()
936 builder.report()
937 elif command == "help":
938 usage()
939 else:
940 usage("Unknown command '%s'" % command)
943 def buildapp(**kwargs):
944 builder = AppBuilder(**kwargs)
945 main(builder)
948 if __name__ == "__main__":
949 main()