Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
4dbac71
#240 Added new base class for Fab scripts.
hiker Jan 23, 2026
2254b31
#240 Removed support for transmute step to keep this PR smaller.
hiker Jan 23, 2026
c1e87d1
#240 Add all skeleton fab script, helper classes and site-configurati…
hiker Jan 23, 2026
a350ef3
#240 Updated tests to pass all linters, fixed licence statement.
hiker Jan 27, 2026
962ce0a
#240 Updated skeleton build script.
hiker Jan 27, 2026
54491da
#240 Fixed incorrect typing in test.
hiker Jan 27, 2026
22c2ef9
#240 Added README file.
hiker Jan 27, 2026
25c26fb
#240 Removed fab and vernier support from NCI site.
hiker Jan 27, 2026
72192df
#240 Fixed typos.
hiker Jan 27, 2026
f7a839c
#240 Allow import of psyclone_tools without setting PYTHONPATH.
hiker Jan 27, 2026
170cab3
#240 Fixed flake8 errors.
hiker Jan 27, 2026
d7aa9f1
#240 Added myself to contributors list.
hiker Jan 27, 2026
2315007
Merge branch 'main' into 240_add_skeleton_fab_script
hiker Jan 27, 2026
9ee0b64
Fixed rose picker etc issues raised in review.
hiker Feb 8, 2026
6dc4bd8
Renamed test directory to tests (to avoid clash with .gitignore in ro…
hiker Feb 8, 2026
00b7ec9
#240 Renamed rose_picker_tool.py to rose_picker.py etc.
hiker Feb 9, 2026
6a25195
#240 Addressed issues raised in review.
hiker Feb 9, 2026
1d4c878
Merge remote-tracking branch 'upstream/main' into 240_add_skeleton_fa…
hiker Feb 9, 2026
4872ec9
#240 Removed get_additional_psyclone_options.
hiker Feb 10, 2026
495d651
#240 Fixed configurator to use the new names of the tools.
hiker Feb 10, 2026
fa84662
#240 Fixed building current LFRic skeleton due to changes in build sy…
hiker Feb 10, 2026
bf87398
#240 Updated tests to work with changes to LFRic build system.
hiker Feb 10, 2026
463faae
#240 Removed unnecessary path for rose picker.
hiker Feb 10, 2026
0c9a599
#240 Set a default as project name.
hiker Feb 10, 2026
ac0999c
#240 Fixed incorrect names in templaterator.
hiker Feb 11, 2026
8421891
Merge remote-tracking branch 'upstream/main' into 240_add_skeleton_fa…
hiker Feb 11, 2026
0f42ad1
Merge remote-tracking branch 'upstream/main' into 240_add_skeleton_fa…
hiker Feb 11, 2026
c0fc24f
#240 Removed support for old-style environment variables for precision.
hiker Feb 19, 2026
434c394
#240 Removed unnecessary import.
hiker Feb 19, 2026
890a725
#240 Marked the new steps as steps so they get measured.
hiker Feb 19, 2026
d98699b
#240 Clarified docstring.
hiker Feb 19, 2026
5f2c892
Updated comments.
hiker Feb 20, 2026
3c3ba13
#240 Handle warning in new steps.
hiker Feb 20, 2026
d16a46b
#240 Moved build files into lfric_build directory.
hiker Feb 20, 2026
d3d4673
#240 Renamed default compiler setup scripts to 'setup_script_XXX.py'.
hiker Feb 23, 2026
a5309c1
#240 Fixed failing tests, handled warning.
hiker Feb 23, 2026
40b1863
Merge branch 'main' into 240_add_skeleton_fab_script
MatthewHambley Feb 26, 2026
9731fa1
#240 Remove unit-test as compilation profile.
hiker Mar 2, 2026
c54edab
#240 Removed setting up unit-test for gnu (since unit-test was removed).
hiker Mar 4, 2026
9942f1c
#292 Started to add support for pFUNit.
hiker Mar 6, 2026
558d9c9
Merge branch 'main' into 240_add_skeleton_fab_script
yaswant Mar 10, 2026
1c7a85f
#240 Updated precision handling as requested in review.
hiker Mar 19, 2026
620cdae
Merge branch '240_add_skeleton_fab_script' of github.com:hiker/lfric_…
hiker Mar 19, 2026
c543cf6
Merge branch '240_add_skeleton_fab_script' into 292_fab_pfunit
hiker Mar 19, 2026
b4b7672
Merge branch 'main' into 240_add_skeleton_fab_script
hiker Mar 19, 2026
294500f
Merge branch '240_add_skeleton_fab_script' into 292_fab_pfunit
hiker Mar 30, 2026
92008c5
#292 Changed the base class into a mixin.
hiker Mar 31, 2026
5ae6376
Merge branch 'main' into 240_add_skeleton_fab_script
hiker Mar 31, 2026
06a3217
Merge branch '240_add_skeleton_fab_script' into 292_fab_pfunit
hiker Mar 31, 2026
e0984ee
#292 Fixed small bugs and incorrect name of test symbol.
hiker Apr 1, 2026
f6185d7
#292 Ignore fab-workspace directories.
hiker Apr 1, 2026
afd5c12
#292 Added pfunit tool.
hiker Apr 1, 2026
b707e15
#292 Updated documentation.
hiker Apr 1, 2026
835ab8b
#292 Fixed typo.
hiker Apr 1, 2026
280268b
#240 Addressed issues raised in review.
hiker Apr 9, 2026
ecdff1d
#240 Check that NetCDF is available before starting to compile.
hiker Apr 9, 2026
cf5b686
Merge remote-tracking branch 'origin/main' into 240_add_skeleton_fab_…
hiker Apr 9, 2026
2b7d2ae
#292 Use new Category implementation.
hiker Apr 23, 2026
ed7203e
Update lfric_build/site_specific/default/setup_script_gnu.py
hiker May 4, 2026
6ea9af2
Update lfric_build/site_specific/default/setup_script_gnu.py
hiker May 4, 2026
513da0b
Update lfric_build/site_specific/default/config.py
hiker May 4, 2026
7bf6c2a
Update lfric_build/site_specific/default/config.py
hiker May 4, 2026
101ea01
#240 Added copyright statement.
hiker May 4, 2026
bd21fdb
Merge remote-tracking branch 'origin/main' into 240_add_skeleton_fab_…
hiker May 4, 2026
489ba95
Merge branch '240_add_skeleton_fab_script' into 292_fab_pfunit
hiker May 5, 2026
e8c84ce
Merge remote-tracking branch 'upstream/main' into 240_add_skeleton_fa…
t00sa May 5, 2026
97490d7
Add fab-workspace to gitignore
t00sa May 5, 2026
3f9cc9e
#292 Removed old-style typing.
hiker May 6, 2026
6682d87
#292 Updated nci gadi config.
hiker May 6, 2026
acc5c8e
#292 Updated NCI setup.
hiker May 6, 2026
c7e03ee
#292 Fixed coding style.
hiker May 6, 2026
7d99ddf
#292 Fix pfunit build.
hiker May 6, 2026
cd3db4c
#292 Fix pfunit build by disabling a warning (needs path-specific fla…
hiker May 6, 2026
e4ccac0
Merge remote-tracking branch 'origin/main' into 240_add_skeleton_fab_…
hiker May 6, 2026
afa7fdc
Merge branch '240_add_skeleton_fab_script' into 292_fab_pfunit
hiker May 6, 2026
f676f43
#292 Use new public names for some Fab attributes.
hiker May 9, 2026
8ebad2f
#292 Abort build if OpenMP is disabled, since LFRic does not link wit…
hiker May 9, 2026
7b5f885
#292 Fixed tests (missing app_dir, linkger without netcdf support, tr…
hiker May 9, 2026
5ac67e9
#292 Added test for aborting when trying to disable OpenMP with lfric.
hiker May 9, 2026
6575f4d
Merge remote-tracking branch 'origin/main' into 292_fab_pfunit
hiker May 9, 2026
4d6d3af
Merge remote-tracking branch 'origin/main' into 292_fab_pfunit
hiker May 12, 2026
0fec60b
Merge remote-tracking branch 'origin/main' into 292_fab_pfunit
hiker May 14, 2026
4fc519e
#292 Include the files from components/science/unit-test.
hiker May 14, 2026
a779860
#292 Only copy .f90 files from components/science/unit-tests.
hiker May 14, 2026
cccfeac
#292 Added explanations.
hiker May 15, 2026
e50b673
Merge remote-tracking branch 'origin/main' into 292_fab_pfunit
hiker May 15, 2026
40961e6
#292 Rename apps_dir to app_dir.
hiker May 21, 2026
d8573cf
#371 Added first working version of transmute support.
hiker May 27, 2026
da94e44
#292 Removed outdated comment.
hiker May 27, 2026
974b55f
Merge remote-tracking branch 'origin/main' into 292_fab_pfunit
hiker May 27, 2026
ea1cbf8
Merge branch '292_fab_pfunit' into 371_fab_transmute_support
hiker May 27, 2026
e3b7dd7
#371 Renmaed psyclone_config to psyclone_control.
hiker May 27, 2026
9742b1f
#292 Added netcdf config object to lfric_core, as requested in the Fa…
hiker May 28, 2026
a1a64eb
#292 Removed pkg_config object, since it will be added to Fab.
hiker May 28, 2026
900a984
#292 Fixed typo in comment.
hiker May 28, 2026
37990ca
Merge branch '292_fab_pfunit' into 371_fab_transmute_support
hiker May 28, 2026
3b397a6
#292 Use instead of sys.path to add to python search path, since sys…
hiker May 28, 2026
f86dfcf
Merge branch '292_fab_pfunit' into 371_fab_transmute_support
hiker May 28, 2026
e33c642
#371 Better support running without a script.
hiker May 28, 2026
cbaed2e
#292 Updated unit tests.
hiker Jun 2, 2026
8c073c2
Merge branch '292_fab_pfunit' into 371_fab_transmute_support
hiker Jun 2, 2026
c14d243
#371 Fixed failing nf_config test.
hiker Jun 2, 2026
acf57a6
#371 Updated tests for new psyclone usage.
hiker Jun 2, 2026
236ad57
#371 Linting changes.
hiker Jun 2, 2026
4b1ee14
#371 Removed Fab as dependency of psyclone_control.py.
hiker Jun 3, 2026
fdfa624
#371 Fixed failing test.
hiker Jun 9, 2026
ab8ed85
#371 Small NCI bug fix.
hiker Jun 10, 2026
ba03118
#371 Add the PSyclone control file to the logger.
hiker Jun 10, 2026
2ab9ed8
#371 Added gemini-created unit test.
hiker Jun 10, 2026
6135b12
#371 Black formatted, fixed line length.
hiker Jun 10, 2026
c8138de
#371 Removed artefacts attribute, which is not used or required.
hiker Jun 10, 2026
da6643b
371 Updated fixtures to use generic flags from Fab.
hiker Jun 29, 2026
c8aed18
#371 Moved changes from dependency pr here, since they will be requir…
hiker Jul 1, 2026
36903c3
#371 Also copy the optimisations script across to be consistent.
hiker Jul 1, 2026
d5c1132
Merge remote-tracking branch 'origin/main' into 371_fab_transmute_sup…
hiker Jul 1, 2026
572bfb7
#371 Renamed psyclone_info.yaml to psyclone_control.yaml
hiker Jul 1, 2026
05669e4
Merge branch 'main' into 371_fab_transmute_support
hiker Aug 3, 2026
db20fd9
#371 Updated documentation in psyclone control file.
hiker Aug 3, 2026
9575380
#371 Added public function to add to the Python search path.
hiker Aug 3, 2026
2ee4e86
#371 Minor code cleanup in preparation for apps-specific settings.
hiker Aug 5, 2026
e34ccb6
371 Started to remove compilation flags that were required for lfric_…
hiker Aug 10, 2026
adaf728
Merge branch 'main' into 371_fab_transmute_support
MatthewHambley Aug 17, 2026
e818c14
#371 Removed debug print.
hiker Aug 18, 2026
c164dd6
Merge branch '371_fab_transmute_support' of github.com:hiker/lfric_co…
hiker Aug 18, 2026
9db6e1c
#371 Remove unused parameter (due to change in the corresponding Fab …
hiker Aug 19, 2026
38fcf64
#371 Use simpler profiling definition.
hiker Aug 27, 2026
91e7947
#371 Split tests into smaller pieces.
hiker Aug 27, 2026
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
223 changes: 148 additions & 75 deletions lfric_build/lfric_base.py

Large diffs are not rendered by default.

311 changes: 311 additions & 0 deletions lfric_build/psyclone_control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
#!/usr/bin/env python3

##############################################################################
# (c) Crown copyright Met Office. All rights reserved.
# For further details please refer to the file COPYRIGHT
# which you should have received as part of this distribution
##############################################################################

'''
This module reads in a psyclone_info.yaml file.
'''
from pathlib import Path
from typing import Optional
import yaml


class PsycloneInfo:
"""
This class stores the set of rules and settings for one specific PSyclone
phase.

:param name: the name of this phase.
:param fab_base: the application script derived from FabBase. Required to
get access to the build config for paths, and the selected site and
platform.
"""

# Define a new 'script paths' to indicate special results. Use : in the
# name, which is typically not a valid name (at the start)
RESULT_EXCLUDE = Path("::RESULT_EXCLUDE")
RESULT_NO_SCRIPT = Path("::RESULT_NO_SCRIPT")

FILE_SPECIFIC = "file_specific"
EXCLUDE = "exclude"
NO_SCRIPT = "no_script"

def __init__(self, name: str,
base_paths: list[Path],
script_root: Path) -> None:
self._base_paths = base_paths
self._script_root = script_root
# This will be initialised/updated each time when reading an info file.
self._opt_path = Path()
self._relative_script_dir: str = ""
self._name: str = name
self._comment: str = ""
self._api: str = ""
self._rules: list[tuple[str, list[str]]] = []

@property
def name(self) -> str:
"""
:returns: the name of this phas.
"""
return self._name

@property
def comment(self) -> str:
"""
:returns: the comment for this phase.
"""
return self._comment

@property
def api(self) -> str:
"""
:returns: the PSyclone command line options.
"""
return self._api

@property
def opt_path(self) -> Path:
"""
:return: the optimisation root as absolute path (including site-
and platform-specific settings, and subdirectory, e.g. psykal
or transmute).
"""
return self._opt_path

def update(self, info: dict[str, str]) -> None:
"""
Update this PSyclone information with data taken from a
PSyclone info yaml file. This function is used to initially
read in a first specification, or update a specification based
on an additional file being read in later.

:param info: the yaml information taken from a PSyclone info file.
"""
for rule in info:
if rule == "comment":
self._comment = info["comment"]
elif rule == "api":
self._api = info["api"]
elif rule == "script_dir":
self._relative_script_dir = info["script_dir"]
else:
self._read_rule(rule, info[rule])

# Store the potentially updated optimisation root path, i.e. the site-
# and platform-specific location, followed by a script dir (typically
# transmute or psykal). This path is used in a few places.
self._opt_path = self._script_root / self._relative_script_dir

def _read_rule(self, rule: str, file_list: str) -> None:
"""
Parses a single rule from the yaml file. It especially handles
various way a '*' can be specified in a yaml file.

:param rule: the name of the rule (typically the script name, or
special term like `exclude` or `file_specific`).
:param file_list: the list of files to which to apply the rule to.
"""
# Support '*', which is a reserved character in yaml and needs to
# be escaped or quoted.
if file_list in ["\\*", "'*'", '"*"']:
file_list = "*"
self._rules.append((rule, file_list.split()))

def get_yaml_dict(self) -> dict[str, list[str]]:
"""
:returns: returns ths data in this object in a dictionary, suitable
to write them back as a yaml file.
"""

yaml_dict = {"comment": self.comment,
"api": self.api,
"script_dir": self._relative_script_dir}

for rule, file_list in self._rules:
yaml_dict[rule] = " ".join(file_list)

return yaml_dict

def file_specific_script(self, fpath: Path) -> Optional[Path]:
"""
Searches for a file-specific optimisation script. It will search
both under the source and the build directories of the project
directory.

:param fpath: the file path of the Fortran file.
:returns: the path of the file-specific optimisation script, or
None if no such file exists.
"""
relative_path = None
# The source file might be either in build_output (e.g. a preprocessed
# .X90 file), or still in source (.x90 file). Check if the file
# is in one of the two sub-trees, and use the relative path to
# check if there is a file-specific optimisation script
for base_path in self._base_paths:
try:
relative_path = fpath.relative_to(base_path)
except ValueError:
# The file is not under the `base_path` - keep on checking
pass

if relative_path:
# The file was under either source or build. Check if there
# is a file-specific optimisation script:
local_transformation_script = (self.opt_path /
(relative_path.with_suffix('.py')))
if local_transformation_script.exists():
return local_transformation_script
return None

def get_script(self, fpath: Path) -> Path:
"""
This method returns the script to be used for a given filename, or
None if no rule applies (or an explicit exclude rule applies)

This function will also provided to Fab's PSyclone step, and as such
it will receive the config object, even though it is not used.

:param fpath: the Fortran source file for which to find a
transformation script.

:returns: the path to the transformation script, or None if no rule
applies (or an exclude rule applies).
"""

file_str = str(fpath)
# Search starting from the end, so last rule wins
for rule, file_list in self._rules[::-1]:
for pattern in file_list:
if pattern not in file_str and pattern != "*":
continue

# Now the pattern matches. Check which rule is used
# (note that file_specific might fall through in case that
# there is no file-specific script)
if rule == PsycloneInfo.FILE_SPECIFIC:
script = self.file_specific_script(fpath)
if script:
return script
if pattern == "*":
# Fall through, i.e. check for other rules
continue

# Now we have an explicit request for a file-specific
# script, but that script does not exist.
raise FileNotFoundError(
f"Cannot find explicitly requested script '{script}'.")

if rule == PsycloneInfo.EXCLUDE:
# Exclude pattern matches:
return PsycloneInfo.RESULT_EXCLUDE

if rule == PsycloneInfo.NO_SCRIPT:
# Exclude pattern matches:
return PsycloneInfo.RESULT_NO_SCRIPT

opt_script = self.opt_path / rule
if not opt_script.exists():
raise FileNotFoundError(f"Cannot find script "
f"'{opt_script}' for '{fpath}'.")
return opt_script

return PsycloneInfo.RESULT_EXCLUDE


class PsycloneControl:
"""
This class stores the information from psyclone_control.yaml file(s).
Several files can be read, and latter information will extend the rules
from previous files, and replace the phases executed.

Details of each phase will be stored in PsycloneInfo instances.

:param base_paths:
:param fab_base: The FabBase derived application script. This is required
to get site, platform and config information when searching for
PSyclone scripts to be executed.
"""

def __init__(self,
script_root: Path,
base_paths: list[Path]) -> None:
# Keep a copy in case that the user modifies the list later
self._base_paths = base_paths[:]
self._script_root = script_root
self._all_phases: list[str] = []
self._psyclone_info: dict[str, PsycloneInfo] = {}
# A list of all PSyclone info files that were read.
# Only used to add useful comments to the yaml output.
self._all_files_read: list[Path] = []

@property
def all_phases(self) -> list[str]:
"""
:returns: the list of all PSyclone phases to execute.
"""
return self._all_phases

def get_info(self, phase: str) -> PsycloneInfo:
"""
Returns the PSyclone information for the specified phase.

:param phase: the name of the phase.

:returns: the PSyclone Information for the specified phase.
"""
return self._psyclone_info[phase]

def to_yaml(self) -> str:
"""
This returns a string representation of the combined read yaml files.
This is useful to be logged to show the actual details used.

:returns: the string represenation in yaml format of this PSyclone
control instance.
"""

yaml_dict = {"phases": self._all_phases}
for phase in self._all_phases:
yaml_dict[phase] = self._psyclone_info[phase].get_yaml_dict()

files_read = '\n'.join(f"# {i}" for i in self._all_files_read)
yaml_string = yaml.dump(yaml_dict, default_flow_style=False,
sort_keys=False)
return f"# Files read:\n{files_read}\n{yaml_string}"

def read(self, file_path: Path) -> None:
"""
Reads a yaml file, and extends the potentially existing information.
Any phases specified in the new read yaml file will replace the
phases to be executed (i.e. will overwrite what was previously
specified). Any new rule sets will be added as new PsycloneInfo
instance. Rules for an exiting phase will be appended to the
existing information. The precedence handling means that any later
rule will overwrite any previous rule.

:param file_path: the file_path to read.
"""

with open(file_path, "r", encoding="utf8") as stream:
dependencies = yaml.safe_load(stream)
self._all_files_read.append(file_path.resolve())

# First take phases (if available)
if dependencies.get("phases", None):
self._all_phases = dependencies["phases"]

for key in dependencies:
if key == "phases":
# Already handled
continue
if key not in self._psyclone_info:
self._psyclone_info[key] = PsycloneInfo(key,
self._base_paths,
self._script_root)

self._psyclone_info[key].update(dependencies[key])
31 changes: 31 additions & 0 deletions lfric_build/psyclone_control.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This file controls which files are being processed by PSyclone,
# both using PSyclone's transmute ability (transformation generic
# Fortran files), and its DSL (kernel based) capability.

# First we specify the phases for PSyclone. This allows
# any apps to run transmute and DSL steps in any order just by
# changing (or supplying a different) PSyclone-info file.
# In this file, we only run a single DSL phase.

phases:
# Just a single phase: running PSyclone in DSL mode
- dsl

dsl:
comment: "PSylone DSL Phase"
api: lfric

script_dir: psykal

# These two directives reproduce the current LFRic default PSyclone triggering:

# Run optimisation/.../global.py on all x90 files (x90 because this is
# the dsl section, as specified in the artefacts above)
global.py: \*

# This represents existing functionality: if there is a file-specific .py
# file, use it. The specification of '*' does not trigger an error
# if there is no file_specific script (while explicitly putting a name
# in the file list will trigger an error if the corresponding PSyclone
# script does not exist. This way, errors are caught early).
file_specific: \*
Loading
Loading