aboutsummaryrefslogtreecommitdiff
path: root/contrib/macdeploy/macdeployqtplus
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/macdeploy/macdeployqtplus')
-rwxr-xr-xcontrib/macdeploy/macdeployqtplus252
1 files changed, 194 insertions, 58 deletions
diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus
index e159f9bbc3..a625987ca7 100755
--- a/contrib/macdeploy/macdeployqtplus
+++ b/contrib/macdeploy/macdeployqtplus
@@ -17,8 +17,8 @@
# 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
+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
@@ -37,7 +37,10 @@ class FrameworkInfo(object):
self.sourceFilePath = ""
self.destinationDirectory = ""
self.sourceResourcesDirectory = ""
+ self.sourceVersionContentsDirectory = ""
+ self.sourceContentsDirectory = ""
self.destinationResourcesDirectory = ""
+ self.destinationVersionContentsDirectory = ""
def __eq__(self, other):
if self.__class__ == other.__class__:
@@ -141,14 +144,18 @@ class FrameworkInfo(object):
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 = os.path.splitext(os.path.basename(path))[0]
+ appName = "Bitcoin-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)
@@ -169,7 +176,12 @@ class DeploymentInfo(object):
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):
@@ -190,7 +202,8 @@ class DeploymentInfo(object):
def getFrameworks(binaryPath, verbose):
if verbose >= 3:
print "Inspecting with otool: " + binaryPath
- otool = subprocess.Popen(["otool", "-L", binaryPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ 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:
@@ -205,6 +218,7 @@ def getFrameworks(binaryPath, verbose):
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:
@@ -215,7 +229,8 @@ def getFrameworks(binaryPath, verbose):
return libraries
def runInstallNameTool(action, *args):
- subprocess.check_call(["install_name_tool", "-"+action] + list(args))
+ installnametoolbin=os.getenv("INSTALLNAMETOOL", "install_name_tool")
+ subprocess.check_call([installnametoolbin, "-"+action] + list(args))
def changeInstallName(oldName, newName, binaryPath, verbose):
if verbose >= 3:
@@ -233,13 +248,18 @@ def changeIdentification(id, binaryPath, verbose):
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(["strip", "-x", binaryPath])
+ subprocess.check_call([stripbin, "-x", binaryPath])
def copyFramework(framework, path, verbose):
- fromPath = framework.sourceFilePath
+ 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)
@@ -262,18 +282,35 @@ def copyFramework(framework, path, verbose):
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)
+ 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)
+ contentslinkfrom = os.path.join(path, framework.destinationContentsDirectory)
+ 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)
+ shutil.copytree(qtMenuNibSourcePath, qtMenuNibDestinationPath, symlinks=True)
if verbose >= 3:
print "Copied for libQtGui:", qtMenuNibSourcePath
print " to:", qtMenuNibDestinationPath
@@ -295,7 +332,7 @@ def deployFrameworks(frameworks, bundlePath, binaryPath, strip, verbose, deploym
if deploymentInfo.qtPath is None and framework.isQtFramework():
deploymentInfo.detectQtPath(framework.frameworkDirectory)
- if framework.installName.startswith("@executable_path"):
+ if framework.installName.startswith("@executable_path") or framework.installName.startswith(bundlePath):
if verbose >= 2:
print framework.frameworkName, "already deployed, skipping."
continue
@@ -337,12 +374,14 @@ def deployFrameworksForAppBundle(applicationBundle, 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":
+ elif pluginDirectory == "phonon" or pluginDirectory == "phonon_backend":
# Deploy the phonon plugins only if phonon is in use
if not deploymentInfo.usesFramework("phonon"):
continue
@@ -354,7 +393,7 @@ def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose):
# Deploy the script plugins only if QtScript is in use
if not deploymentInfo.usesFramework("QtScript"):
continue
- elif pluginDirectory == "qmltooling":
+ elif pluginDirectory == "qmltooling" or pluginDirectory == "qml1tooling":
# Deploy the qml plugins only if QtDeclarative is in use
if not deploymentInfo.usesFramework("QtDeclarative"):
continue
@@ -362,7 +401,23 @@ def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose):
# 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"):
@@ -380,7 +435,11 @@ def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose):
# 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:
@@ -411,8 +470,8 @@ def deployPlugins(appBundleInfo, deploymentInfo, strip, verbose):
deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
qt_conf="""[Paths]
-translations=Resources
-plugins=PlugIns
+Translations=Resources
+Plugins=PlugIns
"""
ap = ArgumentParser(description="""Improved version of macdeployqt.
@@ -420,15 +479,21 @@ 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.""")
+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 ressources; 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")
config = ap.parse_args()
@@ -447,6 +512,15 @@ if not os.path.exists(app_bundle):
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:
@@ -468,16 +542,6 @@ if len(config.fancy) == 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
@@ -532,7 +596,7 @@ if os.path.exists("dist"):
# ------------------------------------------------
-target = os.path.join("dist", app_bundle)
+target = os.path.join("dist", "Bitcoin Core.app")
if verbose >= 2:
print "+ Copying source bundle +"
@@ -540,7 +604,7 @@ if verbose >= 3:
print app_bundle, "->", target
os.mkdir("dist")
-shutil.copytree(app_bundle, target)
+shutil.copytree(app_bundle, target, symlinks=True)
applicationBundle = ApplicationBundleInfo(target)
@@ -560,7 +624,7 @@ try:
except RuntimeError as e:
if verbose >= 1:
sys.stderr.write("Error: %s\n" % str(e))
- sys.exit(ret)
+ sys.exit(1)
# ------------------------------------------------
@@ -573,14 +637,21 @@ if config.plugins:
except RuntimeError as e:
if verbose >= 1:
sys.stderr.write("Error: %s\n" % str(e))
- sys.exit(ret)
+ sys.exit(1)
# ------------------------------------------------
if len(config.add_qt_tr) == 0:
add_qt_tr = []
else:
- qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations")
+ 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)
@@ -620,13 +691,39 @@ for p in config.add_resources:
if verbose >= 3:
print p, "->", t
if os.path.isdir(p):
- shutil.copytree(p, t)
+ 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 kwargs.has_key("capture_stdout"):
@@ -670,7 +767,7 @@ if config.dmg is not None:
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)
+ size += int(size * 0.15)
if verbose >= 3:
print "Creating temp image for modification..."
@@ -694,7 +791,8 @@ if config.dmg is not None:
print "+ Applying fancy settings +"
if fancy.has_key("background_picture"):
- bg_path = os.path.join(disk_root, os.path.basename(fancy["background_picture"]))
+ 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)
@@ -704,33 +802,71 @@ if config.dmg is not 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)])
+ # 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 fancy.has_key("items_position"):
for name, position in fancy["items_position"].iteritems():
- window.items[name].position.set(position)
- disk.close()
+ params = { "item" : name, "position" : ",".join([str(p) for p in position]) }
+ items_positions.append(itemscript.substitute(params))
+
+ params = {
+ "disk" : "Bitcoin-Core",
+ "window_bounds" : "300,300,800,620",
+ "icon_size" : "96",
+ "background_commands" : "",
+ "items_positions" : "\n ".join(items_positions)
+ }
+ if fancy.has_key("window_bounds"):
+ params["window.bounds"] = ",".join([str(p) for p in fancy["window_bounds"]])
+ if fancy.has_key("icon_size"):
+ params["icon_size"] = str(fancy["icon_size"])
if bg_path is not None:
- subprocess.call(["SetFile", "-a", "V", bg_path])
- disk.update(registering_applications=False)
- sleep(2)
- disk.eject()
-
+ # 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)
+ 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)