Source code for pycif.plugins.models.chimere.compile

import os
import subprocess
from logging import debug
from shutil import copy, copytree, ignore_patterns, rmtree


[docs] def compile(self): """Compile (or copy) the CHIMERE forward, TL, and adjoint executables. Follows a two-strategy approach: 1. **Copy pre-compiled executables** (default) — copies ``fwdchimere.e``, ``tlchimere.e``, and ``achimere.e`` from ``self.direxec`` into ``$workdir/model/``. Skipped when ``force-recompile = True``. 2. **Compile from sources** — triggered when ``auto-recompile = True`` (or when the pre-compiled executables are not found). Clones the source tree from ``self.dir_sources`` (or ``self.direxec``), patches the Makefiles for the configured compiler and library paths (NetCDF, GRIB, LDFLAGS), and runs ``make`` for each of the three build variants (forward, TL, adjoint) in PROD or DEBUG mode. Args: self (Plugin): CHIMERE model plugin instance (carries ``workdir``, ``direxec``, ``dir_sources``, compiler flags, and ``force-recompile`` / ``auto-recompile`` flags). Raises: RuntimeError: if ``auto-recompile`` is False and the executables cannot be found. """ 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 source = f"{self.direxec}/src/fwdchimere.e" copy(source, comp_dir) source = f"{self.direxec}/src_tl/tlchimere.e" copy(source, comp_dir) source = f"{self.direxec}/src_ad/achimere.e" copy(source, comp_dir) return except IOError as e: if not getattr(self, "auto-recompile"): raise RuntimeError( f"CHIMERE could not find executables ({self.direxec}) and was not asked to " "compile them; specify auto-recompile = True in Yaml to do so" ) from e # 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"): for fld in ["src", "src_tl", "src_ad"]: old_lines = open( f"{comp_dir}/sources/{fld}/Makefile_chimere", "r" ).readlines() old_lines = [ln.rstrip() for ln in old_lines] for k, ln in enumerate(old_lines): if ln[:7] == "LDFLAGS": old_lines[k] = f"LDFLAGS =\t{self.LDFLAGS}" with open(f"{comp_dir}/sources/{fld}/Makefile_chimere", "w") as f: f.write("\n".join(old_lines)) # Modifying Makefile header if needed makefile_sed = os.path.join(comp_dir, "sources", "Makefile.hdr.sed") with open(makefile_sed, "r") as f: lines = [line.strip() for line in f.readlines()] # Join lines that are split with a backslash (keep backslash and newline characters) joined_lines = [] join = False for line in lines: if join: joined_lines[-1] = joined_lines[-1] + "\n" + line # append line to previous else: joined_lines.append(line) # new line # Join next line if current line ends with a backslash join = line.endswith("\\") gfortran_line = False new_lines = [] for line in joined_lines: if gfortran_line: gfortran_line = False # Reset flag if line.startswith("MF77") and hasattr(self, "gfortran_executable"): line = f"MF77\t=\t{self.gfortran_executable}" # Replace line with new value elif line.startswith("F77FLAGS1") and hasattr(self, "COMPILOPTIONS"): line = line + " " + self.COMPILOPTIONS # append value to line elif line == "REALFC\t=\tgfortran": gfortran_line = True # Replace next line else: for varname in ("NETCDFLIB", "NETCDFINC", "GRIBLIB", "GRIBINC"): if line.startswith(varname) and hasattr(self, varname): line = f"{varname}\t=\t{getattr(self, varname)}" # Replace line with new value break new_lines.append(line) with open(makefile_sed, "w") as f: f.write("\n".join(new_lines)) # Now compiling comp_mode = "" if getattr(self, "compile-mode") == "PROD" else "--debug" comp_clean = "--clean" if getattr(self, "compile-clean") else "" compiler = f"--{getattr(self, 'compiler', 'ifort')}" for mode, pref, suff in zip( ["A", "L", "D"], ["a", "tl", "fwd"], ["_ad", "_tl", ""] ): if not mode in getattr(self, "compile-only"): continue debug(f"Compiling CHIMERE {mode}") with open(f"{comp_dir}/CHIMERE_compiling_{mode}.log", "w") as log: process = subprocess.Popen( f"./compile-chimere -m " f"{mode} {comp_mode} {comp_clean} {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}/{pref}chimere.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 copy(exe_file, comp_dir)