abi: extract_symbols: group symbols by requiring module

In order to make the symbol whitelists easier to maintain, group the
symbols by requiring module. A section with commonly required symbols
remains. The new output would look like:

  [abi_whitelist]
  # commonly used symbols
    func1
    var1

  # required by mymod.ko
    func2
    var2

Also drop the functionality to update whitelists. Now that GKI is
without modules, we do not need to pile on a central whitelists. The
addititive nature of contributed whitelists needs to be checked at
review time anyway. Also, updating commented whitelists is now becoming
infeasible for this tool.

Change-Id: I8dca8db15117a519004f69be55b9117a14807bd9
Signed-off-by: Matthias Maennich <maennich@google.com>
diff --git a/abi/extract_symbols b/abi/extract_symbols
index 7db1032..c2f8984 100755
--- a/abi/extract_symbols
+++ b/abi/extract_symbols
@@ -16,6 +16,7 @@
 #
 
 import argparse
+import collections
 import functools
 import itertools
 import os
@@ -72,7 +73,7 @@
   # yes, we could pass all of them to nm, but I want to avoid hitting shell
   # limits with long lists of modules
   result = {}
-  for module in modules:
+  for module in sorted(modules):
     symbols = []
     out = subprocess.check_output(["nm", "--undefined-only", module],
                                   encoding="ascii",
@@ -80,7 +81,7 @@
     for line in out.splitlines():
       symbols.append(line.strip().split()[1])
 
-    result[module] = symbol_sort(symbols)
+    result[os.path.basename(module)] = symbol_sort(symbols)
 
   return result
 
@@ -113,20 +114,43 @@
             symbol, module))
 
 
-def update_whitelist(whitelist, undefined_symbols, exported_in_vmlinux):
-  """Create or update an existing symbol whitelist for libabigail."""
-  new_wl = []
-  if os.path.isfile(whitelist):
-    with open(whitelist) as wl:
-      new_wl = [line.strip() for line in wl.readlines()[1:] if line.strip()]
+def create_whitelist(whitelist, undefined_symbols, exported):
+  """Create a symbol whitelist for libabigail."""
+  symbol_counter = collections.Counter(
+      itertools.chain.from_iterable(undefined_symbols.values()))
 
-  new_wl.extend(
-      [symbol for symbol in undefined_symbols if symbol in exported_in_vmlinux])
-  new_wl = symbol_sort(new_wl)
+
   with open(whitelist, "w") as wl:
-    pass
-    wl.write("[abi_whitelist]\n  ")
-    wl.write("\n  ".join(new_wl))
+
+    common_wl_section = symbol_sort([
+        symbol for symbol, count in symbol_counter.items()
+        if count > 1 and symbol in exported
+    ])
+
+    # always write the header
+    wl.write("[abi_whitelist]\n")
+
+    if common_wl_section:
+      wl.write("# commonly used symbols\n  ")
+      wl.write("\n  ".join(common_wl_section))
+      wl.write("\n")
+    else:
+      # ensure the whitelist contains at least one symbol to not be ignored
+      wl.write("  dummy_symbol\n")
+
+    for module, symbols in undefined_symbols.items():
+
+      new_wl_section = symbol_sort([
+          symbol for symbol in symbols
+          if symbol in exported and symbol not in common_wl_section
+      ])
+
+      if not new_wl_section:
+        continue
+
+      wl.write("\n# required by {}\n  ".format(module))
+      wl.write("\n  ".join(new_wl_section))
+      wl.write("\n")
 
 
 def main():
@@ -155,7 +179,7 @@
   parser.add_argument(
       "--whitelist",
       default="/dev/stdout",
-      help="The whitelist to create or update")
+      help="The whitelist to create")
 
   args = parser.parse_args()
 
@@ -173,8 +197,6 @@
 
   # Get required symbols of all modules
   undefined_symbols = extract_undefined_symbols(modules)
-  all_undefined = symbol_sort(
-      itertools.chain.from_iterable(undefined_symbols.values()))
 
   # Get the actually defined and exported symbols
   exported_in_vmlinux = extract_exported_symbols(vmlinux)
@@ -190,10 +212,10 @@
   if args.report_missing:
     report_missing(undefined_symbols, all_exported)
 
-  # If specified, update the whitelist
+  # If specified, create the whitelist
   if args.whitelist:
-    update_whitelist(
-        args.whitelist, all_undefined,
+    create_whitelist(
+        args.whitelist, undefined_symbols,
         all_exported if args.include_module_exports else exported_in_vmlinux)