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