From f29f9fa365aae6021c14f9abfa4d76ce898318a7 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Thu, 26 Nov 2020 23:51:25 +0100 Subject: [PATCH] Implement CMake bootstrapping + integration --- cmake/CMakeLists.txt | 23 +++++++++ cmake/cmake.toml | 13 +++++ cmake/cmkr.cmake | 112 ++++++++++++++++++++++++++++++++++++++++++ cmake/src/example.cpp | 6 +++ src/gen.cpp | 10 +++- 5 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 cmake/CMakeLists.txt create mode 100644 cmake/cmake.toml create mode 100644 cmake/cmkr.cmake create mode 100644 cmake/src/example.cpp diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt new file mode 100644 index 0000000..4fb8b85 --- /dev/null +++ b/cmake/CMakeLists.txt @@ -0,0 +1,23 @@ +# This file was generated automatically by cmkr. + +# Regenerate CMakeLists.txt file when necessary +include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT) +if(CMKR_INCLUDE_RESULT) + cmkr() +endif() + +cmake_minimum_required(VERSION 3.15) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(example_PROJECT_VERSION 0.1.0) +project(example VERSION ${example_PROJECT_VERSION}) + +set(EXAMPLE_SOURCES + "src/example.cpp" + ) + +add_executable(example ${EXAMPLE_SOURCES}) + + + diff --git a/cmake/cmake.toml b/cmake/cmake.toml new file mode 100644 index 0000000..b8089b3 --- /dev/null +++ b/cmake/cmake.toml @@ -0,0 +1,13 @@ +# This is a minimal example project used for testing the cmkr bootstrapping process + +[cmake] +minimum = "3.15" + +[project] +name = "example" +version = "0.1.0" + +[[bin]] +name = "example" +type = "exe" +sources = ["src/example.cpp"] \ No newline at end of file diff --git a/cmake/cmkr.cmake b/cmake/cmkr.cmake new file mode 100644 index 0000000..09721fa --- /dev/null +++ b/cmake/cmkr.cmake @@ -0,0 +1,112 @@ +include_guard() + +# Disable cmkr if no cmake.toml file is found +if(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/cmake.toml) + message(STATUS "[cmkr] Not found: ${CMAKE_CURRENT_LIST_DIR}/cmake.toml") + macro(cmkr) + endmacro() + return() +endif() + +# Add a build-time dependency on the contents of cmake.toml to regenerate the CMakeLists.txt when modified +configure_file(${CMAKE_CURRENT_LIST_DIR}/cmake.toml ${CMAKE_CURRENT_BINARY_DIR}/cmake.toml COPYONLY) + +# Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher) +macro(cmkr_exec) + execute_process(COMMAND ${ARGV} RESULT_VARIABLE CMKR_EXEC_RESULT) + if(NOT CMKR_EXEC_RESULT EQUAL 0) + message(FATAL_ERROR "cmkr_exec(${ARGV}) failed (exit code ${CMKR_EXEC_RESULT})") + endif() +endmacro() + +# Windows-specific hack (CMAKE_EXECUTABLE_PREFIX is not set at the moment) +if(WIN32) + set(CMKR_EXECUTABLE_NAME "cmkr.exe") +else() + set(CMKR_EXECUTABLE_NAME "cmkr") +endif() + +# Use cached cmkr if found +if(DEFINED CACHE{CMKR_EXECUTABLE} AND EXISTS ${CMKR_EXECUTABLE}) + message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'") +else() + if(DEFINED CACHE{CMKR_EXECUTABLE}) + message(VERBOSE "[cmkr] '${CMKR_EXECUTABLE}' not found") + endif() + set(CMKR_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/_cmkr) + set(CMKR_EXECUTABLE ${CMAKE_CURRENT_BINARY_DIR}/_cmkr/bin/${CMKR_EXECUTABLE_NAME} CACHE INTERNAL "Full path to cmkr executable") + if(NOT EXISTS ${CMKR_EXECUTABLE}) + message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'") + message(STATUS "[cmkr] Fetching cmkr...") + if(EXISTS ${CMKR_DIRECTORY}) + cmkr_exec(${CMAKE_COMMAND} -E rm -rf ${CMKR_DIRECTORY}) + endif() + find_package(Git QUIET REQUIRED) + set(CMKR_REPO "https://github.com/mrexodia/cmkr") + cmkr_exec(${GIT_EXECUTABLE} clone ${CMKR_REPO} ${CMKR_DIRECTORY}) + cmkr_exec(${CMAKE_COMMAND} ${CMKR_DIRECTORY} -B${CMKR_DIRECTORY}/build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}) + cmkr_exec(${CMAKE_COMMAND} --build ${CMKR_DIRECTORY}/build --parallel --config Release) + cmkr_exec(${CMAKE_COMMAND} --install ${CMKR_DIRECTORY}/build --config Release --prefix ${CMKR_DIRECTORY} --component cmkr) + if(NOT EXISTS ${CMKR_EXECUTABLE}) + message(FATAL_ERROR "[cmkr] Failed to bootstrap '${CMKR_EXECUTABLE}'") + endif() + cmkr_exec(${CMKR_EXECUTABLE} version OUTPUT_VARIABLE CMKR_VERSION) + string(STRIP ${CMKR_VERSION} CMKR_VERSION) + message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}") + else() + message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'") + endif() +endif() +execute_process(COMMAND ${CMKR_EXECUTABLE} version + OUTPUT_VARIABLE CMKR_VERSION + RESULT_VARIABLE CMKR_EXEC_RESULT +) +if(NOT CMKR_EXEC_RESULT EQUAL 0) + unset(CMKR_EXECUTABLE CACHE) + message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding") +endif() +string(STRIP ${CMKR_VERSION} CMKR_VERSION) +message(STATUS "[cmkr] Using ${CMKR_VERSION}") + +# This is the macro that contains black magic +macro(cmkr) + # When this macro is called from the generated file, fake some internal CMake variables + get_source_file_property(CMKR_CURRENT_LIST_FILE ${CMAKE_CURRENT_LIST_FILE} CMKR_CURRENT_LIST_FILE) + if(CMKR_CURRENT_LIST_FILE) + set(CMAKE_CURRENT_LIST_FILE ${CMKR_CURRENT_LIST_FILE}) + get_filename_component(CMAKE_CURRENT_LIST_DIR ${CMAKE_CURRENT_LIST_FILE} DIRECTORY) + endif() + + # File-based include guard (include_guard is not documented to work) + get_source_file_property(CMKR_INCLUDE_GUARD ${CMAKE_CURRENT_LIST_FILE} CMKR_INCLUDE_GUARD) + if(NOT CMKR_INCLUDE_GUARD) + set_source_files_properties(${CMAKE_CURRENT_LIST_FILE} PROPERTIES CMKR_INCLUDE_GUARD TRUE) + + # Generate CMakeLists.txt + cmkr_exec(${CMKR_EXECUTABLE} gen -y + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + OUTPUT_VARIABLE CMKR_GEN_OUTPUT + ) + string(STRIP ${CMKR_GEN_OUTPUT} CMKR_GEN_OUTPUT) + message(STATUS "[cmkr] ${CMKR_GEN_OUTPUT}") + + # Copy the now-generated CMakeLists.txt to CMakerLists.txt + # This is done because you cannot include() a file you are currently in + set(CMKR_TEMP_FILE ${CMAKE_CURRENT_LIST_DIR}/CMakerLists.txt) + configure_file(CMakeLists.txt ${CMKR_TEMP_FILE} COPYONLY) + + # Add the macro required for the hack at the start of the cmkr macro + set_source_files_properties(${CMKR_TEMP_FILE} PROPERTIES + CMKR_CURRENT_LIST_FILE ${CMAKE_CURRENT_LIST_FILE} + ) + + # 'Execute' the newly-generated CMakeLists.txt + include(${CMKR_TEMP_FILE}) + + # Delete the generated file + file(REMOVE ${CMKR_TEMP_FILE}) + + # Do not execute the rest of the original CMakeLists.txt + return() + endif() +endmacro() \ No newline at end of file diff --git a/cmake/src/example.cpp b/cmake/src/example.cpp new file mode 100644 index 0000000..50a67c2 --- /dev/null +++ b/cmake/src/example.cpp @@ -0,0 +1,6 @@ +#include + +int main() +{ + puts("Hello from cmkr!"); +} \ No newline at end of file diff --git a/src/gen.cpp b/src/gen.cpp index ff406a3..065e4b3 100644 --- a/src/gen.cpp +++ b/src/gen.cpp @@ -109,7 +109,15 @@ int generate_cmake(const char *path) { if (fs::exists(fs::path(path) / "cmake.toml")) { cmake::CMake cmake(path, false); std::stringstream ss; - ss << "# This file was generated automatically by cmkr.\n\n"; + ss << "# This file was generated automatically by cmkr.\n"; + ss << "\n"; + + ss << "# Regenerate CMakeLists.txt file when necessary\n"; + ss << "include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT)\n"; + ss << "if(CMKR_INCLUDE_RESULT)\n"; + ss << "\tcmkr()\n"; + ss << "endif()\n"; + ss << "\n"; if (!cmake.cmake_version.empty()) { ss << "cmake_minimum_required(VERSION " << cmake.cmake_version << ")\n\n";