|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Usage: |
| 3 | +# # Do work and commit your work. |
| 4 | +# |
| 5 | +# # Format files that differ from origin/main. |
| 6 | +# bash format.sh |
| 7 | +# |
| 8 | +# # Format all files. |
| 9 | +# bash format.sh --all |
| 10 | +# |
| 11 | +# |
| 12 | +# YAPF + Clang formatter (if installed). This script formats all changed files from the last mergebase. |
| 13 | +# You are encouraged to run this locally before pushing changes for review. |
| 14 | + |
| 15 | +# Cause the script to exit if a single command fails |
| 16 | +set -eo pipefail |
| 17 | + |
| 18 | +if [[ -z "${BASH_VERSION}" ]]; then |
| 19 | + echo "Please run this script using bash." >&2 |
| 20 | + exit 1 |
| 21 | +fi |
| 22 | + |
| 23 | +# this stops git rev-parse from failing if we run this from the .git directory |
| 24 | +builtin cd "$(dirname "${BASH_SOURCE:-$0}")" |
| 25 | +ROOT="$(git rev-parse --show-toplevel)" |
| 26 | +builtin cd "$ROOT" || exit 1 |
| 27 | + |
| 28 | +ALL_FILES='' |
| 29 | +ONLY_CHANGED='' |
| 30 | +FILES=() |
| 31 | +if (($# == 0)); then |
| 32 | + if [[ -n "$(git status --porcelain)" ]]; then |
| 33 | + echo 'Detected uncommitted changes. Please commit or stash them before running format.sh.' >&2 |
| 34 | + exit 1 |
| 35 | + fi |
| 36 | + ONLY_CHANGED='true' |
| 37 | +else |
| 38 | + while (($# > 0)); do |
| 39 | + case $1 in |
| 40 | + --files) |
| 41 | + shift |
| 42 | + while (($# > 0)); do |
| 43 | + FILES+=("$1") |
| 44 | + shift |
| 45 | + done |
| 46 | + ;; |
| 47 | + --all) |
| 48 | + ALL_FILES='true' |
| 49 | + shift |
| 50 | + ;; |
| 51 | + *) |
| 52 | + echo "Unknown argument: '$1'" >&2 |
| 53 | + exit 1 |
| 54 | + ;; |
| 55 | + esac |
| 56 | + done |
| 57 | +fi |
| 58 | + |
| 59 | +MERGE_BASE="" |
| 60 | +get_merge_base() { |
| 61 | + UPSTREAM_REPO="https://github.com/tile-ai/tilelang" |
| 62 | + if git ls-remote --exit-code "${UPSTREAM_REPO}" main &>/dev/null; then |
| 63 | + # First try to use the upstream repository directly |
| 64 | + MERGE_BASE="$(git fetch "${UPSTREAM_REPO}" main &>/dev/null && git merge-base FETCH_HEAD HEAD)" |
| 65 | + elif git show-ref --verify --quiet refs/remotes/origin/main; then |
| 66 | + # Fall back to origin/main if available |
| 67 | + BASE_BRANCH="origin/main" |
| 68 | + MERGE_BASE="$(git merge-base "${BASE_BRANCH}" HEAD)" |
| 69 | + else |
| 70 | + # Last resort, use local main |
| 71 | + BASE_BRANCH="main" |
| 72 | + MERGE_BASE="$(git merge-base "${BASE_BRANCH}" HEAD)" |
| 73 | + fi |
| 74 | + echo "${MERGE_BASE}" |
| 75 | +} |
| 76 | + |
| 77 | +if [[ -n "${ALL_FILES}" ]]; then |
| 78 | + echo "Checking all files..." >&2 |
| 79 | +elif [[ -n "${ONLY_CHANGED}" ]]; then |
| 80 | + MERGE_BASE="$(get_merge_base)" |
| 81 | + echo "Checking changed files compared to merge base (${MERGE_BASE})..." >&2 |
| 82 | +elif [[ "${#FILES[@]}" -gt 0 ]]; then |
| 83 | + echo "Checking specified files: ${FILES[*]}..." >&2 |
| 84 | +fi |
| 85 | + |
| 86 | +# If pre-commit is not installed, install it. |
| 87 | +if python3 -m pre_commit --version &>/dev/null; then |
| 88 | + python3 -m pip install pre-commit |
| 89 | +fi |
| 90 | + |
| 91 | +if [[ ! -f "${ROOT}/.git/hooks/pre-commit" ]]; then |
| 92 | + echo "Installing and initializing pre-commit hooks..." |
| 93 | + python3 -m pre_commit install --install-hooks |
| 94 | +fi |
| 95 | + |
| 96 | +echo 'tile-lang pre-commit: Check Start' |
| 97 | + |
| 98 | +if [[ -n "${ALL_FILES}" ]]; then |
| 99 | + python3 -m pre_commit run --all-files |
| 100 | +elif [[ -n "${ONLY_CHANGED}" ]]; then |
| 101 | + python3 -m pre_commit run --from-ref "${MERGE_BASE}" --to-ref HEAD |
| 102 | +elif [[ "${#FILES[@]}" -gt 0 ]]; then |
| 103 | + python3 -m pre_commit run --files "${FILES[@]}" |
| 104 | +fi |
| 105 | + |
| 106 | +echo 'tile-lang pre-commit: Done' |
| 107 | + |
| 108 | +echo 'tile-lang clang-tidy: Check Start' |
| 109 | +# If clang-tidy is available, run it; otherwise, skip |
| 110 | +if command -v run-clang-tidy &>/dev/null; then |
| 111 | + # Check if clang-tidy is available |
| 112 | + if ! command -v clang-tidy &>/dev/null; then |
| 113 | + python3 -m pip install --upgrade --requirements "${ROOT}/requirements-lint.txt" |
| 114 | + fi |
| 115 | + # Get clang-tidy version |
| 116 | + CLANG_TIDY_VERSION="$(clang-tidy --version | head -n1 | awk '{print $4}')" |
| 117 | + echo "Using clang-tidy version: ${CLANG_TIDY_VERSION}" |
| 118 | + |
| 119 | + # Check if build directory exists |
| 120 | + if [[ ! -d "${ROOT}/build" ]]; then |
| 121 | + echo "Build directory not found. Skipping clang-tidy checks." |
| 122 | + else |
| 123 | + # Run clang-tidy on specified files |
| 124 | + clang_tidy_files() { |
| 125 | + run-clang-tidy -j 64 "$@" -p build |
| 126 | + } |
| 127 | + |
| 128 | + # Run clang-tidy on all C/C++ source files |
| 129 | + clang_tidy_all() { |
| 130 | + run-clang-tidy -j 64 src/*.cc -p build |
| 131 | + } |
| 132 | + |
| 133 | + # Run clang-tidy on changed C/C++ files relative to main |
| 134 | + clang_tidy_changed() { |
| 135 | + # Get changed C/C++ files |
| 136 | + CHANGED_FILES="$(git diff --name-only --diff-filter=ACM "${MERGE_BASE}" -- '*.c' '*.cc' '*.cpp' '*.h' '*.hpp' 2>/dev/null || true)" |
| 137 | + |
| 138 | + if [[ -n "${CHANGED_FILES}" ]]; then |
| 139 | + echo "Running clang-tidy on changed files:" |
| 140 | + echo "${CHANGED_FILES}" |
| 141 | + # Convert newline-separated files to space-separated and run clang-tidy once |
| 142 | + CHANGED_FILES_SPACE="$(echo "${CHANGED_FILES}" | tr '\n' ' ')" |
| 143 | + run-clang-tidy -j 64 ${CHANGED_FILES_SPACE} -p build -fix |
| 144 | + else |
| 145 | + echo "No C/C++ files changed. Skipping clang-tidy." |
| 146 | + fi |
| 147 | + } |
| 148 | + |
| 149 | + if [[ -n "${ALL_FILES}" ]]; then |
| 150 | + # If --all is given, run clang-tidy on all source files |
| 151 | + clang_tidy_all |
| 152 | + elif [[ -n "${ONLY_CHANGED}" ]]; then |
| 153 | + # Otherwise, run clang-tidy only on changed C/C++ files |
| 154 | + clang_tidy_changed |
| 155 | + elif [[ "${#FILES[@]}" -gt 0 ]]; then |
| 156 | + # If --files is given, run clang-tidy only on the provided files |
| 157 | + clang_tidy_files "${FILES[@]}" |
| 158 | + fi |
| 159 | + fi |
| 160 | + |
| 161 | +else |
| 162 | + echo "run-clang-tidy not found. Skipping clang-tidy checks." |
| 163 | + echo "To install clang-tidy tools, you may need to install clang-tidy and run-clang-tidy." |
| 164 | +fi |
| 165 | +echo 'tile-lang clang-tidy: Done' |
| 166 | + |
| 167 | +# Check if there are any uncommitted changes after all formatting steps. |
| 168 | +# If there are, ask the user to review and stage them. |
| 169 | +if ! git diff --quiet &>/dev/null; then |
| 170 | + echo 'Reformatted files. Please review and stage the changes.' |
| 171 | + echo 'Changes not staged for commit:' |
| 172 | + echo |
| 173 | + git --no-pager diff --name-only |
| 174 | + |
| 175 | + exit 1 |
| 176 | +fi |
| 177 | + |
| 178 | +echo 'tile-lang: All checks passed' |
0 commit comments