Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions lfric_macros/validate_rose_meta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#!/usr/bin/env python3
##############################################################################
# (c) Crown copyright 2024 Met Office. All rights reserved.
# The file LICENCE, distributed with this code, contains details of the terms
# under which the code may be used.
##############################################################################
"""
Validate the rose metadata in lfric_apps
"""

import os
import sys
import subprocess
import argparse

# A list of invalid metadata sections. Most are invalid as they are imported by
# lfric-gungho but also use the files namelist contained there, creating a circular
# dependency
INVALID_METADATA = [
"jules-lfric",
"lfric-jules-shared",
"socrates-radiation",
"um-aerosol",
"um-boundary_layer",
"um-chemistry",
"um-cloud",
"um-convection",
"um-iau",
"um-microphysics",
"um-orographic_drag",
"um-spectral_gwd",
"um-stochastic_physics",
]

# A list of invalid rose-stem apps to ignore when checking metadata validity. These are
# mostly apps required for lfric coupled which use metadata from other repositories
INVALID_APPS = [
"fcm_make_drivers",
"fcm_make_ocean",
"fcm_make_river",
"lfric_coupled_rivers",
]


def run_command(command, shell=False, env=None):
"""
Run a subprocess command and return the result object
Inputs:
- command, str with command to run
Outputs:
- result object from subprocess.run
"""
if not shell:
command = command.split()
if not env:
env = os.environ
return subprocess.run(
command,
capture_output=True,
text=True,
timeout=120,
shell=shell,
check=False,
env=env,
)


def check_rose_metadata(rose_meta_path, source_path):
"""
Auto find rose-meta sections from the top level rose-meta directory and run `rose
metadata-check` on each
"""

print("\n\n[INFO] - Checking rose metadata sections\n\n")
failed = False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
failed = False


# Add ROSE_META_PATH to env for rose metadata-check command
my_env = os.environ.copy()
my_env["ROSE_META_PATH"] = rose_meta_path

start_dir = os.path.join(source_path, "rose-meta")
dirs = os.listdir(start_dir)
for section in dirs:
if section in INVALID_METADATA:
continue
meta_dir = os.path.join(start_dir, section, "HEAD")
command = f"rose metadata-check --verbose -C {meta_dir}"
result = run_command(command, env=my_env)
if result.returncode:
print(f"[FAIL] - {section} failed to validate")
print(
f"Failure running rose metata-check on {section}:\n{result.stderr}",
file=sys.stderr,
)
failed = True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
failed = True
return False

else:
print(f"[PASS] - {section} validated")

return failed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return failed
return True



def parse_suite_controlled(err_msg):
"""
Remove any app validation error messages resulting from suite_controlled option
configs
"""

err = []
split_err = err_msg.split("\n")
i = 0
while i < len(split_err):
line = split_err[i]
if "opts=suite_controlled" in line:
i += 2
continue
if line.strip():
err.append(line)
i += 1
if len(err) > 1:
return err
return []


def check_rose_stem_apps(meta_paths, source_path):
"""
Auto find rose-stem apps that use rose metadata and validate these using 'rose
macro --validate'
"""

print("\n\n[INFO] - Validating rose-stem apps\n\n")
failed = False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
failed = False


start_dir = os.path.join(source_path, "rose-stem", "app")
apps = os.listdir(start_dir)
for app in apps:
if app in INVALID_APPS:
continue
app_dir = os.path.join(start_dir, app)
conf_file = os.path.join(app_dir, "rose-app.conf")
with open(conf_file, "r") as f:
for line in f:
if line.startswith("meta="):
break
else:
continue
command = f"rose macro --validate {meta_paths} -C {app_dir} --no-warn version"
result = run_command(command)
# Check that errors are only from rose-app-suite-controlled.conf
errors = []
if result.returncode:
errors = parse_suite_controlled(result.stderr)
errors = "\n".join(e for e in errors)

if errors:
print(f"[FAIL] - {app} failed to validate")
print(f"Failure validating app {app}:\n{errors}", file=sys.stderr)
failed = True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
failed = True
return False

else:
print(f"[PASS] - {app} validated")

return failed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return failed
return True



def parse_args():
"""
Read command line args
"""

parser = argparse.ArgumentParser("Pre-process and apply LFRic Apps upgrade macros.")
parser.add_argument(
"-a",
"--apps",
default=None,
help="The path to the LFRic Apps source. At least one of this or Core are "
"required. If both are provided then it will be assumed that Apps is the "
"repository to check.",
)
parser.add_argument(
"-c",
"--core",
default=None,
help="The path to the LFRic Core source. At least one of this or Apps are "
"required. If both are provided then it will be assumed that Apps is the "
"repository to check.",
)

args = parser.parse_args()

if args.apps:
args.apps = os.path.expanduser(args.apps)
if args.core:
args.core = os.path.expanduser(args.core)

return args


def main():
"""
main function for this script
"""
args = parse_args()

meta_paths = ""
rose_meta_path = ""
if args.apps:
source_path = args.apps
meta_paths += f"-M {os.path.join(args.apps, "rose-meta")}"
rose_meta_path += args.apps
elif args.core:
source_path = args.core
meta_paths += f"-M {os.path.join(args.core, "rose-meta")} "
if rose_meta_path:
rose_meta_path += f":{args.core}"
else:
rose_meta_path = args.core
else:
raise RuntimeError(
"At least one of the Apps or Core sources must be provided as a command "
"line argument"
)

if check_rose_metadata(rose_meta_path, source_path) or check_rose_stem_apps(
meta_paths, source_path
):
sys.exit("There were metadata validation failures. See output for details")
print("All metadata successfully validated")


if __name__ == "__main__":
main()