From ff43952683f17ba628afeeb08057c788e4e27a60 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 27 Sep 2011 09:26:00 -0700 Subject: [PATCH] Added tar2pkg.py to tools --- code/tools/tar2pkg.py | 313 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100755 code/tools/tar2pkg.py diff --git a/code/tools/tar2pkg.py b/code/tools/tar2pkg.py new file mode 100755 index 00000000..5f61e071 --- /dev/null +++ b/code/tools/tar2pkg.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python +# encoding: utf-8 +# +# Copyright 2009-2011 Greg Neagle. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +tar2pkg.py + +Created by Greg Neagle on 2011-06-16. +""" + +import sys +import os +import stat +import plistlib +import subprocess +import optparse +import tempfile + +# change these to suit yourself +packagemaker = "/Developer/usr/bin/packagemaker" +pkgidprefix = "com.myorg.pkg." +pkgoutputdir = "/Users/Shared/pkgs" + + +def nameAndVersion(s): + """ + Splits a string into the name and version numbers: + 'TextWrangler2.3b1' becomes ('TextWrangler', '2.3b1') + 'AdobePhotoshopCS3-11.2.1' becomes ('AdobePhotoshopCS3', '11.2.1') + 'MicrosoftOffice2008v12.2.1' becomes ('MicrosoftOffice2008', '12.2.1') + """ + index = 0 + for char in s: + if char in "0123456789": + possibleVersion = s[index:] + if not (" " in possibleVersion or "_" in possibleVersion + or "-" in possibleVersion or "v" in possibleVersion): + return (s[0:index].rstrip(" .-_v"), possibleVersion) + index += 1 + # no version number found, just return original string and empty string + return (s, '') + + +def makeDMG(pkgpath): + print "Making disk image..." + cmd = ["/usr/bin/hdiutil", "create", "-srcfolder", + pkgpath, pkgpath + ".dmg"] + p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + while True: + output = p.stdout.readline() + if not output and (p.poll() != None): + break + print output.rstrip("\n") + sys.stdout.flush() + + retcode = p.poll() + if retcode: + print >>sys.stderr, "Disk image creation failed." + return(-1) + + +def untarItem(sourceitem, packageroot): + '''untars item into packageroot''' + os.chdir(packageroot) + return subprocess.call(['/usr/bin/tar', '-xf', sourceitem]) + + +def main(): + # command-line options + p = optparse.OptionParser() + p.add_option('--nomakedmg', action='store_true', + help='Don\'t make a disk image containing the package.') + p.add_option('--name', '-n', + help='Specify a name for the package.') + p.add_option('--version', '-v', + help='Specify a version number for the package.') + p.add_option('--id', + help='Specify a package id for the package.') + p.add_option('--displayname', + help='Specify a display name for the package.') + p.add_option('--description', + help='Specify a description for the package.') + p.add_option('--restart', action='store_true', + help='Restart is required on install.') + p.add_option('--logout', action='store_true', + help='Logout is required to install.') + + # Get our options and our package names + options, tarballs = p.parse_args() + + if not os.path.exists(packagemaker): + print >>sys.stderr, \ + "packagemaker tool not found at %s." % packagemaker + exit(-1) + + if len(tarballs) == 0: + print >>sys.stderr, "You must specify a tar archive!" + exit(-1) + + if len(tarballs) > 1: + print >>sys.stderr, "You may convert only one tar archive at a time." + exit(-1) + + tarball = tarballs[0] + if not os.path.exists(tarball): + print "No tar archive at %s" % tarball + exit(-1) + + mytmpdir = tempfile.mkdtemp() + if options.name: + pkgname = options.name + else: + pkgname = os.path.splitext(os.path.basename(tarball))[0] + + packageroot = os.path.join(mytmpdir, pkgname) + os.mkdir(packageroot) + untarItem(tarball, packageroot) + + # some default values + (name, versionInName) = nameAndVersion(pkgname) + if options.id: + pkgid = options.id + else: + pkgid = pkgidprefix + name.lower() + if options.version: + pkgvers = options.version + elif versionInName: + pkgvers = versionInName + else: + pkgvers = "1.0.0" + + infopath = "" + + # look through packageroot dir for Receipts + pkgrootreceipts = os.path.join(packageroot, "Library/Receipts") + if os.path.exists(pkgrootreceipts): + receiptlist = os.listdir(pkgrootreceipts) + if len(receiptlist) == 1: + receipt = os.path.join(pkgrootreceipts, receiptlist[0]) + infopath = os.path.join(receipt,"Contents/Info.plist") + if os.path.exists(infopath): + print "Using package info from %s" % infopath + else: + infopath = "" + else: + print >>sys.stderr, \ + ("Found multiple receipts, " + "so cannot determine pkgid and version.") + + if not infopath: + # look for a single application bundle and get info from that + appinfo = "" + for dirpath, dirnames, filenames in os.walk(packageroot): + if dirpath.endswith('.app'): + if not appinfo: + appinfo = os.path.join(dirpath, "Contents/Info.plist") + if not os.path.exists(appinfo): + appinfo = "" + else: + # crap, found more than one. + appinfo = "" + print >>sys.stderr, \ + ("Found multiple application bundles, " + "so cannot determine pkgid and version.") + break + + if appinfo: + pl = plistlib.readPlist(appinfo) + if "CFBundleIdentifier" in pl and not options.id: + pkgid = pl["CFBundleIdentifier"] + ".pkg" + if "CFBundleShortVersionString" in pl and not options.version: + pkgvers = pl["CFBundleShortVersionString"] + print "Using pkgid: %s, version: %s from %s" % (pkgid, pkgvers, + appinfo) + else: + # let's look for any Contents/Info.plist + infoplist = "" + for dirpath, dirnames, filenames in os.walk(packageroot): + if dirpath.endswith("/Contents") and \ + "Info.plist" in filenames: + if not infoplist: + infoplist = os.path.join(dirpath, "Info.plist") + if not os.path.exists(infoplist): + infoplist = "" + else: + # found more than one Info.plist + infoplist = "" + break + + if infoplist: + pl = plistlib.readPlist(infoplist) + if "CFBundleIdentifier" in pl and not options.id: + pkgid = pl["CFBundleIdentifier"] + ".pkg" + if "CFBundleShortVersionString" in pl and not options.version: + pkgvers = pl["CFBundleShortVersionString"] + print "Using pkgid: %s, version: %s from %s" % (pkgid, + pkgvers, + infoplist) + + print "Package name: %s" % pkgname + newdisplayname = raw_input("Display name [%s]: " + % options.displayname) + options.displayname = newdisplayname or options.displayname + newdescription = raw_input("Description [%s]: " % options.description) + options.description = newdescription or options.description + newid = raw_input("PackageID [%s]: " % pkgid) + pkgid = newid or pkgid + newversion = raw_input("Version [%s]: " % pkgvers) + pkgvers = newversion or pkgvers + + print + print + print "Package name: %s" % pkgname + print "Display name: %s" % options.displayname + print "Description: %s" % options.description + print "PackageID: %s" % pkgid + print "Version: %s" % pkgvers + print + answer = raw_input("Build the package? [y/n] ") + if not answer.lower().startswith("y"): + exit(0) + + # build package + outputname = os.path.join(pkgoutputdir, pkgname + ".pkg") + if os.path.exists(outputname): + retcode = subprocess.call(["/bin/rm", "-rf", outputname]) + + if infopath: + cmd = [packagemaker, '--root', packageroot, '--info', infopath, + '--no-recommend', '--out', outputname, '--verbose', + '--filter', 'Library/Receipts', + '--filter', '.DS_Store$'] + else: + cmd = [packagemaker, '--root', packageroot, '--id', pkgid, + '--version', pkgvers, '--out', outputname, + '--verbose', '--no-recommend', + '--filter', 'Library/Receipts', + '--filter', '.DS_Store$'] + print cmd + p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + while True: + output = p.stdout.readline() + if not output and (p.poll() != None): + break + print output.rstrip("\n") + sys.stdout.flush() + + retcode = p.poll() + if retcode: + print >>sys.stderr, "Package creation failed." + exit(-1) + else: + # remove relocatable stuff + tokendefinitions = os.path.join(outputname, + "Contents/Resources/TokenDefinitions.plist") + if os.path.exists(tokendefinitions): + os.remove(tokendefinitions) + infoplist = os.path.join(outputname, "Contents/Info.plist") + pl = plistlib.readPlist(infoplist) + if 'IFPkgPathMappings' in pl: + del pl['IFPkgPathMappings'] + + if options.restart: + # add restart info to plist + pl['IFPkgFlagRestartAction'] = "RequiredRestart" + elif options.logout: + # add logout info to plist + pl['IFPkgFlagRestartAction'] = "RequiredLogout" + + plistlib.writePlist(pl, infoplist) + + if options.displayname or options.description: + languages = ['en.lproj', 'English.lproj'] + for item in languages: + lprojpath = os.path.join(outputname, + 'Contents/Resources', item) + if os.path.exists(lprojpath): + descriptionplist = os.path.join(lprojpath, + "Description.plist") + pl = {} + pl['IFPkgDescriptionTitle'] = (options.displayname or + pkgname) + pl['IFPkgDescriptionDescription'] = (options.description + or "") + plistlib.writePlist(pl, descriptionplist) + break + + print "Completed package is at %s" % outputname + if not options.nomakedmg: + makeDMG(outputname) + + #cleanup temp dir + retcode = subprocess.call(["/bin/rm", "-rf", mytmpdir]) + + +if __name__ == '__main__': + main()