From be0dce37718223dc5ebead2cb71bb6586d08a79f Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Fri, 9 Jan 2026 17:41:59 +0000 Subject: [PATCH 1/9] added fortitude test and configuration --- rose-stem/site/meto/groups.cylc | 1 + rose-stem/templates/graph/populate_graph_scripts.cylc | 1 + rose-stem/templates/runtime/generate_runtime_scripts.cylc | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/rose-stem/site/meto/groups.cylc b/rose-stem/site/meto/groups.cylc index 2452ddd76..62646ad80 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", "site_validator", "validate_rose_meta", "rose-stem_lint_checker", diff --git a/rose-stem/templates/graph/populate_graph_scripts.cylc b/rose-stem/templates/graph/populate_graph_scripts.cylc index 25e0eeda1..2aa328729 100644 --- a/rose-stem/templates/graph/populate_graph_scripts.cylc +++ b/rose-stem/templates/graph/populate_graph_scripts.cylc @@ -51,6 +51,7 @@ {# * config_dump_checker #} {# * style_checker #} {# * site_validator #} + {# * fortitude_linter #} {# * rose-stem_lint_checker #} {# * validate_rose_meta #} {# * global_variables_checker #} diff --git a/rose-stem/templates/runtime/generate_runtime_scripts.cylc b/rose-stem/templates/runtime/generate_runtime_scripts.cylc index 02faaf7b2..3c501f06f 100644 --- a/rose-stem/templates/runtime/generate_runtime_scripts.cylc +++ b/rose-stem/templates/runtime/generate_runtime_scripts.cylc @@ -36,6 +36,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 7a4cb6c9be090eb91461dd2a17a0614dd35b98d4 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Fri, 9 Jan 2026 17:42:55 +0000 Subject: [PATCH 2/9] added new files --- infrastructure/fortitude.toml | 14 +++ .../file/fortitude.toml | 15 ++++ rose-stem/bin/fortitude_launcher.py | 88 +++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 infrastructure/fortitude.toml create mode 100644 rose-stem/app/check_fortitude_linter/file/fortitude.toml create mode 100644 rose-stem/bin/fortitude_launcher.py diff --git a/infrastructure/fortitude.toml b/infrastructure/fortitude.toml new file mode 100644 index 000000000..e3552c8e5 --- /dev/null +++ b/infrastructure/fortitude.toml @@ -0,0 +1,14 @@ +############################################################################## +# (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"] #check these file types + + +select = ["C121"] + +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..02708f2b3 --- /dev/null +++ b/rose-stem/app/check_fortitude_linter/file/fortitude.toml @@ -0,0 +1,15 @@ +############################################################################## +# (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"] #check these file types + + +select = ["C121"] + + +output-format = "grouped" #group results by file diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py new file mode 100644 index 000000000..188746e15 --- /dev/null +++ b/rose-stem/bin/fortitude_launcher.py @@ -0,0 +1,88 @@ +#!/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 fortitude on list of directories. Run on all and print outputs. Fail if +any style changes required. +""" + +import argparse +import os +import subprocess +import sys + +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_core directory.", + required=True, + ) + args = parser.parse_args() + + failed_apps = {} + + candidates = [ + "infrastructure", + "mesh_tools", + "components/coupling", + "components/driver", + "components/science", + "components/inventory", + "components/lfric-xios", + "applications/skeleton", + "applications/simple_diffusion", + "applications/io_demo", + ] + for app in candidates: + print(f"Running on {app}\n") + app_path = os.path.join(args.source, 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: + print(f"Checking: {app} \n", file=sys.stderr) #prints the app run on if there are errors of any kind + if not result.stderr: + print("Found lint errors:", file=sys.stderr)# prints if no other/config errors are found + print(result.stdout, file=sys.stderr) #prints the lint errors + if result.stderr: + print("Found non-lint errors: \n", file=sys.stderr) #prints if there are other/config errors + print(result.stderr, "\n\n\n", file=sys.stderr) #prints the other/config error + 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) From 4dae812ef9e34ac30ab376c5e4486598e434461a Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Mon, 12 Jan 2026 17:54:50 +0000 Subject: [PATCH 3/9] added launch command --- rose-stem/app/check_fortitude_linter/rose-app.conf | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 rose-stem/app/check_fortitude_linter/rose-app.conf 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..c2ced2a12 --- /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 From e3b02060143153fe8997208b499bf1f1b2abcb60 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Tue, 13 Jan 2026 17:36:25 +0000 Subject: [PATCH 4/9] updated permissions on launch file --- rose-stem/bin/fortitude_launcher.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 rose-stem/bin/fortitude_launcher.py diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py old mode 100644 new mode 100755 From 5b86c63693a98fc0821c647f1fca54aa216637a1 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Tue, 13 Jan 2026 18:27:53 +0000 Subject: [PATCH 5/9] updated source --- rose-stem/app/check_fortitude_linter/rose-app.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rose-stem/app/check_fortitude_linter/rose-app.conf b/rose-stem/app/check_fortitude_linter/rose-app.conf index c2ced2a12..e20cad3bb 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 +default=$CYLC_WORKFLOW_RUN_DIR/bin/fortitude_launcher.py -s $SOURCE_ROOT/lfric_core From 7b33f39654d90aa236117f5962bb1f2febde8155 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Wed, 14 Jan 2026 17:19:19 +0000 Subject: [PATCH 6/9] vupdating copyright --- infrastructure/fortitude.toml | 2 +- rose-stem/app/check_fortitude_linter/file/fortitude.toml | 2 +- rose-stem/bin/fortitude_launcher.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/fortitude.toml b/infrastructure/fortitude.toml index e3552c8e5..2019891b4 100644 --- a/infrastructure/fortitude.toml +++ b/infrastructure/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 02708f2b3..577dcd310 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 188746e15..7cdeb6fae 100755 --- a/rose-stem/bin/fortitude_launcher.py +++ b/rose-stem/bin/fortitude_launcher.py @@ -1,6 +1,6 @@ #!/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. ############################################################################## From 329abfacc387431282e03a57854349642bc4d588 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Wed, 14 Jan 2026 17:51:50 +0000 Subject: [PATCH 7/9] fixing pep8 violations --- rose-stem/bin/fortitude_launcher.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/rose-stem/bin/fortitude_launcher.py b/rose-stem/bin/fortitude_launcher.py index 7cdeb6fae..ce44a464b 100755 --- a/rose-stem/bin/fortitude_launcher.py +++ b/rose-stem/bin/fortitude_launcher.py @@ -15,6 +15,7 @@ import subprocess import sys + def launch_fortitude(config_path, app_path): """ Launch fortitude as a subprocess command and check the output @@ -26,6 +27,7 @@ def launch_fortitude(config_path, app_path): print(result.stdout) return result + if __name__ == "__main__": parser = argparse.ArgumentParser( description="Run fortitude on all applications. If " @@ -60,7 +62,8 @@ def launch_fortitude(config_path, app_path): app_path = os.path.join(args.source, 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", @@ -69,20 +72,27 @@ def launch_fortitude(config_path, app_path): "file", "fortitude.toml", ) + result = launch_fortitude(config_path, app_path) if result.returncode: - print(f"Checking: {app} \n", file=sys.stderr) #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: - print("Found lint errors:", file=sys.stderr)# prints if no other/config errors are found - print(result.stdout, file=sys.stderr) #prints the lint errors + # 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: - print("Found non-lint errors: \n", file=sys.stderr) #prints if there are other/config errors - print(result.stderr, "\n\n\n", file=sys.stderr) #prints the other/config error + # prints if there are other/config errors + print("Found non-lint errors: \n", file=sys.stderr) + # prints the other/config error + 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) + 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 ae23ff80c2192ea8323e2ece78d2191fda06a24a Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Wed, 14 Jan 2026 18:19:51 +0000 Subject: [PATCH 8/9] signing contributor page --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1e79a1045..9a8006ef3 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -11,4 +11,5 @@ | stevemullerworth | Steve Mullerworth | Met Office | 2026-01-08 | | harry-shepherd | Harry Shepherd | Met Office | 2026-01-08 | | EdHone | Ed Hone | Met Office | 2026-01-09 | +| mo-lucy-gordon | Lucy Gordon | Met Office | 2026-01-14 | From 9191270ec7e3e14c28965360b211ba1d2f68d317 Mon Sep 17 00:00:00 2001 From: mo-lucy-gordon Date: Fri, 16 Jan 2026 10:51:57 +0000 Subject: [PATCH 9/9] corrected error 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 ce44a464b..d39729af3 100755 --- a/rose-stem/bin/fortitude_launcher.py +++ b/rose-stem/bin/fortitude_launcher.py @@ -62,8 +62,8 @@ def launch_fortitude(config_path, app_path): app_path = os.path.join(args.source, 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", @@ -91,8 +91,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)