blob: 3308df6fc5afe53ae040104f5c53b9df1a02ca01 [file] [log] [blame]
# 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