Mooncake/scripts/code_format.sh

271 lines
8.1 KiB
Bash
Executable File

#!/bin/bash
# =============================================================================
# code_format.sh - Format C/C++ code changed in current PR
#
# This script formats all C/C++ files that have been modified in the current
# branch compared to the base branch (default: origin/main).
#
# Usage:
# ./scripts/code_format.sh [OPTIONS]
#
# Options:
# -a, --all Format all C/C++ files in the project
# -b, --base <branch> Base branch to compare against (default: origin/main)
# -c, --check Check mode: only report files that need formatting
# -h, --help Show this help message
#
# Examples:
# ./scripts/code_format.sh # Format all changed files
# ./scripts/code_format.sh --all # Format all C/C++ files
# ./scripts/code_format.sh -b origin/dev # Compare against origin/dev
# ./scripts/code_format.sh --check # Check without modifying files
# =============================================================================
set -e
# Default values
BASE_BRANCH="origin/main"
CHECK_MODE=false
ALL_MODE=false
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# File extensions to format
FILE_EXTENSIONS="\.(h|hpp|cpp|cu|cuh|c|cc|cxx)$"
# Directories to exclude (add more patterns as needed)
EXCLUDE_DIRS=(
"cachelib_memory_allocator"
"thirdparty"
)
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Print usage information
usage() {
sed -n '/^# ====/,/^# ====/p' "$0" | grep -v "^# ====" | sed 's/^# //'
exit 0
}
# Print colored message
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Find clang-format binary (require version 20)
find_clang_format() {
if command -v clang-format-20 &> /dev/null; then
echo "clang-format-20"
return 0
else
{
print_error "clang-format-20 not found. This project requires clang-format version 20."
echo ""
print_info "Installation instructions for Ubuntu:"
echo " wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc > /dev/null"
echo " echo \"deb http://apt.llvm.org/\$(lsb_release -cs)/ llvm-toolchain-\$(lsb_release -cs)-20 main\" | sudo tee /etc/apt/sources.list.d/llvm-20.list"
echo " sudo apt-get update && sudo apt-get install -y clang-format-20"
echo ""
print_info "Or use the LLVM installation script:"
echo " wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 20"
echo " sudo apt-get install -y clang-format-20"
} >&2
return 1
fi
}
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-a|--all)
ALL_MODE=true
shift
;;
-b|--base)
BASE_BRANCH="$2"
shift 2
;;
-c|--check)
CHECK_MODE=true
shift
;;
-h|--help)
usage
;;
*)
print_error "Unknown option: $1"
usage
;;
esac
done
}
# Get list of all C/C++ files in the project
get_all_files() {
cd "${PROJECT_ROOT}"
# Find all files, respecting .gitignore, then filter by FILE_EXTENSIONS
local all_files
all_files=$(git ls-files --cached --others --exclude-standard 2>/dev/null || \
find . -type f | sed 's|^\./||')
# Filter by file extensions
local result
result=$(echo "${all_files}" | grep -E "${FILE_EXTENSIONS}" || true)
# Exclude specified directories
for pattern in "${EXCLUDE_DIRS[@]}"; do
result=$(echo "${result}" | grep -v "${pattern}" || true)
done
echo "${result}"
}
# Get list of changed C/C++ files
get_changed_files() {
cd "${PROJECT_ROOT}"
# Get changed files compared to base branch
local changed_files
if git rev-parse --verify "${BASE_BRANCH}" &> /dev/null; then
changed_files=$(git diff --name-only "${BASE_BRANCH}"...HEAD 2>/dev/null || \
git diff --name-only "${BASE_BRANCH}" HEAD 2>/dev/null || true)
else
print_warn "Base branch '${BASE_BRANCH}' not found, trying upstream tracking branch"
# Try upstream tracking branch, then fall back to comparing all commits on current branch
changed_files=$(git diff --name-only "@{upstream}"...HEAD 2>/dev/null || \
git log --name-only --pretty=format: HEAD 2>/dev/null | sort -u || true)
fi
# Filter C/C++ files and exclude specified directories
local result
result=$(echo "${changed_files}" | grep -E "${FILE_EXTENSIONS}" || true)
for pattern in "${EXCLUDE_DIRS[@]}"; do
result=$(echo "${result}" | grep -v "${pattern}" || true)
done
echo "${result}"
}
# Format files
format_files() {
local files="$1"
local clang_format="$2"
local formatted_count=0
local failed_count=0
if [[ -z "${files}" ]]; then
if ${ALL_MODE}; then
print_info "No C/C++ files found in the project."
else
print_info "No C/C++ files changed in this PR."
fi
return 0
fi
print_info "Using $(${clang_format} --version)"
if ${ALL_MODE}; then
print_info "Formatting all C/C++ files in the project"
else
print_info "Comparing against: ${BASE_BRANCH}"
fi
echo ""
while IFS= read -r file; do
if [[ -f "${PROJECT_ROOT}/${file}" ]]; then
if ${CHECK_MODE}; then
# Check mode: verify if file needs formatting
if ! ${clang_format} -style=file --dry-run -Werror "${PROJECT_ROOT}/${file}" 2>&1; then
print_warn "Needs formatting: ${file}"
failed_count=$((failed_count + 1))
else
print_info "OK: ${file}"
fi
else
# Format mode: apply formatting
if ${clang_format} -style=file -i "${PROJECT_ROOT}/${file}"; then
# Check if file actually changed
if git diff --quiet "${PROJECT_ROOT}/${file}" 2>/dev/null; then
print_info "Already formatted: ${file}"
else
print_info "Formatted: ${file}"
formatted_count=$((formatted_count + 1))
fi
else
print_error "Failed to format: ${file}"
failed_count=$((failed_count + 1))
fi
fi
else
print_warn "File not found (deleted?): ${file}"
fi
done <<< "${files}"
echo ""
if ${CHECK_MODE}; then
if [[ ${failed_count} -gt 0 ]]; then
print_error "${failed_count} file(s) need formatting."
print_info "Run './scripts/code_format.sh' to format them."
return 1
else
print_info "All files are properly formatted."
fi
else
print_info "Formatted ${formatted_count} file(s)."
if [[ ${failed_count} -gt 0 ]]; then
print_error "Failed to format ${failed_count} file(s)."
return 1
fi
fi
return 0
}
# Main function
main() {
parse_args "$@"
print_info "Mooncake Code Format Script"
echo ""
# Check for .clang-format file
if [[ ! -f "${PROJECT_ROOT}/.clang-format" ]]; then
print_error ".clang-format not found in project root"
exit 1
fi
# Find clang-format
local clang_format
if ! clang_format="$(find_clang_format)"; then
exit 1
fi
# Get files to format
local files
if ${ALL_MODE}; then
files=$(get_all_files)
else
files=$(get_changed_files)
fi
# Format files
format_files "${files}" "${clang_format}"
}
main "$@"