yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Janne Kiviluoto (NVIDIA)Feature/improve formatting sh (#8641)eda080bbf

master
8.3 KiB339 linesraw
1#!/usr/bin/env bash
2
3set -e
4
5# Check Bash version
6if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
7  echo "Error: Bash 4 or newer is required. Current version: $BASH_VERSION" >&2
8  if [[ "$(uname)" == "Darwin" ]]; then
9    echo "Please install a newer version of Bash using Homebrew:" >&2
10    echo "  brew install bash" >&2
11  fi
12  exit 1
13fi
14
15script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
16source_dir="$(dirname "$script_dir")"
17since_rev=""
18
19# Detect macOS and set appropriate binary names
20if [[ "$(uname)" == "Darwin" ]]; then
21  # On macOS, check for GNU versions
22  # grep and xargs use g-prefix, diff is installed as /opt/homebrew/bin/diff
23  missing_tools=()
24
25  if ! command -v ggrep &>/dev/null; then
26    missing_tools+=("grep")
27  fi
28
29  if ! command -v gxargs &>/dev/null; then
30    missing_tools+=("findutils")
31  fi
32
33  if ! command -v /opt/homebrew/bin/diff &>/dev/null; then
34    missing_tools+=("diffutils")
35  fi
36
37  if [ ${#missing_tools[@]} -gt 0 ]; then
38    echo "Error: GNU versions of grep, xargs, and diff are required on macOS." >&2
39    echo "Please install them using Homebrew:" >&2
40    echo "  brew install ${missing_tools[*]}" >&2
41    exit 1
42  fi
43
44  GREP_BIN="ggrep"
45  XARGS_BIN="gxargs"
46  DIFF_BIN="/opt/homebrew/bin/diff"
47else
48  # On other systems, use standard binaries
49  GREP_BIN="grep"
50  XARGS_BIN="xargs"
51  DIFF_BIN="diff"
52fi
53
54check_only=0
55no_version_check=0
56modified_files=0
57run_cpp=0
58run_yaml=0
59run_markdown=0
60run_sh=0
61run_cmake=0
62run_all=1
63
64show_help() {
65  me=$(basename "$0")
66  cat <<EOF
67$me: Format or check formatting of files in this repo
68
69Usage: $me [--check-only] [--no-version-check] [--source <path>] [--cpp] [--yaml] [--md] [--sh] [--cmake]
70
71Options:
72    --check-only       Check formatting without modifying files
73    --no-version-check Skip version compatibility checks
74    --source          Path to source directory to format (defaults to parent of script directory)
75    --cpp             Format only C++ files
76    --yaml            Format only YAML/JSON files
77    --md              Format only markdown files
78    --sh              Format only shell script files
79    --cmake           Format only CMake files
80    --since <rev>     Only format files since Git revision <rev>
81EOF
82}
83
84while [[ "$#" -gt 0 ]]; do
85  case $1 in
86  -h | --help)
87    show_help
88    exit 0
89    ;;
90  --check-only) check_only=1 ;;
91  --no-version-check) no_version_check=1 ;;
92  --modified) modified_files=1 ;;
93  --cpp)
94    run_cpp=1
95    run_all=0
96    ;;
97  --yaml)
98    run_yaml=1
99    run_all=0
100    ;;
101  --md)
102    run_markdown=1
103    run_all=0
104    ;;
105  --sh)
106    run_sh=1
107    run_all=0
108    ;;
109  --cmake)
110    run_cmake=1
111    run_all=0
112    ;;
113  --source)
114    source_dir="$2"
115    shift
116    ;;
117  --since)
118    since_rev="$2"
119    shift
120    ;;
121  *)
122    echo "unrecognized argument: $1"
123    show_help
124    exit 1
125    ;;
126  esac
127  shift
128done
129
130cd "$source_dir" || exit 1
131
132require_bin() {
133  local name="$1"
134  local min_version="$2"
135  local max_version="${3:-}"
136  local version
137
138  if ! command -v "$name" &>/dev/null; then
139    echo "This script needs $name, but it isn't in \$PATH" >&2
140    missing_bin=1
141    return
142  fi
143
144  if [ "$no_version_check" -eq 0 ]; then
145    version=$("$name" --version | $GREP_BIN -oP "\d+\.\d+\.?\d*" | head -n1)
146
147    # Debug output to stderr
148    if [ -n "$max_version" ]; then
149      echo "found $name $version, required [$min_version, $max_version)" >&2
150    else
151      echo "found $name $version, required at least $min_version" >&2
152    fi
153
154    if ! printf '%s\n%s\n' "$min_version" "$version" | sort -V -C; then
155      echo "$name version $version is too old. Version $min_version or newer is required." >&2
156      missing_bin=1
157      return
158    fi
159
160    if [ -n "$max_version" ]; then
161      if ! printf '%s\n%s\n' "$version" "$max_version" | sort -V -C; then
162        echo "$name version $version is too new. Version less than $max_version is required." >&2
163        missing_bin=1
164        return
165      fi
166    fi
167  fi
168}
169
170require_bin "git" "1.8"
171((run_all || run_cmake)) && require_bin "gersemi" "0.21" "0.22"
172((run_all || run_cpp)) && require_bin "$XARGS_BIN" "3"
173require_bin "$DIFF_BIN" "2"
174((run_all || run_cpp)) && require_bin "clang-format" "17" "18"
175((run_all || run_yaml || run_markdown)) && require_bin "prettier" "3"
176((run_all || run_sh)) && require_bin "shfmt" "3"
177
178if [ "$missing_bin" ]; then
179  exit 1
180fi
181
182get_nproc() {
183  local nproc_count
184  if command -v nproc &>/dev/null; then
185    nproc_count=$(nproc)
186  elif [[ "$(uname)" == "Darwin" ]] && command -v sysctl &>/dev/null; then
187    nproc_count=$(sysctl -n hw.logicalcpu)
188  elif command -v getconf &>/dev/null; then
189    nproc_count=$(getconf _NPROCESSORS_ONLN 2>/dev/null)
190  fi
191  echo "${nproc_count:-1}"
192}
193
194exit_code=0
195
196function list_files() {
197  if [ "$since_rev" ] || [ "$modified_files" -eq 1 ]; then
198    command="git diff --name-only"
199    if [ "$since_rev" ]; then
200      command="$command $since_rev"
201    else
202      command="$command HEAD"
203    fi
204    if [ "$modified_files" -eq 0 ]; then
205      command="$command HEAD"
206    fi
207    $command "$@"
208  else
209    git ls-files $@
210  fi
211}
212
213cmake_formatting() {
214  echo "Formatting CMake files..." >&2
215
216  readarray -t files < <(list_files '*.cmake' 'CMakeLists.txt' '**/CMakeLists.txt')
217
218  common_args=(
219    # turn on warning when this is fixed https://github.com/BlankSpruce/gersemi/issues/39
220    --no-warn-about-unknown-commands
221    --definitions "${files[@]}"
222  )
223
224  if [ "$check_only" -eq 1 ]; then
225    gersemi "${common_args[@]}" --diff --color "${files[@]}"
226    gersemi "${common_args[@]}" --check "${files[@]}" || exit_code=1
227  else
228    gersemi "${common_args[@]}" --in-place "${files[@]}"
229  fi
230}
231
232track_progress() {
233  # Don't output the progress bar if stderr isn't a terminal, just eat all the input
234  [ -t 2 ] || {
235    cat >/dev/null
236    return
237  }
238
239  local total=$1
240  local current=0
241
242  ((total)) && while IFS= read -r _; do
243    ((current++)) || :
244    percent=$((current * 100 / total))
245    printf '\rProgress: [%-50s] %d%%' "$(printf '#%.0s' $(seq 1 $((percent / 2))))" "$percent" >&2
246  done
247  echo >&2
248}
249
250cpp_formatting() {
251  echo "Formatting cpp files..." >&2
252
253  readarray -t files < <(list_files '*.cpp' '*.hpp' '*.c' '*.h' ':!external/**')
254
255  # The progress reporting is a bit sneaky, we use `--verbose` with xargs which
256  # prints a line to stderr for each command, and we simply count these...
257
258  if [ "$check_only" -eq 1 ]; then
259    local tmpdir
260    tmpdir=$(mktemp -d)
261    trap 'rm -rf "$tmpdir"' EXIT
262
263    printf '%s\n' "${files[@]}" | $XARGS_BIN --verbose -P "$(get_nproc)" -I{} bash -c "
264      mkdir -p \"\$(dirname \"$tmpdir/{}\")\"
265      $DIFF_BIN -u --color=always --label \"{}\" --label \"{}\" \"{}\" <(clang-format \"{}\") > \"$tmpdir/{}\"
266      :
267    " |& track_progress ${#files[@]}
268
269    for file in "${files[@]}"; do
270      # Fail if any of the diffs have contents
271      if [ -s "$tmpdir/$file" ]; then
272        cat "$tmpdir/$file"
273        exit_code=1
274      fi
275    done
276  else
277    printf '%s\n' "${files[@]}" | $XARGS_BIN --verbose -n1 -P "$(get_nproc)" clang-format -i |&
278      track_progress ${#files[@]}
279  fi
280}
281
282# Format the 'files' array using the prettier tool (abstracted here because
283# it's used by markdown and json
284prettier_formatting() {
285  if [ "$check_only" -eq 1 ]; then
286    for file in "${files[@]}"; do
287      if ! output=$(prettier "$file" 2>/dev/null); then
288        continue
289      fi
290      if ! $DIFF_BIN -q "$file" <(echo "$output") >/dev/null 2>&1; then
291        $DIFF_BIN --color -u --label "$file" --label "$file" "$file" <(echo "$output") || :
292        exit_code=1
293      fi
294    done
295  else
296    prettier --write "${files[@]}" | $GREP_BIN -v '(unchanged)' >&2 || :
297  fi
298}
299
300yaml_json_formatting() {
301  echo "Formatting yaml and json files..." >&2
302
303  readarray -t files < <(list_files "*.yaml" "*.yml" "*.json" ':!external/**')
304
305  prettier_formatting
306}
307
308markdown_formatting() {
309  echo "Formatting markdown files..." >&2
310
311  readarray -t files < <(list_files "*.md" ':!external/**')
312
313  prettier_formatting
314}
315
316sh_formatting() {
317  echo "Formatting sh files..." >&2
318
319  readarray -t files < <(list_files "*.sh")
320
321  common_args=(
322    # default 8 is way too wide
323    --indent 2
324  )
325
326  if [ "$check_only" -eq 1 ]; then
327    shfmt "${common_args[@]}" --diff "${files[@]}" || exit_code=1
328  else
329    shfmt "${common_args[@]}" --write "${files[@]}"
330  fi
331}
332
333((run_all || run_sh)) && sh_formatting
334((run_all || run_cmake)) && cmake_formatting
335((run_all || run_yaml)) && yaml_json_formatting
336((run_markdown)) && markdown_formatting
337((run_all || run_cpp)) && cpp_formatting
338
339exit $exit_code