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)