From 66bb68edea0da30084c2bcd8476aeaa6e88530b5 Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Mon, 9 Mar 2026 18:11:46 -0700 Subject: [PATCH 01/23] Use new nwm-ewts libraries and python package EWTS versions to build summary output to console Updates to CMakeLists.txt for integrating the nwm-ewts libraries Initial conversion to ewts_ngen_bridge library. Updated CMakeLists. Replaced logMsgAndThrowError with separate calls to LOG and throw. --- CMakeLists.txt | 35 +- Dockerfile | 4 +- extern/LASAM | 2 +- extern/SoilFreezeThaw/SoilFreezeThaw | 2 +- .../SoilMoistureProfiles/SoilMoistureProfiles | 2 +- extern/cfe/CMakeLists.txt | 15 +- extern/cfe/cfe | 2 +- extern/lstm | 2 +- extern/noah-owp-modular/CMakeLists.txt | 14 + extern/noah-owp-modular/noah-owp-modular | 2 +- extern/sac-sma/CMakeLists.txt | 16 +- extern/sac-sma/sac-sma | 2 +- extern/snow17 | 2 +- extern/t-route | 2 +- extern/topmodel/CMakeLists.txt | 14 +- extern/topmodel/topmodel | 2 +- extern/ueb-bmi | 2 +- include/bmi/Bmi_C_Adapter.hpp | 2 +- include/bmi/Bmi_Cpp_Adapter.hpp | 2 +- include/bmi/Bmi_Fortran_Adapter.hpp | 2 +- include/bmi/Bmi_Py_Adapter.hpp | 2 +- include/core/Layer.hpp | 2 +- include/core/Partition_Parser.hpp | 2 +- include/core/mediator/UnitsHelper.hpp | 2 +- .../core/nexus/HY_PointHydroNexusRemote.hpp | 2 +- .../forcing/CsvPerFeatureForcingProvider.hpp | 2 +- .../forcing/ForcingsEngineDataProvider.hpp | 2 +- .../forcing/OptionalWrappedDataProvider.hpp | 2 +- include/geojson/JSONProperty.hpp | 2 +- include/geojson/features/FeatureBase.hpp | 2 +- .../catchment/Bmi_Multi_Formulation.hpp | 2 +- .../catchment/Catchment_Formulation.hpp | 2 +- .../realizations/catchment/Formulation.hpp | 2 +- .../catchment/Formulation_Constructors.hpp | 2 +- .../catchment/Formulation_Manager.hpp | 2 +- include/realizations/config/layer.hpp | 2 +- include/realizations/config/time.hpp | 2 +- include/simulation_time/Simulation_Time.hpp | 2 +- include/utilities/CSV_Reader.h | 2 +- include/utilities/FileChecker.h | 5 +- include/utilities/Logger.hpp | 84 --- include/utilities/bmi_utilities.hpp | 2 +- include/utilities/mdarray/mdarray.hpp | 2 +- include/utilities/mdframe/mdframe.hpp | 2 +- .../utilities/python/HydrofabricSubsetter.hpp | 2 +- src/NGen.cpp | 8 +- src/bmi/AbstractCLibBmiAdapter.cpp | 20 +- src/bmi/Bmi_Adapter.cpp | 28 +- src/bmi/Bmi_C_Adapter.cpp | 89 ++- src/bmi/Bmi_Fortran_Adapter.cpp | 89 ++- src/bmi/Bmi_Py_Adapter.cpp | 19 +- src/bmi/CMakeLists.txt | 2 + src/core/CMakeLists.txt | 4 + src/core/HY_Features.cpp | 2 +- src/core/HY_Features_MPI.cpp | 2 +- src/core/NgenSimulation.cpp | 2 +- src/core/SurfaceLayer.cpp | 2 +- src/core/mediator/CMakeLists.txt | 2 + src/core/nexus/CMakeLists.txt | 2 + src/core/nexus/HY_PointHydroNexus.cpp | 2 +- src/core/nexus/HY_PointHydroNexusRemote.cpp | 6 +- src/forcing/CMakeLists.txt | 1 + src/forcing/ForcingsEngineDataProvider.cpp | 14 +- src/forcing/NetCDFPerFeatureDataProvider.cpp | 15 +- src/forcing/NullForcingProvider.cpp | 2 +- src/geojson/CMakeLists.txt | 4 + src/geojson/JSONProperty.cpp | 2 +- src/geopackage/CMakeLists.txt | 2 + src/geopackage/feature.cpp | 2 +- src/geopackage/geometry.cpp | 6 +- src/geopackage/ngen_sqlite.cpp | 2 +- src/geopackage/proj.cpp | 2 +- src/geopackage/read.cpp | 15 +- src/geopackage/wkb.cpp | 16 +- src/partitionGenerator.cpp | 33 +- .../catchment/Bmi_C_Formulation.cpp | 13 +- .../catchment/Bmi_Cpp_Formulation.cpp | 14 +- .../catchment/Bmi_Fortran_Formulation.cpp | 15 +- .../catchment/Bmi_Module_Formulation.cpp | 7 +- .../catchment/Bmi_Multi_Formulation.cpp | 18 +- .../catchment/Bmi_Py_Formulation.cpp | 13 +- src/realizations/catchment/CMakeLists.txt | 3 + src/utilities/CMakeLists.txt | 2 + src/utilities/bmi/CMakeLists.txt | 2 + src/utilities/bmi/mass_balance.cpp | 4 +- src/utilities/logging/CMakeLists.txt | 4 +- src/utilities/logging/Logger.cpp | 671 ------------------ src/utilities/logging/logging_utils.cpp | 2 +- src/utilities/mdframe/CMakeLists.txt | 1 + src/utilities/mdframe/handler_csv.cpp | 12 +- src/utilities/mdframe/handler_netcdf.cpp | 3 +- src/utilities/python/CMakeLists.txt | 3 + src/utilities/python/InterpreterUtil.cpp | 2 +- 93 files changed, 466 insertions(+), 979 deletions(-) delete mode 100644 include/utilities/Logger.hpp delete mode 100644 src/utilities/logging/Logger.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ba84add7e..a320eab3a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,7 @@ +# +# ngen/CMakeLists.txt +# + # Ensure CMake policies have defaults depending on the CMake version used # between the two versions specified. e.g. if 3.18 is used, then 3.18 defaults # will be used instead of 3.17 defaults. @@ -118,6 +122,30 @@ project(ngen VERSION 0.3.0) add_executable(ngen "${NGEN_SRC_DIR}/NGen.cpp") +# --- EWTS (installed from nwm-ewts) --- +find_package(ewts CONFIG REQUIRED) + +if(DEFINED ewts_VERSION AND NOT "${ewts_VERSION}" STREQUAL "") + set(NGEN_EWTS_VERSION "${ewts_VERSION}") +else() + set(NGEN_EWTS_VERSION "") +endif() + +if(DEFINED EWTS_NGWPC_VERSION AND NOT "${EWTS_NGWPC_VERSION}" STREQUAL "") + set(NGEN_EWTS_NGWPC_VERSION "${EWTS_NGWPC_VERSION}") +else() + set(NGEN_EWTS_NGWPC_VERSION "") +endif() + +get_filename_component(EWTS_PREFIX "${ewts_DIR}" DIRECTORY) +get_filename_component(EWTS_PREFIX "${EWTS_PREFIX}" DIRECTORY) +get_filename_component(EWTS_PREFIX "${EWTS_PREFIX}" DIRECTORY) + +message(STATUS "Found EWTS: ${EWTS_PREFIX} (found version ${ewts_VERSION})") + +# NGen itself uses the NGen-specific EWTS logger/bridge +target_link_libraries(ngen PRIVATE ewts::ewts_ngen_bridge) + # Dependencies ================================================================ # ----------------------------------------------------------------------------- @@ -286,7 +314,6 @@ if(UDUNITS_QUIET) add_compile_definitions(UDUNITS_QUIET) endif() - # ----------------------------------------------------------------------------- # Project Targets # ----------------------------------------------------------------------------- @@ -359,6 +386,7 @@ if(NGEN_WITH_ROUTING) endif() add_executable(partitionGenerator src/partitionGenerator.cpp) +target_link_libraries(partitionGenerator PRIVATE ewts::ewts_ngen_bridge) target_link_libraries(partitionGenerator PUBLIC NGen::logging) target_include_directories(partitionGenerator PUBLIC "${PROJECT_BINARY_DIR}/include") if(NGEN_WITH_SQLITE) @@ -449,7 +477,10 @@ ngen_multiline_message( " Boost:" " Version: ${Boost_VERSION}" " Include: ${Boost_INCLUDE_DIRS}" -" Library: ${Boost_LIBRARY_DIRS}") +" Library: ${Boost_LIBRARY_DIRS}" +" EWTS:" +" Package Version: ${NGEN_EWTS_VERSION}" +" NGWPC Version: ${NGEN_EWTS_NGWPC_VERSION}") ngen_dependent_multiline_message(INTEL_DPCPP " Intel DPC++:" " Version: ${SYCL_LANGUAGE_VERSION}" diff --git a/Dockerfile b/Dockerfile index 93edd0d218..ca6b38dbfc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,10 +7,10 @@ ARG ORG=ngwpc ARG NGEN_FORCING_IMAGE_TAG=latest ARG NGEN_FORCING_IMAGE=ghcr.io/ngwpc/ngen-bmi-forcing:${NGEN_FORCING_IMAGE_TAG} -FROM ${NGEN_FORCING_IMAGE} AS base +#FROM ${NGEN_FORCING_IMAGE} AS base # Uncomment when building locally -#FROM ngen-bmi-forcing AS base +FROM ngen-bmi-forcing AS base # OCI Metadata Arguments ARG NGEN_FORCING_IMAGE diff --git a/extern/LASAM b/extern/LASAM index be9146acc4..1a659e98b0 160000 --- a/extern/LASAM +++ b/extern/LASAM @@ -1 +1 @@ -Subproject commit be9146acc442d3efeb0b59531b0af6e8ed75ba89 +Subproject commit 1a659e98b0eee7630afa592a8bbc6714864ee971 diff --git a/extern/SoilFreezeThaw/SoilFreezeThaw b/extern/SoilFreezeThaw/SoilFreezeThaw index 6d8b09bdf5..e2b5246e29 160000 --- a/extern/SoilFreezeThaw/SoilFreezeThaw +++ b/extern/SoilFreezeThaw/SoilFreezeThaw @@ -1 +1 @@ -Subproject commit 6d8b09bdf5be7f85a8ab6de69ee0356164f1e12b +Subproject commit e2b5246e29721a965e8999859316ed19e3ee8414 diff --git a/extern/SoilMoistureProfiles/SoilMoistureProfiles b/extern/SoilMoistureProfiles/SoilMoistureProfiles index cd99f4f056..53b6f55ad9 160000 --- a/extern/SoilMoistureProfiles/SoilMoistureProfiles +++ b/extern/SoilMoistureProfiles/SoilMoistureProfiles @@ -1 +1 @@ -Subproject commit cd99f4f056f763d9ce76b20789ec51901c5389a7 +Subproject commit 53b6f55ad90087665d541732e184c97ce4f426c0 diff --git a/extern/cfe/CMakeLists.txt b/extern/cfe/CMakeLists.txt index 6ef23bf9d5..52bfb303a4 100644 --- a/extern/cfe/CMakeLists.txt +++ b/extern/cfe/CMakeLists.txt @@ -22,9 +22,9 @@ find_package(Boost 1.79.0 REQUIRED COMPONENTS serialization) if(WIN32) - add_library(cfebmi cfe/src/bmi_cfe.c cfe/src/cfe.c cfe/src/giuh.c cfe/src/logger.c cfe/src/conceptual_reservoir.c cfe/src/nash_cascade.c cfe/src/bmi_serialization.cxx) + add_library(cfebmi cfe/src/bmi_cfe.c cfe/src/cfe.c cfe/src/giuh.c cfe/src/conceptual_reservoir.c cfe/src/nash_cascade.c cfe/src/bmi_serialization.cxx) else() - add_library(cfebmi SHARED cfe/src/bmi_cfe.c cfe/src/cfe.c cfe/src/giuh.c cfe/src/logger.c cfe/src/conceptual_reservoir.c cfe/src/nash_cascade.c cfe/src/bmi_serialization.cxx) + add_library(cfebmi SHARED cfe/src/bmi_cfe.c cfe/src/cfe.c cfe/src/giuh.c cfe/src/conceptual_reservoir.c cfe/src/nash_cascade.c cfe/src/bmi_serialization.cxx) endif() target_include_directories(cfebmi PRIVATE cfe/include) @@ -35,6 +35,17 @@ set_target_properties(cfebmi PROPERTIES PUBLIC_HEADER cfe/include/bmi_cfe.h) target_link_libraries(cfebmi PRIVATE Boost::serialization) +# --- EWTS (installed from nwm-ewts) --- +find_package(ewts CONFIG REQUIRED) + +# Always use EWTS runtime logger for C +target_link_libraries(cfebmi PRIVATE ewts::ewts_c) + +# Built with ngen bridge +target_link_libraries(cfebmi PRIVATE ewts::ewts_ngen_bridge) +target_compile_definitions(cfebmi PRIVATE EWTS_HAVE_NGEN_BRIDGE) + + # Code requires minimum of C99 standard to compile set_target_properties(cfebmi PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED ON) diff --git a/extern/cfe/cfe b/extern/cfe/cfe index db54ba0ef3..9581344a05 160000 --- a/extern/cfe/cfe +++ b/extern/cfe/cfe @@ -1 +1 @@ -Subproject commit db54ba0ef3707d87202d958ca1a075921fdb5855 +Subproject commit 9581344a057ab18a1f954c484999c2647cca2443 diff --git a/extern/lstm b/extern/lstm index d9e3102b62..8e90c914ec 160000 --- a/extern/lstm +++ b/extern/lstm @@ -1 +1 @@ -Subproject commit d9e3102b62ecd95037ca47a9a8d610e70ec588a5 +Subproject commit 8e90c914ec0deeccf087dc3fabf3132c50501dfa diff --git a/extern/noah-owp-modular/CMakeLists.txt b/extern/noah-owp-modular/CMakeLists.txt index 526e6c9239..1aeb29f7ea 100644 --- a/extern/noah-owp-modular/CMakeLists.txt +++ b/extern/noah-owp-modular/CMakeLists.txt @@ -1,3 +1,7 @@ +# +# noah-owp-modular CMakeLists.txt +# + cmake_minimum_required(VERSION 3.12...3.26) # NGen NOAH-OWP-MODULAR listfile shim @@ -28,6 +32,12 @@ target_compile_options(surfacebmi PRIVATE -cpp -ffree-line-length-none) target_include_directories(surfacebmi INTERFACE "${_SURFACEBMI_BINARY_DIR}/mod") target_compile_definitions(surfacebmi PRIVATE BMI_ACTIVE) +# --- EWTS (installed from nwm-ewts) --- +find_package(ewts CONFIG REQUIRED) + +# Always use EWTS runtime logger for Fortran +target_link_libraries(surfacebmi PRIVATE ewts::ewts_fortran) + if(NGEN_IS_MAIN_PROJECT) # This ensures we can build NOAH-OWP-Modular with NGen support, but @@ -41,6 +51,10 @@ if(NGEN_IS_MAIN_PROJECT) target_link_libraries(surfacebmi PUBLIC iso_c_bmi) target_compile_definitions(surfacebmi PRIVATE NGEN_FORCING_ACTIVE NGEN_OUTPUT_ACTIVE NGEN_ACTIVE) + + # Built with ngen bridge + target_link_libraries(surfacebmi PRIVATE ewts::ewts_ngen_bridge) + target_compile_definitions(surfacebmi PRIVATE EWTS_HAVE_NGEN_BRIDGE) else() find_path(NETCDF_MODULE_DIR netcdf.mod PATHS "${NETCDF_ROOT}/include" diff --git a/extern/noah-owp-modular/noah-owp-modular b/extern/noah-owp-modular/noah-owp-modular index 713244b946..6f14b6026a 160000 --- a/extern/noah-owp-modular/noah-owp-modular +++ b/extern/noah-owp-modular/noah-owp-modular @@ -1 +1 @@ -Subproject commit 713244b94638224e3772ce96338e8c7bdaf44b09 +Subproject commit 6f14b6026a0f65f6e1e48d4acd399d6f0b5427df diff --git a/extern/sac-sma/CMakeLists.txt b/extern/sac-sma/CMakeLists.txt index 4d06eaa19e..2646aa7ab2 100644 --- a/extern/sac-sma/CMakeLists.txt +++ b/extern/sac-sma/CMakeLists.txt @@ -1,3 +1,7 @@ +# +# ngen/extern/sac-sma/CMakeLists.txt +# + cmake_minimum_required(VERSION 3.12) enable_language( Fortran ) add_subdirectory(../iso_c_fortran_bmi ${CMAKE_BINARY_DIR}/iso_c_bmi) @@ -83,7 +87,17 @@ target_link_libraries( sacbmi PUBLIC iso_c_bmi ) set_target_properties(sacbmi PROPERTIES VERSION ${PROJECT_VERSION}) #TODO is this needed for fortran? -#set_target_properties(surfacebmi PROPERTIES PUBLIC_HEADER ${BMI_SOURCE}) +#set_target_properties(sacbmi PROPERTIES PUBLIC_HEADER ${BMI_SOURCE}) + +# --- EWTS (installed from nwm-ewts) --- +find_package(ewts CONFIG REQUIRED) + +# Always use EWTS runtime logger for Fortran +target_link_libraries(sacbmi PRIVATE ewts::ewts_fortran) + +# Built with ngen bridge +target_link_libraries(sacbmi PRIVATE ewts::ewts_ngen_bridge) +target_compile_definitions(sacbmi PRIVATE EWTS_HAVE_NGEN_BRIDGE) include(GNUInstallDirs) diff --git a/extern/sac-sma/sac-sma b/extern/sac-sma/sac-sma index 986953f471..08e4e4b929 160000 --- a/extern/sac-sma/sac-sma +++ b/extern/sac-sma/sac-sma @@ -1 +1 @@ -Subproject commit 986953f471e3996670a07df9d6850fe286e8435a +Subproject commit 08e4e4b929083603ade1fc9c45141078d3a6649a diff --git a/extern/snow17 b/extern/snow17 index 70ff380df0..1eef00157c 160000 --- a/extern/snow17 +++ b/extern/snow17 @@ -1 +1 @@ -Subproject commit 70ff380df0a57dd175b007eda04ce71561f16dfa +Subproject commit 1eef00157c6c66ffcc1dd9c34ef53f44a9e89a8e diff --git a/extern/t-route b/extern/t-route index c00f9fd16b..da36292587 160000 --- a/extern/t-route +++ b/extern/t-route @@ -1 +1 @@ -Subproject commit c00f9fd16b0aa7b6de2bf9f9696166402291b743 +Subproject commit da36292587df8a3aafe62b73004022851a56ea8a diff --git a/extern/topmodel/CMakeLists.txt b/extern/topmodel/CMakeLists.txt index 0bc32b327b..604a11c45e 100644 --- a/extern/topmodel/CMakeLists.txt +++ b/extern/topmodel/CMakeLists.txt @@ -19,9 +19,9 @@ set(Boost_USE_STATIC_RUNTIME OFF) find_package(Boost 1.79.0 REQUIRED COMPONENTS serialization) if(WIN32) - add_library(topmodelbmi topmodel/src/bmi_topmodel.c topmodel/src/topmodel.c topmodel/src/logger.c topmodel/src/bmi_serialization.cpp) + add_library(topmodelbmi topmodel/src/bmi_topmodel.c topmodel/src/topmodel.c topmodel/src/bmi_serialization.cpp) else() - add_library(topmodelbmi SHARED topmodel/src/bmi_topmodel.c topmodel/src/topmodel.c topmodel/src/logger.c topmodel/src/bmi_serialization.cpp) + add_library(topmodelbmi SHARED topmodel/src/bmi_topmodel.c topmodel/src/topmodel.c topmodel/src/bmi_serialization.cpp) endif() target_include_directories(topmodelbmi PRIVATE topmodel/include) @@ -32,6 +32,16 @@ set_target_properties(topmodelbmi PROPERTIES PUBLIC_HEADER topmodel/include/bmi_ target_link_libraries(topmodelbmi PRIVATE Boost::serialization) +# --- EWTS (installed from nwm-ewts) --- +find_package(ewts CONFIG REQUIRED) + +# Always use EWTS runtime logger for C +target_link_libraries(topmodelbmi PRIVATE ewts::ewts_c) + +# Built with ngen bridge +target_link_libraries(topmodelbmi PRIVATE ewts::ewts_ngen_bridge) +target_compile_definitions(topmodelbmi PRIVATE EWTS_HAVE_NGEN_BRIDGE) + # Code requires minimum of C99 standard to compile set_target_properties(topmodelbmi PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED ON) diff --git a/extern/topmodel/topmodel b/extern/topmodel/topmodel index 6259302492..1f08e9822b 160000 --- a/extern/topmodel/topmodel +++ b/extern/topmodel/topmodel @@ -1 +1 @@ -Subproject commit 6259302492941fbae086a685a80c5b4632cf822d +Subproject commit 1f08e9822ba3eead7c70257949337f9fa3bda2d3 diff --git a/extern/ueb-bmi b/extern/ueb-bmi index 1b2804cbb0..e47f8a0846 160000 --- a/extern/ueb-bmi +++ b/extern/ueb-bmi @@ -1 +1 @@ -Subproject commit 1b2804cbb04acbd38642bd63d213517b13c78df1 +Subproject commit e47f8a0846c988cc732ea295bd116653ebac0b89 diff --git a/include/bmi/Bmi_C_Adapter.hpp b/include/bmi/Bmi_C_Adapter.hpp index 9ac7b1620f..e2068f8b59 100644 --- a/include/bmi/Bmi_C_Adapter.hpp +++ b/include/bmi/Bmi_C_Adapter.hpp @@ -3,7 +3,7 @@ #include #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include "bmi.h" #include "AbstractCLibBmiAdapter.hpp" diff --git a/include/bmi/Bmi_Cpp_Adapter.hpp b/include/bmi/Bmi_Cpp_Adapter.hpp index 91a67b1601..66abb1f648 100644 --- a/include/bmi/Bmi_Cpp_Adapter.hpp +++ b/include/bmi/Bmi_Cpp_Adapter.hpp @@ -3,7 +3,7 @@ #include #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include "bmi.hpp" #include "AbstractCLibBmiAdapter.hpp" diff --git a/include/bmi/Bmi_Fortran_Adapter.hpp b/include/bmi/Bmi_Fortran_Adapter.hpp index f3818c96e8..c168819e9d 100644 --- a/include/bmi/Bmi_Fortran_Adapter.hpp +++ b/include/bmi/Bmi_Fortran_Adapter.hpp @@ -2,7 +2,7 @@ #define NGEN_BMI_FORTRAN_ADAPTER_HPP #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #if NGEN_WITH_BMI_FORTRAN diff --git a/include/bmi/Bmi_Py_Adapter.hpp b/include/bmi/Bmi_Py_Adapter.hpp index 2e48dc7845..3e47fc2d16 100644 --- a/include/bmi/Bmi_Py_Adapter.hpp +++ b/include/bmi/Bmi_Py_Adapter.hpp @@ -2,7 +2,7 @@ #define NGEN_BMI_PY_ADAPTER_H #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #if NGEN_WITH_PYTHON diff --git a/include/core/Layer.hpp b/include/core/Layer.hpp index 9a21401fb2..643f1b6970 100644 --- a/include/core/Layer.hpp +++ b/include/core/Layer.hpp @@ -2,7 +2,7 @@ #define __NGEN_LAYER__ #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include "LayerData.hpp" #include "Simulation_Time.hpp" diff --git a/include/core/Partition_Parser.hpp b/include/core/Partition_Parser.hpp index d479aec04c..c05c88021e 100644 --- a/include/core/Partition_Parser.hpp +++ b/include/core/Partition_Parser.hpp @@ -17,7 +17,7 @@ #include #include "JSONProperty.hpp" #include "Partition_Data.hpp" -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" class Partitions_Parser { diff --git a/include/core/mediator/UnitsHelper.hpp b/include/core/mediator/UnitsHelper.hpp index 7d61f90d30..6887389ee5 100644 --- a/include/core/mediator/UnitsHelper.hpp +++ b/include/core/mediator/UnitsHelper.hpp @@ -1,6 +1,6 @@ #ifndef NGEN_UNITSHELPER_H #define NGEN_UNITSHELPER_H -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" // FIXME: Workaround to handle UDUNITS2 includes with differing paths. // Not exactly sure why CMake can't handle this, but even with diff --git a/include/core/nexus/HY_PointHydroNexusRemote.hpp b/include/core/nexus/HY_PointHydroNexusRemote.hpp index ebac2e9ace..aa3857d67b 100644 --- a/include/core/nexus/HY_PointHydroNexusRemote.hpp +++ b/include/core/nexus/HY_PointHydroNexusRemote.hpp @@ -1,6 +1,6 @@ #ifndef HY_POINTHDRONEXUSREMOTE_H #define HY_POINTHDRONEXUSREMOTE_H -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #if NGEN_WITH_MPI diff --git a/include/forcing/CsvPerFeatureForcingProvider.hpp b/include/forcing/CsvPerFeatureForcingProvider.hpp index 1570c67b2e..a8b4bbd22c 100644 --- a/include/forcing/CsvPerFeatureForcingProvider.hpp +++ b/include/forcing/CsvPerFeatureForcingProvider.hpp @@ -1,6 +1,6 @@ #ifndef NGEN_CSVPERFEATUREFORCING_H #define NGEN_CSVPERFEATUREFORCING_H -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #include diff --git a/include/forcing/ForcingsEngineDataProvider.hpp b/include/forcing/ForcingsEngineDataProvider.hpp index 37df90d59b..5987c9bf48 100644 --- a/include/forcing/ForcingsEngineDataProvider.hpp +++ b/include/forcing/ForcingsEngineDataProvider.hpp @@ -16,7 +16,7 @@ #include "DataProvider.hpp" #include "bmi/Bmi_Py_Adapter.hpp" -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" namespace data_access { diff --git a/include/forcing/OptionalWrappedDataProvider.hpp b/include/forcing/OptionalWrappedDataProvider.hpp index 3a2c2c7c94..06dab46c61 100644 --- a/include/forcing/OptionalWrappedDataProvider.hpp +++ b/include/forcing/OptionalWrappedDataProvider.hpp @@ -1,6 +1,6 @@ #ifndef NGEN_OPTIONALWRAPPEDPROVIDER_HPP #define NGEN_OPTIONALWRAPPEDPROVIDER_HPP -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include diff --git a/include/geojson/JSONProperty.hpp b/include/geojson/JSONProperty.hpp index edcdb3a781..61e2836fd6 100644 --- a/include/geojson/JSONProperty.hpp +++ b/include/geojson/JSONProperty.hpp @@ -9,7 +9,7 @@ #include #include #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" namespace geojson { class JSONProperty; diff --git a/include/geojson/features/FeatureBase.hpp b/include/geojson/features/FeatureBase.hpp index a11669808f..2c89aa2474 100644 --- a/include/geojson/features/FeatureBase.hpp +++ b/include/geojson/features/FeatureBase.hpp @@ -1,6 +1,6 @@ #ifndef GEOJSON_FEATURE_H #define GEOJSON_FEATURE_H -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include "JSONGeometry.hpp" #include "JSONProperty.hpp" diff --git a/include/realizations/catchment/Bmi_Multi_Formulation.hpp b/include/realizations/catchment/Bmi_Multi_Formulation.hpp index 20d9158b94..1eb2487917 100644 --- a/include/realizations/catchment/Bmi_Multi_Formulation.hpp +++ b/include/realizations/catchment/Bmi_Multi_Formulation.hpp @@ -1,6 +1,6 @@ #ifndef NGEN_BMI_MULTI_FORMULATION_HPP #define NGEN_BMI_MULTI_FORMULATION_HPP -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #include diff --git a/include/realizations/catchment/Catchment_Formulation.hpp b/include/realizations/catchment/Catchment_Formulation.hpp index 5a777c6857..31522e953c 100644 --- a/include/realizations/catchment/Catchment_Formulation.hpp +++ b/include/realizations/catchment/Catchment_Formulation.hpp @@ -7,7 +7,7 @@ #include #include "GenericDataProvider.hpp" -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #define DEFAULT_FORMULATION_OUTPUT_DELIMITER "," diff --git a/include/realizations/catchment/Formulation.hpp b/include/realizations/catchment/Formulation.hpp index ad8c6f097c..5b63d509ff 100644 --- a/include/realizations/catchment/Formulation.hpp +++ b/include/realizations/catchment/Formulation.hpp @@ -1,6 +1,6 @@ #ifndef FORMULATION_H #define FORMULATION_H -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #include diff --git a/include/realizations/catchment/Formulation_Constructors.hpp b/include/realizations/catchment/Formulation_Constructors.hpp index a1afd75f46..c24b686111 100644 --- a/include/realizations/catchment/Formulation_Constructors.hpp +++ b/include/realizations/catchment/Formulation_Constructors.hpp @@ -1,6 +1,6 @@ #ifndef NGEN_FORMULATION_CONSTRUCTORS_H #define NGEN_FORMULATION_CONSTRUCTORS_H -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include diff --git a/include/realizations/catchment/Formulation_Manager.hpp b/include/realizations/catchment/Formulation_Manager.hpp index 4006409231..89383e792d 100644 --- a/include/realizations/catchment/Formulation_Manager.hpp +++ b/include/realizations/catchment/Formulation_Manager.hpp @@ -2,7 +2,7 @@ #define NGEN_FORMULATION_MANAGER_H #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #include diff --git a/include/realizations/config/layer.hpp b/include/realizations/config/layer.hpp index fffef3c5d9..c947079306 100644 --- a/include/realizations/config/layer.hpp +++ b/include/realizations/config/layer.hpp @@ -1,6 +1,6 @@ #ifndef NGEN_REALIZATION_CONFIG_LAYER_H #define NGEN_REALIZATION_CONFIG_LAYER_H -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include diff --git a/include/realizations/config/time.hpp b/include/realizations/config/time.hpp index 0825eef698..b00ed4e09f 100644 --- a/include/realizations/config/time.hpp +++ b/include/realizations/config/time.hpp @@ -1,6 +1,6 @@ #ifndef NGEN_REALIZATION_CONFIG_TIME_H #define NGEN_REALIZATION_CONFIG_TIME_H -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #include diff --git a/include/simulation_time/Simulation_Time.hpp b/include/simulation_time/Simulation_Time.hpp index c017e2c267..d2ea4969d4 100644 --- a/include/simulation_time/Simulation_Time.hpp +++ b/include/simulation_time/Simulation_Time.hpp @@ -1,7 +1,7 @@ #ifndef SIMULATION_TIME_H #define SIMULATION_TIME_H -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #include diff --git a/include/utilities/CSV_Reader.h b/include/utilities/CSV_Reader.h index 21adf3d745..bcda340385 100644 --- a/include/utilities/CSV_Reader.h +++ b/include/utilities/CSV_Reader.h @@ -1,6 +1,6 @@ #ifndef CSV_Reader_H #define CSV_Reader_H -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #include diff --git a/include/utilities/FileChecker.h b/include/utilities/FileChecker.h index b9304587a0..92b2ee1045 100644 --- a/include/utilities/FileChecker.h +++ b/include/utilities/FileChecker.h @@ -6,7 +6,8 @@ #include #include #include -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" namespace utils { @@ -122,7 +123,7 @@ namespace utils { else { std::stringstream ss; ss << description << " path " << path << " not readable" << std::endl; - LOG(ss.str(), LogLevel::INFO); ss.str(""); + LOG(LogLevel::INFO, ss.str()); ss.str(""); return false; } } diff --git a/include/utilities/Logger.hpp b/include/utilities/Logger.hpp deleted file mode 100644 index 50f6c91220..0000000000 --- a/include/utilities/Logger.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef LOGGER_HPP -#define LOGGER_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -enum class LogLevel { - NONE = 0, - DEBUG = 1, - INFO = 2, - WARNING = 3, - SEVERE = 4, - FATAL = 5, -}; - -/** -* Logger Class Used to Output Details of Current Application Flow -*/ -class Logger { - public: - Logger(); - ~Logger() = default; - - // Methods - static void Log(std::string message, LogLevel messageLevel=LogLevel::INFO); - static void Log(LogLevel messageLevel, const char* message, ...); - static void Log(LogLevel messageLevel, std::string message); - bool IsLoggingEnabled(void); - LogLevel GetLogLevel(void); - void SetLogPreferences(LogLevel level=LogLevel::INFO); - - static __always_inline void logMsgAndThrowError(const std::string& message) { - Log(message, LogLevel::SEVERE); - throw std::runtime_error(message); - }; - - static Logger* GetLogger(); - - private: - // Methods - static std::string ConvertLogLevelToString(LogLevel level); - static LogLevel ConvertStringToLogLevel(const std::string& logLevel); - std::string CreateDateString(void); - bool CreateDirectory(const std::string& path); - static std::string CreateTimestamp(bool appendMS=true, bool iso=true); - bool DirectoryExists(const std::string& path); - std::string ExtractFirstNDirs(const std::string& path, int numDirs); - bool FileExists(const std::string& path); - bool FindAndOpenLogConfigFile(std::string path, std::ifstream& configFileStream); - std::string GetLogFilePath(void); - std::string GetParentDirName(const std::string& path); - bool IsValidEnvVarName(const std::string& name); - bool LogFileReady(void); - bool ParseLoggerConfigFile(std::ifstream& jsonFile); - void ReadConfigFile(std::string searchPath); - void SetupLogFile(void); - void ManageLoggingEnvVars(bool set=true); - static std::string ToUpper(const std::string& str); - static std::string TrimString(const std::string& str); - - // Variables - bool loggerInitialized = false; - bool loggingEnabled = true; - std::fstream logFile; - std::string logFileDir = ""; - std::string logFilePath = ""; - LogLevel logLevel = LogLevel::INFO; - std::string moduleName = ""; - std::string ngenResultsDir = ""; - bool openedOnce = false; - - std::unordered_map moduleLogLevels; -}; - -// Placed here to ensure the class is declared before setting this preprocessor symbol -#define LOG Logger::Log - -#endif diff --git a/include/utilities/bmi_utilities.hpp b/include/utilities/bmi_utilities.hpp index eea56f6de2..b33f4390bf 100644 --- a/include/utilities/bmi_utilities.hpp +++ b/include/utilities/bmi_utilities.hpp @@ -1,6 +1,6 @@ #ifndef NGEN_BMI_UTILITIES_HPP #define NGEN_BMI_UTILITIES_HPP -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #include diff --git a/include/utilities/mdarray/mdarray.hpp b/include/utilities/mdarray/mdarray.hpp index e5a6fa867e..7a229e0e93 100644 --- a/include/utilities/mdarray/mdarray.hpp +++ b/include/utilities/mdarray/mdarray.hpp @@ -5,7 +5,7 @@ #include #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" namespace ngen { diff --git a/include/utilities/mdframe/mdframe.hpp b/include/utilities/mdframe/mdframe.hpp index 4fa97dd5f3..79f66c332b 100644 --- a/include/utilities/mdframe/mdframe.hpp +++ b/include/utilities/mdframe/mdframe.hpp @@ -1,6 +1,6 @@ #ifndef NGEN_MDFRAME_DEFINITION_HPP #define NGEN_MDFRAME_DEFINITION_HPP -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #include diff --git a/include/utilities/python/HydrofabricSubsetter.hpp b/include/utilities/python/HydrofabricSubsetter.hpp index eaf71e42e4..0c0bfc1f39 100644 --- a/include/utilities/python/HydrofabricSubsetter.hpp +++ b/include/utilities/python/HydrofabricSubsetter.hpp @@ -1,6 +1,6 @@ #ifndef NGEN_HYDROFABRICSUBSETTER_HPP #define NGEN_HYDROFABRICSUBSETTER_HPP -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include diff --git a/src/NGen.cpp b/src/NGen.cpp index 79946fd64f..b4b8d14978 100644 --- a/src/NGen.cpp +++ b/src/NGen.cpp @@ -17,7 +17,7 @@ #include "NGenConfig.h" -#include +#include "ewts_ngen/logger.hpp" #include #include @@ -455,7 +455,8 @@ int run_ngen(int argc, char* argv[], int mpi_num_procs, int mpi_rank) { throw std::runtime_error(msg); } #else - Logger::logMsgAndThrowError("SQLite3 support required to read GeoPackage files."); + LOG(LogLevel::FATAL, "SQLite3 support required to read GeoPackage files."); + throw std::runtime_error("SQLite3 support required to read GeoPackage files."); #endif } else { nexus_collection = geojson::read(nexusDataFile, nexus_subset_ids); @@ -487,7 +488,8 @@ int run_ngen(int argc, char* argv[], int mpi_num_procs, int mpi_rank) { } #else - Logger::logMsgAndThrowError("SQLite3 support required to read GeoPackage files."); + LOG(LogLevel::FATAL, "SQLite3 support required to read GeoPackage files."); + throw std::runtime_error("SQLite3 support required to read GeoPackage files."); #endif } else { catchment_collection = geojson::read(catchmentDataFile, catchment_subset_ids); diff --git a/src/bmi/AbstractCLibBmiAdapter.cpp b/src/bmi/AbstractCLibBmiAdapter.cpp index dd7565a272..058aa07ab4 100644 --- a/src/bmi/AbstractCLibBmiAdapter.cpp +++ b/src/bmi/AbstractCLibBmiAdapter.cpp @@ -5,7 +5,8 @@ #include "utilities/logging_utils.h" #include -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" namespace models { namespace bmi { @@ -37,7 +38,8 @@ void AbstractCLibBmiAdapter::dynamic_library_load() { if (bmi_registration_function.empty()) { this->init_exception_msg = "Can't init " + this->model_name + "; empty name given for library's registration function."; - Logger::logMsgAndThrowError(this->init_exception_msg); + LOG(LogLevel::FATAL, this->init_exception_msg); + throw std::runtime_error(this->init_exception_msg); } if (dyn_lib_handle != nullptr) { std::string message = "AbstractCLibBmiAdapter::dynamic_library_load: ignoring attempt to reload dynamic shared library '" + bmi_lib_file + "' for " + this->model_name; @@ -54,7 +56,8 @@ void AbstractCLibBmiAdapter::dynamic_library_load() { if (bmi_lib_file.length() == 0) { this->init_exception_msg = "Can't init " + this->model_name + "; library file path is empty"; - Logger::logMsgAndThrowError(this->init_exception_msg); + LOG(LogLevel::FATAL, this->init_exception_msg); + throw std::runtime_error(this->init_exception_msg); } if (bmi_lib_file.substr(idx) == ".so") { alt_bmi_lib_file = bmi_lib_file.substr(0, idx) + ".dylib"; @@ -77,7 +80,8 @@ void AbstractCLibBmiAdapter::dynamic_library_load() { } else { this->init_exception_msg = "Can't init " + this->model_name + "; unreadable shared library file '" + bmi_lib_file + "'"; - Logger::logMsgAndThrowError(this->init_exception_msg); + LOG(LogLevel::FATAL, this->init_exception_msg); + throw std::runtime_error(this->init_exception_msg); } } @@ -102,10 +106,10 @@ void* AbstractCLibBmiAdapter::dynamic_load_symbol( bool is_null_valid ) { if (dyn_lib_handle == nullptr) { - Logger::logMsgAndThrowError( - "Cannot load symbol '" + symbol_name + - "' without handle to shared library (bmi_lib_file = '" + bmi_lib_file + "')" - ); + this->init_exception_msg = "Cannot load symbol '" + symbol_name + + "' without handle to shared library (bmi_lib_file = '" + bmi_lib_file + "')"; + LOG(LogLevel::FATAL, this->init_exception_msg); + throw std::runtime_error(this->init_exception_msg); } // Call first to ensure any previous error is cleared before trying to load the symbol dlerror(); diff --git a/src/bmi/Bmi_Adapter.cpp b/src/bmi/Bmi_Adapter.cpp index 516efb1da2..a527efd091 100644 --- a/src/bmi/Bmi_Adapter.cpp +++ b/src/bmi/Bmi_Adapter.cpp @@ -1,8 +1,10 @@ +#include #include "bmi/Bmi_Adapter.hpp" #include "bmi/State_Exception.hpp" #include "utilities/FileChecker.h" #include "utilities/logging_utils.h" -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" using namespace std; std::stringstream str_stream; @@ -27,7 +29,8 @@ Bmi_Adapter::Bmi_Adapter( init_exception_msg = "Cannot create and initialize " + this->model_name + " using unreadable file '" + this->bmi_init_config + "'. Error: " + std::strerror(errno); - Logger::logMsgAndThrowError(init_exception_msg); + LOG(LogLevel::FATAL, init_exception_msg); + throw std::runtime_error(init_exception_msg); } str_stream << "Bmi_Adapter: Model name: " << this->model_name << std::endl; LOG(str_stream.str(), LogLevel::INFO); str_stream.str(""); @@ -43,7 +46,7 @@ double Bmi_Adapter::get_time_convert_factor() { input_units = GetTimeUnits(); } catch(std::exception &e){ - //Re-throwing any exception as a runtime_error so we don't lose + //Re-throwing any exception as a std::runtime_error so we don't lose //the error context/message. We will lose the original exception type, though //When a python exception is raised from the py adapter subclass, the //pybind exception is lost and all we see is a generic "uncaught exception" @@ -70,9 +73,8 @@ void Bmi_Adapter::Initialize() { // previous message errno = 0; if (model_initialized && !init_exception_msg.empty()) { - Logger::logMsgAndThrowError( - "Previous " + model_name + " init attempt had exception: \n\t" + init_exception_msg - ); + LOG(LogLevel::FATAL, init_exception_msg); + throw std::runtime_error(init_exception_msg); } // If there was previous init attempt w/ (implicitly) no exception on previous attempt, just // return @@ -82,7 +84,8 @@ void Bmi_Adapter::Initialize() { init_exception_msg = "Cannot initialize " + model_name + " using unreadable file '" + bmi_init_config + "'. Error: " + std::strerror(errno); ; - Logger::logMsgAndThrowError(init_exception_msg); + LOG(LogLevel::FATAL, init_exception_msg); + throw std::runtime_error(init_exception_msg); } else { try { // TODO: make this same name as used with other testing (adjust name in docstring above @@ -104,10 +107,10 @@ void Bmi_Adapter::Initialize() { void Bmi_Adapter::Initialize(std::string config_file) { if (config_file != bmi_init_config && model_initialized) { - Logger::logMsgAndThrowError( - "Model init previously attempted; cannot change config from " + bmi_init_config + - " to " + config_file - ); + LOG(LogLevel::FATAL, "Model init previously attempted; cannot change config from " + bmi_init_config + + " to " + config_file); + throw std::runtime_error("Model init previously attempted; cannot change config from " + bmi_init_config + + " to " + config_file); } str_stream << __FILE__ << ":" << __LINE__ << " Bmi_Adapter::Initialize: config_file = " << config_file << std::endl; LOG(str_stream.str(), LogLevel::INFO); str_stream.str(""); @@ -123,7 +126,8 @@ void Bmi_Adapter::Initialize(std::string config_file) { } catch (models::external::State_Exception& e) { throw e; } catch (std::exception& e) { - Logger::logMsgAndThrowError(e.what()); + LOG(LogLevel::FATAL, e.what()); + throw std::runtime_error(e.what()); } } diff --git a/src/bmi/Bmi_C_Adapter.cpp b/src/bmi/Bmi_C_Adapter.cpp index e85678946f..02ce6bcb71 100644 --- a/src/bmi/Bmi_C_Adapter.cpp +++ b/src/bmi/Bmi_C_Adapter.cpp @@ -2,7 +2,8 @@ #include #include -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" using namespace models::bmi; @@ -120,7 +121,8 @@ Bmi_C_Adapter::Bmi_C_Adapter(Bmi_C_Adapter &adapter) : model_name(adapter.model_ std::string Bmi_C_Adapter::GetComponentName() { char component_name[BMI_MAX_COMPONENT_NAME]; if (bmi_model->get_component_name(bmi_model.get(), component_name) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get model component name."); + LOG(LogLevel::FATAL, "failed to get model component name."); + std::runtime_error("failed to get model component name."); } return {component_name}; } @@ -129,7 +131,8 @@ double Bmi_C_Adapter::GetCurrentTime() { double current_time; int result = bmi_model->get_current_time(bmi_model.get(), ¤t_time); if (result != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get current model time."); + LOG(LogLevel::FATAL, "failed to get current model time."); + std::runtime_error("failed to get current model time."); } return current_time; } @@ -138,7 +141,8 @@ double Bmi_C_Adapter::GetEndTime() { double end_time; int result = bmi_model->get_end_time(bmi_model.get(), &end_time); if (result != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get model end time."); + LOG(LogLevel::FATAL, " failed to get model end time."); + std::runtime_error("failed to get model end time."); } return end_time; } @@ -171,7 +175,8 @@ double Bmi_C_Adapter::GetStartTime() { double start_time; int result = bmi_model->get_start_time(bmi_model.get(), &start_time); if (result != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get model start time."); + LOG(LogLevel::FATAL, "failed to get model start time."); + std::runtime_error("failed to get model start time."); } return start_time; } @@ -180,7 +185,8 @@ std::string Bmi_C_Adapter::GetTimeUnits() { char time_units_cstr[BMI_MAX_UNITS_NAME]; int result = bmi_model->get_time_units(bmi_model.get(), time_units_cstr); if (result != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to read time units from model."); + LOG(LogLevel::FATAL, "failed to read time units from model."); + std::runtime_error("failed to read time units from model."); } return std::string(time_units_cstr); } @@ -195,7 +201,8 @@ int Bmi_C_Adapter::GetVarItemsize(std::string name) { int size; int success = bmi_model->get_var_itemsize(bmi_model.get(), name.c_str(), &size); if (success != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable item size for " + name + "."); + LOG(LogLevel::FATAL, "failed to get variable item size for " + name + "."); + std::runtime_error("failed to get variable item size for " + name + "."); } return size; } @@ -204,7 +211,8 @@ int Bmi_C_Adapter::GetVarNbytes(std::string name) { int size; int success = bmi_model->get_var_nbytes(bmi_model.get(), name.c_str(), &size); if (success != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable array size (i.e., nbytes) for " + name + "."); + LOG(LogLevel::FATAL, "failed to get variable array size (i.e., nbytes) for " + name + "."); + std::runtime_error("failed to get variable array size (i.e., nbytes) for " + name + "."); } return size; } @@ -213,7 +221,8 @@ std::string Bmi_C_Adapter::GetVarType(std::string name) { char type_c_str[BMI_MAX_TYPE_NAME]; int success = bmi_model->get_var_type(bmi_model.get(), name.c_str(), type_c_str); if (success != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable type for " + name + "."); + LOG(LogLevel::FATAL, "failed to get variable type for " + name + "."); + std::runtime_error("failed to get variable type for " + name + "."); } return std::string(type_c_str); } @@ -222,7 +231,8 @@ std::string Bmi_C_Adapter::GetVarUnits(std::string name) { char units_c_str[BMI_MAX_UNITS_NAME]; int success = bmi_model->get_var_units(bmi_model.get(), name.c_str(), units_c_str); if (success != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable units for " + name + "."); + LOG(LogLevel::FATAL, "failed to get variable units for " + name + "."); + std::runtime_error("failed to get variable units for " + name + "."); } return std::string(units_c_str); } @@ -231,7 +241,7 @@ std::string Bmi_C_Adapter::GetVarLocation(std::string name) { char location_c_str[BMI_MAX_LOCATION_NAME]; int success = bmi_model->get_var_location(bmi_model.get(), name.c_str(), location_c_str); if (success != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable location for " + name + "."); + std::runtime_error("failed to get variable location for " + name + "."); } return std::string(location_c_str); } @@ -240,7 +250,8 @@ int Bmi_C_Adapter::GetVarGrid(std::string name) { int grid; int success = bmi_model->get_var_grid(bmi_model.get(), name.c_str(), &grid); if (success != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable grid for " + name + "."); + LOG(LogLevel::FATAL, "failed to get variable grid for " + name + "."); + std::runtime_error("failed to get variable grid for " + name + "."); } return grid; } @@ -249,7 +260,8 @@ std::string Bmi_C_Adapter::GetGridType(int grid_id) { char gridtype_c_str[BMI_MAX_TYPE_NAME]; int success = bmi_model->get_grid_type(bmi_model.get(), grid_id, gridtype_c_str); if (success != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid type for grid ID " + std::to_string(grid_id) + "."); + LOG(LogLevel::FATAL, "failed to get grid type for grid ID " + std::to_string(grid_id) + "."); + std::runtime_error("failed to get grid type for grid ID " + std::to_string(grid_id) + "."); } return std::string(gridtype_c_str); } @@ -258,7 +270,8 @@ int Bmi_C_Adapter::GetGridRank(int grid_id) { int gridrank; int success = bmi_model->get_grid_rank(bmi_model.get(), grid_id, &gridrank); if (success != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid rank for grid ID " + std::to_string(grid_id) + "."); + LOG(LogLevel::FATAL, "failed to get grid rank for grid ID " + std::to_string(grid_id) + "."); + std::runtime_error("failed to get grid rank for grid ID " + std::to_string(grid_id) + "."); } return gridrank; } @@ -267,7 +280,8 @@ int Bmi_C_Adapter::GetGridSize(int grid_id) { int gridsize; int success = bmi_model->get_grid_size(bmi_model.get(), grid_id, &gridsize); if (success != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid size for grid ID " + std::to_string(grid_id) + "."); + LOG(LogLevel::FATAL, "failed to get grid size for grid ID " + std::to_string(grid_id) + "."); + std::runtime_error("failed to get grid size for grid ID " + std::to_string(grid_id) + "."); } return gridsize; } @@ -282,7 +296,8 @@ std::shared_ptr> Bmi_C_Adapter::inner_get_variable_name variableCount = (is_input_variables) ? inner_get_input_item_count() : inner_get_output_item_count(); } catch (const std::exception &e) { - Logger::logMsgAndThrowError(model_name + " failed to count of " + varType + " variable names array."); + LOG(LogLevel::FATAL, "failed to count of " + varType + " variable names array."); + std::runtime_error("failed to count of " + varType + " variable names array."); } // With variable count now obtained, create the vector @@ -305,7 +320,8 @@ std::shared_ptr> Bmi_C_Adapter::inner_get_variable_name names_result = bmi_model->get_output_var_names(bmi_model.get(), names_array.data()); } if (names_result != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get array of output variables names."); + LOG(LogLevel::FATAL, "failed to get array of output variables names."); + std::runtime_error("failed to get array of output variables names."); } // Then convert from array of C strings to vector of strings, freeing the allocated space as we go @@ -369,44 +385,51 @@ void Bmi_C_Adapter::UpdateUntil(double time) { void Bmi_C_Adapter::GetGridShape(const int grid, int *shape) { if (bmi_model->get_grid_shape(bmi_model.get(), grid, shape) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " shape."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " shape."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " shape."); } } void Bmi_C_Adapter::GetGridSpacing(const int grid, double *spacing) { if (bmi_model->get_grid_spacing(bmi_model.get(), grid, spacing) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " spacing."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " spacing."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " spacing."); } } void Bmi_C_Adapter::GetGridOrigin(const int grid, double *origin) { if (bmi_model->get_grid_origin(bmi_model.get(), grid, origin) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " origin."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " origin."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " origin."); } } void Bmi_C_Adapter::GetGridX(const int grid, double *x) { if (bmi_model->get_grid_x(bmi_model.get(), grid, x) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " x."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " x."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " x."); } } void Bmi_C_Adapter::GetGridY(const int grid, double *y) { if (bmi_model->get_grid_y(bmi_model.get(), grid, y) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " y."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " y."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " y."); } } void Bmi_C_Adapter::GetGridZ(const int grid, double *z) { if (bmi_model->get_grid_z(bmi_model.get(), grid, z) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " z."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " z."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " z."); } } int Bmi_C_Adapter::GetGridNodeCount(const int grid) { int count; if (bmi_model->get_grid_node_count(bmi_model.get(), grid, &count) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " node count."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " node count."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " node count."); } return count; } @@ -414,7 +437,8 @@ int Bmi_C_Adapter::GetGridNodeCount(const int grid) { int Bmi_C_Adapter::GetGridEdgeCount(const int grid) { int count; if (bmi_model->get_grid_edge_count(bmi_model.get(), grid, &count) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " edge count."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " edge count."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " edge count."); } return count; } @@ -422,31 +446,36 @@ int Bmi_C_Adapter::GetGridEdgeCount(const int grid) { int Bmi_C_Adapter::GetGridFaceCount(const int grid) { int count; if (bmi_model->get_grid_face_count(bmi_model.get(), grid, &count) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " face count."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " face count."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " face count."); } return count; } void Bmi_C_Adapter::GetGridEdgeNodes(const int grid, int *edge_nodes) { if (bmi_model->get_grid_edge_nodes(bmi_model.get(), grid, edge_nodes) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " edge nodes."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " edge nodes."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " edge nodes."); } } void Bmi_C_Adapter::GetGridFaceEdges(const int grid, int *face_edges) { if (bmi_model->get_grid_face_edges(bmi_model.get(), grid, face_edges) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " face edges."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " face edges."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " face edges."); } } void Bmi_C_Adapter::GetGridFaceNodes(const int grid, int *face_nodes) { if (bmi_model->get_grid_face_nodes(bmi_model.get(), grid, face_nodes) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " face nodes."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " face nodes."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " face nodes."); } } void Bmi_C_Adapter::GetGridNodesPerFace(const int grid, int *nodes_per_face) { if (bmi_model->get_grid_nodes_per_face(bmi_model.get(), grid, nodes_per_face) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " nodes per face."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " nodes per face."); + std::runtime_error("failed to get grid " + std::to_string(grid) + " nodes per face."); } } diff --git a/src/bmi/Bmi_Fortran_Adapter.cpp b/src/bmi/Bmi_Fortran_Adapter.cpp index de507b4721..4b0c61ff24 100644 --- a/src/bmi/Bmi_Fortran_Adapter.cpp +++ b/src/bmi/Bmi_Fortran_Adapter.cpp @@ -1,5 +1,5 @@ #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #if NGEN_WITH_BMI_FORTRAN #include "bmi/Bmi_Fortran_Adapter.hpp" @@ -9,7 +9,8 @@ using namespace models::bmi; std::string Bmi_Fortran_Adapter::GetComponentName() { char component_name[BMI_MAX_COMPONENT_NAME]; if (get_component_name(&bmi_model->handle, component_name) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get model component name."); + LOG(LogLevel::FATAL, "failed to get model component name."); + throw std::runtime_error("failed to get model component name."); } return {component_name}; } @@ -17,7 +18,8 @@ std::string Bmi_Fortran_Adapter::GetComponentName() { double Bmi_Fortran_Adapter::GetCurrentTime() { double current_time; if (get_current_time(&bmi_model->handle, ¤t_time) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get current model time."); + LOG(LogLevel::FATAL, "failed to get current model time."); + throw std::runtime_error("failed to get current model time."); } return current_time; } @@ -25,7 +27,8 @@ double Bmi_Fortran_Adapter::GetCurrentTime() { double Bmi_Fortran_Adapter::GetEndTime() { double end_time; if (get_end_time(&bmi_model->handle, &end_time) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get model end time."); + LOG(LogLevel::FATAL, "failed to get model end time."); + throw std::runtime_error("failed to get model end time."); } return end_time; } @@ -47,7 +50,8 @@ std::vector Bmi_Fortran_Adapter::GetOutputVarNames() { double Bmi_Fortran_Adapter::GetStartTime() { double start_time; if (get_start_time(&bmi_model->handle, &start_time) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get model start time."); + LOG(LogLevel::FATAL, "failed to get model start time."); + throw std::runtime_error("failed to get model start time."); } return start_time; } @@ -57,7 +61,8 @@ double Bmi_Fortran_Adapter::GetTimeStep() { //return *get_bmi_model_time_step_size_ptr(); double ts; if (get_time_step(&bmi_model->handle, &ts) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get model time step size."); + LOG(LogLevel::FATAL, "failed to get model time step size."); + throw std::runtime_error("failed to get model time step size."); } return ts; } @@ -65,7 +70,8 @@ double Bmi_Fortran_Adapter::GetTimeStep() { std::string Bmi_Fortran_Adapter::GetTimeUnits() { char time_units_cstr[BMI_MAX_UNITS_NAME]; if (get_time_units(&bmi_model->handle, time_units_cstr) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to read time units from model."); + LOG(LogLevel::FATAL, "failed to read time units from model."); + throw std::runtime_error("failed to read time units from model."); } return {time_units_cstr}; } @@ -79,13 +85,15 @@ void Bmi_Fortran_Adapter::GetValueAtIndices(std::string name, void *dest, int *i " indices."); } */ - Logger::logMsgAndThrowError("Fortran BMI module integration does not currently support getting values by index"); + LOG(LogLevel::FATAL, "Fortran BMI module integration does not currently support getting values by index"); + throw std::runtime_error("Fortran BMI module integration does not currently support getting values by index"); } int Bmi_Fortran_Adapter::GetVarItemsize(std::string name) { int size; if (get_var_itemsize(&bmi_model->handle, name.c_str(), &size) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable item size for " + name + "."); + LOG(LogLevel::FATAL, "failed to get variable item size for " + name + "."); + throw std::runtime_error("failed to get variable item size for " + name + "."); } return size; } @@ -93,7 +101,8 @@ int Bmi_Fortran_Adapter::GetVarItemsize(std::string name) { int Bmi_Fortran_Adapter::GetVarNbytes(std::string name) { int size; if (get_var_nbytes(&bmi_model->handle, name.c_str(), &size) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable array size (i.e., nbytes) for " + name + "."); + LOG(LogLevel::FATAL, "failed to get variable array size (i.e., nbytes) for " + name + "."); + throw std::runtime_error("failed to get variable array size (i.e., nbytes) for " + name + "."); } return size; } @@ -105,7 +114,8 @@ std::string Bmi_Fortran_Adapter::GetVarType(std::string name) { std::string Bmi_Fortran_Adapter::GetVarUnits(std::string name) { char units_c_str[BMI_MAX_UNITS_NAME]; if (get_var_units(&bmi_model->handle, name.c_str(), units_c_str) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable units for " + name + "."); + LOG(LogLevel::FATAL, "failed to get variable units for " + name + "."); + throw std::runtime_error("failed to get variable units for " + name + "."); } return std::string(units_c_str); } @@ -113,7 +123,8 @@ std::string Bmi_Fortran_Adapter::GetVarUnits(std::string name) { std::string Bmi_Fortran_Adapter::GetVarLocation(std::string name) { char location_c_str[BMI_MAX_LOCATION_NAME]; if (get_var_location(&bmi_model->handle, name.c_str(), location_c_str) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable location for " + name + "."); + LOG(LogLevel::FATAL, "failed to get variable location for " + name + "."); + throw std::runtime_error("failed to get variable location for " + name + "."); } return std::string(location_c_str); } @@ -121,7 +132,8 @@ std::string Bmi_Fortran_Adapter::GetVarLocation(std::string name) { int Bmi_Fortran_Adapter::GetVarGrid(std::string name) { int grid; if (get_var_grid(&bmi_model->handle, name.c_str(), &grid) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get variable grid for " + name + "."); + LOG(LogLevel::FATAL, "failed to get variable grid for " + name + "."); + throw std::runtime_error("failed to get variable grid for " + name + "."); } return grid; } @@ -129,7 +141,8 @@ int Bmi_Fortran_Adapter::GetVarGrid(std::string name) { std::string Bmi_Fortran_Adapter::GetGridType(int grid_id) { char gridtype_c_str[BMI_MAX_TYPE_NAME]; if (get_grid_type(&bmi_model->handle, &grid_id, gridtype_c_str) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid type for grid ID " + std::to_string(grid_id) + "."); + LOG(LogLevel::FATAL, "failed to get grid type for grid ID " + std::to_string(grid_id) + "."); + throw std::runtime_error("failed to get grid type for grid ID " + std::to_string(grid_id) + "."); } return std::string(gridtype_c_str); } @@ -137,7 +150,8 @@ std::string Bmi_Fortran_Adapter::GetGridType(int grid_id) { int Bmi_Fortran_Adapter::GetGridRank(int grid_id) { int gridrank; if (get_grid_rank(&bmi_model->handle, &grid_id, &gridrank) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid rank for grid ID " + std::to_string(grid_id) + "."); + LOG(LogLevel::FATAL, "failed to get grid rank for grid ID " + std::to_string(grid_id) + "."); + throw std::runtime_error("failed to get grid rank for grid ID " + std::to_string(grid_id) + "."); } return gridrank; } @@ -145,7 +159,8 @@ int Bmi_Fortran_Adapter::GetGridRank(int grid_id) { int Bmi_Fortran_Adapter::GetGridSize(int grid_id) { int gridsize; if (get_grid_size(&bmi_model->handle, &grid_id, &gridsize) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid size for grid ID " + std::to_string(grid_id) + "."); + LOG(LogLevel::FATAL, "failed to get grid size for grid ID " + std::to_string(grid_id) + "."); + throw std::runtime_error("failed to get grid size for grid ID " + std::to_string(grid_id) + "."); } return gridsize; } @@ -167,7 +182,8 @@ void Bmi_Fortran_Adapter::SetValueAtIndices(std::string name, int *inds, int cou "Failed to set specified indexes for " + name + " variable of " + model_name); } */ - Logger::logMsgAndThrowError("Fortran BMI module integration does not currently support setting values by index"); + LOG(LogLevel::FATAL, "Fortran BMI module integration does not currently support setting values by index"); + throw std::runtime_error("Fortran BMI module integration does not currently support setting values by index"); } /** @@ -201,44 +217,51 @@ void Bmi_Fortran_Adapter::UpdateUntil(double time) { void Bmi_Fortran_Adapter::GetGridShape(int grid, int *shape) { if (get_grid_shape(&bmi_model->handle, &grid, shape) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " shape."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " shape."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " shape."); } } void Bmi_Fortran_Adapter::GetGridSpacing(int grid, double *spacing) { if (get_grid_spacing(&bmi_model->handle, &grid, spacing) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " spacing."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " spacing."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " spacing."); } } void Bmi_Fortran_Adapter::GetGridOrigin(int grid, double *origin) { if (get_grid_origin(&bmi_model->handle, &grid, origin) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " origin."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " origin."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " origin."); } } void Bmi_Fortran_Adapter::GetGridX(int grid, double *x) { if (get_grid_x(&bmi_model->handle, &grid, x) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " x."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " x."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " x."); } } void Bmi_Fortran_Adapter::GetGridY(int grid, double *y) { if (get_grid_y(&bmi_model->handle, &grid, y) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " y."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " y."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " y."); } } void Bmi_Fortran_Adapter::GetGridZ(int grid, double *z) { if (get_grid_z(&bmi_model->handle, &grid, z) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " z."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " z."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " z."); } } int Bmi_Fortran_Adapter::GetGridNodeCount(int grid) { int count; if (get_grid_node_count(&bmi_model->handle, &grid, &count) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " node count."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " node count."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " node count."); } return count; } @@ -246,7 +269,8 @@ int Bmi_Fortran_Adapter::GetGridNodeCount(int grid) { int Bmi_Fortran_Adapter::GetGridEdgeCount(int grid) { int count; if (get_grid_edge_count(&bmi_model->handle, &grid, &count) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " edge count."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " edge count."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " edge count."); } return count; } @@ -254,32 +278,37 @@ int Bmi_Fortran_Adapter::GetGridEdgeCount(int grid) { int Bmi_Fortran_Adapter::GetGridFaceCount(int grid) { int count; if (get_grid_face_count(&bmi_model->handle, &grid, &count) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " face count."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " face count."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " face count."); } return count; } void Bmi_Fortran_Adapter::GetGridEdgeNodes(int grid, int *edge_nodes) { if (get_grid_edge_nodes(&bmi_model->handle, &grid, edge_nodes) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " edge nodes."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " edge nodes."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " edge nodes."); } } void Bmi_Fortran_Adapter::GetGridFaceEdges(int grid, int *face_edges) { if (get_grid_face_edges(&bmi_model->handle, &grid, face_edges) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " face edges."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " face edges."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " face edges."); } } void Bmi_Fortran_Adapter::GetGridFaceNodes(int grid, int *face_nodes) { if (get_grid_face_nodes(&bmi_model->handle, &grid, face_nodes) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " face nodes."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " face nodes."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " face nodes."); } } void Bmi_Fortran_Adapter::GetGridNodesPerFace(int grid, int *nodes_per_face) { if (get_grid_nodes_per_face(&bmi_model->handle, &grid, nodes_per_face) != BMI_SUCCESS) { - Logger::logMsgAndThrowError(model_name + " failed to get grid " + std::to_string(grid) + " nodes per face."); + LOG(LogLevel::FATAL, "failed to get grid " + std::to_string(grid) + " nodes per face."); + throw std::runtime_error("failed to get grid " + std::to_string(grid) + " nodes per face."); } } diff --git a/src/bmi/Bmi_Py_Adapter.cpp b/src/bmi/Bmi_Py_Adapter.cpp index 5fdc3eb8ec..d4d667fedc 100644 --- a/src/bmi/Bmi_Py_Adapter.cpp +++ b/src/bmi/Bmi_Py_Adapter.cpp @@ -5,7 +5,7 @@ #include #include #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include "bmi/Bmi_Py_Adapter.hpp" @@ -102,7 +102,8 @@ void Bmi_Py_Adapter::GetValue(std::string name, void *dest) { catch (std::runtime_error &e) { std::string msg = "Encountered error getting C++ type during call to GetValue: \n"; msg += e.what(); - Logger::logMsgAndThrowError(msg); + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } if (cxx_type == "signed char") { this->copy_to_array(name, static_cast(dest)); @@ -131,7 +132,8 @@ void Bmi_Py_Adapter::GetValue(std::string name, void *dest) { } else if (cxx_type == "long double") { this->copy_to_array(name, static_cast(dest)); } else { - Logger::logMsgAndThrowError("Bmi_Py_Adapter can't get value of unsupported type: " + cxx_type); + LOG(LogLevel::FATAL, "Bmi_Py_Adapter can't get value of unsupported type: " + cxx_type); + throw std::runtime_error("Bmi_Py_Adapter can't get value of unsupported type: " + cxx_type); } } @@ -186,13 +188,13 @@ std::string Bmi_Py_Adapter::get_bmi_type_simple_name() const { * type and size of the variable in question via @ref GetVarType and @ref GetVarItemsize to infer the native * type for this variable (i.e., the actual type for the values pointed to by ``src``). It then uses this * as the type param in a nested called to the template-based @ref set_value_at_indices. If such a type - * param cannot be determined, a ``runtime_error`` is thrown. + * param cannot be determined, a ``std::runtime_error`` is thrown. * * @param name The name of the involved BMI variable. * @param inds A C++ integer array of indices to update, corresponding to each value in ``src``. * @param count Number of elements in the ``inds`` and ``src`` arrays. * @param src A C++ array containing the new values to be set in the BMI variable. - * @throws runtime_error Thrown if @ref GetVarType and @ref GetVarItemsize functions return a combination for + * @throws std::runtime_error Thrown if @ref GetVarType and @ref GetVarItemsize functions return a combination for * which there is not support for mapping to a native type in the framework. * @see set_value_at_indices */ @@ -217,10 +219,11 @@ void Bmi_Py_Adapter::SetValueAtIndices(std::string name, int *inds, int count, v else BMI_PY_SET_VALUE_INDEX(double) else BMI_PY_SET_VALUE_INDEX(long double) else { - Logger::logMsgAndThrowError( - "(Bmi_Py_Adapter) Failed attempt to SET values of BMI variable '" + name + "' from '" + + std::string msg = "(Bmi_Py_Adapter) Failed attempt to SET values of BMI variable '" + name + "' from '" + model_name + "' model: model advertises unsupported combination of type (" + val_type + - ") and size (" + std::to_string(val_item_size) + ")."); + ") and size (" + std::to_string(val_item_size) + ")."; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } #undef BMI_PY_SET_VALUE_INDEX } diff --git a/src/bmi/CMakeLists.txt b/src/bmi/CMakeLists.txt index aafcc1b6f4..d98d201d42 100644 --- a/src/bmi/CMakeLists.txt +++ b/src/bmi/CMakeLists.txt @@ -16,6 +16,8 @@ target_link_libraries(ngen_bmi NGen::core_mediator ) +target_link_libraries(ngen_bmi PRIVATE ewts::ewts_ngen_bridge) + target_sources(ngen_bmi PRIVATE "${CMAKE_CURRENT_LIST_DIR}/Bmi_Adapter.cpp" diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1d28f9f628..3d0126e15d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -11,6 +11,10 @@ target_link_libraries(core PRIVATE NGen::parallel ) +target_link_libraries(core PRIVATE + ewts::ewts_ngen_bridge + ) + target_include_directories(core PUBLIC ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include/simulation_time diff --git a/src/core/HY_Features.cpp b/src/core/HY_Features.cpp index b3653ce7e0..3462c6292e 100644 --- a/src/core/HY_Features.cpp +++ b/src/core/HY_Features.cpp @@ -1,7 +1,7 @@ #include #include #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" using namespace hy_features; diff --git a/src/core/HY_Features_MPI.cpp b/src/core/HY_Features_MPI.cpp index 3490e4f1e6..785cd0e3ec 100644 --- a/src/core/HY_Features_MPI.cpp +++ b/src/core/HY_Features_MPI.cpp @@ -1,7 +1,7 @@ #include #include #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #if NGEN_WITH_MPI diff --git a/src/core/NgenSimulation.cpp b/src/core/NgenSimulation.cpp index 05d4a8076a..9addca80d4 100644 --- a/src/core/NgenSimulation.cpp +++ b/src/core/NgenSimulation.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include "ewts_ngen/logger.hpp" #if NGEN_WITH_MPI #include "HY_Features_MPI.hpp" diff --git a/src/core/SurfaceLayer.cpp b/src/core/SurfaceLayer.cpp index 312f37d3ba..fd06c11c79 100644 --- a/src/core/SurfaceLayer.cpp +++ b/src/core/SurfaceLayer.cpp @@ -1,5 +1,5 @@ #include "SurfaceLayer.hpp" -#include +#include "ewts_ngen/logger.hpp" #if NGEN_WITH_MPI #include "HY_Features_MPI.hpp" diff --git a/src/core/mediator/CMakeLists.txt b/src/core/mediator/CMakeLists.txt index 66b0bc28c8..80374fee2a 100644 --- a/src/core/mediator/CMakeLists.txt +++ b/src/core/mediator/CMakeLists.txt @@ -3,6 +3,8 @@ dynamic_sourced_cxx_library(core_mediator "${CMAKE_CURRENT_SOURCE_DIR}") add_library(NGen::core_mediator ALIAS core_mediator) +target_link_libraries(core_mediator PRIVATE ewts::ewts_ngen_bridge) + if(NGEN_WITH_UDUNITS) target_link_libraries(core_mediator PUBLIC libudunits2) endif() diff --git a/src/core/nexus/CMakeLists.txt b/src/core/nexus/CMakeLists.txt index b35a8c1f08..759a5c3c23 100644 --- a/src/core/nexus/CMakeLists.txt +++ b/src/core/nexus/CMakeLists.txt @@ -3,6 +3,8 @@ dynamic_sourced_cxx_library(core_nexus "${CMAKE_CURRENT_SOURCE_DIR}") add_library(NGen::core_nexus ALIAS core_nexus) +target_link_libraries(core_nexus PRIVATE ewts::ewts_ngen_bridge) + target_include_directories(core_nexus PUBLIC ${PROJECT_SOURCE_DIR}/include/core ${PROJECT_SOURCE_DIR}/include/core/nexus diff --git a/src/core/nexus/HY_PointHydroNexus.cpp b/src/core/nexus/HY_PointHydroNexus.cpp index b38561e2b8..26dd15bbbe 100644 --- a/src/core/nexus/HY_PointHydroNexus.cpp +++ b/src/core/nexus/HY_PointHydroNexus.cpp @@ -2,7 +2,7 @@ #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" typedef boost::error_info errmsg_info; diff --git a/src/core/nexus/HY_PointHydroNexusRemote.cpp b/src/core/nexus/HY_PointHydroNexusRemote.cpp index 1f1e0ab544..3dd9323949 100644 --- a/src/core/nexus/HY_PointHydroNexusRemote.cpp +++ b/src/core/nexus/HY_PointHydroNexusRemote.cpp @@ -6,6 +6,7 @@ #include #include +#include #include // TODO add loggin to this function @@ -126,7 +127,8 @@ double HY_PointHydroNexusRemote::get_downstream_flow(std::string catchment_id, t //because the `add_upstream_flow` call triggers a `send` which removes from the local accounting //all available water and sends it to the remote counterpart for this nexus. std::string msg = "Nexus "+id+" attempted to get_downstream_flow, but its communicator type is sender only."; - Logger::logMsgAndThrowError(msg); + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } else if ( type == receiver || type == sender_receiver ) { @@ -281,4 +283,4 @@ int HY_PointHydroNexusRemote::get_world_rank() return world_rank; } -#endif // NGEN_WITH_MPI \ No newline at end of file +#endif // NGEN_WITH_MPI diff --git a/src/forcing/CMakeLists.txt b/src/forcing/CMakeLists.txt index f64456c873..130ff2508b 100644 --- a/src/forcing/CMakeLists.txt +++ b/src/forcing/CMakeLists.txt @@ -19,6 +19,7 @@ target_link_libraries(forcing PUBLIC NGen::config_header Threads::Threads ) +target_link_libraries(forcing PRIVATE ewts::ewts_ngen_bridge) target_sources(forcing PRIVATE "${CMAKE_CURRENT_LIST_DIR}/NullForcingProvider.cpp") diff --git a/src/forcing/ForcingsEngineDataProvider.cpp b/src/forcing/ForcingsEngineDataProvider.cpp index 91a732f42d..be87ace619 100644 --- a/src/forcing/ForcingsEngineDataProvider.cpp +++ b/src/forcing/ForcingsEngineDataProvider.cpp @@ -3,7 +3,8 @@ #include // timegm #include // std::get_time -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" namespace data_access { namespace detail { @@ -30,9 +31,10 @@ void assert_forcings_engine_requirements() auto mod = interpreter_->getModule(forcings_engine_python_module); auto cls = mod.attr(forcings_engine_python_class).cast(); } catch(std::exception& e) { - Logger::logMsgAndThrowError( - "Failed to initialize ForcingsEngine: ForcingsEngine python module is not installed or is not properly configured. (" + std::string{e.what()} + ")" - ); + std::string msg = "Failed to initialize ForcingsEngine: ForcingsEngine python module is not installed or is not properly configured. (" + + std::string{e.what()} + ")"; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } } @@ -40,7 +42,9 @@ void assert_forcings_engine_requirements() { const auto* wgrib2_exec = std::getenv("WGRIB2"); if (wgrib2_exec == nullptr) { - Logger::logMsgAndThrowError("Failed to initialize ForcingsEngine: $WGRIB2 is not defined"); + std::string msg = "Failed to initialize ForcingsEngine: $WGRIB2 is not defined"; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } } } diff --git a/src/forcing/NetCDFPerFeatureDataProvider.cpp b/src/forcing/NetCDFPerFeatureDataProvider.cpp index bb8507145d..cc41abb7dd 100644 --- a/src/forcing/NetCDFPerFeatureDataProvider.cpp +++ b/src/forcing/NetCDFPerFeatureDataProvider.cpp @@ -8,7 +8,8 @@ #include #include #include -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" using namespace std; std::stringstream netcdf_ss; @@ -103,14 +104,18 @@ NetCDFPerFeatureDataProvider::NetCDFPerFeatureDataProvider(std::string input_pat // some sanity checks if ( id_dim_count > 1) { - Logger::logMsgAndThrowError("Provided NetCDF file has an \"ids\" variable with more than 1 dimension"); + std::string msg = "Provided NetCDF file has an \"ids\" variable with more than 1 dimension"; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } auto id_dim = ids.getDim(0); if (id_dim.isNull() ) { - Logger::logMsgAndThrowError("Provided NetCDF file has a NULL dimension for variable \"ids\""); + std::string msg = "Provided NetCDF file has a NULL dimension for variable \"ids\""; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } auto num_ids = id_dim.getSize(); @@ -235,7 +240,9 @@ NetCDFPerFeatureDataProvider::NetCDFPerFeatureDataProvider(std::string input_pat netcdf_ss << "Error: Time intervals are not constant in forcing file\n" << std::endl; log_stream << netcdf_ss.str(); LOG(netcdf_ss.str(), LogLevel::WARNING); netcdf_ss.str(""); - Logger::logMsgAndThrowError("Time intervals in forcing file are not constant"); + std::string msg = "Time intervals in forcing file are not constant"; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } } #endif diff --git a/src/forcing/NullForcingProvider.cpp b/src/forcing/NullForcingProvider.cpp index 54b244a4ff..2a699e6ec1 100644 --- a/src/forcing/NullForcingProvider.cpp +++ b/src/forcing/NullForcingProvider.cpp @@ -1,5 +1,5 @@ #include "NullForcingProvider.hpp" -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include #include diff --git a/src/geojson/CMakeLists.txt b/src/geojson/CMakeLists.txt index c1f6ab8804..f22dfad944 100644 --- a/src/geojson/CMakeLists.txt +++ b/src/geojson/CMakeLists.txt @@ -12,3 +12,7 @@ target_link_libraries(geojson PUBLIC Boost::boost # Headers-only Boost NGen::logging ) +target_link_libraries(geojson PRIVATE ewts::ewts_ngen_bridge) + + + diff --git a/src/geojson/JSONProperty.cpp b/src/geojson/JSONProperty.cpp index be8bba7f56..32044dd5f7 100644 --- a/src/geojson/JSONProperty.cpp +++ b/src/geojson/JSONProperty.cpp @@ -1,5 +1,5 @@ #include "JSONProperty.hpp" -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" using namespace geojson; diff --git a/src/geopackage/CMakeLists.txt b/src/geopackage/CMakeLists.txt index 0e544b800b..2a10f58ced 100644 --- a/src/geopackage/CMakeLists.txt +++ b/src/geopackage/CMakeLists.txt @@ -10,3 +10,5 @@ add_library(NGen::geopackage ALIAS geopackage) target_include_directories(geopackage PUBLIC ${PROJECT_SOURCE_DIR}/include/geopackage) target_include_directories(geopackage PUBLIC ${PROJECT_SOURCE_DIR}/include/utilities) target_link_libraries(geopackage PUBLIC NGen::geojson Boost::boost SQLite::SQLite3 NGen::logging) +target_link_libraries(geopackage PRIVATE ewts::ewts_ngen_bridge) + diff --git a/src/geopackage/feature.cpp b/src/geopackage/feature.cpp index 2d20014735..202f4a45c2 100644 --- a/src/geopackage/feature.cpp +++ b/src/geopackage/feature.cpp @@ -1,5 +1,5 @@ #include "geopackage.hpp" -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" // Points don't have a bounding box, so we can say its bbox is itself inline void build_point_bbox(const geojson::geometry& geom, std::vector& bbox) diff --git a/src/geopackage/geometry.cpp b/src/geopackage/geometry.cpp index 5f77072d4b..602ec25db8 100644 --- a/src/geopackage/geometry.cpp +++ b/src/geopackage/geometry.cpp @@ -2,7 +2,8 @@ #include "EndianCopy.hpp" #include "wkb.hpp" #include "proj.hpp" -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" geojson::geometry ngen::geopackage::build_geometry( const ngen::sqlite::database::iterator& row, @@ -12,7 +13,8 @@ geojson::geometry ngen::geopackage::build_geometry( { const std::vector geometry_blob = row.get>(geom_col); if (geometry_blob[0] != 'G' || geometry_blob[1] != 'P') { - Logger::logMsgAndThrowError("expected geopackage WKB, but found invalid format instead"); + LOG(LogLevel::FATAL, "expected geopackage WKB, but found invalid format instead"); + throw std::runtime_error("expected geopackage WKB, but found invalid format instead"); } int index = 3; // skip version diff --git a/src/geopackage/ngen_sqlite.cpp b/src/geopackage/ngen_sqlite.cpp index 0bf28bb608..81a136e479 100644 --- a/src/geopackage/ngen_sqlite.cpp +++ b/src/geopackage/ngen_sqlite.cpp @@ -2,7 +2,7 @@ #include #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" namespace ngen { namespace sqlite { diff --git a/src/geopackage/proj.cpp b/src/geopackage/proj.cpp index f1bd075485..70d7e2db22 100644 --- a/src/geopackage/proj.cpp +++ b/src/geopackage/proj.cpp @@ -1,5 +1,5 @@ #include "proj.hpp" -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" namespace ngen { namespace srs { diff --git a/src/geopackage/read.cpp b/src/geopackage/read.cpp index c4031a00a4..50bc062526 100644 --- a/src/geopackage/read.cpp +++ b/src/geopackage/read.cpp @@ -2,19 +2,24 @@ #include #include -#include +#include +#include "ewts_ngen/logger.hpp" std::stringstream read_ss(""); void check_table_name(const std::string& table) { if (boost::algorithm::starts_with(table, "sqlite_")) { - Logger::logMsgAndThrowError("table `" + table + "` is not queryable"); + std::string msg = "table `" + table + "` is not queryable"; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } std::regex allowed("[^-A-Za-z0-9_ ]+"); if (std::regex_match(table, allowed)) { - Logger::logMsgAndThrowError("table `" + table + "` contains invalid characters"); + std::string msg = "table `" + table + "` contains invalid characters"; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } } @@ -53,8 +58,8 @@ std::shared_ptr ngen::geopackage::read( errmsg += ", "; errquery.next(); } - - Logger::logMsgAndThrowError(errmsg); + LOG(LogLevel::FATAL, errmsg); + throw std::runtime_error(errmsg); } // Introspect if the layer is divides to see which ID field is in use diff --git a/src/geopackage/wkb.cpp b/src/geopackage/wkb.cpp index cdcb6f1130..1f2d2fd0bf 100644 --- a/src/geopackage/wkb.cpp +++ b/src/geopackage/wkb.cpp @@ -1,6 +1,7 @@ #include "wkb.hpp" #include "proj.hpp" -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" namespace ngen { namespace geopackage { @@ -19,12 +20,12 @@ enum wkb_geom_t { void throw_if_not_type(uint32_t given, wkb_geom_t expected) { if (given != expected) { - Logger::logMsgAndThrowError( - "expected WKB geometry type " + + std::string msg = "expected WKB geometry type " + std::to_string(expected) + ", but received " + - std::to_string(given) - ); + std::to_string(given); + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } } @@ -164,7 +165,8 @@ typename wkb::multipolygon_t wkb::read_multipolygon(const boost::span buffer) { if (buffer.size() < 5) { - Logger::logMsgAndThrowError("buffer reached end before encountering WKB"); + LOG(LogLevel::FATAL, "buffer reached end before encountering WKB"); + throw std::runtime_error("buffer reached end before encountering WKB"); } int index = 0; @@ -192,7 +194,7 @@ typename wkb::geometry wkb::read(const boost::span buffer) "this reader only implements OGC geometry types 1-6, " "but received type " + std::to_string(type) ); - LOG(throw_msg, LogLevel::WARNING); + LOG(LogLevel::FATAL, throw_msg); throw std::runtime_error(throw_msg); } diff --git a/src/partitionGenerator.cpp b/src/partitionGenerator.cpp index f8b48ee616..16c6b438b7 100644 --- a/src/partitionGenerator.cpp +++ b/src/partitionGenerator.cpp @@ -20,7 +20,7 @@ #endif #include "core/Partition_Parser.hpp" -#include +#include "ewts_ngen/logger.hpp" std::stringstream partgen_ss(""); @@ -218,7 +218,7 @@ void generate_partitions(network::Network& network, const int& num_partitions, P * @param catchment_partitions The global set of partitions * @return int partition number containing the id * - * @throws runtime_error if no partition contains the requested id + * @throws std::runtime_error if no partition contains the requested id */ int find_remote_rank(const std::string& id, const PartitionVSet& catchment_partitions) { @@ -237,7 +237,8 @@ int find_remote_rank(const std::string& id, const PartitionVSet& catchment_parti } if(pos < 0){ std::string msg = "find_remote_rank: Could not find feature id "+id+" in any partition"; - Logger::logMsgAndThrowError(msg); + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } return pos; } @@ -430,18 +431,19 @@ int main(int argc, char* argv[]) geojson::GeoJSON catchment_collection; if (boost::algorithm::ends_with(catchmentDataFile, "gpkg")) { - #if NGEN_WITH_SQLITE3 +#if NGEN_WITH_SQLITE3 try { catchment_collection = ngen::geopackage::read(catchmentDataFile, "divides", catchment_subset_ids); } catch (...) { // Handle all exceptions std::string msg = "Geopackage error occurred reading divides: " + catchmentDataFile; - LOG(msg,LogLevel::FATAL); + LOG(msg,LogLevel::FATAL, msg); throw std::runtime_error(msg); } - #else - Logger::logMsgAndThrowError("SQLite3 support required to read GeoPackage files."); - #endif +#else + LOG(LogLevel::FATAL, "SQLite3 support required to read GeoPackage files"); + throw std::runtime_error("SQLite3 support required to read GeoPackage files"); +#endif } else { @@ -453,8 +455,10 @@ int main(int argc, char* argv[]) //Check that the number of partitions is less or equal to the number of catchment if (num_catchments < num_partitions) { - Logger::logMsgAndThrowError("Input error: total number of catchments: " + std::to_string(num_catchments) + \ - ", cannot be less than the number of partitions: " + std::to_string(num_partitions)); + std::string msg = "Input error: total number of catchments: " + std::to_string(num_catchments) + \ + ", cannot be less than the number of partitions: " + std::to_string(num_partitions); + LOG(msg,LogLevel::FATAL, msg); + throw std::runtime_error(msg); } std::string link_key = "toid"; @@ -467,7 +471,7 @@ int main(int argc, char* argv[]) geojson::GeoJSON global_nexus_collection; if (boost::algorithm::ends_with(nexusDataFile, "gpkg")) { - #if NGEN_WITH_SQLITE3 +#if NGEN_WITH_SQLITE3 try { global_nexus_collection = ngen::geopackage::read(nexusDataFile, "nexus", nexus_subset_ids); } catch (...) { @@ -476,9 +480,10 @@ int main(int argc, char* argv[]) LOG(msg,LogLevel::FATAL); throw std::runtime_error(msg); } - #else - Logger::logMsgAndThrowError("SQLite3 support required to read GeoPackage files."); - #endif +#else + LOG(msg,LogLevel::FATAL, "SQLite3 support required to read GeoPackage files."); + throw std::runtime_error("SQLite3 support required to read GeoPackage files."); +#endif } else { diff --git a/src/realizations/catchment/Bmi_C_Formulation.cpp b/src/realizations/catchment/Bmi_C_Formulation.cpp index c7d4398941..cfc2eaa82c 100644 --- a/src/realizations/catchment/Bmi_C_Formulation.cpp +++ b/src/realizations/catchment/Bmi_C_Formulation.cpp @@ -1,7 +1,8 @@ #include "Bmi_C_Formulation.hpp" using namespace realization; using namespace models::bmi; -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" Bmi_C_Formulation::Bmi_C_Formulation(std::string id, std::shared_ptr forcing_provider, utils::StreamHandler output_stream) : Bmi_Module_Formulation(id, forcing_provider, output_stream) { } @@ -19,7 +20,9 @@ std::string Bmi_C_Formulation::get_formulation_type() const { std::shared_ptr Bmi_C_Formulation::construct_model(const geojson::PropertyMap& properties) { auto library_file_iter = properties.find(BMI_REALIZATION_CFG_PARAM_OPT__LIB_FILE); if (library_file_iter == properties.end()) { - Logger::logMsgAndThrowError("BMI C formulation requires path to library file, but none provided in config"); + std::string msg = "BMI C formulation requires path to library file, but none provided in config"; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } std::string lib_file = library_file_iter->second.as_string(); auto reg_func_itr = properties.find(BMI_REALIZATION_CFG_PARAM_OPT__REGISTRATION_FUNC); @@ -72,7 +75,11 @@ double Bmi_C_Formulation::get_var_value_as_double(const int& index, const std::s if (type == "unsigned long long" || type == "unsigned long long int") return (double) (model->GetValuePtr(var_name))[index]; - Logger::logMsgAndThrowError("Unable to get value of variable " + var_name + " from " + get_model_type_name() + " as double: no logic for converting variable type " + type); + std::string msg = "Unable to get value of variable " + var_name + " from " + + get_model_type_name() + " as double: no logic for converting variable type " + type; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); + return 1.0; } diff --git a/src/realizations/catchment/Bmi_Cpp_Formulation.cpp b/src/realizations/catchment/Bmi_Cpp_Formulation.cpp index 921675ffe7..dffb0e2649 100644 --- a/src/realizations/catchment/Bmi_Cpp_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Cpp_Formulation.cpp @@ -1,5 +1,6 @@ #include "Bmi_Cpp_Formulation.hpp" -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" using namespace realization; using namespace models::bmi; @@ -23,7 +24,9 @@ std::string Bmi_Cpp_Formulation::get_formulation_type() const { std::shared_ptr Bmi_Cpp_Formulation::construct_model(const geojson::PropertyMap& properties) { auto json_prop_itr = properties.find(BMI_REALIZATION_CFG_PARAM_OPT__LIB_FILE); if (json_prop_itr == properties.end()) { - Logger::logMsgAndThrowError("BMI C++ formulation requires path to library file, but none provided in config"); + std::string msg = "BMI C++ formulation requires path to library file, but none provided in config"; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } std::string lib_file = json_prop_itr->second.as_string(); @@ -84,8 +87,11 @@ double Bmi_Cpp_Formulation::get_var_value_as_double(const int& index, const std: if (type == "unsigned long long" || type == "unsigned long long int") return (double) (model->GetValuePtr(var_name))[index]; - Logger::logMsgAndThrowError("Unable to get value of variable " + var_name + " from " + get_model_type_name() + - " as double: no logic for converting variable type " + type); + std::string msg = "Unable to get value of variable " + var_name + " from " + get_model_type_name() + + " as double: no logic for converting variable type " + type; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); + return 1.0; } diff --git a/src/realizations/catchment/Bmi_Fortran_Formulation.cpp b/src/realizations/catchment/Bmi_Fortran_Formulation.cpp index bfd59b0795..04b7e7e51a 100644 --- a/src/realizations/catchment/Bmi_Fortran_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Fortran_Formulation.cpp @@ -1,5 +1,6 @@ #include -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" #if NGEN_WITH_BMI_FORTRAN @@ -29,7 +30,9 @@ Bmi_Fortran_Formulation::Bmi_Fortran_Formulation(std::string id, std::shared_ptr std::shared_ptr Bmi_Fortran_Formulation::construct_model(const geojson::PropertyMap& properties) { auto library_file_iter = properties.find(BMI_REALIZATION_CFG_PARAM_OPT__LIB_FILE); if (library_file_iter == properties.end()) { - Logger::logMsgAndThrowError("BMI C formulation requires path to library file, but none provided in config"); + std::string msg = "BMI C formulation requires path to library file, but none provided in config."; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } std::string lib_file = library_file_iter->second.as_string(); auto reg_func_itr = properties.find(BMI_REALIZATION_CFG_PARAM_OPT__REGISTRATION_FUNC); @@ -54,7 +57,7 @@ double Bmi_Fortran_Formulation::get_var_value_as_double(const int &index, const // don't fit or might convert inappropriately std::string type = model->GetVarType(var_name); //Can cause a segfault here if GetValue returns an empty vector...a "fix" in bmi_utilities GetValue - //will throw a relevant runtime_error if the vector is empty, so this is safe to use this way for now... + //will throw a relevant std::runtime_error if the vector is empty, so this is safe to use this way for now... if (type == "long double") return (double) (models::bmi::GetValue(*model, var_name))[index]; @@ -88,8 +91,10 @@ double Bmi_Fortran_Formulation::get_var_value_as_double(const int &index, const if (type == "unsigned long long" || type == "unsigned long long int") return (double) (models::bmi::GetValue(*model, var_name))[index]; - Logger::logMsgAndThrowError("Unable to get value of variable " + var_name + " from " + get_model_type_name() + - " as double: no logic for converting variable type " + type); + std::string msg = "Unable to get value of variable " + var_name + " from " + get_model_type_name() + + " as double: no logic for converting variable type " + type; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); return 1.0; } diff --git a/src/realizations/catchment/Bmi_Module_Formulation.cpp b/src/realizations/catchment/Bmi_Module_Formulation.cpp index 3fd0b3dcd7..359a257f88 100644 --- a/src/realizations/catchment/Bmi_Module_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Module_Formulation.cpp @@ -1,7 +1,8 @@ #include "Bmi_Module_Formulation.hpp" #include "utilities/logging_utils.h" #include -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" #include "state_save_restore/State_Save_Utils.hpp" #include @@ -247,7 +248,7 @@ namespace realization { // consistent with times /* if (last_model_response_delta == 0 && last_model_response_start_time == 0) { - throw runtime_error(get_formulation_type() + " does not properly set output time validity ranges " + throw std::runtime_error(get_formulation_type() + " does not properly set output time validity ranges " "needed to provide outputs as forcings"); } */ @@ -308,7 +309,7 @@ namespace realization { // consistent with times /* if (last_model_response_delta == 0 && last_model_response_start_time == 0) { - throw runtime_error(get_formulation_type() + " does not properly set output time validity ranges " + throw std::runtime_error(get_formulation_type() + " does not properly set output time validity ranges " "needed to provide outputs as forcings"); } */ diff --git a/src/realizations/catchment/Bmi_Multi_Formulation.cpp b/src/realizations/catchment/Bmi_Multi_Formulation.cpp index 1f4fbbf245..ed5b296193 100644 --- a/src/realizations/catchment/Bmi_Multi_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Multi_Formulation.cpp @@ -11,7 +11,8 @@ #include "Bmi_C_Formulation.hpp" #include "Bmi_Fortran_Formulation.hpp" #include "Bmi_Py_Formulation.hpp" -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" +#include #include "state_save_restore/vecbuf.hpp" #include "state_save_restore/State_Save_Utils.hpp" @@ -128,12 +129,15 @@ void Bmi_Multi_Formulation::create_multi_formulation(geojson::PropertyMap proper #endif // NGEN_WITH_PYTHON } if (inactive_type_requested) { - Logger::logMsgAndThrowError( - get_formulation_type() + " could not initialize sub formulation of type " + type_name + - " due to support for this type not being activated."); + std::string msg = get_formulation_type() + " could not initialize sub formulation of type " + type_name + + " due to support for this type not being activated."; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } if (module == nullptr) { - Logger::logMsgAndThrowError(get_formulation_type() + " received unexpected subtype formulation " + type_name); + std::string msg = get_formulation_type() + " received unexpected subtype formulation " + type_name; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } modules[i] = module; @@ -527,7 +531,9 @@ std::string Bmi_Multi_Formulation::get_output_line_for_timestep(int timestep, st void Bmi_Multi_Formulation::update(time_step_t t_index, time_step_t t_delta) { if (modules.empty()) { - Logger::logMsgAndThrowError("Trying to get response of improperly created empty BMI multi-module formulation."); + std::string msg = "Trying to get response of improperly created empty BMI multi-module formulation."; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } if (t_index < 0) { throw std::invalid_argument( diff --git a/src/realizations/catchment/Bmi_Py_Formulation.cpp b/src/realizations/catchment/Bmi_Py_Formulation.cpp index 1712ec9cc7..c6f14b3ff9 100644 --- a/src/realizations/catchment/Bmi_Py_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Py_Formulation.cpp @@ -1,5 +1,6 @@ #include -#include "Logger.hpp" +#include +#include "ewts_ngen/logger.hpp" #include "state_save_restore/State_Save_Utils.hpp" #if NGEN_WITH_PYTHON @@ -17,7 +18,9 @@ Bmi_Py_Formulation::Bmi_Py_Formulation(std::string id, std::shared_ptr Bmi_Py_Formulation::construct_model(const geojson::PropertyMap &properties) { auto python_type_name_iter = properties.find(BMI_REALIZATION_CFG_PARAM_OPT__PYTHON_TYPE_NAME); if (python_type_name_iter == properties.end()) { - Logger::logMsgAndThrowError("BMI Python formulation requires Python model class type, but none given in config"); + std::string msg = "BMI Python formulation requires Python model class type, but none given in config"; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); } //Load a custom module path, if provided auto python_module_path_iter = properties.find(BMI_REALIZATION_CFG_PARAM_OPT__PYTHON_MODULE_PATH); @@ -78,8 +81,10 @@ double Bmi_Py_Formulation::get_var_value_as_double(const int &index, const std:: else PY_BMI_DOUBLE_AT_INDEX(double) else PY_BMI_DOUBLE_AT_INDEX(long double) #undef PY_BMI_DOUBLE_AT_INDEX - Logger::logMsgAndThrowError("Unable to get value of variable " + var_name + " from " + get_model_type_name() + - " as double: no logic for converting variable type " + val_type); + std::string msg = "Unable to get value of variable " + var_name + " from " + get_model_type_name() + + " as double: no logic for converting variable type " + val_type; + LOG(LogLevel::FATAL, msg); + throw std::runtime_error(msg); return 1.0; } diff --git a/src/realizations/catchment/CMakeLists.txt b/src/realizations/catchment/CMakeLists.txt index 6df221d6af..0cc951afd0 100644 --- a/src/realizations/catchment/CMakeLists.txt +++ b/src/realizations/catchment/CMakeLists.txt @@ -33,3 +33,6 @@ target_link_libraries(realizations_catchment PUBLIC NGen::bmi_protocols ) +target_link_libraries(realizations_catchment PRIVATE ewts::ewts_ngen_bridge) + + diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt index 1b0788f3b6..b1a2aa0c3d 100644 --- a/src/utilities/CMakeLists.txt +++ b/src/utilities/CMakeLists.txt @@ -12,6 +12,8 @@ target_link_libraries(ngen_parallel NGen::logging ) +target_link_libraries(ngen_parallel PRIVATE ewts::ewts_ngen_bridge) + if(NGEN_WITH_MPI) target_link_libraries(ngen_parallel PUBLIC diff --git a/src/utilities/bmi/CMakeLists.txt b/src/utilities/bmi/CMakeLists.txt index 5d86e87f70..ca4bb2bb4b 100644 --- a/src/utilities/bmi/CMakeLists.txt +++ b/src/utilities/bmi/CMakeLists.txt @@ -31,6 +31,8 @@ target_link_libraries(ngen_bmi_protocols NGen::logging ) +target_link_libraries(ngen_bmi_protocols PRIVATE ewts::ewts_ngen_bridge) + target_sources(ngen_bmi_protocols PRIVATE "${PROJECT_SOURCE_DIR}/src/bmi/Bmi_Adapter.cpp" diff --git a/src/utilities/bmi/mass_balance.cpp b/src/utilities/bmi/mass_balance.cpp index f542a682a0..05cfb04810 100644 --- a/src/utilities/bmi/mass_balance.cpp +++ b/src/utilities/bmi/mass_balance.cpp @@ -29,7 +29,7 @@ namespace models { namespace bmi { namespace protocols { NgenMassBalance::NgenMassBalance(const ModelPtr& model, const Properties& properties) : check(false), is_fatal(false), tolerance(1.0E-16), frequency(1){ - initialize(model, properties); + auto init = initialize(model, properties); } NgenMassBalance::NgenMassBalance() : check(false) {} @@ -183,7 +183,7 @@ auto NgenMassBalance::initialize(const ModelPtr& model, const Properties& proper } if ( check ) { //Ensure the model is capable of mass balance using the protocol - check_support(model).or_else( error_or_warning ); + auto result = check_support(model).or_else( error_or_warning ); } return {}; // important to return for the expected to be properly created! } diff --git a/src/utilities/logging/CMakeLists.txt b/src/utilities/logging/CMakeLists.txt index 465cff20b0..0080d26680 100644 --- a/src/utilities/logging/CMakeLists.txt +++ b/src/utilities/logging/CMakeLists.txt @@ -1,7 +1,9 @@ -add_library(logging logging_utils.cpp Logger.cpp) +add_library(logging logging_utils.cpp) add_library(NGen::logging ALIAS logging) target_include_directories(logging PUBLIC ${PROJECT_SOURCE_DIR}/include/utilities) +target_link_libraries(logging PRIVATE ewts::ewts_ngen_bridge) + target_link_libraries(logging PUBLIC Boost::boost ) diff --git a/src/utilities/logging/Logger.cpp b/src/utilities/logging/Logger.cpp deleted file mode 100644 index fb4c8c785a..0000000000 --- a/src/utilities/logging/Logger.cpp +++ /dev/null @@ -1,671 +0,0 @@ -#include "Logger.hpp" -#include -#include -#include -#include -#include // For getenv() -#include -#include -#include // For file handling -#include -#include -#include -#include // For std::string -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -const std::string MODULE_NAME = "ngen"; -const std::string LOG_DIR_NGENCERF = "/ngencerf/data"; // ngenCERF log directory string if environement var empty. -const std::string LOG_DIR_DEFAULT = "run-logs"; // Default parent log directory string if env var empty & ngencerf dosn't exist -const std::string LOG_FILE_EXT = "log"; // Log file name extension -const std::string DS = "/"; // Directory separator -const unsigned int LOG_MODULE_NAME_LEN = 8; // Width of module name for log entries - -const std::string EV_EWTS_LOGGING = "NGEN_EWTS_LOGGING"; // Enable/disable of Error Warning and Trapping System -const std::string EV_NGEN_LOGFILEPATH = "NGEN_LOG_FILE_PATH"; // ngen log file - -const std::string CONFIG_FILENAME = "ngen_logging.json"; // ngen logging config file - -// String to LogLevel map -static const std::unordered_map logLevelMap = { - {"NONE", LogLevel::NONE}, {"0", LogLevel::NONE}, - {"DEBUG", LogLevel::DEBUG}, {"1", LogLevel::DEBUG}, - {"INFO", LogLevel::INFO}, {"2", LogLevel::INFO}, - {"WARNING", LogLevel::WARNING}, {"3", LogLevel::WARNING}, - {"SEVERE", LogLevel::SEVERE}, {"4", LogLevel::SEVERE}, - {"FATAL", LogLevel::FATAL}, {"5", LogLevel::FATAL}, -}; - -// Reverse map: LogLevel to String -static const std::unordered_map logLevelToStringMap = { - {LogLevel::NONE, "NONE "}, - {LogLevel::DEBUG, "DEBUG "}, - {LogLevel::INFO, "INFO "}, - {LogLevel::WARNING, "WARNING"}, - {LogLevel::SEVERE, "SEVERE "}, - {LogLevel::FATAL, "FATAL "}, -}; - -const std::unordered_map moduleNamesMap = { - {"NGEN", "NGEN"}, - {"CFE-S", "CFE"}, - {"CFE-X", "CFE"}, - {"LASAM", "LASAM"}, - {"NOAH-OWP-MODULAR", "NOAHOWP"}, - {"PET", "PET"}, - {"SAC-SMA", "SACSMA"}, - {"SFT", "SFT"}, - {"SMP", "SMP"}, - {"SNOW-17", "SNOW17"}, - {"TOPMODEL", "TOPMODEL"}, - {"TOPOFLOW-GLACIER", "TFGLACR"}, - {"T-ROUTE", "TROUTE"}, - {"UEB", "UEB_BMI"}, - {"LSTM", "LSTM"}, - {"FORCING", "FORCING"} -}; - -bool Logger::DirectoryExists(const std::string& path) { - struct stat info; - if (stat(path.c_str(), &info) != 0) { - return false; // Cannot access path - } - return (info.st_mode & S_IFDIR) != 0; -} - -/** - * Create the directory checking both the call - * to execute the command and the result of the command - */ -bool Logger::CreateDirectory(const std::string& path) { - - if (!DirectoryExists(path)) { - std::string mkdir_cmd = "mkdir -p " + path; - int status = system(mkdir_cmd.c_str()); - - if (status == -1) { - std::cerr << "[CRITICAL] " << MODULE_NAME << " system() failed to run mkdir.\n"; - return false; - } else if (WIFEXITED(status)) { - int exitCode = WEXITSTATUS(status); - if (exitCode != 0) { - std::cerr << "[CRITICAL] " << MODULE_NAME << " mkdir command failed with exit code: " << exitCode << "\n"; - return false; - } - } else { - std::cerr << "[CRITICAL] " << MODULE_NAME << " mkdir terminated abnormally.\n"; - return false; - } - } - return true; -} - -/** - * Open log file and return open status. If already open, - * ensure the write pointer is at the end of the file. - * - * return bool true if open and good, false otherwise - */ -bool Logger::LogFileReady(void) { - - if (openedOnce && logFile.is_open() && logFile.good()) { - logFile.seekp(0, std::ios::end); // Ensure write pointer is at the actual file end - return true; - } - else if (openedOnce) { - // Somehow the logfile was closed. Open in append mode so - // previosly logged messages are not lost - logFile.open(logFilePath, ios::out | ios::app); // This will silently fail if already open. - if (logFile.good()) return true; - } - return false; -} - -void Logger::SetupLogFile(void) { - - // Determine the log file directory and log file name. - // Use name from environment variable if set, otherwise use a default - if (!ngenResultsDir.empty()) { - logFileDir = ngenResultsDir + DS + "logs"; - if (CreateDirectory(logFileDir)) - logFilePath = logFileDir + DS + MODULE_NAME + "." + LOG_FILE_EXT; - } - if (logFilePath.empty()) { - // Get parent log directory - if (DirectoryExists(LOG_DIR_NGENCERF)) { - logFileDir = LOG_DIR_NGENCERF + DS + LOG_DIR_DEFAULT; - } - else { - const char *home = getenv("HOME"); // Get users home directory pathname - std::string dir = (home) ? home : "~"; - logFileDir = dir + DS + LOG_DIR_DEFAULT; - } - - // Ensure parent log direcotry exists - if (CreateDirectory(logFileDir)) { - // Get full log directory path - const char* envUsername = std::getenv("USER"); - std::string dirName = (envUsername) ? envUsername : CreateDateString(); - logFileDir = logFileDir + DS + dirName; - - // Set the full path if log directory exists/created - if (CreateDirectory(logFileDir)) - logFilePath = logFileDir + DS + MODULE_NAME + "_" + CreateTimestamp(false,false) + "." + LOG_FILE_EXT; - } - } - - // Attempt to open log file - if (!logFilePath.empty()) { - logFile.open(logFilePath, ios::out | ios::trunc); // Truncating ensures keeping only the last calibration iteration. - if (logFile.is_open()) { - openedOnce = true; - std::cout << "[DEBUG] " << MODULE_NAME << " Log File: " << logFilePath << std::endl; - return; - } - } - std::cout << "[WARNING] " << MODULE_NAME << " Unable to create log file "; - if (!logFilePath.empty()) { - std::cout << logFilePath; - } - else if (!logFileDir.empty()) { - std::cout << logFileDir; - } - std::cout << " (Perhaps check permissions)" << std::endl; - std::cout << "[WARNING] " << MODULE_NAME << " Log entries will be written to stdout" << std::endl; -} - -std::string Logger::ToUpper(const std::string& input) { - std::string result = input; - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c){ return std::toupper(c); }); - return result; -} - -std::string Logger::ExtractFirstNDirs(const std::string& path, int numDirs) { - size_t pos = 0; - int slashCount = 0; - - while (pos < path.length() && slashCount < numDirs) { - if (path[pos] == '/') { - ++slashCount; - } - ++pos; - } - - // If the path starts with '/', keep it as is; otherwise return substring - return path.substr(0, pos); -} - -std::string CleanJsonToken(const std::string& token) { - std::string s = token; - if (!s.empty() && s.front() == '"') s.erase(0, 1); - if (!s.empty() && s.back() == ',') s.pop_back(); - if (!s.empty() && s.back() == '"') s.pop_back(); - return s; -} - -/* - JSON file format exmaple: - { - "logging_enabled": true, - "modules": { - "ngen": "INFO", - "CFE-S": "INFO", - "UEB": "INFO", - "Noah-OWP-Modular": "DEBUG", - "T-Route": "INFO" - } - } -*/ - -bool Logger::ParseLoggerConfigFile(std::ifstream& jsonFile) -{ - // Rewind file in case it's been partially read - jsonFile.clear(); - jsonFile.seekg(0, std::ios::beg); - - try { - // Read the JSON into a property tree - boost::property_tree::ptree config; - boost::property_tree::read_json(jsonFile, config); - - // Read logging_enabled flag - try { - loggingEnabled = config.get("logging_enabled", true); // default true if missing - std::cout << "[DEBUG] " << MODULE_NAME << " Logging " - << (loggingEnabled ? "ENABLED" : "DISABLED") << std::endl; - } - catch (const boost::property_tree::ptree_bad_data& e) { - std::cout << "[ERROR] " << MODULE_NAME << " JSON data error: " << e.what() << std::endl; - return false; - } - - // Read modules subtree only if logging enabled - if (loggingEnabled) { - bool atLeastOneModuleFound = false; - if (auto modulesOpt = config.get_child_optional("modules")) { - for (const auto& kv : *modulesOpt) { - std::string moduleName = ToUpper(kv.first); - std::string levelStr = ToUpper(kv.second.get_value()); - - auto it = moduleNamesMap.find(moduleName); - if (it != moduleNamesMap.end()) { - atLeastOneModuleFound = true; - moduleLogLevels[moduleName] = ConvertStringToLogLevel(levelStr); - std::cout << "[DEBUG] " << MODULE_NAME << " Found Log level " - << kv.first << "=" - << ConvertLogLevelToString(moduleLogLevels[moduleName]) - << std::endl; - if (moduleName == "NGEN") logLevel = moduleLogLevels[moduleName]; - } else { - std::cout << "[ERROR] " << MODULE_NAME << " Ignoring unknown module " << moduleName << std::endl; - } - } - } else { - std::cout << "[ERROR] " << MODULE_NAME << " Missing 'modules' section in logging.json." << std::endl; - } - return atLeastOneModuleFound; - } - return true; - } - catch (const boost::property_tree::json_parser_error& e) { - std::cout << "[ERROR] " << MODULE_NAME << " JSON parse error: " << e.what() << std::endl; - } - catch (const std::exception& e) { - std::cout << "[ERROR] " << MODULE_NAME << " Exception while parsing config: " << e.what() << std::endl; - } - return false; -} - -void Logger::ReadConfigFile(std::string searchPath) { - - bool success = false; - std::ifstream jsonFile; - - // Set logger defaults - moduleLogLevels.clear(); - loggingEnabled = true; - - // Open and Parse config file - if (searchPath.empty()) { - std::cout << "[WARNING] " << MODULE_NAME << " Logging config file cannot be read from NGEN_RESULTS_DIR environment variable because not set or empty." << std::endl; - std::cout << "[WARNING] " << MODULE_NAME << " Using defaults for logging." << std::endl; - } else { - if (FindAndOpenLogConfigFile(searchPath, jsonFile)) { - if (jsonFile.peek() != std::ifstream::traits_type::eof()) { - std::cout << "[DEBUG] " << MODULE_NAME << " parsing logging config file " << searchPath << std::endl; - success = ParseLoggerConfigFile(jsonFile); - } - } - } - if (loggingEnabled && !success) { - std::cout << "[WARNING] " << MODULE_NAME << " Issue with logging config file " << CONFIG_FILENAME << " in " << ((searchPath.empty())?"undefined path":searchPath) << "." << std::endl; - std::cout << "[WARNING] " << MODULE_NAME << " Using default logging configuration of enabled and log level INFO for all known modules" << std::endl; - for (const auto kv : moduleNamesMap) { - std::string moduleName = ToUpper(kv.first); - moduleLogLevels[moduleName] = LogLevel::INFO; - } - } -} - -bool Logger::IsValidEnvVarName(const std::string& name) { - if (name.empty()) return false; - - // First character must be a letter or underscore - if (!std::isalpha(name[0]) && name[0] != '_') return false; - - // All other characters must be alphanumeric or underscore - for (size_t i = 1; i < name.size(); ++i) { - if (!std::isalnum(name[i]) && name[i] != '_') return false; - } - - return true; -} - -void Logger::ManageLoggingEnvVars(bool set) { - - // Set logger env vars common to all modules - if (set) { - Log("ngen Logger setup: Setting Module Logger Environment Variables", LogLevel::DEBUG); - if (!logFilePath.empty()) { - // Set the log file env var - setenv("NGEN_LOG_FILE_PATH", logFilePath.c_str(), 1); - std::cout << "[DEBUG] " << MODULE_NAME << " Set env var NGEN_LOG_FILE_PATH=" << logFilePath << std::endl; - } - else { - std::cout << "[WARNING] " << MODULE_NAME << " NGEN_LOG_FILE_PATH env var not set. Modules writing to their default logs." << std::endl; - } - - // Set the logging enabled/disabled env var - setenv((EV_EWTS_LOGGING).c_str(), ((loggingEnabled)?"ENABLED":"DISABLED"), 1); - std::string logMsg = std::string("Set Logging ") + ((loggingEnabled)?"ENABLED":"DISABLED"); - std::cout << logMsg << "(envVar=" << EV_EWTS_LOGGING << ")" << std::endl; - if (!logFilePath.empty()) { - LogLevel saveLevel = logLevel; - logLevel = LogLevel::INFO; // Ensure this INFO message is always logged - Log(logMsg, logLevel); - logLevel = saveLevel; - } - } - else { - Log("Logger setup: Unset existing Module Logger Environment Variables", LogLevel::DEBUG); - } - - // Set logger env vars unique to each module in the formulation - // Note: moduleLogLevels is populated from the logging config file which - // only contains the log levels for module in the formulation - for (const auto& modulePair : moduleLogLevels) { - std::string envVar; - std::string moduleNameForEnvVar; - - const std::string& moduleName = modulePair.first; - LogLevel level = modulePair.second; - - // Look up the module env var name in moduleNamesMap - auto it = moduleNamesMap.find(moduleName); - if (it != moduleNamesMap.end()) { - moduleNameForEnvVar = it->second; - if (!IsValidEnvVarName(moduleNameForEnvVar)) { - std::string logMsg = std::string("Invalid env var name ") + moduleNameForEnvVar + - std::string(" for module ") + moduleName; - Log(logMsg, LogLevel::WARNING); - continue; - } - } - else { - std::string logMsg = std::string("Unknown module in logLevels: ") + moduleName; - Log(logMsg, LogLevel::WARNING); - continue; - } - - if (set) { - // Sets the log level envirnoment variable - envVar = moduleNameForEnvVar + "_LOGLEVEL"; - std::string ll = ConvertLogLevelToString(level); - setenv(envVar.c_str(), ll.c_str(), 1); - std::string logMsg = std::string("Set ") + moduleName - + ((moduleName != "NGEN")?" Log Level env var to ":" Log Level to ") - + TrimString(ll); - std::cout << logMsg; - if (moduleName != "NGEN") std::cout << " (" << envVar << ")"; - std::cout << std::endl; - if (!logFilePath.empty()) { - LogLevel saveLevel = logLevel; - logLevel = LogLevel::INFO; // Ensure this INFO message is always logged - Log(logMsg, logLevel); - logLevel = saveLevel; - } - } - else { - if (moduleName != "NGEN") { - // It is possible that individual submodules may be writing to their own - // logs if there was an issue accessing the ngen log file. The log file used - // by each module is stored in its own environment variable. Unsetting this - // environment variable will cause the modules to truncate their logs. - // This is important when running calibrations since iterative runs of ngen - // can number in the 1000's and it is only necessary to retain the last ngen run - envVar = moduleNameForEnvVar + "_LOGFILEPATH"; - unsetenv(envVar.c_str()); - envVar = moduleNameForEnvVar + "_LOGLEVEL"; - unsetenv(envVar.c_str()); - } - } - } -} - -/** -* Configure Logger Preferences and open log file -* @param level: LogLevel::WARNING by Default -* @return void -*/ -void Logger::SetLogPreferences(LogLevel level) { - - if (!loggerInitialized) { - loggerInitialized = true; // Only call this once - - // Unset any existing related environment vars - ManageLoggingEnvVars(false); - - // Determine the log file directory and log file name. - // Use name from environment variable if set, otherwise use a default - const char* envVar = std::getenv("NGEN_RESULTS_DIR"); // Currently set by ngen-cal but envision set for WCOSS at some point - if (envVar != nullptr && envVar[0] != '\0') { - ngenResultsDir = envVar; - std::cout << "[DEBUG] " << MODULE_NAME << " Found envVar NGEN_RESULTS_DIR = " << ngenResultsDir << std::endl; - } - - ReadConfigFile(ngenResultsDir); - - if (loggingEnabled) { - - // Make sure the module name used for logging is all uppercase and LOG_MODULE_NAME_LEN characters wide. - moduleName = MODULE_NAME; - std::string upperName = moduleName.substr(0, LOG_MODULE_NAME_LEN); // Truncate to LOG_MODULE_NAME_LEN chars max - std::transform(upperName.begin(), upperName.end(), upperName.begin(), ::toupper); - - std::ostringstream oss; - oss << std::left << std::setw(LOG_MODULE_NAME_LEN) << std::setfill(' ') << upperName; - moduleName = oss.str(); - - SetupLogFile(); - - // Set the environment variables for the module loggers - ManageLoggingEnvVars(true); - } - } -} - -void Logger::Log(LogLevel messageLevel, const char* message, ...) { - va_list args; - va_start(args, message); - - // Make a copy to calculate required size - va_list args_copy; - va_copy(args_copy, args); - int requiredLen = vsnprintf(nullptr, 0, message, args_copy); - va_end(args_copy); - - if (requiredLen > 0) { - std::vector buffer(requiredLen + 1); // +1 for null terminator - vsnprintf(buffer.data(), buffer.size(), message, args); - - va_end(args); - - Log(std::string(buffer.data()), messageLevel); - } else { - va_end(args); // still need to clean up - } -} - -/** - * Log given message with defined parameters and generate message to pass on Console or File - * @param message: Log Message - * @param messageLevel: Log Level, LogLevel::INFO by default - */ -void Logger::Log(LogLevel messageLevel, std::string message) { - Log(message, messageLevel); -} -/** -* Log given message with defined parameters and generate message to pass on Console or File -* @param message: Log Message -* @param messageLevel: Log Level, LogLevel::INFO by default -*/ -void Logger::Log(std::string message, LogLevel messageLevel) { - Logger *logger = GetLogger(); - - // Log only when appropriate - if ((logger->loggingEnabled) && (messageLevel >= logger->logLevel)) { - std::string logType = ConvertLogLevelToString(messageLevel); - std::string logPrefix = CreateTimestamp() + " " + logger->moduleName + " " + logType; - - // Log message, creating individual entries for a multi-line message - std::istringstream logMsg(message); - std::string line; - if (logger->LogFileReady()) { - while (std::getline(logMsg, line)) { - logger->logFile << logPrefix + " " + line << std::endl; - } - logger->logFile.flush(); - } - else { - // Log file not found. Write to stdout. - while (std::getline(logMsg, line)) { - std::cout << logPrefix + " " + line << std::endl; - } - std::cout << std::flush; - } - } -} - -Logger* Logger::GetLogger() -{ - static Logger* logger = nullptr; - if (logger == nullptr) { - logger = new Logger; - logger->SetLogPreferences(); - } - return logger; -} - -Logger::Logger() -{ - -} - -// Function to trim leading and trailing spaces -std::string Logger::TrimString(const std::string& str) { - // Trim leading spaces - size_t first = str.find_first_not_of(" \t\n\r\f\v"); - if (first == std::string::npos) { - return ""; // No non-whitespace characters - } - - // Trim trailing spaces - size_t last = str.find_last_not_of(" \t\n\r\f\v"); - - // Return the trimmed string - return str.substr(first, last - first + 1); -} - -std::string Logger::ConvertLogLevelToString(LogLevel level) { - auto it = logLevelToStringMap.find(level); - if (it != logLevelToStringMap.end()) { - return it->second; // Found valid named or numeric log level - } - return "NONE"; -} - -/** -* Convert String Representation of Log Level to LogLevel Type -* @param levelStr : String log level -* @return LogLevel -*/ -LogLevel Logger::ConvertStringToLogLevel(const std::string& levelStr) { - std::string level = TrimString(levelStr); - if (!level.empty()) { - // Convert string to LogLevel (supports both names and numbers) - auto it = logLevelMap.find(level); - if (it != logLevelMap.end()) { - return it->second; // Found valid named or numeric log level - } - - // Try parsing as an integer (for cases where an invalid numeric value is given) - try { - int levelNum = std::stoi(level); - if (levelNum >= 0 && levelNum <= 5) { - return static_cast(levelNum); - } - } catch (...) { - // Ignore errors (e.g., if std::stoi fails for non-numeric input) - } - } - return LogLevel::NONE; -} - -std::string Logger::CreateTimestamp(bool appendMS, bool iso) { - using namespace std::chrono; - - // Get current time point - auto now = system_clock::now(); - auto now_time_t = system_clock::to_time_t(now); - - // Get milliseconds - auto ms = duration_cast(now.time_since_epoch()) % 1000; - - // Convert to UTC time - std::tm utc_tm; - gmtime_r(&now_time_t, &utc_tm); - - // Format date/time with strftime - char buffer[32]; - if (iso) { - std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &utc_tm); - } - else { - std::strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%S", &utc_tm); - } - - if (appendMS) { - // Combine with milliseconds - std::ostringstream oss; - oss << buffer << '.' << std::setw(3) << std::setfill('0') << ms.count(); - return oss.str(); - } - return std::string(buffer); -} - -std::string Logger::CreateDateString(void) { - std::time_t tt = std::time(0); - std::tm* timeinfo = std::gmtime(&tt); // Use std::localtime(&tt) if you want local time - - char buffer[11]; // Enough for "YYYY-MM-DD" + null terminator - std::strftime(buffer, sizeof(buffer), "%F", timeinfo); // %F == %Y-%m-%d - - std::stringstream ss; - ss << buffer; - - return ss.str(); -} - -std::string Logger::GetLogFilePath(void) { - return logFilePath; -} - -bool Logger::FileExists(const std::string& path) { - struct stat statbuf{}; - return stat(path.c_str(), &statbuf) == 0 && S_ISREG(statbuf.st_mode); -} - -std::string Logger::GetParentDirName(const std::string& path) { - size_t pos = path.find_last_of('/'); - if (pos == std::string::npos || pos == 0) return "/"; - return path.substr(0, pos); -} - -bool Logger::FindAndOpenLogConfigFile(std::string path, std::ifstream& configFileStream) { - while (!path.empty() && path != "/") { - std::string candidate = path + DS + CONFIG_FILENAME; - if (FileExists(candidate)) { - std::cout << "[DEBUG] " << MODULE_NAME << " Opening logger config file " << candidate << std::endl; - configFileStream.open(candidate); - return configFileStream.is_open(); - } - path = GetParentDirName(path); - } - return false; -} - -LogLevel Logger::GetLogLevel(void) { - return logLevel; -} - -bool Logger::IsLoggingEnabled(void) { - return loggingEnabled; -} diff --git a/src/utilities/logging/logging_utils.cpp b/src/utilities/logging/logging_utils.cpp index 94a0f80567..43f4fc8620 100644 --- a/src/utilities/logging/logging_utils.cpp +++ b/src/utilities/logging/logging_utils.cpp @@ -2,7 +2,7 @@ #include #include #include "logging_utils.h" -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" namespace logging { diff --git a/src/utilities/mdframe/CMakeLists.txt b/src/utilities/mdframe/CMakeLists.txt index cafdf651fc..0073046398 100644 --- a/src/utilities/mdframe/CMakeLists.txt +++ b/src/utilities/mdframe/CMakeLists.txt @@ -7,6 +7,7 @@ NGen::config_header NGen::mdarray NGen::logging ) +target_link_libraries(mdframe PRIVATE ewts::ewts_ngen_bridge) if(NGEN_WITH_NETCDF) target_link_libraries(mdframe PUBLIC NetCDF) diff --git a/src/utilities/mdframe/handler_csv.cpp b/src/utilities/mdframe/handler_csv.cpp index 6f9d291943..8e4526ee97 100644 --- a/src/utilities/mdframe/handler_csv.cpp +++ b/src/utilities/mdframe/handler_csv.cpp @@ -1,5 +1,5 @@ #include -#include "Logger.hpp" +#include "ewts_ngen/logger.hpp" #include "mdframe/mdframe.hpp" #include @@ -41,9 +41,10 @@ void cartesian_indices(const boost::span shape, std::vector variable_subset; @@ -65,7 +66,8 @@ void mdframe::to_csv(const std::string& path, bool header) const } if (variable_subset.empty()) { - Logger::logMsgAndThrowError("cannot output CSV with no output variables"); + LOG(LogLevel::FATAL, "cannot output CSV with no output variables"); + throw std::runtime_error("cannot output CSV with no output variables"); } // Calculate total number of rows across all subdimensions (not including header) diff --git a/src/utilities/mdframe/handler_netcdf.cpp b/src/utilities/mdframe/handler_netcdf.cpp index d36c5a9a65..0858a080d8 100644 --- a/src/utilities/mdframe/handler_netcdf.cpp +++ b/src/utilities/mdframe/handler_netcdf.cpp @@ -82,7 +82,8 @@ void mdframe::to_netcdf(const std::string& path) const namespace ngen { void mdframe::to_netcdf(const std::string& path) const { - Logger::logMsgAndThrowError("This functionality isn't available. Compile NGen with NGEN_WITH_NETCDF=ON to enable NetCDF support"); + LOG(LogLevel::FATAL, "This functionality isn't available. Compile NGen with NGEN_WITH_NETCDF=ON to enable NetCDF support"); + throw std::runtime_error("This functionality isn't available. Compile NGen with NGEN_WITH_NETCDF=ON to enable NetCDF support"); } } diff --git a/src/utilities/python/CMakeLists.txt b/src/utilities/python/CMakeLists.txt index 692bbe8c10..2347625dd2 100644 --- a/src/utilities/python/CMakeLists.txt +++ b/src/utilities/python/CMakeLists.txt @@ -1,6 +1,9 @@ add_library(ngen_python InterpreterUtil.cpp) add_library(NGen::python ALIAS ngen_python) +find_package(ewts CONFIG REQUIRED) # if not already in scope +target_link_libraries(ngen_python PRIVATE ewts::ewts_ngen_bridge) + target_include_directories(ngen_python PUBLIC ${PROJECT_SOURCE_DIR}/include/) target_link_libraries(ngen_python PUBLIC diff --git a/src/utilities/python/InterpreterUtil.cpp b/src/utilities/python/InterpreterUtil.cpp index a4555f3fea..be292349c5 100644 --- a/src/utilities/python/InterpreterUtil.cpp +++ b/src/utilities/python/InterpreterUtil.cpp @@ -4,7 +4,7 @@ #if NGEN_WITH_PYTHON #include -#include +#include "ewts_ngen/logger.hpp" #include #include From 5bbdaa8d402e69d05baec67ab564e3f9abb827c3 Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Wed, 11 Mar 2026 14:01:13 -0700 Subject: [PATCH 02/23] Update submodule references to their nwm-ewts feature branches --- extern/topoflow-glacier | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/topoflow-glacier b/extern/topoflow-glacier index 6ccd49938a..97b455dac6 160000 --- a/extern/topoflow-glacier +++ b/extern/topoflow-glacier @@ -1 +1 @@ -Subproject commit 6ccd49938ab79d61a36791cc25144f3e8dd1229b +Subproject commit 97b455dac674dad3f63a0dcb8ac1f4a3ef3a1be1 From 594b62b8b1dc9bb4794f2b9752fa5a278eea505e Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Fri, 13 Mar 2026 02:23:23 -0700 Subject: [PATCH 03/23] updated Dockerfile to include EWTS --- Dockerfile | 267 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 253 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index ca6b38dbfc..cb880b91d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -222,17 +222,124 @@ RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache \ WORKDIR /ngen-app/ -# TODO This will invalidate the cache for all subsequent stages so we don't really want to do this -# Copy the remainder of your application code -COPY . /ngen-app/ngen/ +############################## +# Stage: EWTS Build – Error, Warning and Trapping System +############################## +# EWTS is built in its own stage so that: +# - It is cached independently from ngen source changes (COPY . /ngen-app/ngen/ +# happens later in the submodules stage). +# - Iterative ngen/submodule development doesn't re-trigger the EWTS clone+build. +# - EWTS_ORG / EWTS_REF can be pinned without affecting other stages' caches. +# +# EWTS provides a unified logging library used by ngen core and ALL Fortran/C/C++ +# submodules (LASAM, snow17, sac-sma, SoilMoistureProfiles, SoilFreezeThaw, +# cfe, topmodel, noah-owp-modular, ueb-bmi) plus a Python package used by lstm. +# +# How the plumbing works: +# 1. We build EWTS here and install it to /opt/ewts. +# 2. Every cmake call in the submodules stage passes +# -DCMAKE_PREFIX_PATH=/opt/ewts so that +# find_package(ewts CONFIG REQUIRED) in each submodule's CMakeLists.txt +# can locate the ewtsConfig.cmake package file. +# 3. That gives each submodule access to the EWTS targets: +# ewts::ewts_cpp – C++ runtime logger (used by LASAM) +# ewts::ewts_fortran – Fortran runtime (snow17, sac-sma, SoilMoistureProfiles, SoilFreezeThaw, noah-owp-modular) +# ewts::ewts_c – C runtime (cfe, topmodel) +# ewts::ewts_ngen_bridge – ngen↔EWTS bridge lib (linked by ngen itself) +# 4. The EWTS Python wheel is pip-installed so that lstm's bmi_lstm.py can +# "import ewts" at runtime. +# +# Build args – override at build time to pin a branch, tag, or full commit SHA: +# docker build --build-arg EWTS_REF=v1.2.3 ... +# docker build --build-arg EWTS_REF=abc123def456 ... +############################## +FROM base AS ewts-build + +SHELL [ "/usr/bin/scl", "enable", "gcc-toolset-10" ] + +ARG EWTS_ORG=NGWPC +ARG EWTS_REF=development + +# Install path for the built EWTS libraries, headers, cmake config, and +# Fortran .mod files. /opt/ewts follows the FHS convention for add-on +# packages (same pattern as /opt/boost already in this image) and avoids +# /tmp which can be cleaned unexpectedly. +ENV EWTS_PREFIX=/opt/ewts + +# Clone nwm-ewts with minimal data (blobless clone), build, install, capture +# git metadata for provenance, then remove the source tree. +# The four-way fetch fallback handles branches, tags, AND bare commit SHAs +RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-ewts \ + set -eux && \ + git clone --filter=blob:none --no-checkout \ + "https://github.com/${EWTS_ORG}/nwm-ewts.git" /tmp/nwm-ewts && \ + cd /tmp/nwm-ewts && \ + (git fetch --depth 1 origin "${EWTS_REF}" \ + || git fetch --depth 1 origin "refs/tags/${EWTS_REF}:refs/tags/${EWTS_REF}" \ + || git fetch origin "${EWTS_REF}" \ + || git fetch origin "refs/tags/${EWTS_REF}:refs/tags/${EWTS_REF}") && \ + git checkout FETCH_HEAD && \ + # ── Build EWTS ── + # This produces: C, C++, Fortran shared libs + ngen bridge + Python wheel. + # -DEWTS_WITH_NGEN=ON enables the ngen bridge (ewts_ngen_bridge.so) which + # provides the C shim ewts_ngen_log() that ngen's core calls into. + # -DEWTS_BUILD_SHARED=ON builds .so's so submodule DSOs can link at runtime. + cmake -S . -B cmake_build \ + -DCMAKE_BUILD_TYPE=Release \ + -DEWTS_WITH_NGEN=ON \ + -DEWTS_BUILD_SHARED=ON && \ + cmake --build cmake_build -j "$(nproc)" && \ + cmake --install cmake_build --prefix ${EWTS_PREFIX} && \ + # ── Capture EWTS git provenance ── + # Saved as a JSON sidecar so the git-info merge step at the bottom of this + # Dockerfile can include EWTS metadata alongside ngen + submodules. + jq -n \ + --arg commit_hash "$(git rev-parse HEAD)" \ + --arg branch "$(git branch -r --contains HEAD 2>/dev/null | grep -v '\->' | sed 's|origin/||' | head -n1 | xargs || echo "${EWTS_REF}")" \ + --arg tags "$(git tag --points-at HEAD 2>–/dev/null | tr '\n' ' ')" \ + --arg author "$(git log -1 --pretty=format:'%an')" \ + --arg commit_date "$(date -u -d @$(git log -1 --pretty=format:'%ct') +'%Y-%m-%d %H:%M:%S UTC')" \ + --arg message "$(git log -1 --pretty=format:'%s' | tr '\n' ';')" \ + --arg build_date "$(date -u +'%Y-%m-%d %H:%M:%S UTC')" \ + '{"nwm-ewts": {commit_hash: $commit_hash, branch: $branch, tags: $tags, author: $author, commit_date: $commit_date, message: $message, build_date: $build_date}}' \ + > /ngen-app/nwm-ewts_git_info.json && \ + # ── Cleanup source ── + cd / && \ + rm -rf /tmp/nwm-ewts + +# Install the EWTS Python wheel into the venv. +# This is what makes "import ewts" work for Python-based submodules (lstm). +# lstm's bmi_lstm.py does: import ewts; LOG = ewts.get_logger(ewts.LSTM_ID) +RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache \ + set -eux && \ + pip install ${EWTS_PREFIX}/python/dist/ewts-*.whl + +# Make EWTS shared libraries (.so) discoverable at runtime. +# Without this, ngen and every submodule DSO would fail with: +# "error while loading shared libraries: libewts_ngen_bridge.so: cannot open" +# We include both lib/ and lib64/ because cmake may install to either depending +# on the platform/distro convention. +ENV LD_LIBRARY_PATH="${EWTS_PREFIX}/lib:${EWTS_PREFIX}/lib64:${LD_LIBRARY_PATH}" ############################## # Stage: Submodules Build ############################## -FROM base AS submodules +# Inherits from ewts-build so /opt/ewts is already present. +# The ngen source COPY happens here — changing ngen code only invalidates +# this stage and below, not the EWTS build above. +############################## +FROM ewts-build AS submodules SHELL [ "/usr/bin/scl", "enable", "gcc-toolset-10" ] +WORKDIR /ngen-app/ + +# Copy the ngen application source. +# This is placed here (not in base) so that +# ngen code changes only invalidate the submodules stage, leaving the base and +# ewts-build stages cached. +COPY . /ngen-app/ngen/ + WORKDIR /ngen-app/ngen/ # Copy only the requirements files first for dependency installation caching @@ -260,12 +367,17 @@ RUN --mount=type=cache,target=/root/.cache/t-route,id=t-route-build \ find /ngen-app/ngen/extern/t-route -name "*.a" -exec rm -f {} + # Configure the build with cache for CMake +# -DCMAKE_PREFIX_PATH=${EWTS_PREFIX} tells cmake where +# to find the ewtsConfig.cmake package file so that ngen's +# CMakeLists.txt line find_package(ewts CONFIG REQUIRED) succeeds. +# ngen links against ewts::ewts_ngen_bridge (the C++/MPI bridge). RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-ngen \ set -eux && \ export FFLAGS="-fPIC" && \ export FCFLAGS="-fPIC" && \ export CMAKE_Fortran_FLAGS="-fPIC" && \ cmake -B cmake_build -S . \ + -DCMAKE_PREFIX_PATH=${EWTS_PREFIX} \ -DNGEN_WITH_MPI=ON \ -DNGEN_WITH_NETCDF=ON \ -DNGEN_WITH_SQLITE=ON \ @@ -284,48 +396,72 @@ RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-ngen \ find /ngen-app/ngen/cmake_build -name "*.a" -exec rm -f {} + && \ find /ngen-app/ngen/cmake_build -name "*.o" -exec rm -f {} + +# ────────────────────────────────────────────────────────────────────────────── # Build each submodule in a separate layer, using cache for CMake as well +# +# IMPORTANT: Every submodule's CMakeLists.txt now contains: +# find_package(ewts CONFIG REQUIRED) +# so we MUST pass -DCMAKE_PREFIX_PATH=${EWTS_PREFIX} to each cmake call. +# Without it, cmake cannot locate ewtsConfig.cmake and the build fails with: +# "Could not find a package configuration file provided by "ewts"..." +# +# What each submodule links: +# LASAM → ewts::ewts_cpp + ewts::ewts_ngen_bridge +# snow17 → ewts::ewts_fortran + ewts::ewts_ngen_bridge +# sac-sma → ewts::ewts_fortran + ewts::ewts_ngen_bridge +# SoilMoistureProfiles → (check its CMakeLists.txt for specifics) +# SoilFreezeThaw → (check its CMakeLists.txt for specifics) +# cfe → (check its CMakeLists.txt for specifics) +# ueb-bmi → (check its CMakeLists.txt for specifics) +# ────────────────────────────────────────────────────────────────────────────── + RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-lasam \ set -eux && \ - cmake -B extern/LASAM/cmake_build -S extern/LASAM/ -DNGEN=ON -DBOOST_ROOT=/opt/boost && \ + cmake -B extern/LASAM/cmake_build -S extern/LASAM/ \ + -DCMAKE_PREFIX_PATH=${EWTS_PREFIX} -DNGEN=ON -DBOOST_ROOT=/opt/boost && \ cmake --build extern/LASAM/cmake_build/ && \ find /ngen-app/ngen/extern/LASAM -name '*.o' -exec rm -f {} + RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-snow17 \ set -eux && \ - cmake -B extern/snow17/cmake_build -S extern/snow17/ -DBOOST_ROOT=/opt/boost && \ + cmake -B extern/snow17/cmake_build -S extern/snow17/ \ + -DCMAKE_PREFIX_PATH=${EWTS_PREFIX} -DBOOST_ROOT=/opt/boost && \ cmake --build extern/snow17/cmake_build/ && \ find /ngen-app/ngen/extern/snow17 -name '*.o' -exec rm -f {} + RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-sac-sma \ set -eux && \ - cmake -B extern/sac-sma/cmake_build -S extern/sac-sma/ -DBOOST_ROOT=/opt/boost && \ + cmake -B extern/sac-sma/cmake_build -S extern/sac-sma/ \ + -DCMAKE_PREFIX_PATH=${EWTS_PREFIX} -DBOOST_ROOT=/opt/boost && \ cmake --build extern/sac-sma/cmake_build/ && \ find /ngen-app/ngen/extern/sac-sma -name '*.o' -exec rm -f {} + RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-soilmoistureprofiles \ set -eux && \ - cmake -B extern/SoilMoistureProfiles/cmake_build -S extern/SoilMoistureProfiles/SoilMoistureProfiles/ -DNGEN=ON -DBOOST_ROOT=/opt/boost && \ + cmake -B extern/SoilMoistureProfiles/cmake_build -S extern/SoilMoistureProfiles/SoilMoistureProfiles/ \ + -DCMAKE_PREFIX_PATH=${EWTS_PREFIX} -DNGEN=ON -DBOOST_ROOT=/opt/boost && \ cmake --build extern/SoilMoistureProfiles/cmake_build/ && \ find /ngen-app/ngen/extern/SoilMoistureProfiles -name '*.o' -exec rm -f {} + RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-soilfreezethaw \ set -eux && \ - cmake -B extern/SoilFreezeThaw/cmake_build -S extern/SoilFreezeThaw/SoilFreezeThaw/ -DNGEN=ON -DBOOST_ROOT=/opt/boost && \ + cmake -B extern/SoilFreezeThaw/cmake_build -S extern/SoilFreezeThaw/SoilFreezeThaw/ \ + -DCMAKE_PREFIX_PATH=${EWTS_PREFIX} -DNGEN=ON -DBOOST_ROOT=/opt/boost && \ cmake --build extern/SoilFreezeThaw/cmake_build/ && \ find /ngen-app/ngen/extern/SoilFreezeThaw -name '*.o' -exec rm -f {} + RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-ueb-bmi \ set -eux && \ cmake -B extern/ueb-bmi/cmake_build -S extern/ueb-bmi/ \ - -DUEB_SUPPRESS_OUTPUTS=ON -DBMICXX_INCLUDE_DIRS=/ngen-app/ngen/extern/bmi-cxx/ -DBOOST_ROOT=/opt/boost && \ + -DUEB_SUPPRESS_OUTPUTS=ON -DCMAKE_PREFIX_PATH=${EWTS_PREFIX} \ + -DBMICXX_INCLUDE_DIRS=/ngen-app/ngen/extern/bmi-cxx/ -DBOOST_ROOT=/opt/boost && \ cmake --build extern/ueb-bmi/cmake_build/ && \ find /ngen-app/ngen/extern/ueb-bmi/ -name '*.o' -exec rm -f {} + RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache \ set -eux; \ cd extern/lstm; \ - pip install . ./lstm_ewts + pip install . RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache \ set -eux; \ @@ -415,10 +551,12 @@ RUN set -eux && \ echo "$info" > /ngen-app/submodules-json/git_info_"$sub_key".json; \ done; \ \ - # Merge the main repository JSON with all submodule JSON files as top-level objects - jq -s 'add' $GIT_INFO_PATH /ngen-app/submodules-json/*.json > /ngen-app/merged_git_info.json && \ + # Merge the main repository JSON + submodule JSONs + EWTS provenance into one file. + # The EWTS json was created during the EWTS build step above; including it + # here means `cat /ngen-app/ngen_git_info.json` shows EWTS version info too. + jq -s 'add' $GIT_INFO_PATH /ngen-app/submodules-json/*.json /ngen-app/nwm-ewts_git_info.json > /ngen-app/merged_git_info.json && \ mv /ngen-app/merged_git_info.json $GIT_INFO_PATH && \ - rm -rf /ngen-app/submodules-json + rm -rf /ngen-app/submodules-json /ngen-app/nwm-ewts_git_info.json # Extend PYTHONPATH for LSTM models (preserve venv path from ngen-bmi-forcing) ENV PYTHONPATH="${PYTHONPATH}:/ngen-app/ngen/extern/lstm:/ngen-app/ngen/extern/lstm/lstm" @@ -432,3 +570,104 @@ SHELL ["/bin/bash", "-c"] ENTRYPOINT [ "/ngen-app/bin/run-ngen.sh" ] CMD [ "--info" ] +############################## +# Stage: EWTS Verification (optional, build with --target ewts-verify) +############################## +# Usage: +# docker build --target ewts-verify -t ngen-ewts-verify -f Dockerfile . +# docker run --rm ngen-ewts-verify +# +# This stage runs a comprehensive check that EWTS is properly wired into +# ngen and all submodules. It does NOT run ngen itself — just verifies +# that libraries, headers, cmake config, Python packages, and shared +# object linkages are all in place. +############################## +FROM submodules AS ewts-verify + +SHELL [ "/usr/bin/scl", "enable", "gcc-toolset-10" ] + +RUN set -eux && \ + echo "" && \ + echo "============================================" && \ + echo " EWTS Integration Verification" && \ + echo "============================================" && \ + echo "" && \ + \ + # ── 1. Check EWTS install tree exists ── + echo "--- 1. EWTS install tree ---" && \ + echo "EWTS_PREFIX=${EWTS_PREFIX}" && \ + ls -la ${EWTS_PREFIX}/lib/ && \ + echo "" && \ + \ + # ── 2. Check EWTS shared libraries are present ── + echo "--- 2. EWTS shared libraries ---" && \ + echo "Looking for libewts_*.so files..." && \ + find ${EWTS_PREFIX} -name '*.so' -o -name '*.so.*' | sort && \ + echo "" && \ + \ + # ── 3. Verify cmake package config is findable ── + echo "--- 3. EWTS cmake config ---" && \ + ls ${EWTS_PREFIX}/lib/cmake/ewts/ewtsConfig.cmake && \ + echo "ewtsConfig.cmake found OK" && \ + echo "" && \ + \ + # ── 4. Check Fortran .mod files (needed by snow17, sac-sma, etc.) ── + echo "--- 4. EWTS Fortran .mod files ---" && \ + find ${EWTS_PREFIX} -name '*.mod' | sort && \ + echo "" && \ + \ + # ── 5. Verify ngen executable exists and links to EWTS ── + echo "--- 5. ngen binary – EWTS linkage ---" && \ + NGEN_BIN=/ngen-app/ngen/cmake_build/ngen && \ + file "$NGEN_BIN" && \ + echo "Checking ldd for ewts symbols..." && \ + ldd "$NGEN_BIN" | grep -i ewts && \ + echo "ngen links to EWTS OK" && \ + echo "" && \ + \ + # ── 6. Check each submodule .so links to EWTS ── + echo "--- 6. Submodule .so files – EWTS linkage ---" && \ + for so in \ + extern/LASAM/cmake_build/*.so \ + extern/snow17/cmake_build/*.so \ + extern/sac-sma/cmake_build/*.so \ + extern/SoilMoistureProfiles/cmake_build/*.so \ + extern/SoilFreezeThaw/cmake_build/*.so \ + extern/ueb-bmi/cmake_build/*.so; \ + do \ + if [ -f "$so" ]; then \ + echo "Checking: $so"; \ + if ldd "$so" | grep -qi ewts; then \ + echo " ✓ links to EWTS"; \ + else \ + echo " ⚠ WARNING: no EWTS linkage found (may be expected if submodule doesn't use EWTS directly)"; \ + fi; \ + fi; \ + done && \ + echo "" && \ + \ + # ── 7. Verify EWTS Python package is importable ── + echo "--- 7. EWTS Python package ---" && \ + python3 -c "import ewts; print(f'ewts version: {ewts.__version__}')" && \ + python3 -c "import ewts; print(f'EWTS module keys available: {dir(ewts)}')" && \ + echo "Python ewts import OK" && \ + echo "" && \ + \ + # ── 8. Verify lstm can import ewts (this is the runtime dependency) ── + echo "--- 8. lstm → ewts Python integration ---" && \ + python3 -c "from lstm.bmi_lstm import *; print('lstm.bmi_lstm imports OK (includes ewts)')" && \ + echo "" && \ + \ + # ── 9. Show git provenance ── + echo "--- 9. Git provenance (nwm-ewts entry) ---" && \ + GIT_INFO=$(find /ngen-app -name '*_git_info.json' | head -1) && \ + if [ -n "$GIT_INFO" ]; then \ + jq '."nwm-ewts"' "$GIT_INFO"; \ + else \ + echo "No git_info.json found"; \ + fi && \ + echo "" && \ + echo "============================================" && \ + echo " EWTS verification complete" && \ + echo "============================================" + From 777a20bf1b7f42fef84d6b61ca0b91809bb90cd3 Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Fri, 13 Mar 2026 08:00:18 -0700 Subject: [PATCH 04/23] Updates to Dockerfile comments for clarification --- Dockerfile | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index cb880b91d7..3f91155216 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,12 +5,12 @@ ############################## ARG ORG=ngwpc ARG NGEN_FORCING_IMAGE_TAG=latest -ARG NGEN_FORCING_IMAGE=ghcr.io/ngwpc/ngen-bmi-forcing:${NGEN_FORCING_IMAGE_TAG} +ARG NGEN_FORCING_IMAGE=ghcr.io/${ORG}/ngen-bmi-forcing:${NGEN_FORCING_IMAGE_TAG} -#FROM ${NGEN_FORCING_IMAGE} AS base +FROM ${NGEN_FORCING_IMAGE} AS base # Uncomment when building locally -FROM ngen-bmi-forcing AS base +#FROM ngen-bmi-forcing AS base # OCI Metadata Arguments ARG NGEN_FORCING_IMAGE @@ -231,23 +231,24 @@ WORKDIR /ngen-app/ # - Iterative ngen/submodule development doesn't re-trigger the EWTS clone+build. # - EWTS_ORG / EWTS_REF can be pinned without affecting other stages' caches. # -# EWTS provides a unified logging library used by ngen core and ALL Fortran/C/C++ -# submodules (LASAM, snow17, sac-sma, SoilMoistureProfiles, SoilFreezeThaw, -# cfe, topmodel, noah-owp-modular, ueb-bmi) plus a Python package used by lstm. +# EWTS provides a unified logging framework used by ngen core and ALL C, C++, Fortran, +# and Python submodules. Libraries are created for C, C++ and Fortran submodules +# (cfe, evapotranspiration, LASAM, noah-owp-modular, snow17, sac-sma, +# SoilFreezeThaw, SoilMoistureProfiles, topmodel, ueb-bmi) and a Python package is +# used by Python sumbodules (lstm, topoflow-glacier and t-route). # # How the plumbing works: # 1. We build EWTS here and install it to /opt/ewts. # 2. Every cmake call in the submodules stage passes -# -DCMAKE_PREFIX_PATH=/opt/ewts so that +# -DCMAKE_PREFIX_PATH=/opt/ewts so that # find_package(ewts CONFIG REQUIRED) in each submodule's CMakeLists.txt # can locate the ewtsConfig.cmake package file. -# 3. That gives each submodule access to the EWTS targets: -# ewts::ewts_cpp – C++ runtime logger (used by LASAM) -# ewts::ewts_fortran – Fortran runtime (snow17, sac-sma, SoilMoistureProfiles, SoilFreezeThaw, noah-owp-modular) -# ewts::ewts_c – C runtime (cfe, topmodel) +# 3. The following gives each submodule access to the EWTS targets: +# ewts::ewts_c – C runtime (cfe, evapotranspiration, topmodel) +# ewts::ewts_cpp – C++ runtime logger (used by LASAM, SoilFreezeThaw, SoilMoistureProfiles) +# ewts::ewts_fortran – Fortran runtime (noah-owp-modular sac-sma,, snow17) # ewts::ewts_ngen_bridge – ngen↔EWTS bridge lib (linked by ngen itself) -# 4. The EWTS Python wheel is pip-installed so that lstm's bmi_lstm.py can -# "import ewts" at runtime. +# EWTS Python wheel – pip intalled package (lstm, topoflow-glacier, t-route) # # Build args – override at build time to pin a branch, tag, or full commit SHA: # docker build --build-arg EWTS_REF=v1.2.3 ... @@ -308,8 +309,8 @@ RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-ewts \ rm -rf /tmp/nwm-ewts # Install the EWTS Python wheel into the venv. -# This is what makes "import ewts" work for Python-based submodules (lstm). -# lstm's bmi_lstm.py does: import ewts; LOG = ewts.get_logger(ewts.LSTM_ID) +# This is what makes "import ewts" work for Python-based submodules. +# For example, lstm's bmi_lstm.py does: import ewts; LOG = ewts.get_logger(ewts.LSTM_ID) RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache \ set -eux && \ pip install ${EWTS_PREFIX}/python/dist/ewts-*.whl From a6b2013096814499ebc728433589274af03a7cca Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Fri, 13 Mar 2026 08:47:09 -0700 Subject: [PATCH 05/23] fixing cloning issue with ewts --- Dockerfile | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f91155216..3b24043efa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -231,7 +231,7 @@ WORKDIR /ngen-app/ # - Iterative ngen/submodule development doesn't re-trigger the EWTS clone+build. # - EWTS_ORG / EWTS_REF can be pinned without affecting other stages' caches. # -# EWTS provides a unified logging framework used by ngen core and ALL C, C++, Fortran, +# EWTS provides a unified logging framework used by ngen core and ALL C, C++, Fortran, # and Python submodules. Libraries are created for C, C++ and Fortran submodules # (cfe, evapotranspiration, LASAM, noah-owp-modular, snow17, sac-sma, # SoilFreezeThaw, SoilMoistureProfiles, topmodel, ueb-bmi) and a Python package is @@ -248,7 +248,7 @@ WORKDIR /ngen-app/ # ewts::ewts_cpp – C++ runtime logger (used by LASAM, SoilFreezeThaw, SoilMoistureProfiles) # ewts::ewts_fortran – Fortran runtime (noah-owp-modular sac-sma,, snow17) # ewts::ewts_ngen_bridge – ngen↔EWTS bridge lib (linked by ngen itself) -# EWTS Python wheel – pip intalled package (lstm, topoflow-glacier, t-route) +# EWTS Python wheel – pip intalled package (lstm, topoflow-glacier, t-route) # # Build args – override at build time to pin a branch, tag, or full commit SHA: # docker build --build-arg EWTS_REF=v1.2.3 ... @@ -267,19 +267,17 @@ ARG EWTS_REF=development # /tmp which can be cleaned unexpectedly. ENV EWTS_PREFIX=/opt/ewts -# Clone nwm-ewts with minimal data (blobless clone), build, install, capture -# git metadata for provenance, then remove the source tree. -# The four-way fetch fallback handles branches, tags, AND bare commit SHAs +# Clone nwm-ewts, build, install, capture git metadata for provenance, +# then remove the source tree. +# Try shallow clone by branch/tag name first; fall back to full clone + checkout +# for bare commit SHAs (which git clone -b doesn't support). RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-ewts \ set -eux && \ - git clone --filter=blob:none --no-checkout \ - "https://github.com/${EWTS_ORG}/nwm-ewts.git" /tmp/nwm-ewts && \ + (git clone --depth 1 -b "${EWTS_REF}" \ + "https://github.com/${EWTS_ORG}/nwm-ewts.git" /tmp/nwm-ewts \ + || (git clone "https://github.com/${EWTS_ORG}/nwm-ewts.git" /tmp/nwm-ewts && \ + cd /tmp/nwm-ewts && git checkout "${EWTS_REF}")) && \ cd /tmp/nwm-ewts && \ - (git fetch --depth 1 origin "${EWTS_REF}" \ - || git fetch --depth 1 origin "refs/tags/${EWTS_REF}:refs/tags/${EWTS_REF}" \ - || git fetch origin "${EWTS_REF}" \ - || git fetch origin "refs/tags/${EWTS_REF}:refs/tags/${EWTS_REF}") && \ - git checkout FETCH_HEAD && \ # ── Build EWTS ── # This produces: C, C++, Fortran shared libs + ngen bridge + Python wheel. # -DEWTS_WITH_NGEN=ON enables the ngen bridge (ewts_ngen_bridge.so) which From f18db6e98cab9bc37493c23e56cd692639448034 Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Fri, 13 Mar 2026 09:10:40 -0700 Subject: [PATCH 06/23] added ewts path to t route build --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index 3b24043efa..fdd31b0934 100644 --- a/Dockerfile +++ b/Dockerfile @@ -355,10 +355,14 @@ RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache \ ENV LD_LIBRARY_PATH=/usr/local/lib64:$LD_LIBRARY_PATH # Use cache for building the t-route submodule +# EWTS_INSTALL_PREFIX tells t-route's compiler.sh where to find the EWTS +# Python wheel (at $EWTS_INSTALL_PREFIX/python/dist/ewts-*.whl). +# Without this it defaults to /tmp/ewts_install which doesn't exist. RUN --mount=type=cache,target=/root/.cache/t-route,id=t-route-build \ set -eux && \ cd extern/t-route && \ echo "Running compiler.sh" && \ + export EWTS_INSTALL_PREFIX=${EWTS_PREFIX} && \ LDFLAGS='-Wl,-L/usr/local/lib64/,-L/usr/local/lib/,-rpath,/usr/local/lib64/,-rpath,/usr/local/lib/' && \ ./compiler.sh no-e && \ rm -rf /ngen-app/ngen/extern/t-route/test/LowerColorado_TX_v4 && \ From 89f143439be28b2ab35d010c70b26f89ccbc070a Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Fri, 13 Mar 2026 10:41:19 -0700 Subject: [PATCH 07/23] dockerfile updates --- Dockerfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index fdd31b0934..3b24043efa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -355,14 +355,10 @@ RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache \ ENV LD_LIBRARY_PATH=/usr/local/lib64:$LD_LIBRARY_PATH # Use cache for building the t-route submodule -# EWTS_INSTALL_PREFIX tells t-route's compiler.sh where to find the EWTS -# Python wheel (at $EWTS_INSTALL_PREFIX/python/dist/ewts-*.whl). -# Without this it defaults to /tmp/ewts_install which doesn't exist. RUN --mount=type=cache,target=/root/.cache/t-route,id=t-route-build \ set -eux && \ cd extern/t-route && \ echo "Running compiler.sh" && \ - export EWTS_INSTALL_PREFIX=${EWTS_PREFIX} && \ LDFLAGS='-Wl,-L/usr/local/lib64/,-L/usr/local/lib/,-rpath,/usr/local/lib64/,-rpath,/usr/local/lib/' && \ ./compiler.sh no-e && \ rm -rf /ngen-app/ngen/extern/t-route/test/LowerColorado_TX_v4 && \ From 29a513015adc233d473cc47045c7fc6a4c91029b Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Fri, 13 Mar 2026 10:52:46 -0700 Subject: [PATCH 08/23] dockerfile updates --- Dockerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3b24043efa..40168627ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -267,6 +267,10 @@ ARG EWTS_REF=development # /tmp which can be cleaned unexpectedly. ENV EWTS_PREFIX=/opt/ewts +# Point the development fallback to the cloned source tree so that +# compiler.sh can pip-install EWTS from source if the wheel is missing. +ENV EWTS_PY_ROOT=/tmp/nwm-ewts/runtime/python/ewts + # Clone nwm-ewts, build, install, capture git metadata for provenance, # then remove the source tree. # Try shallow clone by branch/tag name first; fall back to full clone + checkout @@ -302,9 +306,9 @@ RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-ewts \ --arg build_date "$(date -u +'%Y-%m-%d %H:%M:%S UTC')" \ '{"nwm-ewts": {commit_hash: $commit_hash, branch: $branch, tags: $tags, author: $author, commit_date: $commit_date, message: $message, build_date: $build_date}}' \ > /ngen-app/nwm-ewts_git_info.json && \ - # ── Cleanup source ── + # ── Cleanup source (keep Python source as fallback for compiler.sh) ── cd / && \ - rm -rf /tmp/nwm-ewts + rm -rf /tmp/nwm-ewts/cmake_build /tmp/nwm-ewts/.git # Install the EWTS Python wheel into the venv. # This is what makes "import ewts" work for Python-based submodules. From c0cbd83595a5509cac1bb8959ce5320eb6e83d03 Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Fri, 13 Mar 2026 11:42:00 -0700 Subject: [PATCH 09/23] dockerfile updates --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 40168627ab..1a5a2c1a8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -299,7 +299,7 @@ RUN --mount=type=cache,target=/root/.cache/cmake,id=cmake-ewts \ jq -n \ --arg commit_hash "$(git rev-parse HEAD)" \ --arg branch "$(git branch -r --contains HEAD 2>/dev/null | grep -v '\->' | sed 's|origin/||' | head -n1 | xargs || echo "${EWTS_REF}")" \ - --arg tags "$(git tag --points-at HEAD 2>–/dev/null | tr '\n' ' ')" \ + --arg tags "$(git tag --points-at HEAD 2>/dev/null | tr '\n' ' ')" \ --arg author "$(git log -1 --pretty=format:'%an')" \ --arg commit_date "$(date -u -d @$(git log -1 --pretty=format:'%ct') +'%Y-%m-%d %H:%M:%S UTC')" \ --arg message "$(git log -1 --pretty=format:'%s' | tr '\n' ';')" \ From 74dd81ec868e336b830a971c808a4092717efa32 Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Fri, 13 Mar 2026 12:22:01 -0700 Subject: [PATCH 10/23] fix: change ewts linkage from PRIVATE to PUBLIC in all CMakeLists ewts_ngen/logger.hpp is included in public headers, so the EWTS include directories must propagate to downstream consumers (tests, etc). PRIVATE linkage caused 'ewts_ngen/logger.hpp: No such file' when compiling test targets. --- src/bmi/CMakeLists.txt | 2 +- src/core/CMakeLists.txt | 2 +- src/core/mediator/CMakeLists.txt | 2 +- src/core/nexus/CMakeLists.txt | 2 +- src/forcing/CMakeLists.txt | 2 +- src/geojson/CMakeLists.txt | 2 +- src/geopackage/CMakeLists.txt | 2 +- src/realizations/catchment/CMakeLists.txt | 4 ++-- src/utilities/CMakeLists.txt | 2 +- src/utilities/bmi/CMakeLists.txt | 4 ++-- src/utilities/logging/CMakeLists.txt | 2 +- src/utilities/mdframe/CMakeLists.txt | 2 +- src/utilities/python/CMakeLists.txt | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/bmi/CMakeLists.txt b/src/bmi/CMakeLists.txt index d98d201d42..7c8bbd9d1c 100644 --- a/src/bmi/CMakeLists.txt +++ b/src/bmi/CMakeLists.txt @@ -16,7 +16,7 @@ target_link_libraries(ngen_bmi NGen::core_mediator ) -target_link_libraries(ngen_bmi PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(ngen_bmi PUBLIC ewts::ewts_ngen_bridge) target_sources(ngen_bmi PRIVATE diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3d0126e15d..8793567a65 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -11,7 +11,7 @@ target_link_libraries(core PRIVATE NGen::parallel ) -target_link_libraries(core PRIVATE +target_link_libraries(core PUBLIC ewts::ewts_ngen_bridge ) diff --git a/src/core/mediator/CMakeLists.txt b/src/core/mediator/CMakeLists.txt index 80374fee2a..dcfb2fde6e 100644 --- a/src/core/mediator/CMakeLists.txt +++ b/src/core/mediator/CMakeLists.txt @@ -3,7 +3,7 @@ dynamic_sourced_cxx_library(core_mediator "${CMAKE_CURRENT_SOURCE_DIR}") add_library(NGen::core_mediator ALIAS core_mediator) -target_link_libraries(core_mediator PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(core_mediator PUBLIC ewts::ewts_ngen_bridge) if(NGEN_WITH_UDUNITS) target_link_libraries(core_mediator PUBLIC libudunits2) diff --git a/src/core/nexus/CMakeLists.txt b/src/core/nexus/CMakeLists.txt index 759a5c3c23..2e45b39e02 100644 --- a/src/core/nexus/CMakeLists.txt +++ b/src/core/nexus/CMakeLists.txt @@ -3,7 +3,7 @@ dynamic_sourced_cxx_library(core_nexus "${CMAKE_CURRENT_SOURCE_DIR}") add_library(NGen::core_nexus ALIAS core_nexus) -target_link_libraries(core_nexus PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(core_nexus PUBLIC ewts::ewts_ngen_bridge) target_include_directories(core_nexus PUBLIC ${PROJECT_SOURCE_DIR}/include/core diff --git a/src/forcing/CMakeLists.txt b/src/forcing/CMakeLists.txt index 130ff2508b..b02e3c4ff8 100644 --- a/src/forcing/CMakeLists.txt +++ b/src/forcing/CMakeLists.txt @@ -19,7 +19,7 @@ target_link_libraries(forcing PUBLIC NGen::config_header Threads::Threads ) -target_link_libraries(forcing PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(forcing PUBLIC ewts::ewts_ngen_bridge) target_sources(forcing PRIVATE "${CMAKE_CURRENT_LIST_DIR}/NullForcingProvider.cpp") diff --git a/src/geojson/CMakeLists.txt b/src/geojson/CMakeLists.txt index f22dfad944..d9d26d586e 100644 --- a/src/geojson/CMakeLists.txt +++ b/src/geojson/CMakeLists.txt @@ -11,8 +11,8 @@ target_include_directories(geojson PUBLIC target_link_libraries(geojson PUBLIC Boost::boost # Headers-only Boost NGen::logging + ewts::ewts_ngen_bridge ) -target_link_libraries(geojson PRIVATE ewts::ewts_ngen_bridge) diff --git a/src/geopackage/CMakeLists.txt b/src/geopackage/CMakeLists.txt index 2a10f58ced..c438e03095 100644 --- a/src/geopackage/CMakeLists.txt +++ b/src/geopackage/CMakeLists.txt @@ -10,5 +10,5 @@ add_library(NGen::geopackage ALIAS geopackage) target_include_directories(geopackage PUBLIC ${PROJECT_SOURCE_DIR}/include/geopackage) target_include_directories(geopackage PUBLIC ${PROJECT_SOURCE_DIR}/include/utilities) target_link_libraries(geopackage PUBLIC NGen::geojson Boost::boost SQLite::SQLite3 NGen::logging) -target_link_libraries(geopackage PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(geopackage PUBLIC ewts::ewts_ngen_bridge) diff --git a/src/realizations/catchment/CMakeLists.txt b/src/realizations/catchment/CMakeLists.txt index 0cc951afd0..ffe82683b2 100644 --- a/src/realizations/catchment/CMakeLists.txt +++ b/src/realizations/catchment/CMakeLists.txt @@ -22,7 +22,7 @@ target_include_directories(realizations_catchment PUBLIC ${PROJECT_SOURCE_DIR}/include/geojson ${PROJECT_SOURCE_DIR}/include/bmi ) - + target_link_libraries(realizations_catchment PUBLIC ${CMAKE_DL_LIBS} NGen::config_header @@ -33,6 +33,6 @@ target_link_libraries(realizations_catchment PUBLIC NGen::bmi_protocols ) -target_link_libraries(realizations_catchment PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(realizations_catchment PUBLIC ewts::ewts_ngen_bridge) diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt index b1a2aa0c3d..c9b340fa46 100644 --- a/src/utilities/CMakeLists.txt +++ b/src/utilities/CMakeLists.txt @@ -12,7 +12,7 @@ target_link_libraries(ngen_parallel NGen::logging ) -target_link_libraries(ngen_parallel PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(ngen_parallel PUBLIC ewts::ewts_ngen_bridge) if(NGEN_WITH_MPI) target_link_libraries(ngen_parallel diff --git a/src/utilities/bmi/CMakeLists.txt b/src/utilities/bmi/CMakeLists.txt index ca4bb2bb4b..a98ad04af5 100644 --- a/src/utilities/bmi/CMakeLists.txt +++ b/src/utilities/bmi/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(ngen_bmi_protocols protocols.cpp mass_balance.cpp) add_library(NGen::bmi_protocols ALIAS ngen_bmi_protocols) -target_include_directories(ngen_bmi_protocols PUBLIC +target_include_directories(ngen_bmi_protocols PUBLIC ${PROJECT_SOURCE_DIR}/include/bmi ${PROJECT_SOURCE_DIR}/include/utilities/bmi ${PROJECT_SOURCE_DIR}/include/geojson @@ -31,7 +31,7 @@ target_link_libraries(ngen_bmi_protocols NGen::logging ) -target_link_libraries(ngen_bmi_protocols PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(ngen_bmi_protocols PUBLIC ewts::ewts_ngen_bridge) target_sources(ngen_bmi_protocols PRIVATE diff --git a/src/utilities/logging/CMakeLists.txt b/src/utilities/logging/CMakeLists.txt index 0080d26680..c6745c01ea 100644 --- a/src/utilities/logging/CMakeLists.txt +++ b/src/utilities/logging/CMakeLists.txt @@ -2,7 +2,7 @@ add_library(logging logging_utils.cpp) add_library(NGen::logging ALIAS logging) target_include_directories(logging PUBLIC ${PROJECT_SOURCE_DIR}/include/utilities) -target_link_libraries(logging PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(logging PUBLIC ewts::ewts_ngen_bridge) target_link_libraries(logging PUBLIC Boost::boost diff --git a/src/utilities/mdframe/CMakeLists.txt b/src/utilities/mdframe/CMakeLists.txt index 0073046398..a254268f54 100644 --- a/src/utilities/mdframe/CMakeLists.txt +++ b/src/utilities/mdframe/CMakeLists.txt @@ -7,7 +7,7 @@ NGen::config_header NGen::mdarray NGen::logging ) -target_link_libraries(mdframe PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(mdframe PUBLIC ewts::ewts_ngen_bridge) if(NGEN_WITH_NETCDF) target_link_libraries(mdframe PUBLIC NetCDF) diff --git a/src/utilities/python/CMakeLists.txt b/src/utilities/python/CMakeLists.txt index 2347625dd2..1779770a8b 100644 --- a/src/utilities/python/CMakeLists.txt +++ b/src/utilities/python/CMakeLists.txt @@ -2,7 +2,7 @@ add_library(ngen_python InterpreterUtil.cpp) add_library(NGen::python ALIAS ngen_python) find_package(ewts CONFIG REQUIRED) # if not already in scope -target_link_libraries(ngen_python PRIVATE ewts::ewts_ngen_bridge) +target_link_libraries(ngen_python PUBLIC ewts::ewts_ngen_bridge) target_include_directories(ngen_python PUBLIC ${PROJECT_SOURCE_DIR}/include/) From c10009dd3ac96f81a987bd877e7230b2ea40b3a2 Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Fri, 13 Mar 2026 13:03:32 -0700 Subject: [PATCH 11/23] Fix ewts-verify stage to handle lib64 install path --- Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1a5a2c1a8b..27ddbca009 100644 --- a/Dockerfile +++ b/Dockerfile @@ -597,9 +597,12 @@ RUN set -eux && \ echo "" && \ \ # ── 1. Check EWTS install tree exists ── + # cmake may install to lib/ or lib64/ depending on the platform echo "--- 1. EWTS install tree ---" && \ echo "EWTS_PREFIX=${EWTS_PREFIX}" && \ - ls -la ${EWTS_PREFIX}/lib/ && \ + EWTS_LIBDIR=$(find ${EWTS_PREFIX} -maxdepth 1 -type d \( -name lib -o -name lib64 \) | head -1) && \ + echo "EWTS_LIBDIR=${EWTS_LIBDIR}" && \ + ls -la ${EWTS_LIBDIR}/ && \ echo "" && \ \ # ── 2. Check EWTS shared libraries are present ── @@ -610,7 +613,7 @@ RUN set -eux && \ \ # ── 3. Verify cmake package config is findable ── echo "--- 3. EWTS cmake config ---" && \ - ls ${EWTS_PREFIX}/lib/cmake/ewts/ewtsConfig.cmake && \ + find ${EWTS_PREFIX} -name 'ewtsConfig.cmake' | head -1 | xargs ls && \ echo "ewtsConfig.cmake found OK" && \ echo "" && \ \ From 1ca5362a92803d42da5e10eea6e473be59306989 Mon Sep 17 00:00:00 2001 From: "Miguel.Pena" Date: Fri, 13 Mar 2026 15:25:58 -0700 Subject: [PATCH 12/23] removed ewts verification stage --- Dockerfile | 105 ----------------------------------------------------- 1 file changed, 105 deletions(-) diff --git a/Dockerfile b/Dockerfile index 27ddbca009..c08f6cb206 100644 --- a/Dockerfile +++ b/Dockerfile @@ -572,108 +572,3 @@ SHELL ["/bin/bash", "-c"] ENTRYPOINT [ "/ngen-app/bin/run-ngen.sh" ] CMD [ "--info" ] - -############################## -# Stage: EWTS Verification (optional, build with --target ewts-verify) -############################## -# Usage: -# docker build --target ewts-verify -t ngen-ewts-verify -f Dockerfile . -# docker run --rm ngen-ewts-verify -# -# This stage runs a comprehensive check that EWTS is properly wired into -# ngen and all submodules. It does NOT run ngen itself — just verifies -# that libraries, headers, cmake config, Python packages, and shared -# object linkages are all in place. -############################## -FROM submodules AS ewts-verify - -SHELL [ "/usr/bin/scl", "enable", "gcc-toolset-10" ] - -RUN set -eux && \ - echo "" && \ - echo "============================================" && \ - echo " EWTS Integration Verification" && \ - echo "============================================" && \ - echo "" && \ - \ - # ── 1. Check EWTS install tree exists ── - # cmake may install to lib/ or lib64/ depending on the platform - echo "--- 1. EWTS install tree ---" && \ - echo "EWTS_PREFIX=${EWTS_PREFIX}" && \ - EWTS_LIBDIR=$(find ${EWTS_PREFIX} -maxdepth 1 -type d \( -name lib -o -name lib64 \) | head -1) && \ - echo "EWTS_LIBDIR=${EWTS_LIBDIR}" && \ - ls -la ${EWTS_LIBDIR}/ && \ - echo "" && \ - \ - # ── 2. Check EWTS shared libraries are present ── - echo "--- 2. EWTS shared libraries ---" && \ - echo "Looking for libewts_*.so files..." && \ - find ${EWTS_PREFIX} -name '*.so' -o -name '*.so.*' | sort && \ - echo "" && \ - \ - # ── 3. Verify cmake package config is findable ── - echo "--- 3. EWTS cmake config ---" && \ - find ${EWTS_PREFIX} -name 'ewtsConfig.cmake' | head -1 | xargs ls && \ - echo "ewtsConfig.cmake found OK" && \ - echo "" && \ - \ - # ── 4. Check Fortran .mod files (needed by snow17, sac-sma, etc.) ── - echo "--- 4. EWTS Fortran .mod files ---" && \ - find ${EWTS_PREFIX} -name '*.mod' | sort && \ - echo "" && \ - \ - # ── 5. Verify ngen executable exists and links to EWTS ── - echo "--- 5. ngen binary – EWTS linkage ---" && \ - NGEN_BIN=/ngen-app/ngen/cmake_build/ngen && \ - file "$NGEN_BIN" && \ - echo "Checking ldd for ewts symbols..." && \ - ldd "$NGEN_BIN" | grep -i ewts && \ - echo "ngen links to EWTS OK" && \ - echo "" && \ - \ - # ── 6. Check each submodule .so links to EWTS ── - echo "--- 6. Submodule .so files – EWTS linkage ---" && \ - for so in \ - extern/LASAM/cmake_build/*.so \ - extern/snow17/cmake_build/*.so \ - extern/sac-sma/cmake_build/*.so \ - extern/SoilMoistureProfiles/cmake_build/*.so \ - extern/SoilFreezeThaw/cmake_build/*.so \ - extern/ueb-bmi/cmake_build/*.so; \ - do \ - if [ -f "$so" ]; then \ - echo "Checking: $so"; \ - if ldd "$so" | grep -qi ewts; then \ - echo " ✓ links to EWTS"; \ - else \ - echo " ⚠ WARNING: no EWTS linkage found (may be expected if submodule doesn't use EWTS directly)"; \ - fi; \ - fi; \ - done && \ - echo "" && \ - \ - # ── 7. Verify EWTS Python package is importable ── - echo "--- 7. EWTS Python package ---" && \ - python3 -c "import ewts; print(f'ewts version: {ewts.__version__}')" && \ - python3 -c "import ewts; print(f'EWTS module keys available: {dir(ewts)}')" && \ - echo "Python ewts import OK" && \ - echo "" && \ - \ - # ── 8. Verify lstm can import ewts (this is the runtime dependency) ── - echo "--- 8. lstm → ewts Python integration ---" && \ - python3 -c "from lstm.bmi_lstm import *; print('lstm.bmi_lstm imports OK (includes ewts)')" && \ - echo "" && \ - \ - # ── 9. Show git provenance ── - echo "--- 9. Git provenance (nwm-ewts entry) ---" && \ - GIT_INFO=$(find /ngen-app -name '*_git_info.json' | head -1) && \ - if [ -n "$GIT_INFO" ]; then \ - jq '."nwm-ewts"' "$GIT_INFO"; \ - else \ - echo "No git_info.json found"; \ - fi && \ - echo "" && \ - echo "============================================" && \ - echo " EWTS verification complete" && \ - echo "============================================" - From d646a590c184d0beee28b83f0576e0e2a39d398e Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Mon, 16 Mar 2026 17:05:39 -0700 Subject: [PATCH 13/23] Update submod refs for ewts python bind --- extern/topoflow-glacier | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/topoflow-glacier b/extern/topoflow-glacier index 97b455dac6..0351115301 160000 --- a/extern/topoflow-glacier +++ b/extern/topoflow-glacier @@ -1 +1 @@ -Subproject commit 97b455dac674dad3f63a0dcb8ac1f4a3ef3a1be1 +Subproject commit 03511153013eed900704cc1fae4b10a10cd32b55 From 94de310bdecab7031f77a9ae493dfdeb1345d6de Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Mon, 16 Mar 2026 19:41:53 -0700 Subject: [PATCH 14/23] added ewts build arguments to ngwpc cicd file --- .github/workflows/ngwpc-cicd.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ngwpc-cicd.yml b/.github/workflows/ngwpc-cicd.yml index 87ed350453..0b2a3019a8 100644 --- a/.github/workflows/ngwpc-cicd.yml +++ b/.github/workflows/ngwpc-cicd.yml @@ -23,6 +23,14 @@ on: description: 'NGEN_FORCING_IMAGE_TAG' required: false type: string + EWTS_ORG: + description: 'EWTS_ORG' + required: false + type: string + EWTS_REF: + description: 'EWTS_REF' + required: false + type: string permissions: contents: read @@ -241,6 +249,8 @@ jobs: build-args: | ORG=${{ needs.setup.outputs.org }} NGEN_FORCING_IMAGE_TAG=${{ inputs.NGEN_FORCING_IMAGE_TAG || 'latest' }} + EWTS_ORG=${{ inputs.EWTS_ORG || 'NGWPC' }} + EWTS_REF=${{ inputs.EWTS_REF || 'development' }} IMAGE_SOURCE=https://github.com/${{ github.repository }} IMAGE_VENDOR=${{ github.repository_owner }} IMAGE_VERSION=${{ needs.setup.outputs.clean_ref }} From d6d1e45e14cf33bcf1d11a4560b03b22dc939b0f Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Tue, 17 Mar 2026 15:12:00 -0700 Subject: [PATCH 15/23] added ewts to ngwpc cicd file in codeql step --- .github/workflows/ngwpc-cicd.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ngwpc-cicd.yml b/.github/workflows/ngwpc-cicd.yml index 0b2a3019a8..e09e31c868 100644 --- a/.github/workflows/ngwpc-cicd.yml +++ b/.github/workflows/ngwpc-cicd.yml @@ -180,12 +180,34 @@ jobs: echo "BOOST_ROOT=${PREFIX}" >> "$GITHUB_ENV" echo "CMAKE_PREFIX_PATH=${PREFIX}:${CMAKE_PREFIX_PATH:-}" >> "$GITHUB_ENV" + - name: Build and install EWTS + run: | + set -euo pipefail + EWTS_ORG="${{ inputs.EWTS_ORG || 'NGWPC' }}" + EWTS_REF="${{ inputs.EWTS_REF || 'development' }}" + EWTS_PREFIX=/opt/ewts + + git clone --depth 1 -b "${EWTS_REF}" \ + "https://github.com/${EWTS_ORG}/nwm-ewts.git" /tmp/nwm-ewts \ + || (git clone "https://github.com/${EWTS_ORG}/nwm-ewts.git" /tmp/nwm-ewts && \ + cd /tmp/nwm-ewts && git checkout "${EWTS_REF}") + + cd /tmp/nwm-ewts + cmake -S . -B cmake_build \ + -DCMAKE_BUILD_TYPE=Release \ + -DEWTS_WITH_NGEN=ON \ + -DEWTS_BUILD_SHARED=ON + cmake --build cmake_build -j "$(nproc)" + sudo cmake --install cmake_build --prefix "${EWTS_PREFIX}" + + echo "EWTS_PREFIX=${EWTS_PREFIX}" >> "$GITHUB_ENV" + - name: Build C++ code env: PYTHONPATH: ${{ env.PYTHONPATH }} run: | cmake -B cmake_build -S . \ - -DCMAKE_PREFIX_PATH="${BOOST_ROOT}" \ + -DCMAKE_PREFIX_PATH="${EWTS_PREFIX};${BOOST_ROOT}" \ -DBoost_NO_SYSTEM_PATHS=ON \ -DBOOST_ROOT="${BOOST_ROOT}" \ -DPYTHON_EXECUTABLE=$(which python3) \ From f1be7d57582d3ce2b03ea9c5c1cc5e0d9738f699 Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Tue, 17 Mar 2026 15:29:25 -0700 Subject: [PATCH 16/23] cicd updates --- .github/workflows/ngwpc-cicd.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ngwpc-cicd.yml b/.github/workflows/ngwpc-cicd.yml index e09e31c868..7824b7f1ab 100644 --- a/.github/workflows/ngwpc-cicd.yml +++ b/.github/workflows/ngwpc-cicd.yml @@ -196,7 +196,10 @@ jobs: cmake -S . -B cmake_build \ -DCMAKE_BUILD_TYPE=Release \ -DEWTS_WITH_NGEN=ON \ - -DEWTS_BUILD_SHARED=ON + -DEWTS_BUILD_SHARED=ON \ + -DBOOST_ROOT="${BOOST_ROOT}" \ + -DBoost_NO_SYSTEM_PATHS=ON \ + -DCMAKE_PREFIX_PATH="${BOOST_ROOT}" cmake --build cmake_build -j "$(nproc)" sudo cmake --install cmake_build --prefix "${EWTS_PREFIX}" From 21a25b43dc299b89468eac33a53a0a2b2eeef8ed Mon Sep 17 00:00:00 2001 From: Miguel Pena Date: Tue, 17 Mar 2026 16:02:55 -0700 Subject: [PATCH 17/23] cicd updates --- .github/workflows/ngwpc-cicd.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ngwpc-cicd.yml b/.github/workflows/ngwpc-cicd.yml index 7824b7f1ab..03791a9e98 100644 --- a/.github/workflows/ngwpc-cicd.yml +++ b/.github/workflows/ngwpc-cicd.yml @@ -157,7 +157,7 @@ jobs: BOOST_VER=1.86.0 BOOST_UNDERSCORE=1_86_0 - PREFIX=/opt/boost-${BOOST_VER} + PREFIX=/usr/local curl -fL --retry 10 --retry-delay 2 --max-time 600 \ -o /tmp/boost.tar.bz2 \ @@ -169,7 +169,7 @@ jobs: ./bootstrap.sh --prefix="${PREFIX}" # Build Boost libraries - ./b2 -j"$(nproc)" install \ + sudo ./b2 -j"$(nproc)" install \ --with-system \ --with-filesystem \ --with-program_options \ @@ -178,7 +178,6 @@ jobs: --with-date_time \ --with-serialization echo "BOOST_ROOT=${PREFIX}" >> "$GITHUB_ENV" - echo "CMAKE_PREFIX_PATH=${PREFIX}:${CMAKE_PREFIX_PATH:-}" >> "$GITHUB_ENV" - name: Build and install EWTS run: | @@ -196,10 +195,7 @@ jobs: cmake -S . -B cmake_build \ -DCMAKE_BUILD_TYPE=Release \ -DEWTS_WITH_NGEN=ON \ - -DEWTS_BUILD_SHARED=ON \ - -DBOOST_ROOT="${BOOST_ROOT}" \ - -DBoost_NO_SYSTEM_PATHS=ON \ - -DCMAKE_PREFIX_PATH="${BOOST_ROOT}" + -DEWTS_BUILD_SHARED=ON cmake --build cmake_build -j "$(nproc)" sudo cmake --install cmake_build --prefix "${EWTS_PREFIX}" From 0da60476b669e12c7767275f6cd21c2fe8524401 Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Wed, 18 Mar 2026 10:44:18 -0700 Subject: [PATCH 18/23] Add ewts to ngen state saving. --- src/forcing/NetCDFPerFeatureDataProvider.cpp | 20 +++++++++++++++---- src/state_save_restore/CMakeLists.txt | 2 ++ src/state_save_restore/File_Per_Unit.cpp | 2 +- src/state_save_restore/State_Save_Restore.cpp | 17 ++++++++++++---- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/forcing/NetCDFPerFeatureDataProvider.cpp b/src/forcing/NetCDFPerFeatureDataProvider.cpp index cc41abb7dd..9940521252 100644 --- a/src/forcing/NetCDFPerFeatureDataProvider.cpp +++ b/src/forcing/NetCDFPerFeatureDataProvider.cpp @@ -155,7 +155,10 @@ NetCDFPerFeatureDataProvider::NetCDFPerFeatureDataProvider(std::string input_pat // files generated by the forcings engine have just (time) if (dim_count == 2) { if (time_var.getDim(0).getName() != "catchment-id" || time_var.getDim(1).getName() != "time") { - Logger::logMsgAndThrowError("In NetCDF file '" + input_path + "', 'Time' variable dimensions don't match expectations"); + std::string message = "In NetCDF file '" + input_path + "', 'Time' variable dimensions don't match expectations"; + std::string throw_msg; throw_msg.assign(message); + LOG(throw_msg, LogLevel::WARNING); + throw std::runtime_error(throw_msg); } time_var.getVar({0ul, 0ul}, {1ul, num_times}, raw_time.data()); } else if (dim_count == 1) { @@ -218,7 +221,10 @@ NetCDFPerFeatureDataProvider::NetCDFPerFeatureDataProvider(std::string input_pat time_unit = TIME_NANOSECONDS; time_scale_factor = 1.0e-9; } else { - Logger::logMsgAndThrowError("In NetCDF file '" + input_path + "', time unit '" + time_base_unit + "' could not be converted"); + std::string message = "In NetCDF file '" + input_path + "', time unit '" + time_base_unit + "' could not be converted"; + std::string throw_msg; throw_msg.assign(message); + LOG(throw_msg, LogLevel::WARNING); + throw std::runtime_error(throw_msg); } time_vals.resize(raw_time.size()); @@ -373,7 +379,10 @@ double NetCDFPerFeatureDataProvider::get_value(const CatchmentAggrDataSelector& int dim_time, dim_catchment; if (dims.size() != 2) { - Logger::logMsgAndThrowError("Variable dimension count isn't 2"); + std::string message = "Variable dimension count isn't 2"; + std::string throw_msg; throw_msg.assign(message); + LOG(throw_msg, LogLevel::WARNING); + throw std::runtime_error(throw_msg); } if (dims[0].getName() == "time" && dims[1].getName() == "catchment-id") { // Forcings Engine NetCDF output case @@ -384,7 +393,10 @@ double NetCDFPerFeatureDataProvider::get_value(const CatchmentAggrDataSelector& dim_time = 1; dim_catchment = 0; } else { - Logger::logMsgAndThrowError("Variable dimensions aren't 'time' and 'catchment-id'"); + std::string message = "Variable dimensions aren't 'time' and 'catchment-id'"; + std::string throw_msg; throw_msg.assign(message); + LOG(throw_msg, LogLevel::WARNING); + throw std::runtime_error(throw_msg); } size_t time_dim_size = dims[dim_time].getSize(); diff --git a/src/state_save_restore/CMakeLists.txt b/src/state_save_restore/CMakeLists.txt index cde58c6073..b068d6d4ab 100644 --- a/src/state_save_restore/CMakeLists.txt +++ b/src/state_save_restore/CMakeLists.txt @@ -7,9 +7,11 @@ target_link_libraries(state_save_restore PUBLIC Boost::boost # Headers-only Boost Boost::system Boost::filesystem + ewts::ewts_ngen_bridge ) target_include_directories(state_save_restore PUBLIC ${PROJECT_SOURCE_DIR}/include ) + diff --git a/src/state_save_restore/File_Per_Unit.cpp b/src/state_save_restore/File_Per_Unit.cpp index 80bce91418..6d355d4c93 100644 --- a/src/state_save_restore/File_Per_Unit.cpp +++ b/src/state_save_restore/File_Per_Unit.cpp @@ -1,5 +1,5 @@ #include -#include +#include "ewts_ngen/logger.hpp" #if __has_include() && __cpp_lib_filesystem >= 201703L #include diff --git a/src/state_save_restore/State_Save_Restore.cpp b/src/state_save_restore/State_Save_Restore.cpp index ee3f5ae3c9..4a54b1a21c 100644 --- a/src/state_save_restore/State_Save_Restore.cpp +++ b/src/state_save_restore/State_Save_Restore.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include "ewts_ngen/logger.hpp" #include #include @@ -98,13 +98,19 @@ State_Save_Config::instance::instance(std::string const& direction, std::string } else if (direction == "load") { direction_ = State_Save_Direction::Load; } else { - Logger::logMsgAndThrowError("Unrecognized state saving direction '" + direction + "'"); + std::string message = "Unrecognized state saving direction '" + direction + "'"; + std::string throw_msg; throw_msg.assign(message); + LOG(throw_msg, LogLevel::WARNING); + throw std::runtime_error(throw_msg); } if (mechanism == "FilePerUnit") { mechanism_ = State_Save_Mechanism::FilePerUnit; } else { - Logger::logMsgAndThrowError("Unrecognized state saving mechanism '" + mechanism + "'"); + std::string message = "Unrecognized state saving mechanism '" + mechanism + "'"; + std::string throw_msg; throw_msg.assign(message); + LOG(throw_msg, LogLevel::WARNING); + throw std::runtime_error(throw_msg); } if (timing == "EndOfRun") { @@ -114,7 +120,10 @@ State_Save_Config::instance::instance(std::string const& direction, std::string } else if (timing == "StartOfRun") { timing_ = State_Save_When::StartOfRun; } else { - Logger::logMsgAndThrowError("Unrecognized state saving timing '" + timing + "'"); + std::string message = "Unrecognized state saving timing '" + timing + "'"; + std::string throw_msg; throw_msg.assign(message); + LOG(throw_msg, LogLevel::WARNING); + throw std::runtime_error(throw_msg); } } From 799ab144c5632135d35a88d7be5a4df09056da6c Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Wed, 18 Mar 2026 13:47:27 -0700 Subject: [PATCH 19/23] Update lstm submod ref --- extern/lstm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/lstm b/extern/lstm index 8e90c914ec..88bffb9f60 160000 --- a/extern/lstm +++ b/extern/lstm @@ -1 +1 @@ -Subproject commit 8e90c914ec0deeccf087dc3fabf3132c50501dfa +Subproject commit 88bffb9f60ca2806099f2505d6e3518180b100e4 From 1076cbcdf870a664450a3bd89fcbaac6e044c3d1 Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Thu, 19 Mar 2026 15:01:33 -0700 Subject: [PATCH 20/23] Updates SFT submod ref --- extern/SoilFreezeThaw/SoilFreezeThaw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/SoilFreezeThaw/SoilFreezeThaw b/extern/SoilFreezeThaw/SoilFreezeThaw index e2b5246e29..52685ae338 160000 --- a/extern/SoilFreezeThaw/SoilFreezeThaw +++ b/extern/SoilFreezeThaw/SoilFreezeThaw @@ -1 +1 @@ -Subproject commit e2b5246e29721a965e8999859316ed19e3ee8414 +Subproject commit 52685ae3381599c1583fe6e1cc326a96a75c8478 From dd5e0edc30324941274ce2758b473631d914d463 Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Mon, 23 Mar 2026 10:57:11 -0700 Subject: [PATCH 21/23] Updaate SMP submod ref for sac-sma recent merge to development --- extern/SoilMoistureProfiles/SoilMoistureProfiles | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/SoilMoistureProfiles/SoilMoistureProfiles b/extern/SoilMoistureProfiles/SoilMoistureProfiles index 53b6f55ad9..f4dc944171 160000 --- a/extern/SoilMoistureProfiles/SoilMoistureProfiles +++ b/extern/SoilMoistureProfiles/SoilMoistureProfiles @@ -1 +1 @@ -Subproject commit 53b6f55ad90087665d541732e184c97ce4f426c0 +Subproject commit f4dc944171ea2f65416a4739d9bad58ad3953b19 From 228b627c333fe9064c66bebddf2d355c39e1d883 Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Mon, 23 Mar 2026 21:23:07 -0700 Subject: [PATCH 22/23] Update sac-sma submod ref --- extern/sac-sma/sac-sma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/sac-sma/sac-sma b/extern/sac-sma/sac-sma index 08e4e4b929..bf3efa7d85 160000 --- a/extern/sac-sma/sac-sma +++ b/extern/sac-sma/sac-sma @@ -1 +1 @@ -Subproject commit 08e4e4b929083603ade1fc9c45141078d3a6649a +Subproject commit bf3efa7d85cefb7a2d5688e98ca9e0d8c14410da From 72469845155c3889df7cac8506db18a2833c4eac Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Tue, 24 Mar 2026 21:15:26 -0700 Subject: [PATCH 23/23] Update submod refs for ewts branches merged to development --- extern/LASAM | 2 +- extern/SoilFreezeThaw/SoilFreezeThaw | 2 +- extern/SoilMoistureProfiles/SoilMoistureProfiles | 2 +- extern/cfe/cfe | 2 +- extern/evapotranspiration/evapotranspiration | 2 +- extern/lstm | 2 +- extern/noah-owp-modular/noah-owp-modular | 2 +- extern/sac-sma/sac-sma | 2 +- extern/snow17 | 2 +- extern/t-route | 2 +- extern/topmodel/topmodel | 2 +- extern/topoflow-glacier | 2 +- extern/ueb-bmi | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/extern/LASAM b/extern/LASAM index 1a659e98b0..e3411b3318 160000 --- a/extern/LASAM +++ b/extern/LASAM @@ -1 +1 @@ -Subproject commit 1a659e98b0eee7630afa592a8bbc6714864ee971 +Subproject commit e3411b331854303f7344db795f3afc1e3269810a diff --git a/extern/SoilFreezeThaw/SoilFreezeThaw b/extern/SoilFreezeThaw/SoilFreezeThaw index 52685ae338..815a970af3 160000 --- a/extern/SoilFreezeThaw/SoilFreezeThaw +++ b/extern/SoilFreezeThaw/SoilFreezeThaw @@ -1 +1 @@ -Subproject commit 52685ae3381599c1583fe6e1cc326a96a75c8478 +Subproject commit 815a970af30e407785bc767a4a5f5dffa2708eb5 diff --git a/extern/SoilMoistureProfiles/SoilMoistureProfiles b/extern/SoilMoistureProfiles/SoilMoistureProfiles index f4dc944171..d29f268223 160000 --- a/extern/SoilMoistureProfiles/SoilMoistureProfiles +++ b/extern/SoilMoistureProfiles/SoilMoistureProfiles @@ -1 +1 @@ -Subproject commit f4dc944171ea2f65416a4739d9bad58ad3953b19 +Subproject commit d29f26822391740549642a9818065ea296688b2e diff --git a/extern/cfe/cfe b/extern/cfe/cfe index 9581344a05..1e42e47d9e 160000 --- a/extern/cfe/cfe +++ b/extern/cfe/cfe @@ -1 +1 @@ -Subproject commit 9581344a057ab18a1f954c484999c2647cca2443 +Subproject commit 1e42e47d9edae96e337f0379f71deb4638cb45a6 diff --git a/extern/evapotranspiration/evapotranspiration b/extern/evapotranspiration/evapotranspiration index 456fc262a9..fa10f12c46 160000 --- a/extern/evapotranspiration/evapotranspiration +++ b/extern/evapotranspiration/evapotranspiration @@ -1 +1 @@ -Subproject commit 456fc262a9e78386b788b24f24c2b6c1a2ab7cf0 +Subproject commit fa10f12c46bf6cda028f843292cac7b26d8b6ac0 diff --git a/extern/lstm b/extern/lstm index 88bffb9f60..b3ccaeb458 160000 --- a/extern/lstm +++ b/extern/lstm @@ -1 +1 @@ -Subproject commit 88bffb9f60ca2806099f2505d6e3518180b100e4 +Subproject commit b3ccaeb45897693d7ebb05dc38788a5766a6921e diff --git a/extern/noah-owp-modular/noah-owp-modular b/extern/noah-owp-modular/noah-owp-modular index 6f14b6026a..029fc17a35 160000 --- a/extern/noah-owp-modular/noah-owp-modular +++ b/extern/noah-owp-modular/noah-owp-modular @@ -1 +1 @@ -Subproject commit 6f14b6026a0f65f6e1e48d4acd399d6f0b5427df +Subproject commit 029fc17a3552356c1a62afd31bcf0a00f70948e4 diff --git a/extern/sac-sma/sac-sma b/extern/sac-sma/sac-sma index bf3efa7d85..ca16b6e705 160000 --- a/extern/sac-sma/sac-sma +++ b/extern/sac-sma/sac-sma @@ -1 +1 @@ -Subproject commit bf3efa7d85cefb7a2d5688e98ca9e0d8c14410da +Subproject commit ca16b6e705129913a5bca897428faf8d9a7561ee diff --git a/extern/snow17 b/extern/snow17 index 1eef00157c..e73f49ba8d 160000 --- a/extern/snow17 +++ b/extern/snow17 @@ -1 +1 @@ -Subproject commit 1eef00157c6c66ffcc1dd9c34ef53f44a9e89a8e +Subproject commit e73f49ba8d3180af48cfc0726bebc7d08a792035 diff --git a/extern/t-route b/extern/t-route index da36292587..e2f7d5dcb7 160000 --- a/extern/t-route +++ b/extern/t-route @@ -1 +1 @@ -Subproject commit da36292587df8a3aafe62b73004022851a56ea8a +Subproject commit e2f7d5dcb7efcc684523f759b438ad250543ccdd diff --git a/extern/topmodel/topmodel b/extern/topmodel/topmodel index 1f08e9822b..e608a31687 160000 --- a/extern/topmodel/topmodel +++ b/extern/topmodel/topmodel @@ -1 +1 @@ -Subproject commit 1f08e9822ba3eead7c70257949337f9fa3bda2d3 +Subproject commit e608a31687c00835a5484d83df277a6587989ff1 diff --git a/extern/topoflow-glacier b/extern/topoflow-glacier index 0351115301..4da1b75b8f 160000 --- a/extern/topoflow-glacier +++ b/extern/topoflow-glacier @@ -1 +1 @@ -Subproject commit 03511153013eed900704cc1fae4b10a10cd32b55 +Subproject commit 4da1b75b8fe0fe98a247a4a23f7054089c30a1e5 diff --git a/extern/ueb-bmi b/extern/ueb-bmi index e47f8a0846..cb18da2a41 160000 --- a/extern/ueb-bmi +++ b/extern/ueb-bmi @@ -1 +1 @@ -Subproject commit e47f8a0846c988cc732ea295bd116653ebac0b89 +Subproject commit cb18da2a411f6e6f43c4cc13845c1c2837a1346a