diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 1cc88f3..cba950e 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -36,7 +36,8 @@ jobs: strategy: matrix: os: [ ubuntu-latest ] - name: Build and test (${{ matrix.os }}) + compiler: [clang, gcc] + name: Build and test (${{ matrix.os }} - ${{matrix.compiler}}) runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v5 @@ -44,9 +45,9 @@ jobs: with: github_access_token: ${{ secrets.GITHUB_TOKEN }} - name: CMake configure - run: nix develop --command cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: nix develop .#${{matrix.compiler}} --command cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: CMake Build - run: nix develop --command cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j + run: nix develop .#${{matrix.compiler}} --command cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j - name: CMake Test working-directory: ${{github.workspace}}/build - run: nix develop --command ctest -C ${{ env.BUILD_TYPE }} + run: nix develop .#${{matrix.compiler}} --command ctest -C ${{ env.BUILD_TYPE }} --output-on-failure diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d09a6c..733f115 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +# GCC build struggles on mac and it requires to enforce this variable to empty +# to successfully link agains libraries. +# This option is valid only on Apple architectures +set(CMAKE_OSX_ARCHITECTURES "" CACHE STRING "") + cmake_minimum_required(VERSION 3.31.0) project(Simo VERSION 0.0.1) include(CPack) @@ -78,19 +83,17 @@ set_target_properties(Simo PROPERTIES target_compile_options(Simo PRIVATE -fno-exceptions) target_compile_options(Simo PUBLIC -fno-rtti) -find_package(Boost REQUIRED COMPONENTS unit_test_framework) +find_package(Boost REQUIRED) message("Boost version: ${Boost_VERSION}") find_package(glaze REQUIRED) -target_link_libraries(${PROJECT_NAME} PRIVATE glaze::glaze) +target_link_libraries(${PROJECT_NAME} PRIVATE Boost::boost glaze::glaze) if (DEBUG_CMAKE) message(STATUS "Boost include dirs: ${Boost_INCLUDE_DIRS}") endif () -target_include_directories(Simo SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) - set_target_properties(Simo PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1 @@ -111,6 +114,9 @@ target_include_directories(Simo_includes_only $ ) target_link_libraries(Simo_includes_only INTERFACE glaze::glaze) +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(Simo_includes_only INTERFACE -fno-gnu-unique) +endif() target_compile_options(Simo_includes_only INTERFACE -fno-rtti) # Avoid linking against Simo if (APPLE) @@ -154,9 +160,8 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake/SimoConfig.cmake" "${CM enable_testing() function(create_unit_test_executable name) add_executable("${name}") - target_include_directories("${name}" PRIVATE include) - target_include_directories("${name}" PRIVATE src) - target_link_libraries("${name}" PRIVATE Boost::unit_test_framework) + target_include_directories("${name}" PRIVATE include src tests) + target_link_libraries("${name}" PRIVATE Boost::boost) add_test("${name}" "${name}") if (ENABLE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") set_tests_properties("${name}" PROPERTIES @@ -256,4 +261,4 @@ if (ENABLE_DOCUMENTATION) doxygen_docs include src docs tests "${CMAKE_CURRENT_SOURCE_DIR}/README.md" ) -endif() \ No newline at end of file +endif() diff --git a/README.md b/README.md index fca3ac5..08052f1 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The build is going to be accessible in the `result` folder. Clone Simo and initialize the submodules: Build dependencies: -- Clang (recommended) +- Clang (recommended) or gcc - CMake >= 3.31.0 - Boost ([Boost.Test](https://www.boost.org/libs/test), [Boost.TypeIndex](https://www.boost.org/libs/type_index)) - [Glaze](https://github.com/stephenberry/glaze) @@ -69,6 +69,9 @@ This can be achieved with: - Good documented code - Proper unit-testing coverage +Default compiler is clang. There is support for gcc build as well. With `nix`, use `nix develop .#gcc` to obtain +a development environment that uses gcc as compiler. + ### IDEs You can see [this guide](./support/ides/clion/README.md) on how to use nix binaries to build in CLion. diff --git a/docs/guided_examples/1_FirstModule_and_SimoSim/FirstModule.cc b/docs/guided_examples/1_FirstModule_and_SimoSim/FirstModule.cc index a685923..ea99ed4 100644 --- a/docs/guided_examples/1_FirstModule_and_SimoSim/FirstModule.cc +++ b/docs/guided_examples/1_FirstModule_and_SimoSim/FirstModule.cc @@ -114,6 +114,8 @@ static Simo::Collections::SimoCollection collection{ .factory_list_size = FACTORY_LIST.size(), }; +SIMO_COLLECTION_DECLARATION; + // This exposes the collection SIMO_PUBLIC const Simo::Collections::SimoCollection* simo_get_collection() { return &collection; diff --git a/docs/guided_examples/1_FirstModule_and_SimoSim/README.md b/docs/guided_examples/1_FirstModule_and_SimoSim/README.md index 3ce95ea..6c1b0f8 100644 --- a/docs/guided_examples/1_FirstModule_and_SimoSim/README.md +++ b/docs/guided_examples/1_FirstModule_and_SimoSim/README.md @@ -103,7 +103,8 @@ class FirstModuleParameters : public Parameters { ## Expose modules and parameters for SimoSim `SimoSim` is an executable able to detect shared libraries at runtime that contains -modules. A shared library needs to expose a function named `simo_get_collection`. +modules. A shared library needs to expose a function named `simo_get_collection`and add `SIMO_COLLECTION_DECLARATION` +**only once per library**. With this function, `SimoSim` can load a collection of factories. A Factory tells `SimoSim` how to instantiate a module and the associated parameters. @@ -141,4 +142,4 @@ Try to create your own module and pack it into a collection. A more complete example is the `PingPongCollection` at `tests/collection/PingPongCollection.cc`. -The second example will show how to deal with module ports. \ No newline at end of file +The second example will show how to deal with module ports. diff --git a/flake.nix b/flake.nix index 06e65fb..c39ed77 100644 --- a/flake.nix +++ b/flake.nix @@ -29,6 +29,34 @@ system: let pkgs = import nixpkgs { inherit system; }; + simoBaseAttributes = { + pname = "simo"; + version = "0.0.1"; + src = ./.; + + nativeBuildInputs = with pkgs; [ + # Real build dependencies + cmake + ninja + doxygen + ]; + + buildInputs = with pkgs; [ + boost + glaze + ]; + + cmakeFlags = [ + "-DPORTABLE_BUILD=ON" + "-DENABLE_RELEASE_LTO=OFF" + "-DCMAKE_CXX_SCAN_FOR_MODULES=OFF" + ]; + + doCheck = true; + checkPhase = '' + ctest --output-on-failure + ''; + }; simo = pkgs.clangStdenv.mkDerivation { pname = "simo"; version = "0.0.1"; @@ -57,10 +85,16 @@ ctest --output-on-failure ''; }; + + derivationAttributes = { + default = pkgs.clangStdenv.mkDerivation simoBaseAttributes; + clang = pkgs.clangStdenv.mkDerivation simoBaseAttributes; + gcc = pkgs.gccStdenv.mkDerivation simoBaseAttributes; + }; in { - packages.default = simo; - checks.default = simo; + packages = derivationAttributes; + checks = derivationAttributes; formatter = nixpkgs.legacyPackages.${system}.nixfmt; @@ -70,7 +104,7 @@ stdenv = pkgs.clangStdenv; } { - inputsFrom = [ simo ]; + inputsFrom = [ derivationAttributes.default ]; packages = with pkgs; [ clang-tools ast-grep diff --git a/include/Simo/collection/Collection.h b/include/Simo/collection/Collection.h index 0ee8a9f..4454cde 100644 --- a/include/Simo/collection/Collection.h +++ b/include/Simo/collection/Collection.h @@ -22,9 +22,31 @@ #include "Simo/module/Module.h" -#define SIMO_COLLECTION_FUNCTION_NAME_SYMBOL "SIMO_COLLECTION_FUNCTION" +#define SIMO_STRINGIFY_IMPL(x) #x +#define SIMO_STRINGIFY(x) SIMO_STRINGIFY_IMPL(x) -#define SIMO_COLLECTION_FUNCTION_NAME_DEFAULT "simo_get_collection" +#define SIMO_COLLECTION_FUNCTION_NAME_SYMBOL SIMO_COLLECTION_FUNCTION +#define SIMO_COLLECTION_FUNCTION_NAME_SYMBOL_STR \ + SIMO_STRINGIFY(SIMO_COLLECTION_FUNCTION_NAME_SYMBOL) + +#define SIMO_COLLECTION_FUNCTION_NAME_DEFAULT simo_get_collection +#define SIMO_COLLECTION_FUNCTION_NAME_DEFAULT_STR \ + SIMO_STRINGIFY(SIMO_COLLECTION_FUNCTION_NAME_DEFAULT) + +#define SIMO_COLLECTION_SECTION_STR ".Simo.collection" + +#if defined(__APPLE__) +#define SIMO_COLLECTION_SECTION "__TEXT," SIMO_COLLECTION_SECTION_STR +#else +#define SIMO_COLLECTION_SECTION SIMO_COLLECTION_SECTION_STR +#endif + +#define SIMO_COLLECTION_DECLARATION_NAME(name) \ + extern SIMO_PUBLIC __attribute__((used, section(SIMO_COLLECTION_SECTION))) \ + const char SIMO_COLLECTION_FUNCTION_NAME_SYMBOL[] = SIMO_STRINGIFY(name); + +#define SIMO_COLLECTION_DECLARATION \ + SIMO_COLLECTION_DECLARATION_NAME(SIMO_COLLECTION_FUNCTION_NAME_DEFAULT) namespace Simo { class Module; @@ -100,7 +122,8 @@ enum struct SIMO_PUBLIC GET_COLLECTION_ERROR : std::uint8_t { DLOPEN_FAIL, NO_FUNCTION_NAME, NOT_A_DIRECTORY, - ALREADY_LOADED_LIBRARY + ALREADY_LOADED_LIBRARY, + NO_SIMO_COLLECTION_SECTION, }; struct SIMO_PUBLIC GetCollectionError { diff --git a/include/Simo/module/core/Collector.h b/include/Simo/module/core/Collector.h index ebae320..362679f 100644 --- a/include/Simo/module/core/Collector.h +++ b/include/Simo/module/core/Collector.h @@ -99,4 +99,4 @@ class Collector : public Module { }; } // namespace Simo::Modules::Core -#endif // SIMO_COLLECTOR_HH \ No newline at end of file +#endif // SIMO_COLLECTOR_HH diff --git a/src/collection/Collection.cc b/src/collection/Collection.cc index def51fc..92b2a0d 100644 --- a/src/collection/Collection.cc +++ b/src/collection/Collection.cc @@ -14,11 +14,259 @@ #include "Simo/collection/Collection.h" #include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) +#include +#elif defined(__linux__) +#include +#else +#error \ + "Unsupported platform: this source supports only macOS Mach-O and Linux ELF." +#endif #include #include #include +class unique_fd { + public: + explicit unique_fd(int fd = -1) noexcept : fd_(fd) {} + + ~unique_fd() { + if (fd_ >= 0) { + close(fd_); + } + } + + unique_fd(const unique_fd&) = delete; + unique_fd& operator=(const unique_fd&) = delete; + + unique_fd(unique_fd&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } + + unique_fd& operator=(unique_fd&& other) noexcept { + if (this != &other) { + if (fd_ >= 0) { + close(fd_); + } + + fd_ = other.fd_; + other.fd_ = -1; + } + + return *this; + } + + [[nodiscard]] int get() const noexcept { return fd_; } + + [[nodiscard]] explicit operator bool() const noexcept { return fd_ >= 0; } + + private: + int fd_; +}; + +bool read_exact(int fd, void* buffer, std::size_t size, off_t offset) { + auto* out = static_cast(buffer); + std::size_t total = 0; + + while (total < size) { + const ssize_t n = pread(fd, out + total, size - total, offset + total); + + if (n < 0) { + if (errno == EINTR) { + continue; + } + + return false; + } + + if (n == 0) { + return false; + } + + total += static_cast(n); + } + + return true; +} + +#if defined(__linux__) + +bool elf_string_at_equals(const std::vector& table, std::uint32_t offset, + std::string_view expected) { + if (offset >= table.size()) { + return false; + } + + const char* section_name = table.data() + offset; + const std::size_t remaining = table.size() - offset; + + return remaining > expected.size() && + std::memcmp(section_name, expected.data(), expected.size()) == 0 && + section_name[expected.size()] == '\0'; +} + +bool object_file_has_section_named_impl(int fd, std::string_view section_name) { + unsigned char ident[EI_NIDENT]{}; + + if (!read_exact(fd, ident, sizeof(ident), 0)) { + return false; + } + + if (ident[EI_MAG0] != ELFMAG0 || ident[EI_MAG1] != ELFMAG1 || + ident[EI_MAG2] != ELFMAG2 || ident[EI_MAG3] != ELFMAG3 || + ident[EI_CLASS] != ELFCLASS64) { + return false; + } + + Elf64_Ehdr ehdr{}; + if (!read_exact(fd, &ehdr, sizeof(ehdr), 0)) { + return false; + } + + if (ehdr.e_shoff == 0 || ehdr.e_shnum == 0 || ehdr.e_shstrndx == SHN_UNDEF || + ehdr.e_shstrndx >= ehdr.e_shnum) { + return false; + } + + std::vector sections(ehdr.e_shnum); + + if (!read_exact(fd, sections.data(), sections.size() * sizeof(Elf64_Shdr), + static_cast(ehdr.e_shoff))) { + return false; + } + + const Elf64_Shdr& section_name_table = sections[ehdr.e_shstrndx]; + + if (section_name_table.sh_size == 0) { + return false; + } + + std::vector names(section_name_table.sh_size); + + if (!read_exact(fd, names.data(), names.size(), + static_cast(section_name_table.sh_offset))) { + return false; + } + + for (const Elf64_Shdr& section : sections) { + if (elf_string_at_equals(names, section.sh_name, section_name)) { + return true; + } + } + + return false; +} + +#elif defined(__APPLE__) + +bool macho_fixed_name_equals(const char raw_name[16], + std::string_view expected) { + if (expected.size() > 16) { + return false; + } + + const std::size_t raw_len = strnlen(raw_name, 16); + + if (raw_len != expected.size()) { + return false; + } + + return std::memcmp(raw_name, expected.data(), expected.size()) == 0; +} + +bool object_file_has_section_named_impl(int fd, std::string_view section_name) { + mach_header_64 header{}; + + if (!read_exact(fd, &header, sizeof(header), 0)) { + return false; + } + + if (header.magic != MH_MAGIC_64) { + return false; + } + + off_t command_offset = static_cast(sizeof(mach_header_64)); + + for (std::uint32_t i = 0; i < header.ncmds; ++i) { + load_command lc{}; + + if (!read_exact(fd, &lc, sizeof(lc), command_offset)) { + return false; + } + + if (lc.cmdsize < sizeof(load_command)) { + return false; + } + + if (lc.cmd == LC_SEGMENT_64) { + segment_command_64 segment{}; + + if (lc.cmdsize < sizeof(segment_command_64)) { + return false; + } + + if (!read_exact(fd, &segment, sizeof(segment), command_offset)) { + return false; + } + + const off_t first_section_offset = + command_offset + static_cast(sizeof(segment_command_64)); + + const std::size_t section_table_size = + static_cast(segment.nsects) * sizeof(section_64); + + const std::size_t available_size = + lc.cmdsize - sizeof(segment_command_64); + + if (section_table_size > available_size) { + return false; + } + + for (std::uint32_t s = 0; s < segment.nsects; ++s) { + section_64 section{}; + + const off_t section_offset = + first_section_offset + static_cast(s * sizeof(section_64)); + + if (!read_exact(fd, §ion, sizeof(section), section_offset)) { + return false; + } + + if (macho_fixed_name_equals(section.sectname, section_name)) { + return true; + } + } + } + + command_offset += static_cast(lc.cmdsize); + } + + return false; +} + +#endif + +bool object_file_has_section_named(const std::filesystem::path& path, + std::string_view section_name) { + const unique_fd fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)}; + + if (!fd) { + return false; + } + + return object_file_has_section_named_impl(fd.get(), section_name); +} + namespace Simo::Collections { static_assert(std::is_standard_layout_v, "Factory must be standard layout"); @@ -83,15 +331,15 @@ CollectionWithLib::CollectionWithLib(CollectionWithLib&& other) noexcept CollectionWithLib& CollectionWithLib::operator=( CollectionWithLib&& other) noexcept { - if (this != &other) { - collection = std::exchange(other.collection, nullptr); - if (lib_handle != nullptr) { - dlclose(lib_handle); - } - lib_handle = std::exchange(other.lib_handle, nullptr); + if (this == &other) { + return *this; } - path = other.path; - other.path = ""; + if (lib_handle != nullptr) { + dlclose(lib_handle); + } + path = std::move(other.path); + collection = std::exchange(other.collection, nullptr); + lib_handle = std::exchange(other.lib_handle, nullptr); return *this; } @@ -107,6 +355,15 @@ const SimoCollection* CollectionWithLib::get_collection() const noexcept { std::expected simo_get_collection( const std::filesystem::path& path_to_collection) { + // Check if section is present + if (std::filesystem::exists(path_to_collection) && + !object_file_has_section_named(path_to_collection, + SIMO_COLLECTION_SECTION_STR)) { + return std::unexpected( + {.error_code = GET_COLLECTION_ERROR::NO_SIMO_COLLECTION_SECTION, + .error_message = "No section "}); + } + dlerror(); // Probe to see if the library has already loaded. void* probe_lib = dlopen(path_to_collection.c_str(), RTLD_LAZY | RTLD_NOLOAD); if (probe_lib != nullptr) { @@ -116,6 +373,7 @@ std::expected simo_get_collection( {.error_code = GET_COLLECTION_ERROR::ALREADY_LOADED_LIBRARY, .error_message = ""}); } + dlerror(); void* lib = dlopen(path_to_collection.c_str(), RTLD_NOW | RTLD_LOCAL); if (lib == nullptr) { return std::unexpected( @@ -124,12 +382,12 @@ std::expected simo_get_collection( } dlerror(); // Clear any existing error const char* collection_function_name = reinterpret_cast( - dlsym(lib, SIMO_COLLECTION_FUNCTION_NAME_SYMBOL)); + dlsym(lib, SIMO_COLLECTION_FUNCTION_NAME_SYMBOL_STR)); + // If SIMO_COLLECTION_FUNCTION_NAME_SYMBOL_STR is not found, fall-back to + // default value const char* function_name = collection_function_name != nullptr ? collection_function_name - : SIMO_COLLECTION_FUNCTION_NAME_DEFAULT; - // If SIMO_COLLECTION_FUNCTION_NAME_DEFAULT is not found, fall-back to default - // value + : SIMO_COLLECTION_FUNCTION_NAME_DEFAULT_STR; dlerror(); const auto get_simo_collection_fun = reinterpret_cast(dlsym(lib, function_name)); @@ -164,4 +422,4 @@ simo_get_collection_from_folder(const std::filesystem::path& folder_path) { } return collections; } -} // namespace Simo::Collections \ No newline at end of file +} // namespace Simo::Collections diff --git a/tests/Simo/MainLoopTests.cc b/tests/Simo/MainLoopTests.cc index d981920..1baa879 100644 --- a/tests/Simo/MainLoopTests.cc +++ b/tests/Simo/MainLoopTests.cc @@ -15,7 +15,7 @@ #include #include -#include +#include "support/BoostInclude.h" class TestModule : public Simo::Module { public: @@ -96,4 +96,4 @@ BOOST_AUTO_TEST_CASE(InitializationFailure) { std::stringstream ss; ss << initialize_success; BOOST_CHECK_EQUAL(ss.str(), initialization_success_str); -} \ No newline at end of file +} diff --git a/tests/Simo/SimulationContextTest.cc b/tests/Simo/SimulationContextTest.cc index bb2dc09..22fb28d 100644 --- a/tests/Simo/SimulationContextTest.cc +++ b/tests/Simo/SimulationContextTest.cc @@ -14,13 +14,14 @@ #include -#include #include #include #include #include #include +#include "support/BoostInclude.h" + class InitTrackingModule final : public Simo::Module { public: Simo::InitializationStatus initialize( diff --git a/tests/Simo/UnitTestMain.cc b/tests/Simo/UnitTestMain.cc index 80e1093..b9e7f53 100644 --- a/tests/Simo/UnitTestMain.cc +++ b/tests/Simo/UnitTestMain.cc @@ -13,4 +13,4 @@ // limitations under the License. #define BOOST_TEST_MODULE Simo -#include \ No newline at end of file +#include "support/BoostInclude.h" diff --git a/tests/collection/CollectionTest.cc b/tests/collection/CollectionTest.cc index bd6bb5e..4c44f22 100644 --- a/tests/collection/CollectionTest.cc +++ b/tests/collection/CollectionTest.cc @@ -15,12 +15,13 @@ #define BOOST_TEST_MODULE SimoCollections #include -#include #include #include #include #include +#include "support/BoostInclude.h" + #if defined(__APPLE__) #define DYNLIB_EXT "dylib" #else diff --git a/tests/collection/PingPongCollection.cc b/tests/collection/PingPongCollection.cc index ef0bc49..8727065 100644 --- a/tests/collection/PingPongCollection.cc +++ b/tests/collection/PingPongCollection.cc @@ -155,7 +155,9 @@ static Simo::Collections::SimoCollection collection{ .factory_list_size = FACTORY_LIST.size(), }; +SIMO_COLLECTION_DECLARATION; + SIMO_PUBLIC const Simo::Collections::SimoCollection* simo_get_collection() { return &collection; } -} \ No newline at end of file +} diff --git a/tests/core/RadixHeap/RadixHeapTest.cc b/tests/core/RadixHeap/RadixHeapTest.cc index 095bc2f..f2370cf 100644 --- a/tests/core/RadixHeap/RadixHeapTest.cc +++ b/tests/core/RadixHeap/RadixHeapTest.cc @@ -15,7 +15,7 @@ #define BOOST_TEST_MODULE RadixHeap #include -#include +#include "support/BoostInclude.h" using Simo::Internal::RadixHeap; @@ -95,4 +95,4 @@ BOOST_AUTO_TEST_CASE(pushElementReorder) { heap.push(0); BOOST_CHECK_EQUAL(heap.peek(), 0); BOOST_CHECK_EQUAL(heap.size(), 1); -} \ No newline at end of file +} diff --git a/tests/core/TimePeriod/TimePeriodTest.cc b/tests/core/TimePeriod/TimePeriodTest.cc index 4b22483..7cb903b 100644 --- a/tests/core/TimePeriod/TimePeriodTest.cc +++ b/tests/core/TimePeriod/TimePeriodTest.cc @@ -13,12 +13,12 @@ // limitations under the License. #define BOOST_TEST_MODULE SimoTimePeriod -#include #include #include #include #include "Simo/Simo.h" +#include "support/BoostInclude.h" BOOST_AUTO_TEST_CASE(TimeUnitConversions) { using Simo::Time; @@ -109,4 +109,4 @@ BOOST_AUTO_TEST_CASE(TimeJsonSerializationAndParsing) { glz::read_json(unchanged, R"({"time":"bad","unit":"PS"})"); BOOST_CHECK(invalid_error); BOOST_CHECK_EQUAL(unchanged.to_picoseconds(), 99U); -} \ No newline at end of file +} diff --git a/tests/module/ModuleTest.cc b/tests/module/ModuleTest.cc index 60b8f3b..f190dab 100644 --- a/tests/module/ModuleTest.cc +++ b/tests/module/ModuleTest.cc @@ -16,11 +16,12 @@ #include #include -#include #include #include #include +#include "support/BoostInclude.h" + namespace fs = std::filesystem; namespace { diff --git a/tests/parameter/ParameterTest.cc b/tests/parameter/ParameterTest.cc index 734a054..c6ee045 100644 --- a/tests/parameter/ParameterTest.cc +++ b/tests/parameter/ParameterTest.cc @@ -16,10 +16,11 @@ #include #include -#include #include #include +#include "support/BoostInclude.h" + BOOST_AUTO_TEST_CASE(ParameterTyped_default_constructor_and_setters) { using Simo::Parameter::ParameterTyped; diff --git a/tests/statistics/StatisticsTest.cc b/tests/statistics/StatisticsTest.cc index f4191ee..3736e96 100644 --- a/tests/statistics/StatisticsTest.cc +++ b/tests/statistics/StatisticsTest.cc @@ -23,11 +23,12 @@ #include #include -#include #include #include #include +#include "support/BoostInclude.h" + namespace { class NamedPort final : public Simo::Port { diff --git a/tests/support/BoostInclude.h b/tests/support/BoostInclude.h new file mode 100644 index 0000000..59c2de6 --- /dev/null +++ b/tests/support/BoostInclude.h @@ -0,0 +1,19 @@ +// Copyright 2026 Matteo Fusi and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if !defined(BOOST_TEST_MODULE) && !defined(SIMO_TESTS_NO_MAIN_INCLUDE) +#include +#else +#include +#endif