|  | 
|  | 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