Skip to content
Snippets Groups Projects
Commit e09b6f3a authored by Thiébaud Weksteen's avatar Thiébaud Weksteen
Browse files

enforce_permission_counter: Find targets automatically

The target list of enforce_permission_counter can be reconstructed
based on the dependencies of the "services" module. Add the ability to
soong_lint_fix to collect and parse module_bp_java_deps.json which
contains the dependency list.

SoongLintFix is split into 2 classes: SoongWrapper and SoongLintFix. The
former is used as a general wrapper to invoke Soong and parse the module
files. The latter, a subclass of SoongWrapper, contains the calls to the
different steps.

The building and loading of module-info.json and
module_bp_java_deps.json is now done dynamically whenever required.

Bug: 298285238
Test: enforce_permission_counter
Test: lint_fix --no-fix --check AnnotatedAidlCounter --lint-module AndroidUtilsLintChecker services.autofill
Change-Id: I781e9cdf80feb4c4d480673e044d526c528f8412
parent 9cb671cd
No related branches found
No related tags found
No related merge requests found
......@@ -14,6 +14,7 @@
import argparse
import json
import functools
import os
import shutil
import subprocess
......@@ -28,6 +29,7 @@ SOONG_UI = "build/soong/soong_ui.bash"
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,10 +51,25 @@ class SoongModule:
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 @@ class SoongModule:
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 @@ class SoongLintFix:
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 @@ class SoongLintFix:
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 @@ class SoongLintFix:
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 @@ class SoongLintFix:
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"""
......
......@@ -16,57 +16,38 @@ import re
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 run(self):
opts = soong_lint_fix.SoongLintFixOptions()
opts.check = "AnnotatedAidlCounter"
opts.lint_module = "AndroidUtilsLintChecker"
opts.no_fix = True
opts.modules = TARGETS
self.linter = soong_lint_fix.SoongLintFix(opts)
self.linter.run()
self.parse_lint_reports()
def __init__(self):
super().__init__(check=CHECK, lint_module=LINT_MODULE)
def run(self):
self._setup()
# 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")]
# 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)
def parse_lint_reports(self):
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))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment