2010-05-07 20:24:22 +04:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
#
|
|
|
|
# This is be used to verify that all the dependant libraries of a Mac OS X executable
|
|
|
|
# are present and that they are backwards compatible with at least 10.5.
|
|
|
|
# Run with an executable as parameter
|
2010-05-10 21:42:27 +04:00
|
|
|
# Will return 0 if the executable an all libraries are OK
|
|
|
|
# Returns != 0 and prints some textural description on error
|
2010-05-07 20:24:22 +04:00
|
|
|
#
|
2010-05-07 20:24:26 +04:00
|
|
|
# Author: Marius Kintel <marius@kintel.net>
|
|
|
|
#
|
2011-04-06 17:16:35 +04:00
|
|
|
# This script lives here:
|
|
|
|
# https://github.com/kintel/MacOSX-tools
|
|
|
|
#
|
2010-05-07 20:24:22 +04:00
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
import re
|
|
|
|
|
|
|
|
DEBUG = False
|
|
|
|
|
|
|
|
def usage():
|
|
|
|
print >> sys.stderr, "Usage: " + sys.argv[0] + " <executable>"
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
# Try to find the given library by searching in the typical locations
|
|
|
|
# Returns the full path to the library or None if the library is not found.
|
|
|
|
def lookup_library(file):
|
|
|
|
found = None
|
|
|
|
if not re.match("/", file):
|
|
|
|
if re.search("@executable_path", file):
|
|
|
|
abs = re.sub("^@executable_path", executable_path, file)
|
|
|
|
if os.path.exists(abs): found = abs
|
|
|
|
if DEBUG: print "Lib in @executable_path found: " + found
|
|
|
|
elif re.search("\.app/", file):
|
|
|
|
found = file
|
|
|
|
if DEBUG: print "App found: " + found
|
|
|
|
elif re.search("\.framework/", file):
|
|
|
|
found = os.path.join("/Library/Frameworks", file)
|
|
|
|
if DEBUG: print "Framework found: " + found
|
|
|
|
else:
|
|
|
|
for path in os.getenv("DYLD_LIBRARY_PATH").split(':'):
|
|
|
|
abs = os.path.join(path, file)
|
|
|
|
if os.path.exists(abs): found = abs
|
|
|
|
if DEBUG: print "Library found: " + found
|
|
|
|
else:
|
|
|
|
found = file
|
|
|
|
return found
|
|
|
|
|
|
|
|
# Returns a list of dependent libraries, excluding system libs
|
|
|
|
def find_dependencies(file):
|
|
|
|
libs = []
|
|
|
|
|
2011-04-06 17:16:35 +04:00
|
|
|
args = ["otool", "-L", file]
|
|
|
|
if DEBUG: print "Executing " + " ".join(args)
|
|
|
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
output,err = p.communicate()
|
|
|
|
if p.returncode != 0:
|
|
|
|
print "Failed with return code " + str(p.returncode) + ":"
|
|
|
|
print err
|
|
|
|
return None
|
2010-05-07 20:24:22 +04:00
|
|
|
deps = output.split('\n')
|
|
|
|
for dep in deps:
|
2014-01-05 03:05:43 +04:00
|
|
|
#print dep
|
|
|
|
# Fail if anything is linked with libc++, as that's not backwards compatible
|
|
|
|
# with Mac OS X 10.6
|
|
|
|
if re.search("libc\+\+", dep):
|
|
|
|
print "Error: clang's libc++ is used by " + file
|
|
|
|
return None
|
2010-05-07 20:24:22 +04:00
|
|
|
dep = re.sub(".*:$", "", dep) # Take away header line
|
|
|
|
dep = re.sub("^\t", "", dep) # Remove initial tabs
|
|
|
|
dep = re.sub(" \(.*\)$", "", dep) # Remove trailing parentheses
|
|
|
|
if len(dep) > 0 and not re.search("/System/Library", dep) and not re.search("/usr/lib", dep):
|
|
|
|
libs.append(dep)
|
|
|
|
return libs
|
|
|
|
|
|
|
|
def validate_lib(lib):
|
|
|
|
p = subprocess.Popen(["otool", "-l", lib], stdout=subprocess.PIPE)
|
|
|
|
output = p.communicate()[0]
|
|
|
|
if p.returncode != 0: return False
|
2014-01-05 03:05:43 +04:00
|
|
|
# We don't support Snow Leopard anymore
|
|
|
|
# if re.search("LC_DYLD_INFO_ONLY", output):
|
|
|
|
# print "Error: Requires Snow Leopard: " + lib
|
|
|
|
# return False
|
2010-05-08 07:55:18 +04:00
|
|
|
|
|
|
|
p = subprocess.Popen(["lipo", lib, "-verify_arch", "x86_64"], stdout=subprocess.PIPE)
|
|
|
|
output = p.communicate()[0]
|
|
|
|
if p.returncode != 0:
|
|
|
|
print "Error: x86_64 architecture not supported: " + lib
|
|
|
|
return False
|
|
|
|
|
2014-01-05 03:05:43 +04:00
|
|
|
# We don't support 32-bit binaries anymore
|
|
|
|
# p = subprocess.Popen(["lipo", lib, "-verify_arch", "i386"], stdout=subprocess.PIPE)
|
|
|
|
# output = p.communicate()[0]
|
|
|
|
# if p.returncode != 0:
|
|
|
|
# print "Error: i386 architecture not supported: " + lib
|
|
|
|
# return False
|
2010-05-07 20:24:22 +04:00
|
|
|
return True
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2010-05-10 21:42:27 +04:00
|
|
|
error = False
|
2010-05-07 20:24:22 +04:00
|
|
|
if len(sys.argv) != 2: usage()
|
|
|
|
executable = sys.argv[1]
|
|
|
|
if DEBUG: print "Processing " + executable
|
|
|
|
executable_path = os.path.dirname(executable)
|
|
|
|
# processed is a dict {libname : [parents]} - each parent is dependant on libname
|
|
|
|
processed = {}
|
|
|
|
pending = [executable]
|
2010-05-08 07:55:18 +04:00
|
|
|
processed[executable] = []
|
2010-05-07 20:24:22 +04:00
|
|
|
while len(pending) > 0:
|
|
|
|
dep = pending.pop()
|
|
|
|
if DEBUG: print "Evaluating " + dep
|
|
|
|
deps = find_dependencies(dep)
|
|
|
|
assert(deps)
|
|
|
|
for d in deps:
|
2013-02-07 00:08:22 +04:00
|
|
|
absfile = lookup_library(d)
|
|
|
|
if not re.match(executable_path, absfile):
|
2013-02-06 23:32:59 +04:00
|
|
|
print "Error: External dependency " + d
|
|
|
|
sys.exit(1)
|
2010-05-07 20:24:22 +04:00
|
|
|
if absfile == None:
|
|
|
|
print "Not found: " + d
|
|
|
|
print " ..required by " + str(processed[dep])
|
2010-05-28 03:16:49 +04:00
|
|
|
error = True
|
2010-05-07 20:24:22 +04:00
|
|
|
continue
|
|
|
|
if absfile in processed:
|
|
|
|
processed[absfile].append(dep)
|
|
|
|
else:
|
|
|
|
processed[absfile] = [dep]
|
|
|
|
if DEBUG: print "Pending: " + absfile
|
|
|
|
pending.append(absfile)
|
|
|
|
|
|
|
|
for dep in processed:
|
|
|
|
if DEBUG: print "Validating: " + dep
|
|
|
|
# print " " + str(processed[dep])
|
|
|
|
if not validate_lib(dep):
|
|
|
|
print "..required by " + str(processed[dep])
|
2010-05-10 21:42:27 +04:00
|
|
|
error = True
|
|
|
|
if error: sys.exit(1)
|
|
|
|
else: sys.exit(0)
|