diff --git a/ESMCXX_HelloWorld_CMake/CMakeLists.txt b/ESMCXX_HelloWorld_CMake/CMakeLists.txt new file mode 100644 index 0000000..b24fd8c --- /dev/null +++ b/ESMCXX_HelloWorld_CMake/CMakeLists.txt @@ -0,0 +1,36 @@ +################################################################################ +# Configuration step: cmake -S . -B ./build +# Build step: cmake --build ./build -v +# Execution step: mpirun -np 8 ./build/ESMCXX_HelloWorld +# Clean-up step: rm -rf ./build PET* +################################################################################ + +cmake_minimum_required(VERSION 3.22) + +# Where to look for the local Find.cmake files +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +# Find ESMF +find_package(ESMF 8.3.0 MODULE REQUIRED) + +# Set compilers consistent with ESMF +set(CMAKE_Fortran_COMPILER "${ESMF_F90COMPILER}") +set(CMAKE_CXX_COMPILER "${ESMF_CXXCOMPILER}") +set(CMAKE_C_COMPILER "${ESMF_CCOMPILER}") + +# Optionally set compiler options consistent with ESMF +set(CMAKE_Fortran_FLAGS "${ESMF_F90COMPILEOPTS}") +set(CMAKE_CXX_FLAGS "${ESMF_CXXCOMPILEOPTS}") +set(CMAKE_C_FLAGS "${ESMF_CCOMPILEOPTS}") + +# Project +project(ESMCXX_HelloWorld_CMake + VERSION 1.0 + LANGUAGES Fortran CXX C + ) + +# Executable +add_executable(ESMCXX_HelloWorld ESMCXX_HelloWorld.C) + +# Executable depends on ESMF +target_link_libraries(ESMCXX_HelloWorld ESMF::ESMC) diff --git a/ESMCXX_HelloWorld_CMake/ESMCXX_HelloWorld.C b/ESMCXX_HelloWorld_CMake/ESMCXX_HelloWorld.C new file mode 100644 index 0000000..b00b43e --- /dev/null +++ b/ESMCXX_HelloWorld_CMake/ESMCXX_HelloWorld.C @@ -0,0 +1,25 @@ +// Earth System Modeling Framework +// Copyright (c) 2002-2024, University Corporation for Atmospheric Research, +// Massachusetts Institute of Technology, Geophysical Fluid Dynamics +// Laboratory, University of Michigan, National Centers for Environmental +// Prediction, Los Alamos National Laboratory, Argonne National Laboratory, +// NASA Goddard Space Flight Center. +// Licensed under the University of Illinois-NCSA License. + +#include "ESMC.h" + +int main(int argc, char *argv[]){ + + // local variables + int rc; + + rc = ESMC_Initialize(nullptr, ESMC_ArgLast); + if (rc != ESMF_SUCCESS) ESMC_FinalizeWithFlag(ESMC_END_ABORT); + + rc = ESMC_LogWrite(">>> Hello ESMC World from C++ <<<", ESMC_LOGMSG_INFO); + if (rc != ESMF_SUCCESS) ESMC_FinalizeWithFlag(ESMC_END_ABORT); + + rc = ESMC_Finalize(); + + return 0; +} diff --git a/ESMCXX_HelloWorld_CMake/README.md b/ESMCXX_HelloWorld_CMake/README.md new file mode 100644 index 0000000..a58499e --- /dev/null +++ b/ESMCXX_HelloWorld_CMake/README.md @@ -0,0 +1,33 @@ +ESMCXX_HelloWorld_CMake +======================= + +This directory contains code that is based on the ESMF C API (commonly referred to as ESMC) from C++. + +The application writes ">>> Hello ESMC World from C++ <<<" to the ESMF default log (see PET*.ESMF_LogFile's). + +The main purpose of this example is to demonstrate the use of CMake for ESMF applications written in C++. The code is accompanied by `CMakeLists.txt` and `cmake/FindESMF.cmake` files. + +Notice the dependency of the example on a relatively recent release of CMake: version 3.22. This is specified in file `CMakeLists.txt`. The primary reason for this restictive dependency is that not until version 3.22 was it supported to use the `find_package()` and `set()` functions before `project()`. Hence it was more difficult in the older versions to specified the compilers consistent with those used by ESMF. + +Notice that it is possible to get the desired end result with previous versions of CMake, requiring some re-arranging of the order of functions in `CMakeLists.txt`. However, the more recently supported order of functions leads to a simpler and more intuitive version of `CMakeLists.txt` file shown here. + +The code can be built using any of the usual CMake build procedures: + + mkdir build; cd build + cmake .. + make + +or alternatively using the `-S`, `-B`, and `--build` CMake options: + + cmake -S . -B ./build + cmake --build ./build + +And execute on 8 PETs, e.g. via mpirun: + + mpirun -np 8 ./build/ESMCXX_HelloWorld + +================================================================================ + +Please contact esmf_support@ucar.edu with any questions or problems. + +================================================================================ diff --git a/ESMCXX_HelloWorld_CMake/cmake/FindESMF.cmake b/ESMCXX_HelloWorld_CMake/cmake/FindESMF.cmake new file mode 100644 index 0000000..e0acb45 --- /dev/null +++ b/ESMCXX_HelloWorld_CMake/cmake/FindESMF.cmake @@ -0,0 +1,167 @@ +# - Try to find ESMF +# +# Uses ESMFMKFILE to find the filepath of esmf.mk. If this is NOT set, then this +# module will attempt to find esmf.mk. If ESMFMKFILE exists, then +# ESMF_FOUND=TRUE and all ESMF makefile variables will be set in the global +# scope. Optionally, set ESMF_MKGLOBALS to a string list to filter makefile +# variables. For example, to globally scope only ESMF_LIBSDIR and ESMF_APPSDIR +# variables, use this CMake command in CMakeLists.txt: +# +# set(ESMF_MKGLOBALS "LIBSDIR" "APPSDIR") + +# Set ESMFMKFILE as defined by system env variable. If it's not explicitly set +# try to find esmf.mk file in default locations (ESMF_ROOT, CMAKE_PREFIX_PATH, +# etc) +if(NOT DEFINED ESMFMKFILE) + if(NOT DEFINED ENV{ESMFMKFILE}) + find_path(ESMFMKFILE_PATH esmf.mk PATH_SUFFIXES lib lib64) + if(ESMFMKFILE_PATH) + set(ESMFMKFILE ${ESMFMKFILE_PATH}/esmf.mk) + message(STATUS "Found esmf.mk file ${ESMFMKFILE}") + endif() + else() + set(ESMFMKFILE $ENV{ESMFMKFILE}) + endif() +endif() + +# Only parse the mk file if it is found +if(EXISTS ${ESMFMKFILE}) + set(ESMFMKFILE ${ESMFMKFILE} CACHE FILEPATH "Path to esmf.mk file") + set(ESMF_FOUND TRUE CACHE BOOL "esmf.mk file found" FORCE) + + # Read the mk file + file(STRINGS "${ESMFMKFILE}" esmfmkfile_contents) + # Parse each line in the mk file + foreach(str ${esmfmkfile_contents}) + # Only consider uncommented lines + string(REGEX MATCH "^[^#]" def ${str}) + # Line is not commented + if(def) + # Extract the variable name + string(REGEX MATCH "^[^=]+" esmf_varname ${str}) + # Extract the variable's value + string(REGEX MATCH "=.+$" esmf_vardef ${str}) + # Only for variables with a defined value + if(esmf_vardef) + # Get rid of the assignment string + string(SUBSTRING ${esmf_vardef} 1 -1 esmf_vardef) + # Remove whitespace + string(STRIP ${esmf_vardef} esmf_vardef) + # A string or single-valued list + if(NOT DEFINED ESMF_MKGLOBALS) + # Set in global scope + set(${esmf_varname} ${esmf_vardef}) + # Don't display by default in GUI + mark_as_advanced(esmf_varname) + else() # Need to filter global promotion + foreach(m ${ESMF_MKGLOBALS}) + string(FIND ${esmf_varname} ${m} match) + # Found the string + if(NOT ${match} EQUAL -1) + # Promote to global scope + set(${esmf_varname} ${esmf_vardef}) + # Don't display by default in the GUI + mark_as_advanced(esmf_varname) + # No need to search for the current string filter + break() + endif() + endforeach() + endif() + endif() + endif() + endforeach() + + # Construct ESMF_VERSION from ESMF_VERSION_STRING_GIT + # ESMF_VERSION_MAJOR and ESMF_VERSION_MINOR are defined in ESMFMKFILE + set(ESMF_VERSION 0) + set(ESMF_VERSION_PATCH ${ESMF_VERSION_REVISION}) + set(ESMF_BETA_RELEASE FALSE) + if(ESMF_VERSION_BETASNAPSHOT MATCHES "^('T')$") + set(ESMF_BETA_RELEASE TRUE) + if(ESMF_VERSION_STRING_GIT MATCHES "^ESMF.*beta_snapshot") + set(ESMF_BETA_SNAPSHOT ${ESMF_VERSION_STRING_GIT}) + elseif(ESMF_VERSION_STRING_GIT MATCHES "^v.\..\..b") + set(ESMF_BETA_SNAPSHOT ${ESMF_VERSION_STRING_GIT}) + else() + set(ESMF_BETA_SNAPSHOT 0) + endif() + message(STATUS "Detected ESMF Beta snapshot: ${ESMF_BETA_SNAPSHOT}") + endif() + set(ESMF_VERSION "${ESMF_VERSION_MAJOR}.${ESMF_VERSION_MINOR}.${ESMF_VERSION_PATCH}") + + # Find the ESMF library + if(USE_ESMF_STATIC_LIBS) + find_library(ESMF_LIBRARY_LOCATION NAMES libesmf.a PATHS ${ESMF_LIBSDIR} NO_DEFAULT_PATH) + if(ESMF_LIBRARY_LOCATION MATCHES "ESMF_LIBRARY_LOCATION-NOTFOUND") + message(WARNING "Static ESMF library (libesmf.a) not found in \ + ${ESMF_LIBSDIR}. Try setting USE_ESMF_STATIC_LIBS=OFF") + endif() + if(NOT TARGET ESMF::ESMF) + add_library(ESMF::ESMF STATIC IMPORTED) + endif() + if(NOT TARGET ESMF::ESMC) + add_library(ESMF::ESMC STATIC IMPORTED) + endif() + else() + find_library(ESMF_LIBRARY_LOCATION NAMES esmf PATHS ${ESMF_LIBSDIR} NO_DEFAULT_PATH) + if(ESMF_LIBRARY_LOCATION MATCHES "ESMF_LIBRARY_LOCATION-NOTFOUND") + message(WARNING "ESMF library not found in ${ESMF_LIBSDIR}.") + endif() + if(NOT TARGET ESMF::ESMF) + add_library(ESMF::ESMF UNKNOWN IMPORTED) + endif() + if(NOT TARGET ESMF::ESMC) + add_library(ESMF::ESMC UNKNOWN IMPORTED) + endif() + endif() + + # Add ESMF as an alias to ESMF::ESMF for backward compatibility + if(NOT TARGET ESMF) + add_library(ESMF ALIAS ESMF::ESMF) + endif() + + # Add ESMF include directories + set(ESMF_INCLUDE_DIRECTORIES "") + separate_arguments(_ESMF_F90COMPILEPATHS UNIX_COMMAND ${ESMF_F90COMPILEPATHS}) + foreach(_ITEM ${_ESMF_F90COMPILEPATHS}) + string(REGEX REPLACE "^-I" "" _ITEM "${_ITEM}") + list(APPEND ESMF_INCLUDE_DIRECTORIES ${_ITEM}) + endforeach() + set(ESMC_INCLUDE_DIRECTORIES "") + separate_arguments(_ESMF_CCOMPILEPATHS UNIX_COMMAND ${ESMF_CCOMPILEPATHS}) + foreach(_ITEM ${_ESMF_CCOMPILEPATHS}) + string(REGEX REPLACE "^-I" "" _ITEM "${_ITEM}") + list(APPEND ESMC_INCLUDE_DIRECTORIES ${_ITEM}) + endforeach() + + # Add ESMF link libraries + string(STRIP "${ESMF_F90LINKRPATHS} ${ESMF_F90ESMFLINKRPATHS} ${ESMF_F90ESMFLINKPATHS} ${ESMF_F90LINKPATHS} ${ESMF_F90LINKLIBS} ${ESMF_F90LINKOPTS}" ESMF_INTERFACE_LINK_LIBRARIES) + string(STRIP "${ESMF_CLINKRPATHS} ${ESMF_CESMFLINKRPATHS} ${ESMF_CESMFLINKPATHS} ${ESMF_CLINKPATHS} ${ESMF_CLINKLIBS} ${ESMF_CLINKOPTS}" ESMC_INTERFACE_LINK_LIBRARIES) + + # Finalize find_package + include(FindPackageHandleStandardArgs) + + find_package_handle_standard_args( + ${CMAKE_FIND_PACKAGE_NAME} + REQUIRED_VARS ESMF_LIBRARY_LOCATION + ESMF_INCLUDE_DIRECTORIES + ESMC_INCLUDE_DIRECTORIES + ESMF_INTERFACE_LINK_LIBRARIES + ESMC_INTERFACE_LINK_LIBRARIES + VERSION_VAR ESMF_VERSION) + + set_target_properties(ESMF::ESMF PROPERTIES + IMPORTED_LOCATION "${ESMF_LIBRARY_LOCATION}" + INTERFACE_INCLUDE_DIRECTORIES "${ESMF_INCLUDE_DIRECTORIES}" + INTERFACE_LINK_LIBRARIES "${ESMF_INTERFACE_LINK_LIBRARIES}") + + set_target_properties(ESMF::ESMC PROPERTIES + IMPORTED_LOCATION "${ESMF_LIBRARY_LOCATION}" + INTERFACE_INCLUDE_DIRECTORIES "${ESMC_INCLUDE_DIRECTORIES}" + INTERFACE_LINK_LIBRARIES "${ESMC_INTERFACE_LINK_LIBRARIES}") + +else() + set(ESMF_FOUND FALSE CACHE BOOL "esmf.mk file NOT found" FORCE) + message(WARNING "ESMFMKFILE ${ESMFMKFILE} not found. Try setting ESMFMKFILE \ + to esmf.mk location.") +endif() diff --git a/ESMC_HelloWorld_CMake/README.md b/ESMC_HelloWorld_CMake/README.md index b751895..cafe933 100644 --- a/ESMC_HelloWorld_CMake/README.md +++ b/ESMC_HelloWorld_CMake/README.md @@ -5,7 +5,7 @@ This directory contains code that is based on the ESMF C API (commonly referred The application writes ">>> Hello ESMC World <<<" to the ESMF default log (see PET*.ESMF_LogFile's). -The main purpose of this example is to demonstrate the use of CMake for ESMC applications. The code is accompanied by `CMakeLists.txt` and `cmake/FindESMF.cmake` files. +The main purpose of this example is to demonstrate the use of CMake for ESMF applications written in C. The code is accompanied by `CMakeLists.txt` and `cmake/FindESMF.cmake` files. Notice the dependency of the example on a relatively recent release of CMake: version 3.22. This is specified in file `CMakeLists.txt`. The primary reason for this restictive dependency is that not until version 3.22 was it supported to use the `find_package()` and `set()` functions before `project()`. Hence it was more difficult in the older versions to specified the compilers consistent with those used by ESMF. diff --git a/ESMF_HelloWorld_CMake/README.md b/ESMF_HelloWorld_CMake/README.md index 54aa78b..4fb97c2 100644 --- a/ESMF_HelloWorld_CMake/README.md +++ b/ESMF_HelloWorld_CMake/README.md @@ -5,7 +5,7 @@ This directory contains code that is based on the ESMF Fortran API. The application writes ">>> Hello ESMF World <<<" to the ESMF default log (see PET*.ESMF_LogFile's). -The main purpose of this example is to demonstrate the use of CMake for ESMF applications. The code is accompanied by `CMakeLists.txt` and `cmake/FindESMF.cmake` files. +The main purpose of this example is to demonstrate the use of CMake for ESMF applications written in Fortran. The code is accompanied by `CMakeLists.txt` and `cmake/FindESMF.cmake` files. Notice the dependency of the example on a relatively recent release of CMake: version 3.22. This is specified in file `CMakeLists.txt`. The primary reason for this restictive dependency is that not until version 3.22 was it supported to use the `find_package()` and `set()` functions before `project()`. Hence it was more difficult in the older versions to specified the compilers consistent with those used by ESMF.