Skip to content
Open
3 changes: 2 additions & 1 deletion applications/jules/build/compile_options.mk
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ $(info UM physics specific compile options)

include $(PROJECT_DIR)/build/fortran/$(FORTRAN_COMPILER).mk

science/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
casim/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
ukca/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
jules/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
socrates/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
legacy/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
Expand Down
2 changes: 1 addition & 1 deletion applications/lfric_atm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ build: export BIN_DIR ?= $(PROJECT_DIR)/bin
build: export CXX_LINK = YES
build: export PROGRAMS := $(basename $(notdir $(shell find source -maxdepth 1 -name '*.[Ff]90' -exec egrep -l "^\s*program" {} \;)))
build: export PROJECT = lfric_atm
build: export SCRATCH_DIR := $(WORKING_DIR)/../physics_scratch
build: export SCRATCH_DIR := $(WORKING_DIR)/..
build: export WORKING_DIR := $(WORKING_DIR)

build: export LDFLAGS_GROUPS = OPENMP
Expand Down
3 changes: 2 additions & 1 deletion applications/lfric_atm/build/compile_options.mk
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ $(info UM physics specific compile options)

include $(PROJECT_DIR)/build/fortran/$(FORTRAN_COMPILER).mk

science/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
casim/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
ukca/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
jules/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
socrates/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
legacy/%.o: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
Expand Down
2 changes: 1 addition & 1 deletion applications/lfric_coupled/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ document-api: api-documentation
build: export BIN_DIR ?= $(PROJECT_DIR)/bin
build: export CXX_LINK = TRUE
build: export PROGRAMS := $(basename $(notdir $(shell find source -maxdepth 1 -name '*.[Ff]90' -print)))
build: export SCRATCH_DIR := $(WORKING_DIR)/../physics_scratch
build: export SCRATCH_DIR := $(WORKING_DIR)/..
build: export PROJECT = $(PROJECT_NAME)
build: export WORKING_DIR := $(WORKING_DIR)/$(PROJECT_NAME)

Expand Down
3 changes: 2 additions & 1 deletion applications/lfric_coupled/build/compile_options.mk
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ $(info UM physics specific compile options for $(FORTRAN_COMPILER) compiler)

include $(PROJECT_DIR)/build/fortran/$(FORTRAN_COMPILER).mk

science/%.o science/%.mod: private FFLAGS_EXTRA += $(FFLAGS_UM_PHYSICS)
casim/%.o science/%.mod: private FFLAGS_EXTRA += $(FFLAGS_UM_PHYSICS)
ukca/%.o science/%.mod: private FFLAGS_EXTRA += $(FFLAGS_UM_PHYSICS)
jules/%.o jules/%.mod: private FFLAGS_EXTRA += $(FFLAGS_UM_PHYSICS)
socrates/%.o socrates/%.mod: private FFLAGS_EXTRA += $(FFLAGS_UM_PHYSICS)
legacy/%.o legacy/%.mod: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
Expand Down
2 changes: 1 addition & 1 deletion applications/ngarch/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ document-api: api-documentation
build: export BIN_DIR ?= $(PROJECT_DIR)/bin
build: export CXX_LINK = TRUE
build: export PROGRAMS := $(basename $(notdir $(shell find source -maxdepth 1 -name '*.[Ff]90' -print)))
build: export SCRATCH_DIR := $(WORKING_DIR)/../physics_scratch
build: export SCRATCH_DIR := $(WORKING_DIR)/..
build: export PROJECT = $(PROJECT_NAME)
build: export WORKING_DIR := $(WORKING_DIR)

Expand Down
3 changes: 2 additions & 1 deletion applications/ngarch/build/compile_options.mk
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ $(info UM physics specific compile options)

include $(PROJECT_DIR)/build/fortran/$(FORTRAN_COMPILER).mk

science/%.o science/%.mod: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
casim/%.o science/%.mod: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
ukca/%.o science/%.mod: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
jules/%.o jules/%.mod: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
socrates/%.o socrates/%.mod: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
legacy/%.o legacy/%.mod: private FFLAGS_EXTRA = $(FFLAGS_UM_PHYSICS)
Expand Down
5 changes: 1 addition & 4 deletions build/extract/extract_physics.mk
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,4 @@

extract:
# Retrieve and preprocess the UKCA and CASIM code
python $(APPS_ROOT_DIR)/build/extract/extract_science.py -d $(APPS_ROOT_DIR)/dependencies.yaml -w $(SCRATCH_DIR) -e $(APPS_ROOT_DIR)/build/extract/extract.yaml
$Qrsync -acvz $(SCRATCH_DIR)/ukca $(WORKING_DIR)/science/
$Qrsync -acvz $(SCRATCH_DIR)/casim $(WORKING_DIR)/science/

python $(APPS_ROOT_DIR)/build/extract/extract_science.py -d $(APPS_ROOT_DIR)/dependencies.yaml -w $(WORKING_DIR) -e $(APPS_ROOT_DIR)/build/extract/extract.yaml
66 changes: 29 additions & 37 deletions build/extract/extract_science.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import argparse
import subprocess
import os
import tempfile
import yaml
from shutil import rmtree
from pathlib import Path
from typing import Dict, List
from get_git_sources import get_source
import shlex


def run_command(command):
Expand All @@ -14,7 +14,7 @@ def run_command(command):
Inputs:
- command, str with command to run
"""
command = command.split()
command = shlex.split(command)
result = subprocess.run(
command,
capture_output=True,
Expand All @@ -40,55 +40,47 @@ def load_yaml(fpath: Path) -> Dict:
return sources


def clone_dependency(values: Dict, temp_dep: Path) -> None:
"""
Clone the physics dependencies into a temporary directory
"""

source = values["source"]
ref = values["ref"]

commands = (
f"git -C {temp_dep} init",
f"git -C {temp_dep} remote add origin {source}",
f"git -C {temp_dep} fetch origin {ref}",
f"git -C {temp_dep} checkout FETCH_HEAD"
)
for command in commands:
run_command(command)


def extract_files(dependency: str, values: Dict, files: List[str], working: Path):
"""
Clone the dependency to a temporary location
Then copy the desired files to the working directory
Then delete the temporary directory
"""

tempdir = Path(tempfile.mkdtemp())
mirror_loc: Path = os.getenv("MIRROR_LOC", "")

Choose a reason for hiding this comment

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

Or, in fact, the presence of the variable could signal the requirement while it's content is the location. And how about whole words while we're at it? MIRROR_LOCATION

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure, happy to use that here


if (
"PHYSICS_ROOT" not in os.environ
or not Path(os.environ["PHYSICS_ROOT"]).exists()
):
temp_dep = tempdir / dependency
temp_dep.mkdir(parents=True)
clone_dependency(values, temp_dep)
clone_loc = working / ".." / "scratch" / dependency
get_source(
values["source"],
values["ref"],
clone_loc,
dependency,
bool(mirror_loc),
mirror_loc
)
else:
temp_dep = Path(os.environ["PHYSICS_ROOT"]) / dependency
clone_loc = Path(os.environ["PHYSICS_ROOT"]) / dependency

working_dep = working / dependency

# make the working directory location
working_dep.mkdir(parents=True)
working_dir = working / dependency
working_dir.mkdir(parents=True, exist_ok=True)

# rsync extract files from clone loc to the working directory
copy_command = "rsync --include='**/' "
for extract_file in files:
source_file = temp_dep / extract_file
dest_file = working_dep / extract_file
run_command(f"mkdir -p {dest_file.parents[0]}")
copy_command = f"cp -r {source_file} {dest_file}"
run_command(copy_command)

rmtree(tempdir)
if not extract_file:
continue
if Path(clone_loc / extract_file).is_dir():
extract_file = extract_file.rstrip("/")
extract_file += "/**"
copy_command += f"--include='{extract_file}' "
copy_command += f"--exclude='*' -avmq {clone_loc}/ {working_dir}"
run_command(copy_command)


def parse_args() -> argparse.Namespace:
Expand All @@ -101,10 +93,10 @@ def parse_args() -> argparse.Namespace:
"-d",
"--dependencies",
default="./dependencies.yaml",
help="The dependencies file for the apps working copy.",
help="The dependencies file for the apps working copy",
)
parser.add_argument(
"-w", "--working", default=".", help="Location to perform extract steps in."
"-w", "--working", default=".", help="Build location"
)
parser.add_argument(
"-e",
Expand Down
203 changes: 203 additions & 0 deletions build/extract/get_git_sources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# *****************************COPYRIGHT*******************************
# (C) Crown copyright Met Office. All rights reserved.
# For further details please refer to the file COPYRIGHT.txt
# which you should have received as part of this distribution.
# *****************************COPYRIGHT*******************************
"""
Helper functions for cloning git sources in command line builds
"""

import re
import subprocess
from typing import Optional
from pathlib import Path
from shutil import rmtree
import shlex


def get_source(
source: str,
ref: str,
dest: Path,
repo: str,
use_mirrors: bool = False,
mirror_loc: Path = "",
) -> None:

if ".git" in source:
if use_mirrors:

Choose a reason for hiding this comment

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

For a future change, it may be possible to use a class to wrap repository access. That way the test can be performed once, the correct object instantiated and no further concerns had about which one it is.

mirror_loc = Path(mirror_loc) / "MetOffice" / repo
print(f"Cloning/Updating {repo} from mirror {mirror_loc} at ref {ref}")
clone_repo_mirror(source, ref, repo, mirror_loc, dest)
else:
print(f"Cloning/Updating {repo} from {source} at ref {ref}")
clone_repo(source, ref, dest)
else:
print(f"Syncing {repo} at ref {ref}")
sync_repo(source, ref, dest)


def run_command(

Choose a reason for hiding this comment

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

This seems a lot of effort to recreate the functionality of subprocess.run(). I believe it will take a string or a list of strings and it will throw an exception if you specify check=True. Is there any value here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Potentially not - it's a function I tend to copy and paste around to let me set the defaults as I want them, eg. I don't always have to set the text or timeout entries. Given it matches the version on SimSys_Scripts I'm happy to keep it here

command: str, rval: bool = False, check: bool = True
) -> Optional[subprocess.CompletedProcess]:
"""
Run a subprocess command and return the result object
Inputs:
- command, str with command to run
Outputs:
- result object from subprocess.run
"""
command = shlex.split(command)
result = subprocess.run(
command,
capture_output=True,
text=True,
timeout=300,
shell=False,
check=False,
)
if check and result.returncode:
print(result.stdout, end="\n\n\n")
raise RuntimeError(
f"[FAIL] Issue found running command {command}\n\n{result.stderr}"
)
if rval:
return result


def clone_repo_mirror(
repo_source: str, repo_ref: str, parent: str, mirror_loc: Path, loc: Path
) -> None:
"""
Clone a repo source using a local git mirror.
Assume the mirror is set up as per the Met Office
- repo_source: ssh url of the source repository
- repo_ref: git ref for the source. An empty string will get the default branch
- parent: Owner of the github repository being cloned (required to construct the
mirror path)
- mirror_loc: path to the local git mirrors
- loc: path to clone the repository to
"""

# If the repository exists and isn't a git repo, exit now as we don't want to
# overwrite it
if loc.exists():
if not Path(loc / ".git").exists():
raise RuntimeError(
f"The destination for the clone of {repo_source} already exists but "
"isn't a git directory. Exiting so as to not overwrite it."
)

# Clone if the repo doesn't exist
else:
command = f"git clone {mirror_loc} {loc}"

Choose a reason for hiding this comment

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

mirror_loc is a Git URL? Make sure that's documented in the doc-string above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No it's a path to the local mirrors in this case - that's in the docstring now

run_command(command)

# If not provided a ref, pull the latest repository and return
if not repo_ref:
run_command(f"git -C {loc} pull")
return

repo_source = repo_source.removeprefix("git@github.com:")
user = repo_source.split("/")[0]
# Check that the user is different to the Upstream User
if user in parent.split("/")[0]:
user = None

# If the ref is a hash then we don't need the fork user as part of the fetch.
# Equally, if the user is the Upstream User, it's not needed
if not user or re.match(r"^\s*([0-9a-f]{40})\s*$", repo_ref):
fetch = repo_ref
else:
fetch = f"{user}/{repo_ref}"
commands = (
f"git -C {loc} fetch origin {fetch}",
f"git -C {loc} checkout FETCH_HEAD",
)
for command in commands:
run_command(command)


def clone_repo(repo_source: str, repo_ref: str, loc: Path) -> None:
"""
Clone the repo and checkout the provided ref
Only if a remote source
- repo_source: ssh url of the source repository
- repo_ref: git ref for the source. An empty string will get the default branch
- loc: path to clone the repository to
"""

if not loc.exists():
# Create a clean clone location
loc.mkdir(parents=True)

commands = (
f"git -C {loc} init",
f"git -C {loc} remote add origin {repo_source}",
f"git -C {loc} fetch origin {repo_ref}",
f"git -C {loc} checkout FETCH_HEAD",
f"git -C {loc} fetch origin main:main",
)
Comment on lines +134 to +140

Choose a reason for hiding this comment

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

This seems a lot of effort. Doesn't it just recreate a clone?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, it was initially written to see if we could save space while cloning sources for use in rose-stem. It turns out the difference is very small, but as this was marginally smaller, I left it in. Given it's copied from SimSys_Scripts I don't think it's worth modifying here

for command in commands:
run_command(command)
else:
commands = (
f"git -C {loc} fetch origin {repo_ref}",
f"git -C {loc} checkout FETCH_HEAD",
)
for command in commands:
run_command(command)


def sync_repo(repo_source: str, repo_ref: str, loc: Path) -> None:
"""
Rsync a local git clone and checkout the provided ref
"""

# Remove if this clone already exists
if loc.exists():
rmtree(loc)

# Create a clean clone location
loc.mkdir(parents=True)

exclude_dirs = (
"applications/*/working",
"applications/*/test",
"applications/*/bin",
"science/*/working",
"science/*/test",
"science/*/bin",
"interfaces/*/working",
"interfaces/*/test",
"interfaces/*/bin",
"components/*/working",
"components/*/test",
"components/*/bin",
"infrastructure/*/working",
"infrastructure/*/test",
"infrastructure/*/bin",
"mesh_tools/*/working",
"mesh_tools/*/test",
"mesh_tools/*/bin",
)

# Trailing slash required for rsync
command = f"rsync -av {repo_source}/ {loc}"
for item in exclude_dirs:
command = f"{command} --exclude '{item}'"
run_command(command)

# Fetch the main branch from origin
# Ignore errors - these are likely because the main branch already exists
# Instead write them as warnings
command = f"git -C {loc} fetch origin main:main"

Choose a reason for hiding this comment

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

If you need to treat the copy as a repository, why not clone it. That's what distributed repositories are all about. You might take advantage of a sparse clone to gain the advantage of filtering you are getting from the rsync exclusion list.

result = run_command(command, check=False, rval=True)
if result.returncode:
print("Warning - fetching main from origin resulted in an error")
print("This is likely due to the main branch already existing")
print(f"Error message:\n\n{result.stderr}")

if repo_ref:
command = f"git -C {loc} checkout {repo_ref}"
run_command(command)
Loading
Loading