Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use cable local checkout #255

Merged
merged 7 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
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
20 changes: 18 additions & 2 deletions benchcab/benchcab.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from benchcab.utils.repo import create_repo
from benchcab.utils.subprocess import SubprocessWrapper, SubprocessWrapperInterface
from benchcab.workdir import (
clean_realisation_files,
clean_submission_files,
setup_fluxsite_directory_tree,
setup_spatial_directory_tree,
)
Expand Down Expand Up @@ -74,7 +76,7 @@
self.logger.error("benchcab is currently implemented only on Gadi")
sys.exit(1)

namelist_dir = Path(internal.CWD / internal.NAMELIST_DIR)
namelist_dir = Path(internal.NAMELIST_DIR)

Check warning on line 79 in benchcab/benchcab.py

View check run for this annotation

Codecov / codecov/patch

benchcab/benchcab.py#L79

Added line #L79 was not covered by tests
if not namelist_dir.exists():
self.logger.error(
"Cannot find 'namelists' directory in current working directory"
Expand Down Expand Up @@ -225,7 +227,14 @@
rev_number_log = ""

for model in self._get_models(config):
model.repo.checkout()
try:
model.repo.checkout()
except Exception:
msg = "Try using `benchcab clean realisations` first"
self.logger.error(

Check warning on line 234 in benchcab/benchcab.py

View check run for this annotation

Codecov / codecov/patch

benchcab/benchcab.py#L230-L234

Added lines #L230 - L234 were not covered by tests
"Model checkout failed, probably due to existing realisation name"
)
raise FileExistsError(msg)

Check warning on line 237 in benchcab/benchcab.py

View check run for this annotation

Codecov / codecov/patch

benchcab/benchcab.py#L237

Added line #L237 was not covered by tests
rev_number_log += f"{model.name}: {model.repo.get_revision()}\n"

rev_number_log_path = next_path("rev_number-*.log")
Expand Down Expand Up @@ -315,6 +324,13 @@
else:
self.fluxsite_submit_job(config_path, skip)

def clean(self, config_path: str, clean_option: str):
"""Endpoint for `benchcab clean`."""
if clean_option in ["all", "realisations"]:
clean_realisation_files()
if clean_option in ["all", "submissions"]:
clean_submission_files()

Check warning on line 332 in benchcab/benchcab.py

View check run for this annotation

Codecov / codecov/patch

benchcab/benchcab.py#L329-L332

Added lines #L329 - L332 were not covered by tests

def spatial_setup_work_directory(self, config_path: str):
"""Endpoint for `benchcab spatial-setup-work-dir`."""
config = self._get_config(config_path)
Expand Down
22 changes: 22 additions & 0 deletions benchcab/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,26 @@ def generate_parser(app: Benchcab) -> argparse.ArgumentParser:
)
parser_spatial_run_tasks.set_defaults(func=app.spatial_run_tasks)

# subcommand: 'benchcab clean'
parser_clean = subparsers.add_parser(
"clean",
parents=[args_help, args_subcommand],
help="Cleanup files created by running benchcab.",
description="""Removes src/ and runs/ directories, along with log files in the
project root directory. The user has to specify which stage of files to remove
via \{all, realisations, submissions\} subcommand.""",
add_help=False,
)

parser_clean.add_argument(
"clean_option",
choices=["all", "realisations", "submissions"],
help="""Can be one of three options:

realisations: deletes src/
submissions: deletes runs/ and benchmark submission files
all: deletes in both stages of submissions and realisations""",
)

parser_clean.set_defaults(func=app.clean)
return main_parser
11 changes: 9 additions & 2 deletions benchcab/data/config-schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ realisations:
schema:
git:
type: "dict"
excludes: "svn"
excludes: ["svn", "local"]
schema:
branch:
type: "string"
Expand All @@ -37,13 +37,20 @@ realisations:
required: false
svn:
type: "dict"
excludes: "git"
excludes: ["git", "local"]
schema:
branch_path:
type: "string"
required: true
revision:
type: "integer"
local:
type: "dict"
excludes: ["git", "svn"]
schema:
path:
type: "string"
required: true
name:
nullable: true
type: "string"
Expand Down
19 changes: 17 additions & 2 deletions benchcab/data/test/integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,25 @@

set -ex

CABLE_REPO="[email protected]:CABLE-LSM/CABLE.git"
CABLE_DIR=/scratch/$PROJECT/$USER/benchcab/CABLE

TEST_DIR=/scratch/$PROJECT/$USER/benchcab/integration
EXAMPLE_REPO="[email protected]:CABLE-LSM/bench_example.git"

# Remove the test work space, then recreate
# Remove CABLE and test work space, then recreate
rm -rf $CABLE_DIR
mkdir -p $CABLE_DIR

rm -rf $TEST_DIR
mkdir -p $TEST_DIR

# Clone local checkout for CABLE
git clone $CABLE_REPO $CABLE_DIR
cd $CABLE_DIR
# Note: This is temporary, to be removed once #258 is fixed
git reset --hard 67a52dc5721f0da78ee7d61798c0e8a804dcaaeb
abhaasgoyal marked this conversation as resolved.
Show resolved Hide resolved

# Clone the example repo
git clone $EXAMPLE_REPO $TEST_DIR
cd $TEST_DIR
Expand All @@ -18,10 +30,13 @@ cat > config.yaml << EOL
project: $PROJECT

realisations:
- repo:
local:
path: $CABLE_DIR
- repo:
git:
branch: main

commit: 67a52dc5721f0da78ee7d61798c0e8a804dcaaeb # Note: This is temporary, to be removed once #258 is fixed
modules: [
intel-compiler/2021.1.1,
netcdf/4.7.4,
Expand Down
3 changes: 0 additions & 3 deletions benchcab/internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@

# DIRECTORY PATHS/STRUCTURE:

# Path to the user's current working directory
CWD = Path.cwd()

# Default system paths in Unix
SYSTEM_PATHS = ["/bin", "/usr/bin", "/usr/local/bin"]

Expand Down
4 changes: 2 additions & 2 deletions benchcab/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from benchcab.environment_modules import EnvironmentModules, EnvironmentModulesInterface
from benchcab.utils import get_logger
from benchcab.utils.fs import chdir, copy2, rename
from benchcab.utils.repo import GitRepo, Repo
from benchcab.utils.repo import GitRepo, LocalRepo, Repo
from benchcab.utils.subprocess import SubprocessWrapper, SubprocessWrapperInterface


Expand Down Expand Up @@ -62,7 +62,7 @@ def __init__(
# TODO(Sean) we should not have to know whether `repo` is a `GitRepo` or
# `SVNRepo`, we should only be working with the `Repo` interface.
# See issue https://github.com/CABLE-LSM/benchcab/issues/210
if isinstance(repo, GitRepo):
if isinstance(repo, (GitRepo, LocalRepo)):
self.src_dir = Path("src")

@property
Expand Down
95 changes: 81 additions & 14 deletions benchcab/utils/repo.py
abhaasgoyal marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,59 @@
"""


class LocalRepo(Repo):
"""Concrete implementation of the `Repo` class using local path backend."""

def __init__(self, path: str, realisation_path: str) -> None:
"""_summary_.

Parameters
----------
realisation_path : str
Path for local checkout of CABLE
path : str
Directory where CABLE is symlinked from, assigned as `local_path`

"""
self.name = Path(path).name
self.local_path = path
self.realisation_path = (

Check warning on line 65 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L63-L65

Added lines #L63 - L65 were not covered by tests
realisation_path / self.name
if realisation_path.is_dir()
else realisation_path
)
self.logger = get_logger()

Check warning on line 70 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L70

Added line #L70 was not covered by tests

def checkout(self):
"""Checkout the source code."""
self.realisation_path.symlink_to(self.local_path)
self.logger.info(

Check warning on line 75 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L74-L75

Added lines #L74 - L75 were not covered by tests
f"Created symlink from to {self.realisation_path} named {self.name}"
)

def get_revision(self) -> str:
"""Return the latest revision of the source code.

Returns
-------
str
Human readable string describing the latest revision.

"""
return f"Local CABLE build: {Path(self.local_path).absolute().as_posix}"

Check warning on line 88 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L88

Added line #L88 was not covered by tests

def get_branch_name(self) -> str:
"""Return the branch name of the source code.

Returns
-------
str
Branch name of the source code.

"""
return Path(self.realisation_path).absolute().as_posix()

Check warning on line 99 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L99

Added line #L99 was not covered by tests


class GitRepo(Repo):
"""A concrete implementation of the `Repo` class using a Git backend.

Expand All @@ -59,7 +112,11 @@
subprocess_handler = SubprocessWrapper()

def __init__(
self, url: str, branch: str, path: Path, commit: Optional[str] = None
self,
url: str,
branch: str,
realisation_path: Path,
commit: Optional[str] = None,
) -> None:
"""Return a `GitRepo` instance.

Expand All @@ -69,7 +126,7 @@
URL pointing to the GitHub repository.
branch: str
Name of a branch on the GitHub repository.
path: Path
realisation_path: Path
Path to a directory in which the repository is cloned into. If
`path` points to an existing directory, the repository will be
cloned into `path / branch`.
Expand All @@ -80,7 +137,9 @@
"""
self.url = url
self.branch = branch
self.path = path / branch if path.is_dir() else path
self.realisation_path = (

Check warning on line 140 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L140

Added line #L140 was not covered by tests
realisation_path / branch if realisation_path.is_dir() else realisation_path
)
self.commit = commit
self.logger = get_logger()

Expand All @@ -90,11 +149,11 @@
# remote progress. See
# https://gitpython.readthedocs.io/en/stable/reference.html#git.remote.RemoteProgress
self.subprocess_handler.run_cmd(
f"git clone --branch {self.branch} -- {self.url} {self.path}"
f"git clone --branch {self.branch} -- {self.url} {self.realisation_path}"
)
if self.commit:
self.logger.debug(f"Reset to commit {self.commit} (hard reset)")
repo = git.Repo(self.path)
repo = git.Repo(self.realisation_path)

Check warning on line 156 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L156

Added line #L156 was not covered by tests
repo.head.reset(self.commit, working_tree=True)
self.logger.info(
f"Successfully checked out {self.branch} - {self.get_revision()}"
Expand All @@ -109,7 +168,7 @@
Human readable string describing the latest revision.

"""
repo = git.Repo(self.path)
repo = git.Repo(self.realisation_path)

Check warning on line 171 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L171

Added line #L171 was not covered by tests
return f"commit {repo.head.commit.hexsha}"

def get_branch_name(self) -> str:
Expand Down Expand Up @@ -140,7 +199,7 @@
self,
svn_root: str,
branch_path: str,
path: Path,
realisation_path: Path,
revision: Optional[int] = None,
) -> None:
"""Return an `SVNRepo` instance.
Expand All @@ -151,7 +210,7 @@
URL pointing to the root of the SVN repository.
branch_path: str
Path to a branch relative to `svn_root`.
path: Path
realisation_path: Path
Path to a directory in which the branch is checked out into. If
`path` points to an existing directory, the repository will be
cloned into `path / <branch_name>` where `<branch_name>` is the
Expand All @@ -164,7 +223,11 @@
self.svn_root = svn_root
self.branch_path = branch_path
self.revision = revision
self.path = path / Path(branch_path).name if path.is_dir() else path
self.realisation_path = (

Check warning on line 226 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L226

Added line #L226 was not covered by tests
realisation_path / Path(branch_path).name
if realisation_path.is_dir()
else realisation_path
)
self.logger = get_logger()

def checkout(self):
Expand All @@ -174,12 +237,12 @@
if self.revision:
cmd += f" -r {self.revision}"

cmd += f" {internal.CABLE_SVN_ROOT}/{self.branch_path} {self.path}"
cmd += f" {internal.CABLE_SVN_ROOT}/{self.branch_path} {self.realisation_path}"

Check warning on line 240 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L240

Added line #L240 was not covered by tests

self.subprocess_handler.run_cmd(cmd)

self.logger.info(
f"Successfully checked out {self.path.name} - {self.get_revision()}"
f"Successfully checked out {self.realisation_path.name} - {self.get_revision()}"
)

def get_revision(self) -> str:
Expand All @@ -192,7 +255,7 @@

"""
proc = self.subprocess_handler.run_cmd(
f"svn info --show-item last-changed-revision {self.path}",
f"svn info --show-item last-changed-revision {self.realisation_path}",
capture_output=True,
)
return f"last-changed-revision {proc.stdout.strip()}"
Expand Down Expand Up @@ -233,7 +296,11 @@
if "git" in spec:
if "url" not in spec["git"]:
spec["git"]["url"] = internal.CABLE_GIT_URL
return GitRepo(path=path, **spec["git"])
return GitRepo(realisation_path=path, **spec["git"])

Check warning on line 299 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L299

Added line #L299 was not covered by tests
if "svn" in spec:
return SVNRepo(svn_root=internal.CABLE_SVN_ROOT, path=path, **spec["svn"])
return SVNRepo(

Check warning on line 301 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L301

Added line #L301 was not covered by tests
svn_root=internal.CABLE_SVN_ROOT, realisation_path=path, **spec["svn"]
)
if "local" in spec:
return LocalRepo(realisation_path=path, **spec["local"])

Check warning on line 305 in benchcab/utils/repo.py

View check run for this annotation

Codecov / codecov/patch

benchcab/utils/repo.py#L304-L305

Added lines #L304 - L305 were not covered by tests
raise RepoSpecError
14 changes: 12 additions & 2 deletions benchcab/workdir.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,29 @@
"""Functions for generating the directory structure used for `benchcab`."""

import shutil
from pathlib import Path

from benchcab import internal
from benchcab.utils.fs import mkdir


def clean_directory_tree():
"""Remove pre-existing directories in current working directory."""
def clean_realisation_files():
"""Remove files/directories related to CABLE realisation source codes."""
if internal.SRC_DIR.exists():
for realisation in internal.SRC_DIR.iterdir():
if realisation.is_symlink():
realisation.unlink()
shutil.rmtree(internal.SRC_DIR)


def clean_submission_files():
"""Remove files/directories related to PBS jobs."""
if internal.RUN_DIR.exists():
shutil.rmtree(internal.RUN_DIR)

for pbs_job_file in Path.cwd().glob(f"{internal.QSUB_FNAME}*"):
pbs_job_file.unlink()


def setup_fluxsite_directory_tree():
"""Generate the directory structure used by `benchcab`."""
Expand Down
Loading
Loading