diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f959e632..ba0c7e82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: cxx: g++-14 - cc: clang-18, cxx: clang++-18 - cmake_flags: "-DCMAKE_LINKER=/usr/bin/clang-17" + cmake_flags: "-DCMAKE_LINKER=/usr/bin/clang-18" - cmake_wrapper: emcmake cc: emcc cmake_flags: "-DENABLE_COVERAGE=OFF -DCMAKE_CROSSCOMPILING_EMULATOR=${SYSTEM_NODE}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 50a3ba1c..2baef84e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,17 @@ if (NOT DEFINED GR_TOPLEVEL_PROJECT) endif () endif () +# Use ccache if found and enabled +find_program(CCACHE_PROGRAM ccache) +option(USE_CCACHE "Use ccache if available" ON) +if (CCACHE_PROGRAM AND USE_CCACHE) + message(STATUS "ccache found and will be used") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}") +else () + message(STATUS "ccache will not be used") +endif () + set(CMAKE_EXT_DEP_WARNING_GUARD "") if(DISABLE_EXTERNAL_DEPS_WARNINGS) # enable warnings for external dependencies set(CMAKE_EXT_DEP_WARNING_GUARD SYSTEM) @@ -64,8 +75,8 @@ endif() string(REPLACE ";" " " ALL_COMPILER_FLAGS "${ALL_COMPILER_FLAGS}") if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") # set default C++ STL to Clang's libc++ when using Clang - add_compile_options(-stdlib=libc++ -fcolor-diagnostics) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++") + add_compile_options(-stdlib=libc++ -fcolor-diagnostics -ftime-trace) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++ -ftime-trace") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options(-fdiagnostics-color=always) endif() diff --git a/ClangBuildAnalyzer.ini b/ClangBuildAnalyzer.ini new file mode 100644 index 00000000..b99105c2 --- /dev/null +++ b/ClangBuildAnalyzer.ini @@ -0,0 +1,35 @@ +# ClangBuildAnalyzer reads ClangBuildAnalyzer.ini file from the working directory +# when invoked, and various aspects of reporting can be configured this way. +# This file example is setup to be exactly like what the defaults are. + +# How many of most expensive things are reported? +[counts] + +# files that took most time to parse +fileParse = 10 +# files that took most time to generate code for +fileCodegen = 10 +# functions that took most time to generate code for +function = 30 +# header files that were most expensive to include +header = 10 +# for each expensive header, this many include paths to it are shown +headerChain = 5 +# templates that took longest to instantiate +template = 30 + + +# Minimum times (in ms) for things to be recorded into trace +[minTimes] + +# parse/codegen for a file +file = 20 + +[misc] + +# Maximum length of symbol names printed; longer names will get truncated +maxNameLength = 1000 + +# Only print "root" headers in expensive header report, i.e. +# only headers that are directly included by at least one source file +onlyRootHeaders = true diff --git a/core/include/gnuradio-4.0/Block.hpp b/core/include/gnuradio-4.0/Block.hpp index a42ece07..afe3dec6 100644 --- a/core/include/gnuradio-4.0/Block.hpp +++ b/core/include/gnuradio-4.0/Block.hpp @@ -1056,24 +1056,24 @@ class Block : public lifecycle::StateMachine, // if (message.cmd == Set) { if (!message.data.has_value() && !message.data.value().contains("state")) { - throw gr::exception(fmt::format("block {} (aka. {}) cannot set block state w/o 'state' data msg: {}", unique_name, name, message)); + throw gr::exception(fmt::format("propertyCallbackLifecycleState - cannot set block state w/o 'state' data msg: {}", message)); } std::string stateStr; try { stateStr = std::get(message.data.value().at("state")); } catch (const std::exception &e) { - throw gr::exception(fmt::format("block {} property {} state conversion throws {}, msg: {}", unique_name, propertyName, e.what(), message)); + throw gr::exception(fmt::format("propertyCallbackLifecycleState - state conversion throws {}, msg: {}", e.what(), message)); } catch (...) { - throw gr::exception(fmt::format("block {} property {} state conversion throws unknown exception, msg: {}", unique_name, propertyName, message)); + throw gr::exception(fmt::format("propertyCallbackLifecycleState - state conversion throws unknown exception, msg: {}", message)); } auto state = magic_enum::enum_cast(stateStr); if (!state.has_value()) { - throw gr::exception(fmt::format("block {} property {} invalid lifecycle::State conversion from {}, msg: {}", unique_name, propertyName, stateStr, message)); + throw gr::exception(fmt::format("propertyCallbackLifecycleState - invalid lifecycle::State conversion from {}, msg: {}", stateStr, message)); } if (auto e = this->changeStateTo(state.value()); !e) { - throw gr::exception(fmt::format("error in state transition block {} property {} - what: {}", // - unique_name, propertyName, e.error().message, e.error().sourceLocation, e.error().errorTime)); + throw gr::exception(fmt::format("propertyCallbackLifecycleState - error in state transition - what: {}", // + e.error().message, e.error().sourceLocation, e.error().errorTime)); } return std::nullopt; } else if (message.cmd == Get) { @@ -1089,7 +1089,7 @@ class Block : public lifecycle::StateMachine, // return std::nullopt; } - throw gr::exception(fmt::format("block {} property {} does not implement command {}, msg: {}", unique_name, propertyName, message.cmd, message)); + throw gr::exception(fmt::format("propertyCallbackLifecycleState - does not implement command {}, msg: {}", message.cmd, message)); } std::optional @@ -1125,6 +1125,16 @@ class Block : public lifecycle::StateMachine, // propertyCallbackStagedSettings(std::string_view propertyName, Message message) { using enum gr::message::Command; assert(propertyName == block::property::kStagedSetting); + const auto keys = [](const property_map &map) noexcept { + std::string result; + for (const auto &pair : map) { + if (!result.empty()) { + result += ", "; + } + result += pair.first; + } + return result; + }; if (message.cmd == Set) { if (!message.data.has_value()) { @@ -1143,8 +1153,7 @@ class Block : public lifecycle::StateMachine, // return std::nullopt; } - const auto keys = [](const auto &map) { return fmt::join(map | std::views::transform([](const auto &pair) { return pair.first; }), ", "); }; - throw gr::exception(fmt::format("block {} (aka. {}) could not set fields: {}\nvs. available: {}", unique_name, name, keys(notSet), keys(settings().get()))); + throw gr::exception(fmt::format("propertyCallbackStagedSettings - could not set fields: {}\nvs. available: {}", keys(notSet), keys(settings().get()))); } else if (message.cmd == Get) { message.data = self().settings().stagedParameters(); return message; diff --git a/core/include/gnuradio-4.0/Settings.hpp b/core/include/gnuradio-4.0/Settings.hpp index 95218085..618f6b7b 100644 --- a/core/include/gnuradio-4.0/Settings.hpp +++ b/core/include/gnuradio-4.0/Settings.hpp @@ -306,15 +306,11 @@ class BasicSettings : public SettingsBase { _block->meta_information.value[memberName + "::visible"] = RawType::visible(); } - // detect whether field has one of the DEFAULT_TAGS signature + // detect whether field has one of the kDefaultTags signature if constexpr (traits::port::is_not_any_port_or_collection && !std::is_const_v && is_writable(member) && settings::isSupportedType()) { - meta::tuple_for_each( - [&memberName, this](auto &&default_tag) { - if (default_tag.shortKey() == memberName) { - _auto_forward.emplace(memberName); - } - }, - gr::tag::DEFAULT_TAGS); + if constexpr (std::ranges::find(gr::tag::kDefaultTags, std::string_view(get_display_name_const(member).c_str())) != gr::tag::kDefaultTags.cend()) { + _auto_forward.emplace(memberName); + } _auto_update.emplace(memberName); } }; diff --git a/core/include/gnuradio-4.0/Tag.hpp b/core/include/gnuradio-4.0/Tag.hpp index b8235698..9bd120b1 100644 --- a/core/include/gnuradio-4.0/Tag.hpp +++ b/core/include/gnuradio-4.0/Tag.hpp @@ -46,10 +46,10 @@ namespace gr { * ``` */ enum class TagPropagationPolicy { - TPP_DONT = 0, /*!< Scheduler doesn't propagate tags from in- to output. The block itself is free to insert tags. */ + TPP_DONT = 0, /*!< Scheduler doesn't propagate tags from in- to output. The block itself is free to insert tags. */ TPP_ALL_TO_ALL = 1, /*!< Propagate tags from all in- to all outputs. The scheduler takes care of that. */ TPP_ONE_TO_ONE = 2, /*!< Propagate tags from n. input to n. output. Requires same number of in- and outputs */ - TPP_CUSTOM = 3 /*!< Like TPP_DONT, but signals the block it should implement application-specific forwarding behaviour. */ + TPP_CUSTOM = 3 /*!< Like TPP_DONT, but signals the block it should implement application-specific forwarding behaviour. */ }; using property_map = pmtv::map_t; @@ -246,8 +246,9 @@ inline EM_CONSTEXPR_STATIC DefaultTag<"reset_default", bool, "", "reset block st inline EM_CONSTEXPR_STATIC DefaultTag<"store_default", bool, "", "store block settings as default"> STORE_DEFAULTS; inline EM_CONSTEXPR_STATIC DefaultTag<"end_of_stream", bool, "", "end of stream, receiver should change to DONE state"> END_OF_STREAM; -inline constexpr std::tuple DEFAULT_TAGS = { SAMPLE_RATE, SIGNAL_NAME, SIGNAL_UNIT, SIGNAL_MIN, SIGNAL_MAX, TRIGGER_NAME, TRIGGER_TIME, - TRIGGER_OFFSET, TRIGGER_META_INFO, CONTEXT, RESET_DEFAULTS, STORE_DEFAULTS, END_OF_STREAM }; +inline constexpr std::array kDefaultTags = { "sample_rate", "signal_name", "signal_quantity", "signal_unit", "signal_min", "signal_max", "trigger_name", + "trigger_time", "trigger_offset", "trigger_meta_info", "context", "reset_default", "store_default", "end_of_stream" }; + } // namespace tag } // namespace gr diff --git a/core/include/gnuradio-4.0/Transactions.hpp b/core/include/gnuradio-4.0/Transactions.hpp index c087ea75..078fd0df 100644 --- a/core/include/gnuradio-4.0/Transactions.hpp +++ b/core/include/gnuradio-4.0/Transactions.hpp @@ -84,15 +84,11 @@ class CtxSettings : public SettingsBase { _block->meta_information.value[memberName + "::visible"] = RawType::visible(); } - // detect whether field has one of the DEFAULT_TAGS signature + // detect whether field has one of the kDefaultTags signature if constexpr (traits::port::is_not_any_port_or_collection && !std::is_const_v && is_writable(member) && settings::isSupportedType()) { - meta::tuple_for_each( - [&memberName, this](auto &&default_tag) { - if (default_tag.shortKey() == memberName) { - _auto_forward.emplace(memberName); - } - }, - gr::tag::DEFAULT_TAGS); + if constexpr (std::ranges::find(gr::tag::kDefaultTags, std::string_view(get_display_name_const(member).c_str())) != gr::tag::kDefaultTags.cend()) { + _auto_forward.emplace(memberName); + } _auto_update.emplace(memberName); } }; diff --git a/core/test/qa_Settings.cpp b/core/test/qa_Settings.cpp index 0991778f..4c371cf0 100644 --- a/core/test/qa_Settings.cpp +++ b/core/test/qa_Settings.cpp @@ -247,7 +247,7 @@ const boost::ut::suite SettingsTests = [] { expect(eq(block1.settings().autoUpdateParameters().size(), 8UL)); expect(eq(block1.settings().autoForwardParameters().size(), 2UL)); // need to add 'n_samples_max' to forwarding list for the block to automatically forward it - // as the 'n_samples_max' tag is not part of the canonical 'gr::tag::DEFAULT_TAGS' list + // as the 'n_samples_max' tag is not part of the canonical 'gr::tag::kDefaultTags' list block1.settings().autoForwardParameters().emplace("n_samples_max"); expect(eq(block1.settings().autoForwardParameters().size(), 3UL)); // same check for block2