Issue #20022: Eliminate use of deprecated bundlebuilder in OS X builds.
This commit is contained in:
parent
502ef90ae2
commit
13399445e3
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
|
@ -38,7 +38,7 @@
|
|||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>PythonLauncher</string>
|
||||
<string>Python Launcher</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>%VERSION%, © 2001-2014 Python Software Foundation</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
|
|
|
@ -15,12 +15,10 @@ BUILDPYTHON= $(builddir)/python$(BUILDEXE)
|
|||
PYTHONFRAMEWORK=@PYTHONFRAMEWORK@
|
||||
|
||||
# Deployment target selected during configure, to be checked
|
||||
# by distutils
|
||||
# by distutils
|
||||
MACOSX_DEPLOYMENT_TARGET=@CONFIGURE_MACOSX_DEPLOYMENT_TARGET@
|
||||
@EXPORT_MACOSX_DEPLOYMENT_TARGET@export MACOSX_DEPLOYMENT_TARGET
|
||||
|
||||
BUNDLEBULDER=$(srcdir)/../Tools/bundlebuilder.py
|
||||
|
||||
PYTHONAPPSDIR=@FRAMEWORKINSTALLAPPSPREFIX@/$(PYTHONFRAMEWORK) $(VERSION)
|
||||
OBJECTS=FileSettings.o MyAppDelegate.o MyDocument.o PreferencesWindowController.o doscript.o main.o
|
||||
|
||||
|
@ -30,10 +28,10 @@ install: Python\ Launcher.app
|
|||
/bin/cp -r "Python Launcher.app" "$(DESTDIR)$(PYTHONAPPSDIR)"
|
||||
touch "$(DESTDIR)$(PYTHONAPPSDIR)/Python Launcher.app"
|
||||
|
||||
|
||||
clean:
|
||||
rm -f *.o "Python Launcher"
|
||||
rm -rf "Python Launcher.app"
|
||||
rm -f Info.plist
|
||||
|
||||
Python\ Launcher.app: Info.plist \
|
||||
Python\ Launcher $(srcdir)/../Icons/PythonLauncher.icns \
|
||||
|
@ -41,20 +39,18 @@ Python\ Launcher.app: Info.plist \
|
|||
$(srcdir)/../Icons/PythonCompiled.icns \
|
||||
$(srcdir)/factorySettings.plist
|
||||
rm -fr "Python Launcher.app"
|
||||
$(RUNSHARED) $(BUILDPYTHON) $(BUNDLEBULDER) \
|
||||
--builddir=. \
|
||||
--name="Python Launcher" \
|
||||
--executable="Python Launcher" \
|
||||
--iconfile=$(srcdir)/../Icons/PythonLauncher.icns \
|
||||
--bundle-id=org.python.PythonLauncher \
|
||||
--resource=$(srcdir)/../Icons/PythonSource.icns \
|
||||
--resource=$(srcdir)/../Icons/PythonCompiled.icns \
|
||||
--resource=$(srcdir)/English.lproj \
|
||||
--resource=$(srcdir)/factorySettings.plist \
|
||||
--plist Info.plist \
|
||||
build
|
||||
mkdir "Python Launcher.app"
|
||||
mkdir "Python Launcher.app/Contents"
|
||||
mkdir "Python Launcher.app/Contents/MacOS"
|
||||
mkdir "Python Launcher.app/Contents/Resources"
|
||||
cp "Python Launcher" "Python Launcher.app/Contents/MacOS"
|
||||
cp Info.plist "Python Launcher.app/Contents"
|
||||
cp $(srcdir)/../Icons/PythonLauncher.icns "Python Launcher.app/Contents/Resources"
|
||||
cp $(srcdir)/../Icons/PythonSource.icns "Python Launcher.app/Contents/Resources"
|
||||
cp $(srcdir)/../Icons/PythonCompiled.icns "Python Launcher.app/Contents/Resources"
|
||||
cp $(srcdir)/factorySettings.plist "Python Launcher.app/Contents/Resources"
|
||||
cp -R $(srcdir)/English.lproj "Python Launcher.app/Contents/Resources"
|
||||
find "Python Launcher.app" -name '.svn' -print0 | xargs -0 rm -r
|
||||
|
||||
|
||||
FileSettings.o: $(srcdir)/FileSettings.m
|
||||
$(CC) $(CFLAGS) -o $@ -c $(srcdir)/FileSettings.m
|
||||
|
|
|
@ -1,934 +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 base in cls.__bases__:
|
||||
if hasattr(base, "_getDefaults"):
|
||||
defaults.update(base._getDefaults())
|
||||
for name, value in list(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
|
||||
|
||||
# Destination root directory
|
||||
destroot = ""
|
||||
|
||||
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)
|
||||
if os.path.exists(self.bundlepath + '~'):
|
||||
shutil.rmtree(self.bundlepath + '~')
|
||||
bp = self.bundlepath
|
||||
|
||||
# Create the app bundle in a temporary location and then
|
||||
# rename the completed bundle. This way the Finder will
|
||||
# never see an incomplete bundle (where it might pick up
|
||||
# and cache the wrong meta data)
|
||||
self.bundlepath = bp + '~'
|
||||
try:
|
||||
os.mkdir(self.bundlepath)
|
||||
self.preProcess()
|
||||
self._copyFiles()
|
||||
self._addMetaFiles()
|
||||
self.postProcess()
|
||||
os.rename(self.bundlepath, bp)
|
||||
finally:
|
||||
self.bundlepath = bp
|
||||
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).encode('latin1'))
|
||||
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', '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
|
||||
|
||||
argvemulator.ArgvCollector().mainloop()
|
||||
execfile(os.path.join(os.path.split(__file__)[0], "%(realmainprogram)s"))
|
||||
"""
|
||||
|
||||
#
|
||||
# When building a standalone app with Python.framework, we need to copy
|
||||
# a subset from Python.framework to the bundle. The following list
|
||||
# specifies exactly what items we'll copy.
|
||||
#
|
||||
PYTHONFRAMEWORKGOODIES = [
|
||||
"Python", # the Python core library
|
||||
"Resources/English.lproj",
|
||||
"Resources/Info.plist",
|
||||
"Resources/version.plist",
|
||||
]
|
||||
|
||||
def isFramework():
|
||||
return sys.exec_prefix.find("Python.framework") > 0
|
||||
|
||||
|
||||
LIB = os.path.join(sys.prefix, "lib", "python" + sys.version[:3])
|
||||
SITE_PACKAGES = os.path.join(LIB, "site-packages")
|
||||
|
||||
|
||||
class AppBuilder(BundleBuilder):
|
||||
|
||||
# Override type of the bundle.
|
||||
type = "APPL"
|
||||
|
||||
# platform, name of the subfolder of Contents that contains the executable.
|
||||
platform = "MacOS"
|
||||
|
||||
# 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
|
||||
|
||||
# The name of the icon file to be copied to Resources and used for
|
||||
# the Finder icon.
|
||||
iconfile = None
|
||||
|
||||
# Symlink the executable instead of copying it.
|
||||
symlink_exec = 0
|
||||
|
||||
# If True, build standalone app.
|
||||
standalone = 0
|
||||
|
||||
# If True, build semi-standalone app (only includes third-party modules).
|
||||
semi_standalone = 0
|
||||
|
||||
# If set, use this for #! lines in stead of sys.executable
|
||||
python = None
|
||||
|
||||
# If True, add a real main program that emulates sys.argv before calling
|
||||
# mainprogram
|
||||
argv_emulation = 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 from debug info.
|
||||
strip = 0
|
||||
|
||||
# Found Python modules: [(name, codeobject, ispkg), ...]
|
||||
pymodules = []
|
||||
|
||||
# Modules that modulefinder couldn't find:
|
||||
missingModules = []
|
||||
maybeMissingModules = []
|
||||
|
||||
def setup(self):
|
||||
if ((self.standalone or self.semi_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'")
|
||||
|
||||
self.execdir = pathjoin("Contents", self.platform)
|
||||
|
||||
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 and not isFramework():
|
||||
self.symlink_exec = 1
|
||||
if self.python:
|
||||
self.executable = self.python
|
||||
else:
|
||||
self.executable = sys.executable
|
||||
|
||||
if self.nibname:
|
||||
self.plist.NSMainNibFile = self.nibname
|
||||
if not hasattr(self.plist, "NSPrincipalClass"):
|
||||
self.plist.NSPrincipalClass = "NSApplication"
|
||||
|
||||
if self.standalone and isFramework():
|
||||
self.addPythonFramework()
|
||||
|
||||
BundleBuilder.setup(self)
|
||||
|
||||
self.plist.CFBundleExecutable = self.name
|
||||
|
||||
if self.standalone or self.semi_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.destroot + self.executable, execpath))
|
||||
self.execpath = execpath
|
||||
|
||||
if self.mainprogram is not None:
|
||||
mainprogram = os.path.basename(self.mainprogram)
|
||||
self.files.append((self.mainprogram, pathjoin(resdir, mainprogram)))
|
||||
if self.argv_emulation:
|
||||
# Change the main program, and create the helper main program (which
|
||||
# does argv collection and then calls the real main).
|
||||
# Also update the included modules (if we're creating a standalone
|
||||
# program) and the plist
|
||||
realmainprogram = mainprogram
|
||||
mainprogram = '__argvemulator_' + mainprogram
|
||||
resdirpath = pathjoin(self.bundlepath, resdir)
|
||||
mainprogrampath = pathjoin(resdirpath, mainprogram)
|
||||
makedirs(resdirpath)
|
||||
open(mainprogrampath, "w").write(ARGV_EMULATOR % locals())
|
||||
if self.standalone or self.semi_standalone:
|
||||
self.includeModules.append("argvemulator")
|
||||
self.includeModules.append("os")
|
||||
if "CFBundleDocumentTypes" not in self.plist:
|
||||
self.plist["CFBundleDocumentTypes"] = [
|
||||
{ "CFBundleTypeOSTypes" : [
|
||||
"****",
|
||||
"fold",
|
||||
"disk"],
|
||||
"CFBundleTypeRole": "Viewer"}]
|
||||
# Write bootstrap script
|
||||
executable = os.path.basename(self.executable)
|
||||
execdir = pathjoin(self.bundlepath, self.execdir)
|
||||
bootstrappath = pathjoin(execdir, self.name)
|
||||
makedirs(execdir)
|
||||
if self.standalone or self.semi_standalone:
|
||||
# XXX we're screwed when the end user has deleted
|
||||
# /usr/bin/python
|
||||
hashbang = "/usr/bin/python"
|
||||
elif self.python:
|
||||
hashbang = self.python
|
||||
else:
|
||||
hashbang = os.path.realpath(sys.executable)
|
||||
standalone = self.standalone
|
||||
semi_standalone = self.semi_standalone
|
||||
open(bootstrappath, "w").write(BOOTSTRAP_SCRIPT % locals())
|
||||
os.chmod(bootstrappath, 0o775)
|
||||
|
||||
if self.iconfile is not None:
|
||||
iconbase = os.path.basename(self.iconfile)
|
||||
self.plist.CFBundleIconFile = iconbase
|
||||
self.files.append((self.iconfile, pathjoin(resdir, iconbase)))
|
||||
|
||||
def postProcess(self):
|
||||
if self.standalone or self.semi_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 addPythonFramework(self):
|
||||
# If we're building a standalone app with Python.framework,
|
||||
# include a minimal subset of Python.framework, *unless*
|
||||
# Python.framework was specified manually in self.libs.
|
||||
for lib in self.libs:
|
||||
if os.path.basename(lib) == "Python.framework":
|
||||
# a Python.framework was specified as a library
|
||||
return
|
||||
|
||||
frameworkpath = sys.exec_prefix[:sys.exec_prefix.find(
|
||||
"Python.framework") + len("Python.framework")]
|
||||
|
||||
version = sys.version[:3]
|
||||
frameworkpath = pathjoin(frameworkpath, "Versions", version)
|
||||
destbase = pathjoin("Contents", "Frameworks", "Python.framework",
|
||||
"Versions", version)
|
||||
for item in PYTHONFRAMEWORKGOODIES:
|
||||
src = pathjoin(frameworkpath, item)
|
||||
dst = pathjoin(destbase, item)
|
||||
self.files.append((src, dst))
|
||||
|
||||
def _getSiteCode(self):
|
||||
return compile(SITE_PY % {"semi_standalone": self.semi_standalone},
|
||||
"<-bundlebuilder.py->", "exec")
|
||||
|
||||
def addPythonModules(self):
|
||||
self.message("Adding Python modules", 1)
|
||||
|
||||
if USE_ZIPIMPORT:
|
||||
# Create a zip file containing all modules as pyc.
|
||||
import zipfile
|
||||
relpath = pathjoin("Contents", "Resources", ZIP_ARCHIVE)
|
||||
abspath = pathjoin(self.bundlepath, relpath)
|
||||
zf = zipfile.ZipFile(abspath, "w", zipfile.ZIP_DEFLATED)
|
||||
for name, code, ispkg in self.pymodules:
|
||||
self.message("Adding Python module %s" % name, 2)
|
||||
path, pyc = getPycData(name, code, ispkg)
|
||||
zf.writestr(path, pyc)
|
||||
zf.close()
|
||||
# add site.pyc
|
||||
sitepath = pathjoin(self.bundlepath, "Contents", "Resources",
|
||||
"site" + PYC_EXT)
|
||||
writePyc(self._getSiteCode(), 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:
|
||||
import stat
|
||||
self.message("Stripping binaries", 1)
|
||||
def walk(top):
|
||||
for name in os.listdir(top):
|
||||
path = pathjoin(top, name)
|
||||
if os.path.islink(path):
|
||||
continue
|
||||
if os.path.isdir(path):
|
||||
walk(path)
|
||||
else:
|
||||
mod = os.stat(path)[stat.ST_MODE]
|
||||
if not (mod & 0o100):
|
||||
continue
|
||||
relpath = path[len(self.bundlepath):]
|
||||
self.message("Stripping %s" % relpath, 2)
|
||||
inf, outf = os.popen4("%s -S \"%s\"" %
|
||||
(STRIP_EXEC, path))
|
||||
output = outf.read().strip()
|
||||
if output:
|
||||
# usually not a real problem, like when we're
|
||||
# trying to strip a script
|
||||
self.message("Problem stripping %s:" % relpath, 3)
|
||||
self.message(output, 3)
|
||||
walk(self.bundlepath)
|
||||
|
||||
def findDependencies(self):
|
||||
self.message("Finding module dependencies", 1)
|
||||
import modulefinder
|
||||
mf = modulefinder.ModuleFinder(excludes=self.excludeModules)
|
||||
if USE_ZIPIMPORT:
|
||||
# zipimport imports zlib, must add it manually
|
||||
mf.import_hook("zlib")
|
||||
# manually add our own site.py
|
||||
site = mf.add_module("site")
|
||||
site.__code__ = self._getSiteCode()
|
||||
mf.scan_code(site.__code__, site)
|
||||
|
||||
# warnings.py gets imported implicitly from C
|
||||
mf.import_hook("warnings")
|
||||
|
||||
includeModules = self.includeModules[:]
|
||||
for name in self.includePackages:
|
||||
includeModules.extend(list(findPackageContents(name).keys()))
|
||||
for name in includeModules:
|
||||
try:
|
||||
mf.import_hook(name)
|
||||
except ImportError:
|
||||
self.missingModules.append(name)
|
||||
|
||||
mf.run_script(self.mainprogram)
|
||||
modules = list(mf.modules.items())
|
||||
modules.sort()
|
||||
for name, mod in modules:
|
||||
path = mod.__file__
|
||||
if path and self.semi_standalone:
|
||||
# skip the standard library
|
||||
if path.startswith(LIB) and not path.startswith(SITE_PACKAGES):
|
||||
continue
|
||||
if path and mod.__code__ is None:
|
||||
# C extension
|
||||
filename = os.path.basename(path)
|
||||
pathitems = name.split(".")[:-1] + [filename]
|
||||
dstpath = pathjoin(*pathitems)
|
||||
if USE_ZIPIMPORT:
|
||||
if name != "zlib":
|
||||
# neatly pack all extension modules in a subdirectory,
|
||||
# except zlib, since it's necessary for bootstrapping.
|
||||
dstpath = pathjoin("ExtensionModules", dstpath)
|
||||
# Python modules are stored in a Zip archive, but put
|
||||
# extensions in Contents/Resources/. Add a tiny "loader"
|
||||
# program in the Zip archive. Due to Thomas Heller.
|
||||
source = EXT_LOADER % {"name": name, "filename": dstpath}
|
||||
code = compile(source, "<dynloader for %s>" % name, "exec")
|
||||
mod.__code__ = code
|
||||
self.files.append((path, pathjoin("Contents", "Resources", dstpath)))
|
||||
if mod.__code__ is not None:
|
||||
ispkg = mod.__path__ is not None
|
||||
if not USE_ZIPIMPORT or name != "site":
|
||||
# Our site.py is doing the bootstrapping, so we must
|
||||
# include a real .pyc file if USE_ZIPIMPORT 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 or self.semi_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(MAGIC)
|
||||
f.write("\0" * 4) # don't bother about a time stamp
|
||||
marshal.dump(code, f)
|
||||
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, symlinks=1)
|
||||
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 as why:
|
||||
if why.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
def symlink(src, dst, mkdirs=0):
|
||||
"""Copy a file or a directory."""
|
||||
if not os.path.exists(src):
|
||||
raise IOError("No such file or directory: '%s'" % src)
|
||||
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
|
||||
-f, --file=SRC:DST extra file or folder to be copied into the bundle;
|
||||
DST must be a path relative to the bundle root
|
||||
-e, --executable=FILE the executable to be used
|
||||
-m, --mainprogram=FILE the Python main program
|
||||
-a, --argv add a wrapper main program to create sys.argv
|
||||
-p, --plist=FILE .plist file (default: generate one)
|
||||
--nib=NAME main nib name
|
||||
-c, --creator=CCCC 4-char creator code (default: '????')
|
||||
--iconfile=FILE filename of the icon (an .icns file) to be used
|
||||
as the Finder icon
|
||||
--bundle-id=ID the CFBundleIdentifier, in reverse-dns format
|
||||
(eg. org.python.BuildApplet; this is used for
|
||||
the preferences file name)
|
||||
-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
|
||||
--semi-standalone build a standalone application, which depends on
|
||||
an installed Python, yet includes all third-party
|
||||
modules.
|
||||
--python=FILE Python to use in #! line in stead of current Python
|
||||
--lib=FILE shared library or framework to be copied into
|
||||
the bundle
|
||||
-x, --exclude=MODULE exclude module (with --(semi-)standalone)
|
||||
-i, --include=MODULE include module (with --(semi-)standalone)
|
||||
--package=PACKAGE include a whole package (with --(semi-)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:f:e:m:c:p:lx:i:hvqa"
|
||||
longopts = ("builddir=", "name=", "resource=", "file=", "executable=",
|
||||
"mainprogram=", "creator=", "nib=", "plist=", "link",
|
||||
"link-exec", "help", "verbose", "quiet", "argv", "standalone",
|
||||
"exclude=", "include=", "package=", "strip", "iconfile=",
|
||||
"lib=", "python=", "semi-standalone", "bundle-id=", "destroot=")
|
||||
|
||||
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(os.path.normpath(arg))
|
||||
elif opt in ('-f', '--file'):
|
||||
srcdst = arg.split(':')
|
||||
if len(srcdst) != 2:
|
||||
usage("-f or --file argument must be two paths, "
|
||||
"separated by a colon")
|
||||
builder.files.append(srcdst)
|
||||
elif opt in ('-e', '--executable'):
|
||||
builder.executable = arg
|
||||
elif opt in ('-m', '--mainprogram'):
|
||||
builder.mainprogram = arg
|
||||
elif opt in ('-a', '--argv'):
|
||||
builder.argv_emulation = 1
|
||||
elif opt in ('-c', '--creator'):
|
||||
builder.creator = arg
|
||||
elif opt == '--bundle-id':
|
||||
builder.bundle_id = arg
|
||||
elif opt == '--iconfile':
|
||||
builder.iconfile = arg
|
||||
elif opt == "--lib":
|
||||
builder.libs.append(os.path.normpath(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 == '--semi-standalone':
|
||||
builder.semi_standalone = 1
|
||||
elif opt == '--python':
|
||||
builder.python = arg
|
||||
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
|
||||
elif opt == '--destroot':
|
||||
builder.destroot = arg
|
||||
|
||||
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()
|
Loading…
Reference in New Issue