123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889 |
- #!/usr/bin/env python
- from __future__ import division, print_function, unicode_literals
- #
- # 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, time
- from string import Template
- 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.sourceVersionContentsDirectory = ""
- self.sourceContentsDirectory = ""
- self.destinationResourcesDirectory = ""
- self.destinationVersionContentsDirectory = ""
-
- 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.sourceContentsDirectory = os.path.join(info.frameworkPath, "Contents")
- info.sourceVersionContentsDirectory = os.path.join(info.frameworkPath, "Versions", info.version, "Contents")
- info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources")
- info.destinationContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Contents")
- info.destinationVersionContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Versions", info.version, "Contents")
-
- return info
-
- class ApplicationBundleInfo(object):
- def __init__(self, path):
- self.path = path
- appName = "Starwels-Qt"
- 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)
- otoolbin=os.getenv("OTOOL", "otool")
- otool = subprocess.Popen([otoolbin, "-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.decode().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:
- line = line.replace("@loader_path", os.path.dirname(binaryPath))
- 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):
- installnametoolbin=os.getenv("INSTALLNAMETOOL", "install_name_tool")
- subprocess.check_call([installnametoolbin, "-"+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):
- stripbin=os.getenv("STRIP", "strip")
- if verbose >= 3:
- print("Using strip:")
- print(" stripped", binaryPath)
- subprocess.check_call([stripbin, "-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
-
- linkfrom = os.path.join(path, "Contents","Frameworks", framework.frameworkName, "Versions", "Current")
- linkto = framework.version
- if not os.path.exists(linkfrom):
- os.symlink(linkto, linkfrom)
- if verbose >= 2:
- print("Linked:", linkfrom, "->", linkto)
- fromResourcesDir = framework.sourceResourcesDirectory
- if os.path.exists(fromResourcesDir):
- toResourcesDir = os.path.join(path, framework.destinationResourcesDirectory)
- shutil.copytree(fromResourcesDir, toResourcesDir, symlinks=True)
- if verbose >= 3:
- print("Copied resources:", fromResourcesDir)
- print(" to:", toResourcesDir)
- fromContentsDir = framework.sourceVersionContentsDirectory
- if not os.path.exists(fromContentsDir):
- fromContentsDir = framework.sourceContentsDirectory
- if os.path.exists(fromContentsDir):
- toContentsDir = os.path.join(path, framework.destinationVersionContentsDirectory)
- shutil.copytree(fromContentsDir, toContentsDir, symlinks=True)
- if verbose >= 3:
- print("Copied Contents:", fromContentsDir)
- print(" to:", toContentsDir)
- 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, symlinks=True)
- 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") or framework.installName.startswith(bundlePath):
- 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 framework 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 = []
- if deploymentInfo.pluginPath is None:
- return
- 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" or pluginDirectory == "qml1tooling":
- # 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
- elif pluginDirectory == "position":
- # Deploy the position plugins only if QtPositioning is in use
- if not deploymentInfo.usesFramework("QtPositioning"):
- continue
- elif pluginDirectory == "sensors" or pluginDirectory == "sensorgestures":
- # Deploy the sensor plugins only if QtSensors is in use
- if not deploymentInfo.usesFramework("QtSensors"):
- continue
- elif pluginDirectory == "audio" or pluginDirectory == "playlistformats":
- # Deploy the audio plugins only if QtMultimedia is in use
- if not deploymentInfo.usesFramework("QtMultimedia"):
- continue
- elif pluginDirectory == "mediaservice":
- # Deploy the mediaservice plugins only if QtMultimediaWidgets is in use
- if not deploymentInfo.usesFramework("QtMultimediaWidgets"):
- 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
- elif pluginPath == "accessible/libqtaccessiblequick.dylib":
- # Deploy the accessible qtquick plugin only if QtQuick is in use
- if not deploymentInfo.usesFramework("QtQuick"):
- 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.
-
- Also optionally signs the .app bundle; set the CODESIGNARGS environment variable to pass arguments
- to the codesign tool.
- E.g. CODESIGNARGS='--sign "Developer ID Application: ..." --keychain /encrypted/foo.keychain'""")
-
- 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("-sign", dest="sign", action="store_true", default=False, help="sign .app bundle with codesign tool")
- 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 resources; the language list must be separated with commas, not with whitespace")
- ap.add_argument("-translations-dir", nargs=1, metavar="path", default=None, help="Path to Qt's translation files")
- 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")
- ap.add_argument("-volname", nargs=1, metavar="volname", default=[], help="custom volume name for dmg")
-
- 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]
-
- # ------------------------------------------------
- translations_dir = None
- if config.translations_dir and config.translations_dir[0]:
- if os.path.exists(config.translations_dir[0]):
- translations_dir = config.translations_dir[0]
- else:
- if verbose >= 1:
- sys.stderr.write("Error: Could not find translation dir \"%s\"\n" % (translations_dir))
- sys.exit(1)
- # ------------------------------------------------
-
- 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)
-
- 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 "window_bounds" not in fancy or (isinstance(fancy["window_bounds"], list) and len(fancy["window_bounds"]) == 4)
- assert "background_picture" not in fancy or isinstance(fancy["background_picture"], str)
- assert "icon_size" not in fancy or isinstance(fancy["icon_size"], int)
- assert "applications_symlink" not in fancy or isinstance(fancy["applications_symlink"], bool)
- if "items_position" in fancy:
- assert isinstance(fancy["items_position"], dict)
- for key, value in fancy["items_position"].items():
- 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 "background_picture" in fancy:
- 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")
-
- # ------------------------------------------------
-
- if len(config.volname) == 1:
- volname = config.volname[0]
- else:
- volname = app_bundle_name
-
- # ------------------------------------------------
-
- target = os.path.join("dist", "Starwels-Qt.app")
-
- if verbose >= 2:
- print("+ Copying source bundle +")
- if verbose >= 3:
- print(app_bundle, "->", target)
-
- os.mkdir("dist")
- shutil.copytree(app_bundle, target, symlinks=True)
-
- 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(1)
-
- # ------------------------------------------------
-
- 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(1)
-
- # ------------------------------------------------
-
- if len(config.add_qt_tr) == 0:
- add_qt_tr = []
- else:
- if translations_dir is not None:
- qt_tr_dir = translations_dir
- else:
- if deploymentInfo.qtPath is not None:
- qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations")
- else:
- sys.stderr.write("Error: Could not find Qt translation path\n")
- sys.exit(1)
- 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 +")
-
- with open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb") as f:
- f.write(qt_conf.encode())
-
- # ------------------------------------------------
-
- 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, symlinks=True)
- else:
- shutil.copy2(p, t)
-
- # ------------------------------------------------
-
- if config.sign and 'CODESIGNARGS' not in os.environ:
- print("You must set the CODESIGNARGS environment variable. Skipping signing.")
- elif config.sign:
- if verbose >= 1:
- print("Code-signing app bundle %s"%(target,))
- subprocess.check_call("codesign --force %s %s"%(os.environ['CODESIGNARGS'], target), shell=True)
-
- # ------------------------------------------------
-
- if config.dmg is not None:
-
- #Patch in check_output for Python 2.6
- if "check_output" not in dir( subprocess ):
- def f(*popenargs, **kwargs):
- if 'stdout' in kwargs:
- raise ValueError('stdout argument not allowed, it will be overridden.')
- process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
- output, unused_err = process.communicate()
- retcode = process.poll()
- if retcode:
- cmd = kwargs.get("args")
- if cmd is None:
- cmd = popenargs[0]
- raise CalledProcessError(retcode, cmd)
- return output
- subprocess.check_output = f
-
- def runHDIUtil(verb, image_basename, **kwargs):
- hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"]
- if "capture_stdout" in kwargs:
- 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.items():
- 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=volname, 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.15)
-
- if verbose >= 3:
- print("Creating temp image for modification...")
- try:
- runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=volname, 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.decode())
- disk_root = m.group(0)
- disk_name = m.group(1)
-
- if verbose >= 2:
- print("+ Applying fancy settings +")
-
- if "background_picture" in fancy:
- bg_path = os.path.join(disk_root, ".background", os.path.basename(fancy["background_picture"]))
- os.mkdir(os.path.dirname(bg_path))
- 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"))
-
- # The Python appscript package broke with OSX 10.8 and isn't being fixed.
- # So we now build up an AppleScript string and use the osascript command
- # to make the .dmg file pretty:
- appscript = Template( """
- on run argv
- tell application "Finder"
- tell disk "$disk"
- open
- set current view of container window to icon view
- set toolbar visible of container window to false
- set statusbar visible of container window to false
- set the bounds of container window to {$window_bounds}
- set theViewOptions to the icon view options of container window
- set arrangement of theViewOptions to not arranged
- set icon size of theViewOptions to $icon_size
- $background_commands
- $items_positions
- close -- close/reopen works around a bug...
- open
- update without registering applications
- delay 5
- eject
- end tell
- end tell
- end run
- """)
-
- itemscript = Template('set position of item "${item}" of container window to {${position}}')
- items_positions = []
- if "items_position" in fancy:
- for name, position in fancy["items_position"].items():
- params = { "item" : name, "position" : ",".join([str(p) for p in position]) }
- items_positions.append(itemscript.substitute(params))
-
- params = {
- "disk" : volname,
- "window_bounds" : "300,300,800,620",
- "icon_size" : "96",
- "background_commands" : "",
- "items_positions" : "\n ".join(items_positions)
- }
- if "window_bounds" in fancy:
- params["window_bounds"] = ",".join([str(p) for p in fancy["window_bounds"]])
- if "icon_size" in fancy:
- params["icon_size"] = str(fancy["icon_size"])
- if bg_path is not None:
- # Set background file, then call SetFile to make it invisible.
- # (note: making it invisible first makes set background picture fail)
- bgscript = Template("""set background picture of theViewOptions to file ".background:$bgpic"
- do shell script "SetFile -a V /Volumes/$disk/.background/$bgpic" """)
- params["background_commands"] = bgscript.substitute({"bgpic" : os.path.basename(bg_path), "disk" : params["disk"]})
-
- s = appscript.substitute(params)
- if verbose >= 2:
- print("Running AppleScript:")
- print(s)
-
- p = subprocess.Popen(['osascript', '-'], stdin=subprocess.PIPE)
- p.communicate(input=s.encode('utf-8'))
- if p.returncode:
- print("Error running osascript.")
-
- if verbose >= 2:
- print("+ Finalizing .dmg disk image +")
- time.sleep(5)
-
- 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)
|