- # 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, 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.
- strip = 0
-
- # Found Python modules: [(name, codeobject, ispkg), ...]
- pymodules = []
-
- # Modules that modulefinder couldn't find:
- missingModules = []
- maybeMissingModules = []
-
- # List of all binaries (executables or shared libs), for stripping purposes
- binaries = []
-
- def setup(self):
- if self.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:
- self.symlink_exec = 1
- self.executable = sys.executable
-
- if self.nibname:
- self.plist.NSMainNibFile = self.nibname
- if not hasattr(self.plist, "NSPrincipalClass"):
- self.plist.NSPrincipalClass = "NSApplication"
-
- BundleBuilder.setup(self)
-
- self.plist.CFBundleExecutable = self.name
-
- if self.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.executable, execpath))
- self.binaries.append(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:
- 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"
- pythonhome = 'os.environ["PYTHONHOME"] = resdir'
- else:
- hashbang = sys.executable
- while os.path.islink(hashbang):
- hashbang = os.readlink(hashbang)
- pythonhome = ''
- open(bootstrappath, "w").write(BOOTSTRAP_SCRIPT % locals())
- os.chmod(bootstrappath, 0775)
-
- 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:
- 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 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(SITE_CO, 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:
- self.message("Stripping binaries", 1)
- for relpath in self.binaries:
- self.message("Stripping %s" % relpath, 2)
- abspath = pathjoin(self.bundlepath, relpath)
- assert not os.path.islink(abspath)
- rv = os.system("%s -S \"%s\"" % (STRIP_EXEC, abspath))
-
- 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__ = 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())
- for name in includeModules:
- try:
- mf.import_hook(name)
- except ImportError:
- self.missingModules.append(name)
-
- mf.run_script(self.mainprogram)
- modules = mf.modules.items()
- modules.sort()
- for name, mod in modules:
- if mod.__file__ and mod.__code__ is None:
- # C extension
- path = mod.__file__
- filename = os.path.basename(path)
- if USE_ZIPIMPORT:
- # Python modules are stored in a Zip archive, but put
- # extensions in Contents/Resources/.a and add a tiny "loader"
- # program in the Zip archive. Due to Thomas Heller.
- dstpath = pathjoin("Contents", "Resources", filename)
- source = EXT_LOADER % {"name": name, "filename": filename}
- code = compile(source, "<dynloader for %s>" % name, "exec")
- mod.__code__ = code
- else:
- # just copy the file
- dstpath = name.split(".")[:-1] + [filename]
- dstpath = pathjoin("Contents", "Resources", *dstpath)
- self.files.append((path, dstpath))
- self.binaries.append(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:
- self.reportMissing()
+ # 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.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 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 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, 0775)
+
+ 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 & 0100):
+ 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(findPackageContents(name).keys())
+ for name in includeModules:
+ try:
+ mf.import_hook(name)
+ except ImportError:
+ self.missingModules.append(name)
+
+ mf.run_script(self.mainprogram)
+ modules = 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 neccesary 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()