Browse Source

miniupnpc Porfile removed; new and improved macdeployqtplus

* My patch for miniupnpc has made it into the latest MacPorts release:
* Documentation has been changed appropriately
* New pure-Python macdeployqt; leverages all problems with the stock macdeployqt
p2k 10 years ago

+ 433
- 31
contrib/macdeploy/macdeployqtplus View File

@@ -21,12 +21,397 @@ import subprocess, sys, re, os, shutil, 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__
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,
def isDylib(self):
return self.frameworkName.endswith(".dylib")
def isQtFramework(self):
if self.isDylib():
return self.frameworkName.startswith("libQt")
return self.frameworkName.startswith("Qt")
reOLine = re.compile(r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$')
bundleFrameworkDirectory = "Contents/Frameworks"
bundleBinaryDirectory = "Contents/MacOS"
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 =
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
parts = path.split("/")
i = 0
# Search for the .framework directory
for part in parts:
if part.endswith(".framework"):
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")
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:
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
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):
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):
shutil.copy2(fromPath, toPath)
if verbose >= 3:
print "Copied:", fromPath
print " to:", toPath
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)
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():
if framework.installName.startswith("@executable_path"):
if verbose >= 2:
print framework.frameworkName, "already deployed, skipping."
# 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:
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:
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()
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
elif pluginDirectory == "phonon":
# Deploy the phonon plugins only if phonon is in use
if not deploymentInfo.usesFramework("phonon"):
elif pluginDirectory == "sqldrivers":
# Deploy the sql plugins only if QtSql is in use
if not deploymentInfo.usesFramework("QtSql"):
elif pluginDirectory == "script":
# Deploy the script plugins only if QtScript is in use
if not deploymentInfo.usesFramework("QtScript"):
elif pluginDirectory == "qmltooling":
# Deploy the qml plugins only if QtDeclarative is in use
if not deploymentInfo.usesFramework("QtDeclarative"):
elif pluginDirectory == "bearer":
# Deploy the bearer plugins only if QtNetwork is in use
if not deploymentInfo.usesFramework("QtNetwork"):
for pluginName in filenames:
pluginPath = os.path.join(pluginDirectory, pluginName)
if pluginName.endswith("_debug.dylib"):
# Skip debug plugins
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"):
elif pluginPath == "accessible/libqtaccessiblecompatwidgets.dylib":
# Deploy accessibility for Qt3Support only if the Qt3Support is in use
if not deploymentInfo.usesFramework("Qt3Support"):
elif pluginPath == "graphicssystems/libqglgraphicssystem.dylib":
# Deploy the opengl graphicssystem plugin only if QtOpenGL is in use
if not deploymentInfo.usesFramework("QtOpenGL"):
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):
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)


ap = ArgumentParser(description="""Front-end to macdeployqt with some additional functions.
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.
@@ -69,22 +454,6 @@ for p in config.add_resources:

# ------------------------------------------------

if len(config.add_qt_tr) == 0:
add_qt_tr = []
qt_tr_dir = os.path.join(os.getenv("QTDIR", ""), "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))

# ------------------------------------------------

if len(config.fancy) == 1:
if verbose >= 3:
print "Fancy: Importing plistlib..."
@@ -160,7 +529,6 @@ if os.path.exists("dist"):
# ------------------------------------------------

target = os.path.join("dist", app_bundle)
target_res = os.path.join(target, "Contents", "Resources")

if verbose >= 2:
print "+ Copying source bundle +"
@@ -170,27 +538,61 @@ if verbose >= 3:
shutil.copytree(app_bundle, target)

# ------------------------------------------------
applicationBundle = ApplicationBundleInfo(target)

macdeployqt_args = ["macdeployqt", target, "-verbose=%d" % verbose]
if not config.plugins:
if not config.strip:
# ------------------------------------------------

if verbose >= 2:
print "+ Running macdeployqt +"
print "+ Deploying frameworks +"

ret =
if ret != 0:
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))

# ------------------------------------------------

if config.plugins:
if verbose >= 2:
print "+ Deploying plugins +"
deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
except RuntimeError as e:
if verbose >= 1:
sys.stderr.write("Error: %s\n" % str(e))

# ------------------------------------------------

if len(config.add_qt_tr) == 0:
add_qt_tr = []
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))

# ------------------------------------------------

if verbose >= 2:
print "+ Installing qt.conf +"

f = open(os.path.join(target_res, "qt.conf"), "wb")
f = open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb")

@@ -201,8 +603,8 @@ if len(add_qt_tr) > 0 and verbose >= 2:

for lng_file in add_qt_tr:
if verbose >= 3:
print os.path.join(qt_tr_dir, lng_file), "->", os.path.join(target_res, lng_file)
shutil.copy2(os.path.join(qt_tr_dir, lng_file), os.path.join(target_res, lng_file))
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))

# ------------------------------------------------

@@ -210,7 +612,7 @@ if len(config.add_resources) > 0 and verbose >= 2:
print "+ Adding additional resources +"

for p in config.add_resources:
t = os.path.join(target_res, os.path.basename(p))
t = os.path.join(applicationBundle.resourcesPath, os.path.basename(p))
if verbose >= 3:
print p, "->", t
if os.path.isdir(p):

+ 2
- 2
contrib/macdeploy/notes.txt View File

@@ -6,7 +6,7 @@ You will need the appscript package for the fancy disk image creation to work.
Install it by invoking "sudo easy_install appscript".

Ths script should be invoked in the target directory like this:
$source_dir/contrib/macdeploy/macdeployqtplus -add-qt-tr de,es,ru -dmg -fancy $source_dir/contrib/macdeploy/fancy.plist
$source_dir/contrib/macdeploy/macdeployqtplus -add-qt-tr da,de,es,hu,ru,uk,zh_CN,zh_TW -dmg -fancy $source_dir/contrib/macdeploy/fancy.plist -verbose 2

During the process, the disk image window will pop up briefly where the fancy
settings are applied. This is normal, please do not interfere.
@@ -19,7 +19,7 @@ Fill in the following.
Enable custom process step: [x]
Command: %{sourceDir}/contrib/macdeploy/macdeployqtplus
Working directory: %{buildDir}
Command arguments: -add-qt-tr de,ru -dmg -fancy %{sourceDir}/contrib/macdeploy/fancy.plist
Command arguments: -add-qt-tr da,de,es,hu,ru,uk,zh_CN,zh_TW -dmg -fancy %{sourceDir}/contrib/macdeploy/fancy.plist -verbose 2

After that you can start the deployment process through the menu with
Build -> Deploy Project "bitcoin-qt"

+ 0
- 43
contrib/miniupnpc/Portfile View File

@@ -1,43 +0,0 @@
# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:filetype=tcl:et:sw=4:ts=4:sts=4
# $Id$

PortSystem 1.0

name miniupnpc
epoch 2
version 1.6
revision 2
categories net
platforms darwin
license BSD
maintainers singingwolfboy openmaintainer
description Lightweight client for UPnP protocol
long_description \
checksums md5 88055f2d4a061cfd4cfe25a9eae22f67 \
sha1 ef8f2edb17f2e7c5b8dc67ee80a65c199d823e0a \
rmd160 d86b75b331a3fb5525c71708548f311977c0598f

use_configure no

variant universal {}
if {[variant_isset universal]} {
set archflags ${configure.universal_cflags}
} else {
set archflags ${configure.cc_archflags}

build.args-append CC="${} ${archflags}"

post-patch {
reinplace "s|-Wl,-install_name,|-Wl,-install_name,${prefix}/lib/|" ${worksrcpath}/Makefile

destroot.destdir PREFIX=${prefix} INSTALLPREFIX=${destroot}${prefix}

livecheck.type regex
livecheck.regex ${name}-(\\d+(\\.\\d{1,4})+)${extract.suffix}

+ 1
- 6
doc/build-osx.txt View File

@@ -36,12 +36,7 @@ git clone bitcoin

3. Install dependencies from MacPorts

sudo port install boost db48 openssl

Install the right version of miniupnpc:
pushd bitcoin/contrib/minipupnpc; sudo port install; popd
(this will be unnecessary soon, you will just port install miniupnpc
along with the rest of the dependencies).
sudo port install boost db48 openssl miniupnpc

Optionally install qrencode (and set USE_QRCODE=1):
sudo port install qrencode

+ 1
- 9
doc/readme-qt.rst View File

@@ -92,7 +92,7 @@ Mac OS X

sudo port selfupdate
sudo port install boost db48
sudo port install boost db48 miniupnpc

- Open the .pro file in Qt Creator and build as normal (cmd-B)

@@ -127,14 +127,6 @@ Set USE_UPNP to a different value to control this:
| USE_UPNP=1 | build with UPnP support turned on by default at runtime. |

Mac OS X users: miniupnpc is currently outdated on MacPorts. An updated Portfile is provided in contrib/miniupnpc within this project.
You can execute the following commands in a terminal to install it:


cd <location of bitcoin-qt>/contrib/miniupnpc
sudo port install

Notification support for recent (k)ubuntu versions

+ 4
- 3
doc/release-process.txt View File

@@ -67,14 +67,15 @@
rm -rf bitcoin-${VERSION}-win32

* perform Mac build
See this blog post for how Gavin set up his build environment and
patched macdeployqt to build the OSX release:
See this blog post for how Gavin set up his build environment to build the OSX
release; note that a patched version of macdeployqt is not needed anymore, as
the required functionality and fixes are implemented directly in macdeployqtplus:
qmake USE_SSL=1 USE_UPNP=1
export QTDIR=/opt/local/share/qt4 # needed to find translations/qt_*.qm files
T=$(contrib/ $QTDIR/translations src/qt/locale)
contrib/macdeploy/macdeployqtplus -add-qt-tr $T -dmg -fancy contrib/macdeploy/fancy.plist
contrib/macdeploy/macdeployqtplus -add-qt-tr $T -dmg -fancy contrib/macdeploy/fancy.plist

Build output expected: