diff --git a/tools/lint/Android.bp b/tools/lint/Android.bp index 2601041455050..96618f413db1a 100644 --- a/tools/lint/Android.bp +++ b/tools/lint/Android.bp @@ -51,3 +51,9 @@ java_test_host { unit_test: true, }, } + +python_binary_host { + name: "lint_fix", + main: "fix/lint_fix.py", + srcs: ["fix/lint_fix.py"], +} diff --git a/tools/lint/README.md b/tools/lint/README.md index c674d36431b7e..99149c140c1cd 100644 --- a/tools/lint/README.md +++ b/tools/lint/README.md @@ -44,6 +44,10 @@ m out/soong/.intermediates/frameworks/base/services/autofill/services.autofill/a environment variable with the id of the lint. For example: `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html` +## How to apply automatic fixes suggested by lint + +See [lint_fix](fix/README.md) + ## Create or update a baseline Baseline files can be used to silence known errors (and warnings) that are deemed to be safe. When diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md new file mode 100644 index 0000000000000..367d0bcb1aa7e --- /dev/null +++ b/tools/lint/fix/README.md @@ -0,0 +1,46 @@ +# Refactoring the platform with lint +Inspiration: go/refactor-the-platform-with-lint\ +**Special Thanks: brufino@, azharaa@, for the prior work that made this all possible** + +## What is this? + +It's a python script that runs the framework linter, +and then copies modified files back into the source tree.\ +Why python, you ask? Because python is cool ¯\_(ツ)_/¯. + +## Why? + +Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag. +As a compromise, soong zips up the (potentially) modified sources and leaves them in an intermediate +directory. This script runs the lint, unpacks those files, and copies them back into the tree. + +## How do I run it? +**WARNING: You probably want to commit/stash any changes to your working tree before doing this...** + +From this directory, run `python lint_fix.py -h`. +The script's help output explains things that are omitted here. + +Alternatively, there is a python binary target you can build to make this +available anywhere in your tree: +``` +m lint_fix +lint_fix -h +``` + +**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first. + +Example: `lint_fix frameworks/base/services/core/services.core.unboosted UseEnforcePermissionAnnotation --dry-run` +```shell +( +export ANDROID_LINT_CHECK=UseEnforcePermissionAnnotation; +cd $ANDROID_BUILD_TOP; +source build/envsetup.sh; +rm out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html; +m out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html; +cd out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint; +unzip suggested-fixes.zip -d suggested-fixes; +cd suggested-fixes; +find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1' --; +rm -rf suggested-fixes +) +``` diff --git a/tools/lint/fix/lint_fix.py b/tools/lint/fix/lint_fix.py new file mode 100644 index 0000000000000..3ff8131489ff1 --- /dev/null +++ b/tools/lint/fix/lint_fix.py @@ -0,0 +1,76 @@ +import argparse +import os +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" + +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"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)")