symbols-check: introduce new python script
authorEric Engestrom <eric.engestrom@intel.com>
Thu, 11 Oct 2018 12:08:42 +0000 (13:08 +0100)
committerEric Engestrom <eric@engestrom.ch>
Wed, 10 Jul 2019 11:27:51 +0000 (11:27 +0000)
I've re-written this in bash a couple times over the years, and then
I realised python is much more portable and already required by Mesa, so
we might as well make use of it.

I decided to still use the build system's NM instead of re-implementing
symbols extraction, to offload the complexity of keeping it compatible
with many systems (Linux, Unix, BSD, MacOS, etc.), especially when
cross-building.

This new script checks not only that nothing is exported when it
shouldn't be, but also that everything that should be exported is.
Sometimes, some symbols _can_ be exported but don't have to be, in which
case they can be prefixed with `(optional)`.

Signed-off-by: Eric Engestrom <eric.engestrom@intel.com>
Reviewed-by Dylan Baker <dylan@pnwbakers.com>
Reviewed-by: Emil Velikov <emil.velikov@collabora.com>
bin/meson.build
bin/symbols-check.py [new file with mode: 0644]

index b8b44baf7d03e2b0658f793acd08a77c572eb4a2..ec22e2e298971f3d0dc1d0c23c503ef6c5bcd4b4 100644 (file)
@@ -19,3 +19,4 @@
 # SOFTWARE.
 
 git_sha1_gen_py = files('git_sha1_gen.py')
+symbols_check = find_program('symbols-check.py')
diff --git a/bin/symbols-check.py b/bin/symbols-check.py
new file mode 100644 (file)
index 0000000..001a727
--- /dev/null
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+
+import argparse
+import subprocess
+import os
+
+# This list contains symbols that _might_ be exported for some platforms
+PLATFORM_SYMBOLS = [
+    '__bss_end__',
+    '__bss_start__',
+    '__bss_start',
+    '__end__',
+    '_bss_end__',
+    '_edata',
+    '_end',
+    '_fini',
+    '_init',
+]
+
+
+def get_symbols(nm, lib):
+    '''
+    List all the (non platform-specific) symbols exported by the library
+    '''
+    symbols = []
+    output = subprocess.check_output([nm, '--format=bsd', '-D', '--defined-only', lib],
+                                     stderr=open(os.devnull, 'w')).decode("ascii")
+    for line in output.splitlines():
+        (_, _, symbol_name) = line.split()
+        symbols.append(symbol_name)
+    return symbols
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--symbols-file',
+                        action='store',
+                        required=True,
+                        help='path to file containing symbols')
+    parser.add_argument('--lib',
+                        action='store',
+                        required=True,
+                        help='path to library')
+    parser.add_argument('--nm',
+                        action='store',
+                        required=True,
+                        help='path to binary (or name in $PATH)')
+    args = parser.parse_args()
+
+    lib_symbols = get_symbols(args.nm, args.lib)
+    mandatory_symbols = []
+    optional_symbols = []
+    with open(args.symbols_file) as symbols_file:
+        qualifier_optional = '(optional)'
+        for line in symbols_file.readlines():
+
+            # Strip comments
+            line = line.split('#')[0]
+            line = line.strip()
+            if not line:
+                continue
+
+            # Line format:
+            # [qualifier] symbol
+            qualifier = None
+            symbol = None
+
+            fields = line.split()
+            if len(fields) == 1:
+                symbol = fields[0]
+            elif len(fields) == 2:
+                qualifier = fields[0]
+                symbol = fields[1]
+            else:
+                print(args.symbols_file + ': invalid format: ' + line)
+                exit(1)
+
+            # The only supported qualifier is 'optional', which means the
+            # symbol doesn't have to be exported by the library
+            if qualifier and not qualifier == qualifier_optional:
+                print(args.symbols_file + ': invalid qualifier: ' + qualifier)
+                exit(1)
+
+            if qualifier == qualifier_optional:
+                optional_symbols.append(symbol)
+            else:
+                mandatory_symbols.append(symbol)
+
+    unknown_symbols = []
+    for symbol in lib_symbols:
+        if symbol in mandatory_symbols:
+            continue
+        if symbol in optional_symbols:
+            continue
+        if symbol in PLATFORM_SYMBOLS:
+            continue
+        unknown_symbols.append(symbol)
+
+    missing_symbols = [
+        sym for sym in mandatory_symbols if sym not in lib_symbols
+    ]
+
+    for symbol in unknown_symbols:
+        print(args.lib + ': unknown symbol exported: ' + symbol)
+
+    for symbol in missing_symbols:
+        print(args.lib + ': missing symbol: ' + symbol)
+
+    if unknown_symbols or missing_symbols:
+        exit(1)
+    exit(0)
+
+
+if __name__ == '__main__':
+    main()