Skip to content
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
109 changes: 96 additions & 13 deletions deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Target software deployment entrypoint.

- Reads pinned mache version from deploy/pins.cfg
- Reads CLI spec from deploy/cli_spec.json and builds argparse CLI
- Reads CLI spec from deploy/cli_spec.json plus optional
deploy/custom_cli_spec.json and builds argparse CLI
- Downloads mache/deploy/bootstrap.py for either:
* a given mache fork/branch, or
* the pinned mache version
Expand All @@ -25,6 +26,7 @@

PINS_CFG = os.path.join('deploy', 'pins.cfg')
CLI_SPEC_JSON = os.path.join('deploy', 'cli_spec.json')
CUSTOM_CLI_SPEC_JSON = os.path.join('deploy', 'custom_cli_spec.json')
DEPLOY_TMP_DIR = 'deploy_tmp'
BOOTSTRAP_PATH = os.path.join(DEPLOY_TMP_DIR, 'bootstrap.py')

Expand All @@ -40,6 +42,10 @@ def main():

pinned_mache_version, pinned_python_version = _read_pins(PINS_CFG)
cli_spec = _read_cli_spec(CLI_SPEC_JSON)
cli_spec = _merge_optional_cli_spec(
cli_spec,
_read_optional_cli_spec(CUSTOM_CLI_SPEC_JSON),
)

parser = _build_parser_from_cli_spec(cli_spec)
args = parser.parse_args(sys.argv[1:])
Expand Down Expand Up @@ -225,6 +231,74 @@ def _read_cli_spec(spec_path):
return spec


def _read_optional_cli_spec(spec_path):
if not os.path.exists(spec_path):
return None

try:
with open(spec_path, 'r', encoding='utf-8') as f:
spec = json.load(f)
except (OSError, json.JSONDecodeError) as e:
raise SystemExit(f'ERROR: Failed to parse {spec_path}: {e!r}') from e

if not isinstance(spec, dict):
raise SystemExit(f'ERROR: {spec_path} must contain a JSON object')
if 'arguments' not in spec:
raise SystemExit(
f"ERROR: {spec_path} must contain top-level key 'arguments'"
)
if not isinstance(spec['arguments'], list):
raise SystemExit(f"ERROR: {spec_path} 'arguments' must be a list")
meta = spec.get('meta')
if meta is not None and not isinstance(meta, dict):
raise SystemExit(f"ERROR: {spec_path} 'meta' must be an object")

return spec


def _merge_optional_cli_spec(cli_spec, custom_cli_spec):
if custom_cli_spec is None:
return cli_spec

merged_meta = dict(cli_spec.get('meta', {})) # type: dict
merged_arguments = list(cli_spec.get('arguments', [])) # type: list
merged = {
'meta': merged_meta,
'arguments': merged_arguments,
}

seen_dests = set()
seen_flags = set()
for entry in merged_arguments:
dest = entry.get('dest')
if dest:
seen_dests.add(dest)
for flag in entry.get('flags', []):
seen_flags.add(flag)

for entry in custom_cli_spec['arguments']:
dest = entry.get('dest')
if dest in seen_dests:
raise SystemExit(
'ERROR: deploy/custom_cli_spec.json duplicates generated '
f"dest '{dest}'"
)
flags = entry.get('flags', [])
duplicate_flags = [flag for flag in flags if flag in seen_flags]
if duplicate_flags:
dup_str = ', '.join(duplicate_flags)
raise SystemExit(
'ERROR: deploy/custom_cli_spec.json duplicates generated '
f'flags: {dup_str}'
)
merged_arguments.append(entry)
if dest:
seen_dests.add(dest)
seen_flags.update(flags)

return merged


def _build_parser_from_cli_spec(cli_spec):
description = cli_spec.get('meta', {}).get(
'description', 'Deploy E3SM software environment'
Expand Down Expand Up @@ -468,21 +542,30 @@ def _run_mache_deploy_run(pixi_exe, repo_root, mache_run_argv):
f'ERROR: bootstrap pixi project not found. Expected: {pixi_toml}'
)

# Build a bash command that runs mache inside pixi, then cd's to repo.
mache_cmd = 'mache deploy run'
env = os.environ.copy()
for var in (
'PIXI_PROJECT_MANIFEST',
'PIXI_PROJECT_ROOT',
'PIXI_ENVIRONMENT_NAME',
'PIXI_IN_SHELL',
):
env.pop(var, None)

cmd = [
pixi_exe,
'run',
'-m',
pixi_toml,
'--',
'mache',
'deploy',
'run',
]
if mache_run_argv:
mache_cmd = f'{mache_cmd} ' + ' '.join(
shlex.quote(a) for a in mache_run_argv
)
cmd.extend(mache_run_argv)

cmd = (
f'env -u PIXI_PROJECT_MANIFEST -u PIXI_PROJECT_ROOT '
f'-u PIXI_ENVIRONMENT_NAME -u PIXI_IN_SHELL '
f'{shlex.quote(pixi_exe)} run -m {shlex.quote(pixi_toml)} bash -lc '
f'{shlex.quote("cd " + repo_root + " && " + mache_cmd)}'
)
try:
subprocess.check_call(['/bin/bash', '-lc', cmd])
subprocess.run(cmd, cwd=repo_root, env=env, check=True)
except subprocess.CalledProcessError as e:
raise SystemExit(
f'\nERROR: Deployment step failed (exit code {e.returncode}). '
Expand Down
2 changes: 1 addition & 1 deletion deploy/cli_spec.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"meta": {
"software": "polaris",
"mache_version": "3.0.4",
"mache_version": "3.2.0",
"description": "Deploy polaris environment"
},
"arguments": [
Expand Down
11 changes: 11 additions & 0 deletions deploy/config.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ jigsaw:
# Relative path in the target repo where JIGSAW-Python lives.
jigsaw_python_path: jigsaw-python

permissions:
# Optional overrides for post-deploy permission updates.
# If unset here, `mache deploy run` falls back to:
# 1. hook-provided runtime permissions
# 2. machine config [deploy] group/world_readable
# 3. legacy machine config [e3sm_unified] group
group: null

# Whether deployed files should be readable outside the shared group.
world_readable: true

# Deployment hooks
hooks:
file: "deploy/hooks.py"
Expand Down
12 changes: 7 additions & 5 deletions deploy/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def pre_pixi(ctx: DeployContext) -> dict[str, Any] | None:
"""

polaris_version = _get_version()
mpi = _get_pixi_mpi(ctx.machine, ctx.machine_config)
mpi = _get_pixi_mpi(ctx.machine, ctx.machine_config, ctx.args)

updates: Dict[str, Any] = {
'project': {'version': polaris_version},
Expand Down Expand Up @@ -102,15 +102,17 @@ def _get_version():
return polaris_version


def _get_pixi_mpi(machine, machine_config):
def _get_pixi_mpi(machine, machine_config, args):
"""
Get the MPI implementation for pixi from environment variable
"""
if machine is not None:
# we will use system compilers and mpi, not pixi mpi
if machine is not None and not getattr(args, 'no_spack', False):
# On supported machines with spack enabled, we use the system MPI
# through spack rather than installing an MPI stack in pixi.
mpi = 'nompi'
else:
# we will have the default-<system>.cfg config options
# For unknown machines, and for explicit --no-spack deployments on
# known machines, pixi must provide the MPI-aware dependency stack.
if not machine_config.has_section('deploy'):
raise ValueError("Missing 'deploy' section in machine config")
section = machine_config['deploy']
Expand Down
41 changes: 35 additions & 6 deletions deploy/load.sh
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
# bash snippet for adding Polaris-specific environment variables

_polaris_detect_prefix() {
local helper="$1"

if command -v "${helper}" >/dev/null 2>&1; then
dirname "$(dirname "$(command -v "${helper}")")"
return 0
fi

if [ -n "${CONDA_PREFIX:-}" ] && [ -d "${CONDA_PREFIX}" ]; then
printf '%s\n' "${CONDA_PREFIX}"
return 0
fi

return 1
}

_polaris_stack_root="${MACHE_DEPLOY_SPACK_LIBRARY_VIEW}"
if [ -z "${_polaris_stack_root}" ] && [ -n "${CONDA_PREFIX:-}" ]; then
_polaris_stack_root="${CONDA_PREFIX}"
fi

# we need a special approach for cray machines ($POLARIS_MACHINE), notably
# pm-cpu and pm-gpu
if [ "$POLARIS_MACHINE" = "pm-cpu" ] || [ "$POLARIS_MACHINE" = "pm-gpu" ]; then
export NETCDF=${CRAY_NETCDF_HDF5PARALLEL_PREFIX}
export NETCDFF=${CRAY_NETCDF_HDF5PARALLEL_PREFIX}
export PNETCDF=${CRAY_PARALLEL_NETCDF_PREFIX}
else
export NETCDF=$(dirname $(dirname $(which nc-config)))
export NETCDFF=$(dirname $(dirname $(which nf-config)))
export PNETCDF=$(dirname $(dirname $(which pnetcdf-config)))
if _polaris_detect_prefix nc-config >/dev/null 2>&1; then
export NETCDF=$(_polaris_detect_prefix nc-config)
fi
if _polaris_detect_prefix nf-config >/dev/null 2>&1; then
export NETCDFF=$(_polaris_detect_prefix nf-config)
fi
if command -v pnetcdf-config >/dev/null 2>&1; then
export PNETCDF=$(dirname "$(dirname "$(command -v pnetcdf-config)")")
elif [ -n "${CONDA_PREFIX:-}" ] && ls "${CONDA_PREFIX}"/lib/libpnetcdf* >/dev/null 2>&1; then
export PNETCDF="${CONDA_PREFIX}"
fi
fi

export PIO=${MACHE_DEPLOY_SPACK_LIBRARY_VIEW}
export METIS_ROOT=${MACHE_DEPLOY_SPACK_LIBRARY_VIEW}
export PARMETIS_ROOT=${MACHE_DEPLOY_SPACK_LIBRARY_VIEW}
export PIO=${_polaris_stack_root}
export METIS_ROOT=${_polaris_stack_root}
export PARMETIS_ROOT=${_polaris_stack_root}

export USE_PIO2=true
export OPENMP=true
Expand Down
2 changes: 1 addition & 1 deletion deploy/pins.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
bootstrap_python = 3.14
python = 3.14
geometric_features = 1.6.1
mache = 3.0.4
mache = 3.2.0
mpas_tools = 1.4.0
otps = 2021.10
parallelio = 2.6.9
Expand Down
2 changes: 1 addition & 1 deletion polaris/job/job_script.pbs.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{%- if account != '' %}
#PBS -A {{ account }}
{%- endif %}
#PBS -l select={{ nodes }}{% if gpus_per_node != '' %}:ngpus={{ gpus_per_node }}{% endif %}
#PBS -l select={{ nodes }}
#PBS -l walltime={{ wall_time }}
#PBS -V
{%- if queue != '' %}
Expand Down
6 changes: 6 additions & 0 deletions polaris/machines/aurora.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ polaris_envs = /lus/flare/projects/E3SM_Dec/soft/polaris/aurora/base
# Options related to deploying polaris environments on supported machines
[deploy]

# the unix group for permissions for deployed polaris environments
group = E3SMinput

# whether deployed environments should be readable outside the shared group
world_readable = true

# the compiler set to use for system libraries and MPAS builds
compiler = oneapi-ifx

Expand Down
6 changes: 6 additions & 0 deletions polaris/machines/chrysalis.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ polaris_envs = /lcrc/soft/climate/polaris/chrysalis/base
# Options related to deploying polaris environments on supported machines
[deploy]

# the unix group for permissions for deployed polaris environments
group = cels

# whether deployed environments should be readable outside the shared group
world_readable = true

# the compiler set to use for system libraries and MPAS builds
compiler = intel

Expand Down
6 changes: 6 additions & 0 deletions polaris/machines/frontier.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ polaris_envs = /ccs/proj/cli115/software/polaris/frontier/conda/base
# Options related to deploying polaris environments on supported machines
[deploy]

# the unix group for permissions for deployed polaris environments
group = cli115

# whether deployed environments should be readable outside the shared group
world_readable = true

# the compiler set to use for system libraries and MPAS builds
compiler = craygnu

Expand Down
6 changes: 6 additions & 0 deletions polaris/machines/pm-cpu.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ polaris_envs = /global/common/software/e3sm/polaris/pm-cpu/conda/base
# Options related to deploying polaris environments on supported machines
[deploy]

# the unix group for permissions for deployed polaris environments
group = e3sm

# whether deployed environments should be readable outside the shared group
world_readable = true

# the compiler set to use for system libraries and MPAS builds
compiler = gnu

Expand Down
6 changes: 6 additions & 0 deletions polaris/machines/pm-gpu.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ polaris_envs = /global/common/software/e3sm/polaris/pm-gpu/conda/base
# Options related to deploying polaris environments on supported machines
[deploy]

# the unix group for permissions for deployed polaris environments
group = e3sm

# whether deployed environments should be readable outside the shared group
world_readable = true

# the compiler set to use for system libraries and MPAS builds
compiler = gnugpu

Expand Down
Loading
Loading