]> granicus.if.org Git - python/commitdiff
added command line interface; refactored a bit; little things.
authorJust van Rossum <just@letterror.com>
Thu, 21 Nov 2002 23:19:37 +0000 (23:19 +0000)
committerJust van Rossum <just@letterror.com>
Thu, 21 Nov 2002 23:19:37 +0000 (23:19 +0000)
Mac/Lib/bundlebuilder.py

index 319522be8ea0611d081670e677122f5f1a7177b7..ffcda5b1c0f13888605d70afce321d4ea44e81f1 100755 (executable)
@@ -3,31 +3,39 @@
 """\
 bundlebuilder.py -- Tools to assemble MacOS X (application) bundles.
 
-This module contains three classes to build so called "bundles" for
+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. CocoaAppBuilder is a
-further specialization of AppBuilder.
+specialized in building application bundles.
 
-[Bundle|App|CocoaApp]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.
+[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>)
 
 """
 
 #
 # XXX Todo:
-# - a command line interface, also for use with the buildapp() and
-#   buildcocoaapp() convenience functions.
 # - modulefinder support to build standalone apps
+# - consider turning this into a distutils extension
 #
 
-__all__ = ["BundleBuilder", "AppBuilder", "CocoaAppBuilder",
-               "buildapp", "buildcocoaapp"]
+__all__ = ["BundleBuilder", "AppBuilder", "buildapp"]
 
 
 import sys
 import os, errno, shutil
+import getopt
 from plistlib import Plist
 
 
@@ -62,34 +70,43 @@ class BundleBuilder:
                verbosity: verbosity level, defaults to 1
        """
 
-       def __init__(self, name, plist=None, type="APPL", creator="????",
+       def __init__(self, name=None, plist=None, type="APPL", creator="????",
                        resources=None, files=None, builddir="build", platform="MacOS",
                        symlink=0, verbosity=1):
                """See the class doc string for a description of the arguments."""
-               self.name, ext = os.path.splitext(name)
-               if not ext:
-                       ext = ".bundle"
-               self.bundleextension = ext
                if plist is None:
                        plist = Plist()
+               if resources is None:
+                       resources = []
+               if files is None:
+                       files = []
+               self.name = name
                self.plist = plist
                self.type = type
                self.creator = creator
-               if files is None:
-                       files = []
-               if resources is None:
-                       resources = []
                self.resources = resources
                self.files = files
                self.builddir = builddir
                self.platform = platform
                self.symlink = symlink
-               # misc (derived) attributes
-               self.bundlepath = pathjoin(builddir, self.name + self.bundleextension)
-               self.execdir = pathjoin("Contents", platform)
-               self.resdir = pathjoin("Contents", "Resources")
                self.verbosity = verbosity
 
+       def setup(self):
+               self.name, ext = os.path.splitext(self.name)
+               if not ext:
+                       ext = ".bundle"
+               self.bundleextension = ext
+               # misc (derived) attributes
+               self.bundlepath = pathjoin(self.builddir, self.name + self.bundleextension)
+               self.execdir = pathjoin("Contents", self.platform)
+
+               plist = plistDefaults.copy()
+               plist.CFBundleName = self.name
+               plist.CFBundlePackageType = self.type
+               plist.CFBundleSignature = self.creator
+               plist.update(self.plist)
+               self.plist = plist
+
        def build(self):
                """Build the bundle."""
                builddir = self.builddir
@@ -124,13 +141,8 @@ class BundleBuilder:
                f.close()
                #
                # Write Contents/Info.plist
-               plist = plistDefaults.copy()
-               plist.CFBundleName = self.name
-               plist.CFBundlePackageType = self.type
-               plist.CFBundleSignature = self.creator
-               plist.update(self.plist)
                infoplist = pathjoin(contents, "Info.plist")
-               plist.write(infoplist)
+               self.plist.write(infoplist)
 
        def _copyFiles(self):
                files = self.files[:]
@@ -144,7 +156,10 @@ class BundleBuilder:
                        self.message("Copying files", 1)
                        msg = "Copying"
                for src, dst in files:
-                       self.message("%s %s to %s" % (msg, src, dst), 2)
+                       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)
@@ -153,7 +168,15 @@ class BundleBuilder:
 
        def message(self, msg, level=0):
                if level <= self.verbosity:
-                       sys.stderr.write(msg + "\n")
+                       indent = ""
+                       if level > 1:
+                               indent = (level - 1) * "  "
+                       sys.stderr.write(indent + msg + "\n")
+
+       def report(self):
+               # XXX something decent
+               import pprint
+               pprint.pprint(self.__dict__)
 
 
 mainWrapperTemplate = """\
@@ -166,18 +189,20 @@ resources = os.path.join(os.path.dirname(os.path.dirname(argv[0])),
 mainprogram = os.path.join(resources, "%(mainprogram)s")
 assert os.path.exists(mainprogram)
 argv.insert(1, mainprogram)
-%(executable)s
+os.environ["PYTHONPATH"] = resources
+%(setpythonhome)s
+%(setexecutable)s
 os.execve(executable, argv, os.environ)
 """
 
-executableTemplate = "executable = os.path.join(resources, \"%s\")"
-
+setExecutableTemplate = """executable = os.path.join(resources, "%s")"""
+pythonhomeSnippet = """os.environ["home"] = resources"""
 
 class AppBuilder(BundleBuilder):
 
        """This class extends the BundleBuilder constructor with these
        arguments:
-       
+
                mainprogram: 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.)
@@ -185,46 +210,59 @@ class AppBuilder(BundleBuilder):
                        specified the executable will be copied to Resources and
                        be invoked by the wrapper program mentioned above. Else
                        it will simply be used as the main executable.
-       
+               nibname: The name of the main nib, for Cocoa apps. Defaults
+                       to None, but must be specified when building a Cocoa app.
+
        For the other keyword arguments see the BundleBuilder doc string.
        """
 
        def __init__(self, name=None, mainprogram=None, executable=None,
-                       **kwargs):
+                       nibname=None, **kwargs):
                """See the class doc string for a description of the arguments."""
-               if mainprogram is None and executable is None:
+               self.mainprogram = mainprogram
+               self.executable = executable
+               self.nibname = nibname
+               BundleBuilder.__init__(self, name=name, **kwargs)
+
+       def setup(self):
+               if self.mainprogram is None and self.executable is None:
                        raise TypeError, ("must specify either or both of "
                                        "'executable' and 'mainprogram'")
-               if name is not None:
+
+               if self.name is not None:
                        pass
-               elif mainprogram is not None:
-                       name = os.path.splitext(os.path.basename(mainprogram))[0]
+               elif self.mainprogram is not None:
+                       self.name = os.path.splitext(os.path.basename(self.mainprogram))[0]
                elif executable is not None:
-                       name = os.path.splitext(os.path.basename(executable))[0]
-               if name[-4:] != ".app":
-                       name += ".app"
+                       self.name = os.path.splitext(os.path.basename(self.executable))[0]
+               if self.name[-4:] != ".app":
+                       self.name += ".app"
+               self.plist.CFBundleExecutable = self.name
 
-               self.mainprogram = mainprogram
-               self.executable = executable
+               if self.nibname:
+                       self.plist.NSMainNibFile = self.nibname
+                       if not hasattr(self.plist, "NSPrincipalClass"):
+                               self.plist.NSPrincipalClass = "NSApplication"
 
-               BundleBuilder.__init__(self, name=name, **kwargs)
+               BundleBuilder.setup(self)
 
        def preProcess(self):
-               self.plist.CFBundleExecutable = self.name
+               resdir = pathjoin("Contents", "Resources")
                if self.executable is not None:
                        if self.mainprogram is None:
                                execpath = pathjoin(self.execdir, self.name)
                        else:
-                               execpath = pathjoin(self.resdir, os.path.basename(self.executable))
+                               execpath = pathjoin(resdir, os.path.basename(self.executable))
                        self.files.append((self.executable, execpath))
                        # For execve wrapper
-                       executable = executableTemplate % os.path.basename(self.executable)
+                       setexecutable = setExecutableTemplate % os.path.basename(self.executable)
                else:
-                       executable = ""  # XXX for locals() call
+                       setexecutable = ""  # XXX for locals() call
 
                if self.mainprogram is not None:
+                       setpythonhome = ""  # pythonhomeSnippet if we're making a standalone app
                        mainname = os.path.basename(self.mainprogram)
-                       self.files.append((self.mainprogram, pathjoin(self.resdir, mainname)))
+                       self.files.append((self.mainprogram, pathjoin(resdir, mainname)))
                        # Create execve wrapper
                        mainprogram = self.mainprogram  # XXX for locals() call
                        execdir = pathjoin(self.bundlepath, self.execdir)
@@ -234,22 +272,6 @@ class AppBuilder(BundleBuilder):
                        os.chmod(mainwrapperpath, 0777)
 
 
-class CocoaAppBuilder(AppBuilder):
-
-       """Tiny specialization of AppBuilder. It has an extra constructor
-       argument called 'nibname' which defaults to 'MainMenu'. It will
-       set the appropriate fields in the plist.
-       """
-
-       def __init__(self, nibname="MainMenu", **kwargs):
-               """See the class doc string for a description of the arguments."""
-               self.nibname = nibname
-               AppBuilder.__init__(self, **kwargs)
-               self.plist.NSMainNibFile = self.nibname
-               if not hasattr(self.plist, "NSPrincipalClass"):
-                       self.plist.NSPrincipalClass = "NSApplication"
-
-
 def copy(src, dst, mkdirs=0):
        """Copy a file or a directory."""
        if mkdirs:
@@ -287,21 +309,96 @@ def pathjoin(*args):
        return os.path.join(*args)
 
 
-def buildapp(**kwargs):
-       # XXX cmd line argument parsing
-       builder = AppBuilder(**kwargs)
-       builder.build()
+cmdline_doc = """\
+Usage:
+  python [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
+  -e, --executable=FILE  the executable to be used
+  -m, --mainprogram=FILE the Python main program
+  -p, --plist=FILE       .plist file (default: generate one)
+      --nib=NAME         main nib name
+  -c, --creator=CCCC     4-char creator code (default: '????')
+  -l, --link             symlink files/folder instead of copying them
+  -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 buildcocoaapp(**kwargs):
-       # XXX cmd line argument parsing
-       builder = CocoaAppBuilder(**kwargs)
-       builder.build()
+def main(builder=None):
+       if builder is None:
+               builder = AppBuilder(verbosity=1)
+
+       shortopts = "b:n:r:e:m:c:plhvq"
+       longopts = ("builddir=", "name=", "resource=", "executable=",
+               "mainprogram=", "creator=", "nib=", "plist=", "link", "help",
+               "verbose", "quiet")
+
+       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(arg)
+               elif opt in ('-e', '--executable'):
+                       builder.executable = arg
+               elif opt in ('-m', '--mainprogram'):
+                       builder.mainprogram = arg
+               elif opt in ('-c', '--creator'):
+                       builder.creator = 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 in ('-h', '--help'):
+                       usage()
+               elif opt in ('-v', '--verbose'):
+                       builder.verbosity += 1
+               elif opt in ('-q', '--quiet'):
+                       builder.verbosity -= 1
+
+       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__":
-       # XXX This test is meant to be run in the Examples/TableModel/ folder
-       # of the pyobj project... It will go as soon as I've written a proper
-       # main program.
-       buildcocoaapp(mainprogram="TableModel.py",
-               resources=["English.lproj", "nibwrapper.py"], verbosity=4)
+       main()