1#!/usr/bin/env python3 2 3# 4# //===----------------------------------------------------------------------===// 5# // 6# // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 7# // See https://llvm.org/LICENSE.txt for license information. 8# // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 9# // 10# //===----------------------------------------------------------------------===// 11# 12 13import argparse 14import os 15import platform 16import re 17import sys 18from libomputils import ( 19 ScriptError, 20 error, 21 execute_command, 22 print_info_line, 23 print_error_line, 24) 25 26 27def get_deps_readelf(filename): 28 """Get list of dependencies from readelf""" 29 deps = [] 30 # Force readelf call to be in English 31 os.environ["LANG"] = "C" 32 r = execute_command(["readelf", "-d", filename]) 33 if r.returncode != 0: 34 error("readelf -d {} failed".format(filename)) 35 neededRegex = re.compile(r"\(NEEDED\)\s+Shared library: \[([a-zA-Z0-9_.-]+)\]") 36 for line in r.stdout.split(os.linesep): 37 match = neededRegex.search(line) 38 if match: 39 deps.append(match.group(1)) 40 return deps 41 42 43def get_deps_otool(filename): 44 """Get list of dependencies from otool""" 45 deps = [] 46 r = execute_command(["otool", "-L", filename]) 47 if r.returncode != 0: 48 error("otool -L {} failed".format(filename)) 49 libRegex = re.compile(r"([^ \t]+)\s+\(compatibility version ") 50 thisLibRegex = re.compile(r"@rpath/{}".format(os.path.basename(filename))) 51 for line in r.stdout.split(os.linesep): 52 match = thisLibRegex.search(line) 53 if match: 54 # Don't include the library itself as a needed dependency 55 continue 56 match = libRegex.search(line) 57 if match: 58 deps.append(match.group(1)) 59 continue 60 return deps 61 62 63def get_deps_link(filename): 64 """Get list of dependecies from link (Windows OS)""" 65 depsSet = set([]) 66 f = filename.lower() 67 args = ["link", "/DUMP"] 68 if f.endswith(".lib"): 69 args.append("/DIRECTIVES") 70 elif f.endswith(".dll") or f.endswith(".exe"): 71 args.append("/DEPENDENTS") 72 else: 73 error("unrecognized file extension: {}".format(filename)) 74 args.append(filename) 75 r = execute_command(args) 76 if r.returncode != 0: 77 error("{} failed".format(args.command)) 78 if f.endswith(".lib"): 79 regex = re.compile(r"\s*[-/]defaultlib:(.*)\s*$") 80 for line in r.stdout.split(os.linesep): 81 line = line.lower() 82 match = regex.search(line) 83 if match: 84 depsSet.add(match.group(1)) 85 else: 86 started = False 87 markerStart = re.compile(r"Image has the following depend") 88 markerEnd = re.compile(r"Summary") 89 markerEnd2 = re.compile(r"Image has the following delay load depend") 90 for line in r.stdout.split(os.linesep): 91 if not started: 92 if markerStart.search(line): 93 started = True 94 continue 95 else: # Started parsing the libs 96 line = line.strip() 97 if not line: 98 continue 99 if markerEnd.search(line) or markerEnd2.search(line): 100 break 101 depsSet.add(line.lower()) 102 return list(depsSet) 103 104 105def main(): 106 parser = argparse.ArgumentParser(description="Check library dependencies") 107 parser.add_argument( 108 "--bare", 109 action="store_true", 110 help="Produce plain, bare output: just a list" 111 " of libraries, a library per line", 112 ) 113 parser.add_argument( 114 "--expected", 115 metavar="CSV_LIST", 116 help="CSV_LIST is a comma-separated list of expected" 117 ' dependencies (or "none"). checks the specified' 118 " library has only expected dependencies.", 119 ) 120 121 parser.add_argument("library", help="The library file to check") 122 commandArgs = parser.parse_args() 123 # Get dependencies 124 deps = [] 125 126 system = platform.system() 127 if system == "Windows": 128 deps = get_deps_link(commandArgs.library) 129 elif system == "Darwin": 130 deps = get_deps_otool(commandArgs.library) 131 else: 132 deps = get_deps_readelf(commandArgs.library) 133 deps = sorted(deps) 134 135 # If bare output specified, then just print the dependencies one per line 136 if commandArgs.bare: 137 print(os.linesep.join(deps)) 138 return 139 140 # Calculate unexpected dependencies if expected list specified 141 unexpected = [] 142 if commandArgs.expected: 143 # none => any dependency is unexpected 144 if commandArgs.expected == "none": 145 unexpected = list(deps) 146 else: 147 expected = [d.strip() for d in commandArgs.expected.split(",")] 148 unexpected = [d for d in deps if d not in expected] 149 150 # Regular output 151 print_info_line("Dependencies:") 152 for dep in deps: 153 print_info_line(" {}".format(dep)) 154 if unexpected: 155 print_error_line("Unexpected Dependencies:") 156 for dep in unexpected: 157 print_error_line(" {}".format(dep)) 158 error("found unexpected dependencies") 159 160 161if __name__ == "__main__": 162 try: 163 main() 164 except ScriptError as e: 165 print_error_line(str(e)) 166 sys.exit(1) 167 168# end of file 169