clm5/python/ctsm/lilac_build_ctsm.py
2024-05-09 15:14:01 +08:00

879 lines
31 KiB
Python

"""Functions implementing LILAC's build_ctsm command"""
import argparse
import logging
import os
import shutil
import subprocess
from ctsm.ctsm_logging import (
setup_logging_pre_config,
add_logging_args,
process_logging_args,
)
from ctsm.os_utils import run_cmd_output_on_error, make_link
from ctsm.path_utils import path_to_ctsm_root
from ctsm.utils import abort, fill_template_file
logger = logging.getLogger(__name__)
# ========================================================================
# Define some constants
# ========================================================================
# this matches the machine name in config_machines_template.xml
_MACH_NAME = "ctsm-build"
# these are arbitrary, since we only use the case for its build, not any of the runtime
# settings; they just need to be valid
_COMPSET = "I2000Ctsm50NwpSpAsRs"
_RES = "f10_f10_mg37"
_PATH_TO_TEMPLATES = os.path.join(path_to_ctsm_root(), "lilac", "bld_templates")
_PATH_TO_MAKE_RUNTIME_INPUTS = os.path.join(path_to_ctsm_root(), "lilac", "make_runtime_inputs")
_PATH_TO_DOWNLOAD_INPUT_DATA = os.path.join(path_to_ctsm_root(), "lilac", "download_input_data")
_MACHINE_CONFIG_DIRNAME = "machine_configuration"
_INPUTDATA_DIRNAME = "inputdata"
_RUNTIME_INPUTS_DIRNAME = "runtime_inputs"
_GPTL_NANOTIMERS_CPPDEFS = "-DHAVE_NANOTIME -DBIT64 -DHAVE_VPRINTF -DHAVE_BACKTRACE -DHAVE_SLASHPROC -DHAVE_COMM_F2C -DHAVE_TIMES -DHAVE_GETTIMEOFDAY" # pylint: disable=line-too-long
# ========================================================================
# Public functions
# ========================================================================
def main(cime_path):
"""Main function called when build_ctsm is run from the command-line
Args:
cime_path (str): path to the cime that we're using (this is passed in explicitly
rather than relying on calling path_to_cime so that we can be absolutely sure that
the scripts called here are coming from the same cime as the cime library we're
using).
"""
setup_logging_pre_config()
args = _commandline_args()
process_logging_args(args)
build_dir = os.path.abspath(args.build_dir)
if args.rebuild:
rebuild_ctsm(build_dir=build_dir)
else:
build_ctsm(
cime_path=cime_path,
build_dir=build_dir,
compiler=args.compiler,
no_build=args.no_build,
machine=args.machine,
os_type=args.os,
netcdf_path=args.netcdf_path,
esmf_mkfile_path=args.esmf_mkfile_path,
max_mpitasks_per_node=args.max_mpitasks_per_node,
gmake=args.gmake,
gmake_j=args.gmake_j,
pnetcdf_path=args.pnetcdf_path,
pio_filesystem_hints=args.pio_filesystem_hints,
gptl_nano_timers=args.gptl_nano_timers,
extra_fflags=args.extra_fflags,
extra_cflags=args.extra_cflags,
no_pnetcdf=args.no_pnetcdf,
build_debug=args.build_debug,
build_with_openmp=args.build_with_openmp,
inputdata_path=args.inputdata_path,
)
def build_ctsm(
cime_path,
build_dir,
compiler,
no_build=False,
machine=None,
os_type=None,
netcdf_path=None,
esmf_mkfile_path=None,
max_mpitasks_per_node=None,
gmake=None,
gmake_j=None,
pnetcdf_path=None,
pio_filesystem_hints=None,
gptl_nano_timers=False,
extra_fflags="",
extra_cflags="",
no_pnetcdf=False,
build_debug=False,
build_with_openmp=False,
inputdata_path=None,
):
"""Implementation of build_ctsm command
Args:
cime_path (str): path to root of cime
build_dir (str): path to build directory
compiler (str): compiler type
no_build (bool): If True, set things up, but skip doing the actual build
machine (str or None): machine name (a machine known to cime)
os_type (str or None): operating system type; one of linux, aix, darwin or cnl
Must be given if machine isn't given; ignored if machine is given
netcdf_path (str or None): path to NetCDF installation
Must be given if machine isn't given; ignored if machine is given
esmf_mkfile_path (str or None): path to esmf.mk file (typically within ESMF library directory)
Must be given if machine isn't given; ignored if machine is given
max_mpitasks_per_node (int or None): number of physical processors per shared-memory node
Must be given if machine isn't given; ignored if machine is given
gmake (str or None): name of GNU make tool
Must be given if machine isn't given; ignored if machine is given
gmake_j (int or None): number of threads to use when building
Must be given if machine isn't given; ignored if machine is given
pnetcdf_path (str or None): path to PNetCDF installation, if present (or None)
Ignored if machine is given
pio_filesystem_hints (str or None): if present (not None), enable filesystem hints for the
given filesystem type
Ignored if machine is given
gptl_nano_timers (bool): if True, enable timers in build of the GPTL timing library
Ignored if machine is given
extra_fflags (str): any extra flags to include when compiling Fortran files
Ignored if machine is given
extra_cflags (str): any extra flags to include when compiling C files
Ignored if machine is given
no_pnetcdf (bool): if True, use netcdf rather than pnetcdf
build_debug (bool): if True, build with flags for debugging
build_with_openmp (bool): if True, build with OpenMP support
inputdata_path (str or None): path to existing inputdata directory on this machine
If None, an inputdata directory will be created for this build
(If machine is given, then we use the machine's inputdata directory by default;
but if inputdata_path is given, it overrides the machine's inputdata directory.)
"""
existing_machine = machine is not None
existing_inputdata = existing_machine or inputdata_path is not None
_create_build_dir(build_dir=build_dir, existing_inputdata=existing_inputdata)
# Some error checking
if inputdata_path is not None:
if not os.path.isdir(inputdata_path):
abort("Input inputdata_path directory does NOT exist = " + inputdata_path)
if not os.path.isdir(build_dir):
abort("Input build_dir directory does NOT exist = " + build_dir)
if machine is None:
assert os_type is not None, "with machine absent, os_type must be given"
assert netcdf_path is not None, "with machine absent, netcdf_path must be given"
assert esmf_mkfile_path is not None, "with machine absent, esmf_mkfile_path must be given"
assert max_mpitasks_per_node is not None, (
"with machine absent " "max_mpitasks_per_node must be given"
)
os_type = _check_and_transform_os(os_type)
_fill_out_machine_files(
build_dir=build_dir,
os_type=os_type,
compiler=compiler,
netcdf_path=netcdf_path,
esmf_mkfile_path=esmf_mkfile_path,
max_mpitasks_per_node=max_mpitasks_per_node,
gmake=gmake,
gmake_j=gmake_j,
pnetcdf_path=pnetcdf_path,
pio_filesystem_hints=pio_filesystem_hints,
gptl_nano_timers=gptl_nano_timers,
extra_fflags=extra_fflags,
extra_cflags=extra_cflags,
)
assert os.path.isdir(cime_path), "cime_path must be a directory"
_create_case(
cime_path=cime_path,
build_dir=build_dir,
compiler=compiler,
machine=machine,
build_debug=build_debug,
build_with_openmp=build_with_openmp,
inputdata_path=inputdata_path,
)
_stage_runtime_inputs(build_dir=build_dir, no_pnetcdf=no_pnetcdf)
print(
"Initial setup complete; it is now safe to work with the runtime inputs in\n"
"{}\n".format(os.path.join(build_dir, _RUNTIME_INPUTS_DIRNAME))
)
if not no_build:
_build_case(build_dir=build_dir)
def rebuild_ctsm(build_dir):
"""Re-run the build in an existing directory
Args:
build_dir (str): path to build directory
"""
if not os.path.exists(build_dir):
abort(
"When running with --rebuild, the build directory must already exist\n"
"(<{}> does not exist)".format(build_dir)
)
case_dir = _get_case_dir(build_dir)
if not os.path.exists(case_dir):
abort(
"It appears there was a problem setting up the initial build in\n"
"<{}>\n"
"You should start over with a fresh build directory.".format(build_dir)
)
try:
subprocess.check_call(
[os.path.join(case_dir, "case.build"), "--clean-depends", "lnd"],
cwd=case_dir,
)
except subprocess.CalledProcessError:
abort("ERROR resetting build for CTSM in order to rebuild - see above for details")
_build_case(build_dir)
# ========================================================================
# Private functions
# ========================================================================
def _commandline_args(args_to_parse=None):
"""Parse and return command-line arguments
Args:
args_to_parse: list of strings or None: Generally only used for unit testing; if None,
reads args from sys.argv
"""
# pylint: disable=line-too-long
# pylint: disable=too-many-statements
description = """
Script to build CTSM library and its dependencies
Typical usage:
For a fresh build with a machine that has been ported to cime
(http://esmci.github.io/cime/versions/master/html/users_guide/porting-cime.html):
build_ctsm /path/to/nonexistent/directory --machine MACHINE --compiler COMPILER
(Some other optional arguments are also allowed in this usage, but many are not.)
For a fresh build with a machine that has NOT been ported to cime:
build_ctsm /path/to/nonexistent/directory --os OS --compiler COMPILER --netcdf-path NETCDF_PATH --esmf-mkfile-path ESMF_MKFILE_PATH --max-mpitasks-per-node MAX_MPITASKS_PER_NODE --pnetcdf-path PNETCDF_PATH
If PNetCDF is not available, set --no-pnetcdf instead of --pnetcdf-path.
(Other optional arguments are also allowed in this usage.)
For rebuilding:
build_ctsm /path/to/existing/directory --rebuild
(Most other arguments are NOT allowed in this usage.)
"""
parser = argparse.ArgumentParser(
description=description, formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument(
"build_dir",
help="Path to build directory\n"
"If --rebuild is given, this should be the path to an existing build,\n"
"otherwise this directory must not already exist.",
)
main_opts = parser.add_mutually_exclusive_group()
main_opts.add_argument(
"--machine",
help="Name of machine; this must be a machine that has been ported to cime\n"
"(http://esmci.github.io/cime/versions/master/html/users_guide/porting-cime.html)\n"
"If given, then none of the machine-definition optional arguments should be given.\n",
)
main_opts.add_argument(
"--rebuild",
action="store_true",
help="Rebuild in an existing build directory\n"
"If given, none of the machine-definition or build-related optional arguments\n"
"should be given.\n",
)
non_rebuild_required = parser.add_argument_group(
title="required arguments when not rebuilding",
description="These arguments are required if --rebuild is not given; "
"they are not allowed with --rebuild:",
)
non_rebuild_required_list = []
# For now, only support the compilers that we regularly test with, even though cime
# supports many other options
non_rebuild_required.add_argument(
"--compiler",
type=str.lower,
choices=["gnu", "intel", "nag", "pgi"],
help="Compiler type",
)
non_rebuild_required_list.append("compiler")
non_rebuild_optional = parser.add_argument_group(
title="optional arguments when not rebuilding",
description="These arguments are optional if --rebuild is not given; "
"they are not allowed with --rebuild:",
)
non_rebuild_optional_list = []
non_rebuild_optional.add_argument(
"--no-pnetcdf",
action="store_true",
help="Use NetCDF instead of PNetCDF for CTSM I/O.\n"
"On a user-defined machine, you must either set this flag\n"
"or set --pnetcdf-path. On a cime-ported machine,\n"
"this flag must be set if PNetCDF is not available\n"
"for this machine/compiler.",
)
non_rebuild_optional_list.append("no-pnetcdf")
non_rebuild_optional.add_argument(
"--build-debug",
action="store_true",
help="Build with flags for debugging rather than production runs",
)
non_rebuild_optional_list.append("build-debug")
non_rebuild_optional.add_argument(
"--build-with-openmp",
action="store_true",
help="By default, CTSM is built WITHOUT support for OpenMP threading;\n"
"if this flag is set, then CTSM is built WITH this support.\n"
"This is important for performance if you will be running with\n"
"OpenMP threading-based parallelization, or hybrid MPI/OpenMP.",
)
non_rebuild_optional_list.append("build-with-openmp")
non_rebuild_optional.add_argument(
"--inputdata-path",
help="Path to directory containing CTSM's NetCDF inputs.\n"
"For a machine that has been ported to cime, the default is to\n"
"use this machine's standard inputdata location; this argument\n"
"can be used to override this default.\n"
"For a user-defined machine, the default is to create an inputdata\n"
"directory in the build directory; again, this argument can be\n"
"used to override this default.",
)
non_rebuild_optional_list.append("inputdata-path")
non_rebuild_optional.add_argument(
"--no-build",
action="store_true",
help="Do the pre-build setup, but do not actually build CTSM\n"
"(This is useful for testing, or for expert use.)",
)
non_rebuild_optional_list.append("no-build")
new_machine_required = parser.add_argument_group(
title="required arguments for a user-defined machine",
description="These arguments are required if neither --machine nor --rebuild are given; "
"they are not allowed with either of those arguments:",
)
new_machine_required_list = []
new_machine_required.add_argument(
"--os",
type=str.lower,
choices=["linux", "aix", "darwin", "cnl"],
help="Operating system type",
)
new_machine_required_list.append("os")
new_machine_required.add_argument(
"--netcdf-path",
help="Path to NetCDF installation\n"
"(path to top-level directory, containing subdirectories\n"
"named lib, include, etc.)",
)
new_machine_required_list.append("netcdf-path")
new_machine_required.add_argument(
"--esmf-mkfile-path",
help="Path to esmf.mk file\n" "(typically within ESMF library directory)",
)
new_machine_required_list.append("esmf-mkfile-path")
new_machine_required.add_argument(
"--max-mpitasks-per-node",
type=int,
help="Number of physical processors per shared-memory node\n" "on this machine",
)
new_machine_required_list.append("max-mpitasks-per-node")
new_machine_optional = parser.add_argument_group(
title="optional arguments for a user-defined machine",
description="These arguments are optional if neither --machine nor --rebuild are given; "
"they are not allowed with either of those arguments:",
)
new_machine_optional_list = []
new_machine_optional.add_argument(
"--gmake",
default="gmake",
help="Name of GNU Make tool on your system\n" "Default: gmake",
)
new_machine_optional_list.append("gmake")
new_machine_optional.add_argument(
"--gmake-j",
default=8,
type=int,
help="Number of threads to use when building\n" "Default: 8",
)
new_machine_optional_list.append("gmake-j")
new_machine_optional.add_argument(
"--pnetcdf-path",
help="Path to PNetCDF installation, if present\n"
"You must either specify this or set --no-pnetcdf",
)
new_machine_optional_list.append("pnetcdf-path")
new_machine_optional.add_argument(
"--pio-filesystem-hints",
type=str.lower,
choices=["gpfs", "lustre"],
help="Enable filesystem hints for the given filesystem type\n"
"when building the Parallel IO library",
)
new_machine_optional_list.append("pio-filesystem-hints")
new_machine_optional.add_argument(
"--gptl-nano-timers",
action="store_true",
help="Enable nano timers in build of the GPTL timing library",
)
new_machine_optional_list.append("gptl-nano-timers")
new_machine_optional.add_argument(
"--extra-fflags",
default="",
help="Any extra, non-standard flags to include\n"
"when compiling Fortran files\n"
"Tip: to allow a dash at the start of these flags,\n"
"use a quoted string with an initial space, as in:\n"
' --extra-fflags " -flag1 -flag2"',
)
new_machine_optional_list.append("extra-fflags")
new_machine_optional.add_argument(
"--extra-cflags",
default="",
help="Any extra, non-standard flags to include\n"
"when compiling C files\n"
"Tip: to allow a dash at the start of these flags,\n"
"use a quoted string with an initial space, as in:\n"
' --extra-cflags " -flag1 -flag2"',
)
new_machine_optional_list.append("extra-cflags")
add_logging_args(parser)
args = parser.parse_args(args_to_parse)
if args.rebuild:
_confirm_args_absent(
parser,
args,
"cannot be provided if --rebuild is set",
(
non_rebuild_required_list
+ non_rebuild_optional_list
+ new_machine_required_list
+ new_machine_optional_list
),
)
else:
_confirm_args_present(
parser,
args,
"must be provided if --rebuild is not set",
non_rebuild_required_list,
)
if args.machine:
_confirm_args_absent(
parser,
args,
"cannot be provided if --machine is set",
new_machine_required_list + new_machine_optional_list,
)
else:
_confirm_args_present(
parser,
args,
"must be provided if neither --machine nor --rebuild are set",
new_machine_required_list,
)
if not args.no_pnetcdf and args.pnetcdf_path is None:
parser.error(
"For a user-defined machine, need to specify either --no-pnetcdf or --pnetcdf-path"
)
if args.no_pnetcdf and args.pnetcdf_path is not None:
parser.error("--no-pnetcdf cannot be given if you set --pnetcdf-path")
return args
def _confirm_args_absent(parser, args, errmsg, args_not_allowed):
"""Confirms that all args not allowed in this usage are absent
Calls parser.error if there are problems
Args:
parser: ArgumentParser
args: list of parsed arguments
errmsg: string - message printed if there is a problem
args_not_allowed: list of strings - argument names in this category
"""
for arg in args_not_allowed:
arg_no_dashes = arg.replace("-", "_")
# To determine whether the user specified an argument, we look at whether its
# value differs from its default value. This won't catch the case where the user
# explicitly set an argument to its default value, but it's not a big deal if we
# miss printing an error in that case.
if vars(args)[arg_no_dashes] != parser.get_default(arg_no_dashes):
parser.error("--{} {}".format(arg, errmsg))
def _confirm_args_present(parser, args, errmsg, args_required):
"""Confirms that all args required in this usage are present
Calls parser.error if there are problems
Args:
parser: ArgumentParser
args: list of parsed arguments
errmsg: string - message printed if there is a problem
args_required: list of strings - argument names in this category
"""
for arg in args_required:
arg_no_dashes = arg.replace("-", "_")
if vars(args)[arg_no_dashes] is None:
parser.error("--{} {}".format(arg, errmsg))
def _check_and_transform_os(os_type):
"""Check validity of os_type argument and transform it to proper case
os_type should be a lowercase string; returns a transformed string
"""
transforms = {"linux": "LINUX", "aix": "AIX", "darwin": "Darwin", "cnl": "CNL"}
try:
os_type_transformed = transforms[os_type]
except KeyError as exc:
raise ValueError("Unknown OS: {}".format(os_type)) from exc
return os_type_transformed
def _get_case_dir(build_dir):
"""Given the path to build_dir, return the path to the case directory"""
return os.path.join(build_dir, "case")
def _create_build_dir(build_dir, existing_inputdata):
"""Create the given build directory and any necessary sub-directories
Args:
build_dir (str): path to build directory; this directory shouldn't exist yet!
existing_inputdata (bool): whether the inputdata directory already exists on this machine
"""
if os.path.exists(build_dir):
abort(
"When running without --rebuild, the build directory must not exist yet\n"
"(<{}> already exists)".format(build_dir)
)
os.makedirs(build_dir)
if not existing_inputdata:
os.makedirs(os.path.join(build_dir, _INPUTDATA_DIRNAME))
def _fill_out_machine_files(
build_dir,
os_type,
compiler,
netcdf_path,
esmf_mkfile_path,
max_mpitasks_per_node,
gmake,
gmake_j,
pnetcdf_path=None,
pio_filesystem_hints=None,
gptl_nano_timers=False,
extra_fflags="",
extra_cflags="",
):
"""Fill out the machine porting templates for this machine / compiler
For documentation of args, see the documentation in the build_ctsm function
"""
os.makedirs(os.path.join(build_dir, _MACHINE_CONFIG_DIRNAME, "cmake_macros"))
# ------------------------------------------------------------------------
# Fill in config_machines.xml
# ------------------------------------------------------------------------
fill_template_file(
path_to_template=os.path.join(_PATH_TO_TEMPLATES, "config_machines_template.xml"),
path_to_final=os.path.join(build_dir, _MACHINE_CONFIG_DIRNAME, "config_machines.xml"),
substitutions={
"OS": os_type,
"COMPILER": compiler,
"CIME_OUTPUT_ROOT": build_dir,
"GMAKE": gmake,
"GMAKE_J": gmake_j,
"MAX_TASKS_PER_NODE": max_mpitasks_per_node,
"MAX_MPITASKS_PER_NODE": max_mpitasks_per_node,
"ESMF_MKFILE_PATH": esmf_mkfile_path,
},
)
# ------------------------------------------------------------------------
# Fill in ctsm-build_template.cmake
# ------------------------------------------------------------------------
if gptl_nano_timers:
gptl_cppdefs = _GPTL_NANOTIMERS_CPPDEFS
else:
gptl_cppdefs = ""
if pio_filesystem_hints:
pio_filesystem_hints_addition = 'set(PIO_FILESYSTEM_HINTS "{}")'.format(
pio_filesystem_hints
)
else:
pio_filesystem_hints_addition = ""
if pnetcdf_path:
pnetcdf_path_addition = 'set(PNETCDF_PATH "{}")'.format(pnetcdf_path)
else:
pnetcdf_path_addition = ""
fill_template_file(
path_to_template=os.path.join(_PATH_TO_TEMPLATES, "ctsm-build_template.cmake"),
path_to_final=os.path.join(
build_dir,
_MACHINE_CONFIG_DIRNAME,
"cmake_macros",
"{}_{}.cmake".format(compiler, _MACH_NAME),
),
substitutions={
"GPTL_CPPDEFS": gptl_cppdefs,
"NETCDF_PATH": netcdf_path,
"PIO_FILESYSTEM_HINTS": pio_filesystem_hints_addition,
"PNETCDF_PATH": pnetcdf_path_addition,
"EXTRA_CFLAGS": extra_cflags,
"EXTRA_FFLAGS": extra_fflags,
},
)
def _create_case(
cime_path,
build_dir,
compiler,
machine=None,
build_debug=False,
build_with_openmp=False,
inputdata_path=None,
):
"""Create a case that can later be used to build the CTSM library and its dependencies
Args:
cime_path (str): path to root of cime
build_dir (str): path to build directory
compiler (str): compiler to use
machine (str or None): name of machine or None
If None, we assume we're using an on-the-fly machine port
Otherwise, machine should be the name of a machine known to cime
build_debug (bool): if True, build with flags for debugging
build_with_openmp (bool): if True, build with OpenMP support
inputdata_path (str or None): path to existing inputdata directory on this machine
If None, we use the machine's default DIN_LOC_ROOT
"""
# Note that, for some commands, we want to suppress output, only showing the output if
# the command fails; for these we use run_cmd_output_on_error. For other commands,
# there should be no output in general; for these, we directly use
# subprocess.check_call or similar.
# Also note that, for commands executed from the case directory, we specify the path
# to the case directory both in the command itself and in the cwd argument. We do the
# former in case dot isn't in the user's path; we do the latter in case the commands
# require you to be in the case directory when you execute them.
case_dir = _get_case_dir(build_dir)
xmlchange = os.path.join(case_dir, "xmlchange")
if machine is None:
machine_args = [
"--machine",
_MACH_NAME,
"--extra-machines-dir",
os.path.join(build_dir, _MACHINE_CONFIG_DIRNAME),
]
else:
machine_args = ["--machine", machine]
cmd = os.path.join(cime_path, "scripts", "create_newcase")
if not os.path.exists(cmd):
abort(
"The create_newcase command doesn't exist as expected <{}> does not exist)".format(cmd)
)
create_newcase_cmd = [
cmd,
"--output-root",
build_dir,
"--case",
case_dir,
"--compset",
_COMPSET,
"--res",
_RES,
"--compiler",
compiler,
"--driver",
"nuopc",
# Project isn't used for anything in the LILAC workflow, but it
# still needs to be specified on machines that expect it.
"--project",
"UNSET",
"--run-unsupported",
]
create_newcase_cmd.extend(machine_args)
if inputdata_path:
create_newcase_cmd.extend(["--input-dir", inputdata_path])
if not os.path.isdir(inputdata_path):
abort("inputdata_path directory (<{}> does not exist)".format(inputdata_path))
run_cmd_output_on_error(
create_newcase_cmd,
errmsg="Problem running create_newcase to create the CTSM case directory",
)
subprocess.check_call([xmlchange, "LILAC_MODE=on"], cwd=case_dir)
if build_debug:
subprocess.check_call([xmlchange, "DEBUG=TRUE"], cwd=case_dir)
if build_with_openmp:
subprocess.check_call([xmlchange, "FORCE_BUILD_SMP=TRUE"], cwd=case_dir)
run_cmd_output_on_error(
[os.path.join(case_dir, "case.setup")],
errmsg="Problem setting up CTSM case directory",
cwd=case_dir,
)
make_link(os.path.join(case_dir, "bld"), os.path.join(build_dir, "bld"))
if machine is not None:
# For a pre-existing machine, the .env_mach_specific files are likely useful to
# the user. Make sym links to these with more intuitive names.
for extension in ("sh", "csh"):
make_link(
os.path.join(case_dir, ".env_mach_specific.{}".format(extension)),
os.path.join(build_dir, "ctsm_build_environment.{}".format(extension)),
)
def _stage_runtime_inputs(build_dir, no_pnetcdf):
"""Stage CTSM and LILAC runtime inputs
Args:
build_dir (str): path to build directory
no_pnetcdf (bool): if True, use netcdf rather than pnetcdf
"""
os.makedirs(os.path.join(build_dir, _RUNTIME_INPUTS_DIRNAME))
inputdata_dir = _xmlquery("DIN_LOC_ROOT", build_dir)
fill_template_file(
path_to_template=os.path.join(_PATH_TO_TEMPLATES, "ctsm_template.cfg"),
path_to_final=os.path.join(build_dir, _RUNTIME_INPUTS_DIRNAME, "ctsm.cfg"),
substitutions={"INPUTDATA": inputdata_dir},
)
fill_template_file(
path_to_template=os.path.join(_PATH_TO_TEMPLATES, "lilac_in_template"),
path_to_final=os.path.join(build_dir, _RUNTIME_INPUTS_DIRNAME, "lilac_in"),
substitutions={"INPUTDATA": inputdata_dir},
)
pio_stride = _xmlquery("MAX_MPITASKS_PER_NODE", build_dir)
if no_pnetcdf:
pio_typename = "netcdf"
# pio_rearranger = 1 is generally more efficient with netcdf (see
# https://github.com/ESMCI/cime/pull/3732#discussion_r508954806 and the following
# discussion)
pio_rearranger = 1
else:
pio_typename = "pnetcdf"
pio_rearranger = 2
fill_template_file(
path_to_template=os.path.join(_PATH_TO_TEMPLATES, "lnd_modelio_template.nml"),
path_to_final=os.path.join(build_dir, _RUNTIME_INPUTS_DIRNAME, "lnd_modelio.nml"),
substitutions={
"PIO_REARRANGER": pio_rearranger,
"PIO_STRIDE": pio_stride,
"PIO_TYPENAME": pio_typename,
},
)
shutil.copyfile(
src=os.path.join(
path_to_ctsm_root(), "cime_config", "usermods_dirs", "lilac", "user_nl_ctsm"
),
dst=os.path.join(build_dir, _RUNTIME_INPUTS_DIRNAME, "user_nl_ctsm"),
)
make_link(
_PATH_TO_MAKE_RUNTIME_INPUTS,
os.path.join(build_dir, _RUNTIME_INPUTS_DIRNAME, "make_runtime_inputs"),
)
make_link(
_PATH_TO_DOWNLOAD_INPUT_DATA,
os.path.join(build_dir, _RUNTIME_INPUTS_DIRNAME, "download_input_data"),
)
def _build_case(build_dir):
"""Build the CTSM library and its dependencies
Args:
build_dir (str): path to build directory
"""
# We want user to see output from the build command, so we use subprocess.check_call
# rather than run_cmd_output_on_error.
# See comment in _create_case for why we use case_dir in both the path to the command
# and in the cwd argument to check_call.
case_dir = _get_case_dir(build_dir)
try:
subprocess.check_call(
[os.path.join(case_dir, "case.build"), "--sharedlib-only"], cwd=case_dir
)
except subprocess.CalledProcessError:
abort("ERROR building CTSM or its dependencies - see above for details")
make_link(os.path.join(case_dir, "bld", "ctsm.mk"), os.path.join(build_dir, "ctsm.mk"))
def _xmlquery(varname, build_dir):
"""Run xmlquery from the case in build_dir and return the value of the given variable"""
case_dir = _get_case_dir(build_dir)
xmlquery_path = os.path.join(case_dir, "xmlquery")
value = subprocess.check_output(
[xmlquery_path, "--value", varname], cwd=case_dir, universal_newlines=True
)
return value