Update old Python code
[tinc] / lint.py
diff --git a/lint.py b/lint.py
new file mode 100755 (executable)
index 0000000..076b3e7
--- /dev/null
+++ b/lint.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+"""Run linters on project code. Add --fix to autofix files with linters that support it."""
+
+import sys
+import subprocess as subp
+from glob import glob
+from os import path, environ, chdir
+
+DRY = "--fix" not in sys.argv or environ.get("CI")
+HEADER = "#" * 24
+
+if DRY:
+    MSG = """
+You're running linters in non-destructive readonly mode.
+Some of them support automated fixes (like reformatting code).
+To apply them, run `lint.py --fix` or `ninja -C build reformat`.
+"""
+    print(MSG, file=sys.stderr)
+
+source_root = path.dirname(path.realpath(__file__))
+source_root = environ.get("MESON_SOURCE_ROOT", source_root)
+chdir(source_root)
+
+# It's best not to use globs that cover everything in the project — if integration
+# tests are run with a large --repeat value, test working directory can reach
+# enormous sizes, and linters either get very slow, or start crashing.
+linters = (
+    [
+        "astyle",
+        "--recursive",
+        "--options=.astylerc",
+        "--dry-run" if DRY else "--formatted",
+        "./*.c",
+        "./*.h",
+    ],
+    ["shfmt", "-d" if DRY else "-w", "-i", "2", "-s", "."],
+    ["black", "--check" if DRY else ".", "."],
+    ["pylint", "."],
+    ["mypy", "--exclude", "build", "."],
+    ["shellcheck", "-x", *glob(".ci/**/*.sh", recursive=True)],
+)
+
+failed: bool = False
+
+for cmd in linters:
+    exe = cmd[0]
+    print(f"{HEADER} Running linter '{exe}' {HEADER}")
+
+    try:
+        res = subp.run(
+            cmd,
+            check=False,
+            stdout=subp.PIPE,
+            encoding="utf-8",
+        )
+        failed = (
+            failed
+            or bool(res.returncode)
+            or (exe == "astyle" and "Formatted  " in res.stdout)
+        )
+        print(res.stdout)
+    except FileNotFoundError as e:
+        print(f"Warning: linter {exe} is missing", file=sys.stderr)
+
+sys.exit(int(failed))