From c6512ec7c03ad1221de737866e0ed6f97d934b2c Mon Sep 17 00:00:00 2001 From: Philip Top Date: Tue, 12 May 2026 04:36:39 -0700 Subject: [PATCH 1/9] update version numbers and some warning flags --- .github/workflows/static-analyzers.yml | 30 +++++++++++++++++++++++++- CMakeLists.txt | 2 +- config/compiler_flags.cmake | 4 ++-- units/CMakeLists.txt | 1 + 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.github/workflows/static-analyzers.yml b/.github/workflows/static-analyzers.yml index 89c46272..11c81698 100644 --- a/.github/workflows/static-analyzers.yml +++ b/.github/workflows/static-analyzers.yml @@ -24,4 +24,32 @@ jobs: steps: - uses: actions/checkout@v6 - name: Run cppcheck - run: cppcheck --enable=performance,portability --check-level=exhaustive --suppressions-list=config/cppcheck_suppressions.txt --error-exitcode=-4 -i ThirdParty -i FuzzTargets -i docs -i config . \ No newline at end of file + run: cppcheck --enable=performance,portability --check-level=exhaustive --suppressions-list=config/cppcheck_suppressions.txt --error-exitcode=-4 -i ThirdParty -i FuzzTargets -i docs -i config . + + gcc-strict-overflow: + name: GCC strict-overflow + runs-on: ubuntu-latest + container: + image: helics/buildenv:gcc15-builder + + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Mark workspace as safe for git + run: git config --global --add safe.directory "${GITHUB_WORKSPACE}" + + - name: Configure + run: > + cmake -S . -B build + -DCMAKE_CXX_STANDARD=23 + -DUNITS_ENABLE_ERROR_ON_WARNINGS=ON + -DCMAKE_CXX_FLAGS="-Wstrict-overflow=5 -Werror=strict-overflow" + + - name: Build + run: cmake --build build --parallel 4 + + - name: Test + working-directory: build + run: ctest --output-on-failure diff --git a/CMakeLists.txt b/CMakeLists.txt index a138cc14..666ed284 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ endif() project( ${UNITS_CMAKE_PROJECT_NAME} LANGUAGES C CXX - VERSION 0.13.1 + VERSION 0.14.0 ) include(CMakeDependentOption) include(CTest) diff --git a/config/compiler_flags.cmake b/config/compiler_flags.cmake index a20fdaea..c021abfe 100644 --- a/config/compiler_flags.cmake +++ b/config/compiler_flags.cmake @@ -28,8 +28,8 @@ endif() target_compile_options( compile_flags_target INTERFACE - $<$:$<$:/WX>> - $<$>:$<$:-Werror>> + $<$:$<$:/WX>> + $<$>:$<$:-Werror>> ) if(${PROJECT_NAME}_ENABLE_EXTRA_COMPILER_WARNINGS) diff --git a/units/CMakeLists.txt b/units/CMakeLists.txt index d6adb5aa..fe03aba4 100644 --- a/units/CMakeLists.txt +++ b/units/CMakeLists.txt @@ -106,6 +106,7 @@ elseif(UNITS_BUILD_OBJECT_LIBRARY) target_include_directories( units PRIVATE $ ) + target_link_libraries(units PRIVATE compile_flags_target) if(UNITS_NAMESPACE) target_compile_definitions(units PUBLIC -DUNITS_NAMESPACE=${UNITS_NAMESPACE}) From 282d590eeb9729a61dedf48e40bf8527a72f63e0 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Tue, 12 May 2026 06:00:34 -0700 Subject: [PATCH 2/9] update cli11 and update compiler flags --- ThirdParty/CLI11.hpp | 240 ++++++++++++++++++++++++++---------- config/compiler_flags.cmake | 12 +- 2 files changed, 188 insertions(+), 64 deletions(-) diff --git a/ThirdParty/CLI11.hpp b/ThirdParty/CLI11.hpp index 5a59d8c1..8a4d2348 100644 --- a/ThirdParty/CLI11.hpp +++ b/ThirdParty/CLI11.hpp @@ -3,7 +3,7 @@ // https://github.com/CLIUtils/CLI11 // // This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts -// from: v2.6.2-5-g1946b74 +// from: v2.6.2-30-g3454c0a // // CLI11 2.6.2 Copyright (c) 2017-2026 University of Cincinnati, developed by Henry // Schreiner under NSF AWARD 1414736. All rights reserved. @@ -248,7 +248,7 @@ #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 -#include // NOLINT(build/include) +#include #else #include #include @@ -257,14 +257,13 @@ -#ifdef CLI11_CPP17 +#if defined(CLI11_CPP17) || (defined(CLI11_HAS_FILESYSTEM) && CLI11_HAS_FILESYSTEM > 0) #include -#endif // CLI11_CPP17 - #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 #include -#include // NOLINT(build/include) -#endif // CLI11_HAS_FILESYSTEM +#endif +#endif + @@ -333,7 +332,7 @@ namespace detail { #if !CLI11_HAS_CODECVT /// Attempt to set one of the acceptable unicode locales for conversion CLI11_INLINE void set_unicode_locale() { - static const std::array unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; + static const std::array unicode_locales{{"C.UTF-8", ".UTF-8"}}; for(const auto &locale_name : unicode_locales) { if(std::setlocale(LC_ALL, locale_name) != nullptr) { @@ -912,23 +911,43 @@ find_member(std::string name, const std::vector names, bool ignore_ return (it != std::end(names)) ? (it - std::begin(names)) : (-1); } -CLI11_MODULE_INLINE const std::string &escapedChars("\b\t\n\f\r\"\\"); -CLI11_MODULE_INLINE const std::string &escapedCharsCode("btnfr\"\\"); +CLI11_MODULE_INLINE const std::string &escapedChars() { + static const std::string s{"\b\t\n\f\r\"\\"}; + return s; +} +CLI11_MODULE_INLINE const std::string &escapedCharsCode() { + static const std::string s{"btnfr\"\\"}; + return s; +} +CLI11_MODULE_INLINE const std::string &bracketChars() { + static const std::string s{"\"'`[(<{"}; + return s; +} +CLI11_MODULE_INLINE const std::string &matchBracketChars() { + static const std::string s{"\"'`])>}"}; + return s; +} + +// CLI11_MODULE_INLINE constexpr char escapedChars[]="\b\t\n\f\r\"\\"; +// CLI11_MODULE_INLINE constexpr char escapedCharsCode[]="btnfr\"\\"; +/* +const std::string &escapedChars("\b\t\n\f\r\"\\"); + CLI11_MODULE_INLINE const std::string &bracketChars("\"'`[(<{"); CLI11_MODULE_INLINE const std::string &matchBracketChars("\"'`])>}"); - +*/ CLI11_INLINE bool has_escapable_character(const std::string &str) { - return (str.find_first_of(escapedChars) != std::string::npos); + return (str.find_first_of(escapedChars()) != std::string::npos); } CLI11_INLINE std::string add_escaped_characters(const std::string &str) { std::string out; out.reserve(str.size() + 4); for(char s : str) { - auto sloc = escapedChars.find_first_of(s); + auto sloc = escapedChars().find_first_of(s); if(sloc != std::string::npos) { out.push_back('\\'); - out.push_back(escapedCharsCode[sloc]); + out.push_back(escapedCharsCode()[sloc]); } else { out.push_back(s); } @@ -985,9 +1004,9 @@ CLI11_INLINE std::string remove_escaped_characters(const std::string &str) { if(str.end() - loc < 2) { throw std::invalid_argument("invalid escape sequence " + str); } - auto ecloc = escapedCharsCode.find_first_of(*(loc + 1)); + auto ecloc = escapedCharsCode().find_first_of(*(loc + 1)); if(ecloc != std::string::npos) { - out.push_back(escapedChars[ecloc]); + out.push_back(escapedChars()[ecloc]); ++loc; } else if(*(loc + 1) == 'u') { // must have 4 hex characters @@ -1057,7 +1076,7 @@ CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) { - auto bracket_loc = matchBracketChars.find(closure_char); + auto bracket_loc = matchBracketChars().find(closure_char); switch(bracket_loc) { case 0: return close_string_quote(str, start, closure_char); @@ -1083,7 +1102,7 @@ CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t star return loc; } } - bracket_loc = bracketChars.find(str[loc]); + bracket_loc = bracketChars().find(str[loc]); if(bracket_loc != std::string::npos) { switch(bracket_loc) { case 0: @@ -1094,7 +1113,7 @@ CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t star loc = close_literal_quote(str, loc, str[loc]); break; default: - closures.push_back(matchBracketChars[bracket_loc]); + closures.push_back(matchBracketChars()[bracket_loc]); break; } } @@ -1115,9 +1134,9 @@ CLI11_INLINE std::vector split_up(std::string str, char delimiter) std::vector output; while(!str.empty()) { - if(bracketChars.find_first_of(str[0]) != std::string::npos) { - auto bracketLoc = bracketChars.find_first_of(str[0]); - auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); + if(bracketChars().find_first_of(str[0]) != std::string::npos) { + auto bracketLoc = bracketChars().find_first_of(str[0]); + auto end = close_sequence(str, 0, matchBracketChars()[bracketLoc]); if(end >= str.size()) { output.push_back(std::move(str)); str.clear(); @@ -3069,7 +3088,7 @@ bool lexical_cast(const std::string & /*input*/, T & /*output*/) { /// Strings can be empty so we need to do a little different template ::value && + enable_if_t::value && !is_wrapper::value && (classify_object::value == object_category::string_assignable || classify_object::value == object_category::string_constructible || classify_object::value == object_category::wstring_assignable || @@ -3079,6 +3098,24 @@ bool lexical_assign(const std::string &input, AssignTo &output) { return lexical_cast(input, output); } +/// Assign a value through lexical cast operations +/// Strings can be empty so we need to do a little different but also need to support wrappers +template ::value && is_wrapper::value && + (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible || + classify_object::value == object_category::wstring_assignable || + classify_object::value == object_category::wstring_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = AssignTo{}; + return true; + } + return lexical_cast(input, output); +} + /// Assign a value through lexical cast operations template = int */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" #endif output = val; #if defined(__clang__) #pragma clang diagnostic pop +#elif defined(__GNUC__) && (__GNUC__ == 8) +#pragma GCC diagnostic pop #endif return true; } @@ -3701,6 +3744,9 @@ get_names(const std::vector &input, bool allow_non_standard) { class App; +/// enumeration of output modes for writing config files +enum class ConfigOutputMode : std::uint8_t { Active = 0, AllDefaults, ActiveSubcommandDefaults }; + /// Holds values to load into Options struct ConfigItem { /// This is the list of parents @@ -3730,6 +3776,12 @@ class Config { /// Convert an app into a configuration virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + /// Convert an app into a configuration + virtual std::string + to_config(const App *app, ConfigOutputMode mode, bool write_description, std::string prefix) const { + return to_config(app, mode != ConfigOutputMode::Active, write_description, std::move(prefix)); + } + /// Convert a configuration into an app virtual std::vector from_config(std::istream &) const = 0; @@ -3793,6 +3845,9 @@ class ConfigBase : public Config { std::string configSection{}; public: + std::string + to_config(const App * /*app*/, ConfigOutputMode mode, bool write_description, std::string prefix) const override; + std::string to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; @@ -4428,7 +4483,6 @@ CLI11_INLINE std::pair split_program_name(std::string trim(commandline); auto esp = commandline.find_first_of(' ', 1); while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { - esp = commandline.find_first_of(' ', esp + 1); if(esp == std::string::npos) { // if we have reached the end and haven't found a valid file just assume the first argument is the // program name @@ -4455,6 +4509,7 @@ CLI11_INLINE std::pair split_program_name(std::string break; } + esp = commandline.find_first_of(' ', esp + 1); } if(vals.first.empty()) { vals.first = commandline.substr(0, esp); @@ -5274,6 +5329,9 @@ class FormatterBase { /// Values are Needs, Excludes, etc. std::map labels_{}; + /// Default user-facing help labels used when no override is set + static std::string default_label(const std::string &key) { return key; } + ///@} /// @name Basic ///@{ @@ -5333,9 +5391,8 @@ class FormatterBase { /// Get the current value of a name (REQUIRED, etc.) CLI11_NODISCARD std::string get_label(std::string key) const { - if(labels_.find(key) == labels_.end()) - return key; - return labels_.at(key); + auto it = labels_.find(key); + return it != labels_.end() ? it->second : default_label(key); } /// Get the current left column width (options/flags/subcommands) @@ -5351,7 +5408,7 @@ class FormatterBase { CLI11_NODISCARD std::size_t get_footer_paragraph_width() const { return footer_paragraph_width_; } /// @brief Get the current alignment ratio for long options within the left column - /// @return + /// @return The alignment ratio used for long options within the left column CLI11_NODISCARD float get_long_option_alignment_ratio() const { return long_option_alignment_ratio_; } /// Get the current status of description paragraph formatting @@ -5901,6 +5958,9 @@ class Option : public OptionBase