Mac Application Deployment – dylib

Lately I’ve been battling away with the Mach-O binary file format. I am trying to create a Mac version of an application that depends on dylibs provided by wxWidgets and GraphicsMagick. I started this quest by finding out the hard way that mac binaries (libs and applications) store the path to their dylib dependencies in the binary itself. As you can imagine this is a big hassle for app deployment as you force every user wanting to install the app to have the same exact /usr/lib and /opt/local/lib as your machines does.

After a bit of scavenging in forums I found out that otool allows you to print the list of dependencies of a binary and that install_name_tool allows you to change them. Bearing this in mind I now wanted the simplest way I could get to change the list of dependencies both in my binary and inside all of its dependency dylibs. I ended up writing the python script below for this.

#!/usr/bin/python
# 
# Uses otool and install_name_tool to change a given path in the list of 
# dylib dependencies to another (hopefully relative) path to ease deployment.
# If this is applied to dylib files it will also change their ID.
#
# Author: Rui Barbosa Martins (ruibm@ruibm.com)

import os.path
import re
import subprocess
import sys

def RunCmd(cmd):
	obj = subprocess.Popen(cmd, shell=True, bufsize=42000, stdout=subprocess.PIPE)
	out = ""
	while (True) :
		content = obj.stdout.read()
		if content:
			out += content
		else:
			break
	ret_code = obj.wait()
	if ret_code == 0:
		return out
	else:
		return None
	
	
def GetDependencies(file):
	cmd = "otool -L " + file
	output = RunCmd(cmd)
	if not output:
		raise Exception("Problem running otool. [%s]" % (cmd))
	output = output.split("n")
	deps = list()
	for line in output:
		m = re.match("(.*)\(compatibility version.*", line);		
		if not m:
			continue
		dylib = m.group(1).strip()
		deps.append(dylib)
	return deps

	
def ChangeDependecies(file, dependencies, replaceFrom, replaceTo):
	print "Changing dependencies in %s" % (file)
	fname = os.path.basename(file)
	for d in dependencies:
		if not replaceFrom in d:
			continue
		
		new = d.replace(replaceFrom, replaceTo)
		if fname == os.path.basename(d):
			cmd = "install_name_tool -id %s %s" % (new, file)
			print "ID: %s -> %s" % (d, new)
		else:			
			cmd = "install_name_tool -change %s %s %s" % (d, new, file)
			print "Change: %s -> %s" % (d, new)
		RunCmd(cmd)
		

def main(argv) :
	if len(argv) != 4:
		print "Usage: %s [file] [replaceFrom] [replaceTo]" % (argv[0])
	file = argv[1]
	replaceFrom = argv[2]
	replaceTo = argv[3]
	deps = GetDependencies(file)
	ChangeDependecies(file, deps, replaceFrom, replaceTo)

if __name__ == "__main__":
	main(sys.argv)
	
Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s