blob: a0b50d8a82ddc30eae8a54f5384024752a778b8b [file] [log] [blame]
#!/bin/bash
#
# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
set -e
REPO_ROOT="$(git rev-parse --show-toplevel)"
readonly REPO_ROOT
# Returns true if colors are supported.
function is-stderr-tty {
[[ -t 2 ]]
}
# echo-info prints a line to stderr with a green INFO: prefix.
function echo-info {
if is-stderr-tty; then
echo -e >&2 "\033[1;32mINFO:\033[0m $*"
else
echo -e >&2 "INFO: $*"
fi
}
# echo-warning prints a line to stderr with a yellow WARNING: prefix.
function echo-warning {
if is-stderr-tty; then
echo -e >&2 "\033[1;33mWARNING:\033[0m $*"
else
echo -e >&2 "WARNING: $*"
fi
}
# echo-error prints a line to stderr with a red ERROR: prefix.
function echo-error {
if is-stderr-tty; then
echo -e >&2 "\033[1;31mERROR:\033[0m $*"
else
echo -e >&2 "ERROR: $*"
fi
}
function get-diff-base() {
local upstream=$(git rev-parse --abbrev-ref --symbolic-full-name "@{u}" 2>/dev/null)
if [[ -z "${upstream}" ]]; then
upstream="origin/main"
fi
local local_commit=$(git rev-list HEAD ^${upstream} -- 2>/dev/null | tail -1)
if [[ -z "${local_commit}" ]]; then
printf "HEAD"
else
git rev-parse "${local_commit}"^
fi
}
# Removes leading //, resolves to absolute path, and resolves globs. The first
# argument is a path prefix, and the remaining arguments are relative to that
# path prefix.
function canonicalize() {
local root_dir="$1"
shift
for fileglob in "${@}"; do
# // means it comes from gn, [^/]* means it is relative
if [[ "${fileglob}" = //* || "${fileglob}" = [^/]* ]]; then
local dir="${root_dir}"/
else
local dir=""
fi
for file in "${dir}"${fileglob#"//"}; do
echo "${file}"
done
done
}
# Format the given file.
function format-file() {
# If the user requested formatting of a single specific file, we want to
# loudly alert them to the fact that we can't actually format that file, since
# silently exiting would suggest that we *do* know how to format the file and
# that it was already properly formatted.
must_format=false
if $FILES_SPECIFIED && [[ ${#FILES[@]} == 1 ]]; then
must_format=true
fi
filename="$(basename "$1")"
case "${filename}" in
*.c | *.cc | *.cpp | *.h | *.hh | *.hpp | *.proto | *.ts) format_cc "$1" ;;
*.bazel| *.bzl| BUILD| WORKSPACE) format_bazel "$1" ;;
*.cml) format_cml "$1";;
*.fidl) format_fidl "$1" ;;
*.*)
if $must_format; then
echo-error "Unsupported extension for file $1"
exit 1
fi
;;
*)
if $must_format; then
echo-error "Unable to determine language for file $1"
exit 1
fi
;;
esac
}
function run_tool {
tool_type="$1"
tool_name="$2"
shift 2
bazel_project="$(basename "$REPO_ROOT")"
bazel_dir="${REPO_ROOT}/bazel-${bazel_project}"
if [[ $tool_type == "clang" ]]; then
str="Clang"
build_args=( "--config=fuchsia_x64" "@fuchsia_clang//:toolchain" )
tool_path="${bazel_dir}/external/fuchsia_clang/bin"
else
str="Fuchsia SDK"
build_args=( "@fuchsia_sdk//:fuchsia_toolchain_sdk" )
tool_path="${bazel_dir}/external/fuchsia_sdk/tools/x64"
fi
if [[ ! -d "${bazel_dir}" ]]; then
if [[ ! -x "${REPO_ROOT}/tools/bazel" && -x "${REPO_ROOT}/scripts/bootstrap.sh" ]]; then
echo >&2 "INFO: Cannot find Bazel, attempting to fetch it..."
"${REPO_ROOT}/scripts/bootstrap.sh"
fi
if [[ ! -x "${REPO_ROOT}/tools/bazel" ]]; then
echo >&2 "ERROR: cannot fetch bazel. Ensure scripts/boostrap.sh is able to fetch tools/bazel."
return 1
fi
echo >&2 "INFO: Cannot find the ${str} toolchain, attempting to fetch it..."
"${REPO_ROOT}/tools/bazel" build "${build_args[@]}"
fi
"${tool_path}/${tool_name}" "$@"
}
function format_cc() {
run_tool clang clang-format -style=file -fallback-style=Google -sort-includes -i "$1"
}
function format_bazel() {
if ! command -v buildifier &> /dev/null; then
echo-warning "Cannot find buildifier in path. Please install it to format Bazel BUILD files."
echo-warning "Skipping these files for now: $bazel_source_files"
else
buildifier -lint=warn "$1"
fi
}
function format_cml() {
run_tool sdk cmc format --cml --in-place "$1"
}
function format_fidl() {
run_tool sdk fidl-format -i "$1"
}
# if this tool is named (or symlinked as) 'pre-commit', it is assumed to be a git commit hook and
# it will behave slightly differently:
#
# - it will run 'git add' at the end
if [[ "$( basename "${BASH_SOURCE[0]}" )" == "pre-commit" ]]; then
IN_GIT_HOOK=true
echo-info "Formatting files"
else
IN_GIT_HOOK=false
fi
# with no arguments, this command will format files in the current git diff
# If arguments are given, they are assumed to be file names, which will be
# formatted based on their type regardless of their git status.
FILES_SPECIFIED=false
GIT_FILTER=(
":(top,exclude)third_party"
)
if [[ $# -gt 0 ]]; then
FILES=( "$@" )
FILES_SPECIFIED=true
else
FILES=( \
$(canonicalize $(git rev-parse --show-toplevel) \
$(git diff --name-only $(get-diff-base) "${GIT_FILTER[@]}")) \
)
fi
EXISTING_FILES=()
# Format files.
for file in "${FILES[@]}"; do
# Git reports deleted files, which we don't want to try to format
if [[ ! -f "${file}" ]]; then
if $FILES_SPECIFIED; then
echo-warning "Cannot find file, ignoring: $file"
fi
continue
fi
# Format the file
format-file "${file}"
EXISTING_FILES+=( "$file" )
done
if $IN_GIT_HOOK && [[ ${#EXISTING_FILES[@]} -gt 0 ]]; then
git add "${EXISTING_FILES[@]}"
fi