# Copyright (C) 2022 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import json import os import shutil import subprocess import sys import zipfile ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP") ANDROID_PRODUCT_OUT = os.environ.get("ANDROID_PRODUCT_OUT") PRODUCT_OUT = ANDROID_PRODUCT_OUT.removeprefix(f"{ANDROID_BUILD_TOP}/") SOONG_UI = "build/soong/soong_ui.bash" PATH_PREFIX = "out/soong/.intermediates" PATH_SUFFIX = "android_common/lint" FIX_ZIP = "suggested-fixes.zip" class SoongLintFix: """ This class creates a command line tool that will apply lint fixes to the platform via the necessary combination of soong and shell commands. 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 SoongLintFix().run() ``` """ def __init__(self): self._parser = _setup_parser() self._args = None self._kwargs = None self._path = None self._target = None def run(self, additional_setup=None, custom_fix=None): """ Run the script """ self._setup() self._find_module() self._lint() if not self._args.no_fix: self._fix() if self._args.print: self._print() def _setup(self): self._args = self._parser.parse_args() env = os.environ.copy() if self._args.check: env["ANDROID_LINT_CHECK"] = self._args.check if self._args.lint_module: env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._args.lint_module self._kwargs = { "env": env, "executable": "/bin/bash", "shell": True, } os.chdir(ANDROID_BUILD_TOP) def _find_module(self): print("Refreshing soong modules...") try: os.mkdir(ANDROID_PRODUCT_OUT) except OSError: pass subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs) print("done.") with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f: module_info = json.load(f) if self._args.module not in module_info: sys.exit(f"Module {self._args.module} not found!") module_path = module_info[self._args.module]["path"][0] print(f"Found module {module_path}/{self._args.module}.") self._path = f"{PATH_PREFIX}/{module_path}/{self._args.module}/{PATH_SUFFIX}" self._target = f"{self._path}/lint-report.txt" def _lint(self): print("Cleaning up any old lint results...") try: os.remove(f"{self._target}") os.remove(f"{self._path}/{FIX_ZIP}") except FileNotFoundError: pass print("done.") print(f"Generating {self._target}") subprocess.call(f"{SOONG_UI} --make-mode {self._target}", **self._kwargs) print("done.") def _fix(self): print("Copying suggested fixes to the tree...") with zipfile.ZipFile(f"{self._path}/{FIX_ZIP}") as zip: for name in zip.namelist(): if name.startswith("out") or not name.endswith(".java"): continue with zip.open(name) as src, open(f"{ANDROID_BUILD_TOP}/{name}", "wb") as dst: shutil.copyfileobj(src, dst) print("done.") def _print(self): print("### lint-report.txt ###", end="\n\n") with open(self._target, "r") as f: print(f.read()) def _setup_parser(): parser = argparse.ArgumentParser(description=""" This is a python script that applies lint fixes to the platform: 1. Set up the environment, etc. 2. Run lint on the specified target. 3. Copy the modified files, from soong's intermediate directory, back into the tree. **Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first. """, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('module', help='The soong build module to run ' '(e.g. framework-minus-apex or services.core.unboosted)') parser.add_argument('--check', help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.') parser.add_argument('--lint-module', help='Specific lint module to run. Passed to the ANDROID_LINT_CHECK_EXTRA_MODULES environment variable.') parser.add_argument('--no-fix', action='store_true', help='Just build and run the lint, do NOT apply the fixes.') parser.add_argument('--print', action='store_true', help='Print the contents of the generated lint-report.txt at the end.') return parser if __name__ == "__main__": SoongLintFix().run()