mirror of
https://github.com/munki/munki.git
synced 2025-12-31 03:29:55 -06:00
229 lines
8.0 KiB
Python
Executable File
229 lines
8.0 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# encoding: utf-8
|
|
#
|
|
# Copyright 2009-2014 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
|
|
#
|
|
# https://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.
|
|
|
|
"""
|
|
app2pkg.py
|
|
|
|
Created by Greg Neagle on 2009-09-28.
|
|
"""
|
|
|
|
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 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 main():
|
|
# command-line options
|
|
p = optparse.OptionParser()
|
|
p.add_option('--makedmg', '-d', action='store_true',
|
|
help='Makes 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('--displayname',
|
|
help='Specify a display name for the package.')
|
|
p.add_option('--description',
|
|
help='Specify a description for the package.')
|
|
p.add_option('--id',
|
|
help='Specify a package id for the package.')
|
|
# Get our options and our package names
|
|
options, app_paths = p.parse_args()
|
|
|
|
if not os.path.exists(packagemaker):
|
|
print >>sys.stderr, "packagemaker tool not found at %s." % \
|
|
packagemaker
|
|
exit(-1)
|
|
|
|
if len(app_paths) == 0:
|
|
print >>sys.stderr, "You must specify a path to an application!"
|
|
exit(-1)
|
|
|
|
if len(app_paths) > 1:
|
|
print >>sys.stderr, "You may package only one app at a time."
|
|
exit(-1)
|
|
|
|
app_path = app_paths[0]
|
|
|
|
if not os.path.exists(app_path):
|
|
print "Nothing exists at %s" % app_path
|
|
exit(-1)
|
|
|
|
mytmpdir = tempfile.mkdtemp()
|
|
if options.name:
|
|
pkgname = options.name
|
|
else:
|
|
pkgname = os.path.splitext(os.path.basename(app_path))[0]
|
|
|
|
enclosingpath = os.path.dirname(app_path).lstrip("/") or "Applications"
|
|
|
|
# make packageroot directory
|
|
packageroot = os.path.join(mytmpdir, pkgname)
|
|
os.mkdir(packageroot)
|
|
application_dir = os.path.join(packageroot, enclosingpath)
|
|
os.makedirs(application_dir)
|
|
copytodir = os.path.join(application_dir, os.path.basename(app_path))
|
|
cmd = ['/usr/bin/ditto', '--noqtn', app_path, copytodir ]
|
|
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(output, err) = p.communicate()
|
|
if p.returncode != 0:
|
|
print >>sys.stderr, err
|
|
exit(-1)
|
|
if err:
|
|
print >>sys.stderr, err
|
|
exit(-1)
|
|
|
|
# fix uid/gid/perms on directories
|
|
for dirpath, dirnames, filenames in os.walk(packageroot):
|
|
srcdir = dirpath[len(packageroot):]
|
|
if srcdir == "": srcdir = "/"
|
|
os.chown(dirpath, os.stat(srcdir).st_uid, os.stat(srcdir).st_gid)
|
|
os.chmod(dirpath, stat.S_IMODE(os.stat(srcdir).st_mode))
|
|
|
|
pkgid = pkgvers = ""
|
|
if options.id:
|
|
pkgid = options.id
|
|
if options.version:
|
|
pkgvers = options.version
|
|
|
|
appinfo = os.path.join(app_path, "Contents/Info.plist")
|
|
if os.path.exists(appinfo):
|
|
pl = plistlib.readPlist(appinfo)
|
|
if "CFBundleIdentifier" in pl and pkgid == "":
|
|
pkgid = pl["CFBundleIdentifier"] + ".pkg"
|
|
if "CFBundleShortVersionString" in pl and pkgvers == "":
|
|
pkgvers = pl["CFBundleShortVersionString"]
|
|
|
|
if pkgid == "":
|
|
pkgid = pkgidprefix + pkgname.lower().replace(" ","_")
|
|
if pkgvers == "":
|
|
pkgvers = "1.0.0"
|
|
|
|
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")
|
|
cmd = [packagemaker, '--root', packageroot, '--id', pkgid,
|
|
'--version', pkgvers,
|
|
'--no-recommend', '--out', outputname, '--verbose',
|
|
'--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']
|
|
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 options.makedmg:
|
|
makeDMG(outputname)
|
|
|
|
#cleanup temp dir
|
|
retcode = subprocess.call(["/bin/rm", "-rf", mytmpdir])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|