Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 39892e3e authored by mattgilbride's avatar mattgilbride
Browse files

lint_fix: split into separate main and common modules

- Convert most logic into a common class "SoongLintFix"
- Clean up code for clarity
- Execute using /bin/bash (python uses /bin/sh by default)

This change also makes it easier to consume common code from experimental projects.

Bug: 232058525
Test: TH
Change-Id: I421a78d5ede481baeaa5e082ce4e4ba537259bc5
parent d27d379e
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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"],
}
+29 −77
Original line number Diff line number Diff line
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"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)")
#  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.

from soong_lint_fix import SoongLintFix

SoongLintFix().run()
+169 −0
Original line number Diff line number Diff line
#  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