import os
import subprocess
from shutil import copytree, ignore_patterns, rmtree, copy
from logging import info, debug
[docs]
def compile(self):
"""Compile or copy CHIMERE-ACC executables into the CIF work directory.
Two strategies (tried in order):
1. **Copy from pre-compiled cache** — copies ``fwdchimere.e``,
``tlchimere.e``, and ``achimere.e`` from ``self.direxec`` into
``{self.workdir}/model/``. Skipped if ``force-recompile`` is set.
2. **Full recompile** — runs ``make`` inside the CHIMERE-ACC source tree
(``self.dirsrc``).
Args:
self: CHIMERE-ACC model plugin instance with ``workdir``,
``direxec``, and ``dirsrc`` set.
"""
comp_dir = f"{self.workdir}/model"
# Copying the executables
try:
# If force-recompile = True, return IOError to avoid copying
if getattr(self, "force-recompile"):
raise IOError
# Otherwise, try copying executables
for mode, pref, suff in zip(
["A", "L", "D"], ["a", "tl", "fwd"], ["_ad", "_tl", ""]
):
if not mode in getattr(self, "compile-only"):
continue
exe_file = (
f"{self.direxec}/src{suff}/"
f"{pref}chimere_{getattr(self, 'gpu-mode')}_"
f"{getattr(self, 'optimization-mode').lower()}"
f"{'_monitor' if self.monitor else ''}.e"
)
copy(exe_file, comp_dir)
return
except IOError as e:
if not getattr(self, "auto-recompile"):
raise Exception(
f"CHIMERE could not find executables ({self.direxec}) and was not asked to compile them; specify auto-recompile = True in Yaml to do so"
)
# Otherwise, re-compile
# Copying sources locally; overwrite folder if exists
if os.path.isdir(f"{comp_dir}/sources"):
rmtree(f"{comp_dir}/sources")
dir_sources = self.dir_sources if self.dir_sources != "" else self.direxec
copytree(
dir_sources,
f"{comp_dir}/sources",
ignore=ignore_patterns("*.a"),
)
# Modifying LDFLAGS if needed
if hasattr(self, "LDFLAGS") or hasattr(self, "NETCDFLIB") or hasattr(self, "NETCDFINC"):
old_lines = open(
f"{comp_dir}/sources/Makefile", "r"
).readlines()
old_lines = [ln.rstrip() for ln in old_lines]
for k, ln in enumerate(old_lines):
if ln[:9] == "LIBRARIES" and hasattr(self, "LDFLAGS"):
info('change LIBRARIES')
old_lines[k] = f"LIBRARIES =\t{self.LDFLAGS}"
if ln[:10] == "NETCDF_LIB" and hasattr(self, "NETCDFLIB"):
info('change NETCDF_LIB')
old_lines[k] = f"NETCDF_LIB =\t{self.NETCDFLIB}"
if ln[:10] == "NETCDF_INC" and hasattr(self, "NETCDFINC"):
info('change NETCDF_INC')
old_lines[k] = f"NETCDF_INC =\t{self.NETCDFINC}"
with open(f"{comp_dir}/sources/Makefile", "w") as f:
f.write("\n".join(old_lines))
# Now compiling
comp_mode = "" if getattr(self, "optimization-mode") == "PROD" else "--debug"
comp_clean = "--clean" if getattr(self, "compile-clean") else ""
gpu_mode = f"--{getattr(self, 'gpu-mode')}"
monitor = "-monit" if self.monitor else ""
compiler = f"--compiler {self.compiler}"
for mode, pref, suff in zip(
["A", "L", "D"], ["a", "tl", "fwd"], ["_ad", "_tl", ""]
):
if not mode in getattr(self, "compile-only"):
continue
info(f"Compiling CHIMERE {mode}")
info(
f"./compile-chimere {gpu_mode} -m {mode} {comp_mode} {comp_clean} {monitor} {compiler}"
)
with open(f"{comp_dir}/CHIMERE_compiling_{mode}.log", "w") as log:
process = subprocess.Popen(
f"./compile-chimere {gpu_mode} -m "
f"{mode} {comp_mode} {comp_clean} {monitor} {compiler}",
shell=True,
stdout=log,
cwd=f"{comp_dir}/sources/",
stderr=subprocess.PIPE,
)
_, stderr = process.communicate()
# Print errors if no executable created (or if forced to)
exe_file = (
f"{comp_dir}/sources/src{suff}/"
f"{pref}chimere_{getattr(self, 'gpu-mode')}_"
f"{getattr(self, 'optimization-mode').lower()}"
f"{'_monitor' if self.monitor else ''}.e"
)
if not os.path.isfile(exe_file) or getattr(self, "force-compile-stderr"):
debug("CHIMERE returned errors during compiling.")
debug("There might be some bugs in the Fortran or in the "
"libraries loaded in the system:")
debug("### START OF CHIMERE ERROR MESSAGE ###")
for ln in stderr.decode().split("\n"):
debug(ln)
debug("### END OF CHIMERE ERROR MESSAGE ###")
# Copy executable at model root directory
target_exe = f"{comp_dir}/{pref}chimere.e"
copy(exe_file, target_exe)