summaryrefslogtreecommitdiff
path: root/var/spack/repos/builtin/packages/wrf/package.py
diff options
context:
space:
mode:
Diffstat (limited to 'var/spack/repos/builtin/packages/wrf/package.py')
-rw-r--r--var/spack/repos/builtin/packages/wrf/package.py384
1 files changed, 283 insertions, 101 deletions
diff --git a/var/spack/repos/builtin/packages/wrf/package.py b/var/spack/repos/builtin/packages/wrf/package.py
index 787e180083..b0a9fd450b 100644
--- a/var/spack/repos/builtin/packages/wrf/package.py
+++ b/var/spack/repos/builtin/packages/wrf/package.py
@@ -5,8 +5,53 @@
from spack import *
+from sys import stdout
import glob
-import tempfile
+from os import O_NONBLOCK, rename
+from os.path import basename
+from fcntl import fcntl, F_GETFL, F_SETFL
+from subprocess import Popen, PIPE
+import time
+from llnl.util import tty
+import re
+
+re_optline = re.compile(r'\s+[0-9]+\..*\((serial|smpar|dmpar|dm\+sm)\)\s+')
+re_paroptname = re.compile(r'\((serial|smpar|dmpar|dm\+sm)\)')
+re_paroptnum = re.compile(r'\s+([0-9]+)\.\s+\(')
+re_nestline = re.compile(r'\(([0-9]+=[^)0-9]+)+\)')
+re_nestoptnum = re.compile(r'([0-9]+)=')
+re_nestoptname = re.compile(r'=([^,)]+)')
+
+
+def setNonBlocking(fd):
+ """
+ Set the given file descriptor to non-blocking
+ """
+ flags = fcntl(fd, F_GETFL) | O_NONBLOCK
+ fcntl(fd, F_SETFL, flags)
+
+
+def collect_platform_options(stdoutpipe):
+ # Attempt to parse to collect options
+ optiondict = {}
+ for line in stdoutpipe.splitlines():
+ if re_optline.match(line):
+ numbers = re_paroptnum.findall(line)
+ entries = re_paroptname.findall(line)
+ paropts = dict(zip(entries, numbers))
+ platline = re_optline.sub("", line).strip()
+ optiondict[platline] = paropts
+
+ return optiondict
+
+
+def collect_nesting_options(stdoutpipe):
+ nestoptline = re_nestline.search(stdoutpipe)[0]
+ nestoptnum = re_nestoptnum.findall(nestoptline)
+ nestoptname = re_nestoptname.findall(nestoptline)
+ nestoptname = [x.replace(" ", "_") for x in nestoptname]
+
+ return dict(zip(nestoptname, nestoptnum))
class Wrf(Package):
@@ -15,132 +60,269 @@ class Wrf(Package):
for both atmospheric research and operational forecasting applications.
"""
- homepage = "https://www.mmm.ucar.edu/weather-research-and-forecasting-model"
- url = "https://github.com/wrf-model/WRF/archive/v4.2.tar.gz"
- maintainers = ['MichaelLaufer']
-
- version('4.2', sha256='c39a1464fd5c439134bbd39be632f7ce1afd9a82ad726737e37228c6a3d74706')
- version('4.0', sha256='a5b072492746f96a926badda7e6b44cb0af26695afdd6c029a94de5e1e5eec73')
+ homepage = "https://www.mmm.ucar.edu/weather-research-and-forecasting-model"
+ url = "https://github.com/wrf-model/WRF/archive/v4.2.tar.gz"
+ maintainers = ["MichaelLaufer", "ptooley"]
- variant('build_type', default='dmpar',
- values=('serial', 'smpar', 'dmpar', 'dmsm'))
+ version("4.2", sha256="c39a1464fd5c439134bbd39be632f7ce1afd9a82ad726737e37228c6a3d74706")
+ version("4.0", sha256="9718f26ee48e6c348d8e28b8bc5e8ff20eafee151334b3959a11b7320999cf65")
+ version("3.9.1.1", sha256="a04f5c425bedd262413ec88192a0f0896572cc38549de85ca120863c43df047a", url="https://github.com/wrf-model/WRF/archive/V3.9.1.1.tar.gz")
- variant('nesting', default='basic',
- values=('basic', 'preset', 'vortex'))
+ variant(
+ "build_type",
+ default="dmpar",
+ values=("serial", "smpar", "dmpar", "dm+sm"),
+ )
+ variant(
+ "nesting",
+ default="basic",
+ values=("no_nesting", "basic", "preset_moves", "vortex_following"),
+ )
+ variant(
+ "compile_type",
+ default="em_real",
+ values=(
+ "em_real",
+ "em_quarter_ss",
+ "em_b_wave",
+ "em_les",
+ "em_heldsuarez",
+ "em_tropical_cyclone",
+ "em_hill2d_x",
+ "em_squall2d_x",
+ "em_squall2d_y",
+ "em_grav2d_x",
+ "em_seabreeze2d_x",
+ "em_scm_xy",
+ ),
+ )
+ variant(
+ "pnetcdf",
+ default=True,
+ description="Parallel IO support through Pnetcdf library",
+ )
- variant('compile_type', default='em_real',
- values=('em_real', 'em_quarter_ss', 'em_b_wave', 'em_les',
- 'em_heldsuarez', 'em_tropical_cyclone', 'em_hill2d_x',
- 'em_squall2d_x', 'em_squall2d_y', 'em_grav2d_x',
- 'em_seabreeze2d_x', 'em_scm_xy'))
-
- variant('pnetcdf', default=True,
- description='Parallel IO support through Pnetcdf libray')
+ patch("patches/3.9/netcdf_backport.patch", when="@3.9.1.1")
+ patch("patches/3.9/tirpc_detect.patch", when="@3.9.1.1")
+ patch("patches/3.9/add_aarch64.patch", when="@3.9.1.1")
# These patches deal with netcdf & netcdf-fortran being two diff things
# Patches are based on:
# https://github.com/easybuilders/easybuild-easyconfigs/blob/master/easybuild/easyconfigs/w/WRF/WRF-3.5_netCDF-Fortran_separate_path.patch
- patch('patches/4.0/arch.Config.pl.patch', when='@4.0')
- patch('patches/4.0/arch.configure.defaults.patch', when='@4.0')
- patch('patches/4.0/arch.conf_tokens.patch', when='@4.0')
- patch('patches/4.0/arch.postamble.patch', when='@4.0')
- patch('patches/4.0/configure.patch', when='@4.0')
- patch('patches/4.0/external.io_netcdf.makefile.patch', when='@4.0')
- patch('patches/4.0/Makefile.patch', when='@4.0')
-
- patch('patches/4.2/arch.Config.pl.patch', when='@4.2')
- patch('patches/4.2/arch.configure.defaults.patch', when='@4.2')
- patch('patches/4.2/arch.conf_tokens.patch', when='@4.2')
- patch('patches/4.2/arch.postamble.patch', when='@4.2')
- patch('patches/4.2/configure.patch', when='@4.2')
- patch('patches/4.2/external.io_netcdf.makefile.patch', when='@4.2')
- patch('patches/4.2/var.gen_be.Makefile.patch', when='@4.2')
- patch('patches/4.2/Makefile.patch', when='@4.2')
-
- depends_on('mpi')
+ patch("patches/4.0/arch.Config.pl.patch", when="@4.0")
+ patch("patches/4.0/arch.configure.defaults.patch", when="@4.0")
+ patch("patches/4.0/arch.conf_tokens.patch", when="@4.0")
+ patch("patches/4.0/arch.postamble.patch", when="@4.0")
+ patch("patches/4.0/configure.patch", when="@4.0")
+ patch("patches/4.0/external.io_netcdf.makefile.patch", when="@4.0")
+ patch("patches/4.0/Makefile.patch", when="@4.0")
+ patch("patches/4.0/tirpc_detect.patch", when="@4.0")
+ patch("patches/4.0/add_aarch64.patch", when="@4.0")
+
+ patch("patches/4.2/arch.Config.pl.patch", when="@4.2")
+ patch("patches/4.2/arch.configure.defaults.patch", when="@4.2")
+ patch("patches/4.2/arch.conf_tokens.patch", when="@4.2")
+ patch("patches/4.2/arch.postamble.patch", when="@4.2")
+ patch("patches/4.2/configure.patch", when="@4.2")
+ patch("patches/4.2/external.io_netcdf.makefile.patch", when="@4.2")
+ patch("patches/4.2/var.gen_be.Makefile.patch", when="@4.2")
+ patch("patches/4.2/Makefile.patch", when="@4.2")
+ patch("patches/4.2/tirpc_detect.patch", when="@4.2")
+ patch("patches/4.2/add_aarch64.patch", when="@4.2")
+
+ depends_on("pkgconfig", type=("build"))
+ depends_on("libtirpc")
+
+ depends_on("mpi")
# According to:
# http://www2.mmm.ucar.edu/wrf/users/docs/user_guide_v4/v4.0/users_guide_chap2.html#_Required_Compilers_and_1
# Section: "Required/Optional Libraries to Download"
- depends_on('parallel-netcdf', when='+pnetcdf')
- depends_on('netcdf-c')
- depends_on('netcdf-fortran')
- depends_on('jasper')
- depends_on('libpng')
- depends_on('zlib')
- depends_on('perl')
+ depends_on("parallel-netcdf", when="+pnetcdf")
+ depends_on("netcdf-c")
+ depends_on("netcdf-fortran")
+ depends_on("jasper")
+ depends_on("libpng")
+ depends_on("zlib")
+ depends_on("perl")
# not sure if +fortran is required, but seems like a good idea
- depends_on('hdf5+fortran+hl+mpi')
+ depends_on("hdf5+fortran+hl+mpi")
# build script use csh
- depends_on('tcsh', type=('build'))
+ depends_on("tcsh", type=("build"))
# time is not installed on all systems b/c bash provides it
# this fixes that for csh install scripts
- depends_on('time', type=('build'))
- depends_on('m4', type='build')
- depends_on('libtool', type='build')
- phases = ['configure', 'build', 'install']
+ depends_on("time", type=("build"))
+ depends_on("m4", type="build")
+ depends_on("libtool", type="build")
+ phases = ["configure", "build", "install"]
def setup_build_environment(self, env):
- env.set('NETCDF', self.spec['netcdf-c'].prefix)
- if '+pnetcdf' in self.spec:
- env.set('PNETCDF', self.spec['parallel-netcdf'].prefix)
+ env.set("NETCDF", self.spec["netcdf-c"].prefix)
+ if "+pnetcdf" in self.spec:
+ env.set("PNETCDF", self.spec["parallel-netcdf"].prefix)
# This gets used via the applied patch files
- env.set('NETCDFF', self.spec['netcdf-fortran'].prefix)
- env.set('PHDF5', self.spec['hdf5'].prefix)
- env.set('JASPERINC', self.spec['jasper'].prefix.include)
- env.set('JASPERLIB', self.spec['jasper'].prefix.lib)
+ env.set("NETCDFF", self.spec["netcdf-fortran"].prefix)
+ env.set("PHDF5", self.spec["hdf5"].prefix)
+ env.set("JASPERINC", self.spec["jasper"].prefix.include)
+ env.set("JASPERLIB", self.spec["jasper"].prefix.lib)
- if self.spec.satisfies('%gcc@10:'):
- args = '-w -O2 -fallow-argument-mismatch -fallow-invalid-boz'
- env.set('FCFLAGS', args)
- env.set('FFLAGS', args)
+ if self.spec.satisfies("%gcc@10:"):
+ args = "-w -O2 -fallow-argument-mismatch -fallow-invalid-boz"
+ env.set("FCFLAGS", args)
+ env.set("FFLAGS", args)
def patch(self):
# Let's not assume csh is intalled in bin
- files = glob.glob('*.csh')
+ files = glob.glob("*.csh")
+
+ filter_file("^#!/bin/csh -f", "#!/usr/bin/env csh", *files)
+ filter_file("^#!/bin/csh", "#!/usr/bin/env csh", *files)
+
+ def answer_configure_question(self, outputbuf):
- filter_file('^#!/bin/csh -f', '#!/usr/bin/env csh', *files)
- filter_file('^#!/bin/csh', '#!/usr/bin/env csh', *files)
+ # Platform options question:
+ if "Please select from among the following" in outputbuf:
+ options = collect_platform_options(outputbuf)
+ comp_pair = "%s/%s" % (
+ basename(self.compiler.fc),
+ basename(self.compiler.cc),
+ )
+ compiler_matches = dict(
+ (x, y) for x, y in options.items() if comp_pair in x.lower()
+ )
+ if len(compiler_matches) > 1:
+ tty.warn("Found multiple potential build options")
+ try:
+ compiler_key = min(compiler_matches.keys(), key=len)
+ tty.warn("Selected build option %s." % compiler_key)
+ return (
+ "%s\n"
+ % compiler_matches[compiler_key][
+ self.spec.variants["build_type"].value
+ ]
+ )
+ except KeyError:
+ InstallError(
+ "build_type %s unsupported for %s compilers"
+ % (self.spec.variants["build_type"].value, comp_pair)
+ )
+
+ if "Compile for nesting?" in outputbuf:
+ options = collect_nesting_options(outputbuf)
+ try:
+ return "%s\n" % options[self.spec.variants["nesting"].value]
+ except KeyError:
+ InstallError("Failed to parse correct nesting option")
+
+ def do_configure_fixup(self):
+ # Fix mpi compiler wrapper aliases
+ if self.spec.satisfies("@3.9.1.1 %gcc"):
+ rename(
+ "./arch/configure_new.defaults",
+ "./arch/configure_new.defaults.bak",
+ )
+ with open("./arch/configure_new.defaults.bak", "rt") as ifh:
+ with open("./arch/configure_new.defaults", "wt") as ofh:
+ for line in ifh:
+ if line.startswith("DM_"):
+ line = line.replace(
+ "mpif90 -f90=$(SFC)", self.spec['mpi'].mpif90
+ )
+ line = line.replace(
+ "mpicc -cc=$(SCC)", self.spec['mpi'].mpicc
+ )
+ ofh.write(line)
def configure(self, spec, prefix):
- build_opts = {"gcc": {"serial": '32',
- "smpar": '33',
- "dmpar": '34',
- "dmsm": '35'},
- "intel": {"serial": '13',
- "smpar": '14',
- "dmpar": '15',
- "dmsm": '16'},
- "pgi": {"serial": '52',
- "smpar": '53',
- "dmpar": '54',
- "dmsm": '55'},
- }
-
- nesting_opts = {"basic": "1",
- "preset": "2",
- "vortex": "3"}
-
- try:
- compiler_opts = build_opts[self.spec.compiler.name]
- except KeyError:
- raise InstallError("Compiler not recognized nor supported.")
-
- # Spack already makes sure that the variant value is part of the set.
- build_type = compiler_opts[spec.variants['build_type'].value]
-
- nesting_value = nesting_opts[spec.variants['nesting'].value]
-
- with tempfile.TemporaryFile(mode='w') as fp:
- fp.write(build_type + '\n' + nesting_value + '\n')
- fp.seek(0)
- Executable('./configure')(input=fp)
- def build(self, spec, prefix):
- csh = which('csh')
+ # Remove broken default options...
+ self.do_configure_fixup()
+
+ if self.spec.compiler.name not in ["intel", "gcc"]:
+ raise InstallError(
+ "Compiler %s not currently supported for WRF build."
+ % self.spec.compiler.name
+ )
+
+ p = Popen("./configure", stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ setNonBlocking(p.stdout)
+ setNonBlocking(p.stderr)
+
+ # Because of WRFs custom configure scripts that require interactive
+ # input we need to parse and respond to questions. The details can
+ # vary somewhat with the exact version, so try to detect and fail
+ # gracefully on unexpected questions.
+ stallcounter = 0
+ outputbuf = ""
+ while True:
+ line = p.stderr.readline().decode()
+ if not line:
+ line = p.stdout.readline().decode()
+ if not line:
+ if p.poll() is not None:
+ returncode = p.returncode
+ break
+ if stallcounter > 300:
+ raise InstallError(
+ "Output stalled for 30s, presumably an "
+ "undetected question."
+ )
+ time.sleep(0.1) # Try to do a bit of rate limiting
+ stallcounter += 1
+ continue
+ stdout.write(line)
+ stallcounter = 0
+ outputbuf += line
+ if (
+ "Enter selection" in outputbuf
+ or "Compile for nesting" in outputbuf
+ ):
+ answer = self.answer_configure_question(outputbuf)
+ p.stdin.write(answer.encode())
+ p.stdin.flush()
+ outputbuf = ""
+
+ if returncode != 0:
+ raise InstallError("Configure failed - unknown error")
+
+ def run_compile_script(self):
+ csh_bin = self.spec["tcsh"].prefix.bin.csh
+ csh = Executable(csh_bin)
+
# num of compile jobs capped at 20 in wrf
- csh('./compile', '-j', str(min(int(make_jobs), 20)),
- spec.variants['compile_type'].value)
+ num_jobs = str(min(int(make_jobs, 10)))
+
+ # Now run the compile script and track the output to check for
+ # failure/success We need to do this because upstream use `make -i -k`
+ # and the custom compile script will always return zero regardless of
+ # success or failure
+ result_buf = csh(
+ "./compile",
+ "-j",
+ num_jobs,
+ self.spec.variants["compile_type"].value,
+ )
+
+ if "Executables successfully built" in result_buf:
+ return True
+
+ return False
+
+ def build(self, spec, prefix):
+
+ result = self.run_compile_script()
+
+ if not result:
+ tty.warn(
+ "Compilation failed first time (WRF idiosyncrasies?) "
+ "- trying again..."
+ )
+ result = self.run_compile_script()
+
+ if not result:
+ raise InstallError(
+ "Compile failed. Check the output log for details."
+ )
def install(self, spec, prefix):
# Save all install files as many are needed for WPS and WRF runs
- install_tree('.', prefix)
+ install_tree(".", prefix)