1# Copyright (C) 2022 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import argparse 16import json 17import os 18import shutil 19import subprocess 20import sys 21import zipfile 22 23ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP") 24ANDROID_PRODUCT_OUT = os.environ.get("ANDROID_PRODUCT_OUT") 25PRODUCT_OUT = ANDROID_PRODUCT_OUT.removeprefix(f"{ANDROID_BUILD_TOP}/") 26 27SOONG_UI = "build/soong/soong_ui.bash" 28PATH_PREFIX = "out/soong/.intermediates" 29PATH_SUFFIX = "android_common/lint" 30FIX_ZIP = "suggested-fixes.zip" 31 32class SoongLintFix: 33 """ 34 This class creates a command line tool that will 35 apply lint fixes to the platform via the necessary 36 combination of soong and shell commands. 37 38 It breaks up these operations into a few "private" methods 39 that are intentionally exposed so experimental code can tweak behavior. 40 41 The entry point, `run`, will apply lint fixes using the 42 intermediate `suggested-fixes` directory that soong creates during its 43 invocation of lint. 44 45 Basic usage: 46 ``` 47 from soong_lint_fix import SoongLintFix 48 49 SoongLintFix().run() 50 ``` 51 """ 52 def __init__(self): 53 self._parser = _setup_parser() 54 self._args = None 55 self._kwargs = None 56 self._path = None 57 self._target = None 58 59 60 def run(self, additional_setup=None, custom_fix=None): 61 """ 62 Run the script 63 """ 64 self._setup() 65 self._find_module() 66 self._lint() 67 68 if not self._args.no_fix: 69 self._fix() 70 71 if self._args.print: 72 self._print() 73 74 def _setup(self): 75 self._args = self._parser.parse_args() 76 env = os.environ.copy() 77 if self._args.check: 78 env["ANDROID_LINT_CHECK"] = self._args.check 79 if self._args.lint_module: 80 env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._args.lint_module 81 82 self._kwargs = { 83 "env": env, 84 "executable": "/bin/bash", 85 "shell": True, 86 } 87 88 os.chdir(ANDROID_BUILD_TOP) 89 90 91 def _find_module(self): 92 print("Refreshing soong modules...") 93 try: 94 os.mkdir(ANDROID_PRODUCT_OUT) 95 except OSError: 96 pass 97 subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs) 98 print("done.") 99 100 with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f: 101 module_info = json.load(f) 102 103 if self._args.module not in module_info: 104 sys.exit(f"Module {self._args.module} not found!") 105 106 module_path = module_info[self._args.module]["path"][0] 107 print(f"Found module {module_path}/{self._args.module}.") 108 109 self._path = f"{PATH_PREFIX}/{module_path}/{self._args.module}/{PATH_SUFFIX}" 110 self._target = f"{self._path}/lint-report.txt" 111 112 113 def _lint(self): 114 print("Cleaning up any old lint results...") 115 try: 116 os.remove(f"{self._target}") 117 os.remove(f"{self._path}/{FIX_ZIP}") 118 except FileNotFoundError: 119 pass 120 print("done.") 121 122 print(f"Generating {self._target}") 123 subprocess.call(f"{SOONG_UI} --make-mode {self._target}", **self._kwargs) 124 print("done.") 125 126 127 def _fix(self): 128 print("Copying suggested fixes to the tree...") 129 with zipfile.ZipFile(f"{self._path}/{FIX_ZIP}") as zip: 130 for name in zip.namelist(): 131 if name.startswith("out") or not name.endswith(".java"): 132 continue 133 with zip.open(name) as src, open(f"{ANDROID_BUILD_TOP}/{name}", "wb") as dst: 134 shutil.copyfileobj(src, dst) 135 print("done.") 136 137 138 def _print(self): 139 print("### lint-report.txt ###", end="\n\n") 140 with open(self._target, "r") as f: 141 print(f.read()) 142 143 144def _setup_parser(): 145 parser = argparse.ArgumentParser(description=""" 146 This is a python script that applies lint fixes to the platform: 147 1. Set up the environment, etc. 148 2. Run lint on the specified target. 149 3. Copy the modified files, from soong's intermediate directory, back into the tree. 150 151 **Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first. 152 """, formatter_class=argparse.RawTextHelpFormatter) 153 154 parser.add_argument('module', 155 help='The soong build module to run ' 156 '(e.g. framework-minus-apex or services.core.unboosted)') 157 158 parser.add_argument('--check', 159 help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.') 160 161 parser.add_argument('--lint-module', 162 help='Specific lint module to run. Passed to the ANDROID_LINT_CHECK_EXTRA_MODULES environment variable.') 163 164 parser.add_argument('--no-fix', action='store_true', 165 help='Just build and run the lint, do NOT apply the fixes.') 166 167 parser.add_argument('--print', action='store_true', 168 help='Print the contents of the generated lint-report.txt at the end.') 169 170 return parser 171 172if __name__ == "__main__": 173 SoongLintFix().run()