├── .idea └── vcs.xml ├── CMakeLists.txt ├── SigSlotTest ├── SigSlotTest.sln └── SigSlotTest │ ├── SigSlotTest.vcxproj.filters │ └── SigSlotTest.vcxproj ├── conanfile.py ├── README.md ├── .gitattributes ├── .gitignore ├── .github └── workflows │ └── gtest.yml ├── test ├── sigslot.cc └── main.cpp ├── example.cc ├── sigslot └── sigslot.h └── conan_provider.cmake /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(sigslot) 3 | 4 | # GoogleTest requires at least C++14, coroutines need C++20 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | find_package(GTest) 9 | find_package(sentry) 10 | enable_testing() 11 | add_executable(sigslot-test 12 | test/sigslot.cc 13 | test/main.cpp 14 | ) 15 | target_link_libraries(sigslot-test PUBLIC gtest::gtest sentry-native::sentry-native) 16 | target_include_directories(sigslot-test SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 17 | target_compile_definitions(sigslot-test PRIVATE DWD_GTEST_SENTRY=1) 18 | include(GoogleTest) 19 | gtest_discover_tests(sigslot-test) 20 | 21 | if (UNIX) 22 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines") 23 | endif () 24 | if (WIN32) 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /await") 26 | endif() 27 | 28 | install(DIRECTORY sigslot TYPE INCLUDE) -------------------------------------------------------------------------------- /SigSlotTest/SigSlotTest.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Express 2013 for Windows Desktop 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SigSlotTest", "SigSlotTest\SigSlotTest.vcxproj", "{62CD295D-A6DE-480C-9AE8-2AF3BCE333A5}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Release|Win32 = Release|Win32 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {62CD295D-A6DE-480C-9AE8-2AF3BCE333A5}.Debug|Win32.ActiveCfg = Debug|Win32 15 | {62CD295D-A6DE-480C-9AE8-2AF3BCE333A5}.Debug|Win32.Build.0 = Debug|Win32 16 | {62CD295D-A6DE-480C-9AE8-2AF3BCE333A5}.Release|Win32.ActiveCfg = Release|Win32 17 | {62CD295D-A6DE-480C-9AE8-2AF3BCE333A5}.Release|Win32.Build.0 = Release|Win32 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /SigSlotTest/SigSlotTest/SigSlotTest.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Header Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conan import ConanFile 2 | from conan.tools.cmake import CMakeDeps, CMakeToolchain, CMake, cmake_layout 3 | from conan.tools.files import copy 4 | 5 | class Siglot(ConanFile): 6 | name = "st-sigslot" 7 | license = "MIT" 8 | author = "Dave Cridland " 9 | url = "https://github.com/dwd/SigSlot" 10 | description = "A simple header-only Signal/Slot C++ library" 11 | topics = ("signal", "slot") 12 | exports_sources = "sigslot/*", "CMakeLists.txt", "test/*" 13 | no_copy_source = True 14 | options = { 15 | "tests": [True, False] 16 | } 17 | default_options = { 18 | "tests": False 19 | } 20 | settings = "os", "compiler", "build_type", "arch" 21 | 22 | def configure(self): 23 | if not self.options.get_safe("tests"): 24 | self.settings.clear() 25 | else: 26 | self.options["sentry-native"].backend = "inproc" 27 | 28 | def requirements(self): 29 | if self.options.get_safe("tests"): 30 | self.requires("gtest/1.12.1") 31 | self.requires("sentry-native/0.7.15") 32 | 33 | def layout(self): 34 | if self.options.tests: 35 | cmake_layout(self) 36 | else: 37 | self.folders.source = '.' 38 | 39 | def generate(self): 40 | if self.options.get_safe("tests"): 41 | deps = CMakeDeps(self) 42 | deps.generate() 43 | cmake = CMakeToolchain(self) 44 | cmake.generate() 45 | 46 | def build(self): 47 | if self.options.get_safe("tests"): 48 | cmake = CMake(self) 49 | cmake.configure() 50 | cmake.build() 51 | 52 | def package(self): 53 | copy(self, "*.h", self.source_folder, self.package_folder + '/include') 54 | 55 | def package_info(self): 56 | self.cpp_info.includedirs = ["include"] 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sigslot - C++11 Signal/Slot library 2 | 3 | Originally written by Sarah Thompson. 4 | 5 | Various patches and fixes applied by Cat Nap Games: 6 | 7 | To make this compile under Xcode 4.3 with Clang 3.0 I made some changes myself and also used some diffs published in the original project's Sourceforge forum. 8 | I don't remember which ones though. 9 | 10 | C++11-erization (and C++2x-erixation) by Dave Cridland: 11 | 12 | See example.cc for some documentation and a walk-through example, or read the tests. 13 | 14 | This is public domain; no copyright is claimed or asserted. 15 | 16 | No warranty is implied or offered either. 17 | 18 | ## Tagging and version 19 | 20 | Until recently, I'd say just use HEAD. But some people are really keen on tags, so I'll do some semantic version tagging on this. 21 | 22 | ## Promising, yet oddly vague and sometimes outright misleading documentation 23 | 24 | This library is a pure header library, and consists of four header files: 25 | 26 | 27 | 28 | This contains a sigslot::signal class, and a sigslot::has_slots class. 29 | 30 | Signals can be connected to arbitrary functions, but in order to handle disconnect on lifetime termination, there's a "has_slots" base class to make it simpler. 31 | 32 | Loosely, calling "emit(...)" on the signal will then call all the connected "slots", which are just arbitrary functions. 33 | 34 | If a class is derived (publicly) from has_slots, you can pass in the instance of the class you want to control the lifetime. For calling a specific member directly, that's an easy decision; but if you pass in a lambda or some other arbitrary function, it might not be. 35 | 36 | If there's nothing obvious to hand, something still needs to control the scope - leaving out the has_slots argument therefore returns you a (deliberately undocumented) placeholder class, which acts in lieu of a has_slots derived class of your choice. 37 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | 98 | # NuGet Packages Directory 99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 100 | #packages/ 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | sql/ 111 | *.Cache 112 | ClientBin/ 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *~ 116 | *.dbmdl 117 | *.[Pp]ublish.xml 118 | *.pfx 119 | *.publishsettings 120 | 121 | # RIA/Silverlight projects 122 | Generated_Code/ 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | UpgradeLog*.htm 130 | 131 | # SQL Server files 132 | App_Data/*.mdf 133 | App_Data/*.ldf 134 | 135 | 136 | #LightSwitch generated files 137 | GeneratedArtifacts/ 138 | _Pvt_Extensions/ 139 | ModelManifest.xml 140 | 141 | # ========================= 142 | # Windows detritus 143 | # ========================= 144 | 145 | # Windows image file caches 146 | Thumbs.db 147 | ehthumbs.db 148 | 149 | # Folder config file 150 | Desktop.ini 151 | 152 | # Recycle Bin used on file shares 153 | $RECYCLE.BIN/ 154 | 155 | # Mac desktop service store files 156 | .DS_Store 157 | -------------------------------------------------------------------------------- /.github/workflows/gtest.yml: -------------------------------------------------------------------------------- 1 | name: gtest 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | gtest: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - name: Figure out version 16 | id: tag 17 | run: | 18 | TAG=$(git describe --tags --abbrev=0) 19 | COMMITS_SINCE_TAG=$(git rev-list ${TAG}..HEAD --count) 20 | if [ "${COMMITS_SINCE_TAG}" -eq 0 ]; then 21 | echo "VERSION=${TAG}" >> $GITHUB_ENV 22 | else 23 | echo "VERSION="$(git describe --tags --abbrev=8) >> $GITHUB_ENV 24 | fi 25 | - name: Cache Conan2 dependencies 26 | uses: actions/cache@v3 27 | with: 28 | path: ~/.conan2 29 | key: ${{ runner.os }}-conan2-${{ hashFiles('**/conanfile.py') }} 30 | restore-keys: | 31 | ${{ runner.os }}-conan2- 32 | - name: Set up Python 3.8 for gcovr 33 | uses: actions/setup-python@v4 34 | - name: SonarQube install 35 | uses: SonarSource/sonarcloud-github-c-cpp@v3 36 | - name: Install Conan 37 | run: pip install conan 38 | - name: Configure Conan Profile 39 | run: | 40 | conan profile detect -e 41 | conan remote add conan-nexus https://nexus.cridland.io/repository/dwd-conan --force 42 | conan remote login conan-nexus ci --password ${{ secrets.NEXUS_PASSWORD }} 43 | - name: Conan Deps (Debug) 44 | run: conan install . -s build_type=Debug -s compiler.cppstd=gnu23 -b missing --version=${{ env.VERSION }} 45 | - name: Conan Deps (Debug) 46 | run: conan install . -s build_type=Debug -s compiler.cppstd=gnu23 -b missing --version=${{ env.VERSION }} 47 | - name: Conan Deps (Release) 48 | run: conan install . -s build_type=Release -s compiler.cppstd=gnu23 -b missing --version=${{ env.VERSION }} 49 | - name: Conan Deps (RelWithDebInfo) 50 | run: conan install . -s build_type=RelWithDebInfo -s compiler.cppstd=gnu23 -b missing --version=${{ env.VERSION }} 51 | - name: Conan Deps (Debug+Tests) 52 | run: conan install . -o tests=True -s build_type=Debug -s compiler.cppstd=gnu23 -b missing --version=${{ env.VERSION }} 53 | - name: CMake tests 54 | run: cmake -B gh-build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="conan_provider.cmake" -DCONAN_INSTALL_ARGS="-o=tests=True;--settings=compiler.cppstd=gnu23;--build=missing;--version=${{ env.VERSION }}" 55 | - name: Build 56 | run: cmake --build gh-build 57 | - name: Run Tests 58 | run: cd gh-build && ./sigslot-test 59 | - name: Create package (Debug) 60 | run: conan create . -s build_type=Debug -s compiler.cppstd=gnu23 -b missing --version=${{ env.VERSION }} 61 | - name: Create package (Release) 62 | run: conan create . -s build_type=Release -s compiler.cppstd=gnu23 -b missing --version=${{ env.VERSION }} 63 | - name: Create package (RelWithDebInfo) 64 | run: conan create . -s build_type=RelWithDebInfo -s compiler.cppstd=gnu23 -b missing --version=${{ env.VERSION }} 65 | - name: Upload 66 | run: conan upload -r conan-nexus --confirm 'st-sigslot/*' 67 | -------------------------------------------------------------------------------- /test/sigslot.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dave on 28/03/2024. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | template 9 | class Sink : public sigslot::has_slots { 10 | public: 11 | std::optional> result; 12 | void slot(Args... args) { 13 | result.emplace(args...); 14 | } 15 | void reset() { 16 | result.reset(); 17 | } 18 | }; 19 | 20 | template<> 21 | class Sink : public sigslot::has_slots { 22 | public: 23 | bool result = false; 24 | void slot() { 25 | result = true; 26 | } 27 | void reset() { 28 | result = false; 29 | } 30 | }; 31 | 32 | TEST(Simple, test_bool) { 33 | Sink sink; 34 | EXPECT_FALSE(sink.result.has_value()); 35 | sigslot::signal signal; 36 | signal.connect(&sink, &Sink::slot); 37 | signal(true); 38 | EXPECT_TRUE(sink.result.has_value()); 39 | EXPECT_TRUE(std::get<0>(*sink.result)); 40 | sink.reset(); 41 | signal(false); 42 | EXPECT_TRUE(sink.result.has_value()); 43 | EXPECT_FALSE(std::get<0>(*sink.result)); 44 | } 45 | 46 | 47 | TEST(Simple, test_bool_disconnect) { 48 | sigslot::signal signal; 49 | signal(true); 50 | { 51 | Sink sink; 52 | EXPECT_FALSE(sink.result.has_value()); 53 | sigslot::signal signal; 54 | signal.connect(&sink, &Sink::slot); 55 | signal(true); 56 | EXPECT_TRUE(sink.result.has_value()); 57 | EXPECT_TRUE(std::get<0>(*sink.result)); 58 | } 59 | signal(false); 60 | } 61 | 62 | 63 | TEST(Simple, test_bool_oneshot) { 64 | Sink sink; 65 | EXPECT_FALSE(sink.result.has_value()); 66 | sigslot::signal signal; 67 | signal.connect(&sink, &Sink::slot, true); 68 | signal(true); 69 | EXPECT_TRUE(sink.result.has_value()); 70 | EXPECT_TRUE(std::get<0>(*sink.result)); 71 | sink.reset(); 72 | signal(false); 73 | EXPECT_FALSE(sink.result.has_value()); 74 | } 75 | 76 | 77 | TEST(Simple, test_void) { 78 | Sink sink; 79 | EXPECT_FALSE(sink.result); 80 | sigslot::signal<> signal; 81 | signal.connect(&sink, &Sink::slot); 82 | signal(); 83 | EXPECT_TRUE(sink.result); 84 | sink.reset(); 85 | signal(); 86 | EXPECT_TRUE(sink.result); 87 | } 88 | 89 | 90 | TEST(Simple, test_void_oneshot) { 91 | Sink sink; 92 | EXPECT_FALSE(sink.result); 93 | sigslot::signal<> signal; 94 | signal.connect(&sink, &Sink::slot, true); 95 | signal(); 96 | EXPECT_TRUE(sink.result); 97 | sink.reset(); 98 | signal(); 99 | EXPECT_FALSE(sink.result); 100 | } 101 | 102 | TEST(Simple, test_raii_slot) { 103 | Sink sink; 104 | EXPECT_FALSE(sink.result); 105 | sigslot::signal<> signal; 106 | { 107 | auto scope_slot = signal.connect([&sink]() { 108 | sink.slot(); 109 | }); 110 | signal(); 111 | EXPECT_TRUE(sink.result); 112 | sink.reset(); 113 | EXPECT_FALSE(sink.result); 114 | signal(); 115 | EXPECT_TRUE(sink.result); 116 | } 117 | sink.reset(); 118 | signal(); 119 | EXPECT_FALSE(sink.result); 120 | } 121 | -------------------------------------------------------------------------------- /SigSlotTest/SigSlotTest/SigSlotTest.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {62CD295D-A6DE-480C-9AE8-2AF3BCE333A5} 15 | Win32Proj 16 | SigSlotTest 17 | 18 | 19 | 20 | Application 21 | true 22 | v120 23 | Unicode 24 | 25 | 26 | Application 27 | false 28 | v120 29 | true 30 | Unicode 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | true 44 | 45 | 46 | false 47 | 48 | 49 | 50 | 51 | 52 | Level3 53 | Disabled 54 | WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) 55 | true 56 | ../../ 57 | 58 | 59 | Console 60 | true 61 | 62 | 63 | 64 | 65 | Level3 66 | 67 | 68 | MaxSpeed 69 | true 70 | true 71 | WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) 72 | true 73 | 74 | 75 | Console 76 | true 77 | true 78 | true 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /example.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | class Source { 7 | public: 8 | /* 9 | * Signals are public fields within a class. 10 | * The template args are a multithreading policy, followed by 11 | * zero or more payload arguments. 12 | */ 13 | mutable sigslot::signal<> signal_zero; 14 | mutable sigslot::signal signal_bool; 15 | 16 | Source() = default; 17 | Source(Source &) = delete; 18 | Source(Source &&) = delete; 19 | 20 | void kerpling() { 21 | m_toggle = !m_toggle; 22 | /* 23 | * To emit a signal, you can call its "emit" member with the arguments 24 | * you declared for it. 25 | */ 26 | signal_bool.emit(m_toggle); 27 | } 28 | 29 | void boioing() const { 30 | /* 31 | * Or, you can emit a signal by just calling it. 32 | * This is particularly nice for passing as a functor somewhere. 33 | */ 34 | signal_zero(); 35 | } 36 | typedef sigslot::signal domain_callback_t; 37 | domain_callback_t & callback(std::string const & domain) { 38 | /* 39 | * Sometimes, you might want to have a callback that's 40 | * safely decoupled. This is one way of doing this. 41 | */ 42 | return m_callbacks[domain]; 43 | } 44 | void connect_done(std::string const & domain) { 45 | /* 46 | * Normally one would do something in an event loop here. 47 | * This example is obviously trivial. 48 | */ 49 | auto it = m_callbacks.find(domain); 50 | if (it != m_callbacks.end()) { 51 | (*it).second.emit(*this, domain, true); 52 | m_callbacks.erase(it); // Don't forget to erase when done. 53 | } 54 | } 55 | private: 56 | bool m_toggle{true}; 57 | std::map m_callbacks; 58 | }; 59 | 60 | /* 61 | * A class acting as a sink needs to inherit from has_slots<>. 62 | * The template argument (optional) is a threading policy. 63 | * A sink "owns" the signal connections; when it goes out of scope 64 | * they'll be disconnected. 65 | */ 66 | class Sink : public sigslot::has_slots { 67 | public: 68 | Sink() = default; 69 | 70 | /* 71 | * Slots are just void functions. 72 | * This one takes a bool, obviously. 73 | */ 74 | void slot_bool(bool b) { 75 | std::cout << "Signalled bool(" << (b ? "true" : "false" ) << ")" << std::endl; 76 | } 77 | 78 | /* 79 | * And this one is just void. 80 | */ 81 | void slot_void() { 82 | std::cout << "Signalled void." << std::endl; 83 | } 84 | 85 | void connected(Source & s, std::string const & domain, bool ok) { 86 | std::cout << "Domain " << domain << " connected" << std::endl; 87 | } 88 | }; 89 | 90 | int main() { 91 | Source source; 92 | 93 | /* 94 | * You can call unconnected signals if you want. 95 | */ 96 | source.kerpling(); 97 | source.boioing(); 98 | 99 | { 100 | Sink sink; 101 | using namespace std::placeholders; 102 | 103 | /* 104 | * If you connect a signal using a pointer-to-member, it assumes 105 | * you mean to call the member normally. 106 | */ 107 | source.signal_zero.connect(&sink, &Sink::slot_void); 108 | /* 109 | * You can also connect an arbitrary functor, like something snazzy 110 | * with a lambda. 111 | * Note the first argument remains a has_slots<> derivative acting 112 | * as the connection's owner. 113 | */ 114 | source.signal_bool.connect(&sink, [&sink](bool b) {sink.slot_bool(b);}); 115 | 116 | /* 117 | * Now those slots will be called when the signals are emitted. 118 | */ 119 | std::cout << "Bool: "; 120 | source.kerpling(); 121 | std::cout << "Void: "; 122 | source.boioing(); 123 | 124 | /* 125 | * We can disconnect a sink from a signal in the obvious way. 126 | */ 127 | source.signal_bool.disconnect(&sink); 128 | 129 | std::cout << "Bool: "; 130 | source.kerpling(); 131 | source.boioing(); 132 | 133 | /* 134 | * Callbacks in this model work similarly: 135 | */ 136 | source.callback("dave.cridland.net").connect(&sink, &Sink::connected); 137 | // Wrong domain: 138 | source.connect_done("cridland.im"); 139 | // Right domain: 140 | source.connect_done("dave.cridland.net"); 141 | 142 | { 143 | Source source2; 144 | 145 | /* 146 | * Multiple signals can connect to the same slot. 147 | */ 148 | source2.signal_zero.connect(&sink, &Sink::slot_void); 149 | 150 | source2.kerpling(); 151 | std::cout << "Void: "; 152 | source2.boioing(); 153 | source.kerpling(); 154 | std::cout << "Void: "; 155 | source.boioing(); 156 | 157 | /* 158 | * If a signal is destroyed, the connections will vanish cleanly. 159 | */ 160 | } 161 | 162 | { 163 | Sink sink2; 164 | 165 | /* 166 | * The same signal can emit to multiple slots, too. 167 | */ 168 | source.signal_zero.connect(&sink2, &Sink::slot_void); 169 | 170 | source.kerpling(); 171 | std::cout << "Voidx2: "; 172 | source.boioing(); 173 | } 174 | 175 | /* 176 | * When Sinks are destroyed, the signals are disconnected. 177 | */ 178 | } 179 | 180 | /* 181 | * These are unconnected, but you can still emit them (as a noop) 182 | */ 183 | source.kerpling(); 184 | source.boioing(); 185 | 186 | return 0; 187 | } 188 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dwd on 8/24/24. 3 | // 4 | // 5 | // Created by dave on 30/07/2024. 6 | // 7 | 8 | #include "gtest/gtest.h" 9 | #ifdef DWD_GTEST_SENTRY 10 | #include 11 | 12 | class EventListener : public ::testing::TestEventListener { 13 | sentry_transaction_context_t *tx_ctx = nullptr; 14 | sentry_transaction_t *tx = nullptr; 15 | sentry_span_t *main_span = nullptr; 16 | sentry_span_t *suite_span = nullptr; 17 | sentry_span_t *test_span = nullptr; 18 | std::string const & m_progname; 19 | public: 20 | EventListener(std::string const & progname) : m_progname(progname) {} 21 | ~EventListener() override = default; 22 | 23 | // Override this to define how to set up the environment. 24 | void OnTestProgramStart(const ::testing::UnitTest & u) override { 25 | sentry_options_t *options = sentry_options_new(); 26 | sentry_options_set_traces_sample_rate(options, 1.0); 27 | sentry_init(options); 28 | } 29 | void OnTestProgramEnd(const ::testing::UnitTest &) override { 30 | sentry_shutdown(); 31 | } 32 | 33 | void OnTestStart(::testing::TestInfo const & test_info) override { 34 | const char * testName = test_info.name(); 35 | std::string tname = test_info.test_suite_name(); 36 | tname += "."; 37 | tname += testName; 38 | test_span = sentry_span_start_child( 39 | suite_span, 40 | "test", 41 | tname.c_str() 42 | ); 43 | } 44 | 45 | // Override this to define how to tear down the environment. 46 | void OnTestEnd(const ::testing::TestInfo & ti) override { 47 | if (ti.result()->Failed()) { 48 | sentry_span_set_status(test_span, sentry_span_status_t::SENTRY_SPAN_STATUS_INTERNAL_ERROR); 49 | } 50 | sentry_span_finish(test_span); // Mark the span as finished 51 | } 52 | 53 | void OnTestIterationStart(const testing::UnitTest &unit_test, int iteration) override { 54 | tx_ctx = sentry_transaction_context_new( 55 | m_progname.c_str(), 56 | "googletest" 57 | ); 58 | tx = sentry_transaction_start(tx_ctx, sentry_value_new_null()); 59 | main_span = sentry_transaction_start_child( 60 | tx, 61 | "googletest", 62 | m_progname.c_str() 63 | ); 64 | } 65 | 66 | void OnEnvironmentsSetUpStart(const testing::UnitTest &unit_test) override { 67 | 68 | } 69 | 70 | void OnEnvironmentsSetUpEnd(const testing::UnitTest &unit_test) override { 71 | 72 | } 73 | 74 | void OnTestSuiteStart(const testing::TestSuite &suite) override { 75 | suite_span = sentry_span_start_child( 76 | main_span, 77 | "test.suite", 78 | suite.name() 79 | ); 80 | TestEventListener::OnTestSuiteStart(suite); 81 | } 82 | 83 | void OnTestCaseStart(const testing::TestCase &aCase) override { 84 | TestEventListener::OnTestCaseStart(aCase); 85 | } 86 | 87 | void OnTestDisabled(const testing::TestInfo &info) override { 88 | TestEventListener::OnTestDisabled(info); 89 | } 90 | 91 | void OnTestPartResult(const testing::TestPartResult &test_part_result) override { 92 | sentry_set_span(test_span); 93 | auto val = sentry_value_new_breadcrumb("test", test_part_result.message()); 94 | sentry_add_breadcrumb(val); 95 | if (test_part_result.failed()) { 96 | auto ev = sentry_value_new_event(); 97 | auto exc = sentry_value_new_exception("GoogleTest", test_part_result.message()); 98 | sentry_value_set_stacktrace(exc, nullptr, 0); 99 | sentry_event_add_exception(ev, exc); 100 | sentry_capture_event(ev); 101 | } 102 | } 103 | 104 | void OnTestSuiteEnd(const testing::TestSuite &suite) override { 105 | TestEventListener::OnTestSuiteEnd(suite); 106 | if (suite.failed_test_count() > 0) { 107 | sentry_span_set_status(suite_span, sentry_span_status_t::SENTRY_SPAN_STATUS_INTERNAL_ERROR); 108 | } 109 | sentry_span_finish(suite_span); // Mark the span as finished 110 | } 111 | 112 | void OnTestCaseEnd(const testing::TestCase &aCase) override { 113 | TestEventListener::OnTestCaseEnd(aCase); 114 | } 115 | 116 | void OnEnvironmentsTearDownStart(const testing::UnitTest &unit_test) override { 117 | 118 | } 119 | 120 | void OnEnvironmentsTearDownEnd(const testing::UnitTest &unit_test) override { 121 | 122 | } 123 | 124 | void OnTestIterationEnd(const testing::UnitTest &unit_test, int iteration) override { 125 | if (unit_test.failed_test_count() > 0) { 126 | sentry_span_set_status(main_span, sentry_span_status_t::SENTRY_SPAN_STATUS_INTERNAL_ERROR); 127 | sentry_transaction_set_status(tx, sentry_span_status_t::SENTRY_SPAN_STATUS_INTERNAL_ERROR); 128 | } 129 | sentry_span_finish(main_span); // Mark the span as finished 130 | sentry_transaction_finish(tx); 131 | } 132 | }; 133 | #endif 134 | 135 | int main(int argc, char ** argv) { 136 | std::string progname(argv[0]); 137 | auto slash = progname.find_last_of("/\\"); 138 | if (slash != std::string::npos) { 139 | progname = progname.substr(slash + 1); 140 | } 141 | ::testing::InitGoogleTest(&argc, argv); 142 | auto & listeners = ::testing::UnitTest::GetInstance()->listeners(); 143 | #ifdef DWD_GTEST_SENTRY 144 | listeners.Append(new EventListener(progname)); 145 | #endif 146 | auto ret = RUN_ALL_TESTS(); 147 | return ret; 148 | } 149 | -------------------------------------------------------------------------------- /sigslot/sigslot.h: -------------------------------------------------------------------------------- 1 | // sigslot.h: Signal/Slot classes 2 | // 3 | // Written by Sarah Thompson (sarah@telergy.com) 2002. 4 | // Mangled by Dave Cridland , most recently in 2019. 5 | // 6 | // License: Public domain. You are free to use this code however you like, with the proviso that 7 | // the author takes on no responsibility or liability for any use. 8 | // 9 | // QUICK DOCUMENTATION 10 | // 11 | // (see also the full documentation at http://sigslot.sourceforge.net/) 12 | // 13 | // #define switches 14 | // SIGSLOT_NO_COROUTINES: 15 | // If not defined, this will provide an operator co_await(), so that coroutines can 16 | // co_await on a signal instead of registering a callback. 17 | // 18 | // PLATFORM NOTES 19 | // 20 | // The header file requires C++11 (certainly), C++14 (probably), and C++17 (maybe). 21 | // Coroutine support isn't well-tested, and might only work on CLang for now. 22 | // 23 | // THREADING MODES 24 | // 25 | // Only C++11 threading remains. 26 | // 27 | // USING THE LIBRARY 28 | // 29 | // See the full documentation at http://sigslot.sourceforge.net/ 30 | // 31 | // 32 | 33 | #ifndef SIGSLOT_H__ 34 | #define SIGSLOT_H__ 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #ifndef SIGSLOT_NO_COROUTINES 42 | #include 43 | #include 44 | #include 45 | #endif 46 | 47 | namespace sigslot { 48 | class has_slots; 49 | 50 | namespace internal { 51 | class _signal_base_lo { 52 | protected: 53 | std::recursive_mutex m_barrier; 54 | public: 55 | virtual void slot_disconnect(has_slots *pslot) = 0; 56 | virtual ~_signal_base_lo() = default; 57 | }; 58 | } 59 | 60 | 61 | class has_slots 62 | { 63 | private: 64 | std::recursive_mutex m_barrier; 65 | 66 | public: 67 | has_slots() = default; 68 | 69 | has_slots(const has_slots& hs) = delete; 70 | has_slots(has_slots && hs) = delete; 71 | 72 | void signal_connect(internal::_signal_base_lo* sender) 73 | { 74 | std::scoped_lock lock(m_barrier); 75 | m_senders.insert(sender); 76 | } 77 | 78 | void signal_disconnect(internal::_signal_base_lo* sender) 79 | { 80 | std::scoped_lock lock(m_barrier); 81 | m_senders.erase(sender); 82 | } 83 | 84 | virtual ~has_slots() 85 | { 86 | disconnect_all(); 87 | } 88 | 89 | void disconnect_all() 90 | { 91 | std::scoped_lock lock(m_barrier); 92 | for (auto i : m_senders) { 93 | i->slot_disconnect(this); 94 | } 95 | 96 | m_senders.erase(m_senders.begin(), m_senders.end()); 97 | } 98 | 99 | private: 100 | std::set m_senders; 101 | }; 102 | 103 | namespace internal { 104 | template 105 | class _connection 106 | { 107 | public: 108 | _connection(has_slots *pobject, std::function fn, bool once) 109 | : one_shot(once), m_pobject(pobject), m_fn(fn) {} 110 | 111 | void emit(args... a) 112 | { 113 | m_fn(a...); 114 | } 115 | 116 | [[nodiscard]] has_slots* getdest() const 117 | { 118 | return m_pobject; 119 | } 120 | 121 | const bool one_shot = false; 122 | bool expired = false; 123 | private: 124 | has_slots* m_pobject; 125 | std::function m_fn; 126 | }; 127 | 128 | template 129 | class _signal_base : public _signal_base_lo 130 | { 131 | public: 132 | _signal_base() = default; 133 | 134 | _signal_base(const _signal_base& s) 135 | : _signal_base_lo(s), m_connected_slots() 136 | { 137 | std::scoped_lock lock(m_barrier); 138 | for (auto i : s.m_connected_slots) { 139 | i->getdest()->signal_connect(this); 140 | m_connected_slots.push_back(i->clone()); 141 | } 142 | } 143 | 144 | _signal_base(_signal_base &&) = delete; 145 | 146 | virtual ~_signal_base() 147 | { 148 | disconnect_all(); 149 | } 150 | 151 | void disconnect_all() 152 | { 153 | std::scoped_lock lock(m_barrier); 154 | for (auto i : m_connected_slots) { 155 | i->getdest()->signal_disconnect(this); 156 | delete i; 157 | } 158 | m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); 159 | } 160 | 161 | void disconnect(has_slots* pclass) 162 | { 163 | std::scoped_lock lock(m_barrier); 164 | bool found{false}; 165 | m_connected_slots.remove_if([pclass, &found](_connection * x) { 166 | if (x->getdest() == pclass) { 167 | delete x; 168 | found = true; 169 | return true; 170 | } 171 | return false; 172 | }); 173 | if (found) pclass->signal_disconnect(this); 174 | } 175 | 176 | void slot_disconnect(has_slots* pslot) final 177 | { 178 | std::scoped_lock lock(m_barrier); 179 | m_connected_slots.remove_if( 180 | [pslot](_connection * x) { 181 | if (x->getdest() == pslot) { 182 | delete x; 183 | return true; 184 | } 185 | return false; 186 | } 187 | ); 188 | } 189 | 190 | protected: 191 | std::list<_connection *> m_connected_slots; 192 | }; 193 | 194 | } 195 | 196 | 197 | #ifndef SIGSLOT_NO_COROUTINES 198 | namespace coroutines { 199 | template struct awaitable; 200 | } 201 | #endif 202 | 203 | 204 | template 205 | class signal : public internal::_signal_base 206 | { 207 | public: 208 | signal() = default; 209 | 210 | signal(const signal& s) = default; 211 | 212 | void connect(has_slots *pclass, std::function &&fn, bool one_shot = false) 213 | { 214 | std::scoped_lock lock{internal::_signal_base::m_barrier}; 215 | auto *conn = new internal::_connection( 216 | pclass, std::move(fn), one_shot); 217 | this->m_connected_slots.push_back(conn); 218 | pclass->signal_connect(this); 219 | } 220 | 221 | // Helper for ptr-to-member; call the member function "normally". 222 | template 223 | requires std::derived_from 224 | void connect(desttype *pclass, void (desttype::* memfn)(args...), bool one_shot = false) 225 | { 226 | this->connect(pclass, [pclass, memfn](args... a) { (pclass->*memfn)(a...); }, one_shot); 227 | } 228 | 229 | [[nodiscard]] std::unique_ptr connect(std::function && fn, bool one_shot=false) 230 | { 231 | auto raii = std::make_unique(); 232 | this->connect(raii.get(), std::move(fn), one_shot); 233 | return raii; 234 | } 235 | 236 | // This code uses the long-hand because it assumes it may mutate the list. 237 | void emit(args... a) 238 | { 239 | std::scoped_lock lock{internal::_signal_base::m_barrier}; 240 | auto it = this->m_connected_slots.begin(); 241 | auto itNext = it; 242 | auto itEnd = this->m_connected_slots.end(); 243 | 244 | while(it != itEnd) 245 | { 246 | itNext = it; 247 | ++itNext; 248 | 249 | if ((*it)->one_shot) { 250 | (*it)->expired = true; 251 | } 252 | (*it)->emit(a...); 253 | 254 | it = itNext; 255 | } 256 | 257 | this->m_connected_slots.remove_if([this](internal::_connection *x) { 258 | if (x->expired) { 259 | x->getdest()->signal_disconnect(this); 260 | delete x; 261 | return true; 262 | } 263 | return false; 264 | }); 265 | // Might need to reconnect new signals. This needs improvement... 266 | for (auto const conn : this->m_connected_slots) { 267 | conn->getdest()->signal_connect(this); 268 | } 269 | } 270 | 271 | void operator()(args... a) 272 | { 273 | this->emit(a...); 274 | } 275 | 276 | #ifndef SIGSLOT_NO_COROUTINES 277 | auto operator co_await() const { 278 | return coroutines::awaitable(const_cast(*this)); 279 | } 280 | #endif 281 | }; 282 | 283 | 284 | #ifndef SIGSLOT_NO_COROUTINES 285 | namespace coroutines { 286 | // Generic variant uses a tuple to pass back. 287 | template 288 | struct awaitable : public has_slots { 289 | ::sigslot::signal & signal; 290 | std::coroutine_handle<> awaiting = nullptr; 291 | std::optional> payload; 292 | 293 | explicit awaitable(::sigslot::signal & s) : signal(s) { 294 | signal.connect(this, &awaitable::resolve); 295 | } 296 | awaitable(awaitable const & a) : signal(a.signal), payload(a.payload) { 297 | signal.connect(this, &awaitable::resolve); 298 | } 299 | awaitable(awaitable && other) noexcept : signal(other.signal), payload(std::move(other.payload)) { 300 | signal.connect(this, &awaitable::resolve); 301 | } 302 | 303 | bool await_ready() const { 304 | return payload.has_value(); 305 | } 306 | 307 | void await_suspend(std::coroutine_handle<> h) { 308 | // The awaiting coroutine is already suspended. 309 | awaiting = h; 310 | } 311 | 312 | auto await_resume() const { 313 | return *payload; 314 | } 315 | 316 | void resolve(Args... a) { 317 | payload.emplace(a...); 318 | if (awaiting) awaiting.resume(); 319 | } 320 | }; 321 | 322 | // Single argument version uses a bare T 323 | template 324 | struct awaitable : public has_slots { 325 | ::sigslot::signal & signal; 326 | std::coroutine_handle<> awaiting = nullptr; 327 | std::optional payload; 328 | explicit awaitable(::sigslot::signal & s) : signal(s) { 329 | signal.connect(this, &awaitable::resolve); 330 | } 331 | awaitable(awaitable const & a) : signal(a.signal), payload(a.payload) { 332 | signal.connect(this, &awaitable::resolve); 333 | } 334 | awaitable(awaitable && other) noexcept : signal(other.signal), payload(std::move(other.payload)) { 335 | signal.connect(this, &awaitable::resolve); 336 | } 337 | 338 | bool await_ready() const { 339 | return payload.has_value(); 340 | } 341 | 342 | void await_suspend(std::coroutine_handle<> h) { 343 | // The awaiting coroutine is already suspended. 344 | awaiting = h; 345 | } 346 | 347 | auto await_resume() const { 348 | return *payload; 349 | } 350 | 351 | void resolve(T a) { 352 | payload.emplace(a); 353 | if (awaiting) awaiting.resume(); 354 | } 355 | }; 356 | 357 | // Single argument reference version uses a bare T & 358 | template 359 | struct awaitable : public has_slots { 360 | ::sigslot::signal & signal; 361 | std::coroutine_handle<> awaiting = nullptr; 362 | T *payload = nullptr; 363 | explicit awaitable(::sigslot::signal & s) : signal(s) { 364 | signal.connect(this, &awaitable::resolve); 365 | } 366 | awaitable(awaitable const & a) : signal(a.signal), payload(a.payload) { 367 | signal.connect(this, &awaitable::resolve); 368 | } 369 | awaitable(awaitable && other) noexcept : signal(other.signal), payload(std::move(other.payload)) { 370 | signal.connect(this, &awaitable::resolve); 371 | } 372 | 373 | bool await_ready() { 374 | return payload; 375 | } 376 | 377 | void await_suspend(std::coroutine_handle<> h) { 378 | // The awaiting coroutine is already suspended. 379 | awaiting = h; 380 | } 381 | 382 | auto & await_resume() { 383 | return *payload; 384 | } 385 | 386 | void resolve(T & a) { 387 | payload = &a; 388 | if (awaiting) awaiting.resume(); 389 | } 390 | }; 391 | 392 | // Zero argument version uses nothing, of course. 393 | template<> 394 | struct awaitable<> : public has_slots { 395 | ::sigslot::signal<> & signal; 396 | std::coroutine_handle<> awaiting = nullptr; 397 | bool ready = false; 398 | explicit awaitable(::sigslot::signal<> & s) : signal(s) { 399 | signal.connect(this, &awaitable::resolve); 400 | } 401 | awaitable(awaitable const & a) : signal(a.signal), ready(a.ready) { 402 | signal.connect(this, &awaitable::resolve); 403 | } 404 | awaitable(awaitable && other) noexcept : signal(other.signal), ready(other.ready) { 405 | signal.connect(this, &awaitable::resolve); 406 | } 407 | 408 | bool await_ready() { 409 | return ready; 410 | } 411 | 412 | void await_suspend(std::coroutine_handle<> h) { 413 | // The awaiting coroutine is already suspended. 414 | awaiting = h; 415 | } 416 | 417 | void await_resume() {} 418 | 419 | void resolve() { 420 | ready = true; 421 | if (awaiting) awaiting.resume(); 422 | } 423 | }; 424 | 425 | } 426 | #endif 427 | } // namespace sigslot 428 | 429 | #endif // SIGSLOT_H__ 430 | 431 | -------------------------------------------------------------------------------- /conan_provider.cmake: -------------------------------------------------------------------------------- 1 | # This file is managed by Conan, contents will be overwritten. 2 | # To keep your changes, remove these comment lines, but the plugin won't be able to modify your requirements 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2024 JFrog 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | set(CONAN_MINIMUM_VERSION 2.0.5) 27 | 28 | # Create a new policy scope and set the minimum required cmake version so the 29 | # features behind a policy setting like if(... IN_LIST ...) behaves as expected 30 | # even if the parent project does not specify a minimum cmake version or a minimum 31 | # version less than this module requires (e.g. 3.0) before the first project() call. 32 | # (see: https://cmake.org/cmake/help/latest/variable/CMAKE_PROJECT_TOP_LEVEL_INCLUDES.html) 33 | # 34 | # The policy-affecting calls like cmake_policy(SET...) or `cmake_minimum_required` only 35 | # affects the current policy scope, i.e. between the PUSH and POP in this case. 36 | # 37 | # https://cmake.org/cmake/help/book/mastering-cmake/chapter/Policies.html#the-policy-stack 38 | cmake_policy(PUSH) 39 | cmake_minimum_required(VERSION 3.24) 40 | 41 | 42 | function(detect_os os os_api_level os_sdk os_subsystem os_version) 43 | # it could be cross compilation 44 | message(STATUS "CMake-Conan: cmake_system_name=${CMAKE_SYSTEM_NAME}") 45 | if(CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") 46 | if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") 47 | set(${os} Macos PARENT_SCOPE) 48 | elseif(CMAKE_SYSTEM_NAME STREQUAL "QNX") 49 | set(${os} Neutrino PARENT_SCOPE) 50 | elseif(CMAKE_SYSTEM_NAME STREQUAL "CYGWIN") 51 | set(${os} Windows PARENT_SCOPE) 52 | set(${os_subsystem} cygwin PARENT_SCOPE) 53 | elseif(CMAKE_SYSTEM_NAME MATCHES "^MSYS") 54 | set(${os} Windows PARENT_SCOPE) 55 | set(${os_subsystem} msys2 PARENT_SCOPE) 56 | else() 57 | set(${os} ${CMAKE_SYSTEM_NAME} PARENT_SCOPE) 58 | endif() 59 | if(CMAKE_SYSTEM_NAME STREQUAL "Android") 60 | if(DEFINED ANDROID_PLATFORM) 61 | string(REGEX MATCH "[0-9]+" _os_api_level ${ANDROID_PLATFORM}) 62 | elseif(DEFINED CMAKE_SYSTEM_VERSION) 63 | set(_os_api_level ${CMAKE_SYSTEM_VERSION}) 64 | endif() 65 | message(STATUS "CMake-Conan: android api level=${_os_api_level}") 66 | set(${os_api_level} ${_os_api_level} PARENT_SCOPE) 67 | endif() 68 | if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS") 69 | # CMAKE_OSX_SYSROOT contains the full path to the SDK for MakeFile/Ninja 70 | # generators, but just has the original input string for Xcode. 71 | if(NOT IS_DIRECTORY ${CMAKE_OSX_SYSROOT}) 72 | set(_os_sdk ${CMAKE_OSX_SYSROOT}) 73 | else() 74 | if(CMAKE_OSX_SYSROOT MATCHES Simulator) 75 | set(apple_platform_suffix simulator) 76 | else() 77 | set(apple_platform_suffix os) 78 | endif() 79 | if(CMAKE_OSX_SYSROOT MATCHES AppleTV) 80 | set(_os_sdk "appletv${apple_platform_suffix}") 81 | elseif(CMAKE_OSX_SYSROOT MATCHES iPhone) 82 | set(_os_sdk "iphone${apple_platform_suffix}") 83 | elseif(CMAKE_OSX_SYSROOT MATCHES Watch) 84 | set(_os_sdk "watch${apple_platform_suffix}") 85 | endif() 86 | endif() 87 | if(DEFINED os_sdk) 88 | message(STATUS "CMake-Conan: cmake_osx_sysroot=${CMAKE_OSX_SYSROOT}") 89 | set(${os_sdk} ${_os_sdk} PARENT_SCOPE) 90 | endif() 91 | if(DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) 92 | message(STATUS "CMake-Conan: cmake_osx_deployment_target=${CMAKE_OSX_DEPLOYMENT_TARGET}") 93 | set(${os_version} ${CMAKE_OSX_DEPLOYMENT_TARGET} PARENT_SCOPE) 94 | endif() 95 | endif() 96 | endif() 97 | endfunction() 98 | 99 | 100 | function(detect_arch arch) 101 | # CMAKE_OSX_ARCHITECTURES can contain multiple architectures, but Conan only supports one. 102 | # Therefore this code only finds one. If the recipes support multiple architectures, the 103 | # build will work. Otherwise, there will be a linker error for the missing architecture(s). 104 | if(DEFINED CMAKE_OSX_ARCHITECTURES) 105 | string(REPLACE " " ";" apple_arch_list "${CMAKE_OSX_ARCHITECTURES}") 106 | list(LENGTH apple_arch_list apple_arch_count) 107 | if(apple_arch_count GREATER 1) 108 | message(WARNING "CMake-Conan: Multiple architectures detected, this will only work if Conan recipe(s) produce fat binaries.") 109 | endif() 110 | endif() 111 | if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS" AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL "") 112 | set(host_arch ${CMAKE_OSX_ARCHITECTURES}) 113 | elseif(MSVC) 114 | set(host_arch ${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}) 115 | else() 116 | set(host_arch ${CMAKE_SYSTEM_PROCESSOR}) 117 | endif() 118 | if(host_arch MATCHES "aarch64|arm64|ARM64") 119 | set(_arch armv8) 120 | elseif(host_arch MATCHES "armv7|armv7-a|armv7l|ARMV7") 121 | set(_arch armv7) 122 | elseif(host_arch MATCHES armv7s) 123 | set(_arch armv7s) 124 | elseif(host_arch MATCHES "i686|i386|X86") 125 | set(_arch x86) 126 | elseif(host_arch MATCHES "AMD64|amd64|x86_64|x64") 127 | set(_arch x86_64) 128 | endif() 129 | message(STATUS "CMake-Conan: cmake_system_processor=${_arch}") 130 | set(${arch} ${_arch} PARENT_SCOPE) 131 | endfunction() 132 | 133 | 134 | function(detect_cxx_standard cxx_standard) 135 | set(${cxx_standard} ${CMAKE_CXX_STANDARD} PARENT_SCOPE) 136 | if(CMAKE_CXX_EXTENSIONS) 137 | set(${cxx_standard} "gnu${CMAKE_CXX_STANDARD}" PARENT_SCOPE) 138 | endif() 139 | endfunction() 140 | 141 | 142 | macro(detect_gnu_libstdcxx) 143 | # _conan_is_gnu_libstdcxx true if GNU libstdc++ 144 | check_cxx_source_compiles(" 145 | #include 146 | #if !defined(__GLIBCXX__) && !defined(__GLIBCPP__) 147 | static_assert(false); 148 | #endif 149 | int main(){}" _conan_is_gnu_libstdcxx) 150 | 151 | # _conan_gnu_libstdcxx_is_cxx11_abi true if C++11 ABI 152 | check_cxx_source_compiles(" 153 | #include 154 | static_assert(sizeof(std::string) != sizeof(void*), \"using libstdc++\"); 155 | int main () {}" _conan_gnu_libstdcxx_is_cxx11_abi) 156 | 157 | set(_conan_gnu_libstdcxx_suffix "") 158 | if(_conan_gnu_libstdcxx_is_cxx11_abi) 159 | set(_conan_gnu_libstdcxx_suffix "11") 160 | endif() 161 | unset (_conan_gnu_libstdcxx_is_cxx11_abi) 162 | endmacro() 163 | 164 | 165 | macro(detect_libcxx) 166 | # _conan_is_libcxx true if LLVM libc++ 167 | check_cxx_source_compiles(" 168 | #include 169 | #if !defined(_LIBCPP_VERSION) 170 | static_assert(false); 171 | #endif 172 | int main(){}" _conan_is_libcxx) 173 | endmacro() 174 | 175 | 176 | function(detect_lib_cxx lib_cxx) 177 | if(CMAKE_SYSTEM_NAME STREQUAL "Android") 178 | message(STATUS "CMake-Conan: android_stl=${CMAKE_ANDROID_STL_TYPE}") 179 | set(${lib_cxx} ${CMAKE_ANDROID_STL_TYPE} PARENT_SCOPE) 180 | return() 181 | endif() 182 | 183 | include(CheckCXXSourceCompiles) 184 | 185 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 186 | detect_gnu_libstdcxx() 187 | set(${lib_cxx} "libstdc++${_conan_gnu_libstdcxx_suffix}" PARENT_SCOPE) 188 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") 189 | set(${lib_cxx} "libc++" PARENT_SCOPE) 190 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_SYSTEM_NAME MATCHES "Windows") 191 | # Check for libc++ 192 | detect_libcxx() 193 | if(_conan_is_libcxx) 194 | set(${lib_cxx} "libc++" PARENT_SCOPE) 195 | return() 196 | endif() 197 | 198 | # Check for libstdc++ 199 | detect_gnu_libstdcxx() 200 | if(_conan_is_gnu_libstdcxx) 201 | set(${lib_cxx} "libstdc++${_conan_gnu_libstdcxx_suffix}" PARENT_SCOPE) 202 | return() 203 | endif() 204 | 205 | # TODO: it would be an error if we reach this point 206 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 207 | # Do nothing - compiler.runtime and compiler.runtime_type 208 | # should be handled separately: https://github.com/conan-io/cmake-conan/pull/516 209 | return() 210 | else() 211 | # TODO: unable to determine, ask user to provide a full profile file instead 212 | endif() 213 | endfunction() 214 | 215 | 216 | function(detect_compiler compiler compiler_version compiler_runtime compiler_runtime_type) 217 | if(DEFINED CMAKE_CXX_COMPILER_ID) 218 | set(_compiler ${CMAKE_CXX_COMPILER_ID}) 219 | set(_compiler_version ${CMAKE_CXX_COMPILER_VERSION}) 220 | else() 221 | if(NOT DEFINED CMAKE_C_COMPILER_ID) 222 | message(FATAL_ERROR "C or C++ compiler not defined") 223 | endif() 224 | set(_compiler ${CMAKE_C_COMPILER_ID}) 225 | set(_compiler_version ${CMAKE_C_COMPILER_VERSION}) 226 | endif() 227 | 228 | message(STATUS "CMake-Conan: CMake compiler=${_compiler}") 229 | message(STATUS "CMake-Conan: CMake compiler version=${_compiler_version}") 230 | 231 | if(_compiler MATCHES MSVC) 232 | set(_compiler "msvc") 233 | string(SUBSTRING ${MSVC_VERSION} 0 3 _compiler_version) 234 | # Configure compiler.runtime and compiler.runtime_type settings for MSVC 235 | if(CMAKE_MSVC_RUNTIME_LIBRARY) 236 | set(_msvc_runtime_library ${CMAKE_MSVC_RUNTIME_LIBRARY}) 237 | else() 238 | set(_msvc_runtime_library MultiThreaded$<$:Debug>DLL) # default value documented by CMake 239 | endif() 240 | 241 | set(_KNOWN_MSVC_RUNTIME_VALUES "") 242 | list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded MultiThreadedDLL) 243 | list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreadedDebug MultiThreadedDebugDLL) 244 | list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded$<$:Debug> MultiThreaded$<$:Debug>DLL) 245 | 246 | # only accept the 6 possible values, otherwise we don't don't know to map this 247 | if(NOT _msvc_runtime_library IN_LIST _KNOWN_MSVC_RUNTIME_VALUES) 248 | message(FATAL_ERROR "CMake-Conan: unable to map MSVC runtime: ${_msvc_runtime_library} to Conan settings") 249 | endif() 250 | 251 | # Runtime is "dynamic" in all cases if it ends in DLL 252 | if(_msvc_runtime_library MATCHES ".*DLL$") 253 | set(_compiler_runtime "dynamic") 254 | else() 255 | set(_compiler_runtime "static") 256 | endif() 257 | message(STATUS "CMake-Conan: CMake compiler.runtime=${_compiler_runtime}") 258 | 259 | # Only define compiler.runtime_type when explicitly requested 260 | # If a generator expression is used, let Conan handle it conditional on build_type 261 | if(NOT _msvc_runtime_library MATCHES ":Debug>") 262 | if(_msvc_runtime_library MATCHES "Debug") 263 | set(_compiler_runtime_type "Debug") 264 | else() 265 | set(_compiler_runtime_type "Release") 266 | endif() 267 | message(STATUS "CMake-Conan: CMake compiler.runtime_type=${_compiler_runtime_type}") 268 | endif() 269 | 270 | unset(_KNOWN_MSVC_RUNTIME_VALUES) 271 | 272 | elseif(_compiler MATCHES AppleClang) 273 | set(_compiler "apple-clang") 274 | string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) 275 | list(GET VERSION_LIST 0 _compiler_version) 276 | elseif(_compiler MATCHES Clang) 277 | set(_compiler "clang") 278 | string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) 279 | list(GET VERSION_LIST 0 _compiler_version) 280 | elseif(_compiler MATCHES GNU) 281 | set(_compiler "gcc") 282 | string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) 283 | list(GET VERSION_LIST 0 _compiler_version) 284 | endif() 285 | 286 | message(STATUS "CMake-Conan: [settings] compiler=${_compiler}") 287 | message(STATUS "CMake-Conan: [settings] compiler.version=${_compiler_version}") 288 | if (_compiler_runtime) 289 | message(STATUS "CMake-Conan: [settings] compiler.runtime=${_compiler_runtime}") 290 | endif() 291 | if (_compiler_runtime_type) 292 | message(STATUS "CMake-Conan: [settings] compiler.runtime_type=${_compiler_runtime_type}") 293 | endif() 294 | 295 | set(${compiler} ${_compiler} PARENT_SCOPE) 296 | set(${compiler_version} ${_compiler_version} PARENT_SCOPE) 297 | set(${compiler_runtime} ${_compiler_runtime} PARENT_SCOPE) 298 | set(${compiler_runtime_type} ${_compiler_runtime_type} PARENT_SCOPE) 299 | endfunction() 300 | 301 | 302 | function(detect_build_type build_type) 303 | get_property(multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 304 | if(NOT multiconfig_generator) 305 | # Only set when we know we are in a single-configuration generator 306 | # Note: we may want to fail early if `CMAKE_BUILD_TYPE` is not defined 307 | set(${build_type} ${CMAKE_BUILD_TYPE} PARENT_SCOPE) 308 | endif() 309 | endfunction() 310 | 311 | 312 | macro(set_conan_compiler_if_appleclang lang command output_variable) 313 | if(CMAKE_${lang}_COMPILER_ID STREQUAL "AppleClang") 314 | execute_process(COMMAND xcrun --find ${command} 315 | OUTPUT_VARIABLE _xcrun_out OUTPUT_STRIP_TRAILING_WHITESPACE) 316 | cmake_path(GET _xcrun_out PARENT_PATH _xcrun_toolchain_path) 317 | cmake_path(GET CMAKE_${lang}_COMPILER PARENT_PATH _compiler_parent_path) 318 | if ("${_xcrun_toolchain_path}" STREQUAL "${_compiler_parent_path}") 319 | set(${output_variable} "") 320 | endif() 321 | unset(_xcrun_out) 322 | unset(_xcrun_toolchain_path) 323 | unset(_compiler_parent_path) 324 | endif() 325 | endmacro() 326 | 327 | 328 | macro(append_compiler_executables_configuration) 329 | set(_conan_c_compiler "") 330 | set(_conan_cpp_compiler "") 331 | set(_conan_rc_compiler "") 332 | set(_conan_compilers_list "") 333 | if(CMAKE_C_COMPILER) 334 | set(_conan_c_compiler "\"c\":\"${CMAKE_C_COMPILER}\"") 335 | set_conan_compiler_if_appleclang(C cc _conan_c_compiler) 336 | list(APPEND _conan_compilers_list ${_conan_c_compiler}) 337 | else() 338 | message(WARNING "CMake-Conan: The C compiler is not defined. " 339 | "Please define CMAKE_C_COMPILER or enable the C language.") 340 | endif() 341 | if(CMAKE_CXX_COMPILER) 342 | set(_conan_cpp_compiler "\"cpp\":\"${CMAKE_CXX_COMPILER}\"") 343 | set_conan_compiler_if_appleclang(CXX c++ _conan_cpp_compiler) 344 | list(APPEND _conan_compilers_list ${_conan_cpp_compiler}) 345 | else() 346 | message(WARNING "CMake-Conan: The C++ compiler is not defined. " 347 | "Please define CMAKE_CXX_COMPILER or enable the C++ language.") 348 | endif() 349 | if(CMAKE_RC_COMPILER) 350 | set(_conan_rc_compiler "\"rc\":\"${CMAKE_RC_COMPILER}\"") 351 | list(APPEND _conan_compilers_list ${_conan_rc_compiler}) 352 | # Not necessary to warn if RC not defined 353 | endif() 354 | if(NOT "x${_conan_compilers_list}" STREQUAL "x") 355 | string(REPLACE ";" "," _conan_compilers_list "${_conan_compilers_list}") 356 | string(APPEND profile "tools.build:compiler_executables={${_conan_compilers_list}}\n") 357 | endif() 358 | unset(_conan_c_compiler) 359 | unset(_conan_cpp_compiler) 360 | unset(_conan_rc_compiler) 361 | unset(_conan_compilers_list) 362 | endmacro() 363 | 364 | 365 | function(detect_host_profile output_file) 366 | detect_os(os os_api_level os_sdk os_subsystem os_version) 367 | detect_arch(arch) 368 | detect_compiler(compiler compiler_version compiler_runtime compiler_runtime_type) 369 | detect_cxx_standard(compiler_cppstd) 370 | detect_lib_cxx(compiler_libcxx) 371 | detect_build_type(build_type) 372 | 373 | set(profile "") 374 | string(APPEND profile "[settings]\n") 375 | if(arch) 376 | string(APPEND profile arch=${arch} "\n") 377 | endif() 378 | if(os) 379 | string(APPEND profile os=${os} "\n") 380 | endif() 381 | if(os_api_level) 382 | string(APPEND profile os.api_level=${os_api_level} "\n") 383 | endif() 384 | if(os_version) 385 | string(APPEND profile os.version=${os_version} "\n") 386 | endif() 387 | if(os_sdk) 388 | string(APPEND profile os.sdk=${os_sdk} "\n") 389 | endif() 390 | if(os_subsystem) 391 | string(APPEND profile os.subsystem=${os_subsystem} "\n") 392 | endif() 393 | if(compiler) 394 | string(APPEND profile compiler=${compiler} "\n") 395 | endif() 396 | if(compiler_version) 397 | string(APPEND profile compiler.version=${compiler_version} "\n") 398 | endif() 399 | if(compiler_runtime) 400 | string(APPEND profile compiler.runtime=${compiler_runtime} "\n") 401 | endif() 402 | if(compiler_runtime_type) 403 | string(APPEND profile compiler.runtime_type=${compiler_runtime_type} "\n") 404 | endif() 405 | if(compiler_cppstd) 406 | string(APPEND profile compiler.cppstd=${compiler_cppstd} "\n") 407 | endif() 408 | if(compiler_libcxx) 409 | string(APPEND profile compiler.libcxx=${compiler_libcxx} "\n") 410 | endif() 411 | if(build_type) 412 | string(APPEND profile "build_type=${build_type}\n") 413 | endif() 414 | 415 | if(NOT DEFINED output_file) 416 | set(file_name "${CMAKE_BINARY_DIR}/profile") 417 | else() 418 | set(file_name ${output_file}) 419 | endif() 420 | 421 | string(APPEND profile "[conf]\n") 422 | string(APPEND profile "tools.cmake.cmaketoolchain:generator=${CMAKE_GENERATOR}\n") 423 | 424 | # propagate compilers via profile 425 | append_compiler_executables_configuration() 426 | 427 | if(os STREQUAL "Android") 428 | string(APPEND profile "tools.android:ndk_path=${CMAKE_ANDROID_NDK}\n") 429 | endif() 430 | 431 | message(STATUS "CMake-Conan: Creating profile ${file_name}") 432 | file(WRITE ${file_name} ${profile}) 433 | message(STATUS "CMake-Conan: Profile: \n${profile}") 434 | endfunction() 435 | 436 | 437 | function(conan_profile_detect_default) 438 | message(STATUS "CMake-Conan: Checking if a default profile exists") 439 | execute_process(COMMAND ${CONAN_COMMAND} profile path default 440 | RESULT_VARIABLE return_code 441 | OUTPUT_VARIABLE conan_stdout 442 | ERROR_VARIABLE conan_stderr 443 | ECHO_ERROR_VARIABLE # show the text output regardless 444 | ECHO_OUTPUT_VARIABLE 445 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 446 | if(NOT ${return_code} EQUAL "0") 447 | message(STATUS "CMake-Conan: The default profile doesn't exist, detecting it.") 448 | execute_process(COMMAND ${CONAN_COMMAND} profile detect 449 | RESULT_VARIABLE return_code 450 | OUTPUT_VARIABLE conan_stdout 451 | ERROR_VARIABLE conan_stderr 452 | ECHO_ERROR_VARIABLE # show the text output regardless 453 | ECHO_OUTPUT_VARIABLE 454 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 455 | endif() 456 | endfunction() 457 | 458 | 459 | function(conan_install) 460 | cmake_parse_arguments(ARGS conan_args ${ARGN}) 461 | set(conan_output_folder ${CMAKE_BINARY_DIR}/conan) 462 | # Invoke "conan install" with the provided arguments 463 | set(conan_args ${conan_args} -of=${conan_output_folder}) 464 | message(STATUS "CMake-Conan: conan install ${CMAKE_SOURCE_DIR} ${conan_args} ${ARGN}") 465 | 466 | 467 | # In case there was not a valid cmake executable in the PATH, we inject the 468 | # same we used to invoke the provider to the PATH 469 | if(DEFINED PATH_TO_CMAKE_BIN) 470 | set(old_path $ENV{PATH}) 471 | set(ENV{PATH} "$ENV{PATH}:${PATH_TO_CMAKE_BIN}") 472 | endif() 473 | 474 | execute_process(COMMAND ${CONAN_COMMAND} install ${CMAKE_SOURCE_DIR} ${conan_args} ${ARGN} --format=json 475 | RESULT_VARIABLE return_code 476 | OUTPUT_VARIABLE conan_stdout 477 | ERROR_VARIABLE conan_stderr 478 | ECHO_ERROR_VARIABLE # show the text output regardless 479 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 480 | 481 | if(DEFINED PATH_TO_CMAKE_BIN) 482 | set(ENV{PATH} "${old_path}") 483 | endif() 484 | 485 | if(NOT "${return_code}" STREQUAL "0") 486 | message(FATAL_ERROR "Conan install failed='${return_code}'") 487 | endif() 488 | 489 | # the files are generated in a folder that depends on the layout used, if 490 | # one is specified, but we don't know a priori where this is. 491 | # TODO: this can be made more robust if Conan can provide this in the json output 492 | string(JSON conan_generators_folder GET "${conan_stdout}" graph nodes 0 generators_folder) 493 | cmake_path(CONVERT ${conan_generators_folder} TO_CMAKE_PATH_LIST conan_generators_folder) 494 | 495 | message(STATUS "CMake-Conan: CONAN_GENERATORS_FOLDER=${conan_generators_folder}") 496 | set_property(GLOBAL PROPERTY CONAN_GENERATORS_FOLDER "${conan_generators_folder}") 497 | # reconfigure on conanfile changes 498 | string(JSON conanfile GET "${conan_stdout}" graph nodes 0 label) 499 | message(STATUS "CMake-Conan: CONANFILE=${CMAKE_SOURCE_DIR}/${conanfile}") 500 | set_property(DIRECTORY ${CMAKE_SOURCE_DIR} APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/${conanfile}") 501 | # success 502 | set_property(GLOBAL PROPERTY CONAN_INSTALL_SUCCESS TRUE) 503 | 504 | endfunction() 505 | 506 | 507 | function(conan_get_version conan_command conan_current_version) 508 | execute_process( 509 | COMMAND ${conan_command} --version 510 | OUTPUT_VARIABLE conan_output 511 | RESULT_VARIABLE conan_result 512 | OUTPUT_STRIP_TRAILING_WHITESPACE 513 | ) 514 | if(conan_result) 515 | message(FATAL_ERROR "CMake-Conan: Error when trying to run Conan") 516 | endif() 517 | 518 | string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" conan_version ${conan_output}) 519 | set(${conan_current_version} ${conan_version} PARENT_SCOPE) 520 | endfunction() 521 | 522 | 523 | function(conan_version_check) 524 | set(options ) 525 | set(one_value_args MINIMUM CURRENT) 526 | set(multi_value_args ) 527 | cmake_parse_arguments(conan_version_check 528 | "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) 529 | 530 | if(NOT conan_version_check_MINIMUM) 531 | message(FATAL_ERROR "CMake-Conan: Required parameter MINIMUM not set!") 532 | endif() 533 | if(NOT conan_version_check_CURRENT) 534 | message(FATAL_ERROR "CMake-Conan: Required parameter CURRENT not set!") 535 | endif() 536 | 537 | if(conan_version_check_CURRENT VERSION_LESS conan_version_check_MINIMUM) 538 | message(FATAL_ERROR "CMake-Conan: Conan version must be ${conan_version_check_MINIMUM} or later") 539 | endif() 540 | endfunction() 541 | 542 | 543 | macro(construct_profile_argument argument_variable profile_list) 544 | set(${argument_variable} "") 545 | if("${profile_list}" STREQUAL "CONAN_HOST_PROFILE") 546 | set(_arg_flag "--profile:host=") 547 | elseif("${profile_list}" STREQUAL "CONAN_BUILD_PROFILE") 548 | set(_arg_flag "--profile:build=") 549 | endif() 550 | 551 | set(_profile_list "${${profile_list}}") 552 | list(TRANSFORM _profile_list REPLACE "auto-cmake" "${CMAKE_BINARY_DIR}/conan_host_profile") 553 | list(TRANSFORM _profile_list PREPEND ${_arg_flag}) 554 | set(${argument_variable} ${_profile_list}) 555 | 556 | unset(_arg_flag) 557 | unset(_profile_list) 558 | endmacro() 559 | 560 | 561 | macro(conan_provide_dependency method package_name) 562 | set_property(GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED TRUE) 563 | get_property(_conan_install_success GLOBAL PROPERTY CONAN_INSTALL_SUCCESS) 564 | if(NOT _conan_install_success) 565 | find_program(CONAN_COMMAND "conan" REQUIRED) 566 | conan_get_version(${CONAN_COMMAND} CONAN_CURRENT_VERSION) 567 | conan_version_check(MINIMUM ${CONAN_MINIMUM_VERSION} CURRENT ${CONAN_CURRENT_VERSION}) 568 | message(STATUS "CMake-Conan: first find_package() found. Installing dependencies with Conan") 569 | if("default" IN_LIST CONAN_HOST_PROFILE OR "default" IN_LIST CONAN_BUILD_PROFILE) 570 | conan_profile_detect_default() 571 | endif() 572 | if("auto-cmake" IN_LIST CONAN_HOST_PROFILE) 573 | detect_host_profile(${CMAKE_BINARY_DIR}/conan_host_profile) 574 | endif() 575 | construct_profile_argument(_host_profile_flags CONAN_HOST_PROFILE) 576 | construct_profile_argument(_build_profile_flags CONAN_BUILD_PROFILE) 577 | if(EXISTS "${CMAKE_SOURCE_DIR}/conanfile.py") 578 | file(READ "${CMAKE_SOURCE_DIR}/conanfile.py" outfile) 579 | if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") 580 | message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile") 581 | endif() 582 | set(generator "") 583 | elseif (EXISTS "${CMAKE_SOURCE_DIR}/conanfile.txt") 584 | file(READ "${CMAKE_SOURCE_DIR}/conanfile.txt" outfile) 585 | if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") 586 | message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile. " 587 | "Please define the generator as it will be mandatory in the future") 588 | endif() 589 | set(generator "-g;CMakeDeps") 590 | endif() 591 | get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 592 | if(NOT _multiconfig_generator) 593 | message(STATUS "CMake-Conan: Installing single configuration ${CMAKE_BUILD_TYPE}") 594 | conan_install(${_host_profile_flags} ${_build_profile_flags} ${CONAN_INSTALL_ARGS} ${generator}) 595 | else() 596 | message(STATUS "CMake-Conan: Installing both Debug and Release") 597 | conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Release ${CONAN_INSTALL_ARGS} ${generator}) 598 | conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Debug ${CONAN_INSTALL_ARGS} ${generator}) 599 | endif() 600 | unset(_host_profile_flags) 601 | unset(_build_profile_flags) 602 | unset(_multiconfig_generator) 603 | unset(_conan_install_success) 604 | else() 605 | message(STATUS "CMake-Conan: find_package(${ARGV1}) found, 'conan install' already ran") 606 | unset(_conan_install_success) 607 | endif() 608 | 609 | get_property(_conan_generators_folder GLOBAL PROPERTY CONAN_GENERATORS_FOLDER) 610 | 611 | # Ensure that we consider Conan-provided packages ahead of any other, 612 | # irrespective of other settings that modify the search order or search paths 613 | # This follows the guidelines from the find_package documentation 614 | # (https://cmake.org/cmake/help/latest/command/find_package.html): 615 | # find_package ( PATHS paths... NO_DEFAULT_PATH) 616 | # find_package () 617 | 618 | # Filter out `REQUIRED` from the argument list, as the first call may fail 619 | set(_find_args_${package_name} "${ARGN}") 620 | list(REMOVE_ITEM _find_args_${package_name} "REQUIRED") 621 | if(NOT "MODULE" IN_LIST _find_args_${package_name}) 622 | find_package(${package_name} ${_find_args_${package_name}} BYPASS_PROVIDER PATHS "${_conan_generators_folder}" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) 623 | unset(_find_args_${package_name}) 624 | endif() 625 | 626 | # Invoke find_package a second time - if the first call succeeded, 627 | # this will simply reuse the result. If not, fall back to CMake default search 628 | # behaviour, also allowing modules to be searched. 629 | if(NOT ${package_name}_FOUND) 630 | list(FIND CMAKE_MODULE_PATH "${_conan_generators_folder}" _index) 631 | if(_index EQUAL -1) 632 | list(PREPEND CMAKE_MODULE_PATH "${_conan_generators_folder}") 633 | endif() 634 | unset(_index) 635 | find_package(${package_name} ${ARGN} BYPASS_PROVIDER) 636 | list(REMOVE_ITEM CMAKE_MODULE_PATH "${_conan_generators_folder}") 637 | endif() 638 | endmacro() 639 | 640 | 641 | cmake_language( 642 | SET_DEPENDENCY_PROVIDER conan_provide_dependency 643 | SUPPORTED_METHODS FIND_PACKAGE 644 | ) 645 | 646 | 647 | macro(conan_provide_dependency_check) 648 | set(_conan_provide_dependency_invoked FALSE) 649 | get_property(_conan_provide_dependency_invoked GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED) 650 | if(NOT _conan_provide_dependency_invoked) 651 | message(WARNING "Conan is correctly configured as dependency provider, " 652 | "but Conan has not been invoked. Please add at least one " 653 | "call to `find_package()`.") 654 | if(DEFINED CONAN_COMMAND) 655 | # supress warning in case `CONAN_COMMAND` was specified but unused. 656 | set(_conan_command ${CONAN_COMMAND}) 657 | unset(_conan_command) 658 | endif() 659 | endif() 660 | unset(_conan_provide_dependency_invoked) 661 | endmacro() 662 | 663 | 664 | # Add a deferred call at the end of processing the top-level directory 665 | # to check if the dependency provider was invoked at all. 666 | cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL conan_provide_dependency_check) 667 | 668 | # Configurable variables for Conan profiles 669 | set(CONAN_HOST_PROFILE "default;auto-cmake" CACHE STRING "Conan host profile") 670 | set(CONAN_BUILD_PROFILE "default" CACHE STRING "Conan build profile") 671 | set(CONAN_INSTALL_ARGS "--build=missing" CACHE STRING "Command line arguments for conan install") 672 | 673 | find_program(_cmake_program NAMES cmake NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH) 674 | if(NOT _cmake_program) 675 | get_filename_component(PATH_TO_CMAKE_BIN "${CMAKE_COMMAND}" DIRECTORY) 676 | set(PATH_TO_CMAKE_BIN "${PATH_TO_CMAKE_BIN}" CACHE INTERNAL "Path where the CMake executable is") 677 | endif() 678 | 679 | cmake_policy(POP) 680 | --------------------------------------------------------------------------------