Updated bundlebuilder from Python CVS and added ability to specify

libs to be added to the bundle.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_4_BRANCH@19145 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2003-02-07 00:58:59 +00:00
parent 16b5e0e8e1
commit a490d0f89c

View File

@@ -81,7 +81,7 @@ class BundleBuilder(Defaults):
# The property list ("plist") # The property list ("plist")
plist = Plist(CFBundleDevelopmentRegion = "English", plist = Plist(CFBundleDevelopmentRegion = "English",
CFBundleInfoDictionaryVersion = "6.0") CFBundleInfoDictionaryVersion = "6.0")
# The type of the bundle. # The type of the bundle.
type = "APPL" type = "APPL"
@@ -95,6 +95,10 @@ class BundleBuilder(Defaults):
# (eg. "Contents/Resources/MyStuff/SomeFile.ext). # (eg. "Contents/Resources/MyStuff/SomeFile.ext).
files = [] 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. # Directory where the bundle will be assembled.
builddir = "build" builddir = "build"
@@ -127,6 +131,8 @@ class BundleBuilder(Defaults):
else: else:
self.creator = "????" self.creator = "????"
plist.CFBundleSignature = self.creator plist.CFBundleSignature = self.creator
if not hasattr(plist, "CFBundleIdentifier"):
plist.CFBundleIdentifier = self.name
def build(self): def build(self):
"""Build the bundle.""" """Build the bundle."""
@@ -171,6 +177,9 @@ class BundleBuilder(Defaults):
for path in self.resources: for path in self.resources:
files.append((path, pathjoin("Contents", "Resources", files.append((path, pathjoin("Contents", "Resources",
os.path.basename(path)))) os.path.basename(path))))
for path in self.libs:
files.append((path, pathjoin("Contents", "Frameworks",
os.path.basename(path))))
if self.symlink: if self.symlink:
self.message("Making symbolic links", 1) self.message("Making symbolic links", 1)
msg = "Making symlink from" msg = "Making symlink from"
@@ -207,7 +216,7 @@ else:
PYC_EXT = ".pyo" PYC_EXT = ".pyo"
MAGIC = imp.get_magic() MAGIC = imp.get_magic()
USE_FROZEN = hasattr(imp, "set_frozenmodules") USE_ZIPIMPORT = 0 ## "zipimport" in sys.builtin_module_names
# For standalone apps, we have our own minimal site.py. We don't need # For standalone apps, we have our own minimal site.py. We don't need
# all the cruft of the real site.py. # all the cruft of the real site.py.
@@ -216,28 +225,30 @@ import sys
del sys.path[1:] # sys.path[0] is Contents/Resources/ del sys.path[1:] # sys.path[0] is Contents/Resources/
""" """
if USE_FROZEN: if USE_ZIPIMPORT:
FROZEN_ARCHIVE = "FrozenModules.marshal" ZIP_ARCHIVE = "Modules.zip"
SITE_PY += """\ SITE_PY += "sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
# bootstrapping def getPycData(fullname, code, ispkg):
import imp, marshal if ispkg:
f = open(sys.path[0] + "/%s", "rb") fullname += ".__init__"
imp.set_frozenmodules(marshal.load(f)) path = fullname.replace(".", os.sep) + PYC_EXT
f.close() return path, MAGIC + '\0\0\0\0' + marshal.dumps(code)
""" % FROZEN_ARCHIVE
SITE_CO = compile(SITE_PY, "<-bundlebuilder.py->", "exec") SITE_CO = compile(SITE_PY, "<-bundlebuilder.py->", "exec")
EXT_LOADER = """\ EXT_LOADER = """\
import imp, sys, os def __load():
for p in sys.path: import imp, sys, os
path = os.path.join(p, "%(filename)s") for p in sys.path:
if os.path.exists(path): path = os.path.join(p, "%(filename)s")
break if os.path.exists(path):
else: break
assert 0, "file not found: %(filename)s" else:
mod = imp.load_dynamic("%(name)s", path) assert 0, "file not found: %(filename)s"
sys.modules["%(name)s"] = mod mod = imp.load_dynamic("%(name)s", path)
__load()
del __load
""" """
MAYMISS_MODULES = ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath', MAYMISS_MODULES = ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath',
@@ -250,13 +261,15 @@ STRIP_EXEC = "/usr/bin/strip"
BOOTSTRAP_SCRIPT = """\ BOOTSTRAP_SCRIPT = """\
#!/bin/sh #!/bin/sh
execdir=$(dirname ${0}) execdir=$(dirname "${0}")
DYLD_LIBRARY_PATH=$(dirname "${execdir}")/Frameworks
export DYLD_LIBRARY_PATH
executable=${execdir}/%(executable)s executable=${execdir}/%(executable)s
resdir=$(dirname ${execdir})/Resources resdir=$(dirname "${execdir}")/Resources
main=${resdir}/%(mainprogram)s main=${resdir}/%(mainprogram)s
PYTHONPATH=$resdir PYTHONPATH=$resdir
export PYTHONPATH export PYTHONPATH
exec ${executable} ${main} ${1} exec "${executable}" "${main}" "${1}"
""" """
@@ -277,7 +290,8 @@ class AppBuilder(BundleBuilder):
# when building a Cocoa app. # when building a Cocoa app.
nibname = None nibname = None
# The name of the icon file to be copied to Resources and used for the Finder icon. # The name of the icon file to be copied to Resources and used for
# the Finder icon.
iconfile = None iconfile = None
# Symlink the executable instead of copying it. # Symlink the executable instead of copying it.
@@ -371,7 +385,7 @@ class AppBuilder(BundleBuilder):
if self.iconfile is not None: if self.iconfile is not None:
iconbase = os.path.basename(self.iconfile) iconbase = os.path.basename(self.iconfile)
self.plist.CFBundleIconFile = iconbase self.plist.CFBundleIconFile = iconbase
self.files.append( (self.iconfile, pathjoin(resdir, iconbase)) ) self.files.append((self.iconfile, pathjoin(resdir, iconbase)))
def postProcess(self): def postProcess(self):
if self.standalone: if self.standalone:
@@ -392,23 +406,17 @@ class AppBuilder(BundleBuilder):
def addPythonModules(self): def addPythonModules(self):
self.message("Adding Python modules", 1) self.message("Adding Python modules", 1)
if USE_FROZEN: if USE_ZIPIMPORT:
# This anticipates the acceptance of this patch: # Create a zip file containing all modules as pyc.
# http://www.python.org/sf/642578 import zipfile
# Create a file containing all modules, frozen. relpath = pathjoin("Contents", "Resources", ZIP_ARCHIVE)
frozenmodules = []
for name, code, ispkg in self.pymodules:
if ispkg:
self.message("Adding Python package %s" % name, 2)
else:
self.message("Adding Python module %s" % name, 2)
frozenmodules.append((name, marshal.dumps(code), ispkg))
frozenmodules = tuple(frozenmodules)
relpath = pathjoin("Contents", "Resources", FROZEN_ARCHIVE)
abspath = pathjoin(self.bundlepath, relpath) abspath = pathjoin(self.bundlepath, relpath)
f = open(abspath, "wb") zf = zipfile.ZipFile(abspath, "w", zipfile.ZIP_DEFLATED)
marshal.dump(frozenmodules, f) for name, code, ispkg in self.pymodules:
f.close() self.message("Adding Python module %s" % name, 2)
path, pyc = getPycData(name, code, ispkg)
zf.writestr(path, pyc)
zf.close()
# add site.pyc # add site.pyc
sitepath = pathjoin(self.bundlepath, "Contents", "Resources", sitepath = pathjoin(self.bundlepath, "Contents", "Resources",
"site" + PYC_EXT) "site" + PYC_EXT)
@@ -446,6 +454,9 @@ class AppBuilder(BundleBuilder):
self.message("Finding module dependencies", 1) self.message("Finding module dependencies", 1)
import modulefinder import modulefinder
mf = modulefinder.ModuleFinder(excludes=self.excludeModules) 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 # manually add our own site.py
site = mf.add_module("site") site = mf.add_module("site")
site.__code__ = SITE_CO site.__code__ = SITE_CO
@@ -468,9 +479,10 @@ class AppBuilder(BundleBuilder):
# C extension # C extension
path = mod.__file__ path = mod.__file__
filename = os.path.basename(path) filename = os.path.basename(path)
if USE_FROZEN: if USE_ZIPIMPORT:
# "proper" freezing, put extensions in Contents/Resources/, # Python modules are stored in a Zip archive, but put
# freeze a tiny "loader" program. Due to Thomas Heller. # extensions in Contents/Resources/.a and add a tiny "loader"
# program in the Zip archive. Due to Thomas Heller.
dstpath = pathjoin("Contents", "Resources", filename) dstpath = pathjoin("Contents", "Resources", filename)
source = EXT_LOADER % {"name": name, "filename": filename} source = EXT_LOADER % {"name": name, "filename": filename}
code = compile(source, "<dynloader for %s>" % name, "exec") code = compile(source, "<dynloader for %s>" % name, "exec")
@@ -483,9 +495,9 @@ class AppBuilder(BundleBuilder):
self.binaries.append(dstpath) self.binaries.append(dstpath)
if mod.__code__ is not None: if mod.__code__ is not None:
ispkg = mod.__path__ is not None ispkg = mod.__path__ is not None
if not USE_FROZEN or name != "site": if not USE_ZIPIMPORT or name != "site":
# Our site.py is doing the bootstrapping, so we must # Our site.py is doing the bootstrapping, so we must
# include a real .pyc file if USE_FROZEN is True. # include a real .pyc file if USE_ZIPIMPORT is True.
self.pymodules.append((name, mod.__code__, ispkg)) self.pymodules.append((name, mod.__code__, ispkg))
if hasattr(mf, "any_missing_maybe"): if hasattr(mf, "any_missing_maybe"):
@@ -508,17 +520,17 @@ class AppBuilder(BundleBuilder):
maybe.sort() maybe.sort()
if maybe: if maybe:
self.message("Warning: couldn't find the following submodules:", 1) self.message("Warning: couldn't find the following submodules:", 1)
self.message(" (Note that these could be false alarms -- " self.message(" (Note that these could be false alarms -- "
"it's not always", 1) "it's not always", 1)
self.message(" possible to distinguish between \"from package " self.message(" possible to distinguish between \"from package "
"import submodule\" ", 1) "import submodule\" ", 1)
self.message(" and \"from package import name\")", 1) self.message(" and \"from package import name\")", 1)
for name in maybe: for name in maybe:
self.message(" ? " + name, 1) self.message(" ? " + name, 1)
if missing: if missing:
self.message("Warning: couldn't find the following modules:", 1) self.message("Warning: couldn't find the following modules:", 1)
for name in missing: for name in missing:
self.message(" ? " + name, 1) self.message(" ? " + name, 1)
def report(self): def report(self):
# XXX something decent # XXX something decent
@@ -554,10 +566,9 @@ def findPackageContents(name, searchpath=None):
def writePyc(code, path): def writePyc(code, path):
f = open(path, "wb") f = open(path, "wb")
f.write("\0" * 8) # don't bother about a time stamp
marshal.dump(code, f)
f.seek(0, 0)
f.write(MAGIC) f.write(MAGIC)
f.write("\0" * 4) # don't bother about a time stamp
marshal.dump(code, f)
f.close() f.close()
def copy(src, dst, mkdirs=0): def copy(src, dst, mkdirs=0):
@@ -585,6 +596,8 @@ def makedirs(dir):
def symlink(src, dst, mkdirs=0): def symlink(src, dst, mkdirs=0):
"""Copy a file or a directory.""" """Copy a file or a directory."""
if not os.path.exists(src):
raise IOError, "No such file or directory: '%s'" % src
if mkdirs: if mkdirs:
makedirs(os.path.dirname(dst)) makedirs(os.path.dirname(dst))
os.symlink(os.path.abspath(src), dst) os.symlink(os.path.abspath(src), dst)
@@ -603,31 +616,33 @@ Usage:
python mybuildscript.py [options] command python mybuildscript.py [options] command
Commands: Commands:
build build the application build build the application
report print a report report print a report
Options: Options:
-b, --builddir=DIR the build directory; defaults to "build" -b, --builddir=DIR the build directory; defaults to "build"
-n, --name=NAME application name -n, --name=NAME application name
-r, --resource=FILE extra file or folder to be copied to Resources -r, --resource=FILE extra file or folder to be copied to Resources
-e, --executable=FILE the executable to be used -e, --executable=FILE the executable to be used
-m, --mainprogram=FILE the Python main program -m, --mainprogram=FILE the Python main program
-p, --plist=FILE .plist file (default: generate one) -p, --plist=FILE .plist file (default: generate one)
--nib=NAME main nib name --nib=NAME main nib name
-c, --creator=CCCC 4-char creator code (default: '????') -c, --creator=CCCC 4-char creator code (default: '????')
--iconfile=FILE Filename of the icon to be copied to Resources and --iconfile=FILE filename of the icon (an .icns file) to be used
used as the Finder icon as the Finder icon
-l, --link symlink files/folder instead of copying them -l, --link symlink files/folder instead of copying them
--link-exec symlink the executable instead of copying it --link-exec symlink the executable instead of copying it
--standalone build a standalone application, which is fully --standalone build a standalone application, which is fully
independent of a Python installation independent of a Python installation
-x, --exclude=MODULE exclude module (with --standalone) --lib=FILE shared library or framework to be copied into
-i, --include=MODULE include module (with --standalone) the bundle
--package=PACKAGE include a whole package (with --standalone) -x, --exclude=MODULE exclude module (with --standalone)
--strip strip binaries (remove debug info) -i, --include=MODULE include module (with --standalone)
-v, --verbose increase verbosity level --package=PACKAGE include a whole package (with --standalone)
-q, --quiet decrease verbosity level --strip strip binaries (remove debug info)
-h, --help print this message -v, --verbose increase verbosity level
-q, --quiet decrease verbosity level
-h, --help print this message
""" """
def usage(msg=None): def usage(msg=None):
@@ -644,7 +659,8 @@ def main(builder=None):
longopts = ("builddir=", "name=", "resource=", "executable=", longopts = ("builddir=", "name=", "resource=", "executable=",
"mainprogram=", "creator=", "nib=", "plist=", "link", "mainprogram=", "creator=", "nib=", "plist=", "link",
"link-exec", "help", "verbose", "quiet", "standalone", "link-exec", "help", "verbose", "quiet", "standalone",
"exclude=", "include=", "package=", "strip", "iconfile=") "exclude=", "include=", "package=", "strip", "iconfile=",
"lib=")
try: try:
options, args = getopt.getopt(sys.argv[1:], shortopts, longopts) options, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
@@ -666,6 +682,8 @@ def main(builder=None):
builder.creator = arg builder.creator = arg
elif opt == '--iconfile': elif opt == '--iconfile':
builder.iconfile = arg builder.iconfile = arg
elif opt == "--lib":
builder.libs.append(arg)
elif opt == "--nib": elif opt == "--nib":
builder.nibname = arg builder.nibname = arg
elif opt in ('-p', '--plist'): elif opt in ('-p', '--plist'):