aboutsummaryrefslogtreecommitdiff
path: root/lint.sh
blob: 55305e26d9660937521ae6c28d7e1dcde7558568 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#!/usr/bin/env bash
#
# Run clang-tidy over source translation units we maintain, inside the build container so
# the toolchain (gcc 15, libstdc++, Qt 6.11 headers, etc.) matches what
# generated build/compile_commands.json.
#
# Usage:
#   ./lint.sh                  # lint all our source files (parallel)
#   ./lint.sh path/to/file.cpp # lint a single file (relative to project root)
#   ./lint.sh --fix            # apply clang-tidy's auto-fixes in place
#
# Requires:
#   - build/compile_commands.json (built by `./build.sh tarball`)
#   - The fluorine-builder Docker image with clang-tidy installed.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "${SCRIPT_DIR}"

if [ ! -f build/compile_commands.json ]; then
    echo "ERROR: build/compile_commands.json not found."
    echo "Run './build.sh tarball' first to generate it."
    exit 1
fi

if command -v podman >/dev/null 2>&1; then
    DOCKER=podman
elif command -v docker >/dev/null 2>&1; then
    DOCKER=docker
else
    echo "ERROR: neither podman nor docker found in PATH"
    exit 1
fi

IMAGE_NAME="fluorine-builder"

if ! ${DOCKER} images --format '{{.Repository}}' 2>/dev/null \
        | grep -qE "(^|/)${IMAGE_NAME}$"; then
    echo "ERROR: ${IMAGE_NAME} image not found. Run './build.sh tarball' first."
    exit 1
fi

if ! ${DOCKER} run --rm "${IMAGE_NAME}" which clang-tidy >/dev/null 2>&1; then
    echo "WARNING: clang-tidy not in ${IMAGE_NAME}. Rebuilding image..."
    ${DOCKER} build -t "${IMAGE_NAME}" docker/
fi

FIX_FLAG=()
SINGLE_FILE=""
for arg in "$@"; do
    case "$arg" in
        --fix) FIX_FLAG+=(--fix) ;;
        --*)   echo "unknown flag: $arg"; exit 1 ;;
        *)     SINGLE_FILE="$arg" ;;
    esac
done

collect_files() {
    # Headers are checked through their owning translation units. Running
    # clang-tidy directly on every header produces duplicate diagnostics and
    # false positives for Qt-generated include paths.
    #
    # browserview / browserdialog depend on QtWebEngine which is Windows-only
    # in this fork (CMake removes them from ORGANIZER_SOURCES on Linux), so
    # clang-tidy can't parse their headers. Skip them.
    {
        find src/src -type f -name '*.cpp'
        find libs/skse_log_redirector -type f -name '*.cpp'
    } | grep -v -E '(/moc_|/ui_|/qrc_|_autogen/|/build/|browserview|browserdialog)'
}

if [ -n "${SINGLE_FILE}" ]; then
    REL_FILES=("${SINGLE_FILE#./}")
else
    mapfile -t REL_FILES < <(collect_files | sort)
fi

if [ "${#REL_FILES[@]}" -eq 0 ]; then
    echo "no files to lint"
    exit 0
fi

CONTAINER_PATHS=()
for f in "${REL_FILES[@]}"; do
    CONTAINER_PATHS+=("/src/${f}")
done

JOBS="${BUILD_JOBS:-4}"

echo "Linting ${#CONTAINER_PATHS[@]} files in ${IMAGE_NAME} (${JOBS} jobs)..."

# Container builds with GCC, which uses -mno-direct-extern-access — clang in
# the same container doesn't recognise that flag, so strip it from the
# compile DB before running clang-tidy. Copy to a tmp DB so we don't disturb
# ninja's incremental build.
printf '%s\n' "${CONTAINER_PATHS[@]}" | \
    ${DOCKER} run --rm -i \
        -v "${SCRIPT_DIR}:/src:rw" \
        -w /src \
        "${IMAGE_NAME}" \
        bash -c '
            set -e
            TIDY_DIR=$(mktemp -d)
            trap "rm -rf $TIDY_DIR" EXIT
            sed "s| -mno-direct-extern-access||g" /src/build/compile_commands.json \
                > "$TIDY_DIR/compile_commands.json"
            # stdbuf -oL forces line-buffered stdout so output flushes through
            # the podman pipe instead of being held until container exit.
            xargs -P '"${JOBS}"' -I{} stdbuf -oL clang-tidy '"${FIX_FLAG[*]:-}"' \
                -p "$TIDY_DIR" \
                --extra-arg=-Wno-ignored-gch \
                --exclude-header-filter="(^|/)(build/|.*_autogen/|ui_.*\\.h$)" \
                --quiet {}
        '