4 # Copyright (C) 2011 Patrick "p2k" Schneider <me@p2k-network.org>
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 import subprocess, sys, re, os, shutil, stat, os.path
21 from string import Template
22 from time import sleep
23 from argparse import ArgumentParser
25 # This is ported from the original macdeployqt with modifications
27 class FrameworkInfo(object):
29 self.frameworkDirectory = ""
30 self.frameworkName = ""
31 self.frameworkPath = ""
32 self.binaryDirectory = ""
37 self.deployedInstallName = ""
38 self.sourceFilePath = ""
39 self.destinationDirectory = ""
40 self.sourceResourcesDirectory = ""
41 self.destinationResourcesDirectory = ""
43 def __eq__(self, other):
44 if self.__class__ == other.__class__:
45 return self.__dict__ == other.__dict__
50 return """ Framework name: %s
51 Framework directory: %s
58 Deployed install name: %s
60 Deployed Directory (relative to bundle): %s
61 """ % (self.frameworkName,
62 self.frameworkDirectory,
69 self.deployedInstallName,
71 self.destinationDirectory)
74 return self.frameworkName.endswith(".dylib")
76 def isQtFramework(self):
78 return self.frameworkName.startswith("libQt")
80 return self.frameworkName.startswith("Qt")
82 reOLine = re.compile(r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$')
83 bundleFrameworkDirectory = "Contents/Frameworks"
84 bundleBinaryDirectory = "Contents/MacOS"
87 def fromOtoolLibraryLine(cls, line):
88 # Note: line must be trimmed
92 # Don't deploy system libraries (exception for libQtuitools and libQtlucene).
93 if line.startswith("/System/Library/") or line.startswith("@executable_path") or (line.startswith("/usr/lib/") and "libQt" not in line):
96 m = cls.reOLine.match(line)
98 raise RuntimeError("otool line could not be parsed: " + line)
103 info.sourceFilePath = path
104 info.installName = path
106 if path.endswith(".dylib"):
107 dirname, filename = os.path.split(path)
108 info.frameworkName = filename
109 info.frameworkDirectory = dirname
110 info.frameworkPath = path
112 info.binaryDirectory = dirname
113 info.binaryName = filename
114 info.binaryPath = path
117 info.installName = path
118 info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName
119 info.sourceFilePath = path
120 info.destinationDirectory = cls.bundleFrameworkDirectory
122 parts = path.split("/")
124 # Search for the .framework directory
126 if part.endswith(".framework"):
130 raise RuntimeError("Could not find .framework or .dylib in otool line: " + line)
132 info.frameworkName = parts[i]
133 info.frameworkDirectory = "/".join(parts[:i])
134 info.frameworkPath = os.path.join(info.frameworkDirectory, info.frameworkName)
136 info.binaryName = parts[i+3]
137 info.binaryDirectory = "/".join(parts[i+1:i+3])
138 info.binaryPath = os.path.join(info.binaryDirectory, info.binaryName)
139 info.version = parts[i+2]
141 info.deployedInstallName = "@executable_path/../Frameworks/" + os.path.join(info.frameworkName, info.binaryPath)
142 info.destinationDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory)
144 info.sourceResourcesDirectory = os.path.join(info.frameworkPath, "Resources")
145 info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources")
149 class ApplicationBundleInfo(object):
150 def __init__(self, path):
152 appName = os.path.splitext(os.path.basename(path))[0]
153 self.binaryPath = os.path.join(path, "Contents", "MacOS", appName)
154 if not os.path.exists(self.binaryPath):
155 raise RuntimeError("Could not find bundle binary for " + path)
156 self.resourcesPath = os.path.join(path, "Contents", "Resources")
157 self.pluginPath = os.path.join(path, "Contents", "PlugIns")
159 class DeploymentInfo(object):
162 self.pluginPath = None
163 self.deployedFrameworks = []
165 def detectQtPath(self, frameworkDirectory):
166 parentDir = os.path.dirname(frameworkDirectory)
167 if os.path.exists(os.path.join(parentDir, "translations")):
168 # Classic layout, e.g. "/usr/local/Trolltech/Qt-4.x.x"
169 self.qtPath = parentDir
170 elif os.path.exists(os.path.join(parentDir, "share", "qt4", "translations")):
171 # MacPorts layout, e.g. "/opt/local/share/qt4"
172 self.qtPath = os.path.join(parentDir, "share", "qt4")
173 elif os.path.exists(os.path.join(os.path.dirname(parentDir), "share", "qt4", "translations")):
174 # Newer Macports layout
175 self.qtPath = os.path.join(os.path.dirname(parentDir), "share", "qt4")
177 self.qtPath = os.getenv("QTDIR", None)
179 if self.qtPath is not None:
180 pluginPath = os.path.join(self.qtPath, "plugins")
181 if os.path.exists(pluginPath):
182 self.pluginPath = pluginPath
184 def usesFramework(self, name):
185 nameDot = "%s." % name
186 libNameDot = "lib%s." % name
187 for framework in self.deployedFrameworks:
188 if framework.endswith(".framework"):
189 if framework.startswith(nameDot):
191 elif framework.endswith(".dylib"):
192 if framework.startswith(libNameDot):
196 def getFrameworks(binaryPath, verbose):
198 print "Inspecting with otool: " + binaryPath
199 otool = subprocess.Popen(["otool", "-L", binaryPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
200 o_stdout, o_stderr = otool.communicate()
201 if otool.returncode != 0:
203 sys.stderr.write(o_stderr)
205 raise RuntimeError("otool failed with return code %d" % otool.returncode)
207 otoolLines = o_stdout.split("\n")
208 otoolLines.pop(0) # First line is the inspected binary
209 if ".framework" in binaryPath or binaryPath.endswith(".dylib"):
210 otoolLines.pop(0) # Frameworks and dylibs list themselves as a dependency.
213 for line in otoolLines:
214 info = FrameworkInfo.fromOtoolLibraryLine(line.strip())
217 print "Found framework:"
219 libraries.append(info)
223 def runInstallNameTool(action, *args):
224 subprocess.check_call(["install_name_tool", "-"+action] + list(args))
226 def changeInstallName(oldName, newName, binaryPath, verbose):
228 print "Using install_name_tool:"
229 print " in", binaryPath
230 print " change reference", oldName
232 runInstallNameTool("change", oldName, newName, binaryPath)
234 def changeIdentification(id, binaryPath, verbose):
236 print "Using install_name_tool:"
237 print " change identification in", binaryPath
239 runInstallNameTool("id", id, binaryPath)
241 def runStrip(binaryPath, verbose):
244 print " stripped", binaryPath
245 subprocess.check_call(["strip", "-x", binaryPath])
247 def copyFramework(framework, path, verbose):
248 if framework.sourceFilePath.startswith("Qt"):
249 #standard place for Nokia Qt installer's frameworks
250 fromPath = "/Library/Frameworks/" + framework.sourceFilePath
252 fromPath = framework.sourceFilePath
254 toDir = os.path.join(path, framework.destinationDirectory)
255 toPath = os.path.join(toDir, framework.binaryName)
257 if not os.path.exists(fromPath):
258 raise RuntimeError("No file at " + fromPath)
260 if os.path.exists(toPath):
261 return None # Already there
263 if not os.path.exists(toDir):
266 shutil.copy2(fromPath, toPath)
268 print "Copied:", fromPath
271 permissions = os.stat(toPath)
272 if not permissions.st_mode & stat.S_IWRITE:
273 os.chmod(toPath, permissions.st_mode | stat.S_IWRITE)
275 if not framework.isDylib(): # Copy resources for real frameworks
276 fromResourcesDir = framework.sourceResourcesDirectory
277 if os.path.exists(fromResourcesDir):
278 toResourcesDir = os.path.join(path, framework.destinationResourcesDirectory)
279 shutil.copytree(fromResourcesDir, toResourcesDir)
281 print "Copied resources:", fromResourcesDir
282 print " to:", toResourcesDir
283 elif framework.frameworkName.startswith("libQtGui"): # Copy qt_menu.nib (applies to non-framework layout)
284 qtMenuNibSourcePath = os.path.join(framework.frameworkDirectory, "Resources", "qt_menu.nib")
285 qtMenuNibDestinationPath = os.path.join(path, "Contents", "Resources", "qt_menu.nib")
286 if os.path.exists(qtMenuNibSourcePath) and not os.path.exists(qtMenuNibDestinationPath):
287 shutil.copytree(qtMenuNibSourcePath, qtMenuNibDestinationPath)
289 print "Copied for libQtGui:", qtMenuNibSourcePath
290 print " to:", qtMenuNibDestinationPath
294 def deployFrameworks(frameworks, bundlePath, binaryPath, strip, verbose, deploymentInfo=None):
295 if deploymentInfo is None:
296 deploymentInfo = DeploymentInfo()
298 while len(frameworks) > 0:
299 framework = frameworks.pop(0)
300 deploymentInfo.deployedFrameworks.append(framework.frameworkName)
303 print "Processing", framework.frameworkName, "..."
305 # Get the Qt path from one of the Qt frameworks
306 if deploymentInfo.qtPath is None and framework.isQtFramework():
307 deploymentInfo.detectQtPath(framework.frameworkDirectory)
309 if framework.installName.startswith("@executable_path"):
311 print framework.frameworkName, "already deployed, skipping."
314 # install_name_tool the new id into the binary
315 changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose)
317 # Copy farmework to app bundle.
318 deployedBinaryPath = copyFramework(framework, bundlePath, verbose)
319 # Skip the rest if already was deployed.
320 if deployedBinaryPath is None:
324 runStrip(deployedBinaryPath, verbose)
326 # install_name_tool it a new id.
327 changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose)
328 # Check for framework dependencies
329 dependencies = getFrameworks(deployedBinaryPath, verbose)
331 for dependency in dependencies:
332 changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose)
334 # Deploy framework if necessary.
335 if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks:
336 frameworks.append(dependency)
338 return deploymentInfo
340 def deployFrameworksForAppBundle(applicationBundle, strip, verbose):
341 frameworks = getFrameworks(applicationBundle.binaryPath, verbose)
342 if len(frameworks) == 0 and verbose >= 1:
343 print "Warning: Could not find any external frameworks to deploy in %s." % (applicationBundle.path)
344 return DeploymentInfo()
346 return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose)
348 def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose):
349 # Lookup available plugins, exclude unneeded
351 for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath):
352 pluginDirectory = os.path.relpath(dirpath, deploymentInfo.pluginPath)
353 if pluginDirectory == "designer":
354 # Skip designer plugins
356 elif pluginDirectory == "phonon" or pluginDirectory == "phonon_backend":
357 # Deploy the phonon plugins only if phonon is in use
358 if not deploymentInfo.usesFramework("phonon"):
360 elif pluginDirectory == "sqldrivers":
361 # Deploy the sql plugins only if QtSql is in use
362 if not deploymentInfo.usesFramework("QtSql"):
364 elif pluginDirectory == "script":
365 # Deploy the script plugins only if QtScript is in use
366 if not deploymentInfo.usesFramework("QtScript"):
368 elif pluginDirectory == "qmltooling":
369 # Deploy the qml plugins only if QtDeclarative is in use
370 if not deploymentInfo.usesFramework("QtDeclarative"):
372 elif pluginDirectory == "bearer":
373 # Deploy the bearer plugins only if QtNetwork is in use
374 if not deploymentInfo.usesFramework("QtNetwork"):
377 for pluginName in filenames:
378 pluginPath = os.path.join(pluginDirectory, pluginName)
379 if pluginName.endswith("_debug.dylib"):
382 elif pluginPath == "imageformats/libqsvg.dylib" or pluginPath == "iconengines/libqsvgicon.dylib":
383 # Deploy the svg plugins only if QtSvg is in use
384 if not deploymentInfo.usesFramework("QtSvg"):
386 elif pluginPath == "accessible/libqtaccessiblecompatwidgets.dylib":
387 # Deploy accessibility for Qt3Support only if the Qt3Support is in use
388 if not deploymentInfo.usesFramework("Qt3Support"):
390 elif pluginPath == "graphicssystems/libqglgraphicssystem.dylib":
391 # Deploy the opengl graphicssystem plugin only if QtOpenGL is in use
392 if not deploymentInfo.usesFramework("QtOpenGL"):
395 plugins.append((pluginDirectory, pluginName))
397 for pluginDirectory, pluginName in plugins:
399 print "Processing plugin", os.path.join(pluginDirectory, pluginName), "..."
401 sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName)
402 destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory)
403 if not os.path.exists(destinationDirectory):
404 os.makedirs(destinationDirectory)
406 destinationPath = os.path.join(destinationDirectory, pluginName)
407 shutil.copy2(sourcePath, destinationPath)
409 print "Copied:", sourcePath
410 print " to:", destinationPath
413 runStrip(destinationPath, verbose)
415 dependencies = getFrameworks(destinationPath, verbose)
417 for dependency in dependencies:
418 changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose)
420 # Deploy framework if necessary.
421 if dependency.frameworkName not in deploymentInfo.deployedFrameworks:
422 deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
425 translations=Resources
429 ap = ArgumentParser(description="""Improved version of macdeployqt.
431 Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .dmg file.
432 Note, that the "dist" folder will be deleted before deploying on each run.
434 Optionally, Qt translation files (.qm) and additional resources can be added to the bundle.""")
436 ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed")
437 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")
438 ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment")
439 ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries")
440 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")
441 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")
442 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")
443 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")
445 config = ap.parse_args()
447 verbose = config.verbose[0]
449 # ------------------------------------------------
451 app_bundle = config.app_bundle[0]
453 if not os.path.exists(app_bundle):
455 sys.stderr.write("Error: Could not find app bundle \"%s\"\n" % (app_bundle))
458 app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0]
460 # ------------------------------------------------
462 for p in config.add_resources:
464 print "Checking for \"%s\"..." % p
465 if not os.path.exists(p):
467 sys.stderr.write("Error: Could not find additional resource file \"%s\"\n" % (p))
470 # ------------------------------------------------
472 if len(config.fancy) == 1:
474 print "Fancy: Importing plistlib..."
479 sys.stderr.write("Error: Could not import plistlib which is required for fancy disk images.\n")
483 print "Fancy: Importing appscript..."
488 sys.stderr.write("Error: Could not import appscript which is required for fancy disk images.\n")
489 sys.stderr.write("Please install it e.g. with \"sudo easy_install appscript\".\n")
494 print "Fancy: Loading \"%s\"..." % p
495 if not os.path.exists(p):
497 sys.stderr.write("Error: Could not find fancy disk image plist at \"%s\"\n" % (p))
501 fancy = plistlib.readPlist(p)
504 sys.stderr.write("Error: Could not parse fancy disk image plist at \"%s\"\n" % (p))
508 assert not fancy.has_key("window_bounds") or (isinstance(fancy["window_bounds"], list) and len(fancy["window_bounds"]) == 4)
509 assert not fancy.has_key("background_picture") or isinstance(fancy["background_picture"], str)
510 assert not fancy.has_key("icon_size") or isinstance(fancy["icon_size"], int)
511 assert not fancy.has_key("applications_symlink") or isinstance(fancy["applications_symlink"], bool)
512 if fancy.has_key("items_position"):
513 assert isinstance(fancy["items_position"], dict)
514 for key, value in fancy["items_position"].iteritems():
515 assert isinstance(value, list) and len(value) == 2 and isinstance(value[0], int) and isinstance(value[1], int)
518 sys.stderr.write("Error: Bad format of fancy disk image plist at \"%s\"\n" % (p))
521 if fancy.has_key("background_picture"):
522 bp = fancy["background_picture"]
524 print "Fancy: Resolving background picture \"%s\"..." % bp
525 if not os.path.exists(bp):
526 bp = os.path.join(os.path.dirname(p), bp)
527 if not os.path.exists(bp):
529 sys.stderr.write("Error: Could not find background picture at \"%s\" or \"%s\"\n" % (fancy["background_picture"], bp))
532 fancy["background_picture"] = bp
536 # ------------------------------------------------
538 if os.path.exists("dist"):
540 print "+ Removing old dist folder +"
542 shutil.rmtree("dist")
544 # ------------------------------------------------
546 target = os.path.join("dist", app_bundle)
549 print "+ Copying source bundle +"
551 print app_bundle, "->", target
554 shutil.copytree(app_bundle, target)
556 applicationBundle = ApplicationBundleInfo(target)
558 # ------------------------------------------------
561 print "+ Deploying frameworks +"
564 deploymentInfo = deployFrameworksForAppBundle(applicationBundle, config.strip, verbose)
565 if deploymentInfo.qtPath is None:
566 deploymentInfo.qtPath = os.getenv("QTDIR", None)
567 if deploymentInfo.qtPath is None:
569 sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
570 config.plugins = False
571 except RuntimeError as e:
573 sys.stderr.write("Error: %s\n" % str(e))
576 # ------------------------------------------------
580 print "+ Deploying plugins +"
583 deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
584 except RuntimeError as e:
586 sys.stderr.write("Error: %s\n" % str(e))
589 # ------------------------------------------------
591 if len(config.add_qt_tr) == 0:
594 qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations")
595 add_qt_tr = ["qt_%s.qm" % lng for lng in config.add_qt_tr[0].split(",")]
596 for lng_file in add_qt_tr:
597 p = os.path.join(qt_tr_dir, lng_file)
599 print "Checking for \"%s\"..." % p
600 if not os.path.exists(p):
602 sys.stderr.write("Error: Could not find Qt translation file \"%s\"\n" % (lng_file))
605 # ------------------------------------------------
608 print "+ Installing qt.conf +"
610 f = open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb")
614 # ------------------------------------------------
616 if len(add_qt_tr) > 0 and verbose >= 2:
617 print "+ Adding Qt translations +"
619 for lng_file in add_qt_tr:
621 print os.path.join(qt_tr_dir, lng_file), "->", os.path.join(applicationBundle.resourcesPath, lng_file)
622 shutil.copy2(os.path.join(qt_tr_dir, lng_file), os.path.join(applicationBundle.resourcesPath, lng_file))
624 # ------------------------------------------------
626 if len(config.add_resources) > 0 and verbose >= 2:
627 print "+ Adding additional resources +"
629 for p in config.add_resources:
630 t = os.path.join(applicationBundle.resourcesPath, os.path.basename(p))
634 shutil.copytree(p, t)
638 # ------------------------------------------------
640 if config.dmg is not None:
641 def runHDIUtil(verb, image_basename, **kwargs):
642 hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"]
643 if kwargs.has_key("capture_stdout"):
644 del kwargs["capture_stdout"]
645 run = subprocess.check_output
648 hdiutil_args.append("-quiet")
650 hdiutil_args.append("-verbose")
651 run = subprocess.check_call
653 for key, value in kwargs.iteritems():
654 hdiutil_args.append("-" + key)
655 if not value is True:
656 hdiutil_args.append(str(value))
658 return run(hdiutil_args)
662 print "+ Creating .dmg disk image +"
664 print "+ Preparing .dmg disk image +"
667 dmg_name = config.dmg
669 spl = app_bundle_name.split(" ")
670 dmg_name = spl[0] + "".join(p.capitalize() for p in spl[1:])
674 runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname=app_bundle_name, ov=True)
675 except subprocess.CalledProcessError as e:
676 sys.exit(e.returncode)
679 print "Determining size of \"dist\"..."
681 for path, dirs, files in os.walk("dist"):
683 size += os.path.getsize(os.path.join(path, file))
684 size += int(size * 0.1)
687 print "Creating temp image for modification..."
689 runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=app_bundle_name, ov=True)
690 except subprocess.CalledProcessError as e:
691 sys.exit(e.returncode)
694 print "Attaching temp image..."
696 output = runHDIUtil("attach", dmg_name + ".temp", readwrite=True, noverify=True, noautoopen=True, capture_stdout=True)
697 except subprocess.CalledProcessError as e:
698 sys.exit(e.returncode)
700 m = re.search("/Volumes/(.+$)", output)
701 disk_root = m.group(0)
702 disk_name = m.group(1)
705 print "+ Applying fancy settings +"
707 if fancy.has_key("background_picture"):
708 bg_path = os.path.join(disk_root, os.path.basename(fancy["background_picture"]))
710 print fancy["background_picture"], "->", bg_path
711 shutil.copy2(fancy["background_picture"], bg_path)
715 if fancy.get("applications_symlink", False):
716 os.symlink("/Applications", os.path.join(disk_root, "Applications"))
718 finder = appscript.app("Finder")
719 disk = finder.disks[disk_name]
721 window = disk.container_window
722 window.current_view.set(appscript.k.icon_view)
723 window.toolbar_visible.set(False)
724 window.statusbar_visible.set(False)
725 if fancy.has_key("window_bounds"):
726 window.bounds.set(fancy["window_bounds"])
727 view_options = window.icon_view_options
728 view_options.arrangement.set(appscript.k.not_arranged)
729 if fancy.has_key("icon_size"):
730 view_options.icon_size.set(fancy["icon_size"])
731 if bg_path is not None:
732 view_options.background_picture.set(disk.files[os.path.basename(bg_path)])
733 if fancy.has_key("items_position"):
734 for name, position in fancy["items_position"].iteritems():
735 window.items[name].position.set(position)
737 if bg_path is not None:
738 subprocess.call(["SetFile", "-a", "V", bg_path])
739 disk.update(registering_applications=False)
744 print "+ Finalizing .dmg disk image +"
747 runHDIUtil("convert", dmg_name + ".temp", format="UDBZ", o=dmg_name + ".dmg", ov=True)
748 except subprocess.CalledProcessError as e:
749 sys.exit(e.returncode)
751 os.unlink(dmg_name + ".temp.dmg")
753 # ------------------------------------------------