1
1
#!/usr/bin/env python
2
2
# Script checking that all symbols exported by libpython start with Py or _Py
3
3
4
+ import os .path
4
5
import subprocess
5
6
import sys
6
7
import sysconfig
7
8
8
9
9
- def get_exported_symbols ():
10
- LIBRARY = sysconfig .get_config_var ('LIBRARY' )
11
- if not LIBRARY :
12
- raise Exception ("failed to get LIBRARY" )
10
+ ALLOWED_PREFIXES = ('Py' , '_Py' )
11
+ if sys .platform == 'darwin' :
12
+ ALLOWED_PREFIXES += ('__Py' ,)
13
+
14
+ IGNORED_EXTENSION = "_ctypes_test"
15
+ # Ignore constructor and destructor functions
16
+ IGNORED_SYMBOLS = {'_init' , '_fini' }
17
+
18
+
19
+ def is_local_symbol_type (symtype ):
20
+ # Ignore local symbols.
21
+
22
+ # If lowercase, the symbol is usually local; if uppercase, the symbol
23
+ # is global (external). There are however a few lowercase symbols that
24
+ # are shown for special global symbols ("u", "v" and "w").
25
+ if symtype .islower () and symtype not in "uvw" :
26
+ return True
27
+
28
+ # Ignore the initialized data section (d and D) and the BSS data
29
+ # section. For example, ignore "__bss_start (type: B)"
30
+ # and "_edata (type: D)".
31
+ if symtype in "bBdD" :
32
+ return True
33
+
34
+ return False
13
35
14
- args = ('nm' , '-p' , LIBRARY )
36
+
37
+ def get_exported_symbols (library , dynamic = False ):
38
+ print (f"Check that { library } only exports symbols starting with Py or _Py" )
39
+
40
+ # Only look at dynamic symbols
41
+ args = ['nm' , '--no-sort' ]
42
+ if dynamic :
43
+ args .append ('--dynamic' )
44
+ args .append (library )
15
45
print ("+ %s" % ' ' .join (args ))
16
46
proc = subprocess .run (args , stdout = subprocess .PIPE , universal_newlines = True )
17
47
if proc .returncode :
@@ -25,12 +55,9 @@ def get_exported_symbols():
25
55
26
56
27
57
def get_smelly_symbols (stdout ):
28
- symbols = []
29
- ignored_symtypes = set ()
30
-
31
- allowed_prefixes = ('Py' , '_Py' )
32
- if sys .platform == 'darwin' :
33
- allowed_prefixes += ('__Py' ,)
58
+ smelly_symbols = []
59
+ python_symbols = []
60
+ local_symbols = []
34
61
35
62
for line in stdout .splitlines ():
36
63
# Split line '0000000000001b80 D PyTextIOWrapper_Type'
@@ -42,41 +69,98 @@ def get_smelly_symbols(stdout):
42
69
continue
43
70
44
71
symtype = parts [1 ].strip ()
45
- # Ignore private symbols.
46
- #
47
- # If lowercase, the symbol is usually local; if uppercase, the symbol
48
- # is global (external). There are however a few lowercase symbols that
49
- # are shown for special global symbols ("u", "v" and "w").
50
- if symtype .islower () and symtype not in "uvw" :
51
- ignored_symtypes .add (symtype )
72
+ symbol = parts [- 1 ]
73
+ result = '%s (type: %s)' % (symbol , symtype )
74
+
75
+ if symbol .startswith (ALLOWED_PREFIXES ):
76
+ python_symbols .append (result )
52
77
continue
53
78
54
- symbol = parts [- 1 ]
55
- if symbol .startswith (allowed_prefixes ):
79
+ if is_local_symbol_type (symtype ):
80
+ local_symbols .append (result )
81
+ elif symbol in IGNORED_SYMBOLS :
82
+ local_symbols .append (result )
83
+ else :
84
+ smelly_symbols .append (result )
85
+
86
+ if local_symbols :
87
+ print (f"Ignore { len (local_symbols )} local symbols" )
88
+ return smelly_symbols , python_symbols
89
+
90
+
91
+ def check_library (library , dynamic = False ):
92
+ nm_output = get_exported_symbols (library , dynamic )
93
+ smelly_symbols , python_symbols = get_smelly_symbols (nm_output )
94
+
95
+ if not smelly_symbols :
96
+ print (f"OK: no smelly symbol found ({ len (python_symbols )} Python symbols)" )
97
+ return 0
98
+
99
+ print ()
100
+ smelly_symbols .sort ()
101
+ for symbol in smelly_symbols :
102
+ print ("Smelly symbol: %s" % symbol )
103
+
104
+ print ()
105
+ print ("ERROR: Found %s smelly symbols!" % len (smelly_symbols ))
106
+ return len (smelly_symbols )
107
+
108
+
109
+ def check_extensions ():
110
+ print (__file__ )
111
+ srcdir = os .path .dirname (os .path .dirname (os .path .dirname (__file__ )))
112
+ filename = os .path .join (srcdir , "pybuilddir.txt" )
113
+ try :
114
+ with open (filename , encoding = "utf-8" ) as fp :
115
+ pybuilddir = fp .readline ()
116
+ except FileNotFoundError :
117
+ print (f"Cannot check extensions because { filename } does not exist" )
118
+ return True
119
+
120
+ print (f"Check extension modules from { pybuilddir } directory" )
121
+ builddir = os .path .join (srcdir , pybuilddir )
122
+ nsymbol = 0
123
+ for name in os .listdir (builddir ):
124
+ if not name .endswith (".so" ):
125
+ continue
126
+ if IGNORED_EXTENSION in name :
127
+ print ()
128
+ print (f"Ignore extension: { name } " )
56
129
continue
57
- symbol = '%s (type: %s)' % (symbol , symtype )
58
- symbols .append (symbol )
59
130
60
- if ignored_symtypes :
61
- print ("Ignored symbol types: %s" % ', ' .join (sorted (ignored_symtypes )))
62
131
print ()
63
- return symbols
132
+ filename = os .path .join (builddir , name )
133
+ nsymbol += check_library (filename , dynamic = True )
134
+
135
+ return nsymbol
64
136
65
137
66
138
def main ():
67
- nm_output = get_exported_symbols ()
68
- symbols = get_smelly_symbols (nm_output )
139
+ # static library
140
+ LIBRARY = sysconfig .get_config_var ('LIBRARY' )
141
+ if not LIBRARY :
142
+ raise Exception ("failed to get LIBRARY variable from sysconfig" )
143
+ nsymbol = check_library (LIBRARY )
144
+
145
+ # dynamic library
146
+ LDLIBRARY = sysconfig .get_config_var ('LDLIBRARY' )
147
+ if not LDLIBRARY :
148
+ raise Exception ("failed to get LDLIBRARY variable from sysconfig" )
149
+ if LDLIBRARY != LIBRARY :
150
+ print ()
151
+ nsymbol += check_library (LDLIBRARY , dynamic = True )
69
152
70
- if not symbols :
71
- print ("OK: no smelly symbol found" )
72
- sys .exit (0 )
153
+ # Check extension modules like _ssl.cpython-310d-x86_64-linux-gnu.so
154
+ nsymbol += check_extensions ()
155
+
156
+ if nsymbol :
157
+ print ()
158
+ print (f"ERROR: Found { nsymbol } smelly symbols in total!" )
159
+ sys .exit (1 )
73
160
74
- symbols .sort ()
75
- for symbol in symbols :
76
- print ("Smelly symbol: %s" % symbol )
77
161
print ()
78
- print ("ERROR: Found %s smelly symbols!" % len ( symbols ))
79
- sys . exit ( 1 )
162
+ print (f"OK: all exported symbols of all libraries "
163
+ f"are prefixed with { ' or ' . join ( map ( repr , ALLOWED_PREFIXES )) } " )
80
164
81
165
82
166
if __name__ == "__main__" :
0 commit comments