def-tool: Add app-specific search path

This commit adds app-specific search paths for the native libraries in
APK files.

Bug: 120520550
Test: ./tests/run.py
Change-Id: Id547c963c4fb2cf2f29f9eb07a958f1d43e86b65
diff --git a/vndk/tools/definition-tool/tests/test_elf_resolver.py b/vndk/tools/definition-tool/tests/test_elf_resolver.py
index 020ba5c..29973e3 100755
--- a/vndk/tools/definition-tool/tests/test_elf_resolver.py
+++ b/vndk/tools/definition-tool/tests/test_elf_resolver.py
@@ -29,43 +29,69 @@
 
         self.assertEqual(
                 ['/system/lib/libx.so', '/vendor/lib/libx.so'],
-                list(r.get_candidates('libx.so')))
+                list(r.get_candidates('/system/lib/libreq.so', 'libx.so')))
 
         self.assertEqual(
                 ['/C/libx.so', '/system/lib/libx.so', '/vendor/lib/libx.so'],
-                list(r.get_candidates('libx.so', ['/C'])))
+                list(r.get_candidates('/system/lib/libreq.so', 'libx.so',
+                                      ['/C'])))
 
         self.assertEqual(
                 ['/C/libx.so', '/D/libx.so', '/system/lib/libx.so',
                  '/vendor/lib/libx.so'],
-                list(r.get_candidates('libx.so', ['/C', '/D'])))
+                list(r.get_candidates('/system/lib/libreq.so', 'libx.so',
+                                      ['/C', '/D'])))
 
         self.assertEqual(
                 ['/E/libx.so', '/system/lib/libx.so', '/vendor/lib/libx.so'],
-                list(r.get_candidates('libx.so', None, ['/E'])))
+                list(r.get_candidates('/system/lib/libreq.so', 'libx.so', None,
+                                      ['/E'])))
 
         self.assertEqual(
                 ['/E/libx.so', '/F/libx.so', '/system/lib/libx.so',
                  '/vendor/lib/libx.so'],
-                list(r.get_candidates('libx.so', None, ['/E', '/F'])))
+                list(r.get_candidates('/system/lib/libreq.so', 'libx.so', None,
+                                      ['/E', '/F'])))
 
         self.assertEqual(
                 ['/C/libx.so', '/D/libx.so', '/E/libx.so', '/F/libx.so',
                  '/system/lib/libx.so', '/vendor/lib/libx.so'],
-                list(r.get_candidates('libx.so', ['/C', '/D'], ['/E', '/F'])))
+                list(r.get_candidates('/system/lib/libreq.so', 'libx.so',
+                                      ['/C', '/D'], ['/E', '/F'])))
+
+        # Test app-specific search paths.
+        self.assertEqual(
+                ['/system/app/example/lib/armeabi-v7a/libx.so',
+                 '/C/libx.so', '/D/libx.so', '/E/libx.so', '/F/libx.so',
+                 '/system/lib/libx.so', '/vendor/lib/libx.so'],
+                list(r.get_candidates(
+                        '/system/app/example/lib/armeabi-v7a/libreq.so',
+                        'libx.so',
+                        ['/C', '/D'], ['/E', '/F'])))
 
     def test_resolve(self):
         r = self.resolver
-        self.assertEqual('a', r.resolve('liba.so'))
-        self.assertEqual('c', r.resolve('libc.so'))
+        self.assertEqual('a', r.resolve('/system/lib/libreq.so', 'liba.so'))
+        self.assertEqual('c', r.resolve('/system/lib/libreq.so', 'libc.so'))
 
-        self.assertEqual(None, r.resolve('libe.so'))
-        self.assertEqual('e', r.resolve('libe.so', dt_rpath=['/system/lib/hw']))
+        self.assertEqual(None, r.resolve('/system/lib/libreq.so', 'libe.so'))
         self.assertEqual(
-                'e', r.resolve('libe.so', dt_runpath=['/system/lib/hw']))
+                'e',
+                r.resolve('/system/lib/libreq.so', 'libe.so',
+                          dt_rpath=['/system/lib/hw']))
+        self.assertEqual(
+                'e',
+                r.resolve('/system/lib/libreq.so', 'libe.so',
+                          dt_runpath=['/system/lib/hw']))
 
-        self.assertEqual('a2', r.resolve('liba.so', dt_rpath=['/vendor/lib']))
-        self.assertEqual('a2', r.resolve('liba.so', dt_runpath=['/vendor/lib']))
+        self.assertEqual(
+                'a2',
+                r.resolve('/system/lib/libreq.so', 'liba.so',
+                          dt_rpath=['/vendor/lib']))
+        self.assertEqual(
+                'a2',
+                r.resolve('/system/lib/libreq.so', 'liba.so',
+                          dt_runpath=['/vendor/lib']))
 
 
 if __name__ == '__main__':
diff --git a/vndk/tools/definition-tool/vndk_definition_tool.py b/vndk/tools/definition-tool/vndk_definition_tool.py
index 13a2310..cd505a1 100755
--- a/vndk/tools/definition-tool/vndk_definition_tool.py
+++ b/vndk/tools/definition-tool/vndk_definition_tool.py
@@ -1646,12 +1646,21 @@
         return self.sorted_version(self)[0]
 
 
+# File path patterns for Android apps
+_APP_DIR_PATTERNS = re.compile('^(?:/[^/]+){1,2}/(?:priv-)?app/')
+
+
 class ELFResolver(object):
     def __init__(self, lib_set, default_search_path):
         self.lib_set = lib_set
         self.default_search_path = default_search_path
 
-    def get_candidates(self, name, dt_rpath=None, dt_runpath=None):
+    def get_candidates(self, requester, name, dt_rpath=None, dt_runpath=None):
+        # Search app-specific search paths.
+        if _APP_DIR_PATTERNS.match(requester):
+            yield os.path.join(os.path.dirname(requester), name)
+
+        # Search default search paths.
         if dt_rpath:
             for d in dt_rpath:
                 yield os.path.join(d, name)
@@ -1661,8 +1670,8 @@
         for d in self.default_search_path:
             yield os.path.join(d, name)
 
-    def resolve(self, name, dt_rpath=None, dt_runpath=None):
-        for path in self.get_candidates(name, dt_rpath, dt_runpath):
+    def resolve(self, requester, name, dt_rpath=None, dt_runpath=None):
+        for path in self.get_candidates(requester, name, dt_rpath, dt_runpath):
             try:
                 return self.lib_set[path]
             except KeyError:
@@ -2019,11 +2028,11 @@
     def _resolve_lib_dt_needed(self, lib, resolver):
         imported_libs = []
         for dt_needed in lib.elf.dt_needed:
-            dep = resolver.resolve(dt_needed, lib.elf.dt_rpath,
+            dep = resolver.resolve(lib.path, dt_needed, lib.elf.dt_rpath,
                                    lib.elf.dt_runpath)
             if not dep:
                 candidates = list(resolver.get_candidates(
-                    dt_needed, lib.elf.dt_rpath, lib.elf.dt_runpath))
+                    lib.path, dt_needed, lib.elf.dt_rpath, lib.elf.dt_runpath))
                 print('warning: {}: Missing needed library: {}  Tried: {}'
                       .format(lib.path, dt_needed, candidates), file=sys.stderr)
                 lib.unresolved_dt_needed.append(dt_needed)
@@ -2728,7 +2737,19 @@
         libs = set()
         for string in strings:
             try:
-                libs.update(libnames[string])
+                for dep_file in libnames[string]:
+                    match = _APP_DIR_PATTERNS.match(dep_file.path)
+
+                    # List the lib if it is not embedded in the app.
+                    if not match:
+                        libs.add(dep_file)
+                        continue
+
+                    # Only list the embedded lib if it is in the same app.
+                    common = os.path.commonprefix([ap, dep_file.path])
+                    if len(common) > len(match.group(0)):
+                        libs.add(dep_file)
+                        continue
             except KeyError:
                 pass