Automatically generate cmkr.cmake when missing

toml-checker archive_285614e4
Duncan Ogilvie 3 years ago
parent e725b10a9f
commit 285614e4c2

@ -17,6 +17,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Tag cmkr.cmake
if: ${{ startsWith(github.ref, 'refs/tags/') }}
run: cmake -P "cmake/replace_tag.cmake"
- name: Build
run: |
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }}
@ -26,7 +30,7 @@ jobs:
- name: Check if cmkr was run
run: |
./install/bin/cmkr gen
git diff --exit-code
git diff --exit-code -- . ":(exclude)cmake/cmkr.cmake"
- name: Test
run: |
@ -53,7 +57,7 @@ jobs:
- name: Release
uses: softprops/action-gh-release@v1
if: ${{ startsWith(github.ref, 'refs/tags/') && (matrix.os == 'windows-2019' || matrix.os == 'macos-10.15' || matrix.os == 'ubuntu-16.04') }}
if: ${{ startsWith(github.ref, 'refs/tags/') }}
with:
prerelease: ${{ !startsWith(github.ref, 'refs/tags/v') || contains(github.ref, '-pre') }}
files: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip

10
CMakeLists.txt generated

@ -12,12 +12,6 @@ set(CMKR_ROOT_PROJECT OFF)
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(CMKR_ROOT_PROJECT ON)
# Bootstrap cmkr
include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT)
if(CMKR_INCLUDE_RESULT)
cmkr()
endif()
# Enable folder support
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
endif()
@ -37,6 +31,7 @@ project(cmkr
)
include("cmake/generate_documentation.cmake")
include("cmake/generate_resources.cmake")
# third_party
set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER})
@ -97,6 +92,7 @@ list(APPEND cmkr_SOURCES
"include/help.hpp"
"include/literals.hpp"
"include/project_parser.hpp"
"cmake/cmkr.cmake"
)
list(APPEND cmkr_SOURCES
@ -133,6 +129,8 @@ target_link_libraries(cmkr PRIVATE
nlohmann_json
)
generate_resources(${CMKR_TARGET})
unset(CMKR_TARGET)
unset(CMKR_SOURCES)

@ -1,5 +1,6 @@
[cmake]
version = "2.8...3.8"
cmkr-include = false
[project]
name = "cmkr"
@ -7,7 +8,10 @@ version = "0.1.4"
description = "CMakeLists generator from TOML"
languages = ["CXX"]
subdirs = ["third_party", "tests"]
include-after = ["cmake/generate_documentation.cmake"]
include-after = [
"cmake/generate_documentation.cmake",
"cmake/generate_resources.cmake"
]
[target.cmkr_generate_documentation]
type = "interface"
@ -17,10 +21,25 @@ generate_documentation()
[target.cmkr]
type = "executable"
sources = ["src/*.cpp", "include/*.hpp"]
include-directories = ["include"]
sources = [
"src/*.cpp",
"include/*.hpp",
"cmake/cmkr.cmake",
]
include-directories = [
"include",
]
compile-features = ["cxx_std_11"]
link-libraries = ["toml11", "ghc_filesystem", "mpark_variant", "ordered_map", "nlohmann_json"]
link-libraries = [
"toml11",
"ghc_filesystem",
"mpark_variant",
"ordered_map",
"nlohmann_json"
]
cmake-after = """
generate_resources(${CMKR_TARGET})
"""
[[install]]
targets = ["cmkr"]

44
cmake/CMakeLists.txt generated

@ -1,44 +0,0 @@
# This file was generated automatically by cmkr.
cmake_minimum_required(VERSION 3.15)
# Regenerate CMakeLists.txt automatically in the root project
set(CMKR_ROOT_PROJECT OFF)
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(CMKR_ROOT_PROJECT ON)
# Bootstrap cmkr
include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT)
if(CMKR_INCLUDE_RESULT)
cmkr()
endif()
# Enable folder support
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
endif()
# Create a configure-time dependency on cmake.toml to improve IDE support
if(CMKR_ROOT_PROJECT)
configure_file(cmake.toml cmake.toml COPYONLY)
endif()
project(example
VERSION
0.1.0
)
# Target example
set(example_SOURCES
"src/example.cpp"
cmake.toml
)
add_executable(example ${example_SOURCES})
get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT)
if(NOT CMKR_VS_STARTUP_PROJECT)
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT example)
endif()
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${example_SOURCES})

@ -1,12 +0,0 @@
# This is a minimal example project used for testing the cmkr bootstrapping process
[cmake]
version = "3.15"
[project]
name = "example"
version = "0.1.0"
[target.example]
type = "executable"
sources = ["src/example.cpp"]

@ -1,162 +1,162 @@
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 "archive_a718dfd6" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
# 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")
# Disable cmkr if generation is disabled
if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION)
message(STATUS "[cmkr] Skipping automatic cmkr generation")
macro(cmkr)
endmacro()
return()
endif()
# Disable cmkr if no cmake.toml file is found
if(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
set(CMKR_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_${CMKR_TAG}")
set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}")
if(NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE AND CMKR_EXECUTABLE MATCHES "^${CMAKE_CURRENT_BINARY_DIR}/_cmkr")
message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'")
unset(CMKR_EXECUTABLE CACHE)
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")
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}"
)
message(STATUS "[cmkr] Building cmkr...")
cmkr_exec("${CMAKE_COMMAND}"
--no-warn-unused-cli
"${CMKR_DIRECTORY}"
"-B${CMKR_DIRECTORY}/build"
"-DCMAKE_BUILD_TYPE=Release"
"-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}"
"-DCMKR_GENERATE_DOCUMENTATION=OFF"
)
cmkr_exec("${CMAKE_COMMAND}"
--build "${CMKR_DIRECTORY}/build"
--config Release
--parallel
)
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)
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()
# 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()
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 "archive_9e1fa5dc" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
# 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")
# Disable cmkr if generation is disabled
if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION)
message(STATUS "[cmkr] Skipping automatic cmkr generation")
macro(cmkr)
endmacro()
return()
endif()
# Disable cmkr if no cmake.toml file is found
if(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
set(CMKR_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_${CMKR_TAG}")
set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}")
if(NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE AND CMKR_EXECUTABLE MATCHES "^${CMAKE_CURRENT_BINARY_DIR}/_cmkr")
message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'")
unset(CMKR_EXECUTABLE CACHE)
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")
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}"
)
message(STATUS "[cmkr] Building cmkr...")
cmkr_exec("${CMAKE_COMMAND}"
--no-warn-unused-cli
"${CMKR_DIRECTORY}"
"-B${CMKR_DIRECTORY}/build"
"-DCMAKE_BUILD_TYPE=Release"
"-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}"
"-DCMKR_GENERATE_DOCUMENTATION=OFF"
)
cmkr_exec("${CMAKE_COMMAND}"
--build "${CMKR_DIRECTORY}/build"
--config Release
--parallel
)
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)
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()
# 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()

@ -1,76 +1,76 @@
option(CMKR_GENERATE_DOCUMENTATION "Generate cmkr documentation" ${CMKR_ROOT_PROJECT})
set(CMKR_TESTS "" CACHE INTERNAL "List of test directories in the order declared in tests/cmake.toml")
if(CMKR_GENERATE_DOCUMENTATION)
# Hook the add_test function to capture the tests in the order declared in tests/cmake.toml
function(add_test)
cmake_parse_arguments(TEST "" "WORKING_DIRECTORY" "" ${ARGN})
get_filename_component(TEST_WORKING_DIRECTORY "${TEST_WORKING_DIRECTORY}" NAME)
list(APPEND CMKR_TESTS "${TEST_WORKING_DIRECTORY}")
set(CMKR_TESTS "${CMKR_TESTS}" CACHE INTERNAL "")
_add_test(${test} ${ARGN})
endfunction()
endif()
function(generate_documentation)
if(CMKR_GENERATE_DOCUMENTATION)
message(STATUS "[cmkr] Generating documentation...")
# Delete previously generated examples
set(example_folder "${PROJECT_SOURCE_DIR}/docs/examples")
file(GLOB example_files "${example_folder}/*.md")
list(REMOVE_ITEM example_files "${example_folder}/index.md")
if(example_files)
file(REMOVE ${example_files})
endif()
message(DEBUG "[cmkr] Test directories: ${CMKR_TESTS}")
set(test_index 0)
foreach(test_dir ${CMKR_TESTS})
set(test_name "${test_dir}")
set(test_dir "${PROJECT_SOURCE_DIR}/tests/${test_dir}")
set(test_toml "${test_dir}/cmake.toml")
if(IS_DIRECTORY "${test_dir}" AND EXISTS "${test_toml}")
message(DEBUG "[cmkr] Generating documentation for: ${test_toml} (index: ${test_index})")
# Set template variables
set(EXAMPLE_PERMALINK "${test_name}")
set(EXAMPLE_INDEX ${test_index})
math(EXPR test_index "${test_index}+1")
# Read cmake.toml file
file(READ "${test_toml}" test_contents NO_HEX_CONVERSION)
string(LENGTH "${test_contents}" toml_length)
# Extract header text
string(REGEX MATCH "^(\n*(#[^\n]+\n)+\n*)" EXAMPLE_HEADER "${test_contents}")
string(LENGTH "${EXAMPLE_HEADER}" header_length)
string(STRIP "${EXAMPLE_HEADER}" EXAMPLE_HEADER)
string(REGEX REPLACE "\n# ?" "\n" EXAMPLE_HEADER "\n${EXAMPLE_HEADER}")
string(STRIP "${EXAMPLE_HEADER}" EXAMPLE_HEADER)
# Extract footer text
string(REGEX MATCH "(((#[^\n]+)(\n+|$))+)$" EXAMPLE_FOOTER "${test_contents}")
string(LENGTH "${EXAMPLE_FOOTER}" footer_length)
string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER)
string(REGEX REPLACE "\n# ?" "\n" EXAMPLE_FOOTER "\n${EXAMPLE_FOOTER}")
string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER)
# Extract toml body
math(EXPR toml_length "${toml_length}-${header_length}-${footer_length}")
string(SUBSTRING "${test_contents}" ${header_length} ${toml_length} EXAMPLE_TOML)
string(STRIP "${EXAMPLE_TOML}" EXAMPLE_TOML)
# Extract title from description
if("${EXAMPLE_TOML}" MATCHES "description *= *\"([^\"]+)\"")
set(EXAMPLE_TITLE "${CMAKE_MATCH_1}")
# Generate documentation markdown page
configure_file("${PROJECT_SOURCE_DIR}/cmake/example.md.in" "${example_folder}/${EXAMPLE_PERMALINK}.md" @ONLY NEWLINE_STYLE LF)
else()
message(DEBUG "[cmkr] Skipping documentation generation for ${test_name} because description is missing")
endif()
endif()
endforeach()
endif()
endfunction()
option(CMKR_GENERATE_DOCUMENTATION "Generate cmkr documentation" ${CMKR_ROOT_PROJECT})
set(CMKR_TESTS "" CACHE INTERNAL "List of test directories in the order declared in tests/cmake.toml")
if(CMKR_GENERATE_DOCUMENTATION)
# Hook the add_test function to capture the tests in the order declared in tests/cmake.toml
function(add_test)
cmake_parse_arguments(TEST "" "WORKING_DIRECTORY" "" ${ARGN})
get_filename_component(TEST_WORKING_DIRECTORY "${TEST_WORKING_DIRECTORY}" NAME)
list(APPEND CMKR_TESTS "${TEST_WORKING_DIRECTORY}")
set(CMKR_TESTS "${CMKR_TESTS}" CACHE INTERNAL "")
_add_test(${test} ${ARGN})
endfunction()
endif()
function(generate_documentation)
if(CMKR_GENERATE_DOCUMENTATION)
message(STATUS "[cmkr] Generating documentation...")
# Delete previously generated examples
set(example_folder "${PROJECT_SOURCE_DIR}/docs/examples")
file(GLOB example_files "${example_folder}/*.md")
list(REMOVE_ITEM example_files "${example_folder}/index.md")
if(example_files)
file(REMOVE ${example_files})
endif()
message(DEBUG "[cmkr] Test directories: ${CMKR_TESTS}")
set(test_index 0)
foreach(test_dir ${CMKR_TESTS})
set(test_name "${test_dir}")
set(test_dir "${PROJECT_SOURCE_DIR}/tests/${test_dir}")
set(test_toml "${test_dir}/cmake.toml")
if(IS_DIRECTORY "${test_dir}" AND EXISTS "${test_toml}")
message(DEBUG "[cmkr] Generating documentation for: ${test_toml} (index: ${test_index})")
# Set template variables
set(EXAMPLE_PERMALINK "${test_name}")
set(EXAMPLE_INDEX ${test_index})
math(EXPR test_index "${test_index}+1")
# Read cmake.toml file
file(READ "${test_toml}" test_contents NO_HEX_CONVERSION)
string(LENGTH "${test_contents}" toml_length)
# Extract header text
string(REGEX MATCH "^(\n*(#[^\n]+\n)+\n*)" EXAMPLE_HEADER "${test_contents}")
string(LENGTH "${EXAMPLE_HEADER}" header_length)
string(STRIP "${EXAMPLE_HEADER}" EXAMPLE_HEADER)
string(REGEX REPLACE "\n# ?" "\n" EXAMPLE_HEADER "\n${EXAMPLE_HEADER}")
string(STRIP "${EXAMPLE_HEADER}" EXAMPLE_HEADER)
# Extract footer text
string(REGEX MATCH "(((#[^\n]+)(\n+|$))+)$" EXAMPLE_FOOTER "${test_contents}")
string(LENGTH "${EXAMPLE_FOOTER}" footer_length)
string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER)
string(REGEX REPLACE "\n# ?" "\n" EXAMPLE_FOOTER "\n${EXAMPLE_FOOTER}")
string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER)
# Extract toml body
math(EXPR toml_length "${toml_length}-${header_length}-${footer_length}")
string(SUBSTRING "${test_contents}" ${header_length} ${toml_length} EXAMPLE_TOML)
string(STRIP "${EXAMPLE_TOML}" EXAMPLE_TOML)
# Extract title from description
if("${EXAMPLE_TOML}" MATCHES "description *= *\"([^\"]+)\"")
set(EXAMPLE_TITLE "${CMAKE_MATCH_1}")
# Generate documentation markdown page
configure_file("${PROJECT_SOURCE_DIR}/cmake/example.md.in" "${example_folder}/${EXAMPLE_PERMALINK}.md" @ONLY NEWLINE_STYLE LF)
else()
message(DEBUG "[cmkr] Skipping documentation generation for ${test_name} because description is missing")
endif()
endif()
endforeach()
endif()
endfunction()

@ -0,0 +1,21 @@
# Enumerates the target sources and automatically generates include/resources/${RESOURCE_NAME}.h from all .cmake files
function(generate_resources target)
get_property(TARGET_SOURCES
TARGET ${target}
PROPERTY SOURCES
)
foreach(SOURCE ${TARGET_SOURCES})
if(SOURCE MATCHES ".cmake$")
get_filename_component(RESOURCE_NAME "${SOURCE}" NAME_WE)
set(RESOURCE_HEADER "include/resources/${RESOURCE_NAME}.h")
configure_file("${SOURCE}" "${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_HEADER}")
file(READ "${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_HEADER}" RESOURCE_CONTENTS)
file(GENERATE
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_HEADER}"
CONTENT "namespace cmkr {\nnamespace resources {\nstatic const char* ${RESOURCE_NAME} = R\"RESOURCE(${RESOURCE_CONTENTS})RESOURCE\";\n}\n}"
)
message(STATUS "[cmkr] Generated ${RESOURCE_HEADER}")
endif()
endforeach()
target_include_directories(${target} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/include")
endfunction()

@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.20)
find_package(Git REQUIRED)
execute_process(COMMAND
"${GIT_EXECUTABLE}" name-rev --tags --name-only HEAD
OUTPUT_VARIABLE GIT_TAG
)
string(FIND "${GIT_TAG}" "\n" NEWLINE_POS)
string(SUBSTRING "${GIT_TAG}" 0 ${NEWLINE_POS} GIT_TAG)
string(STRIP "${GIT_TAG}" GIT_TAG)
if("${GIT_TAG}" STREQUAL "")
message(FATAL_ERROR "Failed to retrieve git tag!")
endif()
message(STATUS "CMKR_TAG: '${GIT_TAG}'")
file(READ "cmake/cmkr.cmake" CMKR_CMAKE)
string(REGEX REPLACE "CMKR_TAG \"[^\"]+\"" "CMKR_TAG \"${GIT_TAG}\"" CMKR_CMAKE "${CMKR_CMAKE}")
file(CONFIGURE
OUTPUT "cmake/cmkr.cmake"
CONTENT "${CMKR_CMAKE}"
@ONLY
NEWLINE_STYLE LF
)

@ -1,6 +0,0 @@
#include <cstdio>
int main()
{
puts("Hello from cmkr!");
}

@ -25,7 +25,7 @@ int run(int argc, char **argv) {
if (gen::generate_cmake(fs::current_path().string().c_str()))
throw std::runtime_error("CMake generation failure!");
ss << "cmake -S. -B" << project.build_dir << " ";
ss << "cmake -S. -DCMKR_SKIP_GENERATION=ON -B" << project.build_dir << " ";
if (!project.generator.empty()) {
ss << "-G \"" << project.generator << "\" ";

@ -1,6 +1,7 @@
#include "cmake_generator.hpp"
#include "error.hpp"
#include "literals.hpp"
#include <resources/cmkr.h>
#include "fs.hpp"
#include <cassert>
@ -478,16 +479,28 @@ int generate_cmake(const char *path, const parser::Project *parent_project) {
cmd("if")("CMAKE_CURRENT_SOURCE_DIR", "STREQUAL", "CMAKE_SOURCE_DIR");
cmd("set")("CMKR_ROOT_PROJECT", "ON").endl();
comment("Bootstrap cmkr");
cmd("include")(project.cmkr_include, "OPTIONAL", "RESULT_VARIABLE", "CMKR_INCLUDE_RESULT");
cmd("if")("CMKR_INCLUDE_RESULT");
cmd("cmkr")();
cmd("endif")().endl();
if (!project.cmkr_include.empty()) {
comment("Bootstrap cmkr");
cmd("include")(project.cmkr_include, "OPTIONAL", "RESULT_VARIABLE", "CMKR_INCLUDE_RESULT");
cmd("if")("CMKR_INCLUDE_RESULT");
cmd("cmkr")();
cmd("endif")().endl();
}
comment("Enable folder support");
cmd("set_property")("GLOBAL", "PROPERTY", "USE_FOLDERS", "ON");
cmd("endif")().endl();
// clang-format on
fs::path cmkr_include(project.cmkr_include);
if (!project.cmkr_include.empty() && !fs::exists(cmkr_include) && cmkr_include.is_relative()) {
fs::create_directories(cmkr_include.parent_path());
std::ofstream ofs(cmkr_include.string(), std::ios::binary);
if (!ofs) {
throw std::runtime_error("Failed to create " + project.cmkr_include);
}
ofs.write(resources::cmkr, strlen(resources::cmkr));
}
}
// clang-format off

@ -85,7 +85,15 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
if (toml.contains("cmake")) {
const auto &cmake = toml::find(toml, "cmake");
cmake_version = toml::find(cmake, "version").as_string();
get_optional(cmake, "cmkr-include", cmkr_include);
if (cmake.contains("cmkr-include")) {
const auto &cmkr_include_kv = toml::find(cmake, "cmkr-include");
if (cmkr_include_kv.is_string()) {
cmkr_include = cmkr_include_kv.as_string();
} else {
// Allow disabling this feature with cmkr-include = false
cmkr_include = "";
}
}
get_optional(cmake, "cpp-flags", cppflags);
get_optional(cmake, "c-flags", cflags);
get_optional(cmake, "link-flags", linkflags);

5
tests/.gitignore vendored

@ -1,2 +1,3 @@
# These will be generated by cmkr, so no point in tracking them
**/CMakeLists.txt
# These will be generated by cmkr, so no point in tracking them
**/CMakeLists.txt
**/cmkr.cmake
Loading…
Cancel
Save