⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 bundlebuilder.py

📁 Wxpython Implemented on Windows CE, Source code
💻 PY
📖 第 1 页 / 共 3 页
字号:
#! /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 base in cls.__bases__:
            if hasattr(base, "_getDefaults"):
                defaults.update(base._getDefaults())
        for name, value in cls.__dict__.items():
            if name[0] != "_" and not isinstance(value,
                    (function, classmethod)):
                defaults[name] = deepcopy(value)
        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 = "BNDL"
    # The creator code of the bundle.
    creator = None

    # the CFBundleIdentifier (this is used for the preferences file name)
    bundle_id = 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 = []

    # List of shared libraries (dylibs, Frameworks) to bundle with the app
    # will be placed in Contents/Frameworks
    libs = []

    # Directory where the bundle will be assembled.
    builddir = "build"

    # 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)

        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
        if self.bundle_id:
            plist.CFBundleIdentifier = self.bundle_id
        elif not hasattr(plist, "CFBundleIdentifier"):
            plist.CFBundleIdentifier = self.name

    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))))
        for path in self.libs:
            files.append((path, pathjoin("Contents", "Frameworks",
                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_ZIPIMPORT = "zipimport" in sys.builtin_module_names

# 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
if not %(semi_standalone)s:
    del sys.path[1:]  # sys.path[0] is Contents/Resources/
"""

if USE_ZIPIMPORT:
    ZIP_ARCHIVE = "Modules.zip"
    SITE_PY += "sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
    def getPycData(fullname, code, ispkg):
        if ispkg:
            fullname += ".__init__"
        path = fullname.replace(".", os.sep) + PYC_EXT
        return path, MAGIC + '\0\0\0\0' + marshal.dumps(code)

#
# Extension modules can't be in the modules zip archive, so a placeholder
# is added instead, that loads the extension from a specified location.
#
EXT_LOADER = """\
def __load():
    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)

__load()
del __load
"""

MAYMISS_MODULES = ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath',
    'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize',
    'org.python.core', 'riscos', 'riscosenviron', 'riscospath'
]

STRIP_EXEC = "/usr/bin/strip"

#
# We're using a stock interpreter to run the app, yet we need
# a way to pass the Python main program to the interpreter. The
# bootstrapping script fires up the interpreter with the right
# arguments. os.execve() is used as OSX doesn't like us to
# start a real new process. Also, the executable name must match
# the CFBundleExecutable value in the Info.plist, so we lie
# deliberately with argv[0]. The actual Python executable is
# passed in an environment variable so we can "repair"
# sys.executable later.
#
BOOTSTRAP_SCRIPT = """\
#!%(hashbang)s

import sys, os
execdir = os.path.dirname(sys.argv[0])
executable = os.path.join(execdir, "%(executable)s")
resdir = os.path.join(os.path.dirname(execdir), "Resources")
libdir = os.path.join(os.path.dirname(execdir), "Frameworks")
mainprogram = os.path.join(resdir, "%(mainprogram)s")

sys.argv.insert(1, mainprogram)
if %(standalone)s or %(semi_standalone)s:
    os.environ["PYTHONPATH"] = resdir
    if %(standalone)s:
        os.environ["PYTHONHOME"] = resdir
else:
    pypath = os.getenv("PYTHONPATH", "")
    if pypath:
        pypath = ":" + pypath
    os.environ["PYTHONPATH"] = resdir + pypath
os.environ["PYTHONEXECUTABLE"] = executable
os.environ["DYLD_LIBRARY_PATH"] = libdir
os.environ["DYLD_FRAMEWORK_PATH"] = libdir
os.execve(executable, sys.argv, os.environ)
"""


#
# Optional wrapper that converts "dropped files" into sys.argv values.
#
ARGV_EMULATOR = """\
import argvemulator, os

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -