879 lines
31 KiB
Python
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
|