bin/symbols-check: explain C++ symbols workaround
[mesa.git] / bin / symbols-check.py
1 #!/usr/bin/env python
2
3 import argparse
4 import os
5 import platform
6 import subprocess
7
8 # This list contains symbols that _might_ be exported for some platforms
9 PLATFORM_SYMBOLS = [
10 '__bss_end__',
11 '__bss_start__',
12 '__bss_start',
13 '__end__',
14 '_bss_end__',
15 '_edata',
16 '_end',
17 '_fini',
18 '_init',
19 ]
20
21
22 def get_symbols_nm(nm, lib):
23 '''
24 List all the (non platform-specific) symbols exported by the library
25 using `nm`
26 '''
27 symbols = []
28 platform_name = platform.system()
29 output = subprocess.check_output([nm, '-gP', lib],
30 stderr=open(os.devnull, 'w')).decode("ascii")
31 for line in output.splitlines():
32 fields = line.split()
33 if len(fields) == 2 or fields[1] == 'U':
34 continue
35 symbol_name = fields[0]
36 if platform_name == 'Linux':
37 if symbol_name in PLATFORM_SYMBOLS:
38 continue
39 elif platform_name == 'Darwin':
40 assert symbol_name[0] == '_'
41 symbol_name = symbol_name[1:]
42 symbols.append(symbol_name)
43 return symbols
44
45
46 def get_symbols_dumpbin(dumpbin, lib):
47 '''
48 List all the (non platform-specific) symbols exported by the library
49 using `dumpbin`
50 '''
51 symbols = []
52 output = subprocess.check_output([dumpbin, '/exports', lib],
53 stderr=open(os.devnull, 'w')).decode("ascii")
54 for line in output.splitlines():
55 fields = line.split()
56 # The lines with the symbols are made of at least 4 columns; see details below
57 if len(fields) < 4:
58 continue
59 try:
60 # Making sure the first 3 columns are a dec counter, a hex counter
61 # and a hex address
62 _ = int(fields[0], 10)
63 _ = int(fields[1], 16)
64 _ = int(fields[2], 16)
65 except ValueError:
66 continue
67 symbol_name = fields[3]
68 # De-mangle symbols
69 if symbol_name[0] == '_':
70 symbol_name = symbol_name[1:].split('@')[0]
71 symbols.append(symbol_name)
72 return symbols
73
74
75 def main():
76 parser = argparse.ArgumentParser()
77 parser.add_argument('--symbols-file',
78 action='store',
79 required=True,
80 help='path to file containing symbols')
81 parser.add_argument('--lib',
82 action='store',
83 required=True,
84 help='path to library')
85 parser.add_argument('--nm',
86 action='store',
87 help='path to binary (or name in $PATH)')
88 parser.add_argument('--dumpbin',
89 action='store',
90 help='path to binary (or name in $PATH)')
91 args = parser.parse_args()
92
93 try:
94 if platform.system() == 'Windows':
95 if not args.dumpbin:
96 parser.error('--dumpbin is mandatory')
97 lib_symbols = get_symbols_dumpbin(args.dumpbin, args.lib)
98 else:
99 if not args.nm:
100 parser.error('--nm is mandatory')
101 lib_symbols = get_symbols_nm(args.nm, args.lib)
102 except:
103 # We can't run this test, but we haven't technically failed it either
104 # Return the GNU "skip" error code
105 exit(77)
106 mandatory_symbols = []
107 optional_symbols = []
108 with open(args.symbols_file) as symbols_file:
109 qualifier_optional = '(optional)'
110 for line in symbols_file.readlines():
111
112 # Strip comments
113 line = line.split('#')[0]
114 line = line.strip()
115 if not line:
116 continue
117
118 # Line format:
119 # [qualifier] symbol
120 qualifier = None
121 symbol = None
122
123 fields = line.split()
124 if len(fields) == 1:
125 symbol = fields[0]
126 elif len(fields) == 2:
127 qualifier = fields[0]
128 symbol = fields[1]
129 else:
130 print(args.symbols_file + ': invalid format: ' + line)
131 exit(1)
132
133 # The only supported qualifier is 'optional', which means the
134 # symbol doesn't have to be exported by the library
135 if qualifier and not qualifier == qualifier_optional:
136 print(args.symbols_file + ': invalid qualifier: ' + qualifier)
137 exit(1)
138
139 if qualifier == qualifier_optional:
140 optional_symbols.append(symbol)
141 else:
142 mandatory_symbols.append(symbol)
143
144 unknown_symbols = []
145 for symbol in lib_symbols:
146 if symbol in mandatory_symbols:
147 continue
148 if symbol in optional_symbols:
149 continue
150 if symbol[:2] == '_Z':
151 # As ajax found out, the compiler intentionally exports symbols
152 # that we explicitely asked it not to export, and we can't do
153 # anything about it:
154 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36022#c4
155 continue
156 unknown_symbols.append(symbol)
157
158 missing_symbols = [
159 sym for sym in mandatory_symbols if sym not in lib_symbols
160 ]
161
162 for symbol in unknown_symbols:
163 print(args.lib + ': unknown symbol exported: ' + symbol)
164
165 for symbol in missing_symbols:
166 print(args.lib + ': missing symbol: ' + symbol)
167
168 if unknown_symbols or missing_symbols:
169 exit(1)
170 exit(0)
171
172
173 if __name__ == '__main__':
174 main()