[go: nahoru, domu]

Reland "Android: Move dep-related code to dep_utils.py"

This reverts commit 1a61670568a764168c353c3e4a91f53fdcb72984.

Reason for revert: angle is adding //tools/android in
https://crrev.com/c/4582331

Original change's description:
> Revert "Android: Move dep-related code to dep_utils.py"
>
> This reverts commit 4ba710110b52161201898c376e50cc62418e0052.
>
> Reason for revert: https://anglebug.com/8178
>
> Original change's description:
> > Android: Move dep-related code to dep_utils.py
> >
> > This CL mostly moves code from lookup_dep.py -> dep_utils.py so that it
> > is accessible by //build scripts. This paves the way to simplifying the
> > code in subsequent CLs and making use of it in bytecode_processor.py.
> >
> > Bug: 1099522
> > Change-Id: Ia197e113a26b550a2a080c589ee23200fafe3767
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4567009
> > Commit-Queue: Mohamed Heikal <mheikal@chromium.org>
> > Reviewed-by: Mohamed Heikal <mheikal@chromium.org>
> > Auto-Submit: Peter Wen <wnwen@chromium.org>
> > Commit-Queue: Peter Wen <wnwen@chromium.org>
> > Cr-Commit-Position: refs/heads/main@{#1149807}
>
> Bug: 1099522
> Bug: angleproject:8178
> Change-Id: Id840469247b903de9ed29b016339cca3342c2bf2
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4573447
> Auto-Submit: Roman Lavrov <romanl@google.com>
> Reviewed-by: Mohamed Heikal <mheikal@chromium.org>
> Commit-Queue: Andrew Grieve <agrieve@chromium.org>
> Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
> Reviewed-by: Andrew Grieve <agrieve@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1150305}

Bug: 1099522
Bug: angleproject:8178
Change-Id: I6ed2d0a66851cafe030c433150b1886a828a6226
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4582810
Commit-Queue: Peter Wen <wnwen@chromium.org>
Auto-Submit: Peter Wen <wnwen@chromium.org>
Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/heads/main@{#1152609}
diff --git a/build/android/gyp/bytecode_processor.py b/build/android/gyp/bytecode_processor.py
index ea9470c..127e218 100755
--- a/build/android/gyp/bytecode_processor.py
+++ b/build/android/gyp/bytecode_processor.py
@@ -13,8 +13,8 @@
 import sys
 from typing import Dict, List
 
-import javac_output_processor
 from util import build_utils
+from util import dep_utils
 from util import jar_utils
 from util import server_utils
 import action_helpers  # build_utils adds //build to sys.path.
@@ -215,7 +215,7 @@
   args.full_classpath_jars = action_helpers.parse_gn_list(
       args.full_classpath_jars)
   args.full_classpath_gn_targets = [
-      javac_output_processor.ReplaceGmsPackageIfNeeded(t)
+      dep_utils.ReplaceGmsPackageIfNeeded(t)
       for t in action_helpers.parse_gn_list(args.full_classpath_gn_targets)
   ]
   args.missing_classes_allowlist = action_helpers.parse_gn_list(
diff --git a/build/android/gyp/bytecode_processor.pydeps b/build/android/gyp/bytecode_processor.pydeps
index 1e6956a..78db0c4 100644
--- a/build/android/gyp/bytecode_processor.pydeps
+++ b/build/android/gyp/bytecode_processor.pydeps
@@ -9,13 +9,6 @@
 ../../../third_party/catapult/devil/devil/android/sdk/version_codes.py
 ../../../third_party/catapult/devil/devil/constants/__init__.py
 ../../../third_party/catapult/devil/devil/constants/exit_codes.py
-../../../third_party/colorama/src/colorama/__init__.py
-../../../third_party/colorama/src/colorama/ansi.py
-../../../third_party/colorama/src/colorama/ansitowin32.py
-../../../third_party/colorama/src/colorama/initialise.py
-../../../third_party/colorama/src/colorama/win32.py
-../../../third_party/colorama/src/colorama/winterm.py
-../../../tools/android/modularization/convenience/lookup_dep.py
 ../../../tools/android/modularization/gn/dep_operations.py
 ../../../tools/android/modularization/gn/json_gn_editor.py
 ../../../tools/android/modularization/gn/utils.py
@@ -28,8 +21,8 @@
 ../pylib/__init__.py
 ../pylib/constants/__init__.py
 bytecode_processor.py
-javac_output_processor.py
 util/__init__.py
 util/build_utils.py
+util/dep_utils.py
 util/jar_utils.py
 util/server_utils.py
diff --git a/build/android/gyp/compile_java.pydeps b/build/android/gyp/compile_java.pydeps
index 85da8c9..65cbd13 100644
--- a/build/android/gyp/compile_java.pydeps
+++ b/build/android/gyp/compile_java.pydeps
@@ -15,7 +15,6 @@
 ../../../third_party/colorama/src/colorama/initialise.py
 ../../../third_party/colorama/src/colorama/win32.py
 ../../../third_party/colorama/src/colorama/winterm.py
-../../../tools/android/modularization/convenience/lookup_dep.py
 ../../action_helpers.py
 ../../gn_helpers.py
 ../../print_python_deps.py
@@ -27,6 +26,7 @@
 javac_output_processor.py
 util/__init__.py
 util/build_utils.py
+util/dep_utils.py
 util/jar_info_utils.py
 util/jar_utils.py
 util/md5_check.py
diff --git a/build/android/gyp/compile_kt.pydeps b/build/android/gyp/compile_kt.pydeps
index bac5962..223647d 100644
--- a/build/android/gyp/compile_kt.pydeps
+++ b/build/android/gyp/compile_kt.pydeps
@@ -15,7 +15,6 @@
 ../../../third_party/colorama/src/colorama/initialise.py
 ../../../third_party/colorama/src/colorama/win32.py
 ../../../third_party/colorama/src/colorama/winterm.py
-../../../tools/android/modularization/convenience/lookup_dep.py
 ../../action_helpers.py
 ../../gn_helpers.py
 ../../print_python_deps.py
@@ -28,6 +27,7 @@
 javac_output_processor.py
 util/__init__.py
 util/build_utils.py
+util/dep_utils.py
 util/jar_info_utils.py
 util/jar_utils.py
 util/md5_check.py
diff --git a/build/android/gyp/javac_output_processor.py b/build/android/gyp/javac_output_processor.py
index 6faf5de5..db58845 100755
--- a/build/android/gyp/javac_output_processor.py
+++ b/build/android/gyp/javac_output_processor.py
@@ -5,62 +5,19 @@
 # found in the LICENSE file.
 """Contains helper class for processing javac output."""
 
-import dataclasses
 import os
 import pathlib
 import re
 import sys
 import traceback
-from typing import List
 
 from util import build_utils
+from util import dep_utils
 
 sys.path.insert(
     0,
     os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', 'colorama', 'src'))
 import colorama
-sys.path.insert(
-    0,
-    os.path.join(build_utils.DIR_SOURCE_ROOT, 'tools', 'android',
-                 'modularization', 'convenience'))
-import lookup_dep
-
-
-def ReplaceGmsPackageIfNeeded(target_name: str) -> str:
-  if target_name.startswith(
-      ('//third_party/android_deps:google_play_services_',
-       '//clank/third_party/google3:google_play_services_')):
-    return f'$google_play_services_package:{target_name.split(":")[1]}'
-  return target_name
-
-
-def _DisambiguateDeps(class_entries: List[lookup_dep.ClassEntry]):
-  def filter_if_not_empty(entries, filter_func):
-    filtered_entries = [e for e in entries if filter_func(e)]
-    return filtered_entries or entries
-
-  # When some deps are preferred, ignore all other potential deps.
-  class_entries = filter_if_not_empty(class_entries, lambda e: e.preferred_dep)
-
-  # E.g. javax_annotation_jsr250_api_java.
-  class_entries = filter_if_not_empty(class_entries,
-                                      lambda e: 'jsr' in e.target)
-
-  # Avoid suggesting subtargets when regular targets exist.
-  class_entries = filter_if_not_empty(class_entries,
-                                      lambda e: '__' not in e.target)
-
-  # Swap out GMS package names if needed.
-  class_entries = [
-      dataclasses.replace(e, target=ReplaceGmsPackageIfNeeded(e.target))
-      for e in class_entries
-  ]
-
-  # Convert to dict and then use list to get the keys back to remove dups and
-  # keep order the same as before.
-  class_entries = list({e: True for e in class_entries})
-
-  return class_entries
 
 
 class JavacOutputProcessor:
@@ -181,7 +138,7 @@
       return
 
     if self._class_lookup_index is None:
-      self._class_lookup_index = lookup_dep.ClassLookupIndex(
+      self._class_lookup_index = dep_utils.ClassLookupIndex(
           pathlib.Path(os.getcwd()),
           should_build=False,
       )
@@ -192,7 +149,7 @@
     if not suggested_deps:
       return
 
-    suggested_deps = _DisambiguateDeps(suggested_deps)
+    suggested_deps = dep_utils.DisambiguateDeps(suggested_deps)
     suggested_deps_str = ', '.join(s.target for s in suggested_deps)
 
     if len(suggested_deps) > 1:
diff --git a/build/android/gyp/turbine.pydeps b/build/android/gyp/turbine.pydeps
index dca0c69..cfe15c5c 100644
--- a/build/android/gyp/turbine.pydeps
+++ b/build/android/gyp/turbine.pydeps
@@ -15,7 +15,6 @@
 ../../../third_party/colorama/src/colorama/initialise.py
 ../../../third_party/colorama/src/colorama/win32.py
 ../../../third_party/colorama/src/colorama/winterm.py
-../../../tools/android/modularization/convenience/lookup_dep.py
 ../../action_helpers.py
 ../../gn_helpers.py
 ../../print_python_deps.py
@@ -28,6 +27,7 @@
 turbine.py
 util/__init__.py
 util/build_utils.py
+util/dep_utils.py
 util/jar_info_utils.py
 util/jar_utils.py
 util/md5_check.py
diff --git a/build/android/gyp/util/build_utils.py b/build/android/gyp/util/build_utils.py
index d5dbb3a..ad0515b 100644
--- a/build/android/gyp/util/build_utils.py
+++ b/build/android/gyp/util/build_utils.py
@@ -12,7 +12,6 @@
 import json
 import logging
 import os
-import pipes
 import re
 import shlex
 import shutil
@@ -21,7 +20,6 @@
 import sys
 import tempfile
 import textwrap
-import time
 import zipfile
 
 sys.path.append(os.path.join(os.path.dirname(__file__),
diff --git a/build/android/gyp/util/dep_utils.py b/build/android/gyp/util/dep_utils.py
new file mode 100644
index 0000000..1220991a
--- /dev/null
+++ b/build/android/gyp/util/dep_utils.py
@@ -0,0 +1,265 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Methods for managing deps based on build_config.json files."""
+
+from __future__ import annotations
+import collections
+
+import dataclasses
+import json
+import logging
+import os
+import pathlib
+import subprocess
+import sys
+from typing import Dict, Iterator, List, Set
+
+from util import jar_utils
+
+_SRC_PATH = pathlib.Path(__file__).resolve().parents[4]
+
+sys.path.append(str(_SRC_PATH / 'build/android'))
+# Import list_java_targets so that the dependency is found by print_python_deps.
+import list_java_targets
+
+
+@dataclasses.dataclass(frozen=True)
+class ClassEntry:
+  """An assignment of a Java class to a build target."""
+  full_class_name: str
+  target: str
+  preferred_dep: bool
+
+  def __lt__(self, other: 'ClassEntry'):
+    # Prefer canonical targets first.
+    if self.preferred_dep and not other.preferred_dep:
+      return True
+    # Prefer targets without __ in the name. Usually double underscores are used
+    # for internal subtargets and not top level targets.
+    if '__' not in self.target and '__' in other.target:
+      return True
+    # Prefer shorter target names first since they are usually the correct ones.
+    if len(self.target) < len(other.target):
+      return True
+    if len(self.target) > len(other.target):
+      return False
+    # Use string comparison to get a stable ordering of equal-length names.
+    return self.target < other.target
+
+
+@dataclasses.dataclass
+class BuildConfig:
+  """Container for information from a build config."""
+  target_name: str
+  relpath: str
+  is_group: bool
+  preferred_dep: bool
+  dependent_config_paths: List[str]
+  full_class_names: Set[str]
+
+  def all_dependent_configs(
+      self,
+      path_to_configs: Dict[str, 'BuildConfig'],
+  ) -> Iterator['BuildConfig']:
+    for path in self.dependent_config_paths:
+      dep_build_config = path_to_configs.get(path)
+      # This can happen when a java group depends on non-java targets.
+      if dep_build_config is None:
+        continue
+      yield dep_build_config
+      if dep_build_config.is_group:
+        yield from dep_build_config.all_dependent_configs(path_to_configs)
+
+
+class ClassLookupIndex:
+  """A map from full Java class to its build targets.
+
+  A class might be in multiple targets if it's bytecode rewritten."""
+  def __init__(self, abs_build_output_dir: pathlib.Path, should_build: bool):
+    self._abs_build_output_dir = abs_build_output_dir
+    self._should_build = should_build
+    self._class_index = self._index_root()
+
+  def match(self, search_string: str) -> List[ClassEntry]:
+    """Get class/target entries where the class matches search_string"""
+    # Priority 1: Exact full matches
+    if search_string in self._class_index:
+      return self._entries_for(search_string)
+
+    # Priority 2: Match full class name (any case), if it's a class name
+    matches = []
+    lower_search_string = search_string.lower()
+    if '.' not in lower_search_string:
+      for full_class_name in self._class_index:
+        package_and_class = full_class_name.rsplit('.', 1)
+        if len(package_and_class) < 2:
+          continue
+        class_name = package_and_class[1]
+        class_lower = class_name.lower()
+        if class_lower == lower_search_string:
+          matches.extend(self._entries_for(full_class_name))
+      if matches:
+        return matches
+
+    # Priority 3: Match anything
+    for full_class_name in self._class_index:
+      if lower_search_string in full_class_name.lower():
+        matches.extend(self._entries_for(full_class_name))
+
+    return matches
+
+  def _entries_for(self, class_name) -> List[ClassEntry]:
+    return sorted(self._class_index[class_name])
+
+  def _index_root(self) -> Dict[str, Set[ClassEntry]]:
+    """Create the class to target index."""
+    logging.debug('Running list_java_targets.py...')
+    list_java_targets_command = [
+        'build/android/list_java_targets.py', '--gn-labels',
+        '--print-build-config-paths',
+        f'--output-directory={self._abs_build_output_dir}'
+    ]
+    if self._should_build:
+      list_java_targets_command += ['--build']
+
+    list_java_targets_run = subprocess.run(list_java_targets_command,
+                                           cwd=_SRC_PATH,
+                                           capture_output=True,
+                                           text=True,
+                                           check=True)
+    logging.debug('... done.')
+
+    # Parse output of list_java_targets.py into BuildConfig objects.
+    path_to_build_config: Dict[str, BuildConfig] = {}
+    target_lines = list_java_targets_run.stdout.splitlines()
+    for target_line in target_lines:
+      # Skip empty lines
+      if not target_line:
+        continue
+
+      target_line_parts = target_line.split(': ')
+      assert len(target_line_parts) == 2, target_line_parts
+      target_name, build_config_path = target_line_parts
+
+      if not os.path.exists(build_config_path):
+        assert not self._should_build
+        continue
+
+      with open(build_config_path) as build_config_contents:
+        build_config_json: Dict = json.load(build_config_contents)
+      deps_info = build_config_json['deps_info']
+
+      # Checking the library type here instead of in list_java_targets.py avoids
+      # reading each .build_config file twice.
+      if deps_info['type'] not in ('java_library', 'group'):
+        continue
+
+      relpath = os.path.relpath(build_config_path, self._abs_build_output_dir)
+      preferred_dep = bool(deps_info.get('preferred_dep'))
+      is_group = bool(deps_info.get('type') == 'group')
+      dependent_config_paths = deps_info.get('deps_configs', [])
+      full_class_names = self._compute_full_class_names_for_build_config(
+          deps_info)
+      build_config = BuildConfig(relpath=relpath,
+                                 target_name=target_name,
+                                 is_group=is_group,
+                                 preferred_dep=preferred_dep,
+                                 dependent_config_paths=dependent_config_paths,
+                                 full_class_names=full_class_names)
+      path_to_build_config[relpath] = build_config
+
+    # From GN's perspective, depending on a java group is the same as depending
+    # on all of its deps directly, since groups are collapsed in
+    # write_build_config.py. Thus, collect all the java files in a java group's
+    # deps (recursing into other java groups) and set that as the java group's
+    # list of classes.
+    for build_config in path_to_build_config.values():
+      if build_config.is_group:
+        for dep_build_config in build_config.all_dependent_configs(
+            path_to_build_config):
+          build_config.full_class_names.update(
+              dep_build_config.full_class_names)
+
+    class_index = collections.defaultdict(set)
+    for build_config in path_to_build_config.values():
+      for full_class_name in build_config.full_class_names:
+        class_index[full_class_name].add(
+            ClassEntry(full_class_name=full_class_name,
+                       target=build_config.target_name,
+                       preferred_dep=build_config.preferred_dep))
+
+    return class_index
+
+  def _compute_full_class_names_for_build_config(self,
+                                                 deps_info: Dict) -> Set[str]:
+    """Returns set of fully qualified class names for build config."""
+
+    full_class_names = set()
+
+    # Read the location of the target_sources_file from the build_config
+    sources_path = deps_info.get('target_sources_file')
+    if sources_path:
+      # Read the target_sources_file, indexing the classes found
+      with open(self._abs_build_output_dir / sources_path) as sources_contents:
+        for source_line in sources_contents:
+          source_path = pathlib.Path(source_line.strip())
+          java_class = jar_utils.parse_full_java_class(source_path)
+          if java_class:
+            full_class_names.add(java_class)
+
+    # |unprocessed_jar_path| is set for prebuilt targets. (ex:
+    # android_aar_prebuilt())
+    # |unprocessed_jar_path| might be set but not exist if not all targets have
+    # been built.
+    unprocessed_jar_path = deps_info.get('unprocessed_jar_path')
+    if unprocessed_jar_path:
+      abs_unprocessed_jar_path = (self._abs_build_output_dir /
+                                  unprocessed_jar_path)
+      if abs_unprocessed_jar_path.exists():
+        # Normalize path but do not follow symlink if .jar is symlink.
+        abs_unprocessed_jar_path = (abs_unprocessed_jar_path.parent.resolve() /
+                                    abs_unprocessed_jar_path.name)
+
+        full_class_names.update(
+            jar_utils.extract_full_class_names_from_jar(
+                self._abs_build_output_dir, abs_unprocessed_jar_path))
+
+    return full_class_names
+
+
+def ReplaceGmsPackageIfNeeded(target_name: str) -> str:
+  if target_name.startswith(
+      ('//third_party/android_deps:google_play_services_',
+       '//clank/third_party/google3:google_play_services_')):
+    return f'$google_play_services_package:{target_name.split(":")[1]}'
+  return target_name
+
+
+def DisambiguateDeps(class_entries: List[ClassEntry]):
+  def filter_if_not_empty(entries, filter_func):
+    filtered_entries = [e for e in entries if filter_func(e)]
+    return filtered_entries or entries
+
+  # When some deps are preferred, ignore all other potential deps.
+  class_entries = filter_if_not_empty(class_entries, lambda e: e.preferred_dep)
+
+  # E.g. javax_annotation_jsr250_api_java.
+  class_entries = filter_if_not_empty(class_entries,
+                                      lambda e: 'jsr' in e.target)
+
+  # Avoid suggesting subtargets when regular targets exist.
+  class_entries = filter_if_not_empty(class_entries,
+                                      lambda e: '__' not in e.target)
+
+  # Swap out GMS package names if needed.
+  class_entries = [
+      dataclasses.replace(e, target=ReplaceGmsPackageIfNeeded(e.target))
+      for e in class_entries
+  ]
+
+  # Convert to dict and then use list to get the keys back to remove dups and
+  # keep order the same as before.
+  class_entries = list({e: True for e in class_entries})
+
+  return class_entries
diff --git a/tools/android/modularization/convenience/lookup_dep.py b/tools/android/modularization/convenience/lookup_dep.py
index 5b5adc20..ee72cf9 100755
--- a/tools/android/modularization/convenience/lookup_dep.py
+++ b/tools/android/modularization/convenience/lookup_dep.py
@@ -14,30 +14,18 @@
 Find build target with class FooUtil:
    tools/android/modularization/convenience/lookup_dep.py FooUtil
 '''
-
-from __future__ import annotations
-
 import argparse
-import collections
-import dataclasses
-import json
 import logging
-import os
 import pathlib
-import subprocess
 import sys
-from typing import Dict, Iterator, List, Set
 
 _SRC_DIR = pathlib.Path(__file__).resolve().parents[4]
 
 sys.path.append(str(_SRC_DIR / 'build/android'))
 from pylib import constants
 
-# Import list_java_targets so that the dependency is found by print_python_deps.
-import list_java_targets
-
 sys.path.append(str(_SRC_DIR / 'build/android/gyp'))
-from util import jar_utils
+from util import dep_utils
 
 
 def main():
@@ -71,7 +59,7 @@
   abs_out_dir: pathlib.Path = pathlib.Path(
       constants.GetOutDirectory()).resolve()
 
-  index = ClassLookupIndex(abs_out_dir, arguments.build)
+  index = dep_utils.ClassLookupIndex(abs_out_dir, arguments.build)
   matches = {c: index.match(c) for c in arguments.classes}
 
   if not arguments.build:
@@ -81,7 +69,7 @@
         arguments.build = True
         break
     if arguments.build:
-      index = ClassLookupIndex(abs_out_dir, True)
+      index = dep_utils.ClassLookupIndex(abs_out_dir, True)
       matches = {c: index.match(c) for c in arguments.classes}
 
   if not arguments.build:
@@ -105,210 +93,5 @@
         print()
 
 
-@dataclasses.dataclass(frozen=True)
-class ClassEntry:
-  """An assignment of a Java class to a build target."""
-  full_class_name: str
-  target: str
-  preferred_dep: bool
-
-  def __lt__(self, other: 'ClassEntry'):
-    # Prefer canonical targets first.
-    if self.preferred_dep and not other.preferred_dep:
-      return True
-    # Prefer targets without __ in the name. Usually double underscores are used
-    # for internal subtargets and not top level targets.
-    if '__' not in self.target and '__' in other.target:
-      return True
-    # Prefer shorter target names first since they are usually the correct ones.
-    if len(self.target) < len(other.target):
-      return True
-    elif len(self.target) > len(other.target):
-      return False
-    # Use string comparison to get a stable ordering of equal-length names.
-    return self.target < other.target
-
-
-@dataclasses.dataclass
-class BuildConfig:
-  """Container for information from a build config."""
-  target_name: str
-  relpath: str
-  is_group: bool
-  preferred_dep: bool
-  dependent_config_paths: List[str]
-  full_class_names: Set[str]
-
-  def all_dependent_configs(
-      self,
-      path_to_configs: Dict[str, 'BuildConfig'],
-  ) -> Iterator['BuildConfig']:
-    for path in self.dependent_config_paths:
-      dep_build_config = path_to_configs.get(path)
-      # This can happen when a java group depends on non-java targets.
-      if dep_build_config is None:
-        continue
-      yield dep_build_config
-      if dep_build_config.is_group:
-        yield from dep_build_config.all_dependent_configs(path_to_configs)
-
-
-class ClassLookupIndex:
-  """A map from full Java class to its build targets.
-
-  A class might be in multiple targets if it's bytecode rewritten."""
-
-  def __init__(self, abs_build_output_dir: pathlib.Path, should_build: bool):
-    self._abs_build_output_dir = abs_build_output_dir
-    self._should_build = should_build
-    self._class_index = self._index_root()
-
-  def match(self, search_string: str) -> List[ClassEntry]:
-    """Get class/target entries where the class matches search_string"""
-    # Priority 1: Exact full matches
-    if search_string in self._class_index:
-      return self._entries_for(search_string)
-
-    # Priority 2: Match full class name (any case), if it's a class name
-    matches = []
-    lower_search_string = search_string.lower()
-    if '.' not in lower_search_string:
-      for full_class_name in self._class_index:
-        package_and_class = full_class_name.rsplit('.', 1)
-        if len(package_and_class) < 2:
-          continue
-        class_name = package_and_class[1]
-        class_lower = class_name.lower()
-        if class_lower == lower_search_string:
-          matches.extend(self._entries_for(full_class_name))
-      if matches:
-        return matches
-
-    # Priority 3: Match anything
-    for full_class_name in self._class_index:
-      if lower_search_string in full_class_name.lower():
-        matches.extend(self._entries_for(full_class_name))
-
-    return matches
-
-  def _entries_for(self, class_name) -> List[ClassEntry]:
-    return sorted(self._class_index[class_name])
-
-  def _index_root(self) -> Dict[str, Set[ClassEntry]]:
-    """Create the class to target index."""
-    logging.debug('Running list_java_targets.py...')
-    list_java_targets_command = [
-        sys.executable, 'build/android/list_java_targets.py', '--gn-labels',
-        '--print-build-config-paths',
-        f'--output-directory={self._abs_build_output_dir}'
-    ]
-    if self._should_build:
-      list_java_targets_command += ['--build']
-
-    list_java_targets_run = subprocess.run(list_java_targets_command,
-                                           cwd=_SRC_DIR,
-                                           capture_output=True,
-                                           text=True,
-                                           check=True)
-    logging.debug('... done.')
-
-    # Parse output of list_java_targets.py into BuildConfig objects.
-    path_to_build_config: Dict[str, BuildConfig] = {}
-    target_lines = list_java_targets_run.stdout.splitlines()
-    for target_line in target_lines:
-      # Skip empty lines
-      if not target_line:
-        continue
-
-      target_line_parts = target_line.split(': ')
-      assert len(target_line_parts) == 2, target_line_parts
-      target_name, build_config_path = target_line_parts
-
-      if not os.path.exists(build_config_path):
-        assert not self._should_build
-        continue
-
-      with open(build_config_path) as build_config_contents:
-        build_config_json: Dict = json.load(build_config_contents)
-      deps_info = build_config_json['deps_info']
-
-      # Checking the library type here instead of in list_java_targets.py avoids
-      # reading each .build_config file twice.
-      if deps_info['type'] not in ('java_library', 'group'):
-        continue
-
-      relpath = os.path.relpath(build_config_path, self._abs_build_output_dir)
-      preferred_dep = bool(deps_info.get('preferred_dep'))
-      is_group = bool(deps_info.get('type') == 'group')
-      dependent_config_paths = deps_info.get('deps_configs', [])
-      full_class_names = self._compute_full_class_names_for_build_config(
-          deps_info)
-      build_config = BuildConfig(relpath=relpath,
-                                 target_name=target_name,
-                                 is_group=is_group,
-                                 preferred_dep=preferred_dep,
-                                 dependent_config_paths=dependent_config_paths,
-                                 full_class_names=full_class_names)
-      path_to_build_config[relpath] = build_config
-
-    # From GN's perspective, depending on a java group is the same as depending
-    # on all of its deps directly, since groups are collapsed in
-    # write_build_config.py. Thus, collect all the java files in a java group's
-    # deps (recursing into other java groups) and set that as the java group's
-    # list of classes.
-    for build_config in path_to_build_config.values():
-      if build_config.is_group:
-        for dep_build_config in build_config.all_dependent_configs(
-            path_to_build_config):
-          build_config.full_class_names.update(
-              dep_build_config.full_class_names)
-
-    class_index = collections.defaultdict(set)
-    for build_config in path_to_build_config.values():
-      for full_class_name in build_config.full_class_names:
-        class_index[full_class_name].add(
-            ClassEntry(full_class_name=full_class_name,
-                       target=build_config.target_name,
-                       preferred_dep=build_config.preferred_dep))
-
-    return class_index
-
-  def _compute_full_class_names_for_build_config(self,
-                                                 deps_info: Dict) -> Set[str]:
-    """Returns set of fully qualified class names for build config."""
-
-    full_class_names = set()
-
-    # Read the location of the target_sources_file from the build_config
-    sources_path = deps_info.get('target_sources_file')
-    if sources_path:
-      # Read the target_sources_file, indexing the classes found
-      with open(self._abs_build_output_dir / sources_path) as sources_contents:
-        for source_line in sources_contents:
-          source_path = pathlib.Path(source_line.strip())
-          java_class = jar_utils.parse_full_java_class(source_path)
-          if java_class:
-            full_class_names.add(java_class)
-
-    # |unprocessed_jar_path| is set for prebuilt targets. (ex:
-    # android_aar_prebuilt())
-    # |unprocessed_jar_path| might be set but not exist if not all targets have
-    # been built.
-    unprocessed_jar_path = deps_info.get('unprocessed_jar_path')
-    if unprocessed_jar_path:
-      abs_unprocessed_jar_path = (self._abs_build_output_dir /
-                                  unprocessed_jar_path)
-      if abs_unprocessed_jar_path.exists():
-        # Normalize path but do not follow symlink if .jar is symlink.
-        abs_unprocessed_jar_path = (abs_unprocessed_jar_path.parent.resolve() /
-                                    abs_unprocessed_jar_path.name)
-
-        full_class_names.update(
-            jar_utils.extract_full_class_names_from_jar(
-                self._abs_build_output_dir, abs_unprocessed_jar_path))
-
-    return full_class_names
-
-
 if __name__ == '__main__':
   main()