blob: bc3ec5e5cbccbc3411d37239229a913717e163c2 [file] [log] [blame] [edit]
#!/bin/bash -e
# 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.
# Bazel equivalent of checkpatch[_presubmit].sh that is used for
# checkpatch() macro.
# Wrapper around checkpatch.pl to gather necessary information from the
# dist dir. Notably, this includes the git_sha1 and whether to suppress
# the check for post-submit.
# Parse flags.
CHECKPATCH_ARGS=(--show-types)
GIT_SHA1=""
CHECKPATCH_PL_PATH=""
IGNORELIST_FILE=""
RESULTS_PATH=""
DIR=""
GIT="git"
while [[ $# -gt 0 ]]; do
next="$1"
case ${next} in
--log)
RESULTS_PATH="$2"
shift
;;
--git_sha1)
GIT_SHA1="$2"
shift
;;
--ignored_checks)
IGNORELIST_FILE="$2"
shift
;;
--checkpatch_pl)
CHECKPATCH_PL_PATH="$2"
shift
;;
--dir)
DIR="$2"
shift
;;
--git)
GIT="$2"
shift
;;
--help)
echo "Checks whether given build is for presubmit. If so, extract git_sha1"
echo "from repo.prop and invoke checkpatch.sh."
echo ""
echo "Usage: $0 "
echo " --dir <dir>"
echo " directory to run checkpatch"
echo " If relative, it is interpreted against Bazel workspace root."
echo " [--log <checkpatch.log>]"
echo " Location to *append* logs. If unspecified, use temp file."
echo " If relative, it is interpreted against Bazel workspace root."
echo " [--git_sha1 <GIT_SHA1>]"
echo " Git SHA1 to check patch on. Default is HEAD if applied.prop is"
echo " not provided, otherwise default is value from applied.prop."
echo " [--ignored_checks <checkpatch_ignorelist>]"
echo " List of ignored checks. See checkpatch() rule for defaults."
echo " If relative, it is interpreted against Bazel workspace root."
echo " <args for checkpatch.pl>"
echo " Other arguments are forwarded to checkpatch.pl."
echo
echo "Flags set by Kleaf and not allowed in command line:"
echo " --checkpatch_pl </path/to/checkpatch.pl>"
echo " Absolute path to checkpatch.pl."
echo " [--git </path/to/git>]"
echo " Absolute path to Git binary, if it should not be found in PATH"
exit 0
;;
*)
CHECKPATCH_ARGS+=("$1")
;;
esac
shift
done
# resolve_path <path>
# resolves relative path against BUILD_WORKSPACE_DIRECTORY
function resolve_path() {
local path="$1"
if [[ "${path}" == /* ]]; then
echo "${path}"
else
echo "${BUILD_WORKSPACE_DIRECTORY}/${path}"
fi
}
if [[ -z ${DIR} ]]; then
echo "ERROR: --dir is required" >&2
exit 1
fi
ABS_DIR=$(resolve_path "${DIR}")
if [[ -z ${CHECKPATCH_PL_PATH} ]]; then
echo "ERROR: --checkpatch_pl is required" >&2
exit 1
fi
CHECKPATCH_TMP=$(mktemp -d /tmp/.tmp.checkpatch.XXXXXX)
trap "rm -rf ${CHECKPATCH_TMP}" EXIT
if [[ -n ${RESULTS_PATH} ]]; then
RESULTS_PATH=$(resolve_path "${RESULTS_PATH}")
MY_RESULTS_PATH=$(mktemp /tmp/.tmp.checkpatch.log.XXXXXX)
else
RESULTS_PATH=$(mktemp /tmp/.tmp.checkpatch.log.XXXXXX)
MY_RESULTS_PATH="${RESULTS_PATH}"
fi
if [[ -z ${GIT_SHA1} ]]; then
GIT_SHA1="HEAD"
fi
if [ $("${GIT}" -C "${ABS_DIR}" show --no-patch --format="%p" ${GIT_SHA1} | wc -w) -gt 1 ] ; then
echo "INFO: Merge commit detected for "${DIR}". Skipping this check."
exit 0
fi
SUBJECT=$("${GIT}" -C "${ABS_DIR}" show --no-patch --format="%s" ${GIT_SHA1})
if [[ "$SUBJECT" =~ ^UPSTREAM|^BACKPORT|^FROMGIT ]]; then
echo "Not linting upstream patches for "${DIR}". Skipping this check."
exit 0
fi
if [[ "$SUBJECT" =~ ^Revert|^Reapply ]]; then
echo "Not linting revert/reapply patches for "${DIR}". Skipping this check."
exit 0
fi
# Now run checkpatch.pl on DIR: GIT_SHA1
# Below is the equivalent of build/kernel/static_analysis/checkpatch.sh
PATCH_DIR=${CHECKPATCH_TMP}/checkpatch/patches
# Update ignorelist.
if [[ -n "${IGNORELIST_FILE}" ]]; then
IGNORELIST_FILE=$(resolve_path "${IGNORELIST_FILE}")
if [[ -f "${IGNORELIST_FILE}" ]]; then
IGNORED_ERRORS=$(grep -v '^#' ${IGNORELIST_FILE} | paste -s -d,)
if [[ -n "${IGNORED_ERRORS}" ]]; then
CHECKPATCH_ARGS+=(--ignore)
CHECKPATCH_ARGS+=("${IGNORED_ERRORS}")
fi
else
echo "ERROR: --ignored_checks is not a file: ${IGNORELIST_FILE}" >&2
exit 1
fi
fi
echo "========================================================"
echo " Running static analysis on ${DIR} (${GIT_SHA1}) ..."
echo "========================================================"
pushd ${ABS_DIR} > /dev/null
"${GIT}" format-patch --quiet -o "${PATCH_DIR}" "${GIT_SHA1}^1..${GIT_SHA1}" -- \
':!android/abi*' ':!BUILD.bazel'
PATCH_FILE="${PATCH_DIR}/*.patch"
if ! $(stat -t ${PATCH_FILE} >/dev/null 2>&1); then
echo "Patch empty (probably due to suppressions). Skipping analysis."
popd > /dev/null
exit 0
fi
echo "========================================================" >> "${MY_RESULTS_PATH}"
echo "${DIR}: ${GIT_SHA1}" >> "${MY_RESULTS_PATH}"
echo "========================================================" >> "${MY_RESULTS_PATH}"
# TODO: b/199237323 - Run with --no-tree option for now to avoid spdxcheck.py
# failures. A better fix would be to remove spdxcheck.py.
CHECKPATCH_ARGS+=(--no-tree)
# Run with --noshowfile option to ensure consistent output in cases where
# --showfile is present in a .checkpatch.conf file.
CHECKPATCH_ARGS+=(--noshowfile)
# Delay exit on non-zero checkpatch.pl return code so we can finish logging.
# Note, it's tricky to ignore this exit code completely and instead return only
# based on the log values. For example, if the log is not empty, but contains
# no ERRORS, how do we reliabliy distinguish WARNINGS that were not ignored (or
# other conditions we want to ignore), from legitimate errors running the
# script itself (e.g. bad flags)? checkpatch.pl will return 1 in both cases.
# For now, include all known warnings in the ignorelist, and forward this code
# unconditionally.
set +e
(
# checkpatch.pl only looks up git from PATH.
export PATH=$PATH:$(dirname "${GIT}")
"${CHECKPATCH_PL_PATH}" ${CHECKPATCH_ARGS[*]} $PATCH_FILE >> "${MY_RESULTS_PATH}"
)
CHECKPATCH_RC=$?
set -e
# Summarize errors in the build log (full copy included in dist dir).
if [[ $CHECKPATCH_RC -ne 0 ]]; then
echo "Errors were reported from checkpatch.pl." >&2
echo "" >&2
echo "Summary:" >&2
echo "" >&2
{ grep -r -h -E -A1 "^(ERROR|WARNING):" "${MY_RESULTS_PATH}" 1>&2; } || true
echo "" >&2
echo "See ${MY_RESULTS_PATH} for complete output." >&2
CLEANUP_CHECKPATCH_RESULTS=0
fi
# Suppress checkpatch errors if Ignore-Checkpatch: is in commit message.
if "${GIT}" -C "${ABS_DIR}" log --format="%B" -1 ${GIT_SHA1} | \
grep -q "^Ignore-Checkpatch:\s*\S\+"; then
echo "Suppressing checkpatch errors due to \"Ignore-Checkpatch:\"".
CHECKPATCH_RC=0
fi
# Append my results to --log
if [[ "${MY_RESULTS_PATH}" != "${RESULTS_PATH}" ]]; then
cat "${MY_RESULTS_PATH}" >> "${RESULTS_PATH}"
fi
if [[ "${CLEANUP_CHECKPATCH_RESULTS}" == 1 ]]; then
rm -f "${MY_RESULTS_PATH}"
fi
echo "========================================================"
echo "Finished running static analysis on ${DIR}."
echo "========================================================"
popd > /dev/null
exit ${CHECKPATCH_RC}