diff --git a/tools/precommit/Dockerfile b/tools/precommit/Dockerfile index f886074270..0d5b312e6a 100644 --- a/tools/precommit/Dockerfile +++ b/tools/precommit/Dockerfile @@ -3,13 +3,14 @@ FROM ubuntu:24.04 # author: Ole Schuett WORKDIR /opt/cp2k-precommit -COPY . /opt/cp2k-precommit/ +COPY install_requirements.sh requirements.txt ./ RUN ./install_requirements.sh ENV PATH="/opt/venv/bin:/opt/cp2k-precommit:$PATH" ARG REVISION ENV REVISION=${REVISION} +COPY . ./ CMD ["gunicorn", "--bind=:8080", "--workers=1", "--threads=8", "--timeout=0", "precommit_server:app"] #EOF diff --git a/tools/precommit/fortitude.toml b/tools/precommit/fortitude.toml new file mode 100644 index 0000000000..74de898c3b --- /dev/null +++ b/tools/precommit/fortitude.toml @@ -0,0 +1,40 @@ +[check] +line-length = 132 + +# Also see https://fortitude.readthedocs.io/en/stable/rules +ignore = [ + "non-standard-file-extension", + "old-style-array-literal", + "bad-quote-string", + "multiple-allocations-with-stat", + "missing-intent", + "misleading-inline-if-continuation", + "deprecated-relational-operator", + "missing-default-case", + "implicit-external-procedures", + "keywords-missing-space", + "superfluous-semicolon", + "missing-exit-or-cycle-label", + "assumed-size", + "unnamed-end-statement", + "unchecked-stat", + "assumed-size-character-intent", + "external-procedure", + "literal-kind", + "trailing-whitespace", + "specific-name", + "missing-default-pointer-initalisation", + "interface-implicit-typing", + "misleading-inline-if-semicolon", + "procedure-not-in-module", + "keyword-has-whitespace", + "implicit-typing", + "trailing-backslash", + "missing-accessibility-statement", + "line-too-long", + "initialisation-in-declaration", + "pointer-initialisation-in-declaration", + "nonportable-shortcircuit-inquiry", +] + +#EOF diff --git a/tools/precommit/precommit.py b/tools/precommit/precommit.py index bfad5ab4c5..b2c51a63a5 100755 --- a/tools/precommit/precommit.py +++ b/tools/precommit/precommit.py @@ -26,6 +26,29 @@ SCRATCH_DIR = Path("./obj/precommit") CACHE_FILE = SCRATCH_DIR / "cache.json" SERVER = os.environ.get("CP2K_PRECOMMIT_SERVER", "https://precommit.cp2k.org") +# The following Fortran files can not be parsed by Fortitude. +# Typically because Fortran statements are inter-leafed with pre-processor macros. +FORTITUDE_EXCLUDE = [ + "machine.F", + "fftw3_lib.F", + "pw_methods.F", + "local_gemm_api.F", + "cp_fm_diag.F", + "cp_fm_cholesky.F", + "cp_fm_basic_linalg.F", + "cp_cfm_diag.F", + "cp_cfm_cholesky.F", + "cp_cfm_basic_linalg.F", + "dbt_split.F", + "dbt_tas_util.F", + "dbt_array_list_methods.F", + "xc_libxc_wrap.F", + "ai_contraction_sphi.F", + "smeagol_control_types.F", + "message_passing.F", # fypp output too large + "eri_mme_lattice_summation.F", # fypp output too large +] + # ====================================================================================== def main() -> None: @@ -203,14 +226,21 @@ def print_box(fn: str, message: str) -> None: # ====================================================================================== def process_file(fn: str, allow_modifications: bool) -> None: # Make a backup copy. + basename = Path(fn).name orig_content = Path(fn).read_bytes() - bak_fn = SCRATCH_DIR / f"{Path(fn).name}_{time()}.bak" + bak_fn = SCRATCH_DIR / f"{basename}_{time()}.bak" shutil.copy2(fn, bak_fn) if re.match(r".*\.(F|fypp)$", fn): run_local_tool("./tools/doxify/doxify.sh", fn) run_format_fortran(fn) + if re.match(r".*\.F$", fn) and basename not in FORTITUDE_EXCLUDE: + fypped_fn = SCRATCH_DIR / basename + run_local_tool("./tools/build_utils/fypp", fn, str(fypped_fn)) + run_remote_tool("fortitude", str(fypped_fn)) + fypped_fn.unlink() + if re.match(r".*\.(c|cu|cl|h)$", fn): run_remote_tool("clangformat", fn) diff --git a/tools/precommit/precommit_server.py b/tools/precommit/precommit_server.py index 13f3a10488..a1ff64163a 100644 --- a/tools/precommit/precommit_server.py +++ b/tools/precommit/precommit_server.py @@ -64,11 +64,18 @@ def clangformat(): # ====================================================================================== @app.route("/cmakeformat", methods=["POST"]) def cmakeformat(): - return run_tool(["cmake-format", "-i"]) + return run_tool(["cmake-format", "-i"], timeout=30) # ====================================================================================== -def run_tool(cmd, timeout=30): +@app.route("/fortitude", methods=["POST"]) +def fortitude(): + config = f"--config-file={os.getcwd()}/fortitude.toml" + return run_tool(["fortitude", config, "check"]) + + +# ====================================================================================== +def run_tool(cmd, timeout=3): assert len(request.files) == 1 orig_fn = list(request.files.keys())[0] data_before = request.files[orig_fn].read() @@ -88,7 +95,6 @@ def run_tool(cmd, timeout=30): return f"Timeout while running {cmd[0]} - please try again.", 504 t2 = time() app.logger.info(f"Ran {cmd[0]} on {data_kb:.1f}KB in {t2-t1:.1f}s.") - if p.returncode != 0: return p.stdout, 422 # Unprocessable Entity data_after = open(abs_fn, "rb").read() diff --git a/tools/precommit/requirements.txt b/tools/precommit/requirements.txt index c799fbe5dd..027386f0ef 100644 --- a/tools/precommit/requirements.txt +++ b/tools/precommit/requirements.txt @@ -4,6 +4,7 @@ click==8.1.7 cmake-format==0.6.13 cmakelang==0.6.13 Flask==3.0.0 +fortitude-lint==0.7.5 gunicorn==23.0.0 itsdangerous==2.1.2 Jinja2==3.1.6