diff options
Diffstat (limited to 'Mac/Lib/bundlebuilder.py')
-rwxr-xr-x | Mac/Lib/bundlebuilder.py | 704 |
1 files changed, 0 insertions, 704 deletions
diff --git a/Mac/Lib/bundlebuilder.py b/Mac/Lib/bundlebuilder.py deleted file mode 100755 index d911292..0000000 --- a/Mac/Lib/bundlebuilder.py +++ /dev/null @@ -1,704 +0,0 @@ -#! /usr/bin/env python - -"""\ -bundlebuilder.py -- Tools to assemble MacOS X (application) bundles. - -This module contains two classes to build so called "bundles" for -MacOS X. BundleBuilder is a general tool, AppBuilder is a subclass -specialized in building application bundles. - -[Bundle|App]Builder objects are instantiated with a bunch of keyword -arguments, and have a build() method that will do all the work. See -the class doc strings for a description of the constructor arguments. - -The module contains a main program that can be used in two ways: - - % python bundlebuilder.py [options] build - % python buildapp.py [options] build - -Where "buildapp.py" is a user-supplied setup.py-like script following -this model: - - from bundlebuilder import buildapp - buildapp(<lots-of-keyword-args>) - -""" - - -__all__ = ["BundleBuilder", "BundleBuilderError", "AppBuilder", "buildapp"] - - -import sys -import os, errno, shutil -import imp, marshal -import re -from copy import deepcopy -import getopt -from plistlib import Plist -from types import FunctionType as function - - -class BundleBuilderError(Exception): pass - - -class Defaults: - - """Class attributes that don't start with an underscore and are - not functions or classmethods are (deep)copied to self.__dict__. - This allows for mutable default values. - """ - - def __init__(self, **kwargs): - defaults = self._getDefaults() - defaults.update(kwargs) - self.__dict__.update(defaults) - - def _getDefaults(cls): - defaults = {} - for name, value in cls.__dict__.items(): - if name[0] != "_" and not isinstance(value, - (function, classmethod)): - defaults[name] = deepcopy(value) - for base in cls.__bases__: - if hasattr(base, "_getDefaults"): - defaults.update(base._getDefaults()) - return defaults - _getDefaults = classmethod(_getDefaults) - - -class BundleBuilder(Defaults): - - """BundleBuilder is a barebones class for assembling bundles. It - knows nothing about executables or icons, it only copies files - and creates the PkgInfo and Info.plist files. - """ - - # (Note that Defaults.__init__ (deep)copies these values to - # instance variables. Mutable defaults are therefore safe.) - - # Name of the bundle, with or without extension. - name = None - - # The property list ("plist") - plist = Plist(CFBundleDevelopmentRegion = "English", - CFBundleInfoDictionaryVersion = "6.0") - - # The type of the bundle. - type = "APPL" - # The creator code of the bundle. - creator = None - - # List of files that have to be copied to <bundle>/Contents/Resources. - resources = [] - - # List of (src, dest) tuples; dest should be a path relative to the bundle - # (eg. "Contents/Resources/MyStuff/SomeFile.ext). - files = [] - - # Directory where the bundle will be assembled. - builddir = "build" - - # platform, name of the subfolder of Contents that contains the executable. - platform = "MacOS" - - # Make symlinks instead copying files. This is handy during debugging, but - # makes the bundle non-distributable. - symlink = 0 - - # Verbosity level. - verbosity = 1 - - def setup(self): - # XXX rethink self.name munging, this is brittle. - self.name, ext = os.path.splitext(self.name) - if not ext: - ext = ".bundle" - bundleextension = ext - # misc (derived) attributes - self.bundlepath = pathjoin(self.builddir, self.name + bundleextension) - self.execdir = pathjoin("Contents", self.platform) - - plist = self.plist - plist.CFBundleName = self.name - plist.CFBundlePackageType = self.type - if self.creator is None: - if hasattr(plist, "CFBundleSignature"): - self.creator = plist.CFBundleSignature - else: - self.creator = "????" - plist.CFBundleSignature = self.creator - - def build(self): - """Build the bundle.""" - builddir = self.builddir - if builddir and not os.path.exists(builddir): - os.mkdir(builddir) - self.message("Building %s" % repr(self.bundlepath), 1) - if os.path.exists(self.bundlepath): - shutil.rmtree(self.bundlepath) - os.mkdir(self.bundlepath) - self.preProcess() - self._copyFiles() - self._addMetaFiles() - self.postProcess() - self.message("Done.", 1) - - def preProcess(self): - """Hook for subclasses.""" - pass - def postProcess(self): - """Hook for subclasses.""" - pass - - def _addMetaFiles(self): - contents = pathjoin(self.bundlepath, "Contents") - makedirs(contents) - # - # Write Contents/PkgInfo - assert len(self.type) == len(self.creator) == 4, \ - "type and creator must be 4-byte strings." - pkginfo = pathjoin(contents, "PkgInfo") - f = open(pkginfo, "wb") - f.write(self.type + self.creator) - f.close() - # - # Write Contents/Info.plist - infoplist = pathjoin(contents, "Info.plist") - self.plist.write(infoplist) - - def _copyFiles(self): - files = self.files[:] - for path in self.resources: - files.append((path, pathjoin("Contents", "Resources", - os.path.basename(path)))) - if self.symlink: - self.message("Making symbolic links", 1) - msg = "Making symlink from" - else: - self.message("Copying files", 1) - msg = "Copying" - files.sort() - for src, dst in files: - if os.path.isdir(src): - self.message("%s %s/ to %s/" % (msg, src, dst), 2) - else: - self.message("%s %s to %s" % (msg, src, dst), 2) - dst = pathjoin(self.bundlepath, dst) - if self.symlink: - symlink(src, dst, mkdirs=1) - else: - copy(src, dst, mkdirs=1) - - def message(self, msg, level=0): - if level <= self.verbosity: - indent = "" - if level > 1: - indent = (level - 1) * " " - sys.stderr.write(indent + msg + "\n") - - def report(self): - # XXX something decent - pass - - -if __debug__: - PYC_EXT = ".pyc" -else: - PYC_EXT = ".pyo" - -MAGIC = imp.get_magic() -USE_FROZEN = hasattr(imp, "set_frozenmodules") - -# For standalone apps, we have our own minimal site.py. We don't need -# all the cruft of the real site.py. -SITE_PY = """\ -import sys -del sys.path[1:] # sys.path[0] is Contents/Resources/ -""" - -if USE_FROZEN: - FROZEN_ARCHIVE = "FrozenModules.marshal" - SITE_PY += """\ -# bootstrapping -import imp, marshal -f = open(sys.path[0] + "/%s", "rb") -imp.set_frozenmodules(marshal.load(f)) -f.close() -""" % FROZEN_ARCHIVE - -SITE_CO = compile(SITE_PY, "<-bundlebuilder.py->", "exec") - -EXT_LOADER = """\ -import imp, sys, os -for p in sys.path: - path = os.path.join(p, "%(filename)s") - if os.path.exists(path): - break -else: - assert 0, "file not found: %(filename)s" -mod = imp.load_dynamic("%(name)s", path) -sys.modules["%(name)s"] = mod -""" - -MAYMISS_MODULES = ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath', - 'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize', - 'org.python.core', 'riscos', 'riscosenviron', 'riscospath' -] - -STRIP_EXEC = "/usr/bin/strip" - -BOOTSTRAP_SCRIPT = """\ -#!/bin/sh - -execdir=$(dirname ${0}) -executable=${execdir}/%(executable)s -resdir=$(dirname ${execdir})/Resources -main=${resdir}/%(mainprogram)s -PYTHONPATH=$resdir -export PYTHONPATH -exec ${executable} ${main} ${1} -""" - - -class AppBuilder(BundleBuilder): - - # A Python main program. If this argument is given, the main - # executable in the bundle will be a small wrapper that invokes - # the main program. (XXX Discuss why.) - mainprogram = None - - # The main executable. If a Python main program is specified - # the executable will be copied to Resources and be invoked - # by the wrapper program mentioned above. Otherwise it will - # simply be used as the main executable. - executable = None - - # The name of the main nib, for Cocoa apps. *Must* be specified - # when building a Cocoa app. - nibname = None - - # Symlink the executable instead of copying it. - symlink_exec = 0 - - # If True, build standalone app. - standalone = 0 - - # The following attributes are only used when building a standalone app. - - # Exclude these modules. - excludeModules = [] - - # Include these modules. - includeModules = [] - - # Include these packages. - includePackages = [] - - # Strip binaries. - strip = 0 - - # Found Python modules: [(name, codeobject, ispkg), ...] - pymodules = [] - - # Modules that modulefinder couldn't find: - missingModules = [] - maybeMissingModules = [] - - # List of all binaries (executables or shared libs), for stripping purposes - binaries = [] - - def setup(self): - if self.standalone and self.mainprogram is None: - raise BundleBuilderError, ("must specify 'mainprogram' when " - "building a standalone application.") - if self.mainprogram is None and self.executable is None: - raise BundleBuilderError, ("must specify either or both of " - "'executable' and 'mainprogram'") - - if self.name is not None: - pass - elif self.mainprogram is not None: - self.name = os.path.splitext(os.path.basename(self.mainprogram))[0] - elif executable is not None: - self.name = os.path.splitext(os.path.basename(self.executable))[0] - if self.name[-4:] != ".app": - self.name += ".app" - - if self.executable is None: - if not self.standalone: - self.symlink_exec = 1 - self.executable = sys.executable - - if self.nibname: - self.plist.NSMainNibFile = self.nibname - if not hasattr(self.plist, "NSPrincipalClass"): - self.plist.NSPrincipalClass = "NSApplication" - - BundleBuilder.setup(self) - - self.plist.CFBundleExecutable = self.name - - if self.standalone: - self.findDependencies() - - def preProcess(self): - resdir = "Contents/Resources" - if self.executable is not None: - if self.mainprogram is None: - execname = self.name - else: - execname = os.path.basename(self.executable) - execpath = pathjoin(self.execdir, execname) - if not self.symlink_exec: - self.files.append((self.executable, execpath)) - self.binaries.append(execpath) - self.execpath = execpath - - if self.mainprogram is not None: - mainprogram = os.path.basename(self.mainprogram) - self.files.append((self.mainprogram, pathjoin(resdir, mainprogram))) - # Write bootstrap script - executable = os.path.basename(self.executable) - execdir = pathjoin(self.bundlepath, self.execdir) - bootstrappath = pathjoin(execdir, self.name) - makedirs(execdir) - open(bootstrappath, "w").write(BOOTSTRAP_SCRIPT % locals()) - os.chmod(bootstrappath, 0775) - - def postProcess(self): - if self.standalone: - self.addPythonModules() - if self.strip and not self.symlink: - self.stripBinaries() - - if self.symlink_exec and self.executable: - self.message("Symlinking executable %s to %s" % (self.executable, - self.execpath), 2) - dst = pathjoin(self.bundlepath, self.execpath) - makedirs(os.path.dirname(dst)) - os.symlink(os.path.abspath(self.executable), dst) - - if self.missingModules or self.maybeMissingModules: - self.reportMissing() - - def addPythonModules(self): - self.message("Adding Python modules", 1) - - if USE_FROZEN: - # This anticipates the acceptance of this patch: - # http://www.python.org/sf/642578 - # Create a file containing all modules, frozen. - frozenmodules = [] - for name, code, ispkg in self.pymodules: - if ispkg: - self.message("Adding Python package %s" % name, 2) - else: - self.message("Adding Python module %s" % name, 2) - frozenmodules.append((name, marshal.dumps(code), ispkg)) - frozenmodules = tuple(frozenmodules) - relpath = pathjoin("Contents", "Resources", FROZEN_ARCHIVE) - abspath = pathjoin(self.bundlepath, relpath) - f = open(abspath, "wb") - marshal.dump(frozenmodules, f) - f.close() - # add site.pyc - sitepath = pathjoin(self.bundlepath, "Contents", "Resources", - "site" + PYC_EXT) - writePyc(SITE_CO, sitepath) - else: - # Create individual .pyc files. - for name, code, ispkg in self.pymodules: - if ispkg: - name += ".__init__" - path = name.split(".") - path = pathjoin("Contents", "Resources", *path) + PYC_EXT - - if ispkg: - self.message("Adding Python package %s" % path, 2) - else: - self.message("Adding Python module %s" % path, 2) - - abspath = pathjoin(self.bundlepath, path) - makedirs(os.path.dirname(abspath)) - writePyc(code, abspath) - - def stripBinaries(self): - if not os.path.exists(STRIP_EXEC): - self.message("Error: can't strip binaries: no strip program at " - "%s" % STRIP_EXEC, 0) - else: - self.message("Stripping binaries", 1) - for relpath in self.binaries: - self.message("Stripping %s" % relpath, 2) - abspath = pathjoin(self.bundlepath, relpath) - assert not os.path.islink(abspath) - rv = os.system("%s -S \"%s\"" % (STRIP_EXEC, abspath)) - - def findDependencies(self): - self.message("Finding module dependencies", 1) - import modulefinder - mf = modulefinder.ModuleFinder(excludes=self.excludeModules) - # manually add our own site.py - site = mf.add_module("site") - site.__code__ = SITE_CO - mf.scan_code(SITE_CO, site) - - includeModules = self.includeModules[:] - for name in self.includePackages: - includeModules.extend(findPackageContents(name).keys()) - for name in includeModules: - try: - mf.import_hook(name) - except ImportError: - self.missingModules.append(name) - - mf.run_script(self.mainprogram) - modules = mf.modules.items() - modules.sort() - for name, mod in modules: - if mod.__file__ and mod.__code__ is None: - # C extension - path = mod.__file__ - filename = os.path.basename(path) - if USE_FROZEN: - # "proper" freezing, put extensions in Contents/Resources/, - # freeze a tiny "loader" program. Due to Thomas Heller. - dstpath = pathjoin("Contents", "Resources", filename) - source = EXT_LOADER % {"name": name, "filename": filename} - code = compile(source, "<dynloader for %s>" % name, "exec") - mod.__code__ = code - else: - # just copy the file - dstpath = name.split(".")[:-1] + [filename] - dstpath = pathjoin("Contents", "Resources", *dstpath) - self.files.append((path, dstpath)) - self.binaries.append(dstpath) - if mod.__code__ is not None: - ispkg = mod.__path__ is not None - if not USE_FROZEN or name != "site": - # Our site.py is doing the bootstrapping, so we must - # include a real .pyc file if USE_FROZEN is True. - self.pymodules.append((name, mod.__code__, ispkg)) - - if hasattr(mf, "any_missing_maybe"): - missing, maybe = mf.any_missing_maybe() - else: - missing = mf.any_missing() - maybe = [] - self.missingModules.extend(missing) - self.maybeMissingModules.extend(maybe) - - def reportMissing(self): - missing = [name for name in self.missingModules - if name not in MAYMISS_MODULES] - if self.maybeMissingModules: - maybe = self.maybeMissingModules - else: - maybe = [name for name in missing if "." in name] - missing = [name for name in missing if "." not in name] - missing.sort() - maybe.sort() - if maybe: - self.message("Warning: couldn't find the following submodules:", 1) - self.message(" (Note that these could be false alarms -- " - "it's not always", 1) - self.message(" possible to distinguish between \"from package " - "import submodule\" ", 1) - self.message(" and \"from package import name\")", 1) - for name in maybe: - self.message(" ? " + name, 1) - if missing: - self.message("Warning: couldn't find the following modules:", 1) - for name in missing: - self.message(" ? " + name, 1) - - def report(self): - # XXX something decent - import pprint - pprint.pprint(self.__dict__) - if self.standalone: - self.reportMissing() - -# -# Utilities. -# - -SUFFIXES = [_suf for _suf, _mode, _tp in imp.get_suffixes()] -identifierRE = re.compile(r"[_a-zA-z][_a-zA-Z0-9]*$") - -def findPackageContents(name, searchpath=None): - head = name.split(".")[-1] - if identifierRE.match(head) is None: - return {} - try: - fp, path, (ext, mode, tp) = imp.find_module(head, searchpath) - except ImportError: - return {} - modules = {name: None} - if tp == imp.PKG_DIRECTORY and path: - files = os.listdir(path) - for sub in files: - sub, ext = os.path.splitext(sub) - fullname = name + "." + sub - if sub != "__init__" and fullname not in modules: - modules.update(findPackageContents(fullname, [path])) - return modules - -def writePyc(code, path): - f = open(path, "wb") - f.write("\0" * 8) # don't bother about a time stamp - marshal.dump(code, f) - f.seek(0, 0) - f.write(MAGIC) - f.close() - -def copy(src, dst, mkdirs=0): - """Copy a file or a directory.""" - if mkdirs: - makedirs(os.path.dirname(dst)) - if os.path.isdir(src): - shutil.copytree(src, dst) - else: - shutil.copy2(src, dst) - -def copytodir(src, dstdir): - """Copy a file or a directory to an existing directory.""" - dst = pathjoin(dstdir, os.path.basename(src)) - copy(src, dst) - -def makedirs(dir): - """Make all directories leading up to 'dir' including the leaf - directory. Don't moan if any path element already exists.""" - try: - os.makedirs(dir) - except OSError, why: - if why.errno != errno.EEXIST: - raise - -def symlink(src, dst, mkdirs=0): - """Copy a file or a directory.""" - if mkdirs: - makedirs(os.path.dirname(dst)) - os.symlink(os.path.abspath(src), dst) - -def pathjoin(*args): - """Safe wrapper for os.path.join: asserts that all but the first - argument are relative paths.""" - for seg in args[1:]: - assert seg[0] != "/" - return os.path.join(*args) - - -cmdline_doc = """\ -Usage: - python bundlebuilder.py [options] command - python mybuildscript.py [options] command - -Commands: - build build the application - report print a report - -Options: - -b, --builddir=DIR the build directory; defaults to "build" - -n, --name=NAME application name - -r, --resource=FILE extra file or folder to be copied to Resources - -e, --executable=FILE the executable to be used - -m, --mainprogram=FILE the Python main program - -p, --plist=FILE .plist file (default: generate one) - --nib=NAME main nib name - -c, --creator=CCCC 4-char creator code (default: '????') - -l, --link symlink files/folder instead of copying them - --link-exec symlink the executable instead of copying it - --standalone build a standalone application, which is fully - independent of a Python installation - -x, --exclude=MODULE exclude module (with --standalone) - -i, --include=MODULE include module (with --standalone) - --package=PACKAGE include a whole package (with --standalone) - --strip strip binaries (remove debug info) - -v, --verbose increase verbosity level - -q, --quiet decrease verbosity level - -h, --help print this message -""" - -def usage(msg=None): - if msg: - print msg - print cmdline_doc - sys.exit(1) - -def main(builder=None): - if builder is None: - builder = AppBuilder(verbosity=1) - - shortopts = "b:n:r:e:m:c:p:lx:i:hvq" - longopts = ("builddir=", "name=", "resource=", "executable=", - "mainprogram=", "creator=", "nib=", "plist=", "link", - "link-exec", "help", "verbose", "quiet", "standalone", - "exclude=", "include=", "package=", "strip") - - try: - options, args = getopt.getopt(sys.argv[1:], shortopts, longopts) - except getopt.error: - usage() - - for opt, arg in options: - if opt in ('-b', '--builddir'): - builder.builddir = arg - elif opt in ('-n', '--name'): - builder.name = arg - elif opt in ('-r', '--resource'): - builder.resources.append(arg) - elif opt in ('-e', '--executable'): - builder.executable = arg - elif opt in ('-m', '--mainprogram'): - builder.mainprogram = arg - elif opt in ('-c', '--creator'): - builder.creator = arg - elif opt == "--nib": - builder.nibname = arg - elif opt in ('-p', '--plist'): - builder.plist = Plist.fromFile(arg) - elif opt in ('-l', '--link'): - builder.symlink = 1 - elif opt == '--link-exec': - builder.symlink_exec = 1 - elif opt in ('-h', '--help'): - usage() - elif opt in ('-v', '--verbose'): - builder.verbosity += 1 - elif opt in ('-q', '--quiet'): - builder.verbosity -= 1 - elif opt == '--standalone': - builder.standalone = 1 - elif opt in ('-x', '--exclude'): - builder.excludeModules.append(arg) - elif opt in ('-i', '--include'): - builder.includeModules.append(arg) - elif opt == '--package': - builder.includePackages.append(arg) - elif opt == '--strip': - builder.strip = 1 - - if len(args) != 1: - usage("Must specify one command ('build', 'report' or 'help')") - command = args[0] - - if command == "build": - builder.setup() - builder.build() - elif command == "report": - builder.setup() - builder.report() - elif command == "help": - usage() - else: - usage("Unknown command '%s'" % command) - - -def buildapp(**kwargs): - builder = AppBuilder(**kwargs) - main(builder) - - -if __name__ == "__main__": - main() |