mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	added command line interface; refactored a bit; little things.
This commit is contained in:
		
							parent
							
								
									7d791240c0
								
							
						
					
					
						commit
						ceeb9627c1
					
				
					 1 changed files with 182 additions and 85 deletions
				
			
		|  | @ -3,31 +3,39 @@ | ||||||
| """\ | """\ | ||||||
| bundlebuilder.py -- Tools to assemble MacOS X (application) bundles. | 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 | MacOS X. BundleBuilder is a general tool, AppBuilder is a subclass | ||||||
| specialized in building application bundles. CocoaAppBuilder is a | specialized in building application bundles. | ||||||
| further specialization of AppBuilder. |  | ||||||
| 
 | 
 | ||||||
| [Bundle|App|CocoaApp]Builder objects are instantiated with a bunch | [Bundle|App]Builder objects are instantiated with a bunch of keyword | ||||||
| of keyword arguments, and have a build() method that will do all the | arguments, and have a build() method that will do all the work. See | ||||||
| work. See the class doc strings for a description of the constructor | the class doc strings for a description of the constructor arguments. | ||||||
| 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: | # XXX Todo: | ||||||
| # - a command line interface, also for use with the buildapp() and |  | ||||||
| #   buildcocoaapp() convenience functions. |  | ||||||
| # - modulefinder support to build standalone apps | # - modulefinder support to build standalone apps | ||||||
|  | # - consider turning this into a distutils extension | ||||||
| # | # | ||||||
| 
 | 
 | ||||||
| __all__ = ["BundleBuilder", "AppBuilder", "CocoaAppBuilder", | __all__ = ["BundleBuilder", "AppBuilder", "buildapp"] | ||||||
| 		"buildapp", "buildcocoaapp"] |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| import sys | import sys | ||||||
| import os, errno, shutil | import os, errno, shutil | ||||||
|  | import getopt | ||||||
| from plistlib import Plist | from plistlib import Plist | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -62,34 +70,43 @@ class BundleBuilder: | ||||||
| 		verbosity: verbosity level, defaults to 1 | 		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", | 			resources=None, files=None, builddir="build", platform="MacOS", | ||||||
| 			symlink=0, verbosity=1): | 			symlink=0, verbosity=1): | ||||||
| 		"""See the class doc string for a description of the arguments.""" | 		"""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: | 		if plist is None: | ||||||
| 			plist = Plist() | 			plist = Plist() | ||||||
|  | 		if resources is None: | ||||||
|  | 			resources = [] | ||||||
|  | 		if files is None: | ||||||
|  | 			files = [] | ||||||
|  | 		self.name = name | ||||||
| 		self.plist = plist | 		self.plist = plist | ||||||
| 		self.type = type | 		self.type = type | ||||||
| 		self.creator = creator | 		self.creator = creator | ||||||
| 		if files is None: |  | ||||||
| 			files = [] |  | ||||||
| 		if resources is None: |  | ||||||
| 			resources = [] |  | ||||||
| 		self.resources = resources | 		self.resources = resources | ||||||
| 		self.files = files | 		self.files = files | ||||||
| 		self.builddir = builddir | 		self.builddir = builddir | ||||||
| 		self.platform = platform | 		self.platform = platform | ||||||
| 		self.symlink = symlink | 		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 | 		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): | 	def build(self): | ||||||
| 		"""Build the bundle.""" | 		"""Build the bundle.""" | ||||||
| 		builddir = self.builddir | 		builddir = self.builddir | ||||||
|  | @ -124,13 +141,8 @@ def _addMetaFiles(self): | ||||||
| 		f.close() | 		f.close() | ||||||
| 		# | 		# | ||||||
| 		# Write Contents/Info.plist | 		# 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") | 		infoplist = pathjoin(contents, "Info.plist") | ||||||
| 		plist.write(infoplist) | 		self.plist.write(infoplist) | ||||||
| 
 | 
 | ||||||
| 	def _copyFiles(self): | 	def _copyFiles(self): | ||||||
| 		files = self.files[:] | 		files = self.files[:] | ||||||
|  | @ -144,6 +156,9 @@ def _copyFiles(self): | ||||||
| 			self.message("Copying files", 1) | 			self.message("Copying files", 1) | ||||||
| 			msg = "Copying" | 			msg = "Copying" | ||||||
| 		for src, dst in files: | 		for src, dst in files: | ||||||
|  | 			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) | 				self.message("%s %s to %s" % (msg, src, dst), 2) | ||||||
| 			dst = pathjoin(self.bundlepath, dst) | 			dst = pathjoin(self.bundlepath, dst) | ||||||
| 			if self.symlink: | 			if self.symlink: | ||||||
|  | @ -153,7 +168,15 @@ def _copyFiles(self): | ||||||
| 
 | 
 | ||||||
| 	def message(self, msg, level=0): | 	def message(self, msg, level=0): | ||||||
| 		if level <= self.verbosity: | 		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 = """\ | mainWrapperTemplate = """\ | ||||||
|  | @ -166,12 +189,14 @@ def message(self, msg, level=0): | ||||||
| mainprogram = os.path.join(resources, "%(mainprogram)s") | mainprogram = os.path.join(resources, "%(mainprogram)s") | ||||||
| assert os.path.exists(mainprogram) | assert os.path.exists(mainprogram) | ||||||
| argv.insert(1, mainprogram) | argv.insert(1, mainprogram) | ||||||
| %(executable)s | os.environ["PYTHONPATH"] = resources | ||||||
|  | %(setpythonhome)s | ||||||
|  | %(setexecutable)s | ||||||
| os.execve(executable, argv, os.environ) | 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): | class AppBuilder(BundleBuilder): | ||||||
| 
 | 
 | ||||||
|  | @ -185,46 +210,59 @@ class AppBuilder(BundleBuilder): | ||||||
| 			specified the executable will be copied to Resources and | 			specified the executable will be copied to Resources and | ||||||
| 			be invoked by the wrapper program mentioned above. Else | 			be invoked by the wrapper program mentioned above. Else | ||||||
| 			it will simply be used as the main executable. | 			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. | 	For the other keyword arguments see the BundleBuilder doc string. | ||||||
| 	""" | 	""" | ||||||
| 
 | 
 | ||||||
| 	def __init__(self, name=None, mainprogram=None, executable=None, | 	def __init__(self, name=None, mainprogram=None, executable=None, | ||||||
| 			**kwargs): | 			nibname=None, **kwargs): | ||||||
| 		"""See the class doc string for a description of the arguments.""" | 		"""See the class doc string for a description of the arguments.""" | ||||||
| 		if mainprogram is None and executable is None: |  | ||||||
| 			raise TypeError, ("must specify either or both of " |  | ||||||
| 					"'executable' and 'mainprogram'") |  | ||||||
| 		if name is not None: |  | ||||||
| 			pass |  | ||||||
| 		elif mainprogram is not None: |  | ||||||
| 			name = os.path.splitext(os.path.basename(mainprogram))[0] |  | ||||||
| 		elif executable is not None: |  | ||||||
| 			name = os.path.splitext(os.path.basename(executable))[0] |  | ||||||
| 		if name[-4:] != ".app": |  | ||||||
| 			name += ".app" |  | ||||||
| 
 |  | ||||||
| 		self.mainprogram = mainprogram | 		self.mainprogram = mainprogram | ||||||
| 		self.executable = executable | 		self.executable = executable | ||||||
| 
 | 		self.nibname = nibname | ||||||
| 		BundleBuilder.__init__(self, name=name, **kwargs) | 		BundleBuilder.__init__(self, name=name, **kwargs) | ||||||
| 
 | 
 | ||||||
| 	def preProcess(self): | 	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 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" | ||||||
| 		self.plist.CFBundleExecutable = self.name | 		self.plist.CFBundleExecutable = self.name | ||||||
|  | 
 | ||||||
|  | 		if self.nibname: | ||||||
|  | 			self.plist.NSMainNibFile = self.nibname | ||||||
|  | 			if not hasattr(self.plist, "NSPrincipalClass"): | ||||||
|  | 				self.plist.NSPrincipalClass = "NSApplication" | ||||||
|  | 
 | ||||||
|  | 		BundleBuilder.setup(self) | ||||||
|  | 
 | ||||||
|  | 	def preProcess(self): | ||||||
|  | 		resdir = pathjoin("Contents", "Resources") | ||||||
| 		if self.executable is not None: | 		if self.executable is not None: | ||||||
| 			if self.mainprogram is None: | 			if self.mainprogram is None: | ||||||
| 				execpath = pathjoin(self.execdir, self.name) | 				execpath = pathjoin(self.execdir, self.name) | ||||||
| 			else: | 			else: | ||||||
| 				execpath = pathjoin(self.resdir, os.path.basename(self.executable)) | 				execpath = pathjoin(resdir, os.path.basename(self.executable)) | ||||||
| 			self.files.append((self.executable, execpath)) | 			self.files.append((self.executable, execpath)) | ||||||
| 			# For execve wrapper | 			# For execve wrapper | ||||||
| 			executable = executableTemplate % os.path.basename(self.executable) | 			setexecutable = setExecutableTemplate % os.path.basename(self.executable) | ||||||
| 		else: | 		else: | ||||||
| 			executable = ""  # XXX for locals() call | 			setexecutable = ""  # XXX for locals() call | ||||||
| 
 | 
 | ||||||
| 		if self.mainprogram is not None: | 		if self.mainprogram is not None: | ||||||
|  | 			setpythonhome = ""  # pythonhomeSnippet if we're making a standalone app | ||||||
| 			mainname = os.path.basename(self.mainprogram) | 			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 | 			# Create execve wrapper | ||||||
| 			mainprogram = self.mainprogram  # XXX for locals() call | 			mainprogram = self.mainprogram  # XXX for locals() call | ||||||
| 			execdir = pathjoin(self.bundlepath, self.execdir) | 			execdir = pathjoin(self.bundlepath, self.execdir) | ||||||
|  | @ -234,22 +272,6 @@ def preProcess(self): | ||||||
| 			os.chmod(mainwrapperpath, 0777) | 			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): | def copy(src, dst, mkdirs=0): | ||||||
| 	"""Copy a file or a directory.""" | 	"""Copy a file or a directory.""" | ||||||
| 	if mkdirs: | 	if mkdirs: | ||||||
|  | @ -287,21 +309,96 @@ def pathjoin(*args): | ||||||
| 	return os.path.join(*args) | 	return os.path.join(*args) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 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 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): | def buildapp(**kwargs): | ||||||
| 	# XXX cmd line argument parsing |  | ||||||
| 	builder = AppBuilder(**kwargs) | 	builder = AppBuilder(**kwargs) | ||||||
| 	builder.build() | 	main(builder) | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def buildcocoaapp(**kwargs): |  | ||||||
| 	# XXX cmd line argument parsing |  | ||||||
| 	builder = CocoaAppBuilder(**kwargs) |  | ||||||
| 	builder.build() |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
| 	# XXX This test is meant to be run in the Examples/TableModel/ folder | 	main() | ||||||
| 	# 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) |  | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Just van Rossum
						Just van Rossum