| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  | #!/usr/bin/env python | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | """buildpkg.py -- Build OS X packages for Apple's Installer.app.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This is an experimental command-line tool for building packages to be | 
					
						
							|  |  |  | installed with the Mac OS X Installer.app application.  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | It is much inspired by Apple's GUI tool called PackageMaker.app, that  | 
					
						
							|  |  |  | seems to be part of the OS X developer tools installed in the folder  | 
					
						
							|  |  |  | /Developer/Applications. But apparently there are other free tools to  | 
					
						
							|  |  |  | do the same thing which are also named PackageMaker like Brian Hill's  | 
					
						
							|  |  |  | one:  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   http://personalpages.tds.net/~brian_hill/packagemaker.html | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Beware of the multi-package features of Installer.app (which are not  | 
					
						
							|  |  |  | yet supported here) that can potentially screw-up your installation  | 
					
						
							|  |  |  | and are discussed in these articles on Stepwise: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   http://www.stepwise.com/Articles/Technical/Packages/InstallerWoes.html | 
					
						
							|  |  |  |   http://www.stepwise.com/Articles/Technical/Packages/InstallerOnX.html | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Beside using the PackageMaker class directly, by importing it inside  | 
					
						
							|  |  |  | another module, say, there are additional ways of using this module: | 
					
						
							|  |  |  | the top-level buildPackage() function provides a shortcut to the same  | 
					
						
							|  |  |  | feature and is also called when using this module from the command- | 
					
						
							|  |  |  | line. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     **************************************************************** | 
					
						
							|  |  |  |     NOTE: For now you should be able to run this even on a non-OS X  | 
					
						
							|  |  |  |           system and get something similar to a package, but without | 
					
						
							|  |  |  |           the real archive (needs pax) and bom files (needs mkbom)  | 
					
						
							|  |  |  |           inside! This is only for providing a chance for testing to  | 
					
						
							|  |  |  |           folks without OS X. | 
					
						
							|  |  |  |     **************************************************************** | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | TODO: | 
					
						
							|  |  |  |   - test pre-process and post-process scripts (Python ones?) | 
					
						
							|  |  |  |   - handle multi-volume packages (?) | 
					
						
							|  |  |  |   - integrate into distutils (?) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Dinu C. Gherman,  | 
					
						
							|  |  |  | gherman@europemail.com | 
					
						
							|  |  |  | November 2001 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | !! USE AT YOUR OWN RISK !! | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | __version__ = 0.2 | 
					
						
							|  |  |  | __license__ = "FreeBSD" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import os, sys, glob, fnmatch, shutil, string, copy, getopt | 
					
						
							|  |  |  | from os.path import basename, dirname, join, islink, isdir, isfile | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  | Error = "buildpkg.Error" | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | PKG_INFO_FIELDS = """\
 | 
					
						
							|  |  |  | Title | 
					
						
							|  |  |  | Version | 
					
						
							|  |  |  | Description | 
					
						
							|  |  |  | DefaultLocation | 
					
						
							|  |  |  | Diskname | 
					
						
							|  |  |  | DeleteWarning | 
					
						
							|  |  |  | NeedsAuthorization | 
					
						
							|  |  |  | DisableStop | 
					
						
							|  |  |  | UseUserMask | 
					
						
							|  |  |  | Application | 
					
						
							|  |  |  | Relocatable | 
					
						
							|  |  |  | Required | 
					
						
							|  |  |  | InstallOnly | 
					
						
							|  |  |  | RequiresReboot | 
					
						
							| 
									
										
										
										
											2003-02-01 10:07:28 +00:00
										 |  |  | RootVolumeOnly | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  | InstallFat\ | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Helpers | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Convenience class, as suggested by /F. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class GlobDirectoryWalker: | 
					
						
							|  |  |  |     "A forward iterator that traverses files in a directory tree." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, directory, pattern="*"): | 
					
						
							|  |  |  |         self.stack = [directory] | 
					
						
							|  |  |  |         self.pattern = pattern | 
					
						
							|  |  |  |         self.files = [] | 
					
						
							|  |  |  |         self.index = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __getitem__(self, index): | 
					
						
							|  |  |  |         while 1: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 file = self.files[self.index] | 
					
						
							|  |  |  |                 self.index = self.index + 1 | 
					
						
							|  |  |  |             except IndexError: | 
					
						
							|  |  |  |                 # pop next directory from stack | 
					
						
							|  |  |  |                 self.directory = self.stack.pop() | 
					
						
							|  |  |  |                 self.files = os.listdir(self.directory) | 
					
						
							|  |  |  |                 self.index = 0 | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 # got a filename | 
					
						
							|  |  |  |                 fullname = join(self.directory, file) | 
					
						
							|  |  |  |                 if isdir(fullname) and not islink(fullname): | 
					
						
							|  |  |  |                     self.stack.append(fullname) | 
					
						
							|  |  |  |                 if fnmatch.fnmatch(file, self.pattern): | 
					
						
							|  |  |  |                     return fullname | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # The real thing | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PackageMaker: | 
					
						
							|  |  |  |     """A class to generate packages for Mac OS X.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     This is intended to create OS X packages (with extension .pkg) | 
					
						
							|  |  |  |     containing archives of arbitrary files that the Installer.app  | 
					
						
							|  |  |  |     will be able to handle. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     As of now, PackageMaker instances need to be created with the  | 
					
						
							|  |  |  |     title, version and description of the package to be built.  | 
					
						
							|  |  |  |     The package is built after calling the instance method  | 
					
						
							|  |  |  |     build(root, **options). It has the same name as the constructor's  | 
					
						
							|  |  |  |     title argument plus a '.pkg' extension and is located in the same  | 
					
						
							|  |  |  |     parent folder that contains the root folder. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     E.g. this will create a package folder /my/space/distutils.pkg/: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       pm = PackageMaker("distutils", "1.0.2", "Python distutils.") | 
					
						
							|  |  |  |       pm.build("/my/space/distutils") | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     packageInfoDefaults = { | 
					
						
							|  |  |  |         'Title': None, | 
					
						
							|  |  |  |         'Version': None, | 
					
						
							|  |  |  |         'Description': '', | 
					
						
							|  |  |  |         'DefaultLocation': '/', | 
					
						
							|  |  |  |         'Diskname': '(null)', | 
					
						
							|  |  |  |         'DeleteWarning': '', | 
					
						
							|  |  |  |         'NeedsAuthorization': 'NO', | 
					
						
							|  |  |  |         'DisableStop': 'NO', | 
					
						
							|  |  |  |         'UseUserMask': 'YES', | 
					
						
							|  |  |  |         'Application': 'NO', | 
					
						
							|  |  |  |         'Relocatable': 'YES', | 
					
						
							|  |  |  |         'Required': 'NO', | 
					
						
							|  |  |  |         'InstallOnly': 'NO', | 
					
						
							|  |  |  |         'RequiresReboot': 'NO', | 
					
						
							| 
									
										
										
										
											2003-02-01 10:07:28 +00:00
										 |  |  |         'RootVolumeOnly' : 'NO', | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         'InstallFat': 'NO'} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, title, version, desc): | 
					
						
							|  |  |  |         "Init. with mandatory title/version/description arguments." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         info = {"Title": title, "Version": version, "Description": desc} | 
					
						
							|  |  |  |         self.packageInfo = copy.deepcopy(self.packageInfoDefaults) | 
					
						
							|  |  |  |         self.packageInfo.update(info) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         # variables set later | 
					
						
							|  |  |  |         self.packageRootFolder = None | 
					
						
							|  |  |  |         self.packageResourceFolder = None | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         self.sourceFolder = None | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         self.resourceFolder = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def build(self, root, resources=None, **options): | 
					
						
							|  |  |  |         """Create a package for some given root folder.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         With no 'resources' argument set it is assumed to be the same  | 
					
						
							|  |  |  |         as the root directory. Option items replace the default ones  | 
					
						
							|  |  |  |         in the package info. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # set folder attributes | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         self.sourceFolder = root | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         if resources == None: | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |             self.resourceFolder = root | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.resourceFolder = resources | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # replace default option settings with user ones if provided | 
					
						
							|  |  |  |         fields = self. packageInfoDefaults.keys() | 
					
						
							|  |  |  |         for k, v in options.items(): | 
					
						
							|  |  |  |             if k in fields: | 
					
						
							|  |  |  |                 self.packageInfo[k] = v | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |             elif not k in ["OutputDir"]: | 
					
						
							|  |  |  |                 raise Error, "Unknown package option: %s" % k | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         # Check where we should leave the output. Default is current directory | 
					
						
							|  |  |  |         outputdir = options.get("OutputDir", os.getcwd()) | 
					
						
							|  |  |  |         packageName = self.packageInfo["Title"] | 
					
						
							|  |  |  |         self.PackageRootFolder = os.path.join(outputdir, packageName + ".pkg") | 
					
						
							|  |  |  |   | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         # do what needs to be done | 
					
						
							|  |  |  |         self._makeFolders() | 
					
						
							|  |  |  |         self._addInfo() | 
					
						
							|  |  |  |         self._addBom() | 
					
						
							|  |  |  |         self._addArchive() | 
					
						
							|  |  |  |         self._addResources() | 
					
						
							|  |  |  |         self._addSizes() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _makeFolders(self): | 
					
						
							|  |  |  |         "Create package folder structure." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Not sure if the package name should contain the version or not... | 
					
						
							|  |  |  |         # packageName = "%s-%s" % (self.packageInfo["Title"],  | 
					
						
							|  |  |  |         #                          self.packageInfo["Version"]) # ?? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         contFolder = join(self.PackageRootFolder, "Contents") | 
					
						
							|  |  |  |         self.packageResourceFolder = join(contFolder, "Resources") | 
					
						
							|  |  |  |         os.mkdir(self.PackageRootFolder) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         os.mkdir(contFolder) | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         os.mkdir(self.packageResourceFolder) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def _addInfo(self): | 
					
						
							|  |  |  |         "Write .info file containing installing options." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Not sure if options in PKG_INFO_FIELDS are complete... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         info = "" | 
					
						
							|  |  |  |         for f in string.split(PKG_INFO_FIELDS, "\n"): | 
					
						
							|  |  |  |             info = info + "%s %%(%s)s\n" % (f, f) | 
					
						
							|  |  |  |         info = info % self.packageInfo | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         base = self.packageInfo["Title"] + ".info" | 
					
						
							|  |  |  |         path = join(self.packageResourceFolder, base) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         f = open(path, "w") | 
					
						
							|  |  |  |         f.write(info) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _addBom(self): | 
					
						
							|  |  |  |         "Write .bom file containing 'Bill of Materials'." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Currently ignores if the 'mkbom' tool is not available. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |             base = self.packageInfo["Title"] + ".bom" | 
					
						
							|  |  |  |             bomPath = join(self.packageResourceFolder, base) | 
					
						
							|  |  |  |             cmd = "mkbom %s %s" % (self.sourceFolder, bomPath) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |             res = os.system(cmd) | 
					
						
							|  |  |  |         except: | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _addArchive(self): | 
					
						
							|  |  |  |         "Write .pax.gz file, a compressed archive using pax/gzip." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Currently ignores if the 'pax' tool is not available. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         cwd = os.getcwd() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         # create archive | 
					
						
							|  |  |  |         os.chdir(self.sourceFolder) | 
					
						
							|  |  |  |         base = basename(self.packageInfo["Title"]) + ".pax" | 
					
						
							|  |  |  |         self.archPath = join(self.packageResourceFolder, base) | 
					
						
							|  |  |  |         cmd = "pax -w -f %s %s" % (self.archPath, ".") | 
					
						
							|  |  |  |         res = os.system(cmd) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         # compress archive | 
					
						
							|  |  |  |         cmd = "gzip %s" % self.archPath | 
					
						
							|  |  |  |         res = os.system(cmd) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         os.chdir(cwd) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _addResources(self): | 
					
						
							|  |  |  |         "Add Welcome/ReadMe/License files, .lproj folders and scripts." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Currently we just copy everything that matches the allowed  | 
					
						
							|  |  |  |         # filenames. So, it's left to Installer.app to deal with the  | 
					
						
							|  |  |  |         # same file available in multiple formats... | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         if not self.resourceFolder: | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # find candidate resource files (txt html rtf rtfd/ or lproj/) | 
					
						
							|  |  |  |         allFiles = [] | 
					
						
							|  |  |  |         for pat in string.split("*.txt *.html *.rtf *.rtfd *.lproj", " "): | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |             pattern = join(self.resourceFolder, pat) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |             allFiles = allFiles + glob.glob(pattern) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # find pre-process and post-process scripts | 
					
						
							| 
									
										
										
										
											2003-02-01 10:07:28 +00:00
										 |  |  |         # naming convention: packageName.{pre,post}_{upgrade,install} | 
					
						
							|  |  |  |         # Alternatively the filenames can be {pre,post}_{upgrade,install} | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         # in which case we prepend the package name | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         packageName = self.packageInfo["Title"] | 
					
						
							| 
									
										
										
										
											2003-02-01 10:07:28 +00:00
										 |  |  |         for pat in ("*upgrade", "*install", "*flight"): | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |             pattern = join(self.resourceFolder, packageName + pat) | 
					
						
							| 
									
										
										
										
											2003-02-01 10:07:28 +00:00
										 |  |  |             pattern2 = join(self.resourceFolder, pat) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |             allFiles = allFiles + glob.glob(pattern) | 
					
						
							| 
									
										
										
										
											2003-02-01 10:07:28 +00:00
										 |  |  |             allFiles = allFiles + glob.glob(pattern2) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # check name patterns | 
					
						
							|  |  |  |         files = [] | 
					
						
							|  |  |  |         for f in allFiles: | 
					
						
							|  |  |  |             for s in ("Welcome", "License", "ReadMe"): | 
					
						
							|  |  |  |                 if string.find(basename(f), s) == 0: | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |                     files.append((f, f)) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |             if f[-6:] == ".lproj": | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |                 files.append((f, f)) | 
					
						
							| 
									
										
										
										
											2003-02-01 10:07:28 +00:00
										 |  |  |             elif basename(f) in ["pre_upgrade", "pre_install", "post_upgrade", "post_install"]: | 
					
						
							|  |  |  |                 files.append((f, packageName+"."+basename(f))) | 
					
						
							|  |  |  |             elif basename(f) in ["preflight", "postflight"]: | 
					
						
							|  |  |  |                 files.append((f, f)) | 
					
						
							|  |  |  |             elif f[-8:] == "_upgrade": | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |                 files.append((f,f)) | 
					
						
							| 
									
										
										
										
											2003-02-01 10:07:28 +00:00
										 |  |  |             elif f[-8:] == "_install": | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |                 files.append((f,f)) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # copy files | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         for src, dst in files: | 
					
						
							| 
									
										
										
										
											2003-02-01 10:07:28 +00:00
										 |  |  |             src = basename(src) | 
					
						
							|  |  |  |             dst = basename(dst) | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |             f = join(self.resourceFolder, src) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |             if isfile(f): | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |                 shutil.copy(f, os.path.join(self.packageResourceFolder, dst)) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |             elif isdir(f): | 
					
						
							|  |  |  |                 # special case for .rtfd and .lproj folders... | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |                 d = join(self.packageResourceFolder, dst) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |                 os.mkdir(d) | 
					
						
							|  |  |  |                 files = GlobDirectoryWalker(f) | 
					
						
							|  |  |  |                 for file in files: | 
					
						
							|  |  |  |                     shutil.copy(file, d) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _addSizes(self): | 
					
						
							|  |  |  |         "Write .sizes file with info about number and size of files." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Not sure if this is correct, but 'installedSize' and  | 
					
						
							|  |  |  |         # 'zippedSize' are now in Bytes. Maybe blocks are needed?  | 
					
						
							|  |  |  |         # Well, Installer.app doesn't seem to care anyway, saying  | 
					
						
							|  |  |  |         # the installation needs 100+ MB... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         numFiles = 0 | 
					
						
							|  |  |  |         installedSize = 0 | 
					
						
							|  |  |  |         zippedSize = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         files = GlobDirectoryWalker(self.sourceFolder) | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         for f in files: | 
					
						
							|  |  |  |             numFiles = numFiles + 1 | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |             installedSize = installedSize + os.lstat(f)[6] | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |             zippedSize = os.stat(self.archPath+ ".gz")[6] | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         except OSError: # ignore error  | 
					
						
							|  |  |  |             pass | 
					
						
							| 
									
										
										
										
											2002-09-06 21:55:13 +00:00
										 |  |  |         base = self.packageInfo["Title"] + ".sizes" | 
					
						
							|  |  |  |         f = open(join(self.packageResourceFolder, base), "w") | 
					
						
							|  |  |  |         format = "NumFiles %d\nInstalledSize %d\nCompressedSize %d\n" | 
					
						
							| 
									
										
										
										
											2002-09-06 19:47:49 +00:00
										 |  |  |         f.write(format % (numFiles, installedSize, zippedSize)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Shortcut function interface | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def buildPackage(*args, **options): | 
					
						
							|  |  |  |     "A Shortcut function for building a package." | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     o = options | 
					
						
							|  |  |  |     title, version, desc = o["Title"], o["Version"], o["Description"] | 
					
						
							|  |  |  |     pm = PackageMaker(title, version, desc) | 
					
						
							|  |  |  |     apply(pm.build, list(args), options) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Tests | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test0(): | 
					
						
							|  |  |  |     "Vanilla test for the distutils distribution." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pm = PackageMaker("distutils2", "1.0.2", "Python distutils package.") | 
					
						
							|  |  |  |     pm.build("/Users/dinu/Desktop/distutils2") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test1(): | 
					
						
							|  |  |  |     "Test for the reportlab distribution with modified options." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pm = PackageMaker("reportlab", "1.10",  | 
					
						
							|  |  |  |                       "ReportLab's Open Source PDF toolkit.") | 
					
						
							|  |  |  |     pm.build(root="/Users/dinu/Desktop/reportlab",  | 
					
						
							|  |  |  |              DefaultLocation="/Applications/ReportLab", | 
					
						
							|  |  |  |              Relocatable="YES") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test2(): | 
					
						
							|  |  |  |     "Shortcut test for the reportlab distribution with modified options." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     buildPackage( | 
					
						
							|  |  |  |         "/Users/dinu/Desktop/reportlab",  | 
					
						
							|  |  |  |         Title="reportlab",  | 
					
						
							|  |  |  |         Version="1.10",  | 
					
						
							|  |  |  |         Description="ReportLab's Open Source PDF toolkit.", | 
					
						
							|  |  |  |         DefaultLocation="/Applications/ReportLab", | 
					
						
							|  |  |  |         Relocatable="YES") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Command-line interface | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def printUsage(): | 
					
						
							|  |  |  |     "Print usage message." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     format = "Usage: %s <opts1> [<opts2>] <root> [<resources>]" | 
					
						
							|  |  |  |     print format % basename(sys.argv[0]) | 
					
						
							|  |  |  |     print | 
					
						
							|  |  |  |     print "       with arguments:" | 
					
						
							|  |  |  |     print "           (mandatory) root:         the package root folder" | 
					
						
							|  |  |  |     print "           (optional)  resources:    the package resources folder" | 
					
						
							|  |  |  |     print | 
					
						
							|  |  |  |     print "       and options:" | 
					
						
							|  |  |  |     print "           (mandatory) opts1:" | 
					
						
							|  |  |  |     mandatoryKeys = string.split("Title Version Description", " ") | 
					
						
							|  |  |  |     for k in mandatoryKeys: | 
					
						
							|  |  |  |         print "               --%s" % k | 
					
						
							|  |  |  |     print "           (optional) opts2: (with default values)" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pmDefaults = PackageMaker.packageInfoDefaults | 
					
						
							|  |  |  |     optionalKeys = pmDefaults.keys() | 
					
						
							|  |  |  |     for k in mandatoryKeys: | 
					
						
							|  |  |  |         optionalKeys.remove(k) | 
					
						
							|  |  |  |     optionalKeys.sort() | 
					
						
							|  |  |  |     maxKeyLen = max(map(len, optionalKeys)) | 
					
						
							|  |  |  |     for k in optionalKeys: | 
					
						
							|  |  |  |         format = "               --%%s:%s %%s" | 
					
						
							|  |  |  |         format = format % (" " * (maxKeyLen-len(k))) | 
					
						
							|  |  |  |         print format % (k, repr(pmDefaults[k])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def main(): | 
					
						
							|  |  |  |     "Command-line interface." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     shortOpts = "" | 
					
						
							|  |  |  |     keys = PackageMaker.packageInfoDefaults.keys() | 
					
						
							|  |  |  |     longOpts = map(lambda k: k+"=", keys) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         opts, args = getopt.getopt(sys.argv[1:], shortOpts, longOpts) | 
					
						
							|  |  |  |     except getopt.GetoptError, details: | 
					
						
							|  |  |  |         print details | 
					
						
							|  |  |  |         printUsage() | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     optsDict = {} | 
					
						
							|  |  |  |     for k, v in opts: | 
					
						
							|  |  |  |         optsDict[k[2:]] = v | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ok = optsDict.keys() | 
					
						
							|  |  |  |     if not (1 <= len(args) <= 2): | 
					
						
							|  |  |  |         print "No argument given!" | 
					
						
							|  |  |  |     elif not ("Title" in ok and \ | 
					
						
							|  |  |  |               "Version" in ok and \ | 
					
						
							|  |  |  |               "Description" in ok): | 
					
						
							|  |  |  |         print "Missing mandatory option!" | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         apply(buildPackage, args, optsDict) | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     printUsage() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # sample use: | 
					
						
							|  |  |  |     # buildpkg.py --Title=distutils \ | 
					
						
							|  |  |  |     #             --Version=1.0.2 \ | 
					
						
							|  |  |  |     #             --Description="Python distutils package." \ | 
					
						
							|  |  |  |     #             /Users/dinu/Desktop/distutils | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     main() |