| #!/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 |