Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions .ci/check-format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
# Copyright (c) 2025 The F9 Microkernel Project. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Format validation for C, shell, and Python files
# The -e is not set because we want to collect all format mismatches at once

set -u -o pipefail

# In CI environment, require all formatting tools to be present
# GitHub Actions sets CI=true automatically (also accepts CI=1, CI=yes, etc.)
REQUIRE_TOOLS="false"
if [ -n "${CI:-}" ]; then
REQUIRE_TOOLS="true"
fi

C_FORMAT_EXIT=0
SH_FORMAT_EXIT=0
PY_FORMAT_EXIT=0

# Use git ls-files to exclude submodules and untracked files
# F9 Microkernel structure: include/, kernel/, platform/, user/, board/, loader/
# Exclude: externals/ (third-party code with own style)
C_SOURCES=()
while IFS= read -r file; do
[ -n "$file" ] && C_SOURCES+=("$file")
done < <(git ls-files -- 'include/*.h' 'include/**/*.h' 'kernel/*.c' 'platform/*.c' 'platform/**/*.c' 'platform/*.h' 'platform/**/*.h' 'user/*.c' 'user/**/*.c' 'user/*.h' 'user/**/*.h' 'board/*.c' 'board/**/*.c' 'board/*.h' 'board/**/*.h' 'loader/*.c' 'loader/**/*.c' 'loader/*.h' 'loader/**/*.h' | grep -v '^externals/' || true)

if [ ${#C_SOURCES[@]} -gt 0 ]; then
# Try clang-format with version suffix first, then generic
CLANG_FORMAT=""
for cmd in clang-format-21 clang-format-20 clang-format-19 clang-format; do
if command -v "$cmd" >/dev/null 2>&1; then
CLANG_FORMAT="$cmd"
break
fi
done

if [ -n "$CLANG_FORMAT" ]; then
echo "Checking ${#C_SOURCES[@]} C/C++ files with $CLANG_FORMAT..."
$CLANG_FORMAT -n --Werror "${C_SOURCES[@]}"
C_FORMAT_EXIT=$?
if [ $C_FORMAT_EXIT -eq 0 ]; then
echo " ✓ All C/C++ files properly formatted"
else
echo " ✗ C/C++ format violations found" >&2
fi
else
if [ "$REQUIRE_TOOLS" = "true" ]; then
echo "ERROR: clang-format not found (required in CI)" >&2
C_FORMAT_EXIT=1
else
echo "Skipping C format check: clang-format not found" >&2
fi
fi
else
echo "No C/C++ files to check"
fi

SH_SOURCES=()
while IFS= read -r file; do
[ -n "$file" ] && SH_SOURCES+=("$file")
done < <(git ls-files -- '*.sh' '.ci/*.sh' 'scripts/*.sh' | grep -v '^externals/' || true)

if [ ${#SH_SOURCES[@]} -gt 0 ]; then
if command -v shfmt >/dev/null 2>&1; then
echo "Checking ${#SH_SOURCES[@]} shell scripts with shfmt..."
MISMATCHED_SH=$(shfmt -l "${SH_SOURCES[@]}")
if [ -n "$MISMATCHED_SH" ]; then
echo " ✗ The following shell scripts need formatting:" >&2
printf ' %s\n' $MISMATCHED_SH >&2
echo " Run: shfmt -w \$(git ls-files '*.sh' '.ci/*.sh' 'scripts/*.sh')" >&2
SH_FORMAT_EXIT=1
else
echo " ✓ All shell scripts properly formatted"
fi
else
if [ "$REQUIRE_TOOLS" = "true" ]; then
echo "ERROR: shfmt not found (required in CI)" >&2
SH_FORMAT_EXIT=1
else
echo "Skipping shell script format check: shfmt not found" >&2
fi
fi
else
echo "No shell scripts to check"
fi

PY_SOURCES=()
while IFS= read -r file; do
[ -n "$file" ] && PY_SOURCES+=("$file")
done < <(git ls-files -- 'scripts/*.py' | grep -v '^externals/' || true)

if [ ${#PY_SOURCES[@]} -gt 0 ]; then
if command -v black >/dev/null 2>&1; then
echo "Checking ${#PY_SOURCES[@]} Python files with black..."
black --check --quiet "${PY_SOURCES[@]}" 2>&1
PY_FORMAT_EXIT=$?
if [ $PY_FORMAT_EXIT -eq 0 ]; then
echo " ✓ All Python files properly formatted"
else
echo " ✗ Python format violations found" >&2
echo " Run: black scripts/*.py" >&2
fi
else
if [ "$REQUIRE_TOOLS" = "true" ]; then
echo "ERROR: black not found (required in CI)" >&2
PY_FORMAT_EXIT=1
else
echo "Skipping Python format check: black not found" >&2
fi
fi
else
echo "No Python files to check"
fi

# Summary
echo ""
echo "Format check summary:"
echo " C/C++: $([ $C_FORMAT_EXIT -eq 0 ] && echo "✓ PASS" || echo "✗ FAIL")"
echo " Shell: $([ $SH_FORMAT_EXIT -eq 0 ] && echo "✓ PASS" || echo "✗ FAIL")"
echo " Python: $([ $PY_FORMAT_EXIT -eq 0 ] && echo "✓ PASS" || echo "✗ FAIL")"

# Use logical OR to avoid exit code overflow (codes are mod 256)
if [ $C_FORMAT_EXIT -ne 0 ] || [ $SH_FORMAT_EXIT -ne 0 ] || [ $PY_FORMAT_EXIT -ne 0 ]; then
echo ""
echo "Format check FAILED"
exit 1
fi

echo ""
echo "Format check PASSED"
exit 0
55 changes: 55 additions & 0 deletions .ci/check-newline.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Copyright (c) 2025 The F9 Microkernel Project. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Check all tracked text files for trailing newlines
# Excludes: externals/ (third-party code), binary files, build artifacts
# Reference: https://medium.com/@alexey.inkin/how-to-force-newline-at-end-of-files-and-why-you-should-do-it-fdf76d1d090e

set -e -u -o pipefail

ret=0
show=0
checked=0
failed=0
echo "Checking for missing trailing newlines..."

while IFS= read -rd '' f; do
# Skip excluded directories and files
case "$f" in
externals/*) continue ;; # Third-party code
.config | .config.* | build/*) continue ;; # Build artifacts
*.bin | *.elf | *.o | *.a) continue ;; # Binaries
esac

# Skip empty files (e.g., __init__.py markers)
[ -s "$f" ] || continue

# Only check text files
if file --mime-encoding "$f" | grep -qv binary; then
checked=$((checked + 1))
tail -c1 <"$f" | read -r _ || show=1
if [ $show -eq 1 ]; then
echo " ✗ Missing newline: $f" >&2
failed=$((failed + 1))
ret=1
show=0
fi
fi
done < <(git ls-files -z)

# Summary
echo ""
echo "Newline check summary:"
echo " Files checked: $checked"
if [ $ret -eq 0 ]; then
echo " Result: ✓ PASS - All files end with newline"
else
echo " Files failed: $failed"
echo " Result: ✗ FAIL - Some files missing trailing newline" >&2
echo ""
echo "To fix: Add newline to end of each file listed above" >&2
fi

exit $ret
Loading
Loading