build_app.py :  » Development » PyObjC » trunk » pyobjc » py2app » py2app » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » PyObjC 
PyObjC » trunk » pyobjc » py2app » py2app » build_app.py
"""
Mac OS X .app build command for distutils

Originally (loosely) based on code from py2exe's build_exe.py by Thomas Heller.
"""
from pkg_resources import require
require("altgraph", "modulegraph", "macholib")

import imp
import sys
import os
import zipfile
import plistlib
import shlex
import shutil
from cStringIO import StringIO

from setuptools import Command
from distutils.util import convert_path
from distutils import log
from distutils.errors import *

from altgraph.compat import *

from modulegraph.find_modules import find_modules,parse_mf_results
from modulegraph.modulegraph import SourceModule,Package,os_listdir

import macholib.dyld
import macholib.MachOStandalone

from py2app.create_appbundle import create_appbundle
from py2app.create_pluginbundle import create_pluginbundle
from py2app.util import \
    fancy_split, byte_compile, make_loader, imp_find_module, \
    copy_tree, fsencoding, strip_files, in_system_path, makedirs, \
    iter_platform_files, find_version, skipscm, momc, copy_file, \
    os_path_isdir, copy_resource
from py2app.filters import \
    not_stdlib_filter, not_system_filter, has_filename_filter
from py2app import recipes

from distutils.sysconfig import get_config_var
PYTHONFRAMEWORK=get_config_var('PYTHONFRAMEWORK')


def get_zipfile(dist):
    return getattr(dist, "zipfile", None) or "site-packages.zip"

def framework_copy_condition(src):
    # Skip Headers, .svn, and CVS dirs
    return skipscm(src) and os.path.basename(src) != 'Headers'

class PythonStandalone(macholib.MachOStandalone.MachOStandalone):
    def __init__(self, appbuilder, *args, **kwargs):
        super(PythonStandalone, self).__init__(*args, **kwargs)
        self.appbuilder = appbuilder

    def copy_dylib(self, src):
        dest = os.path.join(self.dest, os.path.basename(src))
        return self.appbuilder.copy_dylib(src, dest)

    def copy_framework(self, info):
        destfn = self.appbuilder.copy_framework(info, self.dest)
        dest = os.path.join(self.dest, info['shortname'] + '.framework')
        self.pending.append((destfn, iter_platform_files(dest)))
        return destfn

def iterRecipes(module=recipes):
    for name in dir(module):
        if name.startswith('_'):
            continue
        check = getattr(getattr(module, name), 'check', None)
        if check is not None:
            yield (name, check)

# A very loosely defined "target".  We assume either a "script" or "modules"
# attribute.  Some attributes will be target specific.
class Target(object):
    def __init__(self, **kw):
        self.__dict__.update(kw)
        # If modules is a simple string, assume they meant list
        m = self.__dict__.get("modules")
        if m and isinstance(m, basestring):
            self.modules = [m]

    def get_dest_base(self):
        dest_base = getattr(self, "dest_base", None)
        if dest_base: return dest_base
        script = getattr(self, "script", None)
        if script:
            return os.path.basename(os.path.splitext(script)[0])
        modules = getattr(self, "modules", None)
        assert modules, "no script, modules or dest_base specified"
        return modules[0].split(".")[-1]

    def validate(self):
        resources = getattr(self, "resources", [])
        for r_filename in resources:
            if not os.path.isfile(r_filename):
                raise DistutilsOptionError(
                    "Resource filename '%s' does not exist" % (r_filename,))


def validate_target(dist, attr, value):
    res = FixupTargets(value, "script")
    other = {"app": "plugin", "plugin": "app"}
    if res and getattr(dist, other[attr]):
        # XXX - support apps and plugins?
        raise DistutilsOptionError(
            "You must specify either app or plugin, not both")

def FixupTargets(targets, default_attribute):
    if not targets:
        return targets
    try:
        targets = eval(targets)
    except:
        pass
    ret = []
    for target_def in targets:
        if isinstance(target_def, basestring):
            # Create a default target object, with the string as the attribute
            target = Target(**{default_attribute: target_def})
        else:
            d = getattr(target_def, "__dict__", target_def)
            if default_attribute not in d:
                raise DistutilsOptionError(
                    "This target class requires an attribute '%s'"
                    % (default_attribute,))
            target = Target(**d)
        target.validate()
        ret.append(target)
    return ret

def normalize_data_file(fn):
    if isinstance(fn, basestring):
        fn = convert_path(fn)
        return ('', [fn])
    return fn

def is_system(executable=None):
    if executable is None:
        executable = sys.executable
    return in_system_path(executable)

def installation_info(executable=None, version=None):
    if version is None:
        version = sys.version
    if is_system(executable):
        return version[:3] + " (FORCED: Using vendor Python)"
    else:
        return version[:3]

class py2app(Command):
    description = "create a Mac OS X application or plugin from Python scripts"
    # List of option tuples: long name, short name (None if no short
    # name), and help string.

    user_options = [
        ("app=", None,
         "application bundle to be built"),
        ("plugin=", None,
         "puglin bundle to be built"),
        ('optimize=', 'O',
         "optimization level: -O1 for \"python -O\", "
         "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
        ("includes=", 'i',
         "comma-separated list of modules to include"),
        ("packages=", 'p',
         "comma-separated list of packages to include"),
        ("iconfile=", None,
         "Icon file to use"),
        ("excludes=", 'e',
         "comma-separated list of modules to exclude"),
        ("dylib-excludes=", 'E',
         "comma-separated list of frameworks or dylibs to exclude"),
        ("datamodels=", None,
         "xcdatamodels to be compiled and copied into Resources"),
        ("mappingmodels=", None,
          "xcmappingmodels to be compiled and copied into Resources"),
        ("resources=", 'r',
         "comma-separated list of additional data files and folders to include (not for code!)"),
        ("frameworks=", 'f',
         "comma-separated list of additional frameworks and dylibs to include"),
        ("plist=", 'P',
         "Info.plist template file, dict, or plistlib.Plist"),
        ("extension=", None,
         "Bundle extension [default:.app for app, .plugin for plugin]"),
        ("graph", 'g',
         "output module dependency graph"),
        ("xref", 'x',
         "output module cross-reference as html"),
        ("no-strip", None,
         "do not strip debug and local symbols from output"),
        #("compressed", 'c',
        # "create a compressed zipfile"),
        ("no-chdir", 'C',
         "do not change to the data directory (Contents/Resources) [forced for plugins]"),
        #("no-zip", 'Z',
        # "do not use a zip file (XXX)"),
        ("semi-standalone", 's',
         "depend on an existing installation of Python " + installation_info()),
        ("alias", 'A',
         "Use an alias to current source file (for development only!)"),
        ("argv-emulation", 'a',
         "Use argv emulation [disabled for plugins]"),
        ("argv-inject=", None,
         "Inject some commands into the argv"),
        ("use-pythonpath", None,
         "Allow PYTHONPATH to effect the interpreter's environment"),
        ('bdist-base=', 'b',
         'base directory for build library (default is build)'),
        ('dist-dir=', 'd',
         "directory to put final built distributions in (default is dist)"),
        ('site-packages', None,
         "include the system and user site-packages into sys.path"),
        ("strip", 'S',
         "strip debug and local symbols from output (on by default, for compatibility)"),
        ("prefer-ppc", None,
         "Force application to run translated on i386 (LSPrefersPPC=True)"),
        ('debug-modulegraph', None,
         'Drop to pdb console after the module finding phase is complete'),
        ("debug-skip-macholib", None,
         "skip macholib phase (app will not be standalone!)"),
        ]

    boolean_options = [
        #"compressed",
        "xref",
        "strip",
        "no-strip",
        "site-packages",
        "semi-standalone",
        "alias",
        "argv-emulation",
        #"no-zip",
        "use-pythonpath",
        "no-chdir",
        "debug-modulegraph",
        "debug-skip-macholib",
        "graph",
        "prefer-ppc",
    ]

    def initialize_options (self):
        self.app = None
        self.plugin = None
        self.bdist_base = None
        self.xref = False
        self.graph = False
        self.no_zip = 0
        self.optimize = 0
        self.strip = True
        self.no_strip = False
        self.iconfile = None
        self.extension = None
        self.alias = 0
        self.argv_emulation = 0
        self.argv_inject = None
        self.no_chdir = 0
        self.site_packages = False
        self.use_pythonpath = False
        self.includes = None
        self.packages = None
        self.excludes = None
        self.dylib_excludes = None
        self.frameworks = None
        self.resources = None
        self.datamodels = None
        self.mappingmodels = None
        self.plist = None
        self.compressed = True
        self.semi_standalone = is_system()
        self.dist_dir = None
        self.debug_skip_macholib = False
        self.debug_modulegraph = False
        self.prefer_ppc = False
        self.filters = []
        self.eggs = []

    def finalize_options (self):
        if not self.strip:
            self.no_strip = True
        elif self.no_strip:
            self.strip = False
        self.optimize = int(self.optimize)
        if self.argv_inject and isinstance(self.argv_inject, basestring):
            self.argv_inject = shlex.split(self.argv_inject)
        self.includes = set(fancy_split(self.includes))
        self.includes.add('encodings.*')
        self.packages = set(fancy_split(self.packages))
        self.excludes = set(fancy_split(self.excludes))
        self.excludes.add('readline')
        # included by apptemplate
        self.excludes.add('site')
        if getattr(self.distribution, 'install_requires', None):
            self.includes.add('pkg_resources')
            self.eggs = require(self.distribution.install_requires)
        dylib_excludes = fancy_split(self.dylib_excludes)
        self.dylib_excludes = []
        for fn in dylib_excludes:
            try:
                res = macholib.dyld.framework_find(fn)
            except ValueError:
                try:
                    res = macholib.dyld.dyld_find(fn)
                except ValueError:
                    res = fn
            self.dylib_excludes.append(res)
        self.resources = fancy_split(self.resources)
        frameworks = fancy_split(self.frameworks)
        self.frameworks = []
        for fn in frameworks:
            try:
                res = macholib.dyld.framework_find(fn)
            except ValueError:
                res = macholib.dyld.dyld_find(fn)
            while res in self.dylib_excludes:
                self.dylib_excludes.remove(res)
            self.frameworks.append(res)
        if not self.plist:
            self.plist = {}
        if isinstance(self.plist, basestring):
            self.plist = plistlib.Plist.fromFile(self.plist)
        if isinstance(self.plist, plistlib.Dict):
            self.plist = dict(self.plist.__dict__)
        else:
            self.plist = dict(self.plist)

        self.set_undefined_options('bdist',
                                   ('dist_dir', 'dist_dir'),
                                   ('bdist_base', 'bdist_base'))

        if self.semi_standalone:
            self.filters.append(not_stdlib_filter)

        if self.iconfile is None and u'CFBundleIconFile' not in self.plist:
            # Default is the generic applet icon in the framework
            iconfile = os.path.join(sys.prefix, 'Resources', 'Python.app',
                'Contents', 'Resources', 'PythonApplet.icns')
            if os.path.exists(iconfile):
                self.iconfile = iconfile

        self.runtime_preferences = list(self.get_runtime_preferences())


        if self.datamodels:
            print "WARNING: the datamodels option is deprecated, add model files to the list of resources"

        if self.mappingmodels:
            print "WARNING: the mappingmodels option is deprecated, add model files to the list of resources"


    def get_default_plist(self):
        # XXX - this is all single target stuff
        plist = {}
        target = self.targets[0]

        version = self.distribution.get_version()
        if version == '0.0.0':
            try:
                version = find_version(target.script)
            except ValueError:
                pass
        plist['CFBundleVersion'] = version

        name = self.distribution.get_name()
        if name == 'UNKNOWN':
            base = target.get_dest_base()
            name = os.path.basename(base)
        plist['CFBundleName'] = name

        return plist

    def get_runtime(self, prefix=None, version=None):
        # XXX - this is a bit of a hack!
        #       ideally we'd use dylib functions to figure this out
        if prefix is None:
            prefix = sys.prefix
        if version is None:
            version = sys.version
        version = version[:3]
        info = None
        try:
            fmwk = macholib.dyld.framework_find(prefix)
        except ValueError:
            info = None
        else:
            info = macholib.dyld.framework_info(fmwk)
        if info is not None:
            dylib = info['name']
            runtime = os.path.join(info['location'], info['name'])
        else:
            dylib = 'libpython%s.dylib' % (sys.version[:3],)
            runtime = os.path.join(prefix, 'lib', dylib)
        return dylib, runtime

    def symlink(self, src, dst):
        try:
            os.remove(dst)
        except OSError:
            pass
        os.symlink(src, dst)

    def get_runtime_preferences(self, prefix=None, version=None):
        dylib, runtime = self.get_runtime(prefix=prefix, version=version)
        yield os.path.join('@executable_path', '..', 'Frameworks', dylib)
        if self.semi_standalone or self.alias:
            yield runtime

    def run(self):
        build = self.reinitialize_command('build')
        build.build_base = self.bdist_base
        build.run()
        self.create_directories()
        self.fixup_distribution()
        self.initialize_plist()

        sys_old_path = sys.path[:]
        extra_paths = [
            os.path.dirname(target.script)
            for target in self.targets
        ]
        extra_paths.extend([build.build_platlib, build.build_lib])
        self.additional_paths = [
            os.path.abspath(p)
            for p in extra_paths
            if p is not None
        ]
        sys.path[:0] = self.additional_paths

        # this needs additional_paths
        self.initialize_prescripts()

        try:
            self._run()
        finally:
            sys.path = sys_old_path


    def iter_datamodels(self, resdir):
        for (path, files) in imap(normalize_data_file, self.datamodels or ()):
            path = fsencoding(path)
            for fn in files:
                fn = fsencoding(fn)
                basefn, ext = os.path.splitext(fn)
                if ext != '.xcdatamodel':
                    basefn = fn
                    fn += '.xcdatamodel'
                destfn = os.path.basename(basefn) + '.mom'
                yield fn, os.path.join(resdir, path, destfn)

    def compile_datamodels(self, resdir):
        for src, dest in self.iter_datamodels(resdir):
            print "compile datamodel", src, "->", dest
            self.mkpath(os.path.dirname(dest))
            momc(src, dest)

    def iter_mappingmodels(self, resdir):
        for (path, files) in imap(normalize_data_file, self.mappingmodels or ()):
            path = fsencoding(path)
            for fn in files:
                fn = fsencoding(fn)
                basefn, ext = os.path.splitext(fn)
                if ext != '.xcmappingmodel':
                    basefn = fn
                    fn += '.xcmappingmodel'
                destfn = os.path.basename(basefn) + '.cdm'
                yield fn, os.path.join(resdir, path, destfn)

    def compile_mappingmodels(self, resdir):
        for src, dest in self.iter_mappingmodels(resdir):
            self.mkpath(os.path.dirname(dest))
            mapc(src, dest)
        
    def iter_data_files(self):
        dist = self.distribution
        allres = chain(getattr(dist, 'data_files', ()) or (), self.resources)
        for (path, files) in imap(normalize_data_file, allres):
            path = fsencoding(path)
            for fn in files:
                fn = fsencoding(fn)
                yield fn, os.path.join(path, os.path.basename(fn))

    def collect_scripts(self):
        # these contains file names
        scripts = set()

        for target in self.targets:
            scripts.add(target.script)
            scripts.update([
                k for k in target.prescripts if isinstance(k, basestring)
            ])

        return scripts

    def get_plist_options(self):
        return dict(
            PyOptions=dict(
                use_pythonpath=bool(self.use_pythonpath),
                site_packages=bool(self.site_packages),
                alias=bool(self.alias),
                argv_emulation=bool(self.argv_emulation),
                no_chdir=bool(self.no_chdir),
                optimize=self.optimize,
                prefer_ppc=self.prefer_ppc,
            ),
        )

    
    def initialize_plist(self):
        plist = self.get_default_plist()
        for target in self.targets:
            plist.update(getattr(target, 'plist', {}))
        plist.update(self.plist)
        plist.update(self.get_plist_options())

        if self.iconfile:
            iconfile = self.iconfile
            if not os.path.exists(iconfile):
                iconfile = iconfile + '.icns'
            if not os.path.exists(iconfile):
                raise DistutilsOptionError("icon file must exist: %r"
                    % (self.iconfile,))
            self.resources.append(iconfile)
            plist[u'CFBundleIconFile'] = os.path.basename(iconfile)
        if self.prefer_ppc:
            plist[u'LSPrefersPPC'] = True

        self.plist = plist
        return plist

    def run_alias(self):
        self.app_files = []
        for target in self.targets:
            dst = self.build_alias_executable(target, target.script)
            self.app_files.append(dst)

    def collect_recipedict(self):
        return dict(iterRecipes())

    def get_modulefinder(self):
        if self.debug_modulegraph:
            debug = 4
        else:
            debug = 0
        return find_modules(
            scripts=self.collect_scripts(),
            includes=self.includes,
            packages=self.packages,
            excludes=self.excludes,
            debug=debug,
        )

    def collect_filters(self):
        return [has_filename_filter] + list(self.filters)

    def process_recipes(self, mf, filters, flatpackages, loader_files):
        rdict = self.collect_recipedict()
        while True:
            for name, check in rdict.iteritems():
                rval = check(self, mf)
                if rval is None:
                    continue
                # we can pull this off so long as we stop the iter
                del rdict[name]
                print '*** using recipe: %s ***' % (name,)
                self.packages.update(rval.get('packages', ()))
                for pkg in rval.get('flatpackages', ()):
                    if isinstance(pkg, basestring):
                        pkg = (os.path.basename(pkg), pkg)
                    flatpackages[pkg[0]] = pkg[1]
                filters.extend(rval.get('filters', ()))
                loader_files.extend(rval.get('loader_files', ()))
                newbootstraps = map(self.get_bootstrap,
                    rval.get('prescripts', ()))

                for fn in newbootstraps:
                    if isinstance(fn, basestring):
                        mf.run_script(fn)
                for target in self.targets:
                    target.prescripts.extend(newbootstraps)
                break
            else:
                break

    def _run(self):
        try:
            if self.alias:
                self.run_alias()
            else:
                self.run_normal()
        except:
            # XXX - remove when not debugging
            #       distutils sucks
            import pdb, sys, traceback
            traceback.print_exc()
            pdb.post_mortem(sys.exc_info()[2])

    def filter_dependencies(self, mf, filters):
        print "*** filtering dependencies ***"
        nodes_seen, nodes_removed, nodes_orphaned = mf.filterStack(filters)
        print '%d total' % (nodes_seen,)
        print '%d filtered' % (nodes_removed,)
        print '%d orphaned' % (nodes_orphaned,)
        print '%d remaining' % (nodes_seen - nodes_removed,)

    def get_appname(self):
        return self.plist['CFBundleName']

    def build_xref(self, mf, flatpackages):
        for target in self.targets:
            base = target.get_dest_base()
            appdir = os.path.join(self.dist_dir, os.path.dirname(base))
            appname = self.get_appname()
            dgraph = os.path.join(appdir, appname + '.html')
            print ("*** creating dependency html: %s ***"
                % (os.path.basename(dgraph),))
            mf.create_xref(file(dgraph, 'w'))

    def build_graph(self, mf, flatpackages):
        for target in self.targets:
            base = target.get_dest_base()
            appdir = os.path.join(self.dist_dir, os.path.dirname(base))
            appname = self.get_appname()
            dgraph = os.path.join(appdir, appname + '.dot')
            print ("*** creating dependency graph: %s ***"
                % (os.path.basename(dgraph),))
            mf.graphreport(file(dgraph, 'w'), flatpackages=flatpackages)

    def finalize_modulefinder(self, mf):
        py_files, extensions = parse_mf_results(mf)
        py_files = list(py_files)
        extensions = list(extensions)
        return py_files, extensions

    def collect_packagedirs(self):
        return filter(os.path.exists, [
            os.path.join(os.path.realpath(self.get_bootstrap(pkg)), '')
            for pkg in self.packages
        ])

    def run_normal(self):
        mf = self.get_modulefinder()
        filters = self.collect_filters()
        flatpackages = {}
        loader_files = []
        self.process_recipes(mf, filters, flatpackages, loader_files)

        if self.debug_modulegraph:
            import pdb
            pdb.Pdb().set_trace()

        self.filter_dependencies(mf, filters)

        if self.graph:
            self.build_graph(mf, flatpackages)
        if self.xref:
            self.build_xref(mf, flatpackages)

        py_files, extensions = self.finalize_modulefinder(mf)
        pkgdirs = self.collect_packagedirs()
        self.create_binaries(py_files, pkgdirs, extensions, loader_files)

    def create_directories(self):
        bdist_base = self.bdist_base
        if self.semi_standalone:
            self.bdist_dir = os.path.join(bdist_base,
                'python%s-semi_standalone' % (sys.version[:3],), 'app')
        else:
            self.bdist_dir = os.path.join(bdist_base,
                'python%s-standalone' % (sys.version[:3],), 'app')

        self.collect_dir = os.path.abspath(
            os.path.join(self.bdist_dir, "collect"))
        self.mkpath(self.collect_dir)

        self.temp_dir = os.path.abspath(os.path.join(self.bdist_dir, "temp"))
        self.mkpath(self.temp_dir)

        self.dist_dir = os.path.abspath(self.dist_dir)
        self.mkpath(self.dist_dir)

        self.lib_dir = os.path.join(self.bdist_dir,
            os.path.dirname(get_zipfile(self.distribution)))
        self.mkpath(self.lib_dir)

        self.ext_dir = os.path.join(self.lib_dir, 'lib-dynload')
        self.mkpath(self.ext_dir)

        self.framework_dir = os.path.join(self.bdist_dir, 'Frameworks')
        self.mkpath(self.framework_dir)

    def create_binaries(self, py_files, pkgdirs, extensions, loader_files):
        print "*** create binaries ***"
        dist = self.distribution
        pkgexts = []
        copyexts = []
        extmap = {}
        def packagefilter(mod, pkgdirs=pkgdirs):
            fn = os.path.realpath(getattr(mod, 'filename', None))
            if fn is None:
                return None
            for pkgdir in pkgdirs:
                if fn.startswith(pkgdir):
                    return None
            return fn
        if pkgdirs:
            py_files = filter(packagefilter, py_files)
        for ext in extensions:
            fn = packagefilter(ext)
            if fn is None:
                fn = os.path.realpath(getattr(ext, 'filename', None))
                pkgexts.append(ext)
            else:
                py_files.append(self.create_loader(ext))
                copyexts.append(ext)
            extmap[fn] = ext

        # byte compile the python modules into the target directory
        print "*** byte compile python files ***"
        byte_compile(py_files,
                     target_dir=self.collect_dir,
                     optimize=self.optimize,
                     force=self.force,
                     verbose=self.verbose,
                     dry_run=self.dry_run)

        for item in py_files:
            if not isinstance(item, Package): continue
            self.copy_package_data(item, self.collect_dir)

        self.lib_files = []
        self.app_files = []

        # create the shared zipfile containing all Python modules
        archive_name = os.path.join(self.lib_dir,
                                    os.path.basename(get_zipfile(dist)))

        for path, files in loader_files:
            dest = os.path.join(self.collect_dir, path)
            self.mkpath(dest)
            for fn in files:
                destfn = os.path.join(dest, os.path.basename(fn))
                if os.path.isdir(fn):
                    self.copy_tree(fn, destfn, preserve_symlinks=False)
                else:
                    self.copy_file(fn, destfn)

        arcname = self.make_lib_archive(archive_name,
            base_dir=self.collect_dir, verbose=self.verbose,
            dry_run=self.dry_run)
        self.lib_files.append(arcname)

        # build the executables
        for target in self.targets:
            dst = self.build_executable(
                target, arcname, pkgexts, copyexts, target.script)
            exp = os.path.join(dst, 'Contents', 'MacOS')
            execdst = os.path.join(exp, 'python')
            if self.semi_standalone:
                self.symlink(sys.executable, execdst)
            else:
                self.copy_file(sys.executable, execdst)
            if not self.debug_skip_macholib:
                mm = PythonStandalone(self, dst, executable_path=exp)
                dylib, runtime = self.get_runtime()
                if self.semi_standalone:
                    mm.excludes.append(runtime)
                else:
                    mm.mm.run_file(runtime)
                for exclude in self.dylib_excludes:
                    info = macholib.dyld.framework_info(exclude)
                    if info is not None:
                        exclude = os.path.join(
                            info['location'], info['shortname'] + '.framework')
                    mm.excludes.append(exclude)
                for fmwk in self.frameworks:
                    mm.mm.run_file(fmwk)
                platfiles = mm.run()
                if self.strip:
                    platfiles = self.strip_dsym(platfiles)
                    self.strip_files(platfiles)
            self.app_files.append(dst)

    def copy_package_data(self, package, target_dir):
        """
        Copy any package data in a python package into the target_dir.

        This is a bit of a hack, it would be better to identify python eggs
        and copy those in whole.
        """
        exts = [ i[0] for i in imp.get_suffixes() ]
        exts.append('.py')
        exts.append('.pyc')
        exts.append('.pyo')
        def datafilter(item):
            for e in exts:
                if item.endswith(e):
                    return False
            return True

        target_dir = os.path.join(target_dir, *(package.identifier.split('.')))
        for dname in package.packagepath:
            filenames = filter(datafilter, os_listdir(dname))
            for fname in filenames:
                if fname in ('.svn', 'CVS'):
                    # Scrub revision manager junk
                    continue
                pth = os.path.join(dname, fname)

                # Check if we have found a package, exclude those
                if os_path_isdir(pth):
                    for p in os_listdir(pth):
                        if p.startswith('__init__.') and p[8:] in exts:
                            break

                    else:
                        copy_tree(pth, os.path.join(target_dir, fname))
                    continue
                else:
                    copy_file(pth, os.path.join(target_dir, fname))


    def strip_dsym(self, platfiles):
        """ Remove .dSYM directories in the bundled application """

        #
        # .dSYM directories are contain detached debugging information and
        # should be completely removed when the "strip" option is specified.
        #
        if self.dry_run:
            return platfiles
        for dirpath, dnames, fnames in os.walk(self.appdir):
            for nm in list(dnames):
                if nm.endswith('.dSYM'):
                    print "removing debug info: %s/%s"%(dirpath, nm)
                    shutil.rmtree(os.path.join(dirpath, nm))
                    dnames.remove(nm)
        return [file for file in platfiles if '.dSYM' not in file]

    def strip_files(self, files):
        unstripped = 0L
        stripfiles = []
        for fn in files:
            unstripped += os.stat(fn).st_size
            stripfiles.append(fn)
            log.info('stripping %s', os.path.basename(fn))
        strip_files(stripfiles, dry_run=self.dry_run, verbose=self.verbose)
        stripped = 0L
        for fn in stripfiles:
            stripped += os.stat(fn).st_size
        log.info('stripping saved %d bytes (%d / %d)',
            unstripped - stripped, stripped, unstripped)

    def copy_dylib(self, src, dst):
        # will be copied from the framework?
        if src != sys.executable:
            force, self.force = self.force, True
            self.copy_file(src, dst)
            self.force = force
        return dst

    def copy_versioned_framework(self, info, dst):
        # XXX - Boy is this ugly, but it makes sense because the developer
        #       could have both Python 2.3 and 2.4, or Tk 8.4 and 8.5, etc.
        #       Saves a good deal of space, and I'm pretty sure this ugly
        #       hack is correct in the general case.
        version = info['version']
        if version is None:
            return self.raw_copy_framework(info, dst)
        short = info['shortname'] + '.framework'
        infile = os.path.join(info['location'], short)
        outfile = os.path.join(dst, short)
        vsplit = os.path.join(infile, 'Versions').split(os.sep)
        def condition(src, vsplit=vsplit, version=version):
            srcsplit = src.split(os.sep)
            if (
                    len(srcsplit) > len(vsplit) and
                    srcsplit[:len(vsplit)] == vsplit and
                    srcsplit[len(vsplit)] != version and
                    not os.path.islink(src)
                ):
                return False
            # Skip Headers, .svn, and CVS dirs
            return framework_copy_condition(src)

        return self.copy_tree(infile, outfile,
            preserve_symlinks=True, condition=condition)

    def copy_framework(self, info, dst):
        force, self.force = self.force, True
        if info['shortname'] == PYTHONFRAMEWORK:
            self.copy_python_framework(info, dst)
        else:
            self.copy_versioned_framework(info, dst)
        self.force = force
        return os.path.join(dst, info['name'])

    def raw_copy_framework(self, info, dst):
        short = info['shortname'] + '.framework'
        infile = os.path.join(info['location'], short)
        outfile = os.path.join(dst, short)
        return self.copy_tree(infile, outfile,
            preserve_symlinks=True, condition=framework_copy_condition)

    def copy_python_framework(self, info, dst):
        # XXX - In this particular case we know exactly what we can
        #       get away with.. should this be extended to the general
        #       case?  Per-framework recipes?
        indir = os.path.dirname(os.path.join(info['location'], info['name']))
        outdir = os.path.dirname(os.path.join(dst, info['name']))
        self.mkpath(os.path.join(outdir, 'Resources'))
        pydir = 'python%s'%(info['version'])

        # distutils looks for some files relative to sys.executable, which
        # means they have to be in the framework...
        self.mkpath(os.path.join(outdir, 'include'))
        self.mkpath(os.path.join(outdir, 'include', pydir))
        self.mkpath(os.path.join(outdir, 'lib'))
        self.mkpath(os.path.join(outdir, 'lib', pydir))
        self.mkpath(os.path.join(outdir, 'lib', pydir, 'config'))
        fmwkfiles = [
            os.path.basename(info['name']),
            'Resources/Info.plist',
            'include/%s/pyconfig.h'%(pydir,),
            'lib/%s/config/Makefile'%(pydir,)
        ]
        for fn in fmwkfiles:
            self.copy_file(
                os.path.join(indir, fn),
                os.path.join(outdir, fn))



    def fixup_distribution(self):
        dist = self.distribution

        # Trying to obtain app and plugin from dist for backward compatibility
        # reasons.
        app = dist.app
        plugin = dist.plugin
        # If we can get suitable values from self.app and self.plugin, we prefer
        # them.
        if self.app is not None or self.plugin is not None:
            app = self.app
            plugin = self.plugin

        # Convert our args into target objects.
        dist.app = FixupTargets(app, "script")
        dist.plugin = FixupTargets(plugin, "script")
        if dist.app and dist.plugin:
            # XXX - support apps and plugins?
            raise DistutilsOptionError(
                "You must specify either app or plugin, not both")
        elif dist.app:
            self.style = 'app'
            self.targets = dist.app
        elif dist.plugin:
            self.style = 'plugin'
            self.targets = dist.plugin
        else:
            raise DistutilsOptionError(
                "You must specify either app or plugin")
        if len(self.targets) != 1:
            # XXX - support multiple targets?
            raise DistutilsOptionError(
                "Multiple targets not currently supported")
        if not self.extension:
            self.extension = '.' + self.style

        # make sure all targets use the same directory, this is
        # also the directory where the pythonXX.dylib must reside
        paths = set()
        for target in self.targets:
            paths.add(os.path.dirname(target.get_dest_base()))

        if len(paths) > 1:
            raise DistutilsOptionError(
                  "all targets must use the same directory: %s" %
                  ([p for p in paths],))
        if paths:
            app_dir = paths.pop() # the only element
            if os.path.isabs(app_dir):
                raise DistutilsOptionError(
                      "app directory must be relative: %s" % (app_dir,))
            self.app_dir = os.path.join(self.dist_dir, app_dir)
            self.mkpath(self.app_dir)
        else:
            # Do we allow to specify no targets?
            # We can at least build a zipfile...
            self.app_dir = self.lib_dir

    def initialize_prescripts(self):
        prescripts = []
        if self.site_packages or self.alias:
            prescripts.append('site_packages')

        #if self.style == 'app':
        #    prescripts.append('setup_pkgresource')

        if self.argv_emulation and self.style == 'app':
            prescripts.append('argv_emulation')
            if u'CFBundleDocumentTypes' not in self.plist:
                self.plist[u'CFBundleDocumentTypes'] = [
                    {
                        u'CFBundleTypeOSTypes' : [
                            u'****',
                            u'fold',
                            u'disk',
                        ],
                        u'CFBundleTypeRole': u'Viewer'
                    },
                ]

        if self.argv_inject is not None:
            prescripts.append('argv_inject')
            prescripts.append(
                StringIO('_argv_inject(%r)\n' % (self.argv_inject,)))

        if self.style == 'app' and not self.no_chdir:
            prescripts.append('chdir_resource')
        if not self.alias:
            prescripts.append('disable_linecache')
            prescripts.append('boot_' + self.style)
        else:
            if self.additional_paths:
                prescripts.append('path_inject')
                prescripts.append(
                    StringIO('_path_inject(%r)\n' % (self.additional_paths,)))
            prescripts.append('boot_alias' + self.style)
        newprescripts = []
        for s in prescripts:
            if isinstance(s, basestring):
                newprescripts.append(
                    self.get_bootstrap('py2app.bootstrap.' + s))
            else:
                newprescripts.append(s)

        for target in self.targets:
            prescripts = getattr(target, 'prescripts', [])
            target.prescripts = newprescripts + prescripts


    def get_bootstrap(self, bootstrap):
        if isinstance(bootstrap, basestring):
            if not os.path.exists(bootstrap):
                bootstrap = imp_find_module(bootstrap)[1]
        return bootstrap

    def get_bootstrap_data(self, bootstrap):
        bootstrap = self.get_bootstrap(bootstrap)
        if not isinstance(bootstrap, basestring):
            return bootstrap.getvalue()
        else:
            return file(bootstrap, 'rU').read()

    def create_pluginbundle(self, target, script, use_runtime_preference=True):
        base = target.get_dest_base()
        appdir = os.path.join(self.dist_dir, os.path.dirname(base))
        appname = self.get_appname()
        print "*** creating plugin bundle: %s ***" % (appname,)
        if self.runtime_preferences and use_runtime_preference:
            self.plist.setdefault(
                'PyRuntimeLocations', self.runtime_preferences)
        appdir, plist = create_pluginbundle(
            appdir,
            appname,
            plist=self.plist,
            extension=self.extension,
        )
        appdir = fsencoding(appdir)
        resdir = os.path.join(appdir, 'Contents', 'Resources')
        return appdir, resdir, plist

    def create_appbundle(self, target, script, use_runtime_preference=True):
        base = target.get_dest_base()
        appdir = os.path.join(self.dist_dir, os.path.dirname(base))
        appname = self.get_appname()
        print "*** creating application bundle: %s ***" % (appname,)
        if self.runtime_preferences and use_runtime_preference:
            self.plist.setdefault(
                'PyRuntimeLocations', self.runtime_preferences)
        pythonInfo = self.plist.setdefault(u'PythonInfoDict', {})
        py2appInfo = pythonInfo.setdefault(u'py2app', {}).update(dict(
            alias=bool(self.alias),
        ))
        appdir, plist = create_appbundle(
            appdir,
            appname,
            plist=self.plist,
            extension=self.extension,
        )
        appdir = fsencoding(appdir)
        resdir = os.path.join(appdir, 'Contents', 'Resources')
        return appdir, resdir, plist

    def create_bundle(self, target, script, use_runtime_preference=True):
        fn = getattr(self, 'create_%sbundle' % (self.style,))
        return fn(
            target,
            script,
            use_runtime_preference=use_runtime_preference
        )

    def iter_frameworks(self):
        for fn in self.frameworks:
            fmwk = macholib.dyld.framework_info(fn)
            if fmwk is None:
                yield fn
            else:
                basename = fmwk['shortname'] + '.framework'
                yield os.path.join(fmwk['location'], basename)
    
    def build_alias_executable(self, target, script):
        # Build an alias executable for the target
        appdir, resdir, plist = self.create_bundle(target, script)

        # symlink python executable
        execdst = os.path.join(appdir, 'Contents', 'MacOS', 'python')
        prefixPathExecutable = os.path.join(sys.prefix, 'bin', 'python')
        if os.path.exists(prefixPathExecutable):
            pyExecutable = prefixPathExecutable
        else:
            pyExecutable = sys.executable
        self.symlink(pyExecutable, execdst)

        # make PYTHONHOME
        pyhome = os.path.join(resdir, 'lib', 'python' + sys.version[:3])
        realhome = os.path.join(sys.prefix, 'lib', 'python' + sys.version[:3])
        makedirs(pyhome)
        self.symlink('../../site.py', os.path.join(pyhome, 'site.py'))
        self.symlink(
            os.path.join(realhome, 'config'),
            os.path.join(pyhome, 'config'))
            
        
        # symlink data files
        # XXX: fixme: need to integrate automatic data conversion
        for src, dest in self.iter_data_files():
            dest = os.path.join(resdir, dest)
            if src == dest:
                continue
            makedirs(os.path.dirname(dest))
            self.symlink(os.path.abspath(src), dest)

        # symlink frameworks
        for src in self.iter_frameworks():
            dest = os.path.join(
                appdir, 'Contents', 'Frameworks', os.path.basename(src))
            if src == dest:
                continue
            makedirs(os.path.dirname(dest))
            self.symlink(os.path.abspath(src), dest)

        self.compile_datamodels(resdir)
        self.compile_mappingmodels(resdir)

        from Carbon.File import FSRef
        aliasdata = FSRef(script).FSNewAliasMinimal().data

        bootfn = '__boot__'
        bootfile = file(os.path.join(resdir, bootfn + '.py'), 'w')
        for fn in target.prescripts:
            bootfile.write(self.get_bootstrap_data(fn))
            bootfile.write('\n\n')
        bootfile.write('try:\n')
        bootfile.write('    _run((%r, %r))\n' % (
            aliasdata, os.path.realpath(script),
        ))
        bootfile.write('except KeyboardInterrupt:\n')
        bootfile.write('    pass\n')
        bootfile.close()

        target.appdir = appdir
        return appdir

    def build_executable(self, target, arcname, pkgexts, copyexts, script):
        # Build an executable for the target
        appdir, resdir, plist = self.create_bundle(target, script)
        self.appdir = appdir
        self.resdir = resdir
        self.plist = plist

        for src, dest in self.iter_data_files():
            dest = os.path.join(resdir, dest)
            self.mkpath(os.path.dirname(dest))
            copy_resource(src, dest, dry_run=self.dry_run)

        self.compile_datamodels(resdir)
        self.compile_mappingmodels(resdir)

        bootfn = '__boot__'
        bootfile = file(os.path.join(resdir, bootfn + '.py'), 'w')
        for fn in target.prescripts:
            bootfile.write(self.get_bootstrap_data(fn))
            bootfile.write('\n\n')
        bootfile.write('_run(%r)\n' % (os.path.basename(script),))
        bootfile.close()

        self.copy_file(script, resdir)
        pydir = os.path.join(resdir, 'lib', 'python' + sys.version[:3])
        realhome = os.path.join(sys.prefix, 'lib', 'python' + sys.version[:3])
        self.mkpath(pydir)
        self.symlink('../../site.py', os.path.join(pydir, 'site.py'))
        cfgdir = os.path.join(pydir, 'config')
        realcfg = os.path.join(realhome, 'config')
        real_include = os.path.join(sys.prefix, 'include')
        if self.semi_standalone:
            self.symlink(realcfg, cfgdir)
            self.symlink(real_include, os.path.join(resdir, 'include'))
        else:
            self.mkpath(cfgdir)
            for fn in 'Makefile', 'Setup', 'Setup.local', 'Setup.config':
                rfn = os.path.join(realcfg, fn)
                if os.path.exists(rfn):
                    self.copy_file(rfn, os.path.join(cfgdir, fn))

            inc_dir = os.path.join(resdir, 'include', 'python' + sys.version[:3])
            self.mkpath(inc_dir)
            self.copy_file(os.path.join(real_include, 'python%s/pyconfig.h'%(
                sys.version[:3])), os.path.join(inc_dir, 'pyconfig.h'))


        self.copy_file(arcname, pydir)
        ext_dir = os.path.join(pydir, os.path.basename(self.ext_dir))
        self.copy_tree(self.ext_dir, ext_dir, preserve_symlinks=True)
        self.copy_tree(self.framework_dir,
            os.path.join(appdir, 'Contents', 'Frameworks'),
            preserve_symlinks=True)
        for pkg in self.packages:
            pkg = self.get_bootstrap(pkg)
            dst = os.path.join(pydir, os.path.basename(pkg))
            self.mkpath(dst)
            self.copy_tree(pkg, dst)
        for copyext in copyexts:
            fn = os.path.join(ext_dir,
                (copyext.identifier.replace('.', os.sep) +
                os.path.splitext(copyext.filename)[1])
            )
            self.mkpath(os.path.dirname(fn))
            copy_file(copyext.filename, fn, dry_run=self.dry_run)

        target.appdir = appdir
        return appdir

    def create_loader(self, item):
        # Hm, how to avoid needless recreation of this file?
        slashname = item.identifier.replace('.', os.sep)
        pathname = os.path.join(self.temp_dir, "%s.py" % slashname)
        if os.path.exists(pathname):
            if self.verbose:
                print ("skipping python loader for extension %r"
                    % (item.identifier,))
        else:
            self.mkpath(os.path.dirname(pathname))
            # and what about dry_run?
            if self.verbose:
                print ("creating python loader for extension %r"
                    % (item.identifier,))

            fname = slashname + os.path.splitext(item.filename)[1]
            source = make_loader(fname)
            if not self.dry_run:
                open(pathname, "w").write(source)
            else:
                return
        return SourceModule(item.identifier, pathname)

    def make_lib_archive(self, zip_filename, base_dir, verbose=0,
                         dry_run=0):
        # Like distutils "make_archive", except we can specify the
        # compression to use - default is ZIP_STORED to keep the
        # runtime performance up.
        # Also, we don't append '.zip' to the filename.
        from distutils.dir_util import mkpath
        mkpath(os.path.dirname(zip_filename), dry_run=dry_run)

        if self.compressed:
            compression = zipfile.ZIP_DEFLATED
        else:
            compression = zipfile.ZIP_STORED
        if not dry_run:
            z = zipfile.ZipFile(zip_filename, "w",
                                compression=compression)
            save_cwd = os.getcwd()
            os.chdir(base_dir)
            for dirpath, dirnames, filenames in os.walk('.'):
                for fn in filenames:
                    path = os.path.normpath(os.path.join(dirpath, fn))
                    if os.path.isfile(path):
                        z.write(path, path)
            os.chdir(save_cwd)
            z.close()

        return zip_filename

    def copy_tree(self, infile, outfile,
                   preserve_mode=1, preserve_times=1, preserve_symlinks=0,
                   level=1, condition=None):
        """Copy an entire directory tree respecting verbose, dry-run,
        and force flags.

        This version doesn't bork on existing symlinks
        """
        return copy_tree(
            infile, outfile,
            preserve_mode,preserve_times,preserve_symlinks,
            not self.force,
            dry_run=self.dry_run,
            condition=condition)
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.