Skip to content
Browse files

Extend macOS script

macdeployqt seems to work even less in Qt 5.7.1.  It still copies all
library dependencies into the bundle but does not reliably update shared
library IDs or change absolute paths to bundle relative paths.

Extend further to handle these tasks.

Signed-off-by: Stefan Hajnoczi <>
  • Loading branch information...
stefanha committed Jan 2, 2017
1 parent d7b1652 commit 050eaa44a5942ceb374a6c127cc098d7a97c8cdc
Showing with 45 additions and 11 deletions.
  1. +45 −11 qtclient/installer/macosx/
@@ -25,45 +25,79 @@
import os import os
import re import re

DEPENDENCY_RE = re.compile(r'\s+(/usr/local/.+\.dylib) \(compatibility version [^,]+, current version [^)]+\)') DEPENDENCY_RE = re.compile(r'\s+(/usr/local/[^\s]+) \(compatibility version [^,]+, current version [^)]+\)')

executable_paths = {} processed = set()

def usage(argv): def usage(argv):
print 'usage: %s APP_BUNDLE_PATH' % argv[0] print 'usage: %s APP_BUNDLE_PATH' % argv[0]
sys.exit(1) sys.exit(1)

def process_dylib(filename): def get_executable_rel_path(filename):
components = filename.split(os.sep)
if 'lib' in components:
idx = components.index('lib')
libdir = 'Frameworks'
elif 'plugins' in components:
idx = components.index('plugins')
libdir = 'PlugIns'
raise ValueError('get_bundle_path: unable to handle file %s' % filename)
return os.path.join('..', libdir, *components[idx + 1:])

def process_macho(filename):
if filename in processed:

print 'Processing %s...' % filename print 'Processing %s...' % filename

# Update shared library id
output = subprocess.check_output(['otool', '-D', filename], stderr=subprocess.STDOUT)
macho_id = output.splitlines()[-1]
if macho_id.startswith('/usr/local/'):
new_id = os.path.join('@executable_path', '..', get_executable_rel_path(macho_id))
print 'Updating shared library id %s to %s...' % (macho_id, new_id)
subprocess.check_call(['install_name_tool', '-id', new_id, filename])

# Change dependencies to bundle-relative paths
pending = set()
output = subprocess.check_output(['otool', '-L', filename], stderr=subprocess.STDOUT) output = subprocess.check_output(['otool', '-L', filename], stderr=subprocess.STDOUT)
for line in output.splitlines(): for line in output.splitlines():
m = DEPENDENCY_RE.match(line) m = DEPENDENCY_RE.match(line)
if not m: if not m:
continue continue

dependency = dependency =
new_path = executable_paths[os.path.basename(dependency)] rel_path = get_executable_rel_path(dependency)

pending.add(os.path.join(executable_dir, rel_path))

new_path = os.path.join('@executable_path', rel_path)

print 'Changing %s to %s...' % (dependency, new_path) print 'Changing %s to %s...' % (dependency, new_path)
subprocess.check_call(['install_name_tool', '-change', dependency, new_path, filename]) subprocess.check_call(['install_name_tool', '-change', dependency, new_path, filename])

for path in pending:

def main(argv): def main(argv):
global executable_dir

if len(argv) != 2: if len(argv) != 2:
usage(argv) usage(argv)

executable_dir = os.path.join(argv[1], 'Contents', 'MacOS') executable_dir = os.path.join(argv[1], 'Contents', 'MacOS')
dylibs = []
for dirpath, dirnames, filenames in os.walk(executable_dir):
for filename in filenames:
path = os.path.join(dirpath, filename)

for dirpath, dirnames, filenames in os.walk(argv[1]): for dirpath, dirnames, filenames in os.walk(argv[1]):
for filename in filter(lambda s: s.endswith('.dylib'), filenames): for filename in filter(lambda s: s.endswith('.dylib'), filenames):
path = os.path.join(dirpath, filename) path = os.path.join(dirpath, filename)
rel_path = os.path.relpath(path, executable_dir) process_macho(path)
executable_paths[filename] = os.path.join('@executable_path', rel_path)

for filename in dylibs:

return 0 return 0

0 comments on commit 050eaa4

Please sign in to comment.
You can’t perform that action at this time.