Skip to content

Commit 31d75cb

Browse files
authored
Merge pull request #135 from xylar/detect-dev-labels-in-bootstrap
Automatically detect E3SM-Unified version and dev labels
2 parents 91d91ab + 7cdd69b commit 31d75cb

15 files changed

+205
-105
lines changed

.github/workflows/build_workflow.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ jobs:
4444
- name: Finalize Build Environment
4545
run: |
4646
conda install -y conda conda-build
47-
conda build -m "recipes/e3sm-unified/configs/mpi_${{ matrix.mpi }}_python${{ matrix.python-version }}.yaml" "recipes/e3sm-unified"
47+
cd recipes/e3sm-unified
48+
./build_packages.py --conda ${CONDA_PREFIX} \
49+
--python ${{ matrix.python-version }} \
50+
--mpi ${{ matrix.mpi }}
4851
4952
- name: Install E3SM-Unified
5053
run: |

e3sm_supported_machines/bootstrap.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from shared import (
1515
check_call,
1616
get_conda_base,
17+
get_rc_dev_labels,
1718
install_miniforge3,
1819
parse_args,
1920
)
@@ -132,24 +133,20 @@ def build_env(is_test, recreate, compiler, mpi, conda_mpi, version,
132133

133134
if is_test:
134135

135-
nco_dev = ('alpha' in nco_spec or 'beta' in nco_spec)
136-
137136
channels = '--override-channels'
138137
if local_conda_build is not None:
139138
channels = f'{channels} -c {local_conda_build}'
140139

141-
if nco_dev:
142-
channels = f'{channels} -c conda-forge/label/nco_dev'
143-
for package in ['moab']:
144-
spec = config.get('spack_specs', package)
145-
if 'rc' in spec.lower():
146-
channels = f'{channels} -c conda-forge/label/{package}_dev'
147-
148-
# edit if not using a release candidate for a given package
149-
dev_labels = ['e3sm_unified', 'chemdyg', 'e3sm_diags',
150-
'mpas_analysis', 'zppy', 'zstash']
151-
for package in dev_labels:
152-
channels = f'{channels} -c conda-forge/label/{package}_dev'
140+
meta_yaml_path = os.path.join(
141+
os.path.dirname(__file__),
142+
"..",
143+
"recipes",
144+
"e3sm-unified",
145+
"meta.yaml"
146+
)
147+
dev_labels = get_rc_dev_labels(meta_yaml_path)
148+
for dev_label in dev_labels:
149+
channels = f'{channels} -c conda-forge/label/{dev_label}'
153150
channels = f'{channels} ' \
154151
f'-c conda-forge '
155152
else:

e3sm_supported_machines/shared.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,28 @@
88
try:
99
from urllib.request import urlopen, Request
1010
except ImportError:
11+
# For Python 2 compatibility
1112
from urllib2 import urlopen, Request
1213

14+
LABELS = {
15+
"chemdyg": "chemdyg_dev",
16+
"e3sm_diags": "e3sm_diags_dev",
17+
"e3sm_to_cmip": "e3sm_to_cmip_dev",
18+
"mache": "mache_dev",
19+
"moab": "moab_dev",
20+
"mpas-analysis": "mpas_analysis_dev",
21+
"mpas_tools": "mpas_tools_dev",
22+
"nco": "nco_dev",
23+
"xcdat": "xcdat_dev",
24+
"zppy": "zppy_dev",
25+
"zstash": "zstash_dev",
26+
}
27+
1328

1429
def parse_args(bootstrap):
1530
parser = argparse.ArgumentParser(
1631
description='Deploy E3SM-Unified')
17-
parser.add_argument("--version", dest="version", default="1.11.1",
32+
parser.add_argument("--version", dest="version",
1833
help="The version of E3SM-Unified to deploy")
1934
parser.add_argument("--conda", dest="conda_base",
2035
help="Path for the conda base")
@@ -56,6 +71,16 @@ def parse_args(bootstrap):
5671
raise ValueError('You must supply both or neither of '
5772
'--mache_fork and --mache_branch')
5873

74+
if args.version is None:
75+
meta_yaml_path = os.path.join(
76+
os.path.dirname(__file__),
77+
"..",
78+
"recipes",
79+
"e3sm-unified",
80+
"meta.yaml"
81+
)
82+
args.version = get_version_from_meta(meta_yaml_path)
83+
5984
return args
6085

6186

@@ -125,3 +150,64 @@ def get_conda_base(conda_base, config, shared):
125150
# handle "~" in the path
126151
conda_base = os.path.abspath(os.path.expanduser(conda_base))
127152
return conda_base
153+
154+
155+
def get_rc_dev_labels(meta_yaml_path):
156+
"""Parse meta.yaml and return a list of dev labels for RC dependencies."""
157+
158+
# a rare case where module-level imports are not a good idea because
159+
# the deploy_e3sm_unified.py script may be called from an environment
160+
# where jinja2 and yaml are not installed.
161+
import yaml
162+
from jinja2 import Template
163+
164+
labels_dict = LABELS
165+
166+
# Render the jinja template with dummy/default values
167+
with open(meta_yaml_path) as f:
168+
template_text = f.read()
169+
# Provide dummy/default values for all jinja variables used in meta.yaml
170+
template = Template(template_text)
171+
rendered = template.render(
172+
mpi='mpich', # or any valid value
173+
py='310', # or any valid value
174+
CONDA_PY='310', # used in build string
175+
)
176+
meta = yaml.safe_load(rendered)
177+
dev_labels = []
178+
run_reqs = meta.get("requirements", {}).get("run", [])
179+
for req in run_reqs:
180+
# req can be a string like "pkgname version" or just "pkgname"
181+
if isinstance(req, str):
182+
parts = req.split()
183+
pkg = parts[0]
184+
version = " ".join(parts[1:]) if len(parts) > 1 else ""
185+
186+
# NCO is special: it has a dev label for alpha/beta versions
187+
if pkg == "nco" and ('alpha' in version or 'beta' in version):
188+
label = labels_dict[pkg]
189+
if label not in dev_labels:
190+
dev_labels.append(label)
191+
192+
# Only match 'rc' in version, not in pkg name
193+
if "rc" in version and pkg in labels_dict:
194+
label = labels_dict[pkg]
195+
if label not in dev_labels:
196+
dev_labels.append(label)
197+
return dev_labels
198+
199+
200+
def get_version_from_meta(meta_yaml_path):
201+
"""Parse the version from the {% set version = ... %} line in meta.yaml."""
202+
with open(meta_yaml_path) as f:
203+
for line in f:
204+
if line.strip().startswith("{% set version"):
205+
# e.g., {% set version = "1.11.1rc1" %}
206+
parts = line.split("=")
207+
if len(parts) >= 2:
208+
version = (
209+
parts[1].strip().strip('%}').strip().strip(
210+
'"').strip("'")
211+
)
212+
return version
213+
raise ValueError("Could not find version in meta.yaml")

recipes/e3sm-unified/build_package.bash

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import shutil
4+
import subprocess
5+
import argparse
6+
7+
from jinja2 import Template
8+
9+
from shared import get_rc_dev_labels, get_version_from_meta
10+
11+
DEV_PYTHON_VERSIONS = ["3.10"]
12+
DEV_MPI_VERSIONS = ["nompi", "hpc"]
13+
14+
RELEASE_PYTHON_VERSIONS = ["3.9", "3.10"]
15+
RELEASE_MPI_VERSIONS = ["nompi", "mpich", "openmpi", "hpc"]
16+
17+
18+
def generate_matrix_files(dev, python_versions, mpi_versions):
19+
with open("configs/template.yaml") as f:
20+
template_text = f.read()
21+
template = Template(template_text)
22+
if python_versions is None:
23+
if dev:
24+
python_versions = DEV_PYTHON_VERSIONS
25+
else:
26+
python_versions = RELEASE_PYTHON_VERSIONS
27+
if mpi_versions is None:
28+
if dev:
29+
mpi_versions = DEV_MPI_VERSIONS
30+
else:
31+
mpi_versions = RELEASE_MPI_VERSIONS
32+
matrix_files = []
33+
for python in python_versions:
34+
for mpi in mpi_versions:
35+
script = template.render(python=python, mpi=mpi)
36+
filename = f"configs/mpi_{mpi}_python{python}.yaml"
37+
with open(filename, "w") as handle:
38+
handle.write(script)
39+
matrix_files.append(filename)
40+
return matrix_files
41+
42+
43+
def main():
44+
parser = argparse.ArgumentParser(
45+
description="Build E3SM-Unified conda packages."
46+
)
47+
parser.add_argument(
48+
"--conda",
49+
type=str,
50+
default=os.path.expanduser("~/miniforge3"),
51+
help="Path to the conda base directory (default: ~/miniforge3)."
52+
)
53+
parser.add_argument(
54+
"--python",
55+
nargs="+",
56+
help="Python version(s) to build for (overrides default matrix)."
57+
)
58+
parser.add_argument(
59+
"--mpi",
60+
nargs="+",
61+
help="MPI variant(s) to build for (overrides default matrix)."
62+
)
63+
args = parser.parse_args()
64+
65+
conda_dir = os.path.expanduser(args.conda)
66+
meta_yaml_path = os.path.join(os.path.dirname(__file__), "meta.yaml")
67+
version = get_version_from_meta(meta_yaml_path)
68+
dev = "rc" in version
69+
70+
# Remove conda-bld directory if it exists
71+
bld_dir = os.path.join(conda_dir, "conda-bld")
72+
if os.path.exists(bld_dir):
73+
shutil.rmtree(bld_dir)
74+
75+
# Generate matrix files on the fly
76+
matrix_files = generate_matrix_files(
77+
dev,
78+
python_versions=args.python,
79+
mpi_versions=args.mpi,
80+
)
81+
82+
dev_labels = []
83+
if dev:
84+
dev_labels = get_rc_dev_labels(meta_yaml_path)
85+
86+
channels = []
87+
for label in dev_labels:
88+
channels.extend(["-c", f"conda-forge/label/{label}"])
89+
channels += [
90+
"-c", "conda-forge"
91+
]
92+
93+
for file in matrix_files:
94+
cmd = [
95+
"conda", "build", "-m", file, "--override-channels"
96+
] + channels + ["."]
97+
print("Running:", " ".join(cmd))
98+
subprocess.run(cmd, check=True)
99+
100+
101+
if __name__ == "__main__":
102+
main()

recipes/e3sm-unified/configs/generate.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

recipes/e3sm-unified/configs/mpi_hpc_python3.10.yaml

Lines changed: 0 additions & 5 deletions
This file was deleted.

recipes/e3sm-unified/configs/mpi_hpc_python3.9.yaml

Lines changed: 0 additions & 5 deletions
This file was deleted.

recipes/e3sm-unified/configs/mpi_mpich_python3.10.yaml

Lines changed: 0 additions & 5 deletions
This file was deleted.

recipes/e3sm-unified/configs/mpi_mpich_python3.9.yaml

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)