This is an automated email from the ASF dual-hosted git repository.

pnoltes pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/celix.git

commit 84fed65e9ac1b9e29a5a30bef9d4a026d26cf897
Author: Pepijn Noltes <[email protected]>
AuthorDate: Sat Sep 13 17:16:14 2025 +0200

    Update macOS workflow to  use MacOS 15
    
    To get MacOS 15 working, several changes were needed:
    - Changed Python setup in the workflow to use actions/setup-python.
    - Added gettext dependency for MacOS 15 in Conanfile.
    - Fixed zlib version conflict in Conanfile by specifying zlib/1.3.1.
    - Removed unnecessary `main.cc` file from gtest.
    - Introduced CURL error injection support in the framework.
    - Added new error injector for CURL with corresponding tests.
    - Refactored various files to remove redundant 'template' keywords.
    - Updated benchmarks to use constexpr for maximum length.
---
 .github/workflows/macos.yml                        | 14 +++--
 .../src/ConfiguredDiscoveryManager.cc              | 72 +++++++++++-----------
 .../remote_service_admin_dfi/gtest/CMakeLists.txt  |  4 +-
 .../remote_service_admin_dfi/gtest/src/main.cc     | 32 ----------
 bundles/shell/shell_tui/src/shell_tui.c            |  2 +-
 cmake/Findcivetweb.cmake                           | 10 +--
 cmake/celix_project/CelixProject.cmake             | 12 ++--
 cmake/cmake_celix/BundlePackaging.cmake            |  4 +-
 conanfile.py                                       | 12 ++--
 examples/conan_test_package/conanfile.py           |  2 -
 examples/conan_test_package_v2/conanfile.py        |  2 -
 libs/dfi/gtest/src/dyn_type_tests.cpp              |  2 +-
 libs/error_injector/CMakeLists.txt                 |  5 ++
 .../error_injector/curl/CMakeLists.txt             | 30 ++++-----
 libs/error_injector/curl/include/curl_ei.h         | 36 +++++++++++
 libs/error_injector/curl/src/curl_ei.cc            | 31 ++++++++++
 libs/framework/gtest/CMakeLists.txt                | 15 +++++
 .../CelixLauncherCurlErrorInjectionTestSuite.cc    | 68 ++++++++++++++++++++
 libs/framework/gtest/src/CelixLauncherTestSuite.cc |  2 +-
 libs/framework/include/celix/Trackers.h            | 12 ++--
 libs/framework/include/celix/dm/Component_Impl.h   |  3 +-
 .../include/celix/dm/ServiceDependency_Impl.h      |  7 ++-
 libs/framework/src/celix_launcher.c                | 71 ++++++++++++++++++---
 libs/framework/src/celix_libloader.c               |  3 +-
 libs/promises/api/celix/Promise.h                  |  6 +-
 libs/utils/benchmark/src/StringHashmapBenchmark.cc |  2 +-
 26 files changed, 318 insertions(+), 141 deletions(-)

diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 3ae2a876a..61bfd9eba 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -16,15 +16,17 @@ env:
 jobs:
 
   macos-build-conan:
-    runs-on: macOS-13
+    runs-on: macOS-15
     timeout-minutes: 120
     steps:
       - name: Checkout source code
         uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0
-      - name: Install conan
-        run: |
-          brew install python ninja
-          pip3 install -U conan
+      - name: Set up Python
+        uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c 
#v4.9.1
+        with:
+          python-version: '3.x'        
+      - name: Install Conan
+        run: pip install conan
       - name: Setup Conan Profile
         run: |
           conan profile detect -f
@@ -70,7 +72,7 @@ jobs:
           source deactivate_conanrun.sh
 
   macos-build-brew:
-    runs-on: macOS-14
+    runs-on: macOS-15
     timeout-minutes: 120
     steps:
       - name: Checkout source code
diff --git 
a/bundles/cxx_remote_services/discovery_configured/src/ConfiguredDiscoveryManager.cc
 
b/bundles/cxx_remote_services/discovery_configured/src/ConfiguredDiscoveryManager.cc
index fc47bbcaa..b8ad63265 100644
--- 
a/bundles/cxx_remote_services/discovery_configured/src/ConfiguredDiscoveryManager.cc
+++ 
b/bundles/cxx_remote_services/discovery_configured/src/ConfiguredDiscoveryManager.cc
@@ -19,29 +19,26 @@
 
 #include <ConfiguredDiscoveryManager.h>
 
-#include <optional>
-#include <fstream>
 #include <filesystem>
+#include <fstream>
+#include <optional>
 #include <rapidjson/writer.h>
 
-#define L_TRACE(...) \
-        logHelper.trace(__VA_ARGS__);
-#define L_DEBUG(...) \
-        logHelper.debug(__VA_ARGS__);
-#define L_INFO(...) \
-        logHelper.info(__VA_ARGS__);
-#define L_WARN(...) \
-        logHelper.warning(__VA_ARGS__);
-#define L_ERROR(...) \
-        logHelper.error(__VA_ARGS__);
+#define L_TRACE(...) logHelper.trace(__VA_ARGS__);
+#define L_DEBUG(...) logHelper.debug(__VA_ARGS__);
+#define L_INFO(...) logHelper.info(__VA_ARGS__);
+#define L_WARN(...) logHelper.warning(__VA_ARGS__);
+#define L_ERROR(...) logHelper.error(__VA_ARGS__);
 
-static constexpr const char* ENDPOINT_ARRAY = "endpoints";
+#define ENDPOINT_ARRAY "endpoints"
 
+namespace /*anon*/
+{
+    
 static std::optional<std::string> readFile(const std::string& path) {
-
     std::string contents;
     std::ifstream file(path);
-    if(!file) {
+    if (!file) {
         throw celix::rsa::RemoteServicesException{"Cannot open file"};
     }
     file.seekg(0, std::ios::end);
@@ -52,17 +49,18 @@ static std::optional<std::string> readFile(const 
std::string& path) {
     return contents;
 }
 
-static rapidjson::Document parseJSONFile(std::string& contents)  {
-
+static rapidjson::Document parseJSONFile(std::string& contents) {
     rapidjson::Document resultDocument{};
     resultDocument.ParseInsitu(contents.data());
     return resultDocument;
 }
 
-celix::rsa::ConfiguredDiscoveryManager::ConfiguredDiscoveryManager(std::shared_ptr<celix::BundleContext>
 _ctx) :
-        ctx{std::move(_ctx)},
-        
configuredDiscoveryFiles{ctx->getConfigProperty(celix::rsa::CONFIGURED_DISCOVERY_DISCOVERY_FILES,
 "")},
-        logHelper{ctx, celix::typeName<ConfiguredDiscoveryManager>()}{
+} // namespace
+
+celix::rsa::ConfiguredDiscoveryManager::ConfiguredDiscoveryManager(std::shared_ptr<celix::BundleContext>
 _ctx)
+    : ctx{std::move(_ctx)},
+      
configuredDiscoveryFiles{ctx->getConfigProperty(celix::rsa::CONFIGURED_DISCOVERY_DISCOVERY_FILES,
 "")},
+      logHelper{ctx, celix::typeName<ConfiguredDiscoveryManager>()} {
     readConfiguredDiscoveryFiles();
 }
 
@@ -78,12 +76,13 @@ void 
celix::rsa::ConfiguredDiscoveryManager::readConfiguredDiscoveryFiles() {
     }
 }
 
-celix::Properties 
celix::rsa::ConfiguredDiscoveryManager::convertToEndpointProperties(const 
rapidjson::Value &endpointJSON) {
+celix::Properties
+celix::rsa::ConfiguredDiscoveryManager::convertToEndpointProperties(const 
rapidjson::Value& endpointJSON) {
     celix::Properties result{};
     result.set(celix::rsa::ENDPOINT_FRAMEWORK_UUID, 
ctx->getFramework()->getUUID());
     for (auto it = endpointJSON.MemberBegin(); it != endpointJSON.MemberEnd(); 
++it) {
         if (it->value.IsString()) {
-            if (celix_utils_stringEquals(it->name.GetString(), 
"endpoint.objectClass")) { //TODO improve
+            if (celix_utils_stringEquals(it->name.GetString(), 
"endpoint.objectClass")) { // TODO improve
                 result.set(celix::SERVICE_NAME, it->value.GetString());
             } else {
                 result.set(it->name.GetString(), it->value.GetString());
@@ -97,11 +96,12 @@ celix::Properties 
celix::rsa::ConfiguredDiscoveryManager::convertToEndpointPrope
                     strArray.append(entry.GetString());
                     strArray.append(",");
                 } else {
-                    L_WARN("Cannot parse endpoint member %s. Cannot parse 
array where the elements are not strings", it->name.GetString());
+                    L_WARN("Cannot parse endpoint member %s. Cannot parse 
array where the elements are not strings",
+                           it->name.GetString());
                     continue;
                 }
             }
-            result.set(it->name.GetString(), strArray.substr(0, 
strArray.size() -1 /*remote last ","*/));
+            result.set(it->name.GetString(), strArray.substr(0, 
strArray.size() - 1 /*remote last ","*/));
         } else {
             L_WARN("Cannot parse endpoint member %s. Type is %i", 
it->name.GetString(), (int)it->value.GetType());
         }
@@ -117,20 +117,23 @@ void 
celix::rsa::ConfiguredDiscoveryManager::addConfiguredDiscoveryFile(const st
             auto parsedJson = parseJSONFile(contents.value());
             if (parsedJson.IsObject()) {
                 if (parsedJson.HasMember(ENDPOINT_ARRAY) && 
parsedJson[ENDPOINT_ARRAY].IsArray()) {
-
-                    for (auto &jsonEndpoint : 
parsedJson[ENDPOINT_ARRAY].GetArray()) {
+                    for (auto& jsonEndpoint : 
parsedJson[ENDPOINT_ARRAY].GetArray()) {
                         try {
                             auto endpointProperties = 
convertToEndpointProperties(jsonEndpoint);
-                            auto endpointDescription = 
std::make_shared<EndpointDescription>(
-                                    std::move(endpointProperties));
-                            L_TRACE("Created endpoint description from %s: 
%s", path.c_str(), endpointDescription->toString().c_str())
-
-                            auto reg = 
ctx->registerService<celix::rsa::EndpointDescription>(
-                                            std::move(endpointDescription))
+                            auto endpointDescription =
+                                
std::make_shared<EndpointDescription>(std::move(endpointProperties));
+                            L_TRACE("Created endpoint description from %s: %s",
+                                    path.c_str(),
+                                    endpointDescription->toString().c_str())
+
+                            auto reg =
+                                
ctx->registerService<celix::rsa::EndpointDescription>(std::move(endpointDescription))
                                     .build();
                             newEndpoints.emplace_back(std::move(reg));
                         } catch (celix::rsa::RemoteServicesException& e) {
-                            L_ERROR("Error creating EndpointDescription from 
endpoints entry in JSON from path %s: %s", path.c_str(), e.what());
+                            L_ERROR("Error creating EndpointDescription from 
endpoints entry in JSON from path %s: %s",
+                                    path.c_str(),
+                                    e.what());
                         }
                     }
                 }
@@ -158,4 +161,3 @@ std::vector<std::string> 
celix::rsa::ConfiguredDiscoveryManager::getConfiguredDi
     }
     return result;
 }
-
diff --git 
a/bundles/remote_services/remote_service_admin_dfi/gtest/CMakeLists.txt 
b/bundles/remote_services/remote_service_admin_dfi/gtest/CMakeLists.txt
index 9d023c128..124158889 100644
--- a/bundles/remote_services/remote_service_admin_dfi/gtest/CMakeLists.txt
+++ b/bundles/remote_services/remote_service_admin_dfi/gtest/CMakeLists.txt
@@ -38,7 +38,6 @@ target_link_libraries(rsa_dfi_tst_bundle PRIVATE 
calculator_api remote_example_a
 target_include_directories(rsa_dfi_tst_bundle PRIVATE src)
 
 add_executable(test_rsa_dfi
-    src/main.cc
     src/rsa_tests.cc
     src/rsa_client_server_tests.cc
 )
@@ -48,13 +47,12 @@ celix_deprecated_framework_headers(test_rsa_dfi)
 
 target_link_libraries(test_rsa_dfi PRIVATE
         civetweb::civetweb
-        CURL::libcurl
         Celix::framework
         Celix::rsa_common
         calculator_api
         GTest::gtest
+        GTest::gtest_main
         libffi::libffi
-        ${LIBXML2_LIBRARIES} # work around memory leak reported by ASAN
 )
 
 get_property(rsa_bundle_file TARGET rsa_dfi PROPERTY BUNDLE_FILE)
diff --git a/bundles/remote_services/remote_service_admin_dfi/gtest/src/main.cc 
b/bundles/remote_services/remote_service_admin_dfi/gtest/src/main.cc
deleted file mode 100644
index 9b7f7645d..000000000
--- a/bundles/remote_services/remote_service_admin_dfi/gtest/src/main.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- *Licensed to the Apache Software Foundation (ASF) under one
- *or more contributor license agreements.  See the NOTICE file
- *distributed with this work for additional information
- *regarding copyright ownership.  The ASF licenses this file
- *to you 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.
- */
-
-#include <curl/curl.h>
-#include <civetweb.h>
-#include <gtest/gtest.h>
-
-int main(int argc, char **argv) {
-    curl_global_init(CURL_GLOBAL_ALL);
-    mg_init_library(MG_FEATURES_ALL);
-    ::testing::InitGoogleTest(&argc, argv);
-    int rc = RUN_ALL_TESTS();
-    mg_exit_library();
-    curl_global_cleanup();
-    return rc;
-}
\ No newline at end of file
diff --git a/bundles/shell/shell_tui/src/shell_tui.c 
b/bundles/shell/shell_tui/src/shell_tui.c
index 1d8e78f83..e02a78d63 100644
--- a/bundles/shell/shell_tui/src/shell_tui.c
+++ b/bundles/shell/shell_tui/src/shell_tui.c
@@ -47,7 +47,7 @@
 #define KEY_DEL1               '3'
 #define KEY_DEL2               '~'
 
-const char * const SHELL_NOT_AVAILABLE_MSG = "[Shell TUI] Shell service not 
available.";
+#define SHELL_NOT_AVAILABLE_MSG "[Shell TUI] Shell service not available."
 
 struct shell_tui {
     celix_bundle_context_t* ctx;
diff --git a/cmake/Findcivetweb.cmake b/cmake/Findcivetweb.cmake
index dc8aa91ec..1488e9b3e 100644
--- a/cmake/Findcivetweb.cmake
+++ b/cmake/Findcivetweb.cmake
@@ -18,16 +18,18 @@
 find_package(civetweb CONFIG QUIET)
 if (NOT civetweb_FOUND)
     include(FetchContent)
-    set(CIVETWEB_ENABLE_WEBSOCKETS TRUE CACHE BOOL "" FORCE)
-    set(CIVETWEB_BUILD_TESTING FALSE CACHE BOOL "" FORCE)
-    set(BUILD_SHARED_LIBS TRUE CACHE BOOL "" FORCE)
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error")
     FetchContent_Declare(
             civetweb
             GIT_REPOSITORY https://github.com/civetweb/civetweb.git
 #            GIT_REPOSITORY https://gitee.com/mirrors/civetweb.git
             GIT_TAG        d7ba35bbb649209c66e582d5a0244ba988a15159 # V1.16
     )
+
+    set(CIVETWEB_ENABLE_WEBSOCKETS TRUE CACHE BOOL "" FORCE)
+    set(CIVETWEB_BUILD_TESTING FALSE CACHE BOOL "" FORCE)
+    set(BUILD_SHARED_LIBS TRUE CACHE BOOL "" FORCE)
+    set(CMAKE_C_FLAGS "-Wno-error")
+
     FetchContent_MakeAvailable(civetweb)
     if (NOT TARGET civetweb::civetweb)
         add_library(civetweb::civetweb ALIAS civetweb-c-library)
diff --git a/cmake/celix_project/CelixProject.cmake 
b/cmake/celix_project/CelixProject.cmake
index 96b6e1ea8..0f3132744 100644
--- a/cmake/celix_project/CelixProject.cmake
+++ b/cmake/celix_project/CelixProject.cmake
@@ -26,9 +26,13 @@ mark_as_advanced(CLEAR ENABLE_THREAD_SANITIZER)
 
 if (ENABLE_ADDRESS_SANITIZER)
     if("${CMAKE_C_COMPILER_ID}" MATCHES "Clang")
+        set(CMAKE_C_FLAGS "-DCELIX_ASAN_ENABLED ${CMAKE_C_FLAGS}")
         set(CMAKE_C_FLAGS "-shared-libasan -fsanitize=address 
-fno-omit-frame-pointer ${CMAKE_C_FLAGS}")
         set(CMAKE_CXX_FLAGS "-shared-libasan -fsanitize=address 
-fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}")
-        if (NOT APPLE)
+        if (APPLE)
+            set(CMAKE_EXE_LINKER_FLAGS "-fsanitize=address 
${CMAKE_EXE_LINKER_FLAGS}")
+            set(CMAKE_SHARED_LINKER_FLAGS "-fsanitize=address 
${CMAKE_SHARED_LINKER_FLAGS}")
+        else ()
             # Fix a linux clang deficiency where the ASan runtime library is 
not found automatically
             # Find the ASan runtime library path and set RPATH
             execute_process(
@@ -51,16 +55,12 @@ if (ENABLE_ADDRESS_SANITIZER)
             endif()
         endif ()
     elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
+        set(CMAKE_C_FLAGS "-DCELIX_ASAN_ENABLED ${CMAKE_C_FLAGS}")
         set(CMAKE_C_FLAGS "-lasan -fsanitize=address -fno-omit-frame-pointer 
${CMAKE_C_FLAGS}")
         set(CMAKE_CXX_FLAGS "-lasan -fsanitize=address -fno-omit-frame-pointer 
${CMAKE_CXX_FLAGS}")
     else ()
         message(WARNING "Address sanitizer is not supported for 
${CMAKE_C_COMPILER_ID}")
     endif ()
-
-    if (ENABLE_TESTING)
-        set(CMAKE_C_FLAGS "-DCPPUTEST_MEM_LEAK_DETECTION_DISABLED 
${CMAKE_C_FLAGS}")
-        set(CMAKE_CXX_FLAGS "-DCPPUTEST_MEM_LEAK_DETECTION_DISABLED 
${CMAKE_CXX_FLAGS}")
-    endif ()
 endif()
 
 if (ENABLE_UNDEFINED_SANITIZER)
diff --git a/cmake/cmake_celix/BundlePackaging.cmake 
b/cmake/cmake_celix/BundlePackaging.cmake
index 9c1335e87..4200f390a 100644
--- a/cmake/cmake_celix/BundlePackaging.cmake
+++ b/cmake/cmake_celix/BundlePackaging.cmake
@@ -251,9 +251,7 @@ function(add_celix_bundle)
                 "BUNDLE_TARGET" "${BUNDLE_TARGET_NAME}_bundle"
         )
         target_link_libraries(${BUNDLE_TARGET_NAME} PRIVATE Celix::framework)
-        if(APPLE)
-            target_link_options(${BUNDLE_TARGET_NAME} PRIVATE 
"-Wl,-undefined,error")
-        else ()
+        if (LINUX)
             target_link_options(${BUNDLE_TARGET_NAME} PRIVATE "-Wl,-z,defs")
         endif()
     else ()
diff --git a/conanfile.py b/conanfile.py
index cfce0de63..30f6d190a 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -329,6 +329,8 @@ class CelixConan(ConanFile):
             self.requires("libzip/[>=1.7.3 <2.0.0]")
         if self.options.build_framework:
             self.requires("util-linux-libuuid/[>=2.39 <3.0.0]")
+            if self.settings.os == "Macos":
+                self.requires("gettext/0.21") #needed on MacOS 15 by libuuid
         if ((self.options.build_framework and self.options.framework_curlinit)
                 or self.options.build_celix_etcdlib
                 or self.options.build_rsa_discovery_common or 
self.options.build_rsa_remote_service_admin_dfi
@@ -350,9 +352,9 @@ class CelixConan(ConanFile):
             # TODO: To be replaced with mdnsresponder/1790.80.10, resolve some 
problems of mdnsresponder
             # https://github.com/conan-io/conan-center-index/pull/16254
             self.requires("mdnsresponder/1310.140.1")
-        # 'libzip/1.10.1' requires 'zlib/1.2.13' while 'libcurl/7.64.1' 
requires 'zlib/1.2.12'
         self.requires("openssl/[>=3.2.0]", override=True)
-        self.requires("zlib/1.2.13", override=True)
+        # Fix zlib to 1.3.1, 'libzip/1.10.1' and 'libcurl/7.64.1' requires 
different zlib versions causing conflicts
+        self.requires("zlib/1.3.1", override=True)
         if self.options.build_event_admin_remote_provider_mqtt:
             self.requires("mosquitto/[>=2.0.3 <3.0.0]")
         self.validate()
@@ -369,13 +371,15 @@ class CelixConan(ConanFile):
                 tc.cache_variables["BUILD_ERROR_INJECTOR_JANSSON"] = "ON"
             if "mosquitto" in lst:
                 tc.cache_variables["BUILD_ERROR_INJECTOR_MOSQUITTO"] = "ON"
+            if "libcurl" in lst:
+                tc.cache_variables["BUILD_ERROR_INJECTOR_CURL"] = "ON"
         tc.cache_variables["CELIX_ERR_BUFFER_SIZE"] = 
str(self.options.celix_err_buffer_size)
         # tc.cache_variables["CMAKE_PROJECT_Celix_INCLUDE"] = 
os.path.join(self.build_folder, "conan_paths.cmake")
         # the following is workaround for 
https://github.com/conan-io/conan/issues/7192
         if self.settings.os == "Linux":
             tc.cache_variables["CMAKE_EXE_LINKER_FLAGS"] = 
"-Wl,--unresolved-symbols=ignore-in-shared-libs"
-        elif self.settings.os == "Macos":
-            tc.cache_variables["CMAKE_EXE_LINKER_FLAGS"] = "-Wl,-undefined 
-Wl,dynamic_lookup"
+        #elif self.settings.os == "Macos":
+        #    tc.cache_variables["CMAKE_EXE_LINKER_FLAGS"] = "-Wl,undefined 
-Wl,dynamic_lookup"
         v = Version(self.version)
         tc.cache_variables["CELIX_MAJOR"] = str(v.major.value)
         tc.cache_variables["CELIX_MINOR"] = str(v.minor.value)
diff --git a/examples/conan_test_package/conanfile.py 
b/examples/conan_test_package/conanfile.py
index 6db8716d1..bdcb51686 100644
--- a/examples/conan_test_package/conanfile.py
+++ b/examples/conan_test_package/conanfile.py
@@ -61,8 +61,6 @@ class TestPackageConan(ConanFile):
         # the following is workaround 
https://github.com/conan-io/conan/issues/7192
         if self.settings.os == "Linux":
             cmake.definitions["CMAKE_EXE_LINKER_FLAGS"] = 
"-Wl,--unresolved-symbols=ignore-in-shared-libs"
-        elif self.settings.os == "Macos":
-            cmake.definitions["CMAKE_EXE_LINKER_FLAGS"] = "-Wl,-undefined 
-Wl,dynamic_lookup"
         cmake.configure()
         cmake.build()
 
diff --git a/examples/conan_test_package_v2/conanfile.py 
b/examples/conan_test_package_v2/conanfile.py
index bb202185c..0b57a9d8f 100644
--- a/examples/conan_test_package_v2/conanfile.py
+++ b/examples/conan_test_package_v2/conanfile.py
@@ -70,8 +70,6 @@ class TestPackageConan(ConanFile):
         # the following is workaround 
https://github.com/conan-io/conan/issues/7192
         if self.settings.os == "Linux":
             tc.cache_variables["CMAKE_EXE_LINKER_FLAGS"] = 
"-Wl,--unresolved-symbols=ignore-in-shared-libs"
-        elif self.settings.os == "Macos":
-            tc.cache_variables["CMAKE_EXE_LINKER_FLAGS"] = "-Wl,-undefined 
-Wl,dynamic_lookup"
         tc.user_presets_path = False
         tc.generate()
 
diff --git a/libs/dfi/gtest/src/dyn_type_tests.cpp 
b/libs/dfi/gtest/src/dyn_type_tests.cpp
index 84b4c9c6a..7f6ef037e 100644
--- a/libs/dfi/gtest/src/dyn_type_tests.cpp
+++ b/libs/dfi/gtest/src/dyn_type_tests.cpp
@@ -37,7 +37,7 @@ extern "C" {
         int i;
         int j;
         int nrOfBurst = 10;
-        int burst = 50;
+        constexpr int burst = 50;
         void *pointers[burst];
         for (j = 0; j < nrOfBurst; j += 1) {
             for (i = 0; i < burst ; i +=1 ) {
diff --git a/libs/error_injector/CMakeLists.txt 
b/libs/error_injector/CMakeLists.txt
index c34fd0d26..769f7bd67 100644
--- a/libs/error_injector/CMakeLists.txt
+++ b/libs/error_injector/CMakeLists.txt
@@ -53,3 +53,8 @@ celix_subproject(ERROR_INJECTOR_MOSQUITTO "Option to enable 
building the mosquit
 if (ERROR_INJECTOR_MOSQUITTO)
     add_subdirectory(mosquitto)
 endif ()
+
+celix_subproject(ERROR_INJECTOR_CURL "Option to enable building the curl error 
injector" ON)
+if (ERROR_INJECTOR_CURL)
+    add_subdirectory(curl)
+endif ()
diff --git a/cmake/Findcivetweb.cmake b/libs/error_injector/curl/CMakeLists.txt
similarity index 51%
copy from cmake/Findcivetweb.cmake
copy to libs/error_injector/curl/CMakeLists.txt
index dc8aa91ec..6aeae0f8f 100644
--- a/cmake/Findcivetweb.cmake
+++ b/libs/error_injector/curl/CMakeLists.txt
@@ -15,21 +15,15 @@
 # specific language governing permissions and limitations
 # under the License.
 
-find_package(civetweb CONFIG QUIET)
-if (NOT civetweb_FOUND)
-    include(FetchContent)
-    set(CIVETWEB_ENABLE_WEBSOCKETS TRUE CACHE BOOL "" FORCE)
-    set(CIVETWEB_BUILD_TESTING FALSE CACHE BOOL "" FORCE)
-    set(BUILD_SHARED_LIBS TRUE CACHE BOOL "" FORCE)
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error")
-    FetchContent_Declare(
-            civetweb
-            GIT_REPOSITORY https://github.com/civetweb/civetweb.git
-#            GIT_REPOSITORY https://gitee.com/mirrors/civetweb.git
-            GIT_TAG        d7ba35bbb649209c66e582d5a0244ba988a15159 # V1.16
-    )
-    FetchContent_MakeAvailable(civetweb)
-    if (NOT TARGET civetweb::civetweb)
-        add_library(civetweb::civetweb ALIAS civetweb-c-library)
-    endif ()
-endif()
+add_library(curl_ei STATIC src/curl_ei.cc)
+
+find_package(CURL REQUIRED)
+
+target_include_directories(curl_ei PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
+target_link_libraries(curl_ei PUBLIC Celix::error_injector CURL::libcurl)
+
+target_link_options(curl_ei INTERFACE
+        LINKER:--wrap,curl_global_init
+)
+
+add_library(Celix::curl_ei ALIAS curl_ei)
diff --git a/libs/error_injector/curl/include/curl_ei.h 
b/libs/error_injector/curl/include/curl_ei.h
new file mode 100644
index 000000000..80ddad351
--- /dev/null
+++ b/libs/error_injector/curl/include/curl_ei.h
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#ifndef CELIX_CURL_EI_H
+#define CELIX_CURL_EI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <curl/curl.h>
+#include "celix_error_injector.h"
+
+CELIX_EI_DECLARE(curl_global_init, CURLcode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //CELIX_CURL_EI_H
diff --git a/libs/error_injector/curl/src/curl_ei.cc 
b/libs/error_injector/curl/src/curl_ei.cc
new file mode 100644
index 000000000..d79b8974b
--- /dev/null
+++ b/libs/error_injector/curl/src/curl_ei.cc
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#include "curl_ei.h"
+
+extern "C" {
+
+CURLcode __real_curl_global_init(long flags);
+CELIX_EI_DEFINE(curl_global_init, CURLcode)
+CURLcode __wrap_curl_global_init(long flags) {
+    CELIX_EI_IMPL(curl_global_init);
+    return __real_curl_global_init(flags);
+}
+
+} // extern "C"
diff --git a/libs/framework/gtest/CMakeLists.txt 
b/libs/framework/gtest/CMakeLists.txt
index 08e0c4038..662af0984 100644
--- a/libs/framework/gtest/CMakeLists.txt
+++ b/libs/framework/gtest/CMakeLists.txt
@@ -179,4 +179,19 @@ if (EI_TESTS)
 
     add_test(NAME test_framework_with_ei COMMAND test_framework_with_ei)
     setup_target_for_coverage(test_framework_with_ei SCAN_DIR ..)
+
+    if (FRAMEWORK_CURLINIT)
+        #Note separate target, so that call to curl_global_init() can be 
intercepted (depends on global atomic counter)
+        add_executable(test_framework_with_curl_ei
+            src/CelixLauncherCurlErrorInjectionTestSuite.cc
+        )
+        target_link_libraries(test_framework_with_curl_ei PRIVATE 
+            framework_cut 
+            Celix::curl_ei
+            GTest::gtest GTest::gtest_main
+        )
+
+        add_test(NAME test_framework_with_curl_ei COMMAND 
test_framework_with_curl_ei)
+        setup_target_for_coverage(test_framework_with_curl_ei SCAN_DIR ..)
+    endif ()
 endif ()
diff --git 
a/libs/framework/gtest/src/CelixLauncherCurlErrorInjectionTestSuite.cc 
b/libs/framework/gtest/src/CelixLauncherCurlErrorInjectionTestSuite.cc
new file mode 100644
index 000000000..12aa82670
--- /dev/null
+++ b/libs/framework/gtest/src/CelixLauncherCurlErrorInjectionTestSuite.cc
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "celix_launcher.h"
+#include "curl_ei.h"
+
+#include <future>
+
+#define LAUNCH_WAIT_TIMEOUT_IN_MS 100
+
+
+class CelixLauncherCurlErrorInjectionTestSuite : public ::testing::Test {
+  public:
+    CelixLauncherCurlErrorInjectionTestSuite() {
+        celix_ei_expect_curl_global_init(nullptr, 0, CURLE_OK);
+    }
+
+    static void launchCelixWithCurlInitError() {
+        //Given an error injection for curl_global_init from 
celix_launcher_launchAndWait
+        celix_ei_expect_curl_global_init((void*)celix_launcher_launchAndWait, 
1, CURLE_FAILED_INIT);
+
+        //When calling celix_launcher_launchAndWait
+        auto rc = celix_launcher_launchAndWait(0, nullptr, nullptr);
+
+        //Then the expected error code should be returned
+        EXPECT_EQ(rc, 1);    
+    }
+};
+
+TEST_F(CelixLauncherCurlErrorInjectionTestSuite, LaunchCelixWithCurlInitError) 
{
+    launchCelixWithCurlInitError();
+
+    //When launcher several times, the result should be the same
+    for (int i = 0; i < 5; ++i) {
+        launchCelixWithCurlInitError();
+    }
+
+    //When launching without an error injection
+    celix_ei_expect_curl_global_init((void*)nullptr, 0, CURLE_OK);
+    std::future<int> futureRc = std::async(std::launch::async, [] {
+        return celix_launcher_launchAndWait(0, nullptr, nullptr);
+    });
+
+    //And framework is given time to launch
+    futureRc.wait_for(std::chrono::milliseconds(LAUNCH_WAIT_TIMEOUT_IN_MS));
+
+    //Then the framework should be launched successfully, and return a 0 code 
after exiting
+    celix_launcher_triggerStop();
+    EXPECT_EQ(futureRc.get(), 0);
+}
diff --git a/libs/framework/gtest/src/CelixLauncherTestSuite.cc 
b/libs/framework/gtest/src/CelixLauncherTestSuite.cc
index 4d3a69c81..de57a0211 100644
--- a/libs/framework/gtest/src/CelixLauncherTestSuite.cc
+++ b/libs/framework/gtest/src/CelixLauncherTestSuite.cc
@@ -32,7 +32,7 @@
 #include "celix_stdlib_cleanup.h"
 #include "celix_utils.h"
 
-#define LAUNCH_WAIT_TIMEOUT 100
+#define LAUNCH_WAIT_TIMEOUT 200
 
 class CelixLauncherTestSuite : public ::testing::Test {
   public:
diff --git a/libs/framework/include/celix/Trackers.h 
b/libs/framework/include/celix/Trackers.h
index 1521757e2..bccea25c1 100644
--- a/libs/framework/include/celix/Trackers.h
+++ b/libs/framework/include/celix/Trackers.h
@@ -383,7 +383,7 @@ namespace celix {
          */
         template<typename F>
         size_t useServices(const F& f) {
-            return this->template useServicesInternal(
+            return this->useServicesInternal(
                 [&f](I& svc, const celix::Properties&, const celix::Bundle&) { 
f(svc); });
         }
 
@@ -398,7 +398,7 @@ namespace celix {
          */
         template<typename F>
         size_t useServicesWithProperties(const F& f) {
-            return this->template useServicesInternal(
+            return this->useServicesInternal(
                 [&f](I& svc, const celix::Properties& props, const 
celix::Bundle&) { f(svc, props); });
         }
 
@@ -414,7 +414,7 @@ namespace celix {
          */
         template<typename F>
         size_t useServicesWithOwner(const F& f) {
-            return this->template useServicesInternal(
+            return this->useServicesInternal(
                 [&f](I& svc, const celix::Properties& props, const 
celix::Bundle& bnd) { f(svc, props, bnd); });
         }
 
@@ -429,7 +429,7 @@ namespace celix {
          */
         template<typename F>
         bool useService(const F& f) {
-            return this->template useServiceInternal(
+            return this->useServiceInternal(
                 [&f](I& svc, const celix::Properties&, const celix::Bundle&) { 
f(svc); });
         }
 
@@ -444,7 +444,7 @@ namespace celix {
          */
         template<typename F>
         bool useServiceWithProperties(const F& f) {
-            return this->template useServiceInternal(
+            return this->useServiceInternal(
                 [&f](I& svc, const celix::Properties& props, const 
celix::Bundle&) { f(svc, props); });
         }
 
@@ -460,7 +460,7 @@ namespace celix {
          */
         template<typename F>
         bool useServiceWithOwner(const F& f) {
-            return this->template useServiceInternal(
+            return this->useServiceInternal(
                 [&f](I& svc, const celix::Properties& props, const 
celix::Bundle& bnd) { f(svc, props, bnd); });
         }
     protected:
diff --git a/libs/framework/include/celix/dm/Component_Impl.h 
b/libs/framework/include/celix/dm/Component_Impl.h
index 284f44660..f6b50ad6f 100644
--- a/libs/framework/include/celix/dm/Component_Impl.h
+++ b/libs/framework/include/celix/dm/Component_Impl.h
@@ -385,7 +385,8 @@ Component<T>& Component<T>::removeCallbacks() {
 template<class T>
 Component<T>& Component<T>::addContext(std::shared_ptr<void> context) {
     std::lock_guard<std::mutex> lock{mutex};
-    componentContexts.template emplace_back(std::move(context));
+    //Removed superfluous 'template' keyword which triggered 
'-Wmissing-template-arg-list-after-template-kw'
+    componentContexts.emplace_back(std::move(context));
     return *this;
 }
 
diff --git a/libs/framework/include/celix/dm/ServiceDependency_Impl.h 
b/libs/framework/include/celix/dm/ServiceDependency_Impl.h
index 297e81fcd..7af3c1978 100644
--- a/libs/framework/include/celix/dm/ServiceDependency_Impl.h
+++ b/libs/framework/include/celix/dm/ServiceDependency_Impl.h
@@ -521,7 +521,7 @@ void ServiceDependency<T,I>::setupCallbacks() {
                 auto svc = std::shared_ptr<I>{static_cast<I*>(rawSvc), 
[](I*){/*nop*/}};
                 auto svcId = props->getAsLong(celix::SERVICE_ID, -1);
                 dep->addFpUsingSharedPtr(svc, props);
-                dep->addedServices.template emplace(svcId, 
std::make_pair(std::move(svc), std::move(props)));
+                dep->addedServices.emplace(svcId, 
std::make_pair(std::move(svc), std::move(props)));
             }
             return rc;
         };
@@ -541,8 +541,9 @@ void ServiceDependency<T,I>::setupCallbacks() {
                     std::weak_ptr<const celix::Properties> removedProps = 
it->second.second;
                     dep->removeFpUsingSharedPtr(it->second.first, 
it->second.second);
                     dep->addedServices.erase(it);
-                    dep->template waitForExpired(removedSvc, svcId, "service 
pointer");
-                    dep->template waitForExpired(removedProps, svcId, "service 
properties");
+                    //Removed superfluous 'template' keyword (no explicit 
template args supplied)
+                    dep->waitForExpired(removedSvc, svcId, "service pointer");
+                    dep->waitForExpired(removedProps, svcId, "service 
properties");
                 }
             }
             return rc;
diff --git a/libs/framework/src/celix_launcher.c 
b/libs/framework/src/celix_launcher.c
index ab6832943..7966fedcd 100644
--- a/libs/framework/src/celix_launcher.c
+++ b/libs/framework/src/celix_launcher.c
@@ -28,6 +28,7 @@
 #include <libgen.h>
 
 #ifndef CELIX_NO_CURLINIT
+#include <stdbool.h>
 #include <curl/curl.h>
 #endif
 
@@ -127,6 +128,33 @@ static celix_status_t 
celix_launcher_createBundleCache(celix_properties_t* embed
  */
 static celix_status_t celix_launcher_loadRuntimeProperties(const char* 
configFile, celix_properties_t** outConfigProperties);
 
+#ifndef CELIX_NO_CURLINIT
+/**
+ * @brief Initializes the CURL library if it has not been initialized yet.
+ *
+ * This function ensures that the CURL initialization function is 
+ * called only once, regardless of how many times a celix framework is 
launched.
+ *
+ * @return CELIX_SUCCESS if CURL was initialized successfully, or 
+ * CELIX_ILLEGAL_STATE if curl initialization failed.
+ */
+static celix_status_t celix_launcher_initializeCurl();
+
+/**
+ * @brief Cleans up the CURL library if it was previously initialized.
+ *
+ * This function is called with __attribute__(destructor) to ensure that the
+ * CURL cleanup function is called only once, regardless of how many times
+ * a (global) launched celix framework is stopped and started again.
+ */
+static void celix_launcher_cleanupCurl() __attribute__((destructor));
+
+/**
+ * @brief CURL initialization bool, used to check if CURL has been initialized.
+ */
+static bool g_curl_initialized = 0;
+#endif
+
 /**
  * @brief Set the global framework instance.
  */
@@ -186,12 +214,18 @@ int celix_launcher_launchAndWait(int argc, char* argv[], 
const char* embeddedCon
         
celix_bundleContext_log(celix_framework_getFrameworkContext(framework), 
CELIX_LOG_LEVEL_WARNING,
                                 "Failed to schedule celix_shutdown_check");
     }
-    celix_framework_waitForStop(framework);
-    celix_launcher_resetLauncher();
+
 #ifndef CELIX_NO_CURLINIT
-    // Cleanup Curl
-    curl_global_cleanup();
+    status = celix_launcher_initializeCurl();
+    if (status != CELIX_SUCCESS) {
+        celix_launcher_resetLauncher();
+        return CELIX_LAUNCHER_ERROR_EXIT_CODE;
+    }
 #endif
+
+    celix_framework_waitForStop(framework);
+    celix_launcher_resetLauncher();
+
     return CELIX_LAUNCHER_OK_EXIT_CODE;
 }
 
@@ -249,11 +283,6 @@ static celix_status_t 
celix_launcher_createFramework(celix_properties_t* embedde
     sigaction(SIGUSR1, &sigact, NULL);
     sigaction(SIGUSR2, &sigact, NULL);
 
-#ifndef CELIX_NO_CURLINIT
-    // Before doing anything else, lets setup Curl
-    curl_global_init(CURL_GLOBAL_ALL);
-#endif
-
     *frameworkOut = celix_frameworkFactory_createFramework(embeddedProps);
     return *frameworkOut != NULL ? CELIX_SUCCESS : CELIX_FRAMEWORK_EXCEPTION;
 }
@@ -449,3 +478,27 @@ static void celix_launcher_resetLauncher() {
     }
     g_launcher.launched = false;
 }
+
+#ifndef CELIX_NO_CURLINIT
+celix_status_t celix_launcher_initializeCurl() {
+    bool alreadyInitialized = __atomic_exchange_n(&g_curl_initialized, true, 
__ATOMIC_SEQ_CST);
+    if (alreadyInitialized) {
+        return CELIX_SUCCESS;
+    }
+    CURLcode cc = curl_global_init(CURL_GLOBAL_DEFAULT);
+    if (cc != CURLE_OK) {
+        fprintf(stderr, "Failed to initialize Curl: %s\n", 
curl_easy_strerror(cc));
+        //note only 1 framework can be launcher with celix launcher and the 
launcher is set, so no startup race
+        __atomic_store_n(&g_curl_initialized, false, __ATOMIC_SEQ_CST); 
+        return CELIX_ILLEGAL_STATE;
+    }
+    return CELIX_SUCCESS;
+}
+
+static void celix_launcher_cleanupCurl() {
+    bool wasInitialized = __atomic_load_n(&g_curl_initialized, 
__ATOMIC_SEQ_CST);
+    if (wasInitialized) {
+        curl_global_cleanup();
+    }
+}
+#endif
diff --git a/libs/framework/src/celix_libloader.c 
b/libs/framework/src/celix_libloader.c
index f11053bff..e0d539c01 100644
--- a/libs/framework/src/celix_libloader.c
+++ b/libs/framework/src/celix_libloader.c
@@ -24,11 +24,12 @@
 
 celix_library_handle_t* celix_libloader_open(celix_bundle_context_t *ctx, 
const char *libPath) {
     bool defaultNoDelete = true;
-#if defined(NDEBUG)
+#if defined(NDEBUG) && !defined(CELIX_ASAN_ENABLED)
     defaultNoDelete = false;
 #endif
     celix_library_handle_t* handle = NULL;
     bool noDelete = celix_bundleContext_getPropertyAsBool(ctx, 
CELIX_LOAD_BUNDLES_WITH_NODELETE, defaultNoDelete);
+
     int flags = RTLD_NOW|RTLD_LOCAL;
     if (noDelete) {
         flags = RTLD_NOW|RTLD_LOCAL|RTLD_NODELETE;
diff --git a/libs/promises/api/celix/Promise.h 
b/libs/promises/api/celix/Promise.h
index f16999243..e4481ce51 100644
--- a/libs/promises/api/celix/Promise.h
+++ b/libs/promises/api/celix/Promise.h
@@ -659,13 +659,15 @@ inline celix::Promise<void> 
celix::Promise<void>::timeout(std::chrono::duration<
 template<typename T>
 template<typename Rep, typename Period>
 inline celix::Promise<T>& 
celix::Promise<T>::setTimeout(std::chrono::duration<Rep, Period> duration) {
-    state->template setTimeout(duration);
+    //Removed superfluous 'template' keyword (no explicit template args 
supplied)
+    state->setTimeout(duration);
     return *this;
 }
 
 template<typename Rep, typename Period>
 inline celix::Promise<void>& 
celix::Promise<void>::setTimeout(std::chrono::duration<Rep, Period> duration) {
-    state->template setTimeout(duration);
+    //Removed superfluous 'template' keyword (no explicit template args 
supplied)
+    state->setTimeout(duration);
     return *this;
 }
 
diff --git a/libs/utils/benchmark/src/StringHashmapBenchmark.cc 
b/libs/utils/benchmark/src/StringHashmapBenchmark.cc
index 4552dd044..7ecfb81e0 100644
--- a/libs/utils/benchmark/src/StringHashmapBenchmark.cc
+++ b/libs/utils/benchmark/src/StringHashmapBenchmark.cc
@@ -100,7 +100,7 @@ public:
         return valDistribution(generator);
     }
 
-    const int MAX_LEN = 100;
+    static constexpr int MAX_LEN = 100;
     std::default_random_engine generator{};
     std::uniform_int_distribution<int> lenDistribution{1,MAX_LEN};
     std::uniform_int_distribution<int> charDistribution{'a','z'};


Reply via email to