diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp index 5f6c6f779f85d..7375c160a59fe 100644 --- a/tools/lint/fix/Android.bp +++ b/tools/lint/fix/Android.bp @@ -25,4 +25,10 @@ python_binary_host { name: "lint_fix", main: "lint_fix.py", srcs: ["lint_fix.py"], + libs: ["soong_lint_fix"], +} + +python_library_host { + name: "soong_lint_fix", + srcs: ["soong_lint_fix.py"], } diff --git a/tools/lint/fix/lint_fix.py b/tools/lint/fix/lint_fix.py index 0f94bd2b9b2c6..1c83f7b38400e 100644 --- a/tools/lint/fix/lint_fix.py +++ b/tools/lint/fix/lint_fix.py @@ -1,77 +1,29 @@ -import argparse -import os -import sys +# Copyright (C) 2023 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. +# +# 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. -ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP") -PATH_PREFIX = "out/soong/.intermediates" -PATH_SUFFIX = "android_common/lint" -FIX_DIR = "suggested-fixes" +from soong_lint_fix import SoongLintFix -parser = argparse.ArgumentParser(description=""" -This is a python script that applies lint fixes to the platform: -1. Set up the environment, etc. -2. Build the lint and run it. -3. Unpack soong's intermediate zip containing source files modified by lint. -4. Copy the modified files back into the tree. - -**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` \ -so that the `ANDROID_BUILD_TOP` environment variable has been set. -Alternatively, set it manually in your shell. -""", formatter_class=argparse.RawTextHelpFormatter) - -parser.add_argument('build_path', metavar='build_path', type=str, - help='The build module to run ' - '(e.g. frameworks/base/framework-minus-apex or ' - 'frameworks/base/services/core/services.core.unboosted)') - -parser.add_argument('--check', metavar='check', type=str, - help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.') - -parser.add_argument('--dry-run', dest='dry_run', action='store_true', - help='Just print the resulting shell script instead of running it.') - -parser.add_argument('--no-fix', dest='no_fix', action='store_true', - help='Just build and run the lint, do NOT apply the fixes.') - -args = parser.parse_args() - -path = f"{PATH_PREFIX}/{args.build_path}/{PATH_SUFFIX}" -target = f"{path}/lint-report.html" - -commands = [] - -if not args.dry_run: - commands += [f"export ANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"] - -if args.check: - commands += [f"export ANDROID_LINT_CHECK={args.check}"] - -commands += [ - "cd $ANDROID_BUILD_TOP", - "source build/envsetup.sh", - f"rm {target}", # remove the file first so soong doesn't think there is no work to do - f"rm {path}/{FIX_DIR}.zip", # remove in case there are fixes from a prior run that we don't want applied if this run fails - f"m {target}", -] - -if not args.no_fix: - commands += [ - f"cd {path}", - f"unzip {FIX_DIR}.zip -d {FIX_DIR}", - f"cd {FIX_DIR}", - # Find all the java files in the fix directory, excluding the ./out subdirectory, - # and copy them back into the same path within the tree. - f"find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1' --", - f"rm -rf {FIX_DIR}" - ] - -if args.dry_run: - print("(\n" + ";\n".join(commands) + "\n)") - sys.exit(0) - -with_echo = [] -for c in commands: - with_echo.append(f'echo "{c}"') - with_echo.append(c) - -os.system("(\n" + ";\n".join(with_echo) + "\n)") +SoongLintFix().run() diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py new file mode 100644 index 0000000000000..3308df6fc5afe --- /dev/null +++ b/tools/lint/fix/soong_lint_fix.py @@ -0,0 +1,169 @@ +# 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 os +import subprocess +import sys + +ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP") +PATH_PREFIX = "out/soong/.intermediates" +PATH_SUFFIX = "android_common/lint" +FIX_DIR = "suggested-fixes" + +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 provides some basic hooks for experimental code + to tweak the generation of the resulting shell script. + + By default, it will apply lint fixes using the intermediate `suggested-fixes` + directory that soong creates during its invocation of lint. + + The default argument parser configures a number of command line arguments to + facilitate running lint via soong. + + Basic usage: + ``` + from soong_lint_fix import SoongLintFix + + SoongLintFix().run() + ``` + """ + def __init__(self): + self._commands = None + self._args = None + self._path = None + self._target = None + self._parser = _setup_parser() + + + def add_argument(self, *args, **kwargs): + """ + If necessary, add arguments to the underlying argparse.ArgumentParser before running + """ + self._parser.add_argument(*args, **kwargs) + + + def run(self, add_setup_commands=None, override_fix_commands=None): + """ + Run the script + :param add_setup_commands: OPTIONAL function to add additional setup commands + passed the command line arguments, path, and build target + must return a list of strings (the additional commands) + :param override_fix_commands: OPTIONAL function to override the fix commands + passed the command line arguments, path, and build target + must return a list of strings (the fix commands) + """ + self._setup() + if add_setup_commands: + self._commands += add_setup_commands(self._args, self._path, self._target) + + self._add_lint_report_commands() + + if not self._args.no_fix: + if override_fix_commands: + self._commands += override_fix_commands(self._args, self._path, self._target) + else: + self._commands += [ + f"cd {self._path}", + f"unzip {FIX_DIR}.zip -d {FIX_DIR}", + f"cd {FIX_DIR}", + # Find all the java files in the fix directory, excluding the ./out subdirectory, + # and copy them back into the same path within the tree. + f"find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1 || exit 255' --", + f"rm -rf {FIX_DIR}" + ] + + + if self._args.dry_run: + print(self._get_commands_str()) + else: + self._execute() + + + def _setup(self): + self._args = self._parser.parse_args() + self._commands = [] + self._path = f"{PATH_PREFIX}/{self._args.build_path}/{PATH_SUFFIX}" + self._target = f"{self._path}/lint-report.html" + + if not self._args.dry_run: + self._commands += [f"export ANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"] + + if self._args.check: + self._commands += [f"export ANDROID_LINT_CHECK={self._args.check}"] + + + def _add_lint_report_commands(self): + self._commands += [ + "cd $ANDROID_BUILD_TOP", + "source build/envsetup.sh", + # remove the file first so soong doesn't think there is no work to do + f"rm {self._target}", + # remove in case there are fixes from a prior run, + # that we don't want applied if this run fails + f"rm {self._path}/{FIX_DIR}.zip", + f"m {self._target}", + ] + + + def _get_commands_str(self): + prefix = "(\n" + delimiter = ";\n" + suffix = "\n)" + return f"{prefix}{delimiter.join(self._commands)}{suffix}" + + + def _execute(self, with_echo=True): + if with_echo: + exec_commands = [] + for c in self._commands: + exec_commands.append(f'echo "{c}"') + exec_commands.append(c) + self._commands = exec_commands + + subprocess.call(self._get_commands_str(), executable='/bin/bash', shell=True) + + +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` + so that the `ANDROID_BUILD_TOP` environment variable has been set. + Alternatively, set it manually in your shell. + """, formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument('build_path', metavar='build_path', type=str, + help='The build module to run ' + '(e.g. frameworks/base/framework-minus-apex or ' + 'frameworks/base/services/core/services.core.unboosted)') + + parser.add_argument('--check', metavar='check', type=str, + help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.') + + parser.add_argument('--dry-run', dest='dry_run', action='store_true', + help='Just print the resulting shell script instead of running it.') + + parser.add_argument('--no-fix', dest='no_fix', action='store_true', + help='Just build and run the lint, do NOT apply the fixes.') + + return parser