Commits

Anonymous committed 1ab6214

syncing with Python CVS (post 2.3b2, though!):
- added --semi-standalone option for making compact distributable apps
that will use Jaguar's Python 2.2
- better support for nested extension modules, neater .so organization
in Contents/Resources/ExtensionModules/ (only with 2.3/zipimport)
TODO: I should write a "Building apps without PB" documentation chapter.

Comments (0)

Files changed (1)

pyobjc/MPCompat/bundlebuilder.py

 # all the cruft of the real site.py.
 SITE_PY = """\
 import sys
-del sys.path[1:]  # sys.path[0] is Contents/Resources/
+if not %(semi_standalone)s:
+    del sys.path[1:]  # sys.path[0] is Contents/Resources/
 """
 
 if USE_ZIPIMPORT:
         path = fullname.replace(".", os.sep) + PYC_EXT
         return path, MAGIC + '\0\0\0\0' + marshal.dumps(code)
 
-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.
     "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):
 
     # 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
 
     maybeMissingModules = []
 
     def setup(self):
-        if self.standalone and self.mainprogram is None:
+        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:
             self.name += ".app"
 
         if self.executable is None:
-            if not self.standalone:
+            if not self.standalone and not isFramework():
                 self.symlink_exec = 1
             self.executable = sys.executable
 
             if not hasattr(self.plist, "NSPrincipalClass"):
                 self.plist.NSPrincipalClass = "NSApplication"
 
-        if self.standalone and "Python.framework" in sys.exec_prefix:
+        if self.standalone and isFramework():
             self.addPythonFramework()
 
         BundleBuilder.setup(self)
 
         self.plist.CFBundleExecutable = self.name
 
-        if self.standalone:
+        if self.standalone or self.semi_standalone:
             self.findDependencies()
 
     def preProcess(self):
                 mainprogrampath = pathjoin(resdirpath, mainprogram)
                 makedirs(resdirpath)
                 open(mainprogrampath, "w").write(ARGV_EMULATOR % locals())
-                if self.standalone:
+                if self.standalone or self.semi_standalone:
                     self.includeModules.append("argvemulator")
                     self.includeModules.append("os")
                 if not self.plist.has_key("CFBundleDocumentTypes"):
             execdir = pathjoin(self.bundlepath, self.execdir)
             bootstrappath = pathjoin(execdir, self.name)
             makedirs(execdir)
-            if self.standalone:
+            if self.standalone or self.semi_standalone:
                 # XXX we're screwed when the end user has deleted
                 # /usr/bin/python
                 hashbang = "/usr/bin/python"
             self.files.append((self.iconfile, pathjoin(resdir, iconbase)))
 
     def postProcess(self):
-        if self.standalone:
+        if self.standalone or self.semi_standalone:
             self.addPythonModules()
         if self.strip and not self.symlink:
             self.stripBinaries()
             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)
 
             # add site.pyc
             sitepath = pathjoin(self.bundlepath, "Contents", "Resources",
                     "site" + PYC_EXT)
-            writePyc(SITE_CO, sitepath)
+            writePyc(self._getSiteCode(), sitepath)
         else:
             # Create individual .pyc files.
             for name, code, ispkg in self.pymodules:
             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)
+        site.__code__ = self._getSiteCode()
+        mf.scan_code(site.__code__, site)
 
         # warnings.py gets imported implicitly from C
         mf.import_hook("warnings")
         modules = mf.modules.items()
         modules.sort()
         for name, mod in modules:
-            if mod.__file__ and mod.__code__ is None:
+            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
-                path = mod.__file__
                 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/.a and add a tiny "loader"
+                    # extensions in Contents/Resources/. 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}
+                    source = EXT_LOADER % {"name": name, "filename": dstpath}
                     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.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":
         # XXX something decent
         import pprint
         pprint.pprint(self.__dict__)
-        if self.standalone:
+        if self.standalone or self.semi_standalone:
             self.reportMissing()
 
 #
       --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 --standalone)
-  -i, --include=MODULE   include module (with --standalone)
-      --package=PACKAGE  include a whole package (with --standalone)
+  -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
         "mainprogram=", "creator=", "nib=", "plist=", "link",
         "link-exec", "help", "verbose", "quiet", "argv", "standalone",
         "exclude=", "include=", "package=", "strip", "iconfile=",
-        "lib=", "python=")
+        "lib=", "python=", "semi-standalone")
 
     try:
         options, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
             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'):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.