├── .gitattributes ├── .gitmodules ├── .gitignore ├── src ├── ui.h ├── ui.cpp └── main.cpp ├── README.md ├── cmake.toml ├── CMakeLists.txt └── cmkr.cmake /.gitattributes: -------------------------------------------------------------------------------- 1 | CMakeLists.txt linguist-generated 2 | /cmkr.cmake linguist-generated -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "imgui"] 2 | path = imgui 3 | url = https://github.com/ocornut/imgui 4 | [submodule "glfw"] 5 | path = glfw 6 | url = https://github.com/glfw/glfw 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | bin2 3 | compile_commands.json 4 | .clangd 5 | temp.* 6 | .vs 7 | .cache 8 | build*/ 9 | .idea/ 10 | cmake-build*/ 11 | CMakeLists.txt.user 12 | .vscode/ 13 | imgui.ini -------------------------------------------------------------------------------- /src/ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | struct UI 6 | { 7 | UI(int argc, char** argv); 8 | 9 | bool show_demo_window = false; 10 | bool show_another_window = false; 11 | 12 | bool Show(); 13 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # imgui_cmkr 2 | 3 | Simple template to use [Dear ImGui](https://github.com/ocornut/imgui) with GLFW. 4 | 5 | ## Dependencies 6 | 7 | On Windows and macos this should build without installing any system dependencies. 8 | 9 | On Linux you need to install the X11/Wayland development packages. Ubuntu: 10 | 11 | ```sh 12 | sudo apt install xorg-dev libwayland-dev 13 | ``` 14 | 15 | To only build for X11, use the following configuration options: 16 | 17 | ```sh 18 | cmake -B build -DGLFW_BUILD_WAYLAND=OFF -DGLFW_BUILD_X11=ON 19 | ``` 20 | 21 | ## Building 22 | 23 | ```sh 24 | git submodule update --init 25 | cmake -B build 26 | ``` 27 | -------------------------------------------------------------------------------- /cmake.toml: -------------------------------------------------------------------------------- 1 | # Reference: https://build-cpp.github.io/cmkr/cmake-toml 2 | [project] 3 | name = "imgui_cmkr" 4 | 5 | [variables] 6 | GLFW_BUILD_DOCS = false 7 | GLFW_INSTALL = false 8 | 9 | # 3.3.10 (submodule, newer versions do not work on some Linux distros) 10 | [subdir.glfw] 11 | 12 | # v1.92.1-docking (submodule) 13 | [target.imgui] 14 | type = "static" 15 | include-directories = [ 16 | "imgui", 17 | "imgui/backends", 18 | ] 19 | sources = [ 20 | "imgui/*.cpp", 21 | "imgui/*.h", 22 | "imgui/backends/imgui_impl_opengl3.cpp", 23 | "imgui/backends/imgui_impl_opengl3.h", 24 | "imgui/backends/imgui_impl_opengl3_loader.h", 25 | "imgui/backends/imgui_impl_glfw.cpp", 26 | "imgui/backends/imgui_impl_glfw.h", 27 | ] 28 | windows.sources = [ 29 | "imgui/backends/imgui_impl_win32.cpp", 30 | "imgui/backends/imgui_impl_win32.h", 31 | ] 32 | link-libraries = ["::glfw"] 33 | 34 | [target.imgui_cmkr] 35 | type = "executable" 36 | sources = [ 37 | "src/main.cpp", 38 | "src/ui.cpp", 39 | "src/ui.h", 40 | ] 41 | compile-features = ["cxx_std_11"] 42 | link-libraries = ["::imgui"] 43 | msvc.link-options = ["/SUBSYSTEM:WINDOWS"] 44 | -------------------------------------------------------------------------------- /src/ui.cpp: -------------------------------------------------------------------------------- 1 | #include "ui.h" 2 | 3 | UI::UI(int argc, char** argv) 4 | { 5 | // TODO: process argc and argv if necessary 6 | } 7 | 8 | bool UI::Show() 9 | { 10 | bool keepOpen = true; 11 | 12 | // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. 13 | { 14 | static float f = 0.0f; 15 | static int counter = 0; 16 | 17 | ImGui::Begin("Main Window", &keepOpen); // Create a window called "Hello, world!" and append into it. 18 | 19 | ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) 20 | ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state 21 | ImGui::Checkbox("Another Window", &show_another_window); 22 | 23 | ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f 24 | //ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color 25 | 26 | if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) 27 | counter++; 28 | ImGui::SameLine(); 29 | ImGui::Text("counter = %d", counter); 30 | 31 | ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); 32 | ImGui::End(); 33 | } 34 | 35 | // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). 36 | if (show_demo_window) 37 | ImGui::ShowDemoWindow(&show_demo_window); 38 | 39 | // 3. Show another simple window. 40 | if (show_another_window) 41 | { 42 | ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) 43 | ImGui::Text("Hello from another window!"); 44 | if (ImGui::Button("Close Me")) 45 | show_another_window = false; 46 | ImGui::End(); 47 | } 48 | 49 | return keepOpen; 50 | } -------------------------------------------------------------------------------- /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 | set(CMKR_ROOT_PROJECT OFF) 11 | if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 12 | set(CMKR_ROOT_PROJECT ON) 13 | 14 | # Bootstrap cmkr and automatically regenerate CMakeLists.txt 15 | include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT) 16 | if(CMKR_INCLUDE_RESULT) 17 | cmkr() 18 | endif() 19 | 20 | # Enable folder support 21 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 22 | 23 | # Create a configure-time dependency on cmake.toml to improve IDE support 24 | set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS cmake.toml) 25 | endif() 26 | 27 | # Variables 28 | set(GLFW_BUILD_DOCS OFF) 29 | set(GLFW_INSTALL OFF) 30 | 31 | project(imgui_cmkr) 32 | 33 | # Subdirectory: glfw 34 | set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER}) 35 | if(CMAKE_FOLDER) 36 | set(CMAKE_FOLDER "${CMAKE_FOLDER}/glfw") 37 | else() 38 | set(CMAKE_FOLDER glfw) 39 | endif() 40 | add_subdirectory(glfw) 41 | set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER}) 42 | 43 | # Target: imgui 44 | set(imgui_SOURCES 45 | cmake.toml 46 | "imgui/backends/imgui_impl_glfw.cpp" 47 | "imgui/backends/imgui_impl_glfw.h" 48 | "imgui/backends/imgui_impl_opengl3.cpp" 49 | "imgui/backends/imgui_impl_opengl3.h" 50 | "imgui/backends/imgui_impl_opengl3_loader.h" 51 | "imgui/imconfig.h" 52 | "imgui/imgui.cpp" 53 | "imgui/imgui.h" 54 | "imgui/imgui_demo.cpp" 55 | "imgui/imgui_draw.cpp" 56 | "imgui/imgui_internal.h" 57 | "imgui/imgui_tables.cpp" 58 | "imgui/imgui_widgets.cpp" 59 | "imgui/imstb_rectpack.h" 60 | "imgui/imstb_textedit.h" 61 | "imgui/imstb_truetype.h" 62 | ) 63 | 64 | if(WIN32) # windows 65 | list(APPEND imgui_SOURCES 66 | "imgui/backends/imgui_impl_win32.cpp" 67 | "imgui/backends/imgui_impl_win32.h" 68 | ) 69 | endif() 70 | 71 | add_library(imgui STATIC) 72 | 73 | target_sources(imgui PRIVATE ${imgui_SOURCES}) 74 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${imgui_SOURCES}) 75 | 76 | target_include_directories(imgui PUBLIC 77 | imgui 78 | "imgui/backends" 79 | ) 80 | 81 | if(NOT TARGET glfw) 82 | message(FATAL_ERROR "Target \"glfw\" referenced by \"imgui\" does not exist!") 83 | endif() 84 | 85 | target_link_libraries(imgui PUBLIC 86 | glfw 87 | ) 88 | 89 | # Target: imgui_cmkr 90 | set(imgui_cmkr_SOURCES 91 | cmake.toml 92 | "src/main.cpp" 93 | "src/ui.cpp" 94 | "src/ui.h" 95 | ) 96 | 97 | add_executable(imgui_cmkr) 98 | 99 | target_sources(imgui_cmkr PRIVATE ${imgui_cmkr_SOURCES}) 100 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${imgui_cmkr_SOURCES}) 101 | 102 | target_compile_features(imgui_cmkr PRIVATE 103 | cxx_std_11 104 | ) 105 | 106 | if(NOT TARGET imgui) 107 | message(FATAL_ERROR "Target \"imgui\" referenced by \"imgui_cmkr\" does not exist!") 108 | endif() 109 | 110 | target_link_libraries(imgui_cmkr PRIVATE 111 | imgui 112 | ) 113 | 114 | if(MSVC) # msvc 115 | target_link_options(imgui_cmkr PRIVATE 116 | "/SUBSYSTEM:WINDOWS" 117 | ) 118 | endif() 119 | 120 | get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT) 121 | if(NOT CMKR_VS_STARTUP_PROJECT) 122 | set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT imgui_cmkr) 123 | endif() 124 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ui.h" 2 | #include "imgui_impl_glfw.h" 3 | #include "imgui_impl_opengl3.h" 4 | #include 5 | #include 6 | #define GLFW_INCLUDE_NONE 7 | #define GL_SILENCE_DEPRECATION 8 | #if defined(IMGUI_IMPL_OPENGL_ES2) 9 | #include 10 | #endif 11 | // Kinda illegal, but it works 12 | #include 13 | #include // Will drag system OpenGL headers 14 | 15 | // This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. 16 | #ifdef __EMSCRIPTEN__ 17 | #error TODO: Emscripten support 18 | #include "../libs/emscripten/emscripten_mainloop_stub.h" 19 | #endif 20 | 21 | // Main code 22 | int main(int argc, char** argv) 23 | { 24 | // Initialize our UI state 25 | UI ui(argc, argv); 26 | 27 | glfwSetErrorCallback([](int error, const char* description) 28 | { 29 | #ifdef _WIN32 30 | MessageBoxA(GetDesktopWindow(), description, "Error", MB_ICONERROR | MB_OK | MB_SYSTEMMODAL); 31 | #else 32 | fprintf(stderr, "Error %d: %s\n", error, description); 33 | #endif // _WIN32 34 | }); 35 | if (!glfwInit()) 36 | return EXIT_FAILURE; 37 | 38 | // Decide GL+GLSL versions 39 | #if defined(IMGUI_IMPL_OPENGL_ES2) 40 | // GL ES 2.0 + GLSL 100 41 | const char* glsl_version = "#version 100"; 42 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); 43 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 44 | glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); 45 | #elif defined(__APPLE__) 46 | // GL 3.2 + GLSL 150 47 | const char* glsl_version = "#version 150"; 48 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 49 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); 50 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only 51 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac 52 | #else 53 | // GL 3.0 + GLSL 130 54 | const char* glsl_version = "#version 130"; 55 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 56 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 57 | //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only 58 | //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only 59 | #endif 60 | 61 | // https://github.com/TheCherno/Walnut/issues/13 62 | // Hide the main application window 63 | glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); 64 | 65 | // Create window with graphics context (not visible) 66 | GLFWwindow* window = glfwCreateWindow(1, 1, "", NULL, NULL); 67 | if (window == NULL) 68 | return EXIT_FAILURE; 69 | glfwMakeContextCurrent(window); 70 | 71 | // https://github.com/ocornut/imgui/issues/1664#issuecomment-372022042 72 | glfwWaitEventsTimeout(1.0f / 20.0f); 73 | glfwSwapInterval(1); 74 | 75 | // Setup Dear ImGui context 76 | IMGUI_CHECKVERSION(); 77 | ImGui::CreateContext(); 78 | ImGuiIO& io = ImGui::GetIO(); (void)io; 79 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls 80 | //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls 81 | io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking 82 | io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows 83 | io.ConfigViewportsNoAutoMerge = true; 84 | //io.ConfigViewportsNoTaskBarIcon = true; 85 | 86 | // Setup Dear ImGui style 87 | ImGui::StyleColorsDark(); 88 | //ImGui::StyleColorsLight(); 89 | 90 | // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. 91 | ImGuiStyle& style = ImGui::GetStyle(); 92 | if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) 93 | { 94 | style.WindowRounding = 0.0f; 95 | style.Colors[ImGuiCol_WindowBg].w = 1.0f; 96 | } 97 | 98 | // Setup Platform/Renderer backends 99 | ImGui_ImplGlfw_InitForOpenGL(window, true); 100 | ImGui_ImplOpenGL3_Init(glsl_version); 101 | 102 | // Load Fonts 103 | // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. 104 | // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. 105 | // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). 106 | // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. 107 | // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. 108 | // - Read 'docs/FONTS.md' for more instructions and details. 109 | // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! 110 | // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. 111 | //io.Fonts->AddFontDefault(); 112 | //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); 113 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); 114 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); 115 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); 116 | //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); 117 | //IM_ASSERT(font != NULL); 118 | 119 | ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); 120 | 121 | // Main loop 122 | #ifdef __EMSCRIPTEN__ 123 | // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. 124 | // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. 125 | io.IniFilename = NULL; 126 | EMSCRIPTEN_MAINLOOP_BEGIN 127 | #else 128 | while (!glfwWindowShouldClose(window)) 129 | #endif 130 | { 131 | // Poll and handle events (inputs, window resize, etc.) 132 | // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. 133 | // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. 134 | // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. 135 | // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. 136 | glfwPollEvents(); 137 | 138 | // Start the Dear ImGui frame 139 | ImGui_ImplOpenGL3_NewFrame(); 140 | ImGui_ImplGlfw_NewFrame(); 141 | ImGui::NewFrame(); 142 | 143 | // Show the actual UI 144 | if (!ui.Show()) 145 | { 146 | glfwSetWindowShouldClose(window, GLFW_TRUE); 147 | } 148 | 149 | // Rendering 150 | ImGui::Render(); 151 | int display_w, display_h; 152 | glfwGetFramebufferSize(window, &display_w, &display_h); 153 | glViewport(0, 0, display_w, display_h); 154 | glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); 155 | glClear(GL_COLOR_BUFFER_BIT); 156 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 157 | 158 | // Update and Render additional Platform Windows 159 | // (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere. 160 | // For this specific demo app we could also call glfwMakeContextCurrent(window) directly) 161 | if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) 162 | { 163 | GLFWwindow* backup_current_context = glfwGetCurrentContext(); 164 | ImGui::UpdatePlatformWindows(); 165 | ImGui::RenderPlatformWindowsDefault(); 166 | glfwMakeContextCurrent(backup_current_context); 167 | } 168 | 169 | glfwSwapBuffers(window); 170 | } 171 | #ifdef __EMSCRIPTEN__ 172 | EMSCRIPTEN_MAINLOOP_END; 173 | #endif 174 | 175 | // Cleanup 176 | ImGui_ImplOpenGL3_Shutdown(); 177 | ImGui_ImplGlfw_Shutdown(); 178 | ImGui::DestroyContext(); 179 | 180 | glfwDestroyWindow(window); 181 | glfwTerminate(); 182 | 183 | return EXIT_SUCCESS; 184 | } 185 | 186 | #ifdef WIN32 187 | #include 188 | #include 189 | 190 | int WINAPI CALLBACK WinMain( 191 | _In_ HINSTANCE hInstance, 192 | _In_opt_ HINSTANCE hPrevInstance, 193 | _In_ LPSTR lpCmdLine, 194 | _In_ int nShowCmd) 195 | { 196 | // https://utf8everywhere.org/ 197 | int argc = 0; 198 | auto argv = CommandLineToArgvW(GetCommandLineW(), &argc); 199 | auto argv_utf8 = new char* [argc]; 200 | for (int i = 0; i < argc; i++) 201 | { 202 | int requiredSize = WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, 0, 0, 0, 0); 203 | if (requiredSize > 0) 204 | { 205 | argv_utf8[i] = new char[requiredSize]; 206 | WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, argv_utf8[i], requiredSize, 0, 0); 207 | } 208 | else 209 | { 210 | argv_utf8[i] = new char[1]; 211 | argv_utf8[i][0] = '\0'; 212 | } 213 | } 214 | LocalFree(argv); 215 | return main(argc, argv_utf8); 216 | } 217 | #endif // WIN32 218 | -------------------------------------------------------------------------------- /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 "v0.2.44" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) 6 | set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE) 7 | 8 | # To bootstrap/generate a cmkr project: cmake -P cmkr.cmake 9 | if(CMAKE_SCRIPT_MODE_FILE) 10 | set(CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}/build") 11 | set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}") 12 | file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}") 13 | endif() 14 | 15 | # Set these from the command line to customize for development/debugging purposes 16 | set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable") 17 | set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation") 18 | set(CMKR_BUILD_TYPE "Debug" CACHE STRING "cmkr build configuration") 19 | mark_as_advanced(CMKR_REPO CMKR_TAG CMKR_COMMIT_HASH CMKR_EXECUTABLE CMKR_SKIP_GENERATION CMKR_BUILD_TYPE) 20 | 21 | # Disable cmkr if generation is disabled 22 | if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION) 23 | message(STATUS "[cmkr] Skipping automatic cmkr generation") 24 | unset(CMKR_BUILD_SKIP_GENERATION CACHE) 25 | macro(cmkr) 26 | endmacro() 27 | return() 28 | endif() 29 | 30 | # Disable cmkr if no cmake.toml file is found 31 | if(NOT CMAKE_SCRIPT_MODE_FILE AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") 32 | message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") 33 | macro(cmkr) 34 | endmacro() 35 | return() 36 | endif() 37 | 38 | # Convert a Windows native path to CMake path 39 | if(CMKR_EXECUTABLE MATCHES "\\\\") 40 | string(REPLACE "\\" "/" CMKR_EXECUTABLE_CMAKE "${CMKR_EXECUTABLE}") 41 | set(CMKR_EXECUTABLE "${CMKR_EXECUTABLE_CMAKE}" CACHE FILEPATH "" FORCE) 42 | unset(CMKR_EXECUTABLE_CMAKE) 43 | endif() 44 | 45 | # Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher) 46 | function(cmkr_exec) 47 | execute_process(COMMAND ${ARGV} RESULT_VARIABLE CMKR_EXEC_RESULT) 48 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 49 | message(FATAL_ERROR "cmkr_exec(${ARGV}) failed (exit code ${CMKR_EXEC_RESULT})") 50 | endif() 51 | endfunction() 52 | 53 | # Windows-specific hack (CMAKE_EXECUTABLE_PREFIX is not set at the moment) 54 | if(WIN32) 55 | set(CMKR_EXECUTABLE_NAME "cmkr.exe") 56 | else() 57 | set(CMKR_EXECUTABLE_NAME "cmkr") 58 | endif() 59 | 60 | # Use cached cmkr if found 61 | if(DEFINED ENV{CMKR_CACHE}) 62 | set(CMKR_DIRECTORY_PREFIX "$ENV{CMKR_CACHE}") 63 | string(REPLACE "\\" "/" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}") 64 | if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$") 65 | set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/") 66 | endif() 67 | # Build in release mode for the cache 68 | set(CMKR_BUILD_TYPE "Release") 69 | else() 70 | set(CMKR_DIRECTORY_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_") 71 | endif() 72 | set(CMKR_DIRECTORY "${CMKR_DIRECTORY_PREFIX}${CMKR_TAG}") 73 | set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}") 74 | 75 | # Helper function to check if a string starts with a prefix 76 | # Cannot use MATCHES, see: https://github.com/build-cpp/cmkr/issues/61 77 | function(cmkr_startswith str prefix result) 78 | string(LENGTH "${prefix}" prefix_length) 79 | string(LENGTH "${str}" str_length) 80 | if(prefix_length LESS_EQUAL str_length) 81 | string(SUBSTRING "${str}" 0 ${prefix_length} str_prefix) 82 | if(prefix STREQUAL str_prefix) 83 | set("${result}" ON PARENT_SCOPE) 84 | return() 85 | endif() 86 | endif() 87 | set("${result}" OFF PARENT_SCOPE) 88 | endfunction() 89 | 90 | # Handle upgrading logic 91 | if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE) 92 | cmkr_startswith("${CMKR_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/_cmkr" CMKR_STARTSWITH_BUILD) 93 | cmkr_startswith("${CMKR_EXECUTABLE}" "${CMKR_DIRECTORY_PREFIX}" CMKR_STARTSWITH_CACHE) 94 | if(CMKR_STARTSWITH_BUILD) 95 | if(DEFINED ENV{CMKR_CACHE}) 96 | message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'") 97 | if(EXISTS "${CMKR_CACHED_EXECUTABLE}") 98 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 99 | else() 100 | unset(CMKR_EXECUTABLE CACHE) 101 | endif() 102 | else() 103 | message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") 104 | unset(CMKR_EXECUTABLE CACHE) 105 | endif() 106 | elseif(DEFINED ENV{CMKR_CACHE} AND CMKR_STARTSWITH_CACHE) 107 | message(AUTHOR_WARNING "[cmkr] Upgrading cached '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") 108 | unset(CMKR_EXECUTABLE CACHE) 109 | endif() 110 | endif() 111 | 112 | if(CMKR_EXECUTABLE AND EXISTS "${CMKR_EXECUTABLE}") 113 | message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'") 114 | elseif(CMKR_EXECUTABLE AND NOT CMKR_EXECUTABLE STREQUAL CMKR_CACHED_EXECUTABLE) 115 | message(FATAL_ERROR "[cmkr] '${CMKR_EXECUTABLE}' not found") 116 | elseif(NOT CMKR_EXECUTABLE AND EXISTS "${CMKR_CACHED_EXECUTABLE}") 117 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 118 | message(STATUS "[cmkr] Found cached cmkr: '${CMKR_EXECUTABLE}'") 119 | else() 120 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 121 | message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'") 122 | 123 | message(STATUS "[cmkr] Fetching cmkr...") 124 | if(EXISTS "${CMKR_DIRECTORY}") 125 | cmkr_exec("${CMAKE_COMMAND}" -E rm -rf "${CMKR_DIRECTORY}") 126 | endif() 127 | find_package(Git QUIET REQUIRED) 128 | cmkr_exec("${GIT_EXECUTABLE}" 129 | clone 130 | --config advice.detachedHead=false 131 | --branch ${CMKR_TAG} 132 | --depth 1 133 | ${CMKR_REPO} 134 | "${CMKR_DIRECTORY}" 135 | ) 136 | if(CMKR_COMMIT_HASH) 137 | execute_process( 138 | COMMAND "${GIT_EXECUTABLE}" checkout -q "${CMKR_COMMIT_HASH}" 139 | RESULT_VARIABLE CMKR_EXEC_RESULT 140 | WORKING_DIRECTORY "${CMKR_DIRECTORY}" 141 | ) 142 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 143 | message(FATAL_ERROR "Tag '${CMKR_TAG}' hash is not '${CMKR_COMMIT_HASH}'") 144 | endif() 145 | endif() 146 | message(STATUS "[cmkr] Building cmkr (using system compiler)...") 147 | cmkr_exec("${CMAKE_COMMAND}" 148 | --no-warn-unused-cli 149 | "${CMKR_DIRECTORY}" 150 | "-B${CMKR_DIRECTORY}/build" 151 | "-DCMAKE_BUILD_TYPE=${CMKR_BUILD_TYPE}" 152 | "-DCMAKE_UNITY_BUILD=ON" 153 | "-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}" 154 | "-DCMKR_GENERATE_DOCUMENTATION=OFF" 155 | ) 156 | cmkr_exec("${CMAKE_COMMAND}" 157 | --build "${CMKR_DIRECTORY}/build" 158 | --config "${CMKR_BUILD_TYPE}" 159 | --parallel 160 | ) 161 | cmkr_exec("${CMAKE_COMMAND}" 162 | --install "${CMKR_DIRECTORY}/build" 163 | --config "${CMKR_BUILD_TYPE}" 164 | --prefix "${CMKR_DIRECTORY}" 165 | --component cmkr 166 | ) 167 | if(NOT EXISTS ${CMKR_EXECUTABLE}) 168 | message(FATAL_ERROR "[cmkr] Failed to bootstrap '${CMKR_EXECUTABLE}'") 169 | endif() 170 | cmkr_exec("${CMKR_EXECUTABLE}" version) 171 | message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}") 172 | endif() 173 | execute_process(COMMAND "${CMKR_EXECUTABLE}" version 174 | RESULT_VARIABLE CMKR_EXEC_RESULT 175 | ) 176 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 177 | message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding") 178 | endif() 179 | 180 | # Use cmkr.cmake as a script 181 | if(CMAKE_SCRIPT_MODE_FILE) 182 | if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml") 183 | execute_process(COMMAND "${CMKR_EXECUTABLE}" init 184 | RESULT_VARIABLE CMKR_EXEC_RESULT 185 | ) 186 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 187 | message(FATAL_ERROR "[cmkr] Failed to bootstrap cmkr project. Please report an issue: https://github.com/build-cpp/cmkr/issues/new") 188 | else() 189 | message(STATUS "[cmkr] Modify cmake.toml and then configure using: cmake -B build") 190 | endif() 191 | else() 192 | execute_process(COMMAND "${CMKR_EXECUTABLE}" gen 193 | RESULT_VARIABLE CMKR_EXEC_RESULT 194 | ) 195 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 196 | message(FATAL_ERROR "[cmkr] Failed to generate project.") 197 | else() 198 | message(STATUS "[cmkr] Configure using: cmake -B build") 199 | endif() 200 | endif() 201 | endif() 202 | 203 | # This is the macro that contains black magic 204 | macro(cmkr) 205 | # When this macro is called from the generated file, fake some internal CMake variables 206 | get_source_file_property(CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" CMKR_CURRENT_LIST_FILE) 207 | if(CMKR_CURRENT_LIST_FILE) 208 | set(CMAKE_CURRENT_LIST_FILE "${CMKR_CURRENT_LIST_FILE}") 209 | get_filename_component(CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) 210 | endif() 211 | 212 | # File-based include guard (include_guard is not documented to work) 213 | get_source_file_property(CMKR_INCLUDE_GUARD "${CMAKE_CURRENT_LIST_FILE}" CMKR_INCLUDE_GUARD) 214 | if(NOT CMKR_INCLUDE_GUARD) 215 | set_source_files_properties("${CMAKE_CURRENT_LIST_FILE}" PROPERTIES CMKR_INCLUDE_GUARD TRUE) 216 | 217 | file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_PRE) 218 | 219 | # Generate CMakeLists.txt 220 | cmkr_exec("${CMKR_EXECUTABLE}" gen 221 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 222 | ) 223 | 224 | file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST) 225 | 226 | # Delete the temporary file if it was left for some reason 227 | set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt") 228 | if(EXISTS "${CMKR_TEMP_FILE}") 229 | file(REMOVE "${CMKR_TEMP_FILE}") 230 | endif() 231 | 232 | if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST) 233 | # Copy the now-generated CMakeLists.txt to CMakerLists.txt 234 | # This is done because you cannot include() a file you are currently in 235 | configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY) 236 | 237 | # Add the macro required for the hack at the start of the cmkr macro 238 | set_source_files_properties("${CMKR_TEMP_FILE}" PROPERTIES 239 | CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" 240 | ) 241 | 242 | # 'Execute' the newly-generated CMakeLists.txt 243 | include("${CMKR_TEMP_FILE}") 244 | 245 | # Delete the generated file 246 | file(REMOVE "${CMKR_TEMP_FILE}") 247 | 248 | # Do not execute the rest of the original CMakeLists.txt 249 | return() 250 | endif() 251 | # Resume executing the unmodified CMakeLists.txt 252 | endif() 253 | endmacro() 254 | --------------------------------------------------------------------------------