| #!/usr/bin/env python3 |
| |
| # |
| # //===----------------------------------------------------------------------===// |
| # // |
| # // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| # // See https://llvm.org/LICENSE.txt for license information. |
| # // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| # // |
| # //===----------------------------------------------------------------------===// |
| # |
| |
| import argparse |
| import os |
| import platform |
| import re |
| import sys |
| from libomputils import ( |
| ScriptError, |
| error, |
| execute_command, |
| print_info_line, |
| print_error_line, |
| ) |
| |
| |
| def get_deps_readelf(filename): |
| """Get list of dependencies from readelf""" |
| deps = [] |
| # Force readelf call to be in English |
| os.environ["LANG"] = "C" |
| r = execute_command(["readelf", "-d", filename]) |
| if r.returncode != 0: |
| error("readelf -d {} failed".format(filename)) |
| neededRegex = re.compile(r"\(NEEDED\)\s+Shared library: \[([a-zA-Z0-9_.-]+)\]") |
| for line in r.stdout.split(os.linesep): |
| match = neededRegex.search(line) |
| if match: |
| deps.append(match.group(1)) |
| return deps |
| |
| |
| def get_deps_otool(filename): |
| """Get list of dependencies from otool""" |
| deps = [] |
| r = execute_command(["otool", "-L", filename]) |
| if r.returncode != 0: |
| error("otool -L {} failed".format(filename)) |
| libRegex = re.compile(r"([^ \t]+)\s+\(compatibility version ") |
| thisLibRegex = re.compile(r"@rpath/{}".format(os.path.basename(filename))) |
| for line in r.stdout.split(os.linesep): |
| match = thisLibRegex.search(line) |
| if match: |
| # Don't include the library itself as a needed dependency |
| continue |
| match = libRegex.search(line) |
| if match: |
| deps.append(match.group(1)) |
| continue |
| return deps |
| |
| |
| def get_deps_link(filename): |
| """Get list of dependecies from link (Windows OS)""" |
| depsSet = set([]) |
| f = filename.lower() |
| args = ["link", "/DUMP"] |
| if f.endswith(".lib"): |
| args.append("/DIRECTIVES") |
| elif f.endswith(".dll") or f.endswith(".exe"): |
| args.append("/DEPENDENTS") |
| else: |
| error("unrecognized file extension: {}".format(filename)) |
| args.append(filename) |
| r = execute_command(args) |
| if r.returncode != 0: |
| error("{} failed".format(args.command)) |
| if f.endswith(".lib"): |
| regex = re.compile(r"\s*[-/]defaultlib:(.*)\s*$") |
| for line in r.stdout.split(os.linesep): |
| line = line.lower() |
| match = regex.search(line) |
| if match: |
| depsSet.add(match.group(1)) |
| else: |
| started = False |
| markerStart = re.compile(r"Image has the following depend") |
| markerEnd = re.compile(r"Summary") |
| markerEnd2 = re.compile(r"Image has the following delay load depend") |
| for line in r.stdout.split(os.linesep): |
| if not started: |
| if markerStart.search(line): |
| started = True |
| continue |
| else: # Started parsing the libs |
| line = line.strip() |
| if not line: |
| continue |
| if markerEnd.search(line) or markerEnd2.search(line): |
| break |
| depsSet.add(line.lower()) |
| return list(depsSet) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(description="Check library dependencies") |
| parser.add_argument( |
| "--bare", |
| action="store_true", |
| help="Produce plain, bare output: just a list" |
| " of libraries, a library per line", |
| ) |
| parser.add_argument( |
| "--expected", |
| metavar="CSV_LIST", |
| help="CSV_LIST is a comma-separated list of expected" |
| ' dependencies (or "none"). checks the specified' |
| " library has only expected dependencies.", |
| ) |
| |
| parser.add_argument("library", help="The library file to check") |
| commandArgs = parser.parse_args() |
| # Get dependencies |
| deps = [] |
| |
| system = platform.system() |
| if system == "Windows": |
| deps = get_deps_link(commandArgs.library) |
| elif system == "Darwin": |
| deps = get_deps_otool(commandArgs.library) |
| else: |
| deps = get_deps_readelf(commandArgs.library) |
| deps = sorted(deps) |
| |
| # If bare output specified, then just print the dependencies one per line |
| if commandArgs.bare: |
| print(os.linesep.join(deps)) |
| return |
| |
| # Calculate unexpected dependencies if expected list specified |
| unexpected = [] |
| if commandArgs.expected: |
| # none => any dependency is unexpected |
| if commandArgs.expected == "none": |
| unexpected = list(deps) |
| else: |
| expected = [d.strip() for d in commandArgs.expected.split(",")] |
| unexpected = [d for d in deps if d not in expected] |
| |
| # Regular output |
| print_info_line("Dependencies:") |
| for dep in deps: |
| print_info_line(" {}".format(dep)) |
| if unexpected: |
| print_error_line("Unexpected Dependencies:") |
| for dep in unexpected: |
| print_error_line(" {}".format(dep)) |
| error("found unexpected dependencies") |
| |
| |
| if __name__ == "__main__": |
| try: |
| main() |
| except ScriptError as e: |
| print_error_line(str(e)) |
| sys.exit(1) |
| |
| # end of file |