diff --git a/scripts/check_text_hygiene.py b/scripts/check_text_hygiene.py index 6c3170ae..d12301ec 100644 --- a/scripts/check_text_hygiene.py +++ b/scripts/check_text_hygiene.py @@ -89,6 +89,10 @@ SOURCE_DIRS = ("MobileGL/", "scripts/", "tools/") # Files whose exact bytes are pinned elsewhere: changing them, even a comment, means a protocol # revision bump (scripts/ci/protocol_revision_pins.json), so only the UTF-8 rule applies to them. FROZEN = {"MobileGL/MG_Remote/Protocol/protocol.fbs"} +# Left unformatted: its export table's HEAD/END macro pairs end no statement, so clang-format reads +# the whole table as one expression and needs minutes and gigabytes to wrap it, even under +# `clang-format off`. +FORMAT_SKIP = {"MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp"} NOTES_DIR = "docs/Disaggregated/notes/" # The archived planning notes and agent contracts. The commit that adds ARCHIVE_MARKER fills the # archive; every later commit leaves it alone. @@ -112,6 +116,12 @@ def is_generated(path): return "/generated/" in path or "/gen/" in path or os.path.basename(path).startswith("generated_") +def is_formatted(path): + """Whether clang-format owns the file's layout: the format check and format_code.sh cover it.""" + return (path.startswith(SOURCE_DIRS) and path.endswith(FORMAT_EXT) and not is_generated(path) + and path not in FORMAT_SKIP) + + def submodule_paths(repo=ROOT): out = git("config", "--file", ".gitmodules", "--get-regexp", r"submodule\..*\.path", check=False, repo=repo) return {line.split(" ", 1)[1].strip() for line in out.splitlines() if " " in line} @@ -595,7 +605,7 @@ def check_format(rev_range): if is_merge(sha) or not git("rev-parse", "--verify", "--quiet", sha + "^", check=False).strip(): continue for path, ranges in unmoved_ranges(git("diff", "-U0", "-M", "--no-color", sha + "^", sha)).items(): - if not (path.endswith(FORMAT_EXT) and path.startswith(SOURCE_DIRS)) or is_generated(path): + if not is_formatted(path): continue checked += 1 content = subprocess.run(["git", "-C", ROOT, "show", "%s:%s" % (sha, path)], stdout=subprocess.PIPE).stdout diff --git a/scripts/format_code.sh b/scripts/format_code.sh index 596fd721..82947a1f 100755 --- a/scripts/format_code.sh +++ b/scripts/format_code.sh @@ -7,156 +7,36 @@ # SPDX-License-Identifier: LGPL-3.0-only # End of Source File Header -set -uo pipefail -if [ -t 1 ]; then - CYAN=$'\033[36m' - GREEN=$'\033[32m' - YELLOW=$'\033[33m' - RED=$'\033[31m' - BOLD=$'\033[1m' - RESET=$'\033[0m' -else - CYAN='' ; GREEN='' ; YELLOW='' ; RED='' ; BOLD='' ; RESET='' -fi +# Reformat in place every file the CI format check covers (is_formatted in check_text_hygiene.py: +# tracked C/C++ sources, minus generated files, .inc fragments and FORMAT_SKIP). The tree is +# already formatted, so this only rewrites lines that drifted. CLANG_FORMAT overrides the binary. +set -euo pipefail +cd "$(dirname "$0")/.." +exec python3 - <<'EOF' +import importlib.util +import os +import shutil +import subprocess +import sys +from concurrent.futures import ThreadPoolExecutor -OK="✔"; SKIP="⤼"; FAIL="✖" +spec = importlib.util.spec_from_file_location("hygiene", os.path.join("scripts", "check_text_hygiene.py")) +hygiene = importlib.util.module_from_spec(spec) +spec.loader.exec_module(hygiene) +binary = os.environ.get("CLANG_FORMAT") or shutil.which("clang-format-23") or shutil.which("clang-format") +if not binary: + sys.exit("clang-format not found; install it with: pip install 'clang-format==23.1.3'") +print(subprocess.run([binary, "--version"], stdout=subprocess.PIPE).stdout.decode().strip()) +files = [f for f in hygiene.git("ls-files", *hygiene.SOURCE_DIRS).splitlines() if hygiene.is_formatted(f)] -cd "$(dirname "$0")" || { printf "%s\n" "Cannot cd to script dir"; exit 1; } -DIR="../MobileGL" -CLANG_FORMAT_CONFIG="../.clang-format" +def format_one(path): + before = open(path, "rb").read() + subprocess.run([binary, "-i", "--style=file", path], check=True) + return open(path, "rb").read() != before -CLANG_FORMAT_PATH="$(command -v clang-format-23 2>/dev/null || true)" -if [ -z "$CLANG_FORMAT_PATH" ]; then - CLANG_FORMAT_PATH="$(command -v clang-format 2>/dev/null || true)" -fi -if [ -z "$CLANG_FORMAT_PATH" ]; then - printf '%s\n' "${RED}Error:${RESET} no clang-format found in PATH (tried clang-format-21 and clang-format)." - exit 1 -fi -if [ ! -f "$CLANG_FORMAT_CONFIG" ]; then - printf '%s\n' "${RED}Error:${RESET} clang-format config not found at ${CLANG_FORMAT_CONFIG}" - exit 1 -fi - -printf '%s %s\n' "${CYAN}${BOLD}Using clang-format:${RESET}" "${CLANG_FORMAT_PATH}" -printf '%s %s\n' "${CYAN}${BOLD}Config file:${RESET}" "${CLANG_FORMAT_CONFIG}" -DIR_ABS="(missing)" -if [ -d "$DIR" ]; then - DIR_ABS="$(cd "$DIR" && pwd)" -fi -printf '%s %s -> %s\n\n' "${CYAN}${BOLD}Target dir:${RESET}" "${DIR}" "${DIR_ABS}" - -if [ "$DIR_ABS" = "(missing)" ]; then - printf '%s\n' "${YELLOW}Target directory '${DIR}' does not exist relative to script dir '$(pwd)'.${RESET}" - printf '%s\n' "ls -la of current dir:" - ls -la - exit 1 -fi - -candidates=() -while IFS= read -r -d '' f; do - candidates+=("$f") -done < <( - find "$DIR" \ - \( -type d \( -name out -o -name build \) -prune \) -o \ - -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.cpp" -o -name "*.cc" -o -name "*.c" \) -print0 2>/dev/null -) - -printf '%s\n' "${YELLOW}find results:${RESET} ${#candidates[@]} candidate(s) found." -if [ "${#candidates[@]}" -eq 0 ]; then - printf '%s\n' "${YELLOW}No files matched.${RESET}" - ls -la "$DIR" | sed -n '1,40p' - exit 0 -fi - -todo=() -skipped_def=0 -skipped_big=0 -skipped_err=0 -i=0 -total_candidates=${#candidates[@]} -for f in "${candidates[@]}"; do - i=$((i+1)) - if (( i % 50 == 0 )); then - printf '%s\n' "${CYAN}Filtering progress:${RESET} ${i}/${total_candidates}" - fi - - base=$(basename "$f" 2>/dev/null) || base="" - if [ "$base" = "Definitions.cpp" ]; then - skipped_def=$((skipped_def+1)) - continue - fi - - line_count=$(awk 'END{print NR+0}' "$f" 2>/dev/null) || { line_count=0; skipped_err=$((skipped_err+1)); printf '%s\n' "${YELLOW}Warn:${RESET} awk failed on '$f' (skipping)"; continue; } - if ! [[ "$line_count" =~ ^[0-9]+$ ]]; then - skipped_err=$((skipped_err+1)) - printf '%s\n' "${YELLOW}Warn:${RESET} got non-numeric line count for '$f' -> '${line_count}', skipping" - continue - fi - if [ "$line_count" -gt 10000 ]; then - skipped_big=$((skipped_big+1)) - continue - fi - todo+=("$f") -done - -printf '\n%s\n\n' "${YELLOW}Filtering result:${RESET} total candidates=${total_candidates}, to-format=${#todo[@]}, skipped Definitions.cpp=${skipped_def}, skipped >10000=${skipped_big}, awk-errors=${skipped_err}" - -total=${#todo[@]} -if [ "$total" -eq 0 ]; then - printf '%s\n' "${YELLOW}Nothing to format.${RESET}" - exit 0 -fi - -OK="✔"; SKIP="⤼"; FAIL="✖" -ARROW="╰─>"; SIMPLE_ARROW="─>" - -draw_progress() { - local current=$1 total=$2 width=36 - - local percent=0 - if [ "$total" -gt 0 ]; then - percent=$(( (current * 100 + total/2) / total )) - if [ "$percent" -gt 100 ]; then percent=100; fi - fi - - local filled=0 - if [ "$total" -gt 0 ]; then - filled=$(( (current * width + total - 1) / total )) - fi - if [ "$filled" -gt "$width" ]; then filled=$width; fi - if [ "$filled" -lt 0 ]; then filled=0; fi - - local empty=$(( width - filled )) - - local bar_filled="" - local bar_empty="" - if [ "$filled" -gt 0 ]; then - bar_filled="$(printf '%0.s█' $(seq 1 "$filled") 2>/dev/null || true)" - fi - if [ "$empty" -gt 0 ]; then - bar_empty="$(printf '%0.s░' $(seq 1 "$empty") 2>/dev/null || true)" - fi - - printf "\r%s [%3d/%3d] %3d%% |%s%s| " "${CYAN}${BOLD}Progress${RESET}" "$current" "$total" "$percent" "$bar_filled" "$bar_empty" -} - -processed=0 -trap 'printf "\nInterrupted\n"; exit' INT TERM - -for f in "${todo[@]}"; do - processed=$((processed+1)) - draw_progress "$processed" "$total" - - printf '\n%s %s %s\n' "${CYAN}Formatting${RESET}" "$SIMPLE_ARROW" "$f" - if "$CLANG_FORMAT_PATH" -i -style="file:${CLANG_FORMAT_CONFIG}" "$f"; then - printf ' %s %s %s\n' "$ARROW" "${GREEN}${OK}${RESET}" "done" - else - printf ' %s %s %s\n' "$ARROW" "${RED}${FAIL}${RESET}" "failed" - fi - draw_progress "$processed" "$total" -done - -printf '\n\n%s %s\n' "${GREEN}All done!${RESET}" "Formatted: ${processed}, Skipped: $((skipped_def + skipped_big + skipped_err))" +with ThreadPoolExecutor(os.cpu_count() or 4) as pool: + changed = sum(pool.map(format_one, files)) +print("%d of %d files reformatted" % (changed, len(files))) +EOF