summaryrefslogtreecommitdiffstats
path: root/OSX-package/linktools
diff options
context:
space:
mode:
Diffstat (limited to 'OSX-package/linktools')
-rw-r--r--OSX-package/linktools/MachO.py215
-rwxr-xr-xOSX-package/linktools/ingest.py72
-rwxr-xr-xOSX-package/linktools/mkappbundle.py87
-rwxr-xr-xOSX-package/linktools/mkframework.py111
-rwxr-xr-xOSX-package/linktools/osxtools.py114
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
+