vndk-def: check-dep: Print symbols and module_path

This commit add the functionality to print depended symbols from
ineligible libs and violating module source path to check-dep
subcommand.

Test: ./tests/run.py

Test: Run `vndk_definition_tool.py deps --symbols` on various tree and
the output should remain identical.

Test: Run `vndk_definition_tool.py check-dep --module-info ...`

Bug: 32811412

Change-Id: Ie9fa31da4ac18425604e27e0377c7da2b88bd4eb
diff --git a/vndk/tools/definition-tool/tests/test_elf_link_data.py b/vndk/tools/definition-tool/tests/test_elf_link_data.py
index febe990..ce5038c 100755
--- a/vndk/tools/definition-tool/tests/test_elf_link_data.py
+++ b/vndk/tools/definition-tool/tests/test_elf_link_data.py
@@ -83,6 +83,22 @@
         self.assertTrue(self.x.is_system_lib())
         self.assertFalse(self.v.is_system_lib())
 
+    def test_get_dep_linked_symbols(self):
+        self.x.linked_symbols['c'] = self.y
+        self.x.linked_symbols['b'] = self.y
+        self.x.linked_symbols['a'] = self.y
+
+        self.x.linked_symbols['w'] = self.z
+        self.x.linked_symbols['z'] = self.z
+        self.x.linked_symbols['y'] = self.z
+        self.x.linked_symbols['x'] = self.z
+
+        self.assertEqual(['a', 'b', 'c'],
+                         self.x.get_dep_linked_symbols(self.y))
+
+        self.assertEqual(['w', 'x', 'y', 'z'],
+                         self.x.get_dep_linked_symbols(self.z))
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/vndk/tools/definition-tool/tests/test_module_info.py b/vndk/tools/definition-tool/tests/test_module_info.py
new file mode 100755
index 0000000..ba9f618
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/test_module_info.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+import os
+import sys
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+import unittest
+
+from vndk_definition_tool import ModuleInfo
+
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+
+
+class ModuleInfoTest(unittest.TestCase):
+    def test_default(self):
+        m = ModuleInfo()
+        self.assertEqual([], m.get_module_path('/system/lib64/libA.so'))
+
+    def test_get_module_path(self):
+        m = ModuleInfo(os.path.join(SCRIPT_DIR, 'testdata', 'test_module_info',
+                                    'module-info.json'))
+
+        self.assertEqual(['system/core/libA'],
+                         m.get_module_path('/system/lib64/libA.so'))
+        self.assertEqual(['frameworks/base/libB'],
+                         m.get_module_path('/system/lib64/libB.so'))
+        self.assertEqual(['frameworks/base/libC'],
+                         m.get_module_path('/system/lib64/libC.so'))
+        self.assertEqual(['frameworks/base/libC'],
+                         m.get_module_path('/system/lib64/hw/libC.so'))
+
+        self.assertEqual(
+                [], m.get_module_path('/system/lib64/libdoes_not_exist.so'))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/vndk/tools/definition-tool/tests/testdata/test_module_info/module-info.json b/vndk/tools/definition-tool/tests/testdata/test_module_info/module-info.json
new file mode 100644
index 0000000..81db1d9
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_module_info/module-info.json
@@ -0,0 +1,5 @@
+{
+  "libA": { "path": ["system/core/libA"],  "installed": ["out/target/product/generic_arm64/system/lib64/libA.so"] },
+  "libB": { "path": ["frameworks/base/libB"],  "installed": ["out/target/product/generic_arm64/system/lib64/libB.so"] },
+  "libC": { "path": ["frameworks/base/libC"],  "installed": ["out/target/product/generic_arm64/system/lib64/libC.so", "out/target/product/generic_arm64/system/lib64/hw/libC.so"] }
+}
diff --git a/vndk/tools/definition-tool/vndk_definition_tool.py b/vndk/tools/definition-tool/vndk_definition_tool.py
index 9155eb8..d59275a 100755
--- a/vndk/tools/definition-tool/vndk_definition_tool.py
+++ b/vndk/tools/definition-tool/vndk_definition_tool.py
@@ -792,6 +792,13 @@
     def is_system_lib(self):
         return self.partition == PT_SYSTEM
 
+    def get_dep_linked_symbols(self, dep):
+        symbols = set()
+        for symbol, exp_lib in self.linked_symbols.items():
+            if exp_lib == dep:
+                symbols.add(symbol)
+        return sorted(symbols)
+
 
 def sorted_lib_path_list(libs):
     libs = [lib.path for lib in libs]
@@ -2102,16 +2109,12 @@
         results = []
         for partition in range(NUM_PARTITIONS):
             for name, lib in graph.lib_pt[partition].items():
-                if not args.symbols:
+                if args.symbols:
                     def collect_symbols(user, definer):
-                        return ()
+                        return user.get_dep_linked_symbols(definer)
                 else:
                     def collect_symbols(user, definer):
-                        symbols = set()
-                        for symbol, exp_lib in user.linked_symbols.items():
-                            if exp_lib == definer:
-                                symbols.add(symbol)
-                        return sorted(symbols)
+                        return ()
 
                 data = []
                 if args.revert:
@@ -2186,6 +2189,21 @@
 
 TaggedLibDict = collections.namedtuple('TaggedLibDict', TAGGED_LIB_DICT_FIELDS)
 
+class ModuleInfo(object):
+    def __init__(self, module_info_path=None):
+        if not module_info_path:
+            self.json = dict()
+        else:
+            with open(module_info_path, 'r') as f:
+                self.json = json.load(f)
+
+    def get_module_path(self, installed_path):
+        for name, module in  self.json.items():
+            if any(path.endswith(installed_path)
+                   for path in module['installed']):
+                return module['path']
+        return []
+
 class CheckDepCommand(ELFGraphCommand):
     def __init__(self):
         super(CheckDepCommand, self).__init__(
@@ -2196,6 +2214,8 @@
 
         parser.add_argument('--tag-file', required=True)
 
+        parser.add_argument('--module-info')
+
     def _load_tag_file(self, tag_file_path):
         res = TaggedLibDict(set(), set(), set(), set(), set(), set(), set(),
                             set())
@@ -2230,7 +2250,17 @@
                     res[i].add(lib)
         return res
 
-    def _check_eligible_vndk_dep(self, graph, tagged_libs):
+    @staticmethod
+    def _dump_dep(lib, bad_deps, module_info):
+        print(lib.path)
+        for module_path in module_info.get_module_path(lib.path):
+            print('\tMODULE_PATH:', module_path)
+        for dep in bad_deps:
+            print('\t' + dep.path)
+            for symbol in lib.get_dep_linked_symbols(dep):
+                print('\t\t' + symbol)
+
+    def _check_eligible_vndk_dep(self, graph, tagged_libs, module_info):
         """Check whether eligible sets are self-contained."""
         num_errors = 0
 
@@ -2242,27 +2272,32 @@
 
         # Check eligible vndk is self-contained.
         for lib in eligible_libs:
+            bad_deps = []
             for dep in lib.deps:
                 if dep not in eligible_libs and dep not in indirect_libs:
-                    print('error: eligible-lib: {}: eligible lib "{}" should '
-                          'not depend on non-eligible lib "{}".'
-                          .format(lib.path, lib.path, dep.path),
+                    print('error: eligible lib "{}" should not depend on '
+                          'non-eligible lib "{}".'.format(lib.path, dep.path),
                           file=sys.stderr)
+                    bad_deps.append(dep)
                     num_errors += 1
+            if bad_deps:
+                self._dump_dep(lib, bad_deps, module_info)
 
         # Check the libbinder dependencies.
         for lib in eligible_libs:
+            bad_deps = []
             for dep in lib.deps:
                 if os.path.basename(dep.path) == 'libbinder.so':
-                    print('error: eligible-lib: {}: eligible lib "{}" should '
-                          'not depend on libbinder.so.'
-                          .format(lib.path, lib.path),
-                          file=sys.stderr)
+                    print('error: eligible lib "{}" should not depend on '
+                          'libbinder.so.'.format(lib.path), file=sys.stderr)
+                    bad_deps.append(dep)
                     num_errors += 1
+            if bad_deps:
+                self._dump_dep(lib, bad_deps, module_info)
 
         return num_errors
 
-    def _check_vendor_dep(self, graph, tagged_libs):
+    def _check_vendor_dep(self, graph, tagged_libs, module_info):
         """Check whether vendor libs are depending on non-eligible libs."""
         num_errors = 0
 
@@ -2273,13 +2308,16 @@
                          tagged_libs.vndk | tagged_libs.vndk_indirect)
 
         for lib in vendor_libs:
+            bad_deps = []
             for dep in lib.deps:
                 if dep not in vendor_libs and dep not in eligible_libs:
-                    print('error: vendor-lib: {}: vendor lib "{}" depends on '
-                          'non-eligible lib "{}".'
-                          .format(lib.path, lib.path, dep.path),
+                    print('error: vendor lib "{}" depends on non-eligible '
+                          'lib "{}".'.format(lib.path, dep.path),
                           file=sys.stderr)
+                    bad_deps.append(dep)
                     num_errors += 1
+            if bad_deps:
+                self._dump_dep(lib, bad_deps, module_info)
 
         return num_errors
 
@@ -2291,8 +2329,11 @@
         tags = self._load_tag_file(args.tag_file)
         tagged_libs = self._get_tagged_libs(graph, tags)
 
-        num_errors = self._check_eligible_vndk_dep(graph, tagged_libs)
-        num_errors += self._check_vendor_dep(graph, tagged_libs)
+        module_info = ModuleInfo(args.module_info)
+
+        num_errors = self._check_eligible_vndk_dep(graph, tagged_libs,
+                                                   module_info)
+        num_errors += self._check_vendor_dep(graph, tagged_libs, module_info)
 
         return 0 if num_errors == 0 else 1