include_guard() # Change these defaults to point to your infrastructure if desired set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE) set(CMKR_TAG "v0.2.15" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE) # To bootstrap/generate a cmkr project: cmake -P cmkr.cmake if(CMAKE_SCRIPT_MODE_FILE) set(CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}/build") set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}") file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}") endif() # Set these from the command line to customize for development/debugging purposes set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable") set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation") set(CMKR_BUILD_TYPE "Debug" CACHE STRING "cmkr build configuration") mark_as_advanced(CMKR_REPO CMKR_TAG CMKR_COMMIT_HASH CMKR_EXECUTABLE CMKR_SKIP_GENERATION CMKR_BUILD_TYPE) # Disable cmkr if generation is disabled if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION) message(STATUS "[cmkr] Skipping automatic cmkr generation") unset(CMKR_BUILD_SKIP_GENERATION CACHE) macro(cmkr) endmacro() return() endif() # Disable cmkr if no cmake.toml file is found if(NOT CMAKE_SCRIPT_MODE_FILE AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") macro(cmkr) endmacro() return() endif() # Convert a Windows native path to CMake path if(CMKR_EXECUTABLE MATCHES "\\\\") string(REPLACE "\\" "/" CMKR_EXECUTABLE_CMAKE "${CMKR_EXECUTABLE}") set(CMKR_EXECUTABLE "${CMKR_EXECUTABLE_CMAKE}" CACHE FILEPATH "" FORCE) unset(CMKR_EXECUTABLE_CMAKE) endif() # Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher) function(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() endfunction() # 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 ENV{CMKR_CACHE}) set(CMKR_DIRECTORY_PREFIX "$ENV{CMKR_CACHE}") string(REPLACE "\\" "/" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}") if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$") set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/") endif() # Build in release mode for the cache set(CMKR_BUILD_TYPE "Release") else() set(CMKR_DIRECTORY_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_") endif() set(CMKR_DIRECTORY "${CMKR_DIRECTORY_PREFIX}${CMKR_TAG}") set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}") # Helper function to check if a string starts with a prefix # Cannot use MATCHES, see: https://github.com/build-cpp/cmkr/issues/61 function(cmkr_startswith str prefix result) string(LENGTH "${prefix}" prefix_length) string(LENGTH "${str}" str_length) if(prefix_length LESS_EQUAL str_length) string(SUBSTRING "${str}" 0 ${prefix_length} str_prefix) if(prefix STREQUAL str_prefix) set("${result}" ON PARENT_SCOPE) return() endif() endif() set("${result}" OFF PARENT_SCOPE) endfunction() # Handle upgrading logic if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE) cmkr_startswith("${CMKR_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/_cmkr" CMKR_STARTSWITH_BUILD) cmkr_startswith("${CMKR_EXECUTABLE}" "${CMKR_DIRECTORY_PREFIX}" CMKR_STARTSWITH_CACHE) if(CMKR_STARTSWITH_BUILD) if(DEFINED ENV{CMKR_CACHE}) message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'") if(EXISTS "${CMKR_CACHED_EXECUTABLE}") set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) else() unset(CMKR_EXECUTABLE CACHE) endif() else() message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") unset(CMKR_EXECUTABLE CACHE) endif() elseif(DEFINED ENV{CMKR_CACHE} AND CMKR_STARTSWITH_CACHE) message(AUTHOR_WARNING "[cmkr] Upgrading cached '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") unset(CMKR_EXECUTABLE CACHE) endif() endif() if(CMKR_EXECUTABLE AND EXISTS "${CMKR_EXECUTABLE}") message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'") elseif(CMKR_EXECUTABLE AND NOT CMKR_EXECUTABLE STREQUAL CMKR_CACHED_EXECUTABLE) message(FATAL_ERROR "[cmkr] '${CMKR_EXECUTABLE}' not found") elseif(NOT CMKR_EXECUTABLE AND EXISTS "${CMKR_CACHED_EXECUTABLE}") set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) message(STATUS "[cmkr] Found cached cmkr: '${CMKR_EXECUTABLE}'") else() set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 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) cmkr_exec("${GIT_EXECUTABLE}" clone --config advice.detachedHead=false --branch ${CMKR_TAG} --depth 1 ${CMKR_REPO} "${CMKR_DIRECTORY}" ) if(CMKR_COMMIT_HASH) execute_process( COMMAND "${GIT_EXECUTABLE}" checkout -q "${CMKR_COMMIT_HASH}" RESULT_VARIABLE CMKR_EXEC_RESULT WORKING_DIRECTORY "${CMKR_DIRECTORY}" ) if(NOT CMKR_EXEC_RESULT EQUAL 0) message(FATAL_ERROR "Tag '${CMKR_TAG}' hash is not '${CMKR_COMMIT_HASH}'") endif() endif() message(STATUS "[cmkr] Building cmkr (using system compiler)...") cmkr_exec("${CMAKE_COMMAND}" --no-warn-unused-cli "${CMKR_DIRECTORY}" "-B${CMKR_DIRECTORY}/build" "-DCMAKE_BUILD_TYPE=${CMKR_BUILD_TYPE}" "-DCMAKE_UNITY_BUILD=ON" "-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}" "-DCMKR_GENERATE_DOCUMENTATION=OFF" ) cmkr_exec("${CMAKE_COMMAND}" --build "${CMKR_DIRECTORY}/build" --config "${CMKR_BUILD_TYPE}" --parallel ) cmkr_exec("${CMAKE_COMMAND}" --install "${CMKR_DIRECTORY}/build" --config "${CMKR_BUILD_TYPE}" --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) message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}") endif() execute_process(COMMAND "${CMKR_EXECUTABLE}" version RESULT_VARIABLE CMKR_EXEC_RESULT ) if(NOT CMKR_EXEC_RESULT EQUAL 0) message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding") endif() # Use cmkr.cmake as a script if(CMAKE_SCRIPT_MODE_FILE) if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml") execute_process(COMMAND "${CMKR_EXECUTABLE}" init RESULT_VARIABLE CMKR_EXEC_RESULT ) if(NOT CMKR_EXEC_RESULT EQUAL 0) message(FATAL_ERROR "[cmkr] Failed to bootstrap cmkr project. Please report an issue: https://github.com/build-cpp/cmkr/issues/new") else() message(STATUS "[cmkr] Modify cmake.toml and then configure using: cmake -B build") endif() else() execute_process(COMMAND "${CMKR_EXECUTABLE}" gen RESULT_VARIABLE CMKR_EXEC_RESULT ) if(NOT CMKR_EXEC_RESULT EQUAL 0) message(FATAL_ERROR "[cmkr] Failed to generate project.") else() message(STATUS "[cmkr] Configure using: cmake -B build") endif() endif() endif() # 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) file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_PRE) # Generate CMakeLists.txt cmkr_exec("${CMKR_EXECUTABLE}" gen WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" ) file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST) # Delete the temporary file if it was left for some reason set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt") if(EXISTS "${CMKR_TEMP_FILE}") file(REMOVE "${CMKR_TEMP_FILE}") endif() if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST) # Copy the now-generated CMakeLists.txt to CMakerLists.txt # This is done because you cannot include() a file you are currently in 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() # Resume executing the unmodified CMakeLists.txt endif() endmacro()