├── .gitignore ├── src ├── plugin.h ├── pluginmain.cpp ├── pluginmain.h └── plugin.cpp ├── README.md ├── cmake.toml ├── LICENSE ├── .github └── workflows │ └── build.yml ├── cmake ├── x64dbg.cmake ├── cmkr.cmake └── CPM.cmake └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | .idea/ 3 | .vs/ 4 | cmake-build-*/ 5 | /CMakeSettings.json 6 | -------------------------------------------------------------------------------- /src/plugin.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pluginmain.h" 4 | 5 | //functions 6 | bool pluginInit(PLUG_INITSTRUCT* initStruct); 7 | void pluginStop(); 8 | void pluginSetup(); 9 | -------------------------------------------------------------------------------- /src/pluginmain.cpp: -------------------------------------------------------------------------------- 1 | #include "pluginmain.h" 2 | #include "plugin.h" 3 | 4 | int pluginHandle; 5 | HWND hwndDlg; 6 | int hMenu; 7 | int hMenuDisasm; 8 | int hMenuDump; 9 | int hMenuStack; 10 | 11 | PLUG_EXPORT bool pluginit(PLUG_INITSTRUCT* initStruct) 12 | { 13 | initStruct->pluginVersion = PLUGIN_VERSION; 14 | initStruct->sdkVersion = PLUG_SDKVERSION; 15 | strncpy_s(initStruct->pluginName, PLUGIN_NAME, _TRUNCATE); 16 | pluginHandle = initStruct->pluginHandle; 17 | return pluginInit(initStruct); 18 | } 19 | 20 | PLUG_EXPORT bool plugstop() 21 | { 22 | pluginStop(); 23 | return true; 24 | } 25 | 26 | PLUG_EXPORT void plugsetup(PLUG_SETUPSTRUCT* setupStruct) 27 | { 28 | hwndDlg = setupStruct->hwndDlg; 29 | hMenu = setupStruct->hMenu; 30 | hMenuDisasm = setupStruct->hMenuDisasm; 31 | hMenuDump = setupStruct->hMenuDump; 32 | hMenuStack = setupStruct->hMenuStack; 33 | pluginSetup(); 34 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MiniDumpPlugin 2 | 3 | **Note**: This plugin has been integrated into x64dbg as the [minidump](https://help.x64dbg.com/en/latest/commands/memory-operations/minidump.html) command since 2022-10-10. 4 | 5 | Simple [x64dbg](https://x64dbg.com) plugin to save the current state in a full minidump. Created for [dumpulator](https://github.com/mrexodia/dumpulator). 6 | 7 | **Download the latest binaries [here](https://github.com/mrexodia/MiniDumpPlugin/releases).** 8 | 9 | ## Building 10 | 11 | From a Visual Studio command prompt: 12 | 13 | ``` 14 | cmake -B build64 -A x64 15 | cmake --build build64 --config Release 16 | ``` 17 | 18 | You will get `build64\MiniDump.sln` that you can open in Visual Studio. 19 | 20 | To build a 32-bit plugin: 21 | 22 | ``` 23 | cmake -B build32 -A Win32 24 | cmake --build build32 --config Release 25 | ``` 26 | 27 | Alternatively you can open this folder in Visual Studio/CLion/Qt Creator. 28 | 29 | -------------------------------------------------------------------------------- /cmake.toml: -------------------------------------------------------------------------------- 1 | [cmake] 2 | version = "3.15" 3 | cmkr-include = "cmake/cmkr.cmake" 4 | 5 | [project] 6 | cmake-before = """ 7 | # Set up a more familiar Visual Studio configuration 8 | # Override these options with -DCMAKE_OPTION=Value 9 | # 10 | # See: https://cmake.org/cmake/help/latest/command/set.html#set-cache-entry 11 | set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "") 12 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG:FULL /INCREMENTAL:NO" CACHE STRING "") 13 | set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "/DEBUG:FULL /INCREMENTAL:NO" CACHE STRING "") 14 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") 15 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" CACHE STRING "") 16 | """ 17 | name = "MiniDump" 18 | include-after = [ 19 | "cmake/CPM.cmake", 20 | "cmake/x64dbg.cmake", 21 | ] 22 | 23 | [target.MiniDump] 24 | type = "shared" 25 | sources = [ 26 | "src/*.cpp", 27 | "src/*.h", 28 | ] 29 | cmake-after = """ 30 | x64dbg_plugin(${CMKR_TARGET}) 31 | """ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Visual Studio 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | # Skip building pull requests from the same repository 8 | if: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }} 9 | runs-on: windows-2019 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Add msbuild to PATH 15 | uses: microsoft/setup-msbuild@v1.0.2 16 | 17 | - name: Build (x64) 18 | run: | 19 | mkdir package\x64\plugins 20 | cmake -B build64 -A x64 21 | cmake --build build64 --config Release 22 | copy build64\Release\*.dp64 package\x64\plugins\ 23 | 24 | - name: Build (x32) 25 | run: | 26 | mkdir package\x32\plugins 27 | cmake -B build32 -A Win32 28 | cmake --build build32 --config Release 29 | copy build32\Release\*.dp32 package\x32\plugins\ 30 | 31 | - uses: actions/upload-artifact@v2 32 | with: 33 | name: ${{ github.event.repository.name }}-${{ github.sha }} 34 | path: package/ 35 | 36 | - name: Compress artifacts 37 | uses: papeloto/action-zip@v1 38 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 39 | with: 40 | files: package/ 41 | dest: ${{ github.event.repository.name }}-${{ github.ref_name }}.zip 42 | 43 | - name: Release 44 | uses: softprops/action-gh-release@v1 45 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 46 | with: 47 | prerelease: ${{ !startsWith(github.ref, 'refs/tags/v') || contains(github.ref, '-pre') }} 48 | files: ${{ github.event.repository.name }}-${{ github.ref_name }}.zip 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /cmake/x64dbg.cmake: -------------------------------------------------------------------------------- 1 | CPMAddPackage( 2 | NAME x64dbg 3 | URL https://downloads.sourceforge.net/project/x64dbg/snapshots/snapshot_2021-05-08_14-17.zip 4 | URL_HASH SHA1=a46f3bf3f84fee3b1f7da8949e79d425d7294979 5 | DOWNLOAD_ONLY ON 6 | ) 7 | 8 | if(x64dbg_ADDED) 9 | if(NOT TARGET x64dbg) 10 | file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS ${x64dbg_SOURCE_DIR}/pluginsdk/*.h) 11 | add_custom_target(x64dbg-sdk SOURCES ${HEADERS}) 12 | source_group(TREE ${x64dbg_SOURCE_DIR} FILES ${HEADERS}) 13 | 14 | add_library(x64dbg INTERFACE) 15 | target_include_directories(x64dbg INTERFACE ${x64dbg_SOURCE_DIR}) 16 | target_link_directories(x64dbg INTERFACE ${x64dbg_SOURCE_DIR}) 17 | endif() 18 | 19 | function(x64dbg_plugin target) 20 | if(NOT TARGET ${target}) 21 | # Add plugin dll 22 | add_library(${target} SHARED ${ARGN}) 23 | # Group source files 24 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${ARGN}) 25 | endif() 26 | 27 | # Change extension 28 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 29 | set_target_properties(${target} PROPERTIES SUFFIX ".dp64") 30 | else() 31 | set_target_properties(${target} PROPERTIES SUFFIX ".dp32") 32 | endif() 33 | 34 | # Link to the x64dbg SDK 35 | target_link_libraries(${target} PRIVATE x64dbg) 36 | 37 | # Set plugin name based on the target 38 | target_compile_definitions(${target} PRIVATE "-DPLUGIN_NAME=\"${target}\"") 39 | 40 | # Support PluginDevHelper 41 | add_custom_command(TARGET ${target} PRE_LINK COMMAND if exist "\"$(SolutionDir)PluginDevBuildTool.exe\"" "(\"$(SolutionDir)PluginDevBuildTool.exe\"" unload "\"$(TargetPath)\")" else (echo Copy PluginDevTool.exe next to the .sln to automatically reload plugins when building)) 42 | add_custom_command(TARGET ${target} POST_BUILD COMMAND if exist "\"$(SolutionDir)PluginDevBuildTool.exe\"" ("\"$(SolutionDir)PluginDevBuildTool.exe\"" reload "\"$(TargetPath)\"")) 43 | endfunction() 44 | endif() -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is automatically generated from cmake.toml - DO NOT EDIT 2 | # See https://github.com/build-cpp/cmkr for more information 3 | 4 | cmake_minimum_required(VERSION 3.15) 5 | 6 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) 7 | message(FATAL_ERROR "In-tree builds are not supported. Run CMake from a separate directory: cmake -B build") 8 | endif() 9 | 10 | # Regenerate CMakeLists.txt automatically in the root project 11 | set(CMKR_ROOT_PROJECT OFF) 12 | if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 13 | set(CMKR_ROOT_PROJECT ON) 14 | 15 | # Bootstrap cmkr 16 | include("cmake/cmkr.cmake" OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT) 17 | if(CMKR_INCLUDE_RESULT) 18 | cmkr() 19 | endif() 20 | 21 | # Enable folder support 22 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 23 | endif() 24 | 25 | # Create a configure-time dependency on cmake.toml to improve IDE support 26 | if(CMKR_ROOT_PROJECT) 27 | configure_file(cmake.toml cmake.toml COPYONLY) 28 | endif() 29 | 30 | # Set up a more familiar Visual Studio configuration 31 | # Override these options with -DCMAKE_OPTION=Value 32 | # 33 | # See: https://cmake.org/cmake/help/latest/command/set.html#set-cache-entry 34 | set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "") 35 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG:FULL /INCREMENTAL:NO" CACHE STRING "") 36 | set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "/DEBUG:FULL /INCREMENTAL:NO" CACHE STRING "") 37 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") 38 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" CACHE STRING "") 39 | 40 | project(MiniDump) 41 | 42 | include("cmake/CPM.cmake") 43 | include("cmake/x64dbg.cmake") 44 | 45 | # Target MiniDump 46 | set(CMKR_TARGET MiniDump) 47 | set(MiniDump_SOURCES "") 48 | 49 | list(APPEND MiniDump_SOURCES 50 | "src/plugin.cpp" 51 | "src/pluginmain.cpp" 52 | "src/plugin.h" 53 | "src/pluginmain.h" 54 | ) 55 | 56 | list(APPEND MiniDump_SOURCES 57 | cmake.toml 58 | ) 59 | 60 | set(CMKR_SOURCES ${MiniDump_SOURCES}) 61 | add_library(MiniDump SHARED) 62 | 63 | if(MiniDump_SOURCES) 64 | target_sources(MiniDump PRIVATE ${MiniDump_SOURCES}) 65 | endif() 66 | 67 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${MiniDump_SOURCES}) 68 | 69 | x64dbg_plugin(${CMKR_TARGET}) 70 | 71 | unset(CMKR_TARGET) 72 | unset(CMKR_SOURCES) 73 | 74 | -------------------------------------------------------------------------------- /src/pluginmain.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Plugin information 4 | #ifndef PLUGIN_NAME 5 | #error You need to define PLUGIN_NAME 6 | #endif // PLUGIN_NAME 7 | #define PLUGIN_VERSION 1 8 | 9 | #include "pluginsdk/bridgemain.h" 10 | #include "pluginsdk/_plugins.h" 11 | 12 | #include "pluginsdk/_scriptapi_argument.h" 13 | #include "pluginsdk/_scriptapi_assembler.h" 14 | #include "pluginsdk/_scriptapi_bookmark.h" 15 | #include "pluginsdk/_scriptapi_comment.h" 16 | #include "pluginsdk/_scriptapi_debug.h" 17 | #include "pluginsdk/_scriptapi_flag.h" 18 | #include "pluginsdk/_scriptapi_function.h" 19 | #include "pluginsdk/_scriptapi_gui.h" 20 | #include "pluginsdk/_scriptapi_label.h" 21 | #include "pluginsdk/_scriptapi_memory.h" 22 | #include "pluginsdk/_scriptapi_misc.h" 23 | #include "pluginsdk/_scriptapi_module.h" 24 | #include "pluginsdk/_scriptapi_pattern.h" 25 | #include "pluginsdk/_scriptapi_register.h" 26 | #include "pluginsdk/_scriptapi_stack.h" 27 | #include "pluginsdk/_scriptapi_symbol.h" 28 | 29 | #include "pluginsdk/DeviceNameResolver/DeviceNameResolver.h" 30 | #include "pluginsdk/jansson/jansson.h" 31 | #include "pluginsdk/lz4/lz4file.h" 32 | #include "pluginsdk/TitanEngine/TitanEngine.h" 33 | #include "pluginsdk/XEDParse/XEDParse.h" 34 | 35 | #ifdef _WIN64 36 | #pragma comment(lib, "pluginsdk/x64dbg.lib") 37 | #pragma comment(lib, "pluginsdk/x64bridge.lib") 38 | #pragma comment(lib, "pluginsdk/DeviceNameResolver/DeviceNameResolver_x64.lib") 39 | #pragma comment(lib, "pluginsdk/jansson/jansson_x64.lib") 40 | #pragma comment(lib, "pluginsdk/lz4/lz4_x64.lib") 41 | #pragma comment(lib, "pluginsdk/TitanEngine/TitanEngine_x64.lib") 42 | #pragma comment(lib, "pluginsdk/XEDParse/XEDParse_x64.lib") 43 | #else 44 | #pragma comment(lib, "pluginsdk/x32dbg.lib") 45 | #pragma comment(lib, "pluginsdk/x32bridge.lib") 46 | #pragma comment(lib, "pluginsdk/DeviceNameResolver/DeviceNameResolver_x86.lib") 47 | #pragma comment(lib, "pluginsdk/jansson/jansson_x86.lib") 48 | #pragma comment(lib, "pluginsdk/lz4/lz4_x86.lib") 49 | #pragma comment(lib, "pluginsdk/TitanEngine/TitanEngine_x86.lib") 50 | #pragma comment(lib, "pluginsdk/XEDParse/XEDParse_x86.lib") 51 | #endif //_WIN64 52 | 53 | #define Cmd(x) DbgCmdExecDirect(x) 54 | #define Eval(x) DbgValFromString(x) 55 | #define dprintf(x, ...) _plugin_logprintf("[" PLUGIN_NAME "] " x, __VA_ARGS__) 56 | #define dputs(x) _plugin_logprintf("[" PLUGIN_NAME "] %s\n", x) 57 | #define PLUG_EXPORT extern "C" __declspec(dllexport) 58 | 59 | //superglobal variables 60 | extern int pluginHandle; 61 | extern HWND hwndDlg; 62 | extern int hMenu; 63 | extern int hMenuDisasm; 64 | extern int hMenuDump; 65 | extern int hMenuStack; -------------------------------------------------------------------------------- /src/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.h" 2 | #include 3 | 4 | #pragma comment(lib, "dbghelp.lib") 5 | 6 | static bool g_hasException = false; 7 | static EXCEPTION_DEBUG_INFO g_exception; 8 | 9 | PLUG_EXPORT void CBEXCEPTION(CBTYPE, PLUG_CB_EXCEPTION* exception) 10 | { 11 | if (exception->Exception) 12 | { 13 | g_hasException = true; 14 | memcpy(&g_exception, exception->Exception, sizeof(g_exception)); 15 | } 16 | } 17 | 18 | PLUG_EXPORT void CBSTOPDEBUG(CBTYPE, PLUG_CB_STOPDEBUG*) 19 | { 20 | g_hasException = false; 21 | } 22 | 23 | static bool cbMiniDump(int argc, char* argv[]) 24 | { 25 | if (DbgIsRunning()) 26 | { 27 | dputs("Cannot dump while running..."); 28 | return false; 29 | } 30 | 31 | if (argc < 2) 32 | { 33 | dputs("Usage: MiniDump my.dmp"); 34 | return false; 35 | } 36 | 37 | HANDLE hFile = CreateFileA(argv[1], GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); 38 | if (hFile == INVALID_HANDLE_VALUE) 39 | { 40 | dprintf("Failed to create '%s'\n", argv[1]); 41 | return false; 42 | } 43 | 44 | // Disable all software breakpoints 45 | std::vector disabled_breakpoints; 46 | { 47 | BPMAP bplist = {}; 48 | DbgGetBpList(bp_normal, &bplist); 49 | for (int i = 0; i < bplist.count; i++) 50 | { 51 | const auto& bp = bplist.bp[i]; 52 | if (bp.active && bp.enabled) 53 | { 54 | char cmd[256] = ""; 55 | sprintf_s(cmd, "bd 0x%p", (void*)bp.addr); 56 | if (DbgCmdExecDirect(cmd)) 57 | disabled_breakpoints.push_back(bp.addr); 58 | } 59 | } 60 | BridgeFree(bplist.bp); 61 | } 62 | 63 | CONTEXT context = {}; 64 | context.ContextFlags = CONTEXT_ALL; 65 | GetThreadContext(DbgGetThreadHandle(), &context); 66 | context.EFlags &= ~0x100; // remove trap flag 67 | 68 | EXCEPTION_POINTERS exceptionPointers = {}; 69 | exceptionPointers.ContextRecord = &context; 70 | exceptionPointers.ExceptionRecord = &g_exception.ExceptionRecord; 71 | if (exceptionPointers.ExceptionRecord->ExceptionCode == 0) 72 | { 73 | auto& exceptionRecord = *exceptionPointers.ExceptionRecord; 74 | exceptionRecord.ExceptionCode = 0xFFFFFFFF; 75 | #ifdef _WIN64 76 | exceptionRecord.ExceptionAddress = PVOID(context.Rip); 77 | #else 78 | exceptionRecord.ExceptionAddress = PVOID(context.Eip); 79 | #endif // _WIN64 80 | } 81 | 82 | MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = {}; 83 | exceptionInfo.ThreadId = DbgGetThreadId(); 84 | exceptionInfo.ExceptionPointers = &exceptionPointers; 85 | exceptionInfo.ClientPointers = FALSE; 86 | auto dumpType = MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithHandleData); 87 | auto dumpSaved = !!MiniDumpWriteDump(DbgGetProcessHandle(), DbgGetProcessId(), hFile, dumpType, &exceptionInfo, nullptr, nullptr); 88 | 89 | // Re-enable all breakpoints that were previously disabled 90 | for (auto addr : disabled_breakpoints) 91 | { 92 | char cmd[256] = ""; 93 | sprintf_s(cmd, "be 0x%p", (void*)addr); 94 | DbgCmdExecDirect(cmd); 95 | } 96 | 97 | if (dumpSaved) 98 | { 99 | dputs("Dump saved!"); 100 | } 101 | else 102 | { 103 | dprintf("MiniDumpWriteDump failed :( LastError = %d\n", GetLastError()); 104 | } 105 | 106 | CloseHandle(hFile); 107 | return true; 108 | } 109 | 110 | //Initialize your plugin data here. 111 | bool pluginInit(PLUG_INITSTRUCT* initStruct) 112 | { 113 | _plugin_registercommand(pluginHandle, "MiniDump", cbMiniDump, true); 114 | return true; //Return false to cancel loading the plugin. 115 | } 116 | 117 | //Deinitialize your plugin data here. 118 | void pluginStop() 119 | { 120 | } 121 | 122 | //Do GUI/Menu related things here. 123 | void pluginSetup() 124 | { 125 | } 126 | -------------------------------------------------------------------------------- /cmake/cmkr.cmake: -------------------------------------------------------------------------------- 1 | include_guard() 2 | 3 | # Change these defaults to point to your infrastructure if desired 4 | set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE) 5 | set(CMKR_TAG "archive_7c7144b1" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) 6 | 7 | # Set these from the command line to customize for development/debugging purposes 8 | set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable") 9 | set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation") 10 | 11 | # Disable cmkr if generation is disabled 12 | if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION) 13 | message(STATUS "[cmkr] Skipping automatic cmkr generation") 14 | unset(CMKR_BUILD_SKIP_GENERATION CACHE) 15 | macro(cmkr) 16 | endmacro() 17 | return() 18 | endif() 19 | 20 | # Disable cmkr if no cmake.toml file is found 21 | if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") 22 | message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") 23 | macro(cmkr) 24 | endmacro() 25 | return() 26 | endif() 27 | 28 | # Convert a Windows native path to CMake path 29 | if(CMKR_EXECUTABLE MATCHES "\\\\") 30 | string(REPLACE "\\" "/" CMKR_EXECUTABLE_CMAKE "${CMKR_EXECUTABLE}") 31 | set(CMKR_EXECUTABLE "${CMKR_EXECUTABLE_CMAKE}" CACHE FILEPATH "" FORCE) 32 | unset(CMKR_EXECUTABLE_CMAKE) 33 | endif() 34 | 35 | # Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher) 36 | function(cmkr_exec) 37 | execute_process(COMMAND ${ARGV} RESULT_VARIABLE CMKR_EXEC_RESULT) 38 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 39 | message(FATAL_ERROR "cmkr_exec(${ARGV}) failed (exit code ${CMKR_EXEC_RESULT})") 40 | endif() 41 | endfunction() 42 | 43 | # Windows-specific hack (CMAKE_EXECUTABLE_PREFIX is not set at the moment) 44 | if(WIN32) 45 | set(CMKR_EXECUTABLE_NAME "cmkr.exe") 46 | else() 47 | set(CMKR_EXECUTABLE_NAME "cmkr") 48 | endif() 49 | 50 | # Use cached cmkr if found 51 | set(CMKR_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_${CMKR_TAG}") 52 | set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}") 53 | 54 | if(NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE AND CMKR_EXECUTABLE MATCHES "^${CMAKE_CURRENT_BINARY_DIR}/_cmkr") 55 | message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") 56 | unset(CMKR_EXECUTABLE CACHE) 57 | endif() 58 | 59 | if(CMKR_EXECUTABLE AND EXISTS "${CMKR_EXECUTABLE}") 60 | message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'") 61 | elseif(CMKR_EXECUTABLE AND NOT CMKR_EXECUTABLE STREQUAL CMKR_CACHED_EXECUTABLE) 62 | message(FATAL_ERROR "[cmkr] '${CMKR_EXECUTABLE}' not found") 63 | else() 64 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 65 | message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'") 66 | 67 | message(STATUS "[cmkr] Fetching cmkr...") 68 | if(EXISTS "${CMKR_DIRECTORY}") 69 | cmkr_exec("${CMAKE_COMMAND}" -E rm -rf "${CMKR_DIRECTORY}") 70 | endif() 71 | find_package(Git QUIET REQUIRED) 72 | cmkr_exec("${GIT_EXECUTABLE}" 73 | clone 74 | --config advice.detachedHead=false 75 | --branch ${CMKR_TAG} 76 | --depth 1 77 | ${CMKR_REPO} 78 | "${CMKR_DIRECTORY}" 79 | ) 80 | message(STATUS "[cmkr] Building cmkr...") 81 | cmkr_exec("${CMAKE_COMMAND}" 82 | --no-warn-unused-cli 83 | "${CMKR_DIRECTORY}" 84 | "-B${CMKR_DIRECTORY}/build" 85 | "-DCMAKE_BUILD_TYPE=Release" 86 | "-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}" 87 | "-DCMKR_GENERATE_DOCUMENTATION=OFF" 88 | ) 89 | cmkr_exec("${CMAKE_COMMAND}" 90 | --build "${CMKR_DIRECTORY}/build" 91 | --config Release 92 | --parallel 93 | ) 94 | cmkr_exec("${CMAKE_COMMAND}" 95 | --install "${CMKR_DIRECTORY}/build" 96 | --config Release 97 | --prefix "${CMKR_DIRECTORY}" 98 | --component cmkr 99 | ) 100 | if(NOT EXISTS ${CMKR_EXECUTABLE}) 101 | message(FATAL_ERROR "[cmkr] Failed to bootstrap '${CMKR_EXECUTABLE}'") 102 | endif() 103 | cmkr_exec("${CMKR_EXECUTABLE}" version) 104 | message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}") 105 | endif() 106 | execute_process(COMMAND "${CMKR_EXECUTABLE}" version 107 | RESULT_VARIABLE CMKR_EXEC_RESULT 108 | ) 109 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 110 | message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding") 111 | endif() 112 | 113 | # This is the macro that contains black magic 114 | macro(cmkr) 115 | # When this macro is called from the generated file, fake some internal CMake variables 116 | get_source_file_property(CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" CMKR_CURRENT_LIST_FILE) 117 | if(CMKR_CURRENT_LIST_FILE) 118 | set(CMAKE_CURRENT_LIST_FILE "${CMKR_CURRENT_LIST_FILE}") 119 | get_filename_component(CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) 120 | endif() 121 | 122 | # File-based include guard (include_guard is not documented to work) 123 | get_source_file_property(CMKR_INCLUDE_GUARD "${CMAKE_CURRENT_LIST_FILE}" CMKR_INCLUDE_GUARD) 124 | if(NOT CMKR_INCLUDE_GUARD) 125 | set_source_files_properties("${CMAKE_CURRENT_LIST_FILE}" PROPERTIES CMKR_INCLUDE_GUARD TRUE) 126 | 127 | file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_PRE) 128 | 129 | # Generate CMakeLists.txt 130 | cmkr_exec("${CMKR_EXECUTABLE}" gen 131 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 132 | ) 133 | 134 | file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST) 135 | 136 | # Delete the temporary file if it was left for some reason 137 | set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt") 138 | if(EXISTS "${CMKR_TEMP_FILE}") 139 | file(REMOVE "${CMKR_TEMP_FILE}") 140 | endif() 141 | 142 | if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST) 143 | # Copy the now-generated CMakeLists.txt to CMakerLists.txt 144 | # This is done because you cannot include() a file you are currently in 145 | configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY) 146 | 147 | # Add the macro required for the hack at the start of the cmkr macro 148 | set_source_files_properties("${CMKR_TEMP_FILE}" PROPERTIES 149 | CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" 150 | ) 151 | 152 | # 'Execute' the newly-generated CMakeLists.txt 153 | include("${CMKR_TEMP_FILE}") 154 | 155 | # Delete the generated file 156 | file(REMOVE "${CMKR_TEMP_FILE}") 157 | 158 | # Do not execute the rest of the original CMakeLists.txt 159 | return() 160 | endif() 161 | # Resume executing the unmodified CMakeLists.txt 162 | endif() 163 | endmacro() 164 | -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | # CPM.cmake - CMake's missing package manager 2 | # =========================================== 3 | # See https://github.com/TheLartians/CPM.cmake for usage and update instructions. 4 | # 5 | # MIT License 6 | # ----------- 7 | #[[ 8 | Copyright (c) 2019 Lars Melchior 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | ]] 28 | 29 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 30 | 31 | set(CURRENT_CPM_VERSION 0.27.5) 32 | 33 | if(CPM_DIRECTORY) 34 | if(NOT CPM_DIRECTORY STREQUAL CMAKE_CURRENT_LIST_DIR) 35 | if (CPM_VERSION VERSION_LESS CURRENT_CPM_VERSION) 36 | message(AUTHOR_WARNING "${CPM_INDENT} \ 37 | A dependency is using a more recent CPM version (${CURRENT_CPM_VERSION}) than the current project (${CPM_VERSION}). \ 38 | It is recommended to upgrade CPM to the most recent version. \ 39 | See https://github.com/TheLartians/CPM.cmake for more information." 40 | ) 41 | endif() 42 | return() 43 | endif() 44 | 45 | get_property(CPM_INITIALIZED GLOBAL "" PROPERTY CPM_INITIALIZED SET) 46 | if (CPM_INITIALIZED) 47 | return() 48 | endif() 49 | endif() 50 | 51 | set_property(GLOBAL PROPERTY CPM_INITIALIZED true) 52 | 53 | option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies" $ENV{CPM_USE_LOCAL_PACKAGES}) 54 | option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies" $ENV{CPM_LOCAL_PACKAGES_ONLY}) 55 | option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL}) 56 | option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package" $ENV{CPM_DONT_UPDATE_MODULE_PATH}) 57 | option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path" $ENV{CPM_DONT_CREATE_PACKAGE_LOCK}) 58 | option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK "Add all packages added through CPM.cmake to the package lock" $ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK}) 59 | 60 | set(CPM_VERSION ${CURRENT_CPM_VERSION} CACHE INTERNAL "") 61 | set(CPM_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} CACHE INTERNAL "") 62 | set(CPM_FILE ${CMAKE_CURRENT_LIST_FILE} CACHE INTERNAL "") 63 | set(CPM_PACKAGES "" CACHE INTERNAL "") 64 | set(CPM_DRY_RUN OFF CACHE INTERNAL "Don't download or configure dependencies (for testing)") 65 | 66 | if(DEFINED ENV{CPM_SOURCE_CACHE}) 67 | set(CPM_SOURCE_CACHE_DEFAULT $ENV{CPM_SOURCE_CACHE}) 68 | else() 69 | set(CPM_SOURCE_CACHE_DEFAULT OFF) 70 | endif() 71 | 72 | set(CPM_SOURCE_CACHE ${CPM_SOURCE_CACHE_DEFAULT} CACHE PATH "Directory to downlaod CPM dependencies") 73 | 74 | if (NOT CPM_DONT_UPDATE_MODULE_PATH) 75 | set(CPM_MODULE_PATH "${CMAKE_BINARY_DIR}/CPM_modules" CACHE INTERNAL "") 76 | # remove old modules 77 | FILE(REMOVE_RECURSE ${CPM_MODULE_PATH}) 78 | file(MAKE_DIRECTORY ${CPM_MODULE_PATH}) 79 | # locally added CPM modules should override global packages 80 | set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}") 81 | endif() 82 | 83 | if (NOT CPM_DONT_CREATE_PACKAGE_LOCK) 84 | set(CPM_PACKAGE_LOCK_FILE "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake" CACHE INTERNAL "") 85 | file(WRITE ${CPM_PACKAGE_LOCK_FILE} "# CPM Package Lock\n# This file should be committed to version control\n\n") 86 | endif() 87 | 88 | include(FetchContent) 89 | include(CMakeParseArguments) 90 | 91 | # Initialize logging prefix 92 | if(NOT CPM_INDENT) 93 | set(CPM_INDENT "CPM:") 94 | endif() 95 | 96 | function(cpm_find_package NAME VERSION) 97 | string(REPLACE " " ";" EXTRA_ARGS "${ARGN}") 98 | find_package(${NAME} ${VERSION} ${EXTRA_ARGS} QUIET) 99 | if(${CPM_ARGS_NAME}_FOUND) 100 | message(STATUS "${CPM_INDENT} using local package ${CPM_ARGS_NAME}@${VERSION}") 101 | CPMRegisterPackage(${CPM_ARGS_NAME} "${VERSION}") 102 | set(CPM_PACKAGE_FOUND YES PARENT_SCOPE) 103 | else() 104 | set(CPM_PACKAGE_FOUND NO PARENT_SCOPE) 105 | endif() 106 | endfunction() 107 | 108 | # Create a custom FindXXX.cmake module for a CPM package 109 | # This prevents `find_package(NAME)` from finding the system library 110 | function(CPMCreateModuleFile Name) 111 | if (NOT CPM_DONT_UPDATE_MODULE_PATH) 112 | # erase any previous modules 113 | FILE(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake "include(${CPM_FILE})\n${ARGN}\nset(${Name}_FOUND TRUE)") 114 | endif() 115 | endfunction() 116 | 117 | # Find a package locally or fallback to CPMAddPackage 118 | function(CPMFindPackage) 119 | set(oneValueArgs 120 | NAME 121 | VERSION 122 | GIT_TAG 123 | FIND_PACKAGE_ARGUMENTS 124 | ) 125 | 126 | cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "" ${ARGN}) 127 | 128 | if (NOT DEFINED CPM_ARGS_VERSION) 129 | if (DEFINED CPM_ARGS_GIT_TAG) 130 | cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) 131 | endif() 132 | endif() 133 | 134 | if (CPM_DOWNLOAD_ALL) 135 | CPMAddPackage(${ARGN}) 136 | cpm_export_variables(${CPM_ARGS_NAME}) 137 | return() 138 | endif() 139 | 140 | CPMCheckIfPackageAlreadyAdded(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" "${CPM_ARGS_OPTIONS}") 141 | if (CPM_PACKAGE_ALREADY_ADDED) 142 | cpm_export_variables(${CPM_ARGS_NAME}) 143 | return() 144 | endif() 145 | 146 | cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) 147 | 148 | if(NOT CPM_PACKAGE_FOUND) 149 | CPMAddPackage(${ARGN}) 150 | cpm_export_variables(${CPM_ARGS_NAME}) 151 | endif() 152 | 153 | endfunction() 154 | 155 | # checks if a package has been added before 156 | function(CPMCheckIfPackageAlreadyAdded CPM_ARGS_NAME CPM_ARGS_VERSION CPM_ARGS_OPTIONS) 157 | if ("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES) 158 | CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION) 159 | if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}") 160 | message(WARNING "${CPM_INDENT} requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION}).") 161 | endif() 162 | if (CPM_ARGS_OPTIONS) 163 | foreach(OPTION ${CPM_ARGS_OPTIONS}) 164 | cpm_parse_option(${OPTION}) 165 | if(NOT "${${OPTION_KEY}}" STREQUAL "${OPTION_VALUE}") 166 | message(WARNING "${CPM_INDENT} ignoring package option for ${CPM_ARGS_NAME}: ${OPTION_KEY} = ${OPTION_VALUE} (${${OPTION_KEY}})") 167 | endif() 168 | endforeach() 169 | endif() 170 | cpm_get_fetch_properties(${CPM_ARGS_NAME}) 171 | SET(${CPM_ARGS_NAME}_ADDED NO) 172 | SET(CPM_PACKAGE_ALREADY_ADDED YES PARENT_SCOPE) 173 | cpm_export_variables(${CPM_ARGS_NAME}) 174 | else() 175 | SET(CPM_PACKAGE_ALREADY_ADDED NO PARENT_SCOPE) 176 | endif() 177 | endfunction() 178 | 179 | # Download and add a package from source 180 | function(CPMAddPackage) 181 | 182 | set(oneValueArgs 183 | NAME 184 | FORCE 185 | VERSION 186 | GIT_TAG 187 | DOWNLOAD_ONLY 188 | GITHUB_REPOSITORY 189 | GITLAB_REPOSITORY 190 | GIT_REPOSITORY 191 | SOURCE_DIR 192 | DOWNLOAD_COMMAND 193 | FIND_PACKAGE_ARGUMENTS 194 | NO_CACHE 195 | GIT_SHALLOW 196 | ) 197 | 198 | set(multiValueArgs 199 | OPTIONS 200 | ) 201 | 202 | cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") 203 | 204 | # Set default values for arguments 205 | 206 | if (NOT DEFINED CPM_ARGS_VERSION) 207 | if (DEFINED CPM_ARGS_GIT_TAG) 208 | cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) 209 | endif() 210 | endif() 211 | 212 | if(CPM_ARGS_DOWNLOAD_ONLY) 213 | set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY}) 214 | else() 215 | set(DOWNLOAD_ONLY NO) 216 | endif() 217 | 218 | if (DEFINED CPM_ARGS_GITHUB_REPOSITORY) 219 | set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git") 220 | endif() 221 | 222 | if (DEFINED CPM_ARGS_GITLAB_REPOSITORY) 223 | set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git") 224 | endif() 225 | 226 | if (DEFINED CPM_ARGS_GIT_REPOSITORY) 227 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY}) 228 | if (NOT DEFINED CPM_ARGS_GIT_TAG) 229 | set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION}) 230 | endif() 231 | endif() 232 | 233 | if (DEFINED CPM_ARGS_GIT_TAG) 234 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG}) 235 | # If GIT_SHALLOW is explicitly specified, honor the value. 236 | if (DEFINED CPM_ARGS_GIT_SHALLOW) 237 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW ${CPM_ARGS_GIT_SHALLOW}) 238 | endif() 239 | endif() 240 | 241 | # Check if package has been added before 242 | CPMCheckIfPackageAlreadyAdded(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" "${CPM_ARGS_OPTIONS}") 243 | if (CPM_PACKAGE_ALREADY_ADDED) 244 | cpm_export_variables(${CPM_ARGS_NAME}) 245 | return() 246 | endif() 247 | 248 | # Check for manual overrides 249 | if (NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "") 250 | set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE}) 251 | set(CPM_${CPM_ARGS_NAME}_SOURCE "") 252 | CPMAddPackage( 253 | NAME ${CPM_ARGS_NAME} 254 | SOURCE_DIR ${PACKAGE_SOURCE} 255 | FORCE True 256 | OPTIONS ${CPM_ARGS_OPTIONS} 257 | ) 258 | cpm_export_variables(${CPM_ARGS_NAME}) 259 | return() 260 | endif() 261 | 262 | # Check for available declaration 263 | if (NOT CPM_ARGS_FORCE AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "") 264 | set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}}) 265 | set(CPM_DECLARATION_${CPM_ARGS_NAME} "") 266 | CPMAddPackage(${declaration}) 267 | cpm_export_variables(${CPM_ARGS_NAME}) 268 | # checking again to ensure version and option compatibility 269 | CPMCheckIfPackageAlreadyAdded(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" "${CPM_ARGS_OPTIONS}") 270 | return() 271 | endif() 272 | 273 | if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY) 274 | cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) 275 | 276 | if(CPM_PACKAGE_FOUND) 277 | cpm_export_variables(${CPM_ARGS_NAME}) 278 | return() 279 | endif() 280 | 281 | if(CPM_LOCAL_PACKAGES_ONLY) 282 | message(SEND_ERROR "CPM: ${CPM_ARGS_NAME} not found via find_package(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION})") 283 | endif() 284 | endif() 285 | 286 | CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}") 287 | 288 | if (CPM_ARGS_OPTIONS) 289 | foreach(OPTION ${CPM_ARGS_OPTIONS}) 290 | cpm_parse_option(${OPTION}) 291 | set(${OPTION_KEY} ${OPTION_VALUE} CACHE INTERNAL "") 292 | endforeach() 293 | endif() 294 | 295 | if (DEFINED CPM_ARGS_GIT_TAG) 296 | set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}") 297 | elseif (DEFINED CPM_ARGS_SOURCE_DIR) 298 | set(PACKAGE_INFO "${CPM_ARGS_SOURCE_DIR}") 299 | else() 300 | set(PACKAGE_INFO "${CPM_ARGS_VERSION}") 301 | endif() 302 | 303 | if (DEFINED CPM_ARGS_DOWNLOAD_COMMAND) 304 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND}) 305 | elseif (DEFINED CPM_ARGS_SOURCE_DIR) 306 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR}) 307 | elseif (CPM_SOURCE_CACHE AND NOT CPM_ARGS_NO_CACHE) 308 | string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) 309 | set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS}) 310 | list(SORT origin_parameters) 311 | string(SHA1 origin_hash "${origin_parameters}") 312 | set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}) 313 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory}) 314 | if (EXISTS ${download_directory}) 315 | # disable the download command to allow offline builds 316 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND "${CMAKE_COMMAND}") 317 | set(PACKAGE_INFO "${download_directory}") 318 | else() 319 | # Enable shallow clone when GIT_TAG is not a commit hash. 320 | # Our guess may not be accurate, but it should guarantee no commit hash get mis-detected. 321 | if (NOT DEFINED CPM_ARGS_GIT_SHALLOW) 322 | cpm_is_git_tag_commit_hash("${CPM_ARGS_GIT_TAG}" IS_HASH) 323 | if (NOT ${IS_HASH}) 324 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW TRUE) 325 | endif() 326 | endif() 327 | 328 | # remove timestamps so CMake will re-download the dependency 329 | file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/_deps/${lower_case_name}-subbuild) 330 | set(PACKAGE_INFO "${PACKAGE_INFO} -> ${download_directory}") 331 | endif() 332 | endif() 333 | 334 | CPMCreateModuleFile(${CPM_ARGS_NAME} "CPMAddPackage(${ARGN})") 335 | 336 | if (CPM_PACKAGE_LOCK_ENABLED) 337 | if ((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK) 338 | cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") 339 | elseif(CPM_ARGS_SOURCE_DIR) 340 | cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "local directory") 341 | else() 342 | cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") 343 | endif() 344 | endif() 345 | 346 | cpm_declare_fetch("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}" "${PACKAGE_INFO}" "${CPM_ARGS_UNPARSED_ARGUMENTS}") 347 | cpm_fetch_package("${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}") 348 | cpm_get_fetch_properties("${CPM_ARGS_NAME}") 349 | 350 | SET(${CPM_ARGS_NAME}_ADDED YES) 351 | cpm_export_variables("${CPM_ARGS_NAME}") 352 | endfunction() 353 | 354 | # Fetch a previously declared package 355 | macro(CPMGetPackage Name) 356 | if (DEFINED "CPM_DECLARATION_${Name}") 357 | CPMAddPackage( 358 | NAME ${Name} 359 | ) 360 | else() 361 | message(SEND_ERROR "Cannot retrieve package ${Name}: no declaration available") 362 | endif() 363 | endmacro() 364 | 365 | # export variables available to the caller to the parent scope 366 | # expects ${CPM_ARGS_NAME} to be set 367 | macro(cpm_export_variables name) 368 | SET(${name}_SOURCE_DIR "${${name}_SOURCE_DIR}" PARENT_SCOPE) 369 | SET(${name}_BINARY_DIR "${${name}_BINARY_DIR}" PARENT_SCOPE) 370 | SET(${name}_ADDED "${${name}_ADDED}" PARENT_SCOPE) 371 | endmacro() 372 | 373 | # declares a package, so that any call to CPMAddPackage for the 374 | # package name will use these arguments instead. 375 | # Previous declarations will not be overriden. 376 | macro(CPMDeclarePackage Name) 377 | if (NOT DEFINED "CPM_DECLARATION_${Name}") 378 | set("CPM_DECLARATION_${Name}" "${ARGN}") 379 | endif() 380 | endmacro() 381 | 382 | function(cpm_add_to_package_lock Name) 383 | if (NOT CPM_DONT_CREATE_PACKAGE_LOCK) 384 | file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name} \"${ARGN}\")\n") 385 | endif() 386 | endfunction() 387 | 388 | function(cpm_add_comment_to_package_lock Name) 389 | if (NOT CPM_DONT_CREATE_PACKAGE_LOCK) 390 | file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name} (unversioned)\n# CPMDeclarePackage(${Name} \"${ARGN}\")\n") 391 | endif() 392 | endfunction() 393 | 394 | # includes the package lock file if it exists and creates a target 395 | # `cpm-write-package-lock` to update it 396 | macro(CPMUsePackageLock file) 397 | if (NOT CPM_DONT_CREATE_PACKAGE_LOCK) 398 | get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE) 399 | if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) 400 | include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) 401 | endif() 402 | if (NOT TARGET cpm-update-package-lock) 403 | add_custom_target(cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE} ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) 404 | endif() 405 | set(CPM_PACKAGE_LOCK_ENABLED true) 406 | endif() 407 | endmacro() 408 | 409 | # registers a package that has been added to CPM 410 | function(CPMRegisterPackage PACKAGE VERSION) 411 | list(APPEND CPM_PACKAGES ${PACKAGE}) 412 | set(CPM_PACKAGES ${CPM_PACKAGES} CACHE INTERNAL "") 413 | set("CPM_PACKAGE_${PACKAGE}_VERSION" ${VERSION} CACHE INTERNAL "") 414 | endfunction() 415 | 416 | # retrieve the current version of the package to ${OUTPUT} 417 | function(CPMGetPackageVersion PACKAGE OUTPUT) 418 | set(${OUTPUT} "${CPM_PACKAGE_${PACKAGE}_VERSION}" PARENT_SCOPE) 419 | endfunction() 420 | 421 | # declares a package in FetchContent_Declare 422 | function (cpm_declare_fetch PACKAGE VERSION INFO) 423 | message(STATUS "${CPM_INDENT} adding package ${PACKAGE}@${VERSION} (${INFO})") 424 | 425 | if (${CPM_DRY_RUN}) 426 | message(STATUS "${CPM_INDENT} package not declared (dry run)") 427 | return() 428 | endif() 429 | 430 | FetchContent_Declare(${PACKAGE} 431 | ${ARGN} 432 | ) 433 | endfunction() 434 | 435 | # returns properties for a package previously defined by cpm_declare_fetch 436 | function (cpm_get_fetch_properties PACKAGE) 437 | if (${CPM_DRY_RUN}) 438 | return() 439 | endif() 440 | FetchContent_GetProperties(${PACKAGE}) 441 | string(TOLOWER ${PACKAGE} lpackage) 442 | SET(${PACKAGE}_SOURCE_DIR "${${lpackage}_SOURCE_DIR}" PARENT_SCOPE) 443 | SET(${PACKAGE}_BINARY_DIR "${${lpackage}_BINARY_DIR}" PARENT_SCOPE) 444 | endfunction() 445 | 446 | # downloads a previously declared package via FetchContent 447 | function (cpm_fetch_package PACKAGE DOWNLOAD_ONLY) 448 | if (${CPM_DRY_RUN}) 449 | message(STATUS "${CPM_INDENT} package ${PACKAGE} not fetched (dry run)") 450 | return() 451 | endif() 452 | 453 | if(DOWNLOAD_ONLY) 454 | FetchContent_GetProperties(${PACKAGE}) 455 | if(NOT ${PACKAGE}_POPULATED) 456 | FetchContent_Populate(${PACKAGE}) 457 | endif() 458 | else() 459 | set(CPM_OLD_INDENT "${CPM_INDENT}") 460 | set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:") 461 | FetchContent_MakeAvailable(${PACKAGE}) 462 | set(CPM_INDENT "${CPM_OLD_INDENT}") 463 | endif() 464 | endfunction() 465 | 466 | # splits a package option 467 | function(cpm_parse_option OPTION) 468 | string(REGEX MATCH "^[^ ]+" OPTION_KEY ${OPTION}) 469 | string(LENGTH ${OPTION} OPTION_LENGTH) 470 | string(LENGTH ${OPTION_KEY} OPTION_KEY_LENGTH) 471 | if (OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH) 472 | # no value for key provided, assume user wants to set option to "ON" 473 | set(OPTION_VALUE "ON") 474 | else() 475 | math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1") 476 | string(SUBSTRING ${OPTION} "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE) 477 | endif() 478 | set(OPTION_KEY "${OPTION_KEY}" PARENT_SCOPE) 479 | set(OPTION_VALUE "${OPTION_VALUE}" PARENT_SCOPE) 480 | endfunction() 481 | 482 | # guesses the package version from a git tag 483 | function(cpm_get_version_from_git_tag GIT_TAG RESULT) 484 | string(LENGTH ${GIT_TAG} length) 485 | if (length EQUAL 40) 486 | # GIT_TAG is probably a git hash 487 | SET(${RESULT} 0 PARENT_SCOPE) 488 | else() 489 | string(REGEX MATCH "v?([0123456789.]*).*" _ ${GIT_TAG}) 490 | SET(${RESULT} ${CMAKE_MATCH_1} PARENT_SCOPE) 491 | endif() 492 | endfunction() 493 | 494 | # guesses if the git tag is a commit hash or an actual tag or a branch nane. 495 | function(cpm_is_git_tag_commit_hash GIT_TAG RESULT) 496 | string(LENGTH "${GIT_TAG}" length) 497 | # full hash has 40 characters, and short hash has at least 7 characters. 498 | if (length LESS 7 OR length GREATER 40) 499 | SET(${RESULT} 0 PARENT_SCOPE) 500 | else() 501 | if (${GIT_TAG} MATCHES "^[a-fA-F0-9]+$") 502 | SET(${RESULT} 1 PARENT_SCOPE) 503 | else() 504 | SET(${RESULT} 0 PARENT_SCOPE) 505 | endif() 506 | endif() 507 | endfunction() 508 | --------------------------------------------------------------------------------