name: CI on: push: pull_request: permissions: contents: read jobs: shellcheck: name: ShellCheck runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Run ShellCheck (errors only) # Check every tracked file that has a .sh extension or an sh/bash # shebang. SC2148 (missing shebang) is excluded because many .sh # files are sourced fragments or dracut hooks; ShellCheck then # falls back to checking them as bash. run: | shebang_re='^#!.*[/ ](sh|bash|dash|ash|ksh)([[:blank:]]|$)' { git ls-files '*.sh' git ls-files | while IFS= read -r f; do [ -f "$f" ] || continue firstline= # An empty file makes read fail, which under -e would end the run. IFS= read -r -n 200 firstline < "$f" 2>/dev/null || true if [[ $firstline =~ $shebang_re ]]; then printf '%s\n' "$f" fi done } | sort -u > /tmp/shfiles # A selection that quietly comes up empty would check nothing and # still pass, so say how many files there are and insist on some. echo "$(wc -l < /tmp/shfiles) shell files" [ -s /tmp/shfiles ] || { echo '::error::No shell files found'; exit 1; } xargs -d '\n' shellcheck --severity=error --exclude=SC2148 < /tmp/shfiles ruff: name: Ruff runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 # Pinned to an exact release: unlike actions/checkout, ruff-action # publishes no moving major tag past v3, so @v4 does not resolve. - uses: astral-sh/ruff-action@v4.1.0 with: version: latest # Rule selection, file discovery (the many extensionless Python # executables) and exclusions all live in ruff.toml, so the whole # workspace can be handed over as-is. args: check --output-format=github pyrefly: name: Pyrefly runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: facebook/pyrefly@main python-compileall: name: Python compileall runs-on: ubuntu-latest env: # One entry per Python version shipped by the distros confluent # targets, limited to versions actions/setup-python still provides # on current runners (sles15/alma8 ship 3.6, which is unavailable). # Newline-separated so it feeds both setup-python (multiline input) # and the shell loop below (word-split on whitespace). PYTHON_VERSIONS: | 3.8 3.9 3.10 3.12 3.13 3.14 steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSIONS }} - name: List the Python files without a .py name # compileall only ever compiles *.py: handed anything else, even by # name, it skips it and still exits 0. That leaves every extensionless # CLI tool, deploy script and setup.py.tmpl unchecked, so collect them # here and feed them to py_compile, which does compile what it is # given. The first line is read with the shell builtin rather than # forking head and grep per file. run: | shebang_re='^#!.*python' { git ls-files | while IFS= read -r f; do [ -f "$f" ] || continue case "$f" in *.py) continue ;; esac firstline= # An empty file makes read fail, which under -e would end the run. IFS= read -r -n 200 firstline < "$f" 2>/dev/null || true if [[ $firstline =~ $shebang_re ]]; then printf '%s\n' "$f" fi done # Python that carries no shebang at all, so nothing can detect it. # Kept in step with extend-include in ruff.toml. git ls-files '*/setup.py.tmpl' '*/scripts/configbmc' \ '*/scripts/add_local_repositories' 'misc/filterpasswd' } | sort -u > /tmp/pyfiles # An empty list would leave py_compile with nothing to do and the # job green, which is the very hole this step exists to close. echo "$(wc -l < /tmp/pyfiles) files without a .py name" [ -s /tmp/pyfiles ] || { echo '::error::No such files found'; exit 1; } - name: Compile all Python files run: | rc=0 for v in $PYTHON_VERSIONS; do echo "::group::Python $v" ok=0 "python$v" -W error -m compileall -q -x '/\.git/' . || ok=1 xargs -d '\n' "python$v" -W error -m py_compile < /tmp/pyfiles || ok=1 echo "::endgroup::" if [ "$ok" -ne 0 ]; then echo "::error::Python $v compileall failed" rc=1 fi done exit "$rc"