Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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
Loading
Loading
Loading
Loading
+109 −54
Original line number Diff line number Diff line
@@ -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:
    def _find_module(self, module_name):
        """Returns a SoongModule from a module name.

        Ensures that the module is known to Soong.
        """
        module = SoongModule(module_name)
            module.find(module_info)
            self._modules.append(module)
        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 _lint(self):
    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"""

+27 −46
Original line number Diff line number Diff line
@@ -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))