From 0fbd0d767dc4a72faff13370fb4c3d269718b493 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Thu, 15 Jan 2026 18:12:49 +0000 Subject: [PATCH 01/14] added fortitude to test suite and a rule --- applications/lfricinputs/fortitude.toml | 13 +++ .../file/fortitude.toml | 13 +++ .../app/check_fortitude_linter/rose-app.conf | 2 + rose-stem/bin/fortitude_launcher.py | 86 +++++++++++++++++++ rose-stem/site/azngarch/groups.cylc | 1 + rose-stem/site/meto/groups.cylc | 1 + rose-stem/site/nci/groups.cylc | 1 + rose-stem/site/uoe/groups.cylc | 1 + .../graph/populate_graph_scripts.cylc | 1 + .../runtime/generate_runtime_scripts.cylc | 5 ++ 10 files changed, 124 insertions(+) create mode 100644 applications/lfricinputs/fortitude.toml create mode 100644 rose-stem/app/check_fortitude_linter/file/fortitude.toml create mode 100644 rose-stem/app/check_fortitude_linter/rose-app.conf create mode 100755 rose-stem/bin/fortitude_launcher.py diff --git a/applications/lfricinputs/fortitude.toml b/applications/lfricinputs/fortitude.toml new file mode 100644 index 000000000..70f1ed15c --- /dev/null +++ b/applications/lfricinputs/fortitude.toml @@ -0,0 +1,13 @@ +############################################################################## +# (c) Crown copyright 2026 Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +############################################################################## + +[check] + +file-extensions= ["f90", "F90", "X90", "x90", "pf"] #check these file types + +select = ["S101"] + +output-format = "grouped" #group results by file diff --git a/rose-stem/app/check_fortitude_linter/file/fortitude.toml b/rose-stem/app/check_fortitude_linter/file/fortitude.toml new file mode 100644 index 000000000..70f1ed15c --- /dev/null +++ b/rose-stem/app/check_fortitude_linter/file/fortitude.toml @@ -0,0 +1,13 @@ +############################################################################## +# (c) Crown copyright 2026 Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +############################################################################## + +[check] + +file-extensions= ["f90", "F90", "X90", "x90", "pf"] #check these file types + +select = ["S101"] + +output-format = "grouped" #group results by file diff --git a/rose-stem/app/check_fortitude_linter/rose-app.conf b/rose-stem/app/check_fortitude_linter/rose-app.conf new file mode 100644 index 000000000..452d4c196 --- /dev/null +++ b/rose-stem/app/check_fortitude_linter/rose-app.conf @@ -0,0 +1,2 @@ +[command] +default=$CYLC_WORKFLOW_RUN_DIR/bin/fortitude_launcher.py -s $SOURCE_ROOT/lfric_apps diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py new file mode 100755 index 000000000..bf202c819 --- /dev/null +++ b/rose-stem/bin/fortitude_launcher.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +############################################################################## +# (c) Crown copyright 2026 Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +############################################################################## +""" +Launch stylist on list of directories. Run on all and print outputs. Fail if any +style changes required. +""" + +import sys +import os +import subprocess +import argparse + + +def launch_fortitude(config_path, app_path): + """ + Launch fortitude as a subprocess command and check the output + """ + + command = f"fortitude --config-file {config_path} check {app_path}" + result = subprocess.run(command.split(), capture_output=True, text=True) + + print(result.stdout) + return result + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Run fortitude on all applications. If " + "application/fortitude.toml exists use that file, otherwise " + "use one in rose-stem/app/check_fortitude_linter/file. " + "Print output, raise error if any changes required." + ) + parser.add_argument( + "-s", + "--source", + help="The top level of lfric_apps directory.", + required=True, + ) + args = parser.parse_args() + + failed_apps = {} + + for top_dir in ["applications", "science"]: + applications = os.listdir(os.path.join(args.source, top_dir)) + for app in applications: + print(f"Running on {app}\n") + app_path = os.path.join(args.source, top_dir, app) + config_path = os.path.join(app_path,"fortitude.toml") + if not os.path.exists(os.path.join(config_path)): + print('''Using universal config (toml) file. + (Some apps use their own config file.)''') + config_path = os.path.join( + args.source, + "rose-stem", + "app", + "check_fortitude_linter", + "file", + "fortitude.toml", + ) + result = launch_fortitude(config_path, app_path) + if result.returncode: + #prints the app run on if there are errors of any kind + print(f"Checking: {app} \n", file=sys.stderr) + if not result.stderr: + # prints if no other/config errors are found + print("Found lint errors:", file=sys.stderr) + #prints the lint errors + print(result.stdout, file=sys.stderr) + if result.stderr: + #prints if there are other/config errors + print("Found non-lint errors: \n", file=sys.stderr) + #prints the other/config errors + print(result.stderr, "\n\n\n", file=sys.stderr) + failed_apps[app] = result.stderr + + if failed_apps: + error_message = "" + print('''\n\n\nSummary: Fortitude found errors in + the following repositories:\n''', file=sys.stderr) + for failed in failed_apps: + error_message += f"{failed}\n" + sys.exit(error_message) diff --git a/rose-stem/site/azngarch/groups.cylc b/rose-stem/site/azngarch/groups.cylc index dfd4c44d9..6a861533a 100644 --- a/rose-stem/site/azngarch/groups.cylc +++ b/rose-stem/site/azngarch/groups.cylc @@ -9,6 +9,7 @@ "scripts": [ "config_dump_checker", "style_checker", + "fortitude_linter", "extract_checker", "site_validator", "validate_rose_meta", diff --git a/rose-stem/site/meto/groups.cylc b/rose-stem/site/meto/groups.cylc index fa3a62574..2e3efc1cf 100644 --- a/rose-stem/site/meto/groups.cylc +++ b/rose-stem/site/meto/groups.cylc @@ -9,6 +9,7 @@ "scripts": [ "config_dump_checker", "style_checker", + "fortitude_linter", "extract_checker", "site_validator", "validate_rose_meta", diff --git a/rose-stem/site/nci/groups.cylc b/rose-stem/site/nci/groups.cylc index f3f6e5457..da9f508df 100644 --- a/rose-stem/site/nci/groups.cylc +++ b/rose-stem/site/nci/groups.cylc @@ -22,6 +22,7 @@ "scripts": [ "config_dump_checker", "style_checker", + "fortitude_linter", "extract_checker", "site_validator", "validate_rose_meta", diff --git a/rose-stem/site/uoe/groups.cylc b/rose-stem/site/uoe/groups.cylc index f0c011f3b..40b68874b 100644 --- a/rose-stem/site/uoe/groups.cylc +++ b/rose-stem/site/uoe/groups.cylc @@ -10,6 +10,7 @@ "scripts": [ "config_dump_checker", "style_checker", + "fortitude_linter", "extract_checker", "site_validator", ], diff --git a/rose-stem/templates/graph/populate_graph_scripts.cylc b/rose-stem/templates/graph/populate_graph_scripts.cylc index 41c90a45c..1f70d9baf 100644 --- a/rose-stem/templates/graph/populate_graph_scripts.cylc +++ b/rose-stem/templates/graph/populate_graph_scripts.cylc @@ -51,6 +51,7 @@ {# All other scripts have the same simple graph. These are: #} {# * config_dump_checker #} {# * style_checker #} + {# * fortitude_linter #} {# * site_validator #} {# * rose-stem_lint_checker #} {# * validate_rose_meta #} diff --git a/rose-stem/templates/runtime/generate_runtime_scripts.cylc b/rose-stem/templates/runtime/generate_runtime_scripts.cylc index bd602ad7c..a1ce6f61a 100644 --- a/rose-stem/templates/runtime/generate_runtime_scripts.cylc +++ b/rose-stem/templates/runtime/generate_runtime_scripts.cylc @@ -43,6 +43,11 @@ inherit={{inherit.str|upper}} script="rose task-run --app-key=check_style" +{% elif "fortitude_linter" in task %} + + inherit={{inherit.str|upper}} + script="rose task-run --app-key=check_fortitude_linter" + {% elif "extract_checker" in task %} inherit={{inherit.str|upper}} From 2520a916f39e78da04402cd11e515043a9be7f00 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Thu, 15 Jan 2026 18:21:49 +0000 Subject: [PATCH 02/14] updated copyrights --- applications/lfricinputs/fortitude.toml | 2 +- rose-stem/app/check_fortitude_linter/file/fortitude.toml | 2 +- rose-stem/bin/fortitude_launcher.py | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/applications/lfricinputs/fortitude.toml b/applications/lfricinputs/fortitude.toml index 70f1ed15c..c4e5e9ab7 100644 --- a/applications/lfricinputs/fortitude.toml +++ b/applications/lfricinputs/fortitude.toml @@ -1,5 +1,5 @@ ############################################################################## -# (c) Crown copyright 2026 Met Office. All rights reserved. +# (c) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. ############################################################################## diff --git a/rose-stem/app/check_fortitude_linter/file/fortitude.toml b/rose-stem/app/check_fortitude_linter/file/fortitude.toml index 70f1ed15c..c4e5e9ab7 100644 --- a/rose-stem/app/check_fortitude_linter/file/fortitude.toml +++ b/rose-stem/app/check_fortitude_linter/file/fortitude.toml @@ -1,5 +1,5 @@ ############################################################################## -# (c) Crown copyright 2026 Met Office. All rights reserved. +# (c) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. ############################################################################## diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py index bf202c819..354153245 100755 --- a/rose-stem/bin/fortitude_launcher.py +++ b/rose-stem/bin/fortitude_launcher.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 ############################################################################## -# (c) Crown copyright 2026 Met Office. All rights reserved. +# (c) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. ############################################################################## + """ -Launch stylist on list of directories. Run on all and print outputs. Fail if any +Launch fortitude on list of directories. Run on all and print outputs. Fail if any style changes required. """ From 025a473185ebe798ef20f5bce0725311c7f59b1f Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Thu, 15 Jan 2026 18:29:13 +0000 Subject: [PATCH 03/14] updated to fix PEP8 violations --- rose-stem/bin/fortitude_launcher.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py index 354153245..6a076f386 100755 --- a/rose-stem/bin/fortitude_launcher.py +++ b/rose-stem/bin/fortitude_launcher.py @@ -6,8 +6,8 @@ ############################################################################## """ -Launch fortitude on list of directories. Run on all and print outputs. Fail if any -style changes required. +Launch fortitude on list of directories. Run on all and print outputs. +Fail if any style changes required. """ import sys @@ -23,7 +23,7 @@ def launch_fortitude(config_path, app_path): command = f"fortitude --config-file {config_path} check {app_path}" result = subprocess.run(command.split(), capture_output=True, text=True) - + print(result.stdout) return result @@ -50,9 +50,9 @@ def launch_fortitude(config_path, app_path): for app in applications: print(f"Running on {app}\n") app_path = os.path.join(args.source, top_dir, app) - config_path = os.path.join(app_path,"fortitude.toml") + config_path = os.path.join(app_path, "fortitude.toml") if not os.path.exists(os.path.join(config_path)): - print('''Using universal config (toml) file. + print('''Using universal config (toml) file. (Some apps use their own config file.)''') config_path = os.path.join( args.source, @@ -64,23 +64,23 @@ def launch_fortitude(config_path, app_path): ) result = launch_fortitude(config_path, app_path) if result.returncode: - #prints the app run on if there are errors of any kind + # prints the app run on if there are errors of any kind print(f"Checking: {app} \n", file=sys.stderr) if not result.stderr: # prints if no other/config errors are found print("Found lint errors:", file=sys.stderr) - #prints the lint errors + # prints the lint errors print(result.stdout, file=sys.stderr) if result.stderr: - #prints if there are other/config errors + # prints if there are other/config errors print("Found non-lint errors: \n", file=sys.stderr) - #prints the other/config errors + # prints the other/config errors print(result.stderr, "\n\n\n", file=sys.stderr) failed_apps[app] = result.stderr if failed_apps: error_message = "" - print('''\n\n\nSummary: Fortitude found errors in + print('''\n\n\nSummary: Fortitude found errors in the following repositories:\n''', file=sys.stderr) for failed in failed_apps: error_message += f"{failed}\n" From 25ecd7e75529ef534ea8701085ef694b202a9e72 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Thu, 15 Jan 2026 18:31:43 +0000 Subject: [PATCH 04/14] signed contributors page --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1954477ef..f40d90edb 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -10,3 +10,5 @@ | oakleybrunt | Oakley Brunt | Met Office | 2025-12-19 | | harry-shepherd | Harry Shepherd | Met Office | 2026-01-08 | | DrTVockerodtMO | Terence Vockerodt | Met Office | 2026-01-08 | +| mo-lucy-gordon | Lucy Gordon | Met Office | 2026-01-14 | + From ecaf676d035c26d68c9266a8e7a6b780821d8f00 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Fri, 16 Jan 2026 12:04:41 +0000 Subject: [PATCH 05/14] corrected message formatting --- rose-stem/bin/fortitude_launcher.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py index 6a076f386..3acca0a2b 100755 --- a/rose-stem/bin/fortitude_launcher.py +++ b/rose-stem/bin/fortitude_launcher.py @@ -52,8 +52,8 @@ def launch_fortitude(config_path, app_path): app_path = os.path.join(args.source, top_dir, app) config_path = os.path.join(app_path, "fortitude.toml") if not os.path.exists(os.path.join(config_path)): - print('''Using universal config (toml) file. - (Some apps use their own config file.)''') + print("Using universal config (toml) file." + " (Some apps use their own config file.)") config_path = os.path.join( args.source, "rose-stem", @@ -80,8 +80,8 @@ def launch_fortitude(config_path, app_path): if failed_apps: error_message = "" - print('''\n\n\nSummary: Fortitude found errors in - the following repositories:\n''', file=sys.stderr) + print("\n\n\nSummary: Fortitude found errors in" + " the following repositories:\n", file=sys.stderr) for failed in failed_apps: error_message += f"{failed}\n" sys.exit(error_message) From b943adf62884d66c30ce59ea3fb643296c2dae71 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Tue, 3 Feb 2026 15:44:58 +0000 Subject: [PATCH 06/14] removed test from other sites --- rose-stem/site/azngarch/groups.cylc | 1 - rose-stem/site/nci/groups.cylc | 1 - rose-stem/site/uoe/groups.cylc | 1 - 3 files changed, 3 deletions(-) diff --git a/rose-stem/site/azngarch/groups.cylc b/rose-stem/site/azngarch/groups.cylc index 6a861533a..dfd4c44d9 100644 --- a/rose-stem/site/azngarch/groups.cylc +++ b/rose-stem/site/azngarch/groups.cylc @@ -9,7 +9,6 @@ "scripts": [ "config_dump_checker", "style_checker", - "fortitude_linter", "extract_checker", "site_validator", "validate_rose_meta", diff --git a/rose-stem/site/nci/groups.cylc b/rose-stem/site/nci/groups.cylc index da9f508df..f3f6e5457 100644 --- a/rose-stem/site/nci/groups.cylc +++ b/rose-stem/site/nci/groups.cylc @@ -22,7 +22,6 @@ "scripts": [ "config_dump_checker", "style_checker", - "fortitude_linter", "extract_checker", "site_validator", "validate_rose_meta", diff --git a/rose-stem/site/uoe/groups.cylc b/rose-stem/site/uoe/groups.cylc index 40b68874b..f0c011f3b 100644 --- a/rose-stem/site/uoe/groups.cylc +++ b/rose-stem/site/uoe/groups.cylc @@ -10,7 +10,6 @@ "scripts": [ "config_dump_checker", "style_checker", - "fortitude_linter", "extract_checker", "site_validator", ], From 6d3ec8e0fd474981e5d17a515420c133a8da2206 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Tue, 3 Feb 2026 16:34:13 +0000 Subject: [PATCH 07/14] changed source argument type --- rose-stem/app/check_fortitude_linter/rose-app.conf | 2 +- rose-stem/bin/fortitude_launcher.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/rose-stem/app/check_fortitude_linter/rose-app.conf b/rose-stem/app/check_fortitude_linter/rose-app.conf index 452d4c196..456659c78 100644 --- a/rose-stem/app/check_fortitude_linter/rose-app.conf +++ b/rose-stem/app/check_fortitude_linter/rose-app.conf @@ -1,2 +1,2 @@ [command] -default=$CYLC_WORKFLOW_RUN_DIR/bin/fortitude_launcher.py -s $SOURCE_ROOT/lfric_apps +default=$CYLC_WORKFLOW_RUN_DIR/bin/fortitude_launcher.py $SOURCE_ROOT/lfric_apps diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py index 3acca0a2b..90ed8b006 100755 --- a/rose-stem/bin/fortitude_launcher.py +++ b/rose-stem/bin/fortitude_launcher.py @@ -36,10 +36,8 @@ def launch_fortitude(config_path, app_path): "Print output, raise error if any changes required." ) parser.add_argument( - "-s", - "--source", - help="The top level of lfric_apps directory.", - required=True, + "source", + help="The top level of lfric_apps directory." ) args = parser.parse_args() From 39919764bf9951b35d47854b17e66e22f3b2618c Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Thu, 5 Feb 2026 09:51:42 +0000 Subject: [PATCH 08/14] added pathlib, typehinting and made script generic --- rose-stem/bin/fortitude_launcher.py | 49 +++++++++++-------- .../runtime/generate_runtime_scripts.cylc | 2 + 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py index 90ed8b006..c52386ee0 100755 --- a/rose-stem/bin/fortitude_launcher.py +++ b/rose-stem/bin/fortitude_launcher.py @@ -14,15 +14,16 @@ import os import subprocess import argparse +from pathlib import Path -def launch_fortitude(config_path, app_path): +def launch_fortitude(config_path: Path, app_path: Path) -> subprocess.CompletedProcess[str]: """ Launch fortitude as a subprocess command and check the output """ - command = f"fortitude --config-file {config_path} check {app_path}" - result = subprocess.run(command.split(), capture_output=True, text=True) + command: list[str] = ["fortitude", "--config-file", str(config_path), "check", str(app_path)] + result = subprocess.run(command, capture_output=True, text=True) print(result.stdout) return result @@ -41,26 +42,32 @@ def launch_fortitude(config_path, app_path): ) args = parser.parse_args() - failed_apps = {} + source_path: Path = Path(args.source) + print(source_path) #remove - for top_dir in ["applications", "science"]: - applications = os.listdir(os.path.join(args.source, top_dir)) + subdirs_env: str = os.environ.get("FORTITUDE_SUBDIRS") + subdirs: list[str] = subdirs_env.split(",") + + failed_apps: dict[str, str] = {} + + #for top_dir in ["applications", "science"]: #remove + for top_dir in subdirs: + top_level_path: Path = source_path/top_dir + print(top_level_path) #remove + applications: list[Path] = list(top_level_path.iterdir()) + print(applications) #remove for app in applications: - print(f"Running on {app}\n") - app_path = os.path.join(args.source, top_dir, app) - config_path = os.path.join(app_path, "fortitude.toml") - if not os.path.exists(os.path.join(config_path)): + app_name: str = app.name + print(f"Running on {app_name}\n") + app_path: Path = app + config_path: Path = app_path/"fortitude.toml" + print(app_path) #remove + print(config_path) #remove + if not config_path.exists(): print("Using universal config (toml) file." " (Some apps use their own config file.)") - config_path = os.path.join( - args.source, - "rose-stem", - "app", - "check_fortitude_linter", - "file", - "fortitude.toml", - ) - result = launch_fortitude(config_path, app_path) + config_path: Path = (source_path / "rose-stem" / "app" / "check_fortitude_linter" / "file" / "fortitude.toml") + result: subprocess.CompletedProcess[str] = launch_fortitude(config_path, app_path) if result.returncode: # prints the app run on if there are errors of any kind print(f"Checking: {app} \n", file=sys.stderr) @@ -74,10 +81,10 @@ def launch_fortitude(config_path, app_path): print("Found non-lint errors: \n", file=sys.stderr) # prints the other/config errors print(result.stderr, "\n\n\n", file=sys.stderr) - failed_apps[app] = result.stderr + failed_apps[app_name] = result.stderr if failed_apps: - error_message = "" + error_message: str = "" print("\n\n\nSummary: Fortitude found errors in" " the following repositories:\n", file=sys.stderr) for failed in failed_apps: diff --git a/rose-stem/templates/runtime/generate_runtime_scripts.cylc b/rose-stem/templates/runtime/generate_runtime_scripts.cylc index a1ce6f61a..86df5adc1 100644 --- a/rose-stem/templates/runtime/generate_runtime_scripts.cylc +++ b/rose-stem/templates/runtime/generate_runtime_scripts.cylc @@ -47,6 +47,8 @@ inherit={{inherit.str|upper}} script="rose task-run --app-key=check_fortitude_linter" + [[[environment]]] + FORTITUDE_SUBDIRS = applications,science {% elif "extract_checker" in task %} From 9757ab271afd20936dcb1e12099ef47547825e1a Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Wed, 11 Mar 2026 16:32:48 +0000 Subject: [PATCH 09/14] added rules and config files to remove all errors but still test interfaces --- applications/lfricinputs/fortitude.toml | 8 ++--- .../file/fortitude.toml | 18 +++++++++- rose-stem/bin/fortitude_launcher.py | 36 ++++++++----------- .../runtime/generate_runtime_scripts.cylc | 2 -- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/applications/lfricinputs/fortitude.toml b/applications/lfricinputs/fortitude.toml index c4e5e9ab7..399f4feb4 100644 --- a/applications/lfricinputs/fortitude.toml +++ b/applications/lfricinputs/fortitude.toml @@ -5,9 +5,7 @@ ############################################################################## [check] - -file-extensions= ["f90", "F90", "X90", "x90", "pf"] #check these file types - -select = ["S101"] - +file-extensions= ["f90", "F90", "X90", "x90", "pf"] #check these file types +select = ["S101", "PORT011"] output-format = "grouped" #group results by file + diff --git a/rose-stem/app/check_fortitude_linter/file/fortitude.toml b/rose-stem/app/check_fortitude_linter/file/fortitude.toml index c4e5e9ab7..3988f89c6 100644 --- a/rose-stem/app/check_fortitude_linter/file/fortitude.toml +++ b/rose-stem/app/check_fortitude_linter/file/fortitude.toml @@ -8,6 +8,22 @@ file-extensions= ["f90", "F90", "X90", "x90", "pf"] #check these file types -select = ["S101"] +select = ["S101", "PORT011"] output-format = "grouped" #group results by file + + +# "./interfaces/coupled_interface/source/algorithm/process_send_fields_2d_mod.X90" = ["S101"] + +# "./interfaces/socrates_interface*" = ["S101"] +# "../coupled_interface/*" = ["S101"] + + +# "./interfaces/coupled_interface/* = ["S101"] + + +# "../socrates_interface/*" = ["S101"] +# "physics_schemes_interface/*" = ["S101"] +# "jules_interface/*" = ["S101"] +# "jedi_lfric_interface/*" = ["S101"] + diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py index c52386ee0..09869847c 100755 --- a/rose-stem/bin/fortitude_launcher.py +++ b/rose-stem/bin/fortitude_launcher.py @@ -11,7 +11,6 @@ """ import sys -import os import subprocess import argparse from pathlib import Path @@ -43,46 +42,39 @@ def launch_fortitude(config_path: Path, app_path: Path) -> subprocess.CompletedP args = parser.parse_args() source_path: Path = Path(args.source) - print(source_path) #remove - - subdirs_env: str = os.environ.get("FORTITUDE_SUBDIRS") - subdirs: list[str] = subdirs_env.split(",") failed_apps: dict[str, str] = {} - #for top_dir in ["applications", "science"]: #remove - for top_dir in subdirs: - top_level_path: Path = source_path/top_dir - print(top_level_path) #remove - applications: list[Path] = list(top_level_path.iterdir()) - print(applications) #remove - for app in applications: - app_name: str = app.name + for top_dir_path in source_path.iterdir(): #e.g. applications,science,interfaces + if not top_dir_path.is_dir(): # don't try to loop over files + continue + for app_path in top_dir_path.iterdir(): #e.g. adjoint_tests, adjoint, coupled_interface + if not app_path.is_dir(): + continue + app_name: str = app_path.name print(f"Running on {app_name}\n") - app_path: Path = app config_path: Path = app_path/"fortitude.toml" - print(app_path) #remove - print(config_path) #remove if not config_path.exists(): print("Using universal config (toml) file." - " (Some apps use their own config file.)") + " (Some apps use their own config file.)") config_path: Path = (source_path / "rose-stem" / "app" / "check_fortitude_linter" / "file" / "fortitude.toml") result: subprocess.CompletedProcess[str] = launch_fortitude(config_path, app_path) if result.returncode: # prints the app run on if there are errors of any kind - print(f"Checking: {app} \n", file=sys.stderr) + print(f"Checking: {app_name} \n", file=sys.stderr) if not result.stderr: # prints if no other/config errors are found - print("Found lint errors:", file=sys.stderr) + print("Found lint errors:", file=sys.stderr) # prints the lint errors - print(result.stdout, file=sys.stderr) + print(result.stdout, file=sys.stderr) if result.stderr: # prints if there are other/config errors - print("Found non-lint errors: \n", file=sys.stderr) + print("Found non-lint errors: \n", file=sys.stderr) # prints the other/config errors - print(result.stderr, "\n\n\n", file=sys.stderr) + print(result.stderr, "\n\n\n", file=sys.stderr) failed_apps[app_name] = result.stderr + if failed_apps: error_message: str = "" print("\n\n\nSummary: Fortitude found errors in" diff --git a/rose-stem/templates/runtime/generate_runtime_scripts.cylc b/rose-stem/templates/runtime/generate_runtime_scripts.cylc index 86df5adc1..a1ce6f61a 100644 --- a/rose-stem/templates/runtime/generate_runtime_scripts.cylc +++ b/rose-stem/templates/runtime/generate_runtime_scripts.cylc @@ -47,8 +47,6 @@ inherit={{inherit.str|upper}} script="rose task-run --app-key=check_fortitude_linter" - [[[environment]]] - FORTITUDE_SUBDIRS = applications,science {% elif "extract_checker" in task %} From 93bb772b925777767ebeecd5a9b5daf1e05c26ef Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Wed, 11 Mar 2026 18:20:54 +0000 Subject: [PATCH 10/14] added other toml files --- interfaces/coupled_interface/fortitude.toml | 18 +++++++++++++++++ .../jedi_lfric_interface/fortitude.toml | 18 +++++++++++++++++ interfaces/jules_interface/fortitude.toml | 20 +++++++++++++++++++ .../physics_schemes_interface/fortitude.toml | 13 ++++++++++++ interfaces/socrates_interface/fortitude.toml | 19 ++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 interfaces/coupled_interface/fortitude.toml create mode 100644 interfaces/jedi_lfric_interface/fortitude.toml create mode 100644 interfaces/jules_interface/fortitude.toml create mode 100644 interfaces/physics_schemes_interface/fortitude.toml create mode 100644 interfaces/socrates_interface/fortitude.toml diff --git a/interfaces/coupled_interface/fortitude.toml b/interfaces/coupled_interface/fortitude.toml new file mode 100644 index 000000000..d07084eca --- /dev/null +++ b/interfaces/coupled_interface/fortitude.toml @@ -0,0 +1,18 @@ +############################################################################## +# (c) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +############################################################################## + +[check] + +file-extensions= ["f90", "F90", "X90", "x90", "pf"] #check these file types + +select = ["S101", "PORT011"] + +output-format = "grouped" #group results by file + +[check.per-file-ignores] + +"process_send_fields_2d_mod.X90" = ["S101"] +"set_r_ocean_fraction_mod.F90" = ["S101"] diff --git a/interfaces/jedi_lfric_interface/fortitude.toml b/interfaces/jedi_lfric_interface/fortitude.toml new file mode 100644 index 000000000..f8d609bba --- /dev/null +++ b/interfaces/jedi_lfric_interface/fortitude.toml @@ -0,0 +1,18 @@ +############################################################################## +# (c) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +############################################################################## + +[check] + +file-extensions= ["f90", "F90", "X90", "x90", "pf"] #check these file types + +select = ["S101", "PORT011"] + +output-format = "grouped" #group results by file + +[check.per-file-ignores] + +"jedi_lfric_linear_modeldb_driver_mod.f90" = ["S101"] +"atlas_field_interface_mod.F90" = ["S101"] diff --git a/interfaces/jules_interface/fortitude.toml b/interfaces/jules_interface/fortitude.toml new file mode 100644 index 000000000..1dd4c9b7e --- /dev/null +++ b/interfaces/jules_interface/fortitude.toml @@ -0,0 +1,20 @@ +############################################################################## +# (c) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +############################################################################## + +[check] + +file-extensions= ["f90", "F90", "X90", "x90", "pf"] #check these file types + +select = ["S101", "PORT011"] + +output-format = "grouped" #group results by file + +[check.per-file-ignores] + +"update_ancils_alg_mod.x90" = ["S101"] +"jules_exp_kernel_mod.F90" = ["S101"] +"jules_extra_kernel_mod.F90" = ["S101"] +"jules_imp_kernel_mod.F90" = ["S101"] diff --git a/interfaces/physics_schemes_interface/fortitude.toml b/interfaces/physics_schemes_interface/fortitude.toml new file mode 100644 index 000000000..4848b9899 --- /dev/null +++ b/interfaces/physics_schemes_interface/fortitude.toml @@ -0,0 +1,13 @@ +############################################################################## +# (c) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +############################################################################## + +[check] + +file-extensions= ["f90", "F90", "X90", "x90", "pf"] #check these file types + +select = ["PORT011"] + +output-format = "grouped" #group results by file diff --git a/interfaces/socrates_interface/fortitude.toml b/interfaces/socrates_interface/fortitude.toml new file mode 100644 index 000000000..55535500c --- /dev/null +++ b/interfaces/socrates_interface/fortitude.toml @@ -0,0 +1,19 @@ +############################################################################## +# (c) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +############################################################################## + +[check] + +file-extensions= ["f90", "F90", "X90", "x90", "pf"] #check these file types + +select = ["S101", "PORT011"] + +output-format = "grouped" #group results by file + +[check.per-file-ignores] + +"init_radiation_fields_alg_mod.x90" = ["S101"] +"rad_cloud_alg_mod.x90" = ["S101"] +"lw_kernel_mod.F90" = ["S101"] From 4b6bcc4531f9a4ee18e18ad73b7efe3257d8e30d Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Thu, 12 Mar 2026 10:53:43 +0000 Subject: [PATCH 11/14] removed a space so rule could be used on file --- .../jules_interface/source/algorithm/jules_exp_alg_mod.x90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interfaces/jules_interface/source/algorithm/jules_exp_alg_mod.x90 b/interfaces/jules_interface/source/algorithm/jules_exp_alg_mod.x90 index ee0bb5f8d..971658188 100644 --- a/interfaces/jules_interface/source/algorithm/jules_exp_alg_mod.x90 +++ b/interfaces/jules_interface/source/algorithm/jules_exp_alg_mod.x90 @@ -229,7 +229,7 @@ contains integer(tik) :: id if ( LPROF ) call start_timing( id, 'jules.explicit' ) - + call log_event( 'slow_physics: Running explicit JULES layer', LOG_LEVEL_DEBUG ) ! Cannot pass null pointers to invoke calls. Therefore non-pointer variables From cf854d717cea31a4941211fd96c00fbf32755169 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Thu, 12 Mar 2026 13:37:12 +0000 Subject: [PATCH 12/14] adding fix for pep8 violations --- rose-stem/bin/fortitude_launcher.py | 30 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py index 09869847c..941ea5b93 100755 --- a/rose-stem/bin/fortitude_launcher.py +++ b/rose-stem/bin/fortitude_launcher.py @@ -16,12 +16,14 @@ from pathlib import Path -def launch_fortitude(config_path: Path, app_path: Path) -> subprocess.CompletedProcess[str]: +def launch_fortitude(config_path: Path, app_path: Path + ) -> subprocess.CompletedProcess[str]: """ Launch fortitude as a subprocess command and check the output """ - command: list[str] = ["fortitude", "--config-file", str(config_path), "check", str(app_path)] + command: list[str] = ["fortitude", "--config-file", str(config_path), + "check", str(app_path)] result = subprocess.run(command, capture_output=True, text=True) print(result.stdout) @@ -45,10 +47,12 @@ def launch_fortitude(config_path: Path, app_path: Path) -> subprocess.CompletedP failed_apps: dict[str, str] = {} - for top_dir_path in source_path.iterdir(): #e.g. applications,science,interfaces + for top_dir_path in source_path.iterdir(): + # e.g. applications,science,interfaces if not top_dir_path.is_dir(): # don't try to loop over files continue - for app_path in top_dir_path.iterdir(): #e.g. adjoint_tests, adjoint, coupled_interface + for app_path in top_dir_path.iterdir(): + # e.g. adjoint_tests, adjoint, coupled_interface if not app_path.is_dir(): continue app_name: str = app_path.name @@ -56,25 +60,27 @@ def launch_fortitude(config_path: Path, app_path: Path) -> subprocess.CompletedP config_path: Path = app_path/"fortitude.toml" if not config_path.exists(): print("Using universal config (toml) file." - " (Some apps use their own config file.)") - config_path: Path = (source_path / "rose-stem" / "app" / "check_fortitude_linter" / "file" / "fortitude.toml") - result: subprocess.CompletedProcess[str] = launch_fortitude(config_path, app_path) + " (Some apps use their own config file.)") + config_path: Path = ( + source_path / "rose-stem" / "app" + / "check_fortitude_linter" / "file" / "fortitude.toml") + result: subprocess.CompletedProcess[str] = launch_fortitude( + config_path, app_path) if result.returncode: # prints the app run on if there are errors of any kind print(f"Checking: {app_name} \n", file=sys.stderr) if not result.stderr: # prints if no other/config errors are found - print("Found lint errors:", file=sys.stderr) + print("Found lint errors:", file=sys.stderr) # prints the lint errors - print(result.stdout, file=sys.stderr) + print(result.stdout, file=sys.stderr) if result.stderr: # prints if there are other/config errors - print("Found non-lint errors: \n", file=sys.stderr) + print("Found non-lint errors: \n", file=sys.stderr) # prints the other/config errors - print(result.stderr, "\n\n\n", file=sys.stderr) + print(result.stderr, "\n\n\n", file=sys.stderr) failed_apps[app_name] = result.stderr - if failed_apps: error_message: str = "" print("\n\n\nSummary: Fortitude found errors in" From 5a3f1a95da9577524f1a9af0e6c819fdb493e6bd Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Fri, 13 Mar 2026 13:29:38 +0000 Subject: [PATCH 13/14] get launcher script from SimSys scripts instead --- .../app/extract_source/bin/extract_source.sh | 1 + rose-stem/bin/fortitude_launcher.py | 90 ------------------- 2 files changed, 1 insertion(+), 90 deletions(-) delete mode 100755 rose-stem/bin/fortitude_launcher.py diff --git a/rose-stem/app/extract_source/bin/extract_source.sh b/rose-stem/app/extract_source/bin/extract_source.sh index b1297f8f2..c2310ff35 100755 --- a/rose-stem/app/extract_source/bin/extract_source.sh +++ b/rose-stem/app/extract_source/bin/extract_source.sh @@ -8,3 +8,4 @@ cp $SOURCE_ROOT/SimSys_Scripts/github_scripts/suite_report_git.py $CYLC_WORKFLOW cp $SOURCE_ROOT/SimSys_Scripts/github_scripts/suite_data.py $CYLC_WORKFLOW_RUN_DIR/bin cp $SOURCE_ROOT/SimSys_Scripts/github_scripts/git_bdiff.py $CYLC_WORKFLOW_RUN_DIR/bin cp $SOURCE_DIRECTORY/SimSys_Scripts/github_scripts/get_git_sources.py $CYLC_WORKFLOW_RUN_DIR/bin +cp $SOURCE_DIRECTORY/SimSys_Scripts/fortitude_linter/fortitude_launcher.py $CYLC_WORKFLOW_RUN_DIR/bin diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py deleted file mode 100755 index 941ea5b93..000000000 --- a/rose-stem/bin/fortitude_launcher.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python3 -############################################################################## -# (c) Crown copyright Met Office. All rights reserved. -# The file LICENCE, distributed with this code, contains details of the terms -# under which the code may be used. -############################################################################## - -""" -Launch fortitude on list of directories. Run on all and print outputs. -Fail if any style changes required. -""" - -import sys -import subprocess -import argparse -from pathlib import Path - - -def launch_fortitude(config_path: Path, app_path: Path - ) -> subprocess.CompletedProcess[str]: - """ - Launch fortitude as a subprocess command and check the output - """ - - command: list[str] = ["fortitude", "--config-file", str(config_path), - "check", str(app_path)] - result = subprocess.run(command, capture_output=True, text=True) - - print(result.stdout) - return result - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Run fortitude on all applications. If " - "application/fortitude.toml exists use that file, otherwise " - "use one in rose-stem/app/check_fortitude_linter/file. " - "Print output, raise error if any changes required." - ) - parser.add_argument( - "source", - help="The top level of lfric_apps directory." - ) - args = parser.parse_args() - - source_path: Path = Path(args.source) - - failed_apps: dict[str, str] = {} - - for top_dir_path in source_path.iterdir(): - # e.g. applications,science,interfaces - if not top_dir_path.is_dir(): # don't try to loop over files - continue - for app_path in top_dir_path.iterdir(): - # e.g. adjoint_tests, adjoint, coupled_interface - if not app_path.is_dir(): - continue - app_name: str = app_path.name - print(f"Running on {app_name}\n") - config_path: Path = app_path/"fortitude.toml" - if not config_path.exists(): - print("Using universal config (toml) file." - " (Some apps use their own config file.)") - config_path: Path = ( - source_path / "rose-stem" / "app" - / "check_fortitude_linter" / "file" / "fortitude.toml") - result: subprocess.CompletedProcess[str] = launch_fortitude( - config_path, app_path) - if result.returncode: - # prints the app run on if there are errors of any kind - print(f"Checking: {app_name} \n", file=sys.stderr) - if not result.stderr: - # prints if no other/config errors are found - print("Found lint errors:", file=sys.stderr) - # prints the lint errors - print(result.stdout, file=sys.stderr) - if result.stderr: - # prints if there are other/config errors - print("Found non-lint errors: \n", file=sys.stderr) - # prints the other/config errors - print(result.stderr, "\n\n\n", file=sys.stderr) - failed_apps[app_name] = result.stderr - - if failed_apps: - error_message: str = "" - print("\n\n\nSummary: Fortitude found errors in" - " the following repositories:\n", file=sys.stderr) - for failed in failed_apps: - error_message += f"{failed}\n" - sys.exit(error_message) From b9f2b951fbb7eb1a3eb8030c5577cb172698582a Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Fri, 13 Mar 2026 14:00:07 +0000 Subject: [PATCH 14/14] corrected source for simsys scripts --- rose-stem/app/extract_source/bin/extract_source.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rose-stem/app/extract_source/bin/extract_source.sh b/rose-stem/app/extract_source/bin/extract_source.sh index c2310ff35..9bab6dd06 100755 --- a/rose-stem/app/extract_source/bin/extract_source.sh +++ b/rose-stem/app/extract_source/bin/extract_source.sh @@ -7,5 +7,5 @@ cp $SOURCE_ROOT/lfric_apps/dependencies.yaml $CYLC_WORKFLOW_RUN_DIR cp $SOURCE_ROOT/SimSys_Scripts/github_scripts/suite_report_git.py $CYLC_WORKFLOW_RUN_DIR/bin cp $SOURCE_ROOT/SimSys_Scripts/github_scripts/suite_data.py $CYLC_WORKFLOW_RUN_DIR/bin cp $SOURCE_ROOT/SimSys_Scripts/github_scripts/git_bdiff.py $CYLC_WORKFLOW_RUN_DIR/bin +cp $SOURCE_ROOT/SimSys_Scripts/fortitude_linter/fortitude_launcher.py $CYLC_WORKFLOW_RUN_DIR/bin cp $SOURCE_DIRECTORY/SimSys_Scripts/github_scripts/get_git_sources.py $CYLC_WORKFLOW_RUN_DIR/bin -cp $SOURCE_DIRECTORY/SimSys_Scripts/fortitude_linter/fortitude_launcher.py $CYLC_WORKFLOW_RUN_DIR/bin