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

import os
import subprocess
#
# JvP 20201103: the copytree function from the shutil module only works if the
# destination directory does not exist. If it does exist, an error will be
# issued. Since you will be copying multiple source directories to a single
# target directory (i.e. the different TM5 projects), this is undesired
# behaviour. So I googled around, and found a replacement for copytree that
# apparently also works if the destination directory exists. To be on the
# safe side, I removed copytree from the import statement below.
#
#from shutil import copytree, ignore_patterns, rmtree, copy
from shutil import ignore_patterns, rmtree, copy
#
# JvP 20210119: changed import statement for logging
#from logging import info, debug
import logging

# JvP 20210126: defined MODULE_NAME
# JvP 20210202: updated MODULE_NAME
# The special variable __name__ is a built-in variable which evaluates to the
# name of the current module. When the current module is imported, it evaluates
# to pycif.plugins.models.TM5.compile. The statement below checks if the string
# 'TM5' is in the string __name__, and if so, returns the part of __name__
# starting with 'TM5'. The result is TM5.compile. If the string 'TM5' is not
# in __name__, __name__ is used as module name.
MODULE_NAME = __name__[__name__.index('TM5'):] if 'TM5' in __name__ else __name__

# JvP 20210126: get a module level logger, so that there's always a logger
# available, even if you have no logger defined in a function.
# Note that you've updated the logging formatters in .../TM5/__init__.py so
# that you can do any of the following to suppress formatting of the logging
# messages:
#    logger_1 = logging.getLogger(           ) # gets the root logger
#    logger_2 = logging.getLogger(MODULE_NAME) # gets a module-level logger
#    logger_3 = logging.getLogger('no_fmt'   ) # gets a formatter that will not format any message
#    logger_1.info(""                                ) # unformatted empty line
#    logger_1.info("Formatted line using logger_1."  ) # formatted line using logger_1
#    logger_2.info(""                                ) # unformatted empty line
#    logger_2.info("Formatted line using logger_2."  ) # formatted line using logger_2
#    logger_3.info(""                                ) # unformatted empty line
#    logger_3.info("UNFormatted line using logger_3.") # UNformatted line using logger_3
#
# Inside the functions in this module, you could do the following to set a
# custom logging level:
#    PROG_NAME = MODULE_NAME+'.whatever'
#    logger = logging.getLogger(PROG_NAME)
#    logger.setLevel(logging.DEBUG)
logger = logging.getLogger(MODULE_NAME)

# JvP 20210126: added import sys
import sys


# >>> JvP 20201103: copied the following code from
# https://stackoverflow.com/a/22331852, changed indentation from 2 to 4 spaces,
# and added some "end if" comments (not required, but clearer IMHO). According
# to the stackoverflow page this function has the following characteristics:
# *) Same behavior as shutil.copytree, with symlinks and ignore parameters
# *) Create directory destination structure if non existant
# *) Will not fail if dst already exists
# JvP 20210202: Updated file permissions

import shutil
import stat
[docs] def copytree(src, dst, symlinks = False, ignore = None): if not os.path.exists(dst): os.makedirs(dst) shutil.copystat(src, dst) # end if lst = os.listdir(src) if ignore: excl = ignore(src, lst) lst = [x for x in lst if x not in excl] # end if for item in lst: s = os.path.join(src, item) d = os.path.join(dst, item) if symlinks and os.path.islink(s): if os.path.lexists(d): os.remove(d) # end if os.symlink(os.readlink(s), d) try: st = os.lstat(s) mode = stat.S_IMODE(st.st_mode) os.lchmod(d, mode) except: pass # lchmod not available # end try elif os.path.isdir(s): copytree(s, d, symlinks, ignore) else: shutil.copy2(s, d) # end if symlinks # JvP 20210202: I had a single file for which I've set the permissions # to r--r--r-- to prevent accidental overwriting. But this function # yields an error if the file exists and it tries to overwrite it. To # prevent that, change file permissions to rx-r--r--. # If you still get errors, use a subprocess and rsync instead. os.chmod(d, 0o644)
# end for item # end function copytree # <<< JvP 20201103
[docs] def macros_to_file(macros, macros_file, macros_all): """ PURPOSE Write a list of macros to a file. IN/OUT macros = list of macros to write to file. Only unique entries will be written fo file macros_file = the file to write the macros to macros_all = each macro in macros should be present in this list in order to prevent typos KWARGS none ASSUMPTIONS none EXCEPTIONS When this function encounters a problem that it cannot solve, it will raise a RuntimeError. Pythons exeception handling will provide a traceback, including files, calls and line numbers. PYTHON VERSION 3.7.6 VERSION CHANGE HISTORY 1.2 02-02-2021 by J.C.A. van Peet. *) Updated PROG_NAME *) Used new PROG_NAME as local logger name. 1.1 26-01-2021 by J.C.A. van Peet. Updated the local logger and 'raise RuntimeError' statement. 1.0 18-01-2021 by J.C.A. van Peet. Original code. """ # Name of program PROG_NAME = MODULE_NAME+".macros_to_file" # Local logger logger = logging.getLogger(PROG_NAME) #logger.setLevel(logging.DEBUG) # Use a context manager to open the file with open(macros_file, "w") as f_id: print_kwds = {"file":f_id, "sep":""} print("!", **print_kwds) print("! Include file with macro definitions generated by "+PROG_NAME, **print_kwds) print("!", **print_kwds) # Note that a set can only contain unique elements. So we first convert # the list macros to a set, and then back to a list to keep only the # unique elements in the original list. for macro in list(set(macros)): if( macro in macros_all ): print('#define '+macro, **print_kwds) else: logger.critical("\n"*3+"*"*30) logger.critical(PROG_NAME+" => ERROR: unknown macro!") logger.critical(" macro = "+macro ) logger.critical(" macros = "+macros ) logger.critical(" macros_file = "+macros_file) logger.critical(" macros_all = "+macros_all ) logger.critical(" Computer says no...") logger.critical("*"*30+"\n"*3) try: raise RuntimeError except RuntimeError as e: #logger.exception("OOPS!") logger.critical(e, exc_info=True) #raise # => Will display the traceback on screen a second time sys.exit() # => Just exit.
# end try # end if # end for macros # end with # end function macros_to_file
[docs] def write_dims_grid(dims_grid_path, dx=6.0, dy=4.0, dz=1.0, nregions=1, len_region_name=10, regions=['glb600x400'], parents=['globe'], xcyc=1, touch_np=1, touch_sp=1, xbeg=-180, xend=180, ybeg=-90, yend=90, im=60, jm=45, maxref=10, xref=1, yref=1, zref=1, tref=1): """ PURPOSE Write the Fortran source file dims_grid.F90 IN/OUT dims_grid_path = path to the location where to write dims_grid.F90. The filename itself will be added automatically. KWARGS dx = grid cell size in longitude dy = grid cell size in latitude dz = grid cell size in altitude, set to 1.0 nregions = 1 len_region_name = 10: the length of a string in the regions list regions = list with the name of the regions parents = list with the name of the parent regions Global region 1 should have parent 0 (globe single cell); global surface region should have parent 1 (global region). coordinates (in degrees) for each region: xcyc = 1 if the region has cyclic x-boundary conditions touch_np = 1 if region touches the north pole touch_sp = 1 if region touches the south pole xbeg = the westmost border of the region xend = the eastmost border of the region ybeg = the southmost border of the region yend = the northmost border of the region im = number of gridcells in longitude direction jm = number of gridcells in latitude direction refinement factors for each region (<= maxref) tref may differ from xref/yref. In the current implementation it should be 1,2,4,6,... maxref = maximum refinement factor (can be arbitrary in principle) xref = yref = zref = tref = ASSUMPTIONS none EXCEPTIONS When this function encounters a problem that it cannot solve, it will raise a RuntimeError. Pythons exeception handling will provide a traceback, including files, calls and line numbers. PYTHON VERSION 3.7.6 VERSION CHANGE HISTORY 1.2 02-02-2021 by J.C.A. van Peet. *) Updated PROG_NAME *) Used new PROG_NAME as local logger name. 1.1 26-01-2021 by J.C.A. van Peet. Updated the local logger. 1.0 18-01-2021 by J.C.A. van Peet. Code to fill the list 'lines' copied from ...TM5/base/py/TM5_Tools.py, class Build_Configure_Grid( object ), __init__(...), using "build_release = 1". """ # Name of program PROG_NAME = MODULE_NAME+".write_dims_grid" # Local logger logger = logging.getLogger(PROG_NAME) #logger.setLevel(logging.DEBUG) # start with empty file: lines = [] lines.append( '!#################################################################\n' ) lines.append( '! File written on-the-fly by '+PROG_NAME+'\n' ) lines.append( '!\n' ) lines.append( '! Grids.\n' ) lines.append( '!\n' ) lines.append( '!### macro\'s #####################################################\n' ) lines.append( '!\n' ) lines.append( '#include "tm5.inc"\n' ) lines.append( '!\n' ) lines.append( '!#################################################################\n' ) lines.append( '\n' ) lines.append( 'module dims_grid\n' ) lines.append( '\n' ) lines.append( ' implicit none\n' ) lines.append( ' \n' ) lines.append( ' ! --- in/out ------------------------------\n' ) lines.append( ' \n' ) lines.append( ' public\n' ) lines.append( ' \n' ) lines.append( ' \n' ) lines.append( ' ! --- const -------------------------------\n' ) lines.append( ' \n' ) lines.append( ' \n' ) lines.append( ' ! Basic model definition: resolution etc. including some routines\n' ) lines.append( ' ! to fill the data structure.\n' ) lines.append( '\n' ) lines.append( ' ! basic (coarsest) resolution in degrees for x and y (dz default 1.0)\n' ) lines.append( '\n' ) lines.append( ' real, parameter :: dx = %s\n' % dx ) lines.append( ' real, parameter :: dy = %s\n' % dy ) lines.append( ' real, parameter :: dz = %s\n' % dz ) lines.append( '\n' ) lines.append( '\n' ) lines.append( ' ! Maximum number of zoom regions, \n' ) lines.append( ' ! including the basic (coarsest grid) region;\n' ) lines.append( ' ! arrays are allocated for each of these regions:\n' ) lines.append( ' integer, parameter :: nregions_max = %i\n' % nregions ) lines.append( ' \n' ) lines.append( ' ! Actual number of zoom regions,\n' ) lines.append( ' ! during testing this could be set to 1 to quickly run the model.\n' ) lines.append( ' integer, parameter :: nregions = %s\n' % nregions ) lines.append( '\n' ) lines.append( ' ! region_name is used to recognise the METEO files\n' ) lines.append( ' ! region_name is also used in the HDF output file name\n' ) lines.append( ' ! region 1 should always be the global domain\n' ) lines.append( '\n' ) lines.append( ' integer, parameter :: len_region_name = %i\n' % len_region_name ) lines.append( ' character(len=len_region_name), parameter :: region_name(1:nregions) = &\n' ) line = ' (/ ' for i in range(len(regions)) : if i > 0 : line = line + ', ' fmt = "'%%-%is'" % len_region_name line = line + ( fmt % regions[i] ) #endfor lines.append( line+'/)\n' ) lines.append( '\n' ) lines.append( ' ! coordinates (in degrees) for each region:\n' ) lines.append( ' ! xcyc = 1 if the region has cyclic x-boundary conditions\n' ) lines.append( ' ! touch_np = 1 if region touches the north pole\n' ) lines.append( ' ! touch_sp = 1 if region touches the south pole\n' ) lines.append( ' ! xbeg : the westmost border of the region\n' ) lines.append( ' ! xend : the eastmost border of the region\n' ) lines.append( ' ! ybeg : the southmost border of the region\n' ) lines.append( ' ! yend : the northmost border of the region\n' ) lines.append( '\n' ) fields = ['xcyc','touch_np','touch_sp','xbeg','xend','ybeg','yend','im','jm'] for ifield in range(len(fields)) : field = fields[ifield] line = ' integer, parameter :: %-8s(nregions) = (/ ' % field for iregion in range(len(regions)) : region = regions[iregion] if iregion > 0 : line = line + ', ' # # JvP: changed line #val = rcf.get( 'region.%s.%s' % (region,field) ) val = eval(field) # line = line + ( '%4i' % int(val) ) #endfor lines.append( line+' /)\n' ) #endfor lines.append( '\n' ) lines.append( '\n' ) lines.append( ' ! maximum refinement factor (can be arbitrary in principle):\n' ) lines.append( '\n' ) lines.append( ' integer, parameter :: maxref = %s\n' % maxref ) lines.append( '\n' ) lines.append( ' ! refinement factors for each region (<= maxref)\n' ) lines.append( ' ! tref may differ from xref/yref. In the current \n' ) lines.append( ' ! implementation it should be 1,2,4,6,...\n' ) lines.append( '\n' ) fields = ['xref','yref','zref','tref'] for ifield in range(len(fields)) : field = fields[ifield] line = ' integer, parameter :: %s(0:nregions) = (/ 1' % field for i in range(nregions) : #if i > 0 : line = line + ', ' line = line + ', ' # # JvP: changed line #val = rcf.get( 'region.%s.%s' % (regions[i],field) ) val = eval(field) # line = line + ( '%4i' % int(val) ) #endfor lines.append( line+' /)\n' ) #endfor lines.append( '\n' ) lines.append( ' ! Define the parent of each region. \n' ) lines.append( ' ! Global region 1 should have parent 0 (globe single cell);\n' ) lines.append( ' ! global surface region should have parent 1 (global region).\n' ) line = ' integer, parameter :: parent(nregions) = (/ ' for i in range(nregions) : if i > 0 : line = line + ', ' # # JvP: changed line #val = rcf.get( 'region.%s.parent' % regions[i] ) val = parents[i] # if val == 'globe' : ireg = 0 else : ireg = regions.index(val) + 1 #endif line = line + ( '%i' % ireg ) #endfor lines.append( line+' /)\n' ) lines.append( '\n' ) lines.append( 'end module dims_grid\n' ) # Use a context manager to open the file with open(dims_grid_path+'/dims_grid.F90', "w") as f_id: print_kwds = {"file":f_id, "sep":"", "end":''} for line in lines: print(line, **print_kwds)
# end for # end with open # end function write_dims_grid
[docs] def compile(self): """ PURPOSE Compile the TM5 source tree into an executable IN/OUT none KWARGS none ASSUMPTIONS none EXCEPTIONS When this function encounters a problem that it cannot solve, it will raise a RuntimeError. Pythons exeception handling will provide a traceback, including files, calls and line numbers. PYTHON VERSION 3.7.6 VERSION CHANGE HISTORY 2.4 19-04-2021 by J.C.A. van Peet. After copying the model source from the TM5 source tree to the CIF source tree, I had to prepend self.direxec to a few variables. 2.3 02-02-2021 by J.C.A. van Peet. *) Updated PROG_NAME *) Used new PROG_NAME as local logger name. *) Moved some compilation statements (see 18-01-2021) into the "auto_recompile" if-statement. *) Updated the subprocess commands so that the output is logged live. *) Updated comp_dir to self.workdir/model/src 2.2 26-01-2021 by J.C.A. van Peet. *) Removed function logging.empty(...), you can now simply do logger.info("") to print an empty line. *) Initialised a local logger, so replaced all calls to logging with calls to logger. *) RuntimeErrors are now also logged in the logfile. *) Added universal_newlines=True to all subprocess.Popen calls. As a result, you don't have to do logger.debug("\n"+"".join(stdout.decode("ascii"))) anymore, but you can just do logger.debug(stdout) 2.1 19-01-2021 by J.C.A. van Peet. Added custom function logging.empty(...) to write empty lines to logfile. 2.0 18-01-2021 by J.C.A. van Peet. *) Added compilation of tm5. *) Renamed the tm5.exe to tm5.x *) Replaced print statements with logging statements. 1.0 03-11-2020 by J.C.A. van Peet. Original code, based on an example by A. Berchet. """ # EXAMPLE CODE #comp_dir = "{}/model".format(self.workdir) # #if not getattr(self, "auto-recompile"): # source = "{}/tm5.f90".format(self.direxec) # copy(source, comp_dir) # # return # ## Otherwise, re-compile ## Copying sources locally; overwrite folder if exists #if os.path.isdir("{}/sources".format(comp_dir)): # rmtree("{}/sources".format(comp_dir)) # #copytree( # self.direxec, # "{}/sources".format(comp_dir), # ignore=ignore_patterns("*.a"), #) # # First check whether the executable is available in direxec # To avoid re-compiling each time ref_exe = "{}/base/src/tm5.x".format(self.direxec) comp_dir = "{}/model/src".format(self.workdir) if os.path.isfile(ref_exe): copy(ref_exe, comp_dir) ## Now compiling # Name of program PROG_NAME = MODULE_NAME+".compile" # JvP 20210126: Local logger logger = logging.getLogger(PROG_NAME) logger.setLevel(logging.DEBUG) # Compilation directory (?) Copied variable name from original code # JvP 20210202: added '/src' to comp_dir. comp_dir = "{}/model/src".format(self.workdir) # Print some debug statements # ************************************************************************ # * NOTE: You specified workdir with a trailing slash in the YAML file, * # * and it is printed like that at the top of the output when * # * running pycif. * # * However, the trailing slash is somehow stripped when you print * # * workdir here as self.workdir. * # ************************************************************************ # # JvP 20210118: Replaced print statements with logging.debug(). # JvP 20210126: Replaced logging statements with logger statements. logger.debug("") logger.debug("*"*30) logger.debug(PROG_NAME+" => DEBUG:") logger.debug(" self = %s", str(self)) logger.debug(" dir(self) = %s", str(("\n"+" "*27).join( [ ', '.join(dir(self)[i:i+5]) for i in range(0,len(dir(self)),5) ] ) )) logger.debug(" self.workdir = %s", str(self.workdir )) logger.debug(" self.direxec = %s", str(self.direxec )) logger.debug(" comp_dir = %s", str(comp_dir )) logger.debug(" auto_recompile = %s", str(self.auto_recompile )) logger.debug(" make_clean = %s", str(self.make_clean )) logger.debug(" nlay = %s", str(self.nlay )) logger.debug(" levs = %s", str(self.levs )) logger.debug(" projs = %s", str(self.projs )) logger.debug(" make_options = %s", str(self.make_options )) logger.debug(" makedeps_dir = %s", str(self.makedeps_dir )) logger.debug(" fc = %s", str(self.fc )) logger.debug(" linker = %s", str(self.linker )) logger.debug(" fflags_gfortran = %s", str(self.fflags_gfortran )) logger.debug(" ldflags_gfortran = %s", str(self.ldflags_gfortran )) logger.debug(" fflags_ifort = %s", str(self.fflags_ifort )) logger.debug(" ldflags_ifort = %s", str(self.ldflags_ifort )) logger.debug(" meteo_macros = %s", str(self.meteo_macros )) logger.debug(" model_macros = %s", str(self.model_macros )) logger.debug(" mdf_macros = %s", str(self.mdf_macros )) logger.debug(" self.domain.tm5_label = %s", str(self.domain.tm5_label )) logger.debug(" self.domain.xmin = %s", str(self.domain.xmin )) logger.debug(" self.domain.xmax = %s", str(self.domain.xmax )) logger.debug(" self.domain.nlon = %s", str(self.domain.nlon )) logger.debug(" self.domain.dx = %s", str(self.domain.dx )) logger.debug(" self.domain.ymin = %s", str(self.domain.ymin )) logger.debug(" self.domain.ymax = %s", str(self.domain.ymax )) logger.debug(" self.domain.nlat = %s", str(self.domain.nlat )) logger.debug(" self.domain.dy = %s", str(self.domain.dy )) # # JvP 20210414: added printing of tm5_meteo_dir # JvP 20210503: After some changes by Antoine, use # self.workdir+'/datavect/meteo/' instead of tm5_meteo_dir logger.debug("") #logger.debug(" tm5_meteo_dir = %s", self.tm5_meteo_dir ) logger.debug(" self.workdir+'/datavect/meteo/' = %s" % (self.workdir+'/datavect/meteo/',) ) logger.debug("") logger.debug("*"*30) logger.debug("") #logger.debug("") #logger.debug("*"*30) #logger.debug("JvP: Computer says no!") #logger.debug("*"*30) #logger.debug("") #try: # raise RuntimeError #except RuntimeError as e: # #logger.exception("OOPS!") # logger.critical(e, exc_info=True) # #raise # => Will display the traceback on screen a second time # sys.exit() # => Just exit. # end try # Compile (or recompile) tm5 executable? # In general, do not use minus signs in variable names! # In this case, variables like 'direxec' and 'auto-recompile' will become # attributes to the model TM5. You should be able to get the value of these # attributes by using either of: # 1) self.auto-recompile # 2) getattr(self, 'auto-recompile') # Note that the first method won't work, because python will interpret that # as 'get the value for self.auto and subtract the variable recompile'. That # will yield an AttributeError, since there is no attribute called 'auto'. # To prevent that error from occuring, I replaced auto-recompile with # auto_recompile. See .../pycif/plugins/models/TM5/{__init__,compile}.py # and the yaml file (e.g. config_TM5_forward.yml) for the use of # auto_recompile. # JvP 20210118: renamed tm5.exe to tm5.x if( (self.auto_recompile) or (not os.path.isfile( "{}/tm5.x".format(comp_dir) ) ) ): # >>> JvP 20210118 # Check the domain definition. Other definitions are not yet supported in CIF if( self.domain.tm5_label != 'glb600x400' ): logger.critical("") logger.critical("*"*30) logger.critical(PROG_NAME+" => ERROR: self.domain.tm5_label must be 'glb600x400'!") logger.critical(" self.domain.tm5_label = "+self.domain.tm5_label) logger.critical(" Computer says no...") logger.critical("*"*30) logger.critical("") try: raise RuntimeError except RuntimeError as e: #logger.exception("OOPS!") logger.critical(e, exc_info=True) #raise # => Will display the traceback on screen a second time sys.exit() # => Just exit. # end try # end if # Check the values for xmin, xmax, nlon, ymin, ymax, and nlat domain_valid = {'xmin':-180, 'xmax':180, 'nlon':60, 'dx':6.0, 'ymin':-90, 'ymax':90, 'nlat':45, 'dy':4.0} for key, value in domain_valid.items(): if( value != eval('self.domain.'+key) ): logger.critical("") logger.critical("*"*30) logger.critical(PROG_NAME+" => ERROR: self.domain."+key+" must be "+str(value)+"!") logger.critical(" self.domain.tm5_label = "+self.domain.tm5_label) logger.critical(" Computer says no...") logger.critical("*"*30) logger.critical("") try: raise RuntimeError except RuntimeError as e: #logger.exception("OOPS!") logger.critical(e, exc_info=True) #raise # => Will display the traceback on screen a second time sys.exit() # => Just exit. # end try # end if # end for # Now write the file dims_grid.F90. # The function is copied from .../TM5/base/py/TM5_Tools.py, write_dims_grid("{}".format(comp_dir), xbeg=self.domain.xmin, xend=self.domain.xmax, im=self.domain.nlon, dx=self.domain.dx, ybeg=self.domain.ymin, yend=self.domain.ymax, jm=self.domain.nlat, dy=self.domain.dy, ) # <<< JvP 20210118 # Copy source tree logger.debug("") logger.debug(PROG_NAME+" => Copy source tree.") #copytree( "{}/src".format(self.direxec), "{}/src".format(comp_dir), ignore=ignore_patterns("*.a") ) for proj in self.projs: # JvP 20210419: prepended self.direxec to proj. logger.debug(" Copying {} to {}.".format(self.direxec+proj, comp_dir)) copytree( "{}".format(self.direxec+proj), "{}".format(comp_dir), ignore=ignore_patterns("*.a") ) # end for proj # >>> JvP 20210118 # *) Write compiler flags to Makefile_flags and Makefile_rules: # both are included in Makefile. # *) Copy Makefile_makedeps from the CIF source tree. This is a # makefile that generates Makefile_deps: the dependencies # required to build tm5. Makefile_deps is also included in # Makefile. # *) Run make on Makefile_makedeps # Check fortran compiler if( self.fc == 'gfortran' ): FFLAGS = self.fflags_gfortran LDFLAGS = self.ldflags_gfortran elif( self.fc == 'mpif90' ): FFLAGS = self.fflags_gfortran LDFLAGS = self.ldflags_gfortran elif( self.fc == 'ifort' ): FFLAGS = self.fflags_ifort LDFLAGS = self.ldflags_ifort #logger.critical("") #logger.critical("*"*30) #logger.critical(PROG_NAME+" => ERROR: ifort not yet supported for compiling TM5 in CIF") #logger.critical(" self.fc = "+self.fc) #logger.critical(" Computer says no...") #logger.critical("*"*30) #logger.critical("") #try: # raise RuntimeError #except RuntimeError as e: # #logger.exception("OOPS!") # logger.critical(e, exc_info=True) # #raise # => Will display the traceback on screen a second time # sys.exit() # => Just exit. ## end try else: logger.critical("") logger.critical("*"*30) logger.critical(PROG_NAME+" => ERROR: unknown compiler selected for compiling TM5 in CIF") logger.critical(" self.fc = "+self.fc) logger.critical(" Computer says no...") logger.critical("*"*30) logger.critical("") try: raise RuntimeError except RuntimeError as e: #logger.exception("OOPS!") logger.critical(e, exc_info=True) #raise # => Will display the traceback on screen a second time sys.exit() # => Just exit. # end try # end if # Macros # Make a list of all allowed macros and the names of the include files # to write them to. In tmvar-tm5-build.rc, these variables are called # tm5.src.build.configure.macro.[mdf|tmm|tm5|udunits].[all|hfile]. # Note that my.udunits.def in tmvar-config__sara-cartesius.rc is empty, # so no macro's will be written to the udunits include file. meteo_macros_all = ['with_tmm_tmpp', 'with_tmm_tm5', 'with_tmm_ecmwf', 'with_tmm_ncep', 'with_tmm_msc', 'with_prism', 'with_tmm_convec_raw', 'with_tmm_convec_ec', 'with_tmm_convec_ec_gg', 'with_grib with_w3', 'with_udunits1' 'with_udunits2'] meteo_macros_file = 'tmm.inc' model_macros_all = ['slopes', 'secmom', 'with_zoom', 'with_mpi', 'with_budgets', 'with_tendencies', 'with_lapack', 'with_mkl', 'with_essl', 'without_advection', 'without_wet_deposition', 'without_photolysis', 'with_integer_percent', 'with_single_lapack', 'without_dry_deposition', 'with_convdiff', 'without_chemistry', 'with_essl', 'verbose_debug_output', 'MPI', 'with_limits', 'without_diffusion', 'without_convection', 'PCHECK'] model_macros_file = 'tm5.inc' mdf_macros_all = ['with_go', 'with_hdf4', 'with_netcdf', 'with_netcdf4', 'with_hdf5', 'with_hdf5_beta'] mdf_macros_file = 'mdf.inc' # # Write macros to file. Note that 'with_go' is always added to self.mdf_macros logger.info("") logger.info(PROG_NAME+" => Writing macros / include files.") macros_to_file( self.meteo_macros, ("{}/"+meteo_macros_file).format(comp_dir), meteo_macros_all ) macros_to_file( self.model_macros, ("{}/"+model_macros_file).format(comp_dir), model_macros_all ) macros_to_file( ['with_go']+self.mdf_macros, ("{}/"+mdf_macros_file).format(comp_dir), mdf_macros_all ) macros_to_file( [], ("{}/"+'udunits_version.inc').format(comp_dir), [] ) # Create Makefile_flags logger.info("") logger.info(PROG_NAME+" => Create Makefile_flags.") with open("{}/Makefile_flags".format(comp_dir), "w") as makefile: print_kwds = {"file":makefile, "sep":""} print("F77 = "+self.fc, **print_kwds) print("FC = "+self.fc, **print_kwds) print("LINKER = "+self.linker, **print_kwds) print("FFLAGS = "+FFLAGS, **print_kwds) print("LDFLAGS = "+LDFLAGS, **print_kwds) # end with Makefile_flags # Create Makefile_rules. Just empty for now... logger.info("") logger.info(PROG_NAME+" => Create Makefile_rules.") with open("{}/Makefile_rules".format(comp_dir), "w") as makefile: print_kwds = {"file":makefile, "sep":""} print("#", **print_kwds) print("# include file with explicit compile rules for Makefile.", **print_kwds) print("#", **print_kwds) # end with Makefile_rules # Create Makefile_deps: # *) Copy Makefile_makedeps from the TM5 CIF source directory # *) Run "make -f Makefile_makedeps" # logger.info("") logger.info(PROG_NAME+" => Copy Makefile_makedeps.") # JvP 20210419: prepended self.direxec to makedeps_dir. copy( "{}/Makefile_makedeps".format(self.direxec+self.makedeps_dir), comp_dir ) # make_cmd = "make -f Makefile_makedeps "+self.make_options+" deps" logger.debug("") logger.debug(PROG_NAME+" => "+make_cmd) # # Note that you set the stdout of the following process to a pipe, # and the stderr to stdout (so errors are printed to stdout as well). # Send the output to logfile using logging.debug(...). # https://stackoverflow.com/a/21953948 #process = subprocess.Popen( # make_cmd, # shell=True, # cwd="{}".format(comp_dir), # stdout=subprocess.PIPE, # stderr=subprocess.STDOUT, # universal_newlines=True, #) #stdout, stderr = process.communicate() #logger.debug(stdout) # # JvP 20210202 # With the old method shown above, the output from process.communicate # was buffered until the process was finished, and then logged all at # once. There was no way of checking the ouput of the process live. # This is especially annoying with long running processes such as TM5. # To be able to follow the output of a subprocess live, and log to # file simultaneously, I came up with the following: # *) google: python unbuffered subprocess logging # *) https://stackoverflow.com/q/37123805 # A link in the first comment pointed to the next item # *) https://stackoverflow.com/a/17698359 # Since I redirect stderr to stdout for subprocesses anyway, this method # is used below. Note that the context manager approach is only valid # for Python >= 3.2 according to the subprocess online documentation. # If you want to log stdout and stderr separately, have a look at the # next item. # *) https://stackoverflow.com/a/31953436 # with subprocess.Popen(make_cmd, shell=True, cwd="{}".format(comp_dir), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1) as p: for line in p.stdout: logger.debug(line.strip()) # end for # end with # <<< JvP 20210118 # Remove existing obj, mod and exe files? if( self.make_clean ): logger.info("") logger.info(PROG_NAME+" => Make clean.") with subprocess.Popen("make clean", shell=True, cwd="{}".format(comp_dir), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1) as p: for line in p.stdout: logger.debug(line.strip()) # end for # end with # end if # Now compiling. logger.info(PROG_NAME+" => Compiling.") with subprocess.Popen("make "+self.make_options+" tm5.x", shell=True, cwd="{}".format(comp_dir), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1) as p: for line in p.stdout: logger.debug(line.strip())
# end for # end with # end if #logger.debug("") #logger.debug("*"*30) #logger.debug("JvP: Computer says no!") #logger.debug("*"*30) #logger.debug("") #try: # raise RuntimeError #except RuntimeError as e: # #logger.exception("OOPS!") # logger.critical(e, exc_info=True) # #raise # => Will display the traceback on screen a second time # sys.exit() # => Just exit. ## end try # end if # end function compile