symbian-qemu-0.9.1-12/python-2.6.1/Mac/scripts/buildpkg.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 #!/usr/bin/env python
       
     2 
       
     3 """buildpkg.py -- Build OS X packages for Apple's Installer.app.
       
     4 
       
     5 This is an experimental command-line tool for building packages to be
       
     6 installed with the Mac OS X Installer.app application.
       
     7 
       
     8 It is much inspired by Apple's GUI tool called PackageMaker.app, that
       
     9 seems to be part of the OS X developer tools installed in the folder
       
    10 /Developer/Applications. But apparently there are other free tools to
       
    11 do the same thing which are also named PackageMaker like Brian Hill's
       
    12 one:
       
    13 
       
    14   http://personalpages.tds.net/~brian_hill/packagemaker.html
       
    15 
       
    16 Beware of the multi-package features of Installer.app (which are not
       
    17 yet supported here) that can potentially screw-up your installation
       
    18 and are discussed in these articles on Stepwise:
       
    19 
       
    20   http://www.stepwise.com/Articles/Technical/Packages/InstallerWoes.html
       
    21   http://www.stepwise.com/Articles/Technical/Packages/InstallerOnX.html
       
    22 
       
    23 Beside using the PackageMaker class directly, by importing it inside
       
    24 another module, say, there are additional ways of using this module:
       
    25 the top-level buildPackage() function provides a shortcut to the same
       
    26 feature and is also called when using this module from the command-
       
    27 line.
       
    28 
       
    29     ****************************************************************
       
    30     NOTE: For now you should be able to run this even on a non-OS X
       
    31           system and get something similar to a package, but without
       
    32           the real archive (needs pax) and bom files (needs mkbom)
       
    33           inside! This is only for providing a chance for testing to
       
    34           folks without OS X.
       
    35     ****************************************************************
       
    36 
       
    37 TODO:
       
    38   - test pre-process and post-process scripts (Python ones?)
       
    39   - handle multi-volume packages (?)
       
    40   - integrate into distutils (?)
       
    41 
       
    42 Dinu C. Gherman,
       
    43 gherman@europemail.com
       
    44 November 2001
       
    45 
       
    46 !! USE AT YOUR OWN RISK !!
       
    47 """
       
    48 
       
    49 __version__ = 0.2
       
    50 __license__ = "FreeBSD"
       
    51 
       
    52 
       
    53 import os, sys, glob, fnmatch, shutil, string, copy, getopt
       
    54 from os.path import basename, dirname, join, islink, isdir, isfile
       
    55 
       
    56 Error = "buildpkg.Error"
       
    57 
       
    58 PKG_INFO_FIELDS = """\
       
    59 Title
       
    60 Version
       
    61 Description
       
    62 DefaultLocation
       
    63 DeleteWarning
       
    64 NeedsAuthorization
       
    65 DisableStop
       
    66 UseUserMask
       
    67 Application
       
    68 Relocatable
       
    69 Required
       
    70 InstallOnly
       
    71 RequiresReboot
       
    72 RootVolumeOnly
       
    73 LongFilenames
       
    74 LibrarySubdirectory
       
    75 AllowBackRev
       
    76 OverwritePermissions
       
    77 InstallFat\
       
    78 """
       
    79 
       
    80 ######################################################################
       
    81 # Helpers
       
    82 ######################################################################
       
    83 
       
    84 # Convenience class, as suggested by /F.
       
    85 
       
    86 class GlobDirectoryWalker:
       
    87     "A forward iterator that traverses files in a directory tree."
       
    88 
       
    89     def __init__(self, directory, pattern="*"):
       
    90         self.stack = [directory]
       
    91         self.pattern = pattern
       
    92         self.files = []
       
    93         self.index = 0
       
    94 
       
    95 
       
    96     def __getitem__(self, index):
       
    97         while 1:
       
    98             try:
       
    99                 file = self.files[self.index]
       
   100                 self.index = self.index + 1
       
   101             except IndexError:
       
   102                 # pop next directory from stack
       
   103                 self.directory = self.stack.pop()
       
   104                 self.files = os.listdir(self.directory)
       
   105                 self.index = 0
       
   106             else:
       
   107                 # got a filename
       
   108                 fullname = join(self.directory, file)
       
   109                 if isdir(fullname) and not islink(fullname):
       
   110                     self.stack.append(fullname)
       
   111                 if fnmatch.fnmatch(file, self.pattern):
       
   112                     return fullname
       
   113 
       
   114 
       
   115 ######################################################################
       
   116 # The real thing
       
   117 ######################################################################
       
   118 
       
   119 class PackageMaker:
       
   120     """A class to generate packages for Mac OS X.
       
   121 
       
   122     This is intended to create OS X packages (with extension .pkg)
       
   123     containing archives of arbitrary files that the Installer.app
       
   124     will be able to handle.
       
   125 
       
   126     As of now, PackageMaker instances need to be created with the
       
   127     title, version and description of the package to be built.
       
   128     The package is built after calling the instance method
       
   129     build(root, **options). It has the same name as the constructor's
       
   130     title argument plus a '.pkg' extension and is located in the same
       
   131     parent folder that contains the root folder.
       
   132 
       
   133     E.g. this will create a package folder /my/space/distutils.pkg/:
       
   134 
       
   135       pm = PackageMaker("distutils", "1.0.2", "Python distutils.")
       
   136       pm.build("/my/space/distutils")
       
   137     """
       
   138 
       
   139     packageInfoDefaults = {
       
   140         'Title': None,
       
   141         'Version': None,
       
   142         'Description': '',
       
   143         'DefaultLocation': '/',
       
   144         'DeleteWarning': '',
       
   145         'NeedsAuthorization': 'NO',
       
   146         'DisableStop': 'NO',
       
   147         'UseUserMask': 'YES',
       
   148         'Application': 'NO',
       
   149         'Relocatable': 'YES',
       
   150         'Required': 'NO',
       
   151         'InstallOnly': 'NO',
       
   152         'RequiresReboot': 'NO',
       
   153         'RootVolumeOnly' : 'NO',
       
   154         'InstallFat': 'NO',
       
   155         'LongFilenames': 'YES',
       
   156         'LibrarySubdirectory': 'Standard',
       
   157         'AllowBackRev': 'YES',
       
   158         'OverwritePermissions': 'NO',
       
   159         }
       
   160 
       
   161 
       
   162     def __init__(self, title, version, desc):
       
   163         "Init. with mandatory title/version/description arguments."
       
   164 
       
   165         info = {"Title": title, "Version": version, "Description": desc}
       
   166         self.packageInfo = copy.deepcopy(self.packageInfoDefaults)
       
   167         self.packageInfo.update(info)
       
   168 
       
   169         # variables set later
       
   170         self.packageRootFolder = None
       
   171         self.packageResourceFolder = None
       
   172         self.sourceFolder = None
       
   173         self.resourceFolder = None
       
   174 
       
   175 
       
   176     def build(self, root, resources=None, **options):
       
   177         """Create a package for some given root folder.
       
   178 
       
   179         With no 'resources' argument set it is assumed to be the same
       
   180         as the root directory. Option items replace the default ones
       
   181         in the package info.
       
   182         """
       
   183 
       
   184         # set folder attributes
       
   185         self.sourceFolder = root
       
   186         if resources is None:
       
   187             self.resourceFolder = root
       
   188         else:
       
   189             self.resourceFolder = resources
       
   190 
       
   191         # replace default option settings with user ones if provided
       
   192         fields = self. packageInfoDefaults.keys()
       
   193         for k, v in options.items():
       
   194             if k in fields:
       
   195                 self.packageInfo[k] = v
       
   196             elif not k in ["OutputDir"]:
       
   197                 raise Error, "Unknown package option: %s" % k
       
   198 
       
   199         # Check where we should leave the output. Default is current directory
       
   200         outputdir = options.get("OutputDir", os.getcwd())
       
   201         packageName = self.packageInfo["Title"]
       
   202         self.PackageRootFolder = os.path.join(outputdir, packageName + ".pkg")
       
   203 
       
   204         # do what needs to be done
       
   205         self._makeFolders()
       
   206         self._addInfo()
       
   207         self._addBom()
       
   208         self._addArchive()
       
   209         self._addResources()
       
   210         self._addSizes()
       
   211         self._addLoc()
       
   212 
       
   213 
       
   214     def _makeFolders(self):
       
   215         "Create package folder structure."
       
   216 
       
   217         # Not sure if the package name should contain the version or not...
       
   218         # packageName = "%s-%s" % (self.packageInfo["Title"],
       
   219         #                          self.packageInfo["Version"]) # ??
       
   220 
       
   221         contFolder = join(self.PackageRootFolder, "Contents")
       
   222         self.packageResourceFolder = join(contFolder, "Resources")
       
   223         os.mkdir(self.PackageRootFolder)
       
   224         os.mkdir(contFolder)
       
   225         os.mkdir(self.packageResourceFolder)
       
   226 
       
   227     def _addInfo(self):
       
   228         "Write .info file containing installing options."
       
   229 
       
   230         # Not sure if options in PKG_INFO_FIELDS are complete...
       
   231 
       
   232         info = ""
       
   233         for f in string.split(PKG_INFO_FIELDS, "\n"):
       
   234             if self.packageInfo.has_key(f):
       
   235                 info = info + "%s %%(%s)s\n" % (f, f)
       
   236         info = info % self.packageInfo
       
   237         base = self.packageInfo["Title"] + ".info"
       
   238         path = join(self.packageResourceFolder, base)
       
   239         f = open(path, "w")
       
   240         f.write(info)
       
   241 
       
   242 
       
   243     def _addBom(self):
       
   244         "Write .bom file containing 'Bill of Materials'."
       
   245 
       
   246         # Currently ignores if the 'mkbom' tool is not available.
       
   247 
       
   248         try:
       
   249             base = self.packageInfo["Title"] + ".bom"
       
   250             bomPath = join(self.packageResourceFolder, base)
       
   251             cmd = "mkbom %s %s" % (self.sourceFolder, bomPath)
       
   252             res = os.system(cmd)
       
   253         except:
       
   254             pass
       
   255 
       
   256 
       
   257     def _addArchive(self):
       
   258         "Write .pax.gz file, a compressed archive using pax/gzip."
       
   259 
       
   260         # Currently ignores if the 'pax' tool is not available.
       
   261 
       
   262         cwd = os.getcwd()
       
   263 
       
   264         # create archive
       
   265         os.chdir(self.sourceFolder)
       
   266         base = basename(self.packageInfo["Title"]) + ".pax"
       
   267         self.archPath = join(self.packageResourceFolder, base)
       
   268         cmd = "pax -w -f %s %s" % (self.archPath, ".")
       
   269         res = os.system(cmd)
       
   270 
       
   271         # compress archive
       
   272         cmd = "gzip %s" % self.archPath
       
   273         res = os.system(cmd)
       
   274         os.chdir(cwd)
       
   275 
       
   276 
       
   277     def _addResources(self):
       
   278         "Add Welcome/ReadMe/License files, .lproj folders and scripts."
       
   279 
       
   280         # Currently we just copy everything that matches the allowed
       
   281         # filenames. So, it's left to Installer.app to deal with the
       
   282         # same file available in multiple formats...
       
   283 
       
   284         if not self.resourceFolder:
       
   285             return
       
   286 
       
   287         # find candidate resource files (txt html rtf rtfd/ or lproj/)
       
   288         allFiles = []
       
   289         for pat in string.split("*.txt *.html *.rtf *.rtfd *.lproj", " "):
       
   290             pattern = join(self.resourceFolder, pat)
       
   291             allFiles = allFiles + glob.glob(pattern)
       
   292 
       
   293         # find pre-process and post-process scripts
       
   294         # naming convention: packageName.{pre,post}_{upgrade,install}
       
   295         # Alternatively the filenames can be {pre,post}_{upgrade,install}
       
   296         # in which case we prepend the package name
       
   297         packageName = self.packageInfo["Title"]
       
   298         for pat in ("*upgrade", "*install", "*flight"):
       
   299             pattern = join(self.resourceFolder, packageName + pat)
       
   300             pattern2 = join(self.resourceFolder, pat)
       
   301             allFiles = allFiles + glob.glob(pattern)
       
   302             allFiles = allFiles + glob.glob(pattern2)
       
   303 
       
   304         # check name patterns
       
   305         files = []
       
   306         for f in allFiles:
       
   307             for s in ("Welcome", "License", "ReadMe"):
       
   308                 if string.find(basename(f), s) == 0:
       
   309                     files.append((f, f))
       
   310             if f[-6:] == ".lproj":
       
   311                 files.append((f, f))
       
   312             elif basename(f) in ["pre_upgrade", "pre_install", "post_upgrade", "post_install"]:
       
   313                 files.append((f, packageName+"."+basename(f)))
       
   314             elif basename(f) in ["preflight", "postflight"]:
       
   315                 files.append((f, f))
       
   316             elif f[-8:] == "_upgrade":
       
   317                 files.append((f,f))
       
   318             elif f[-8:] == "_install":
       
   319                 files.append((f,f))
       
   320 
       
   321         # copy files
       
   322         for src, dst in files:
       
   323             src = basename(src)
       
   324             dst = basename(dst)
       
   325             f = join(self.resourceFolder, src)
       
   326             if isfile(f):
       
   327                 shutil.copy(f, os.path.join(self.packageResourceFolder, dst))
       
   328             elif isdir(f):
       
   329                 # special case for .rtfd and .lproj folders...
       
   330                 d = join(self.packageResourceFolder, dst)
       
   331                 os.mkdir(d)
       
   332                 files = GlobDirectoryWalker(f)
       
   333                 for file in files:
       
   334                     shutil.copy(file, d)
       
   335 
       
   336 
       
   337     def _addSizes(self):
       
   338         "Write .sizes file with info about number and size of files."
       
   339 
       
   340         # Not sure if this is correct, but 'installedSize' and
       
   341         # 'zippedSize' are now in Bytes. Maybe blocks are needed?
       
   342         # Well, Installer.app doesn't seem to care anyway, saying
       
   343         # the installation needs 100+ MB...
       
   344 
       
   345         numFiles = 0
       
   346         installedSize = 0
       
   347         zippedSize = 0
       
   348 
       
   349         files = GlobDirectoryWalker(self.sourceFolder)
       
   350         for f in files:
       
   351             numFiles = numFiles + 1
       
   352             installedSize = installedSize + os.lstat(f)[6]
       
   353 
       
   354         try:
       
   355             zippedSize = os.stat(self.archPath+ ".gz")[6]
       
   356         except OSError: # ignore error
       
   357             pass
       
   358         base = self.packageInfo["Title"] + ".sizes"
       
   359         f = open(join(self.packageResourceFolder, base), "w")
       
   360         format = "NumFiles %d\nInstalledSize %d\nCompressedSize %d\n"
       
   361         f.write(format % (numFiles, installedSize, zippedSize))
       
   362 
       
   363     def _addLoc(self):
       
   364         "Write .loc file."
       
   365         base = self.packageInfo["Title"] + ".loc"
       
   366         f = open(join(self.packageResourceFolder, base), "w")
       
   367         f.write('/')
       
   368 
       
   369 # Shortcut function interface
       
   370 
       
   371 def buildPackage(*args, **options):
       
   372     "A Shortcut function for building a package."
       
   373 
       
   374     o = options
       
   375     title, version, desc = o["Title"], o["Version"], o["Description"]
       
   376     pm = PackageMaker(title, version, desc)
       
   377     apply(pm.build, list(args), options)
       
   378 
       
   379 
       
   380 ######################################################################
       
   381 # Tests
       
   382 ######################################################################
       
   383 
       
   384 def test0():
       
   385     "Vanilla test for the distutils distribution."
       
   386 
       
   387     pm = PackageMaker("distutils2", "1.0.2", "Python distutils package.")
       
   388     pm.build("/Users/dinu/Desktop/distutils2")
       
   389 
       
   390 
       
   391 def test1():
       
   392     "Test for the reportlab distribution with modified options."
       
   393 
       
   394     pm = PackageMaker("reportlab", "1.10",
       
   395                       "ReportLab's Open Source PDF toolkit.")
       
   396     pm.build(root="/Users/dinu/Desktop/reportlab",
       
   397              DefaultLocation="/Applications/ReportLab",
       
   398              Relocatable="YES")
       
   399 
       
   400 def test2():
       
   401     "Shortcut test for the reportlab distribution with modified options."
       
   402 
       
   403     buildPackage(
       
   404         "/Users/dinu/Desktop/reportlab",
       
   405         Title="reportlab",
       
   406         Version="1.10",
       
   407         Description="ReportLab's Open Source PDF toolkit.",
       
   408         DefaultLocation="/Applications/ReportLab",
       
   409         Relocatable="YES")
       
   410 
       
   411 
       
   412 ######################################################################
       
   413 # Command-line interface
       
   414 ######################################################################
       
   415 
       
   416 def printUsage():
       
   417     "Print usage message."
       
   418 
       
   419     format = "Usage: %s <opts1> [<opts2>] <root> [<resources>]"
       
   420     print format % basename(sys.argv[0])
       
   421     print
       
   422     print "       with arguments:"
       
   423     print "           (mandatory) root:         the package root folder"
       
   424     print "           (optional)  resources:    the package resources folder"
       
   425     print
       
   426     print "       and options:"
       
   427     print "           (mandatory) opts1:"
       
   428     mandatoryKeys = string.split("Title Version Description", " ")
       
   429     for k in mandatoryKeys:
       
   430         print "               --%s" % k
       
   431     print "           (optional) opts2: (with default values)"
       
   432 
       
   433     pmDefaults = PackageMaker.packageInfoDefaults
       
   434     optionalKeys = pmDefaults.keys()
       
   435     for k in mandatoryKeys:
       
   436         optionalKeys.remove(k)
       
   437     optionalKeys.sort()
       
   438     maxKeyLen = max(map(len, optionalKeys))
       
   439     for k in optionalKeys:
       
   440         format = "               --%%s:%s %%s"
       
   441         format = format % (" " * (maxKeyLen-len(k)))
       
   442         print format % (k, repr(pmDefaults[k]))
       
   443 
       
   444 
       
   445 def main():
       
   446     "Command-line interface."
       
   447 
       
   448     shortOpts = ""
       
   449     keys = PackageMaker.packageInfoDefaults.keys()
       
   450     longOpts = map(lambda k: k+"=", keys)
       
   451 
       
   452     try:
       
   453         opts, args = getopt.getopt(sys.argv[1:], shortOpts, longOpts)
       
   454     except getopt.GetoptError, details:
       
   455         print details
       
   456         printUsage()
       
   457         return
       
   458 
       
   459     optsDict = {}
       
   460     for k, v in opts:
       
   461         optsDict[k[2:]] = v
       
   462 
       
   463     ok = optsDict.keys()
       
   464     if not (1 <= len(args) <= 2):
       
   465         print "No argument given!"
       
   466     elif not ("Title" in ok and \
       
   467               "Version" in ok and \
       
   468               "Description" in ok):
       
   469         print "Missing mandatory option!"
       
   470     else:
       
   471         apply(buildPackage, args, optsDict)
       
   472         return
       
   473 
       
   474     printUsage()
       
   475 
       
   476     # sample use:
       
   477     # buildpkg.py --Title=distutils \
       
   478     #             --Version=1.0.2 \
       
   479     #             --Description="Python distutils package." \
       
   480     #             /Users/dinu/Desktop/distutils
       
   481 
       
   482 
       
   483 if __name__ == "__main__":
       
   484     main()