Merge "enforce_permission_counter: Find targets automatically" into main
diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py
index 4a2e37e..2e82beb 100644
--- a/tools/lint/fix/soong_lint_fix.py
+++ b/tools/lint/fix/soong_lint_fix.py
@@ -14,6 +14,7 @@
import argparse
import json
+import functools
import os
import shutil
import subprocess
@@ -28,6 +29,7 @@
PATH_PREFIX = "out/soong/.intermediates"
PATH_SUFFIX = "android_common/lint"
FIX_ZIP = "suggested-fixes.zip"
+MODULE_JAVA_DEPS = "out/soong/module_bp_java_deps.json"
class SoongModule:
@@ -49,11 +51,26 @@
print(f"Found module {partial_path}/{self._name}.")
self._path = f"{PATH_PREFIX}/{partial_path}/{self._name}/{PATH_SUFFIX}"
+ def find_java_deps(self, module_java_deps):
+ """Finds the dependencies of a Java module in the loaded module_bp_java_deps.json.
+
+ Returns:
+ A list of module names.
+ """
+ if self._name not in module_java_deps:
+ raise Exception(f"Module {self._name} not found!")
+
+ return module_java_deps[self._name]["dependencies"]
+
@property
def name(self):
return self._name
@property
+ def path(self):
+ return self._path
+
+ @property
def lint_report(self):
return f"{self._path}/lint-report.txt"
@@ -62,52 +79,25 @@
return f"{self._path}/{FIX_ZIP}"
-class SoongLintFix:
+class SoongLintWrapper:
"""
- This class creates a command line tool that will apply lint fixes to the
- platform via the necessary combination of soong and shell commands.
+ This class wraps the necessary calls to Soong and/or shell commands to lint
+ platform modules and apply suggested fixes if desired.
- It breaks up these operations into a few "private" methods that are
- intentionally exposed so experimental code can tweak behavior.
-
- The entry point, `run`, will apply lint fixes using the intermediate
- `suggested-fixes` directory that soong creates during its invocation of
- lint.
-
- Basic usage:
- ```
- from soong_lint_fix import SoongLintFix
-
- opts = SoongLintFixOptions()
- opts.parse_args(sys.argv)
- SoongLintFix(opts).run()
- ```
+ It breaks up these operations into a few methods that are available to
+ sub-classes (see SoongLintFix for an example).
"""
- def __init__(self, opts):
- self._opts = opts
+ def __init__(self, check=None, lint_module=None):
+ self._check = check
+ self._lint_module = lint_module
self._kwargs = None
- self._modules = []
-
- def run(self):
- """
- Run the script
- """
- self._setup()
- self._find_modules()
- self._lint()
-
- if not self._opts.no_fix:
- self._fix()
-
- if self._opts.print:
- self._print()
def _setup(self):
env = os.environ.copy()
- if self._opts.check:
- env["ANDROID_LINT_CHECK"] = self._opts.check
- if self._opts.lint_module:
- env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._opts.lint_module
+ if self._check:
+ env["ANDROID_LINT_CHECK"] = self._check
+ if self._lint_module:
+ env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._lint_module
self._kwargs = {
"env": env,
@@ -117,7 +107,10 @@
os.chdir(ANDROID_BUILD_TOP)
- print("Refreshing soong modules...")
+ @functools.cached_property
+ def _module_info(self):
+ """Returns the JSON content of module-info.json."""
+ print("Refreshing Soong modules...")
try:
os.mkdir(ANDROID_PRODUCT_OUT)
except OSError:
@@ -125,19 +118,54 @@
subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs)
print("done.")
-
- def _find_modules(self):
with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f:
- module_info = json.load(f)
+ return json.load(f)
- for module_name in self._opts.modules:
- module = SoongModule(module_name)
- module.find(module_info)
- self._modules.append(module)
+ def _find_module(self, module_name):
+ """Returns a SoongModule from a module name.
- def _lint(self):
+ Ensures that the module is known to Soong.
+ """
+ module = SoongModule(module_name)
+ module.find(self._module_info)
+ return module
+
+ def _find_modules(self, module_names):
+ modules = []
+ for module_name in module_names:
+ modules.append(self._find_module(module_name))
+ return modules
+
+ @functools.cached_property
+ def _module_java_deps(self):
+ """Returns the JSON content of module_bp_java_deps.json."""
+ print("Refreshing Soong Java deps...")
+ subprocess.call(f"{SOONG_UI} --make-mode {MODULE_JAVA_DEPS}", **self._kwargs)
+ print("done.")
+
+ with open(f"{MODULE_JAVA_DEPS}") as f:
+ return json.load(f)
+
+ def _find_module_java_deps(self, module):
+ """Returns a list a dependencies for a module.
+
+ Args:
+ module: A SoongModule.
+
+ Returns:
+ A list of SoongModule.
+ """
+ deps = []
+ dep_names = module.find_java_deps(self._module_java_deps)
+ for dep_name in dep_names:
+ dep = SoongModule(dep_name)
+ dep.find(self._module_info)
+ deps.append(dep)
+ return deps
+
+ def _lint(self, modules):
print("Cleaning up any old lint results...")
- for module in self._modules:
+ for module in modules:
try:
os.remove(f"{module.lint_report}")
os.remove(f"{module.suggested_fixes}")
@@ -145,13 +173,13 @@
pass
print("done.")
- target = " ".join([ module.lint_report for module in self._modules ])
+ target = " ".join([ module.lint_report for module in modules ])
print(f"Generating {target}")
subprocess.call(f"{SOONG_UI} --make-mode {target}", **self._kwargs)
print("done.")
- def _fix(self):
- for module in self._modules:
+ def _fix(self, modules):
+ for module in modules:
print(f"Copying suggested fixes for {module.name} to the tree...")
with zipfile.ZipFile(f"{module.suggested_fixes}") as zip:
for name in zip.namelist():
@@ -161,13 +189,40 @@
shutil.copyfileobj(src, dst)
print("done.")
- def _print(self):
- for module in self._modules:
+ def _print(self, modules):
+ for module in modules:
print(f"### lint-report.txt {module.name} ###", end="\n\n")
with open(module.lint_report, "r") as f:
print(f.read())
+class SoongLintFix(SoongLintWrapper):
+ """
+ Basic usage:
+ ```
+ from soong_lint_fix import SoongLintFix
+
+ opts = SoongLintFixOptions()
+ opts.parse_args()
+ SoongLintFix(opts).run()
+ ```
+ """
+ def __init__(self, opts):
+ super().__init__(check=opts.check, lint_module=opts.lint_module)
+ self._opts = opts
+
+ def run(self):
+ self._setup()
+ modules = self._find_modules(self._opts.modules)
+ self._lint(modules)
+
+ if not self._opts.no_fix:
+ self._fix(modules)
+
+ if self._opts.print:
+ self._print(modules)
+
+
class SoongLintFixOptions:
"""Options for SoongLintFix"""
diff --git a/tools/lint/utils/enforce_permission_counter.py b/tools/lint/utils/enforce_permission_counter.py
index b5c2ffe..a4c00f7 100644
--- a/tools/lint/utils/enforce_permission_counter.py
+++ b/tools/lint/utils/enforce_permission_counter.py
@@ -16,57 +16,38 @@
import soong_lint_fix
-# Libraries that constitute system_server.
-# It is non-trivial to keep in sync with services/Android.bp as some
-# module are post-processed (e.g, services.core).
-TARGETS = [
- "services.core.unboosted",
- "services.accessibility",
- "services.appprediction",
- "services.appwidget",
- "services.autofill",
- "services.backup",
- "services.companion",
- "services.contentcapture",
- "services.contentsuggestions",
- "services.coverage",
- "services.devicepolicy",
- "services.midi",
- "services.musicsearch",
- "services.net",
- "services.people",
- "services.print",
- "services.profcollect",
- "services.restrictions",
- "services.searchui",
- "services.smartspace",
- "services.systemcaptions",
- "services.translation",
- "services.texttospeech",
- "services.usage",
- "services.usb",
- "services.voiceinteraction",
- "services.wallpapereffectsgeneration",
- "services.wifi",
-]
+CHECK = "AnnotatedAidlCounter"
+LINT_MODULE = "AndroidUtilsLintChecker"
-
-class EnforcePermissionMigratedCounter:
+class EnforcePermissionMigratedCounter(soong_lint_fix.SoongLintWrapper):
"""Wrapper around lint_fix to count the number of AIDL methods annotated."""
+
+ def __init__(self):
+ super().__init__(check=CHECK, lint_module=LINT_MODULE)
+
def run(self):
- opts = soong_lint_fix.SoongLintFixOptions()
- opts.check = "AnnotatedAidlCounter"
- opts.lint_module = "AndroidUtilsLintChecker"
- opts.no_fix = True
- opts.modules = TARGETS
+ self._setup()
- self.linter = soong_lint_fix.SoongLintFix(opts)
- self.linter.run()
- self.parse_lint_reports()
+ # Analyze the dependencies of the "services" module and the module
+ # "services.core.unboosted".
+ service_module = self._find_module("services")
+ dep_modules = self._find_module_java_deps(service_module) + \
+ [self._find_module("services.core.unboosted")]
- def parse_lint_reports(self):
+ # Skip dependencies that are not services. Skip the "services.core"
+ # module which is analyzed via "services.core.unboosted".
+ modules = []
+ for module in dep_modules:
+ if "frameworks/base/services" not in module.path:
+ continue
+ if module.name == "services.core":
+ continue
+ modules.append(module)
+
+ self._lint(modules)
+
counts = { "unannotated": 0, "enforced": 0, "notRequired": 0 }
- for module in self.linter._modules:
+ for module in modules:
with open(module.lint_report, "r") as f:
content = f.read()
keys = dict(re.findall(r'(\w+)=(\d+)', content))