diff options
Diffstat (limited to 'OSX-package/linktools')
-rw-r--r-- | OSX-package/linktools/MachO.py | 215 | ||||
-rwxr-xr-x | OSX-package/linktools/ingest.py | 72 | ||||
-rwxr-xr-x | OSX-package/linktools/mkappbundle.py | 87 | ||||
-rwxr-xr-x | OSX-package/linktools/mkframework.py | 111 | ||||
-rwxr-xr-x | OSX-package/linktools/osxtools.py | 114 |
5 files changed, 599 insertions, 0 deletions
diff --git a/OSX-package/linktools/MachO.py b/OSX-package/linktools/MachO.py new file mode 100644 index 0000000..847d539 --- /dev/null +++ b/OSX-package/linktools/MachO.py @@ -0,0 +1,215 @@ +import os +import re +import shutil + + +def findFramework(path, name = None): + "find the framework folder for FW name" + if path == "" or path == "@executable_path" or path == "/": + return None + elif name == None: + return findFramework(os.path.dirname(path), + os.path.basename(path)) + elif os.path.basename(path) == name + ".framework": + return path + elif len(os.path.dirname(path)) >= len(path): + print "MachO.findFramework: Oops '" + path + "', '" + name + "'" + return None + else: + return findFramework(os.path.dirname(path), name) + + + +def stripPrefix(prefix, path): + "Returns the relative path r such that os.path.join(prefix, r) == path" + prefix = os.path.normpath(prefix) + prefixLen = len(prefix) + path = os.path.normpath(path) + if path[0 : prefixLen] == prefix: + if path[prefixLen] == os.sep: + return path[prefixLen+1 : ] + else: + return path[prefixLen : ] + else: + return path + + +class Executable: + "Represents an Mach-O executable." + + def __init__(self, path, kind): + self.Location = path + self.Kind = kind + + + def __repr__(self): + return self.Location + " (" + self.Kind + ")" + + + def getDependencies(self): + "Return a list of MachO.Fixes describing the dependencies." + "Uses otool -L" + f = os.popen("otool -L " + self.Location, "r") + result = [] + pat = re.compile("\s*([^(]*)\s\((.+)\)") + for line in f: + m = pat.match(line) + if m != None: + result.append(Fix(m.group(1), m.group(2))) + status = f.close() + return result + + def applyFixes(self, changes, log): + "Uses install_name_tool to change the links to dependencies." + "changes is a dictionary mapping links (as strings) to Fixes." + args = "" + for dep in self.getDependencies(): + if dep.Link in changes: + args = args + changes[dep.Link].getChange() + log.append(">> " + "install_name_tool " + args + self.Location) + if len(args) > 0: + os.system("install_name_tool " + args + self.Location) + pat = re.compile("(library)|(universal binary)") + if pat.search(self.Kind): + relName = os.path.basename(self.Location) # FIXME: rel to fw + log.append(">> " + "install_name_tool -id " + relName + + " " + self.Location) + os.system("install_name_tool -id " + relName + + " " + self.Location) + + +def findExecutables(bundleDir): + "Return a list of MachO.Executables found in bundleDir" + result = [] + pat = re.compile("Mach-O (.+)") + for root, dirs, files in os.walk(bundleDir): + for n in files: + p = os.path.join(root, n) + f = os.popen("file -b " + p, "r") + m = pat.match(f.readline()) + if m != None: + result.append(Executable(p, m.group(1))) + print "found " + m.group(1) + ": " + n + f.close() + return result
+ + + +class Fix: + "Represents a fix for a library link." + + def __init__(self, dependency, versionString="?"): + self.Link = dependency + self.Location = dependency + self.NewLink = dependency + self.NewLocation = dependency + self.versionString = versionString # not used yet + self.fwPath = None + self.relPath = None + + def __repr__(self): + return (self.Link + " (" + self.versionString + ")") + + def isAbsolute(self): + return os.path.isabs(self.Link) + + def isBundleRelative(self): + return self.Link[0:17] == "@executable_path/" + + def isSystem(self): + return (self.Location[0:8] == "/usr/lib" # also matches libexec + or self.Location[0:8] == "/usr/X11" # also matches X11R6 + or self.Location[0:8] == "/System/") + + def getChange(self): + "Returns argument for install_name_tool." + if self.Link == self.NewLink: + return "" + else: + return "-change " + self.Link + " " + self.NewLink + " " + + def findLocation(self, exePath=None): + if self.isBundleRelative(): + if exePath != None: + self.Location = os.path.normpath( + os.path.join(exePath, self.Link[17:])) + else: + self.Location = self.Link[17:] + else: + self.Location = self.Link + + # check if done + if (os.path.isabs(self.Location) and + os.path.isfile(self.Location)): + self.NewLocation = self.Location + return True + + # search for frameworks in /System/Library and /Library + fwPath = findFramework(self.Location) + if fwPath: + fwdir = os.path.dirname(fwPath) + self.relPath = stripPrefix(fwdir, self.Location) + for d in ["/Library/Frameworks", + "/System/Library/Frameworks"]: + if os.path.isfile(os.path.join(d, self.relPath)): +# self.Location = os.path.join(d, self.relPath) + self.Location = os.path.join(d, self.relPath) + self.NewLocation = self.Location + self.fwPath = os.path.join(d, os.path.basename(fwPath)) + self.relPath = stripPrefix(self.fwPath, self.Location) + return True + + # ok, try libs + lib = os.path.basename(self.Location) + self.relPath = None + for d in ["/usr/local/lib", "/opt/local/lib", + "/usr/lib", "/opt/lib"]: + if os.path.isfile(os.path.join(d, lib)): + self.Location = os.path.join(d, lib) + self.NewLocation = self.Location + return True + + # not found + return False + + + def moveLibrary(self, destBundlePath, stripFW, log): + "Copies the library or fw to destBundlePath." + "Also sets NewLink and NewLocation properties" + "Returns a list of copied executables" + + # dont do this if we are already inside the bundle: + if stripPrefix(destBundlePath, self.Location) != self.Location: + log.append("-- ignoring " + self.Location) + return [] + + if self.relPath != None and not stripFW: + # copy framework + newFwPath = os.path.join(destBundlePath, + "Contents/Frameworks", + os.path.basename(self.fwPath)) + log.append(">> " + self.fwPath + " ===> " + newFwPath) + if (os.path.exists(destBundlePath) and + not os.path.exists(newFwPath)): + shutil.copytree(self.fwPath, newFwPath, True) + self.NewLocation = os.path.join(newFwPath, self.relPath) + self.NewLink = ("@executable_path/" + + os.path.join("../Frameworks", + os.path.basename(self.fwPath), + self.relPath)) + return findExecutables(newFwPath) + else: + # copy lib to bundle.app/Contents/Frameworks/ + self.NewLocation = os.path.join(destBundlePath, + "Contents/Frameworks", + os.path.basename(self.Location)) + self.NewLink = ("@executable_path/" + + os.path.join("../Frameworks", + os.path.basename(self.Location))) + log.append(">> " + self.Location + " ---> " + self.NewLocation) + if (os.path.exists(destBundlePath) and + not os.path.exists(self.NewLocation)): + shutil.copy(self.Location, self.NewLocation) + return [Executable(self.NewLocation, "lib")] + +
\ No newline at end of file diff --git a/OSX-package/linktools/ingest.py b/OSX-package/linktools/ingest.py new file mode 100755 index 0000000..83a0620 --- /dev/null +++ b/OSX-package/linktools/ingest.py @@ -0,0 +1,72 @@ +#!/usr/bin/python + +import os +import sys +import osxtools + +def usage(): + print """ + Usage: ingest.py bundle [-x lib] [-s fw] + + Copies all dependent libraries and frameworks into the app bundle. + System libraries (/usr/lib*, /System/Library) are not copied. + Fixes the dependencies in all executabels contained in bundle. + + bundle: the path to the *.app bundle + -x lib: dont move lib into the bundle. + -s fw: only move the referenced libarry file from framework fw + into the bundle, not the complete framework + """ + + +if len(sys.argv) <= 1 or sys.argv[1] == "-?" : + usage() + sys.exit(0) + + + +exceptions = [] +strippedfws = [] +bundle = None + +argp = 1 + +while argp < len(sys.argv) : + if sys.argv[argp] == '-x' : + exceptions.append(sys.argv[argp + 1]) + argp = argp + 2 + elif sys.argv[argp][0:2] == '-x' : + exceptions.append(sys.argv[argp][2:]) + argp = argp + 1 + elif sys.argv[argp] == '-s' : + strippedfws.append(sys.argv[argp + 1]) + argp = argp + 2 + elif sys.argv[argp][0:2] == '-s' : + strippedfws.append(sys.argv[argp][2:]) + argp = argp + 1 + elif sys.argv[argp][0:1] == '-' : + print "Error: unknown option: " + sys.argv[argp] + usage() + sys.exit(1) + elif bundle == None: + bundle = sys.argv[argp] + argp = argp + 1 + else: + print "Error: more than one bundle path specified!" + usage() + sys.exit(1) + +if bundle == None: + print "Error: no bundle path specified!" + usage() + sys.exit(1) + +if not os.path.isabs(bundle): + bundle = os.path.join(os.getenv("PWD"), bundle) + +if not os.path.isdir(bundle): + print "Error: '" + bundle + "' is no bundle path!" + usage() + sys.exit(1) + +osxtools.ingest(bundle, exceptions, strippedfws) diff --git a/OSX-package/linktools/mkappbundle.py b/OSX-package/linktools/mkappbundle.py new file mode 100755 index 0000000..a8eff74 --- /dev/null +++ b/OSX-package/linktools/mkappbundle.py @@ -0,0 +1,87 @@ +#!/usr/bin/python + +import os +import sys +import shutil +import osxtools + +def usage(): + print """ + Usage: mkappbundle.py bundle [-b binary] [-i infofile|-v version] + + Creates the directory structure for an application bundle. + If binary is given, it will be used as the binaries executable, + otherwise the binary will just be an empty shell. + + bundle: the path to the *.app bundle + -b binary: copy lib into the bundle. + -i infofile: use "ver" as the version instead of the standard 'A' + """ + + +if len(sys.argv) <= 1 or sys.argv[1] == "-?" : + usage() + sys.exit(0) + + + +infofile = None +binfile = None +bundle = None + +argp = 1 + +while argp < len(sys.argv) : + if sys.argv[argp] == '-b' : + binfile = (sys.argv[argp + 1]) + argp = argp + 2 + elif sys.argv[argp][0:2] == '-b' : + binfile = (sys.argv[argp][2:]) + argp = argp + 1 + elif sys.argv[argp] == '-i' : + infofile = (sys.argv[argp + 1]) + argp = argp + 2 + elif sys.argv[argp][0:2] == '-i' : + infofile = (sys.argv[argp][2:]) + argp = argp + 1 + elif sys.argv[argp][0:1] == '-' : + print "Error: unknown option: " + sys.argv[argp] + usage() + sys.exit(1) + elif bundle == None: + bundle = sys.argv[argp] + argp = argp + 1 + else: + print "Error: more than one bundle path specified!" + usage() + sys.exit(1) + +if bundle == None: + print "Error: no bundle path specified!" + usage() + sys.exit(1) + +if not os.path.isabs(bundle): + bundle = os.path.join(os.getenv("PWD"), bundle) + +if bundle[-4 : ] != ".app": + bundle = bundle + ".app" +appName = os.path.basename(bundle)[0: -4] + +if not os.path.exists(bundle): + os.makedirs(bundle, 0755) +elif not os.path.isdir(bundle): + print "Error: '" + bundle + "' is no bundle path!" + usage() + sys.exit(1) + +binPath = os.path.join(bundle, "Contents/MacOS") + +if not os.path.exists(binPath): + os.makedirs(binPath, 0755) + +if binfile != None: + shutil.copy(binfile, os.path.join(binPath, appName)) + +shutil.copy(infofile, os.path.join(bundle, "Contents/Info.plist") + diff --git a/OSX-package/linktools/mkframework.py b/OSX-package/linktools/mkframework.py new file mode 100755 index 0000000..c29e847 --- /dev/null +++ b/OSX-package/linktools/mkframework.py @@ -0,0 +1,111 @@ +#!/usr/bin/python + +import os +import sys +import shutil +import osxtools + +def usage(): + print """ + Usage: mkframework.py bundle [-l libfile] [-v version] + + Creates the directory structure for a framework. + If libfile is given, it will be used as the frameworks executable, + otherwise the framework will just be an empty shell. + + bundle: the path to the *.framework bundle + -l lib: copy lib into the bundle. + -v ver: use "ver" as the version instead of the standard 'A' + -f: overwrite existing files if version exists + """ + + +if len(sys.argv) <= 1 or sys.argv[1] == "-?" : + usage() + sys.exit(0) + + + +version = "A" +overwrite = False +libfile = None +bundle = None + +argp = 1 + +while argp < len(sys.argv) : + if sys.argv[argp] == '-f': + overwrite = True; + argp = argp + 1 + elif sys.argv[argp] == '-l' : + libfile = (sys.argv[argp + 1]) + argp = argp + 2 + elif sys.argv[argp][0:2] == '-l' : + libfile = (sys.argv[argp][2:]) + argp = argp + 1 + elif sys.argv[argp] == '-v' : + version = (sys.argv[argp + 1]) + argp = argp + 2 + elif sys.argv[argp][0:2] == '-v' : + version = (sys.argv[argp][2:]) + argp = argp + 1 + elif sys.argv[argp][0:1] == '-' : + print "Error: unknown option: " + sys.argv[argp] + usage() + sys.exit(1) + elif bundle == None: + bundle = sys.argv[argp] + argp = argp + 1 + else: + print "Error: more than one bundle path specified!" + usage() + sys.exit(1) + +if bundle == None: + print "Error: no bundle path specified!" + usage() + sys.exit(1) + +if not os.path.isabs(bundle): + bundle = os.path.join(os.getenv("PWD"), bundle) + +if bundle[-10 : ] != ".framework": + bundle = bundle + ".framework" +fwName = os.path.basename(bundle)[0: -10] + +if not os.path.exists(bundle): + os.makedirs(bundle, 0755) +elif not os.path.isdir(bundle): + print "Error: '" + bundle + "' is no bundle path!" + usage() + sys.exit(1) + +versionPath = os.path.join(bundle, "Versions", version) + +if os.path.exists(versionPath): + if overwrite: + shutil.removetree(versionPath) + else: + print "Error: '" + versionPath + "' already exists!" + usage() + sys.exit(1) + +os.makedirs(versionPath, 0755) + +if libfile != None: + shutil.copy(libfile, os.path.join(versionPath, fwName)) + os.system("install_name_tool -id @executable_path/" + + os.path.join("../Frameworks", + fwName + ".framework", + "Versions", + version, + fwName) + + " " + + os.path.join(versionPath, fwName)) + +osxtools.createSymlinks(bundle, [ + ("Versions/Current", version), + (fwName, os.path.join("Versions/Current", fwName)), + ("Headers", "Versions/Current/Headers") +]) + diff --git a/OSX-package/linktools/osxtools.py b/OSX-package/linktools/osxtools.py new file mode 100755 index 0000000..ac2bb57 --- /dev/null +++ b/OSX-package/linktools/osxtools.py @@ -0,0 +1,114 @@ +import os +import re +import MachO +from distutils.dir_util import copy_tree +from datetime import datetime + + +def findDependencies(exeFiles, exePath): + "Return a dictionary of MachO.Fixes of all recursive dependencies" + result = {} + + # allow some sloppyness: + if isinstance(exeFiles, str): + exeFiles = [MachO.Executable(exeFiles, "executable")] + elif isinstance(exeFiles, MachO.Executable): + exeFiles = [exeFiles] + + # go through executables and store Fixes + todo = [x for x in exeFiles] + done = [x.Location for x in exeFiles] + while len(todo) > 0: + current = todo.pop() + print "getting dependencies for " + current.Location + for dep in current.getDependencies(): + if dep.Link not in result: + if dep.findLocation(exePath): + result[dep.Link] = dep + # check if we need to traverse the referenced lib + if not dep.isSystem() and dep.Location not in done: + print "- adding " + dep.Location + done.append(dep.Location) + todo.append(MachO.Executable(dep.Location, "lib")) + else: + print ("couldn't find " + dep.Link + + " -> " + dep.Location) + + # forget any system dependencies + for k,fix in result.items(): + if fix.isSystem(): + del result[k] + + return result + + +def ingest(bundle, exceptions=[], strippedFrameworks=[]): + "Moves all needed non-System libraries inside the bundle and fixes links" + # step 1: find all executables + executables = MachO.findExecutables(bundle) + # find the bundle executable + pat = re.compile("executable") + exePath = "" + for exe in executables: + if pat.match(exe.Kind): + exePath = os.path.dirname(exe.Location) + print "using @executable_path=" + exePath + break + # step 2: find all dependencies + fixes = findDependencies(executables, exePath) + # step 3: move all libraries which are not excepted + log = [] + frameworks = os.path.join(bundle, "Contents/Frameworks") + if not os.path.exists(frameworks): + log.append(">> mkdir " + frameworks) + os.makedirs(frameworks, 0755) + for k,fix in fixes.items(): + if fix.Location in exceptions or fix.Link in exceptions: + del fixes[k] + else: + stripFW = fix.Location in strippedFrameworks + executables.extend(fix.moveLibrary(bundle, stripFW, log)) + + # step 3.5: copy aspell dictionaries, hacked for aspell via macports for now, #7371 + aspellsrcpath = "/opt/local/share/aspell" + if os.path.exists(aspellsrcpath): + aspelldestpath = os.path.join(bundle, "Contents/share/aspell") + if not os.path.exists(aspelldestpath): + log.append(">> mkdir " + aspelldestpath) + os.makedirs(aspelldestpath, 0755) + if os.path.exists(aspelldestpath): + log.append(">> copying aspell dictionaries") + print "copying aspell dictionaries" + copy_tree(aspellsrcpath, aspelldestpath) + + # step 4: fix all executables + for exe in executables: + exe.applyFixes(fixes, log) + # step 5: write log + logfile = file(os.path.join(bundle, "Contents/osxtools.log"), "a") + logfile.write("ingest at " + datetime.now().isoformat(" ") + "\n") + for e in log: + logfile.write(e + "\n") + logfile.close() + + +def createSymlinks(bundle, links): + currDir = os.getcwd() + for lnk,tar in links: + print "chdir to " + os.path.join(bundle, os.path.dirname(lnk)) + os.chdir(os.path.join(bundle, os.path.dirname(lnk))) + print "symlink " + os.path.basename(lnk) + " -> " + tar + os.symlink(tar, os.path.basename(lnk)) + os.chdir(currDir) + + +def relinkOld(FILE, LIBDIR, INSTALLDIR): + #LIBS=`otool -L $FILE | sed 's/\([^(]*\)(.*)/\1/g'` + #for LIB in $LIBS ; do + # LNAM=`basename $LIB` + # if [ $FILE -ef $LIBDIR/$LNAM ] ; then + # install_name_tool -id $INSTALLDIR$LNAM $FILE + # elif [ -e $LIBDIR/$LNAM ] ; then + # install_name_tool -change $LIB $INSTALLDIR$LNAM $FILE + pass + |