xref: /llvm-project/openmp/runtime/tools/check-depends.py (revision 88dae3d5d0230747f3cbabdde9ac5ae9e5dc3f8d)
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