| #!/bin/bash |
| # Copyright 2022 The Android Open Source Project |
| # |
| # Formats source files according to Google's style guide. |
| # By default, formats all files. |
| # Use --diff to format files that are different from HEAD (including untracked). |
| # Use --hook to format files passed as arguments (e.g. for pre-commit). |
| |
| # --- Bash Version Check (Strict 4.4+) --- |
| if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4))); then |
| echo "Error: Bash 4.4+ is required." |
| # Try to find a suitable bash |
| for p in /opt/homebrew/bin/bash /usr/local/bin/bash; do |
| if [[ -x "$p" ]] && "$p" -c \ |
| '((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4)))'; then |
| exec "$p" "$0" "$@" |
| fi |
| done |
| exit 1 |
| fi |
| |
| set -euo pipefail |
| |
| # --- Setup --- |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| cd "$SCRIPT_DIR/.." |
| REPO="$SCRIPT_DIR/../../.." |
| OS=$(uname | tr '[:upper:]' '[:lower:]') |
| DESIRED_TAPLO_VERSION="0.10.0" |
| |
| # --- Argument Parsing --- |
| MODE="ALL" |
| if [[ $# -gt 0 ]]; then |
| case "$1" in |
| --diff) MODE="DIFF"; shift ;; |
| --hook) MODE="HOOK"; shift ;; |
| *) echo "Unknown argument: $1"; exit 1 ;; |
| esac |
| fi |
| |
| # --- Configuration --- |
| RUSTFMT="$REPO/prebuilts/rust/$OS-x86/stable/rustfmt" |
| BPFMT="$REPO/prebuilts/build-tools/$OS-x86/bin/bpfmt" |
| TAPLO_CONFIG="$REPO/tools/netsim/next/taplo.toml" |
| |
| KTFMT="ktfmt" |
| if ! command -v ktfmt &> /dev/null; then |
| if [ -x "/google/bin/releases/kotlin-google-eng/ktfmt/ktfmt" ]; then |
| KTFMT="/google/bin/releases/kotlin-google-eng/ktfmt/ktfmt" |
| fi |
| fi |
| |
| # Common find exclusions |
| EXCLUDES=( |
| -not -path '*/target/*' |
| -not -path './.git/*' |
| -not -path './bazel-out/*' |
| -not -path './objs/*' |
| ) |
| |
| # Languages and their "Struct" configurations |
| |
| # Clang |
| Clang_CMD="clang-format -i" |
| Clang_DIRS="src rust next proto ui/ts" |
| Clang_EXTS="-name *.cc -o -name *.h -o -name *.proto -o -name *.ts" |
| Clang_REGEX="\.(cc|h|proto|ts)$" |
| |
| # Rust |
| Rust_CMD="$RUSTFMT --files-with-diff" |
| Rust_DIRS="rust next" |
| Rust_EXTS="-name *.rs" |
| Rust_REGEX="\.rs$" |
| |
| # Java |
| Java_CMD="google-java-format -i" |
| Java_DIRS="." |
| Java_EXTS="-name *.java" |
| Java_REGEX="\.java$" |
| |
| # Python |
| Python_CMD="pyformat --in_place --alsologtostderr --noshowprefixforinfo" |
| Python_DIRS="." |
| Python_EXTS="-name *.py" |
| Python_REGEX="\.py$" |
| |
| # CMake |
| CMake_CMD="cmake-format -i" |
| CMake_DIRS="." |
| CMake_EXTS="-name CMakeLists.txt -o -name *.cmake" |
| CMake_REGEX="CMakeLists\.txt$|\.cmake$" |
| |
| # Blueprint |
| Blueprint_CMD="$BPFMT -w" |
| Blueprint_DIRS="." |
| Blueprint_EXTS="-name Android.bp" |
| Blueprint_FLAGS="-maxdepth 1" |
| Blueprint_REGEX="Android\.bp$" |
| |
| # Bazel |
| Bazel_CMD="buildifier -lint=fix" |
| Bazel_DIRS="." |
| Bazel_EXTS="-name BUILD -o -name MODULE.bazel \ |
| -o -name BUILD.bazel -o -name *.bzl" |
| Bazel_REGEX="BUILD$|MODULE\.bazel$|BUILD\.bazel$|\.bzl$" |
| |
| # Toml |
| Toml_CMD="env RUST_LOG=warn taplo fmt --config $TAPLO_CONFIG" |
| Toml_DIRS="rust next proto" |
| Toml_EXTS="-name Cargo.toml" |
| Toml_FLAGS="-not -path */bazel-bin/* -not -path */bazel-netsim/*" |
| Toml_REGEX="Cargo\.toml$" |
| |
| # Kotlin |
| Kotlin_CMD="$KTFMT --google-style" |
| Kotlin_DIRS="." |
| Kotlin_EXTS="-name *.kt" |
| Kotlin_REGEX="\.kt$" |
| |
| # Ordered list of languages to process |
| LANGS=(Clang Rust Java Python CMake Blueprint Bazel Toml Kotlin) |
| |
| # --- Helpers --- |
| |
| check_taplo_version() { |
| if ! command -v taplo &> /dev/null; then |
| echo "Error: 'taplo' not found. Please install taplo-cli." |
| exit 1 |
| fi |
| local INSTALLED_VERSION |
| INSTALLED_VERSION=$(taplo --version | awk '{print $2}') |
| if [ "$INSTALLED_VERSION" != "$DESIRED_TAPLO_VERSION" ]; then |
| echo "Error: Found taplo version ${INSTALLED_VERSION}, but \ |
| ${DESIRED_TAPLO_VERSION} is required." |
| echo "Please install correct version via cargo." |
| exit 1 |
| fi |
| } |
| |
| format_files() { |
| local lang="$1" |
| # Use nameref for struct access |
| declare -n cmd_ref="${lang}_CMD" |
| local -n files_ref="$2" |
| |
| if [[ ${#files_ref[@]} -eq 0 ]]; then |
| return |
| fi |
| |
| local executable |
| executable=$(echo "${cmd_ref}" | awk '{print $1}') |
| |
| if ! command -v "$executable" &> /dev/null; then |
| echo "Error: '$executable' not found, skipping $lang file formatting." |
| return 0 |
| fi |
| |
| echo "Formatting ${#files_ref[@]} $lang files..." |
| ${cmd_ref} "${files_ref[@]}" & |
| pids+=($!) |
| } |
| |
| check_taplo_version |
| |
| # --- Main Logic --- |
| |
| # Declare file arrays dynamically |
| for lang in "${LANGS[@]}"; do |
| eval "FILES_$lang=()" |
| done |
| |
| pids=() |
| |
| if [[ "$MODE" == "ALL" ]]; then |
| echo "Gathering all files to format..." |
| for lang in "${LANGS[@]}"; do |
| declare -n dirs_ref="${lang}_DIRS" |
| declare -n exts_ref="${lang}_EXTS" |
| declare -n flags_ref="${lang}_FLAGS" |
| |
| # Split regex/globs for find (SC2086 is desired here) |
| # shellcheck disable=SC2086 |
| find_args=( \( ${exts_ref} \) ) |
| |
| flags=() |
| if [[ -n "${flags_ref:-}" ]]; then |
| # shellcheck disable=SC2086 |
| flags=( ${flags_ref} ) |
| fi |
| |
| declare -n target_ref="FILES_$lang" |
| # shellcheck disable=SC2086 |
| mapfile -d '' target_ref < <(find ${dirs_ref} "${flags[@]}" \ |
| -type f "${find_args[@]}" "${EXCLUDES[@]}" -print0) |
| done |
| |
| else |
| echo "Gathering files to format..." |
| all_candidates=() |
| |
| if [[ "$MODE" == "DIFF" ]]; then |
| mapfile -d '' all_candidates < <(git diff -z --name-only \ |
| --diff-filter=ACMRTUXB HEAD && \ |
| git ls-files -z --others --exclude-standard) |
| else # HOOK |
| all_candidates=("$@") |
| fi |
| |
| if [ ${#all_candidates[@]} -eq 0 ]; then |
| echo "No files to format." |
| exit 0 |
| fi |
| |
| for f in "${all_candidates[@]}"; do |
| for lang in "${LANGS[@]}"; do |
| declare -n regex_ref="${lang}_REGEX" |
| if [[ "$f" =~ ${regex_ref} ]]; then |
| declare -n target_ref="FILES_$lang" |
| target_ref+=("$f") |
| break |
| fi |
| done |
| done |
| fi |
| |
| # --- Execution --- |
| |
| for lang in "${LANGS[@]}"; do |
| format_files "$lang" "FILES_$lang" |
| done |
| |
| # Wait for background processes and check for failures |
| FAIL=0 |
| if [[ ${#pids[@]} -gt 0 ]]; then |
| echo "Waiting for formatters to finish..." |
| for pid in "${pids[@]}"; do |
| if ! wait "$pid"; then |
| FAIL=1 |
| fi |
| done |
| fi |
| |
| if [[ $FAIL -ne 0 ]]; then |
| echo "Formatting failed for some files." |
| exit 1 |
| fi |
| |
| echo "Formatting complete." |