From 42cc4efec10e52662e8602f03173788429164405 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:42:52 +0000 Subject: [PATCH 1/4] Rebuild WW4 compile system with pure CMake and local clone builds - Remove ww4_setup script, ww4_compile_config.yaml, and ww4_local_config.cmake - Add WW4_ENABLE_TESTING CMake option to skip GoogleTest and test targets for operational builds - Add support for local offline dependencies in externals/ directory - Update documentation for Linux and macOS compile environment setup Co-authored-by: HendrikTolman-NOAA <237313876+HendrikTolman-NOAA@users.noreply.github.com> --- .gitignore | 3 +- CMakeLists.txt | 177 +++++++++++++----------- README.md | 59 +++++++- externals/README.md | 8 +- templates/README.md | 24 ++-- templates/ww4_compile_config.yaml | 27 ---- tools/README.md | 52 ++----- tools/ww4_setup | 222 ------------------------------ 8 files changed, 187 insertions(+), 385 deletions(-) delete mode 100644 templates/ww4_compile_config.yaml delete mode 100755 tools/ww4_setup diff --git a/.gitignore b/.gitignore index 46925b9..62d8d22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ -build/ +build*/ exe/ *.o ww4_log.txt -ww4_local_config.cmake verify_ww4.py test_format test_format.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 72402b0..9409abb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,9 @@ #+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + # #@file CMakeLists.txt -#@brief CMake build configuration for WAVEWATCH IV (WW4) Utilities. -#@details This file defines the project structure, dependencies (GTest), -#and build targets for the WW4_Utils library and its tests. +#@brief CMake build configuration for WAVEWATCH IV (WW4). +#@details This file defines the project structure, dependencies (yaml-cpp, GTest), +#and build targets for the WW4 libraries, standalone application, and tests. # #@copyright © 2026 National Weather Service, National Oceanic and Atmospheric #Administration. WAVEWATCH IV (TM) and WW4 (TM) are trademarks @@ -14,13 +14,10 @@ #@author Main Author(s) : Aldgisl (AI Persona), Hendrik L. Tolman #@author Contributors : Jules (Agentic AI), Kit Stokes, Jessica Meixner #@date Initial : 2026-07-09 -#@date Last update : 2026-07-13 +#@date Last update : 2026-08-21 cmake_minimum_required(VERSION 3.25) -# Optional local configuration file generated by the setup tool. -include(${CMAKE_SOURCE_DIR}/ww4_local_config.cmake OPTIONAL) - project(WW4_Utils VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -30,9 +27,10 @@ set(CMAKE_CXX_EXTENSIONS OFF) # Set default output directory for executables set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -# Options for strict warnings and sanitizers +# Options for strict warnings, sanitizers, and testing option(WW4_STRICT_WARNINGS "Enable strict compiler warnings and treat as errors" OFF) option(WW4_USE_SANITIZERS "Enable address and undefined behavior sanitizers" OFF) +option(WW4_ENABLE_TESTING "Enable WAVEWATCH IV test suite" ON) # Define common compile options for WW4 targets set(WW4_COMMON_COMPILE_OPTIONS "") @@ -61,43 +59,27 @@ else() endif() endif() -# Google Test include(FetchContent) -FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - SYSTEM -) # yaml-cpp for configuration parsing -FetchContent_Declare( - yaml-cpp - URL https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.8.0.zip - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - SYSTEM -) - -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - -FetchContent_MakeAvailable(googletest yaml-cpp) - -# Suppress warnings from GTest itself for Clang/IntelLLVM during compilation -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") - set(GTEST_SUPPRESS_FLAGS -Wno-unknown-warning-option -Wno-character-conversion) - if(CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") - list(APPEND GTEST_SUPPRESS_FLAGS -diag-disable=10430) - endif() - foreach(target gtest gtest_main gmock gmock_main) - if(TARGET ${target}) - target_compile_options(${target} INTERFACE ${GTEST_SUPPRESS_FLAGS}) - target_compile_options(${target} PRIVATE ${GTEST_SUPPRESS_FLAGS}) - endif() - endforeach() +if(EXISTS "${CMAKE_SOURCE_DIR}/externals/yaml-cpp/CMakeLists.txt") + FetchContent_Declare( + yaml-cpp + SOURCE_DIR "${CMAKE_SOURCE_DIR}/externals/yaml-cpp" + SYSTEM + ) +else() + FetchContent_Declare( + yaml-cpp + URL https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.8.0.zip + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SYSTEM + ) endif() -# Library +FetchContent_MakeAvailable(yaml-cpp) + +# Library: ww4_utils add_library( ww4_utils src/ww4_utils/time_management.cpp @@ -115,6 +97,7 @@ target_link_libraries(ww4_utils PRIVATE yaml-cpp) target_compile_options(ww4_utils PRIVATE ${WW4_COMMON_COMPILE_OPTIONS}) target_link_options(ww4_utils PRIVATE ${WW4_COMMON_LINK_OPTIONS}) +# Library: ww4_core add_library( ww4_core src/ww4_core/w4core_init.cpp @@ -126,7 +109,7 @@ target_link_libraries(ww4_core PUBLIC ww4_utils) target_compile_options(ww4_core PRIVATE ${WW4_COMMON_COMPILE_OPTIONS}) target_link_options(ww4_core PRIVATE ${WW4_COMMON_LINK_OPTIONS}) -# Executables +# Executable: ww4_standalone add_executable(ww4_standalone src/ww4_progs/ww4_standalone.cpp) target_link_libraries(ww4_standalone PRIVATE ww4_core ww4_utils) target_compile_options(ww4_standalone PRIVATE ${WW4_COMMON_COMPILE_OPTIONS}) @@ -138,41 +121,77 @@ set_target_properties( PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/exe ) -# Tests -enable_testing() -include(GoogleTest) - -# Helper function to define tests -function(ww4_add_test test_name source_file dependency_lib) - add_executable(${test_name} ${source_file}) - target_link_libraries(${test_name} PRIVATE ${dependency_lib} GTest::gtest_main) - target_compile_options(${test_name} PRIVATE ${WW4_COMMON_COMPILE_OPTIONS}) - target_link_options(${test_name} PRIVATE ${WW4_COMMON_LINK_OPTIONS}) - gtest_discover_tests(${test_name}) -endfunction() - -# Utils tests -ww4_add_test(test_time_management tests/ww4_utils/L1_test_time_management.cpp ww4_utils) -ww4_add_test(test_memory_utils tests/ww4_utils/L1_test_memory_utils.cpp ww4_utils) -ww4_add_test(test_ww4_std_out tests/ww4_utils/L1_test_ww4_std_out.cpp ww4_utils) -ww4_add_test(test_ww4_logfile tests/ww4_utils/L1_test_ww4_logfile.cpp ww4_utils) -ww4_add_test(test_ww4_standalone_config tests/ww4_utils/L1_test_ww4_standalone_config.cpp ww4_utils) -ww4_add_test(test_ww4_run_config tests/ww4_utils/L1_test_ww4_run_config.cpp ww4_utils) -ww4_add_test(test_ww4_input_utils tests/ww4_utils/L1_test_ww4_input_utils.cpp ww4_utils) -ww4_add_test(test_ww4_output_utils tests/ww4_utils/L1_test_ww4_output_utils.cpp ww4_utils) -ww4_add_test(test_ww4_service tests/ww4_utils/L1_test_ww4_service.cpp ww4_utils) -ww4_add_test(test_ww4_constants tests/ww4_utils/L1_test_ww4_constants.cpp ww4_utils) - -# Core tests -ww4_add_test(test_ww4_standalone tests/ww4_core/L1_test_ww4_standalone.cpp ww4_core) -target_compile_definitions(test_ww4_standalone PRIVATE STANDALONE_EXE_PATH="${CMAKE_SOURCE_DIR}/exe/ww4_standalone") -ww4_add_test(test_w4core_init tests/ww4_core/L1_test_w4core_init.cpp ww4_core) -ww4_add_test(test_w4core_wave tests/ww4_core/L1_test_w4core_wave.cpp ww4_core) -ww4_add_test(test_w4core_finalize tests/ww4_core/L1_test_w4core_finalize.cpp ww4_core) -ww4_add_test(test_w4core_time tests/ww4_core/L2_test_w4core_time.cpp ww4_core) -ww4_add_test(test_w4core_wave_output tests/ww4_core/L2_test_w4core_wave_output.cpp ww4_core) -ww4_add_test(test_w4core_zero_step tests/ww4_core/L2_test_w4core_zero_step.cpp ww4_core) -ww4_add_test(test_w4core_hom_input tests/ww4_core/L2_test_w4core_hom_input.cpp ww4_core) -ww4_add_test(test_w4core_input_cycling tests/ww4_core/L2_test_w4core_input_cycling.cpp ww4_core) -ww4_add_test(test_interpolation_output tests/ww4_core/L2_test_interpolation_output.cpp ww4_core) -ww4_add_test(test_w4core_screen_output tests/ww4_core/L2_test_w4core_screen_output.cpp ww4_core) +# Tests (skipped when WW4_ENABLE_TESTING is OFF for operational builds) +if(WW4_ENABLE_TESTING) + if(EXISTS "${CMAKE_SOURCE_DIR}/externals/googletest/CMakeLists.txt") + FetchContent_Declare( + googletest + SOURCE_DIR "${CMAKE_SOURCE_DIR}/externals/googletest" + SYSTEM + ) + else() + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SYSTEM + ) + endif() + + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + FetchContent_MakeAvailable(googletest) + + # Suppress warnings from GTest itself for Clang/IntelLLVM during compilation + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") + set(GTEST_SUPPRESS_FLAGS -Wno-unknown-warning-option -Wno-character-conversion) + if(CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") + list(APPEND GTEST_SUPPRESS_FLAGS -diag-disable=10430) + endif() + foreach(target gtest gtest_main gmock gmock_main) + if(TARGET ${target}) + target_compile_options(${target} INTERFACE ${GTEST_SUPPRESS_FLAGS}) + target_compile_options(${target} PRIVATE ${GTEST_SUPPRESS_FLAGS}) + endif() + endforeach() + endif() + + enable_testing() + include(GoogleTest) + + # Helper function to define tests + function(ww4_add_test test_name source_file dependency_lib) + add_executable(${test_name} ${source_file}) + target_link_libraries(${test_name} PRIVATE ${dependency_lib} GTest::gtest_main) + target_compile_options(${test_name} PRIVATE ${WW4_COMMON_COMPILE_OPTIONS}) + target_link_options(${test_name} PRIVATE ${WW4_COMMON_LINK_OPTIONS}) + gtest_discover_tests(${test_name}) + endfunction() + + # Utils tests + ww4_add_test(test_time_management tests/ww4_utils/L1_test_time_management.cpp ww4_utils) + ww4_add_test(test_memory_utils tests/ww4_utils/L1_test_memory_utils.cpp ww4_utils) + ww4_add_test(test_ww4_std_out tests/ww4_utils/L1_test_ww4_std_out.cpp ww4_utils) + ww4_add_test(test_ww4_logfile tests/ww4_utils/L1_test_ww4_logfile.cpp ww4_utils) + ww4_add_test(test_ww4_standalone_config tests/ww4_utils/L1_test_ww4_standalone_config.cpp ww4_utils) + ww4_add_test(test_ww4_run_config tests/ww4_utils/L1_test_ww4_run_config.cpp ww4_utils) + ww4_add_test(test_ww4_input_utils tests/ww4_utils/L1_test_ww4_input_utils.cpp ww4_utils) + ww4_add_test(test_ww4_output_utils tests/ww4_utils/L1_test_ww4_output_utils.cpp ww4_utils) + ww4_add_test(test_ww4_service tests/ww4_utils/L1_test_ww4_service.cpp ww4_utils) + ww4_add_test(test_ww4_constants tests/ww4_utils/L1_test_ww4_constants.cpp ww4_utils) + + # Core tests + ww4_add_test(test_ww4_standalone tests/ww4_core/L1_test_ww4_standalone.cpp ww4_core) + target_compile_definitions(test_ww4_standalone PRIVATE STANDALONE_EXE_PATH="${CMAKE_SOURCE_DIR}/exe/ww4_standalone") + ww4_add_test(test_w4core_init tests/ww4_core/L1_test_w4core_init.cpp ww4_core) + ww4_add_test(test_w4core_wave tests/ww4_core/L1_test_w4core_wave.cpp ww4_core) + ww4_add_test(test_w4core_finalize tests/ww4_core/L1_test_w4core_finalize.cpp ww4_core) + ww4_add_test(test_w4core_time tests/ww4_core/L2_test_w4core_time.cpp ww4_core) + ww4_add_test(test_w4core_wave_output tests/ww4_core/L2_test_w4core_wave_output.cpp ww4_core) + ww4_add_test(test_w4core_zero_step tests/ww4_core/L2_test_w4core_zero_step.cpp ww4_core) + ww4_add_test(test_w4core_hom_input tests/ww4_core/L2_test_w4core_hom_input.cpp ww4_core) + ww4_add_test(test_w4core_input_cycling tests/ww4_core/L2_test_w4core_input_cycling.cpp ww4_core) + ww4_add_test(test_interpolation_output tests/ww4_core/L2_test_interpolation_output.cpp ww4_core) + ww4_add_test(test_w4core_screen_output tests/ww4_core/L2_test_w4core_screen_output.cpp ww4_core) +endif() diff --git a/README.md b/README.md index 78f8518..9c88103 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,64 @@ The documentation files (i.e. files with capitalized names and .md extensions) r [CONTRIBUTORS.md](./CONTRIBUTORS.md) identifying those who have made significant contributions to WW4, and [TRADEMARK.md](./TRADEMARK.md) documenting the Trademark Status of WW4. -The only other file in this format is [AGENTS.md](./AGENTS.md), which contains information defining an agentic AI approach used to create, translate or refactor code using AI agents such as Copilot or Jules, the latter of which has been used extensively in developing the WW4 code from WW3. Note that this AI agent is set up to automate coding standards, doxygen documentation and unit testing as mandated for WW4. +The only other file in this format is [AGENTS.md](./AGENTS.md), which contains information defining an agentic AI approach used to create, translate or refactor code using AI agents such as Copilot or Jules, the latter of which has been used extensively in developing the WW4 code from WW3. Note that this AI agent is set up to automate coding standards, doxygen documentation and unit testing as mandated for WW4. -The only other file that could be construed as “documentation” in the home directory of the repository is the VERSION file. +The only other file that could be construed as “documentation” in the home directory of the repository is the VERSION file. + +## Compilation and Environment Setup + +WAVEWATCH IV uses a standard CMake build system (v3.25+). All compilation is local to the active repository clone. The build process does not modify the user's interactive shell environment or profile scripts. + +### 1. Setting up the Compile Environment + +Environment parameters needed for CMake are set externally by the user, depending on the target system hardware and software configuration. + +#### On Linux (GCC, Clang, or Intel LLVM): +Set the C++ compiler via environment variables or CMake definitions: +```bash +# Using GCC +export CXX=g++ +cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + +# Or specifying the compiler explicitly via CMake +cmake -B build -S . -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release +``` + +#### On macOS (Apple Clang or Homebrew LLVM/GCC): +Ensure Xcode Command Line Tools are installed (`xcode-select --install`) or Homebrew compilers are available: +```bash +# Using default Apple Clang +cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + +# Using Homebrew GCC or LLVM +export CXX=/opt/homebrew/bin/g++-13 +cmake -B build -S . -DCMAKE_BUILD_TYPE=Release +``` + +### 2. Building the Project + +Standard development build (includes tests): +```bash +cmake -B build -S . -DCMAKE_BUILD_TYPE=Release +cmake --build build +``` + +### 3. NOAA Operational Builds (Disabling Testing) + +For NOAA operational environments, testing dependencies and test executables can be completely disabled using the `-DWW4_ENABLE_TESTING=OFF` flag: +```bash +cmake -B build_ops -S . -DWW4_ENABLE_TESTING=OFF +cmake --build build_ops +``` +When `WW4_ENABLE_TESTING=OFF`, GoogleTest dependencies and test targets are completely skipped during build configuration and execution. + +### 4. Offline Builds with Local Dependencies + +To perform offline builds without network access during `cmake build`, place local source directories in the `externals/` directory: +- `externals/yaml-cpp` +- `externals/googletest` + +CMake will automatically detect and use these local source trees. #

diff --git a/externals/README.md b/externals/README.md index 7dec9ab..96c639f 100644 --- a/externals/README.md +++ b/externals/README.md @@ -4,11 +4,13 @@ # External libraries used in WW4 -Copies of external libraries used by WW4 are gathered here in the **./externals** directory. We chose to keep copies of these libraries here to allow for off-line development work for WW4. +Copies of external libraries used by WW4 can be placed here in the `./externals` directory to support offline development and compilation without requiring network access during `cmake build`. -The libraries are gathered here but are not part of the repository to avoid unnecessary growth of the size of the repository. +The WW4 CMake build system automatically detects local copies of dependencies if present in this directory: +- `externals/yaml-cpp` (version 0.8.0) - Configuration file parsing +- `externals/googletest` (version 1.14.0) - C++ unit testing framework (when testing is enabled) -The libraries and their version used for WW4 are documented `*** add the YAML file for this ***`, and their inclusion in this directory is automated using the `*** add script name ***` in the **./bin** directory of the repository. +When these directories exist, CMake uses the local source trees directly instead of downloading source archives from external network locations. #

diff --git a/templates/README.md b/templates/README.md index f23e27c..3f5c33e 100644 --- a/templates/README.md +++ b/templates/README.md @@ -4,22 +4,22 @@ #

WAVEWATCH IV TM (WW4 TM) Configuration Templates

-This directory contains template files for configuring the WAVEWATCH IV build environment. These templates provide a starting point for users to set up their local compilation environment. +This directory contains template files for configuring WAVEWATCH IV runtime environments. These templates provide a starting point for users to set up simulation parameters. -## Interactive usage +## Usage -Users can manually copy the templates from this directory to the desired location (typically the repository root) and modify them as needed. +Users can copy templates from this directory to the desired working directory (typically the repository root or execution directory) and modify them as needed: -1. Copy `templates/ww4_compile_config.yaml` to the root directory: - ```bash - cp templates/ww4_compile_config.yaml ./ww4_compile_config.yaml - ``` -2. Edit `ww4_compile_config.yaml` to specify your compiler and preferred options. -3. Copy `templates/ww4_run_config.yaml` to the root directory and modify run-time settings. +1. Copy `templates/ww4_standalone.yaml` to configure simulation start/end times: + ```bash + cp templates/ww4_standalone.yaml ./ww4_standalone.yaml + ``` +2. Copy `templates/ww4_run_config.yaml` to configure general, physics, forcing, and output settings: + ```bash + cp templates/ww4_run_config.yaml ./ww4_run_config.yaml + ``` -## Automatic usage - -Build scripts or CI/CD pipelines can use these templates to generate default configurations if a user-specified configuration is not found. +Note: Runtime YAML configuration files created in the repository root are ignored by Git. #

diff --git a/templates/ww4_compile_config.yaml b/templates/ww4_compile_config.yaml deleted file mode 100644 index 715954a..0000000 --- a/templates/ww4_compile_config.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# @file ww4_compile_config.yaml -# @brief YAML template for WAVEWATCH IV (WW4) compilation configuration. -# @copyright © 2026 National Weather Service, National Oceanic and Atmospheric Administration, U.S. Federal Government. -# -# NWS often uses Generative AI (GenAI) for code development and refactoring. Whenever GenAI is used, NWS requires a full human review of code before it is added to its repositories. -# @details This file provides a template for configuring the compiler and -# compilation options for WAVEWATCH IV. -# -# To use this file, copy it to the root directory of the repository -# or the directory from which the executable is run. -# -# Note: All YAML files in the root directory of the clone are -# automatically ignored by Git. This allows for local testing without -# adding customized input files to the repository. - -compiler: - # Name of the C++ compiler (e.g., g++, clang++, icpx, cl, CC) - name: "g++" - - # Compilation options/flags - options: "-O3 -Wall -Wextra -std=c++20" - - # Enable strict compiler warnings and treat as errors (true/false) - strict_warnings: false - - # Enable address and undefined behavior sanitizers (true/false) - use_sanitizers: false diff --git a/tools/README.md b/tools/README.md index 097cebc..549839e 100644 --- a/tools/README.md +++ b/tools/README.md @@ -4,64 +4,40 @@ #

WW4 tools directory

-Copies of tools created for WW4 are gathered here in the tools/ directory. These are the ‘microtools` to work the repository. ‘Macrotools’ to help develop WW4 applications, for instance to manipulate grids, are gathered in their own repositories. +Copies of tools created for WAVEWATCH IV (WW4) are gathered here in the `tools/` directory. These are the microtools to support repository management and testing. Macrotools to help develop WW4 applications, for instance to manipulate grids, are gathered in their own repositories. -The tools gathered here are documented in the [WW4 Tools](https://github.com/NOAA-EMC/WW4/wiki/Tools.md) page of the WW4 wiki page. +The tools gathered here are documented on the [WW4 Tools](https://github.com/NOAA-EMC/WW4/wiki/Tools.md) page of the WW4 wiki. -# Usage +# Compilation and Setup -WAVEWATCH IV provides multiple ways to set up and build the project, ranging from interactive tools to manual configuration. +WAVEWATCH IV uses a standard CMake build system. Build configuration is handled locally in each repository clone using CMake options and standard environment variables. The build system does not modify the user's interactive environment or shell profile scripts. -## Interactive Usage (Recommended) - -Run the setup tool to interactively configure your active clone and compiler settings: - -### 1. Setup +## Building WAVEWATCH IV ```bash -./ww4_setup -``` -This tool will: -- Identify and set the active WAVEWATCH IV clone in `~/.ww4_config.yaml`. -- Detect available C++ compilers on your system. -- Configure compilation flags for either development or maximum optimization in `ww4_compile_config.yaml`. - -### 2. Compilation - -Once configured, you can compile WAVEWATCH IV using standard CMake: +# Configure build +cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -```bash -cmake -B build +# Build executables and libraries cmake --build build ``` -The `ww4_setup` tool generates a `ww4_local_config.cmake` file that stores your selected compiler and flags, which is automatically included by `CMakeLists.txt`. - -## Manual Usage - -If you prefer to configure the tools manually, follow these steps: -### 1. Setup - -Copy the template configuration file to the repository root: +For NOAA operational builds without testing components: ```bash -cp templates/ww4_compile_config.yaml ./ww4_compile_config.yaml +cmake -B build_ops -S . -DWW4_ENABLE_TESTING=OFF +cmake --build build_ops ``` -Then, edit `ww4_compile_config.yaml` to specify your compiler and preferred options. - -### 2. Compilation - -Configure and build with CMake as shown above. -## Developer Tools +# Developer Tools WAVEWATCH IV provides additional tools to support developers during the coding process. -### Test Availability Check +## Test Availability Check To check if unit tests are available for a specific file and its identified routines: ```bash -./ww4_test_check --file +./tools/ww4_test_check --file ``` *Note: The filename should be provided without extension (e.g., `time_management`).* diff --git a/tools/ww4_setup b/tools/ww4_setup deleted file mode 100755 index a1add7f..0000000 --- a/tools/ww4_setup +++ /dev/null @@ -1,222 +0,0 @@ -#!/bin/bash -# +--------------------------------------------------------+ -# | WAVEWATCH IV, open source, code management by NOAA/NWS | -# +--------------------------------------------------------+ -# -# @file ww4_setup -# @brief Pure Bash configuration tool for WAVEWATCH IV (WW4). -# @details This script provides the recommended way to interactively configure -# the active WW4 clone. -# -# Usage -# ----- -# ww4_setup -# -# @copyright © 2026 National Weather Service, National Oceanic and Atmospheric -# Administration. WAVEWATCH IV (TM) and WW4 (TM) are trademarks -# of the National Weather Service. -# NWS often uses Generative AI (GenAI) for code development and refactoring. Whenever GenAI is used, NWS requires a full human review of code before it is added to its repositories. -# @author Main Author(s): Aldgisl (AI Persona), Hendrik Tolman -# @author Contributors: Jules (Agentic AI) -# @date Initial, 2026-03-24 -# @date Last update : 2026-07-14 - -# Helper function to find all WW4 clones -find_clones() { - local clones=() - local seen_paths="" - - # Helper to add clone path - add_clone() { - local path="$1" - if [ -d "$path" ]; then - local abs_path - abs_path=$(cd "$path" && pwd -P) - if [[ ! " $seen_paths " =~ " $abs_path " ]]; then - clones+=("$abs_path") - seen_paths="$seen_paths $abs_path" - fi - fi - } - - # 1. Check current directory and parent directory first - local cwd - cwd=$(pwd -P) - if [ -f "$cwd/tools/ww4_setup" ]; then - add_clone "$cwd" - fi - if [ -f "$cwd/../tools/ww4_setup" ]; then - add_clone "$(cd "$cwd/.." && pwd -P)" - fi - - # 2. Search in common locations ($HOME, /app, /workspace) - local search_paths=("$HOME" "/app" "/workspace") - for search_path in "${search_paths[@]}"; do - if [ -d "$search_path" ]; then - while IFS= read -r found_file; do - if [ -f "$found_file" ]; then - local parent_dir - parent_dir=$(dirname "$found_file") - if [ "$(basename "$parent_dir")" = "tools" ]; then - local clone_root - clone_root=$(dirname "$parent_dir") - add_clone "$clone_root" - fi - fi - done < <(find "$search_path" -maxdepth 4 -name "ww4_setup" 2>/dev/null) - fi - done - - # Print the clones space-separated - echo "${clones[@]}" -} - -# Helper function to update PATH in the user's shell profile -update_shell_config() { - local clone_path="$1" - local tools_dir="$clone_path/tools" - local exe_dir="$clone_path/exe" - - local shell_name - shell_name=$(basename "$SHELL") - - local config_filename=".bashrc" - case "$shell_name" in - bash) config_filename=".bashrc" ;; - zsh) config_filename=".zshrc" ;; - ksh) config_filename=".kshrc" ;; - sh) config_filename=".profile" ;; - esac - - local config_path="$HOME/$config_filename" - - # Fallback checks if the detected config doesn't exist - if [ ! -f "$config_path" ]; then - if [ -f "$HOME/.bashrc" ]; then - config_path="$HOME/.bashrc" - elif [ -f "$HOME/.profile" ]; then - config_path="$HOME/.profile" - else - echo "Warning: Could not find a suitable shell configuration file (tried $config_filename, .bashrc, .profile)." - return - fi - fi - - local content - content=$(cat "$config_path" 2>/dev/null) - - local path_updates=() - for d in "$tools_dir" "$exe_dir"; do - local export_line="export PATH=\"$d:\$PATH\"" - if [[ ! "$content" =~ "$export_line" ]]; then - path_updates+=("$export_line") - fi - done - - if [ ${#path_updates[@]} -gt 0 ]; then - { - echo "" - echo "# WAVEWATCH IV paths" - for line in "${path_updates[@]}"; do - echo "$line" - done - } >> "$config_path" - echo "Updated $config_path with WAVEWATCH IV paths." - echo "Note: These changes will take effect in all NEW shell sessions." - echo "To update your CURRENT session, please run: source ~/${config_path##*/}" - fi -} - -# Main function to configure active clone -setup_active_clone() { - local config_file="$HOME/.ww4_config.yaml" - local active_clone="" - - if [ -f "$config_file" ]; then - # Safely parse active_clone key - active_clone=$(grep "^active_clone:" "$config_file" | sed -E 's/^active_clone:[[:space:]]*"?([^"]*)"?/\1/') - fi - - if [ -n "$active_clone" ] && [ -d "$active_clone" ]; then - echo "Current active clone found: $active_clone" - local use_current - if ! read -p "Do you want to use this clone? (y/n) [y]: " use_current; then - use_current="y" - fi - use_current=$(echo "$use_current" | tr '[:upper:]' '[:lower:]' | xargs) - if [ -z "$use_current" ] || [ "$use_current" = "y" ] || [ "$use_current" = "yes" ]; then - update_shell_config "$active_clone" - echo "$active_clone" - return - fi - fi - - # Search for clones - echo "Searching for WW4 clones... this may take a moment." - local clones_str - clones_str=$(find_clones) - - local clones=() - for clone in $clones_str; do - clones+=("$clone") - done - - if [ ${#clones[@]} -eq 0 ]; then - echo "Error: No WW4 clones found." - echo "Please ensure you are running this from a WW4 clone or have one installed." - exit 1 - fi - - echo "" - echo "Available WW4 clones:" - for i in "${!clones[@]}"; do - echo "[$i] ${clones[$i]}" - done - - local max_idx=$((${#clones[@]} - 1)) - local selected_clone="" - while true; do - local choice - if ! read -p "Select a clone [0-$max_idx]: " choice; then - echo "Error: EOF reached. Exiting." - exit 1 - fi - choice=$(echo "$choice" | xargs) - if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 0 ] && [ "$choice" -le "$max_idx" ]; then - selected_clone="${clones[$choice]}" - break - fi - echo "Invalid choice. Please try again." - done - - # Update config_file non-destructively - if [ -f "$config_file" ]; then - grep -v "^active_clone:" "$config_file" > "${config_file}.tmp" 2>/dev/null || true - mv "${config_file}.tmp" "$config_file" - fi - echo "active_clone: \"$selected_clone\"" >> "$config_file" - echo "Updated $config_file with active clone: $selected_clone" - - update_shell_config "$selected_clone" - - echo "$selected_clone" -} - -# Script entry point -main() { - echo " +--------------------------------------------------------+" - echo " | WAVEWATCH IV, open source, code management by NOAA/NWS |" - echo " +--------------------------------------------------------+" - echo "" - echo "--- WAVEWATCH IV Setup Tool ---" - - local active_clone - active_clone=$(setup_active_clone) - - echo "" - echo "Setup complete!" - echo "You can now build WAVEWATCH IV using standard CMake:" - echo " cmake -B build && cmake --build build" -} - -main "$@" From 0af0bdbd2d306f62aff4ceaef9c2e63d504ca8c6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:45:25 +0000 Subject: [PATCH 2/4] Rebuild WW4 compile system with pure CMake and local clone builds - Remove ww4_setup script, ww4_compile_config.yaml, and ww4_local_config.cmake - Add WW4_ENABLE_TESTING CMake option to skip GoogleTest and test targets for operational builds - Add support for local offline dependencies in externals/ directory - Update documentation for Linux and macOS compile environment setup - Fix Python ruff linting in tools/ww4_test_check.py Co-authored-by: HendrikTolman-NOAA <237313876+HendrikTolman-NOAA@users.noreply.github.com> --- tools/ww4_test_check.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/tools/ww4_test_check.py b/tools/ww4_test_check.py index 03f049f..c26d7e5 100644 --- a/tools/ww4_test_check.py +++ b/tools/ww4_test_check.py @@ -23,16 +23,15 @@ @author Main Author(s): Aldgisl (AI Persona), Hendrik Tolman @author Contributors: Jules (Agentic AI) @date Initial, 2026-07-09 -@date Last update : 2026-07-09 +@date Last update : 2026-08-21 """ import argparse import re from pathlib import Path -from typing import List, Set -def find_files(root_dir: Path, filename: str) -> List[Path]: +def find_files(root_dir: Path, filename: str) -> list[Path]: """ Find files matching the filename (without extension) in src and include. @@ -45,7 +44,7 @@ def find_files(root_dir: Path, filename: str) -> List[Path]: Returns ------- - List[Path] + list[Path] A list of matching file paths. """ matches = [] @@ -56,12 +55,11 @@ def find_files(root_dir: Path, filename: str) -> List[Path]: if not search_dir.exists(): continue for ext in extensions: - for path in search_dir.rglob(f"{filename}{ext}"): - matches.append(path) + matches.extend(list(search_dir.rglob(f"{filename}{ext}"))) return matches -def extract_routines(file_path: Path) -> Set[str]: +def extract_routines(file_path: Path) -> set[str]: """ Extract routine names (functions/methods) from a C++ file. @@ -72,13 +70,13 @@ def extract_routines(file_path: Path) -> Set[str]: Returns ------- - Set[str] + set[str] A set of routine names found in the file. """ routines = set() try: content = file_path.read_text() - except Exception as e: + except OSError as e: print(f"Error reading {file_path}: {e}") return routines @@ -137,7 +135,7 @@ def extract_routines(file_path: Path) -> Set[str]: return routines -def find_test_files(root_dir: Path, filename: str) -> List[Path]: +def find_test_files(root_dir: Path, filename: str) -> list[Path]: """ Find test files matching the filename in tests. @@ -150,7 +148,7 @@ def find_test_files(root_dir: Path, filename: str) -> List[Path]: Returns ------- - List[Path] + list[Path] A list of matching test file paths. """ test_dir = root_dir / "tests" @@ -167,25 +165,24 @@ def find_test_files(root_dir: Path, filename: str) -> List[Path]: ] matches = [] for pattern in patterns: - for path in test_dir.rglob(pattern): - matches.append(path) + matches.extend(list(test_dir.rglob(pattern))) return matches -def check_routines_in_tests(routines: Set[str], test_files: List[Path]) -> Set[str]: +def check_routines_in_tests(routines: set[str], test_files: list[Path]) -> set[str]: """ Check which routines are mentioned in the test files. Parameters ---------- - routines : Set[str] + routines : set[str] A set of routine names to check. - test_files : List[Path] + test_files : list[Path] A list of test file paths to search in. Returns ------- - Set[str] + set[str] A set of routines found in the tests. """ covered = set() @@ -196,7 +193,7 @@ def check_routines_in_tests(routines: Set[str], test_files: List[Path]) -> Set[s for test_file in test_files: try: test_contents += test_file.read_text() + "\n" - except Exception: + except OSError: pass for routine in routines: @@ -259,7 +256,7 @@ def main() -> None: print("\nRoutine Coverage:") print(f"{'Routine Name':<30} {'Status':<10}") print("-" * 40) - for routine in sorted(list(routines)): + for routine in sorted(routines): status = "[OK]" if routine in covered else "[MISSING]" print(f"{routine:<30} {status}") From 4c21980c270daf49ea37d9025de1133530621901 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 18:03:24 +0000 Subject: [PATCH 3/4] Include externals as git submodules and update documentation Replace dynamic FetchContent URL download logic in CMakeLists.txt with add_subdirectory for git submodules (yaml-cpp 0.8.0 and googletest 1.14.0) located in externals/. Update README.md and externals/README.md with instructions on cloning with --recursive or initializing submodules. Co-authored-by: HendrikTolman-NOAA <237313876+HendrikTolman-NOAA@users.noreply.github.com> --- .gitignore | 3 --- .gitmodules | 6 ++++++ CMakeLists.txt | 37 ++++++------------------------------- README.md | 17 ++++++++++++----- externals/README.md | 14 ++++++++++---- externals/googletest | 1 + externals/yaml-cpp | 1 + 7 files changed, 36 insertions(+), 43 deletions(-) create mode 100644 .gitmodules create mode 160000 externals/googletest create mode 160000 externals/yaml-cpp diff --git a/.gitignore b/.gitignore index 62d8d22..027c790 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,3 @@ __pycache__/ *.py[cod] *$py.class -# Externals -externals/* -!externals/README.md diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c19fb52 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "externals/yaml-cpp"] + path = externals/yaml-cpp + url = https://github.com/jbeder/yaml-cpp.git +[submodule "externals/googletest"] + path = externals/googletest + url = https://github.com/google/googletest.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 9409abb..ff18d19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,26 +59,13 @@ else() endif() endif() -include(FetchContent) - # yaml-cpp for configuration parsing if(EXISTS "${CMAKE_SOURCE_DIR}/externals/yaml-cpp/CMakeLists.txt") - FetchContent_Declare( - yaml-cpp - SOURCE_DIR "${CMAKE_SOURCE_DIR}/externals/yaml-cpp" - SYSTEM - ) + add_subdirectory(externals/yaml-cpp EXCLUDE_FROM_ALL SYSTEM) else() - FetchContent_Declare( - yaml-cpp - URL https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.8.0.zip - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - SYSTEM - ) + message(FATAL_ERROR "yaml-cpp submodule not found in externals/yaml-cpp. Please run: git submodule update --init --recursive") endif() -FetchContent_MakeAvailable(yaml-cpp) - # Library: ww4_utils add_library( ww4_utils @@ -124,25 +111,13 @@ set_target_properties( # Tests (skipped when WW4_ENABLE_TESTING is OFF for operational builds) if(WW4_ENABLE_TESTING) if(EXISTS "${CMAKE_SOURCE_DIR}/externals/googletest/CMakeLists.txt") - FetchContent_Declare( - googletest - SOURCE_DIR "${CMAKE_SOURCE_DIR}/externals/googletest" - SYSTEM - ) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + add_subdirectory(externals/googletest EXCLUDE_FROM_ALL SYSTEM) else() - FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - SYSTEM - ) + message(FATAL_ERROR "googletest submodule not found in externals/googletest. Please run: git submodule update --init --recursive") endif() - # For Windows: Prevent overriding the parent project's compiler/linker settings - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - - FetchContent_MakeAvailable(googletest) - # Suppress warnings from GTest itself for Clang/IntelLLVM during compilation if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") set(GTEST_SUPPRESS_FLAGS -Wno-unknown-warning-option -Wno-character-conversion) diff --git a/README.md b/README.md index 9c88103..641a4aa 100644 --- a/README.md +++ b/README.md @@ -63,13 +63,20 @@ cmake --build build_ops ``` When `WW4_ENABLE_TESTING=OFF`, GoogleTest dependencies and test targets are completely skipped during build configuration and execution. -### 4. Offline Builds with Local Dependencies +### 4. Git Submodules for External Dependencies -To perform offline builds without network access during `cmake build`, place local source directories in the `externals/` directory: -- `externals/yaml-cpp` -- `externals/googletest` +WAVEWATCH IV includes required external dependencies as Git submodules in the `externals/` directory: +- `externals/yaml-cpp` (version 0.8.0) +- `externals/googletest` (version 1.14.0) -CMake will automatically detect and use these local source trees. +When cloning the repository, initialize submodules using: +```bash +git clone --recursive https://github.com/NOAA-EMC/WW4.git +``` +Or if already cloned, initialize and update submodules using: +```bash +git submodule update --init --recursive +``` #

diff --git a/externals/README.md b/externals/README.md index 96c639f..638eb4a 100644 --- a/externals/README.md +++ b/externals/README.md @@ -4,13 +4,19 @@ # External libraries used in WW4 -Copies of external libraries used by WW4 can be placed here in the `./externals` directory to support offline development and compilation without requiring network access during `cmake build`. - -The WW4 CMake build system automatically detects local copies of dependencies if present in this directory: +External libraries used by WW4 are included as Git submodules in the `./externals` directory: - `externals/yaml-cpp` (version 0.8.0) - Configuration file parsing - `externals/googletest` (version 1.14.0) - C++ unit testing framework (when testing is enabled) -When these directories exist, CMake uses the local source trees directly instead of downloading source archives from external network locations. +To clone WW4 with external dependencies included, use: +```bash +git clone --recursive https://github.com/NOAA-EMC/WW4.git +``` + +If the repository was cloned without `--recursive`, initialize and update submodules using: +```bash +git submodule update --init --recursive +``` #

diff --git a/externals/googletest b/externals/googletest new file mode 160000 index 0000000..f8d7d77 --- /dev/null +++ b/externals/googletest @@ -0,0 +1 @@ +Subproject commit f8d7d77c06936315286eb55f8de22cd23c188571 diff --git a/externals/yaml-cpp b/externals/yaml-cpp new file mode 160000 index 0000000..f732014 --- /dev/null +++ b/externals/yaml-cpp @@ -0,0 +1 @@ +Subproject commit f7320141120f720aecc4c32be25586e7da9eb978 From b5a2d992287b1e3ba38e530a6428d2a5e286df83 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 18:10:24 +0000 Subject: [PATCH 4/4] Include externals as git submodules and update documentation Replace dynamic FetchContent URL download logic in CMakeLists.txt with add_subdirectory for git submodules (yaml-cpp 0.8.0 and googletest 1.14.0) located in externals/. Update README.md and externals/README.md with instructions on cloning with --recursive or initializing submodules. Co-authored-by: HendrikTolman-NOAA <237313876+HendrikTolman-NOAA@users.noreply.github.com>