#!/usr/bin/env python
#
# Copyright (C) 2011 Patrick "p2k" Schneider <me@p2k-network.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import subprocess, sys, re, os, shutil, stat, os.path
from time import sleep
from argparse import ArgumentParser
# This is ported from the original macdeployqt with modifications
class FrameworkInfo(object):
def __init__(self):
self.frameworkDirectory = ""
self.frameworkName = ""
self.frameworkPath = ""
self.binaryDirectory = ""
self.binaryName = ""
self.binaryPath = ""
self.version = ""
self.installName = ""
self.deployedInstallName = ""
self.sourceFilePath = ""
self.destinationDirectory = ""
self.sourceResourcesDirectory = ""
self.destinationResourcesDirectory = ""
def __eq__(self, other):
if self.__class__ == other.__class__:
return self.__dict__ == other.__dict__
else:
return False
def __str__(self):
return """ Framework name: %s
Framework directory: %s
Framework path: %s
Binary name: %s
Binary directory: %s
Binary path: %s
Version: %s
Install name: %s
Deployed install name: %s
Source file Path: %s
Deployed Directory (relative to bundle): %s
""" % (self.frameworkName,
self.frameworkDirectory,
self.frameworkPath,
self.binaryName,
self.binaryDirectory,
self.binaryPath,
self.version,
self.installName,
self.deployedInstallName,
self.sourceFilePath,
self.destinationDirectory)
def isDylib(self):
return self.frameworkName.endswith(".dylib")
def isQtFramework(self):
if self.isDylib():
return self.frameworkName.startswith("libQt")
else:
return self.frameworkName.startswith("Qt")
reOLine = re.compile(r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$')
bundleFrameworkDirectory = "Contents/Frameworks"
bundleBinaryDirectory = "Contents/MacOS"
@classmethod
def fromOtoolLibraryLine(cls, line):
# Note: line must be trimmed
if line == "":
return None
# Don't deploy system libraries (exception for libQtuitools and libQtlucene).
if line.startswith("/System/Library/") or line.startswith("@executable_path") or (line.startswith("/usr/lib/") and "libQt" not in line):
return None
m = cls.reOLine.match(line)
if m is None:
raise RuntimeError("otool line could not be parsed: " + line)
path = m.group(1)
info = cls()
info.sourceFilePath = path
info.installName = path
if path.endswith(".dylib"):
dirname, filename = os.path.split(path)
info.frameworkName = filename
info.frameworkDirectory = dirname
info.frameworkPath = path
info.binaryDirectory = dirname
info.binaryName = filename
info.binaryPath = path
info.version = "-"
info.installName = path
info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName
info.sourceFilePath = path
info.destinationDirectory = cls.bundleFrameworkDirectory
else:
parts = path.split("/")
i = 0
# Search for the .framework directory
for part in parts:
if part.endswith(".framework"):
break
i += 1
if i == len(parts):
raise RuntimeError("Could not find .framework or .dylib in otool line: " + line)
info.frameworkName = parts[i]
info.frameworkDirectory = "/".join(parts[:i])
info.frameworkPath = os.path.join(info.frameworkDirectory, info.frameworkName)
info.binaryName = parts[i+3]
info.binaryDirectory = "/".join(parts[i+1:i+3])
info.binaryPath = os.path.join(info.binaryDirectory, info.binaryName)
info.version = parts[i+2]
info.deployedInstallName = "@executable_path/../Frameworks/" + os.path.join(info.frameworkName, info.binaryPath)
info.destinationDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory)
info.sourceResourcesDirectory = os.path.join(info.frameworkPath, "Resources")
info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources")
return info
class ApplicationBundleInfo(object):
def __init__(self, path):
self.path = path
appName = os.path.splitext(os.path.basename(path))[0]
self.binaryPath = os.path.join(path, "Contents", "MacOS", appName)
if not os.path.exists(self.binaryPath):
raise RuntimeError("Could not find bundle binary for " + path)
self.resourcesPath = os.path.join(path, "Contents", "Resources")
self.pluginPath = os.path.join(path, "Contents", "PlugIns")
class DeploymentInfo(object):
def __init__(self):
self.qtPath = None
self.pluginPath = None
self.deployedFrameworks = []
def detectQtPath(self, frameworkDirectory):
parentDir = os.path.dirname(frameworkDirectory)
if os.path.exists(os.path.join(parentDir, "translations")):
# Classic layout, e.g. "/usr/local/Trolltech/Qt-4.x.x"
self.qtPath = parentDir
elif os.path.exists(os.path.join(parentDir, "share", "qt4", "translations")):
# MacPorts layout, e.g. "/opt/local/share/qt4"
self.qtPath = os.path.join(parentDir, "share", "qt4")
elif os.path.exists(os.path.join(os.path.dirname(parentDir), "share", "qt4", "translations")):
# Newer Macports layout
self.qtPath = os.path.join(os.path.dirname(parentDir), "share", "qt4")
else:
self.qtPath = os.getenv("QTDIR", None)
if self.qtPath is not None:
pluginPath = os.path.join(self.qtPath, "plugins")
if os.path.exists(pluginPath):
self.pluginPath = pluginPath
def usesFramework(self, name):
nameDot = "%s." % name
libNameDot = "lib%s." % name
for framework in self.deployedFrameworks:
if framework.endswith(".framework"):
if framework.startswith(nameDot):
return True
elif framework.endswith(".dylib"):
if framework.startswith(libNameDot):
return True
return False
def getFrameworks(binaryPath, verbose):
if verbose >= 3:
print "Inspecting with otool: " + binaryPath
otool = subprocess.Popen(["otool", "-L", binaryPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
o_stdout, o_stderr = otool.communicate()
if otool.returncode != 0:
if verbose >= 1:
sys.stderr.write(o_stderr)
sys.stderr.flush()
raise RuntimeError("otool failed with return code %d" % otool.returncode)
otoolLines = o_stdout.split("\n")
otoolLines.pop(0) # First line is the inspected binary
if ".framework" in binaryPath or binaryPath.endswith(".dylib"):
otoolLines.pop(0) # Frameworks and dylibs list themselves as a dependency.
libraries = []
for line in otoolLines:
info = FrameworkInfo.fromOtoolLibraryLine(line.strip())
if info is not None:
if verbose >= 3:
print "Found framework:"
print info
libraries.append(info)
return libraries
def runInstallNameTool(action, *args):
subprocess.check_call(["install_name_tool", "-"+action] + list(args))
def changeInstallName(oldName, newName, binaryPath, verbose):
if verbose >= 3:
print "Using install_name_tool:"
print " in", binaryPath
print " change reference", oldName
print " to", newName
runInstallNameTool("change", oldName, newName, binaryPath)
def changeIdentification(id, binaryPath, verbose):
if verbose >= 3:
print "Using install_name_tool:"
print " change identification in", binaryPath
print " to", id
runInstallNameTool("id", id, binaryPath)
def runStrip(binaryPath, verbose):
if verbose >= 3:
print "Using strip:"
print " stripped", binaryPath
subprocess.check_call(["strip", "-x", binaryPath])
def copyFramework(framework, path, verbose):
if framework.sourceFilePath.startswith("Qt"):
#standard place for Nokia Qt installer's frameworks
fromPath = "/Library/Frameworks/" + framework.sourceFilePath
else:
fromPath = framework.sourceFilePath
toDir = os.path.join(path, framework.destinationDirectory)
toPath = os.path.join(toDir, framework.binaryName)
if not os.path.exists(fromPath):
raise RuntimeError("No file at " + fromPath)
if os.path.exists(toPath):
return None # Already there
if not os.path.exists(toDir):
os.makedirs(toDir)
shutil.copy2(fromPath, toPath)
if verbose >= 3:
print "Copied:", fromPath
print " to:", toPath
permissions = os.stat(toPath)
if not permissions.st_mode & stat.S_IWRITE:
os.chmod(toPath, permissions.st_mode | stat.S_IWRITE)
if not framework.isDylib(): # Copy resources for real frameworks
fromResourcesDir = framework.sourceResourcesDirectory
if os.path.exists(fromResourcesDir):
toResourcesDir = os.path.join(path, framework.destinationResourcesDirectory)
shutil.copytree(fromResourcesDir, toResourcesDir)
if verbose >= 3:
print "Copied resources:", fromResourcesDir
print " to:", toResourcesDir
elif framework.frameworkName.startswith("libQtGui"): # Copy qt_menu.nib (applies to non-framework layout)
qtMenuNibSourcePath = os.path.join(framework.frameworkDirectory, "Resources", "qt_menu.nib")
qtMenuNibDestinationPath = os.path.join(path, "Contents", "Resources", "qt_menu.nib")
if os.path.exists(qtMenuNibSourcePath) and not os.path.exists(qtMenuNibDestinationPath):
shutil.copytree(qtMenuNibSourcePath, qtMenuNibDestinationPath)
if verbose >= 3:
print "Copied for libQtGui:", qtMenuNibSourcePath
print " to:", qtMenuNibDestinationPath
return toPath
def deployFrameworks(frameworks, bundlePath, binaryPath, strip, verbose, deploymentInfo=None):
if deploymentInfo is None:
deploymentInfo = DeploymentInfo()
while len(frameworks) > 0:
framework = frameworks.pop(0)
deploymentInfo.deployedFrameworks.append(framework.frameworkName)
if verbose >= 2:
print "Processing", framework.frameworkName, "..."
# Get the Qt path from one of the Qt frameworks
if deploymentInfo.qtPath is None and framework.isQtFramework():
deploymentInfo.detectQtPath(framework.frameworkDirectory)
if framework.installName.startswith("@executable_path"):
if verbose >= 2:
print framework.frameworkName, "already deployed, skipping."
continue
# install_name_tool the new id into the binary
changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose)
# Copy farmework to app bundle.
deployedBinaryPath = copyFramework(framework, bundlePath, verbose)
# Skip the rest if already was deployed.
if deployedBinaryPath is None:
continue
if strip:
runStrip(deployedBinaryPath, verbose)
# install_name_tool it a new id.
changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose)
# Check for framework dependencies
dependencies = getFrameworks(deployedBinaryPath, verbose)
for dependency in dependencies:
changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose)
# Deploy framework if necessary.
if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks:
frameworks.append(dependency)
return deploymentInfo
def deployFrameworksForAppBundle(applicationBundle, strip, verbose):
frameworks = getFrameworks(applicationBundle.binaryPath, verbose)
if len(frameworks) == 0 and verbose >= 1:
print "Warning: Could not find any external frameworks to deploy in %s." % (applicationBundle.path)
return DeploymentInfo()
else:
return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose)
def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose):
# Lookup available plugins, exclude unneeded
plugins = []
for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath):
pluginDirectory = os.path.relpath(dirpath, deploymentInfo.pluginPath)
if pluginDirectory == "designer":
# Skip designer plugins
continue
elif pluginDirectory == "phonon" or pluginDirectory == "phonon_backend":
# Deploy the phonon plugins only if phonon is in use
if not deploymentInfo.usesFramework("phonon"):
continue
elif pluginDirectory == "sqldrivers":
# Deploy the sql plugins only if QtSql is in use
if not deploymentInfo.usesFramework("QtSql"):
continue
elif pluginDirectory == "script":
# Deploy the script plugins only if QtScript is in use
if not deploymentInfo.usesFramework("QtScript"):
continue
elif pluginDirectory == "qmltooling":
# Deploy the qml plugins only if QtDeclarative is in use
if not deploymentInfo.usesFramework("QtDeclarative"):
continue
elif pluginDirectory == "bearer":
# Deploy the bearer plugins only if QtNetwork is in use
if not deploymentInfo.usesFramework("QtNetwork"):
continue
for pluginName in filenames:
pluginPath = os.path.join(pluginDirectory, pluginName)
if pluginName.endswith("_debug.dylib"):
# Skip debug plugins
continue
elif pluginPath == "imageformats/libqsvg.dylib" or pluginPath == "iconengines/libqsvgicon.dylib":
# Deploy the svg plugins only if QtSvg is in use
if not deploymentInfo.usesFramework("QtSvg"):
continue
elif pluginPath == "accessible/libqtaccessiblecompatwidgets.dylib":
# Deploy accessibility for Qt3Support only if the Qt3Support is in use
if not deploymentInfo.usesFramework("Qt3Support"):
continue
elif pluginPath == "graphicssystems/libqglgraphicssystem.dylib":
# Deploy the opengl graphicssystem plugin only if QtOpenGL is in use
if not deploymentInfo.usesFramework("QtOpenGL"):
continue
plugins.append((pluginDirectory, pluginName))
for pluginDirectory, pluginName in plugins:
if verbose >= 2:
print "Processing plugin", os.path.join(pluginDirectory, pluginName), "..."
sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName)
destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory)
if not os.path.exists(destinationDirectory):
os.makedirs(destinationDirectory)
destinationPath = os.path.join(destinationDirectory, pluginName)
shutil.copy2(sourcePath, destinationPath)
if verbose >= 3:
print "Copied:", sourcePath
print " to:", destinationPath
if strip:
runStrip(destinationPath, verbose)
dependencies = getFrameworks(destinationPath, verbose)
for dependency in dependencies:
changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose)
# Deploy framework if necessary.
if dependency.frameworkName not in deploymentInfo.deployedFrameworks:
deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
qt_conf="""[Paths]
translations=Resources
plugins=PlugIns
"""
ap = ArgumentParser(description="""Improved version of macdeployqt.
Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .dmg file.
Note, that the "dist" folder will be deleted before deploying on each run.
Optionally, Qt translation files (.qm) and additional resources can be added to the bundle.""")
ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed")
ap.add_argument("-verbose", type=int, nargs=1, default=[1], metavar="<0-3>", help="0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug")
ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment")
ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries")
ap.add_argument("-dmg", nargs="?", const="", metavar="basename", help="create a .dmg disk image; if basename is not specified, a camel-cased version of the app name is used")
ap.add_argument("-fancy", nargs=1, metavar="plist", default=[], help="make a fancy looking disk image using the given plist file with instructions; requires -dmg to work")
ap.add_argument("-add-qt-tr", nargs=1, metavar="languages", default=[], help="add Qt translation files to the bundle's ressources; the language list must be separated with commas, not with whitespace")
ap.add_argument("-add-resources", nargs="+", metavar="path", default=[], help="list of additional files or folders to be copied into the bundle's resources; must be the last argument")
config = ap.parse_args()
verbose = config.verbose[0]
# ------------------------------------------------
app_bundle = config.app_bundle[0]
if not os.path.exists(app_bundle):
if verbose >= 1:
sys.stderr.write("Error: Could not find app bundle \"%s\"\n" % (app_bundle))
sys.exit(1)
app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0]
# ------------------------------------------------
for p in config.add_resources:
if verbose >= 3:
print "Checking for \"%s\"..." % p
if not os.path.exists(p):
if verbose >= 1:
sys.stderr.write("Error: Could not find additional resource file \"%s\"\n" % (p))
sys.exit(1)
# ------------------------------------------------
if len(config.fancy) == 1:
if verbose >= 3:
print "Fancy: Importing plistlib..."
try:
import plistlib
except ImportError:
if verbose >= 1:
sys.stderr.write("Error: Could not import plistlib which is required for fancy disk images.\n")
sys.exit(1)
if verbose >= 3:
print "Fancy: Importing appscript..."
try:
import appscript
except ImportError:
if verbose >= 1:
sys.stderr.write("Error: Could not import appscript which is required for fancy disk images.\n")
sys.stderr.write("Please install it e.g. with \"sudo easy_install appscript\".\n")
sys.exit(1)
p = config.fancy[0]
if verbose >= 3:
print "Fancy: Loading \"%s\"..." % p
if not os.path.exists(p):
if verbose >= 1:
sys.stderr.write("Error: Could not find fancy disk image plist at \"%s\"\n" % (p))
sys.exit(1)
try:
fancy = plistlib.readPlist(p)
except:
if verbose >= 1:
sys.stderr.write("Error: Could not parse fancy disk image plist at \"%s\"\n" % (p))
sys.exit(1)
try:
assert not fancy.has_key("window_bounds") or (isinstance(fancy["window_bounds"], list) and len(fancy["window_bounds"]) == 4)
assert not fancy.has_key("background_picture") or isinstance(fancy["background_picture"], str)
assert not fancy.has_key("icon_size") or isinstance(fancy["icon_size"], int)
assert not fancy.has_key("applications_symlink") or isinstance(fancy["applications_symlink"], bool)
if fancy.has_key("items_position"):
assert isinstance(fancy["items_position"], dict)
for key, value in fancy["items_position"].iteritems():
assert isinstance(value, list) and len(value) == 2 and isinstance(value[0], int) and isinstance(value[1], int)
except:
if verbose >= 1:
sys.stderr.write("Error: Bad format of fancy disk image plist at \"%s\"\n" % (p))
sys.exit(1)
if fancy.has_key("background_picture"):
bp = fancy["background_picture"]
if verbose >= 3:
print "Fancy: Resolving background picture \"%s\"..." % bp
if not os.path.exists(bp):
bp = os.path.join(os.path.dirname(p), bp)
if not os.path.exists(bp):
if verbose >= 1:
sys.stderr.write("Error: Could not find background picture at \"%s\" or \"%s\"\n" % (fancy["background_picture"], bp))
sys.exit(1)
else:
fancy["background_picture"] = bp
else:
fancy = None
# ------------------------------------------------
if os.path.exists("dist"):
if verbose >= 2:
print "+ Removing old dist folder +"
shutil.rmtree("dist")
# ------------------------------------------------
target = os.path.join("dist", app_bundle)
if verbose >= 2:
print "+ Copying source bundle +"
if verbose >= 3:
print app_bundle, "->", target
os.mkdir("dist")
shutil.copytree(app_bundle, target)
applicationBundle = ApplicationBundleInfo(target)
# ------------------------------------------------
if verbose >= 2:
print "+ Deploying frameworks +"
try:
deploymentInfo = deployFrameworksForAppBundle(applicationBundle, config.strip, verbose)
if deploymentInfo.qtPath is None:
deploymentInfo.qtPath = os.getenv("QTDIR", None)
if deploymentInfo.qtPath is None:
if verbose >= 1:
sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
config.plugins = False
except RuntimeError as e:
if verbose >= 1:
sys.stderr.write("Error: %s\n" % str(e))
sys.exit(ret)
# ------------------------------------------------
if config.plugins:
if verbose >= 2:
print "+ Deploying plugins +"
try:
deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
except RuntimeError as e:
if verbose >= 1:
sys.stderr.write("Error: %s\n" % str(e))
sys.exit(ret)
# ------------------------------------------------
if len(config.add_qt_tr) == 0:
add_qt_tr = []
else:
qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations")
add_qt_tr = ["qt_%s.qm" % lng for lng in config.add_qt_tr[0].split(",")]
for lng_file in add_qt_tr:
p = os.path.join(qt_tr_dir, lng_file)
if verbose >= 3:
print "Checking for \"%s\"..." % p
if not os.path.exists(p):
if verbose >= 1:
sys.stderr.write("Error: Could not find Qt translation file \"%s\"\n" % (lng_file))
sys.exit(1)
# ------------------------------------------------
if verbose >= 2:
print "+ Installing qt.conf +"
f = open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb")
f.write(qt_conf)
f.close()
# ------------------------------------------------
if len(add_qt_tr) > 0 and verbose >= 2:
print "+ Adding Qt translations +"
for lng_file in add_qt_tr:
if verbose >= 3:
print os.path.join(qt_tr_dir, lng_file), "->", os.path.join(applicationBundle.resourcesPath, lng_file)
shutil.copy2(os.path.join(qt_tr_dir, lng_file), os.path.join(applicationBundle.resourcesPath, lng_file))
# ------------------------------------------------
if len(config.add_resources) > 0 and verbose >= 2:
print "+ Adding additional resources +"
for p in config.add_resources:
t = os.path.join(applicationBundle.resourcesPath, os.path.basename(p))
if verbose >= 3:
print p, "->", t
if os.path.isdir(p):
shutil.copytree(p, t)
else:
shutil.copy2(p, t)
# ------------------------------------------------
if config.dmg is not None:
def runHDIUtil(verb, image_basename, **kwargs):
hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"]
if kwargs.has_key("capture_stdout"):
del kwargs["capture_stdout"]
run = subprocess.check_output
else:
if verbose < 2:
hdiutil_args.append("-quiet")
elif verbose >= 3:
hdiutil_args.append("-verbose")
run = subprocess.check_call
for key, value in kwargs.iteritems():
hdiutil_args.append("-" + key)
if not value is True:
hdiutil_args.append(str(value))
return run(hdiutil_args)
if verbose >= 2:
if fancy is None:
print "+ Creating .dmg disk image +"
else:
print "+ Preparing .dmg disk image +"
if config.dmg != "":
dmg_name = config.dmg
else:
spl = app_bundle_name.split(" ")
dmg_name = spl[0] + "".join(p.capitalize() for p in spl[1:])
if fancy is None:
try:
runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname=app_bundle_name, ov=True)
except subprocess.CalledProcessError as e:
sys.exit(e.returncode)
else:
if verbose >= 3:
print "Determining size of \"dist\"..."
size = 0
for path, dirs, files in os.walk("dist"):
for file in files:
size += os.path.getsize(os.path.join(path, file))
size += int(size * 0.1)
if verbose >= 3:
print "Creating temp image for modification..."
try:
runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=app_bundle_name, ov=True)
except subprocess.CalledProcessError as e:
sys.exit(e.returncode)
if verbose >= 3:
print "Attaching temp image..."
try:
output = runHDIUtil("attach", dmg_name + ".temp", readwrite=True, noverify=True, noautoopen=True, capture_stdout=True)
except subprocess.CalledProcessError as e:
sys.exit(e.returncode)
m = re.search("/Volumes/(.+$)", output)
disk_root = m.group(0)
disk_name = m.group(1)
if verbose >= 2:
print "+ Applying fancy settings +"
if fancy.has_key("background_picture"):
bg_path = os.path.join(disk_root, os.path.basename(fancy["background_picture"]))
if verbose >= 3:
print fancy["background_picture"], "->", bg_path
shutil.copy2(fancy["background_picture"], bg_path)
else:
bg_path = None
if fancy.get("applications_symlink", False):
os.symlink("/Applications", os.path.join(disk_root, "Applications"))
finder = appscript.app("Finder")
disk = finder.disks[disk_name]
disk.open()
window = disk.container_window
window.current_view.set(appscript.k.icon_view)
window.toolbar_visible.set(False)
window.statusbar_visible.set(False)
if fancy.has_key("window_bounds"):
window.bounds.set(fancy["window_bounds"])
view_options = window.icon_view_options
view_options.arrangement.set(appscript.k.not_arranged)
if fancy.has_key("icon_size"):
view_options.icon_size.set(fancy["icon_size"])
if bg_path is not None:
view_options.background_picture.set(disk.files[os.path.basename(bg_path)])
if fancy.has_key("items_position"):
for name, position in fancy["items_position"].iteritems():
window.items[name].position.set(position)
disk.close()
if bg_path is not None:
subprocess.call(["SetFile", "-a", "V", bg_path])
# disk.update(registering_applications=False)
sleep(2)
disk.eject()
if verbose >= 2:
print "+ Finalizing .dmg disk image +"
try:
runHDIUtil("convert", dmg_name + ".temp", format="UDBZ", o=dmg_name + ".dmg", ov=True)
except subprocess.CalledProcessError as e:
sys.exit(e.returncode)
os.unlink(dmg_name + ".temp.dmg")
# ------------------------------------------------
if verbose >= 2:
print "+ Done +"
sys.exit(0)