Commits

Just van Rossum  committed 24574c3

sync with Python tree (it's been a while)

  • Participants
  • Parent commits fdf3de8

Comments (0)

Files changed (1)

File pyobjc/MPCompat/bundlebuilder.py

 from plistlib import Plist
 from types import FunctionType as function
 
-
 class BundleBuilderError(Exception): pass
 
 
 	              CFBundleInfoDictionaryVersion = "6.0")
 
 	# The type of the bundle.
-	type = "APPL"
+	type = "BNDL"
 	# The creator code of the bundle.
 	creator = None
 
 	# (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"
 
-	# platform, name of the subfolder of Contents that contains the executable.
-	platform = "MacOS"
-
 	# Make symlinks instead copying files. This is handy during debugging, but
 	# makes the bundle non-distributable.
 	symlink = 0
 		bundleextension = ext
 		# misc (derived) attributes
 		self.bundlepath = pathjoin(self.builddir, self.name + bundleextension)
-		self.execdir = pathjoin("Contents", self.platform)
 
 		plist = self.plist
 		plist.CFBundleName = self.name
 		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"
 
 SITE_CO = compile(SITE_PY, "<-bundlebuilder.py->", "exec")
 
+#
+# 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
 
 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 = """\
-#!/bin/sh
+#!%(hashbang)s
 
-execdir=$(dirname "${0}")
-executable="${execdir}/%(executable)s"
-resdir=$(dirname "${execdir}")/Resources
-main="${resdir}/%(mainprogram)s"
-PYTHONPATH="$resdir"
-export PYTHONPATH
-exec "${executable}" "${main}" "${1}"
+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)
+os.environ["PYTHONPATH"] = resdir
+if %(standalone)s:
+	os.environ["PYTHONHOME"] = resdir
+os.environ["PYTHONEXECUTABLE"] = executable
+os.environ["DYLD_LIBRARY_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"))
+"""
+
+
 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.)
 
 	# If True, build standalone app.
 	standalone = 0
+	
+	# 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.
 
 			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:
 		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:
+					self.includeModules.append("argvemulator")
+					self.includeModules.append("os")
+				if not self.plist.has_key("CFBundleDocumentTypes"):
+					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:
+				# XXX we're screwed when the end user has deleted
+				# /usr/bin/python
+				hashbang = "/usr/bin/python"
+			else:
+				hashbang = os.path.realpath(sys.executable)
+			standalone = self.standalone
 			open(bootstrappath, "w").write(BOOTSTRAP_SCRIPT % locals())
 			os.chmod(bootstrappath, 0775)
 
 		site.__code__ = SITE_CO
 		mf.scan_code(SITE_CO, site)
 
+		# warnings.py gets imported implicitly from C
+		mf.import_hook("warnings")
+
 		includeModules = self.includeModules[:]
 		for name in self.includePackages:
 			includeModules.extend(findPackageContents(name).keys())
   -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: '????')
       --link-exec        symlink the executable instead of copying it
       --standalone       build a standalone application, which is fully
                          independent of a Python installation
+      --lib=FILE         shared library or framework to be copied into
+                         the bundle
   -x, --exclude=MODULE   exclude module (with --standalone)
   -i, --include=MODULE   include module (with --standalone)
       --package=PACKAGE  include a whole package (with --standalone)
 	if builder is None:
 		builder = AppBuilder(verbosity=1)
 
-	shortopts = "b:n:r:e:m:c:p:lx:i:hvq"
-	longopts = ("builddir=", "name=", "resource=", "executable=",
+	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", "standalone",
-		"exclude=", "include=", "package=", "strip", "iconfile=")
+		"link-exec", "help", "verbose", "quiet", "argv", "standalone",
+		"exclude=", "include=", "package=", "strip", "iconfile=",
+		"lib=")
 
 	try:
 		options, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
 			builder.name = arg
 		elif opt in ('-r', '--resource'):
 			builder.resources.append(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 == '--iconfile':
 			builder.iconfile = arg
+		elif opt == "--lib":
+			builder.libs.append(arg)
 		elif opt == "--nib":
 			builder.nibname = arg
 		elif opt in ('-p', '--plist'):