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