From 285614e4c2dcaee85a159ed727cc75af254200a0 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Mon, 13 Sep 2021 14:57:26 +0200 Subject: [PATCH] Automatically generate cmkr.cmake when missing --- .github/workflows/build.yml | 8 +- CMakeLists.txt | 10 +- cmake.toml | 27 ++- cmake/CMakeLists.txt | 44 ---- cmake/cmake.toml | 12 -- cmake/cmkr.cmake | 324 ++++++++++++++--------------- cmake/generate_documentation.cmake | 152 +++++++------- cmake/generate_resources.cmake | 21 ++ cmake/replace_tag.cmake | 27 +++ cmake/src/example.cpp | 6 - src/build.cpp | 2 +- src/cmake_generator.cpp | 23 +- src/project_parser.cpp | 10 +- tests/.gitignore | 5 +- 14 files changed, 350 insertions(+), 321 deletions(-) delete mode 100644 cmake/CMakeLists.txt delete mode 100644 cmake/cmake.toml create mode 100644 cmake/generate_resources.cmake create mode 100644 cmake/replace_tag.cmake delete mode 100644 cmake/src/example.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1bb6e3a..3a1e0ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 518387c..814201d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/cmake.toml b/cmake.toml index 8ecdc1d..be3e694 100644 --- a/cmake.toml +++ b/cmake.toml @@ -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"] diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt deleted file mode 100644 index 90e6a84..0000000 --- a/cmake/CMakeLists.txt +++ /dev/null @@ -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}) - diff --git a/cmake/cmake.toml b/cmake/cmake.toml deleted file mode 100644 index c55961b..0000000 --- a/cmake/cmake.toml +++ /dev/null @@ -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"] \ No newline at end of file diff --git a/cmake/cmkr.cmake b/cmake/cmkr.cmake index 65b4a16..4fdc1ca 100644 --- a/cmake/cmkr.cmake +++ b/cmake/cmkr.cmake @@ -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() diff --git a/cmake/generate_documentation.cmake b/cmake/generate_documentation.cmake index 380e543..d4fcb68 100644 --- a/cmake/generate_documentation.cmake +++ b/cmake/generate_documentation.cmake @@ -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() diff --git a/cmake/generate_resources.cmake b/cmake/generate_resources.cmake new file mode 100644 index 0000000..d2ada59 --- /dev/null +++ b/cmake/generate_resources.cmake @@ -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() \ No newline at end of file diff --git a/cmake/replace_tag.cmake b/cmake/replace_tag.cmake new file mode 100644 index 0000000..ab8d175 --- /dev/null +++ b/cmake/replace_tag.cmake @@ -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 +) \ No newline at end of file diff --git a/cmake/src/example.cpp b/cmake/src/example.cpp deleted file mode 100644 index 50a67c2..0000000 --- a/cmake/src/example.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main() -{ - puts("Hello from cmkr!"); -} \ No newline at end of file diff --git a/src/build.cpp b/src/build.cpp index f647746..96248fd 100644 --- a/src/build.cpp +++ b/src/build.cpp @@ -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 << "\" "; diff --git a/src/cmake_generator.cpp b/src/cmake_generator.cpp index 470c3c9..e714a19 100644 --- a/src/cmake_generator.cpp +++ b/src/cmake_generator.cpp @@ -1,6 +1,7 @@ #include "cmake_generator.hpp" #include "error.hpp" #include "literals.hpp" +#include #include "fs.hpp" #include @@ -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 diff --git a/src/project_parser.cpp b/src/project_parser.cpp index bc8d9b6..b347122 100644 --- a/src/project_parser.cpp +++ b/src/project_parser.cpp @@ -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); diff --git a/tests/.gitignore b/tests/.gitignore index d3012f8..21f295a 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,2 +1,3 @@ -# These will be generated by cmkr, so no point in tracking them -**/CMakeLists.txt \ No newline at end of file +# These will be generated by cmkr, so no point in tracking them +**/CMakeLists.txt +**/cmkr.cmake \ No newline at end of file