From 306bb3d4fc76356166b7ec79f1fac4755555b0ab Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Sun, 16 May 2021 22:55:55 +0200 Subject: [PATCH] Automatically generate documentation from tests --- CMakeLists.txt | 17 ++++++ cmake.toml | 7 +++ cmake/cmkr.cmake | 1 + cmake/example.md.in | 20 +++++++ cmake/generate_documentation.cmake | 73 +++++++++++++++++++++++ docs/.gitignore | 3 +- docs/examples/basic.md | 26 ++++++++ docs/examples/cpp-version-change.markdown | 17 ------ docs/examples/fetch-content.md | 30 ++++++++++ docs/examples/import-from-git.markdown | 22 ------- docs/examples/index.markdown | 10 ---- docs/examples/index.md | 11 ++++ docs/examples/interface.md | 31 ++++++++++ docs/examples/quickstart.markdown | 23 ------- docs/index.md | 2 +- docs/run-wsl.sh | 2 + tests/CMakeLists.txt | 20 +++++-- tests/basic/cmake.toml | 6 +- tests/cmake.toml | 16 +++-- tests/fetch-content/cmake.toml | 15 +++++ tests/fetch-content/src/main.cpp | 6 ++ tests/interface/cmake.toml | 1 + 22 files changed, 273 insertions(+), 86 deletions(-) create mode 100644 cmake/example.md.in create mode 100644 cmake/generate_documentation.cmake create mode 100644 docs/examples/basic.md delete mode 100644 docs/examples/cpp-version-change.markdown create mode 100644 docs/examples/fetch-content.md delete mode 100644 docs/examples/import-from-git.markdown delete mode 100644 docs/examples/index.markdown create mode 100644 docs/examples/index.md create mode 100644 docs/examples/interface.md delete mode 100644 docs/examples/quickstart.markdown create mode 100644 docs/run-wsl.sh create mode 100644 tests/fetch-content/cmake.toml create mode 100644 tests/fetch-content/src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a9e02c3..89f9816 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,8 @@ project(cmkr "CMakeLists generator from TOML" ) +include("cmake/generate_documentation.cmake") + # third_party set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER}) if(CMAKE_FOLDER) @@ -54,6 +56,21 @@ set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER}) +# Target cmkr_generate_documentation +set(CMKR_TARGET cmkr_generate_documentation) +set(cmkr_generate_documentation_SOURCES "") + +set(CMKR_SOURCES ${cmkr_generate_documentation_SOURCES}) +add_library(cmkr_generate_documentation INTERFACE) + +if(cmkr_generate_documentation_SOURCES) + target_sources(cmkr_generate_documentation INTERFACE ${cmkr_generate_documentation_SOURCES}) +endif() + +generate_documentation() + +unset(CMKR_TARGET) +unset(CMKR_SOURCES) # Target cmkr set(CMKR_TARGET cmkr) set(cmkr_SOURCES "") diff --git a/cmake.toml b/cmake.toml index 16307d0..1fb86f5 100644 --- a/cmake.toml +++ b/cmake.toml @@ -7,6 +7,13 @@ version = "0.1.4" description = "CMakeLists generator from TOML" languages = ["CXX"] subdirs = ["third_party", "tests"] +include-after = ["cmake/generate_documentation.cmake"] + +[target.cmkr_generate_documentation] +type = "interface" +cmake-after = """ +generate_documentation() +""" [target.cmkr] type = "executable" diff --git a/cmake/cmkr.cmake b/cmake/cmkr.cmake index 61d8bbe..d4cbe7f 100644 --- a/cmake/cmkr.cmake +++ b/cmake/cmkr.cmake @@ -83,6 +83,7 @@ else() "-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" diff --git a/cmake/example.md.in b/cmake/example.md.in new file mode 100644 index 0000000..e2c98e6 --- /dev/null +++ b/cmake/example.md.in @@ -0,0 +1,20 @@ +--- +# Automatically generated from tests/@EXAMPLE_PERMALINK@/cmake.toml - DO NOT EDIT +layout: default +title: @EXAMPLE_TITLE@ +permalink: /examples/@EXAMPLE_PERMALINK@ +parent: Examples +nav_order: @EXAMPLE_INDEX@ +--- + +# @EXAMPLE_TITLE@ + +@EXAMPLE_HEADER@ + +```toml +@EXAMPLE_TOML@ +``` + +@EXAMPLE_FOOTER@ + +This page was automatically generated from [tests/@EXAMPLE_PERMALINK@/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/@EXAMPLE_PERMALINK@/cmake.toml). \ No newline at end of file diff --git a/cmake/generate_documentation.cmake b/cmake/generate_documentation.cmake new file mode 100644 index 0000000..6c3990b --- /dev/null +++ b/cmake/generate_documentation.cmake @@ -0,0 +1,73 @@ +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}) + 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") + file(REMOVE ${example_files}) + + 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/docs/.gitignore b/docs/.gitignore index ca35be0..377c423 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ -_site +_site/ +.jekyll-metadata \ No newline at end of file diff --git a/docs/examples/basic.md b/docs/examples/basic.md new file mode 100644 index 0000000..7f1e416 --- /dev/null +++ b/docs/examples/basic.md @@ -0,0 +1,26 @@ +--- +# Automatically generated from tests/basic/cmake.toml - DO NOT EDIT +layout: default +title: Minimal example +permalink: /examples/basic +parent: Examples +nav_order: 0 +--- + +# Minimal example + +A minimal `cmake.toml` project: + +```toml +[project] +name = "basic" +description = "Minimal example" + +[target.basic] +type = "executable" +sources = ["src/basic.cpp"] +``` + +Declares an executable target called `basic` with `src/basic.cpp` as a source file. Equivalent to CMake's [add_executable](https://cmake.org/cmake/help/latest/command/add_executable.html)`(basic src/basic.cpp)`. + +This page was automatically generated from [tests/basic/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/basic/cmake.toml). diff --git a/docs/examples/cpp-version-change.markdown b/docs/examples/cpp-version-change.markdown deleted file mode 100644 index 0b8bf85..0000000 --- a/docs/examples/cpp-version-change.markdown +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: default -title: Changing C/C++ version -permalink: /examples/cpp-version-change -parent: Examples -nav_order: 3 ---- - -# Changing C/C++ version - -Simple example changing C++ to version 20 and C standard to the version 11 - -```toml -[target.example] -type = "executable" -compile-features = [ "cxx_std_20", "c_std_11" ] -``` diff --git a/docs/examples/fetch-content.md b/docs/examples/fetch-content.md new file mode 100644 index 0000000..c6c4358 --- /dev/null +++ b/docs/examples/fetch-content.md @@ -0,0 +1,30 @@ +--- +# Automatically generated from tests/fetch-content/cmake.toml - DO NOT EDIT +layout: default +title: Fetching from git +permalink: /examples/fetch-content +parent: Examples +nav_order: 2 +--- + +# Fetching from git + +Downloads [fmt v7.1.3](https://fmt.dev/7.1.3/) from [GitHub](https://github.com) and links an `example` target to it: + +```toml +[project] +name = "fetch-content" +description = "Fetching from git" + +[fetch-content] +fmt = { git = "https://github.com/fmtlib/fmt", tag = "7.1.3" } + +[target.example] +type = "executable" +sources = ["src/main.cpp"] +link-libraries = ["fmt::fmt"] +``` + +This is equivalent to calling CMake's [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html). + +This page was automatically generated from [tests/fetch-content/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/fetch-content/cmake.toml). diff --git a/docs/examples/import-from-git.markdown b/docs/examples/import-from-git.markdown deleted file mode 100644 index e92e965..0000000 --- a/docs/examples/import-from-git.markdown +++ /dev/null @@ -1,22 +0,0 @@ ---- -layout: default -title: Import content from Github -permalink: /examples/import-from-git -parent: Examples -nav_order: 2 ---- - -# Import content from Github - -Importing an existing project called Zydis to my project - -tag is optional but you can target any branch with it - -```toml -[fetch-content] -zydis = { git = "https://github.com/zyantific/zydis.git", tag = "v3.1.0" } - -[target.example] -type = "executable" -link-libraries = ["zydis"] -``` diff --git a/docs/examples/index.markdown b/docs/examples/index.markdown deleted file mode 100644 index e25f124..0000000 --- a/docs/examples/index.markdown +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: default -title: Examples -permalink: /examples/ -nav_order: 4 -has_children: true ---- - -# Examples - diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 0000000..9f27ce5 --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,11 @@ +--- +layout: default +title: Examples +permalink: /examples/ +nav_order: 100 +has_children: true +--- + +# Examples + +The examples in this section are automatically generated from the [tests](https://github.com/build-cpp/cmkr/blob/main/tests/cmake.toml). \ No newline at end of file diff --git a/docs/examples/interface.md b/docs/examples/interface.md new file mode 100644 index 0000000..3e27f87 --- /dev/null +++ b/docs/examples/interface.md @@ -0,0 +1,31 @@ +--- +# Automatically generated from tests/interface/cmake.toml - DO NOT EDIT +layout: default +title: Header-only library +permalink: /examples/interface +parent: Examples +nav_order: 1 +--- + +# Header-only library + + + +```toml +[project] +name = "interface" +description = "Header-only library" + +[target.mylib] +type = "interface" +include-directories = ["include"] + +[target.example] +type = "executable" +sources = ["src/main.cpp"] +link-libraries = ["mylib"] +``` + + + +This page was automatically generated from [tests/interface/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/interface/cmake.toml). diff --git a/docs/examples/quickstart.markdown b/docs/examples/quickstart.markdown deleted file mode 100644 index c6aa0a8..0000000 --- a/docs/examples/quickstart.markdown +++ /dev/null @@ -1,23 +0,0 @@ ---- -layout: default -title: Quickstart -permalink: /examples/quickstart -parent: Examples -nav_order: 1 ---- - -# Quickstart - -Smallest possible start point you can have using cmkr - -```toml -[cmake] -version = "3.15" - -[project] -name = "hello-world" - -[target.hello-world] -type = "executable" -sources = [ "src/*.cpp" ] -``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 5e5c957..4ee9fdc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,7 +6,7 @@ nav_order: 0 # Index -[cmkr](https://github.com/build-cpp/cmkr), pronounced "cmaker", is a modern build system based on [CMake](https://cmake.org/) and [TOML](https://toml.io). It was originally created by [Mohammed Alyousef](https://github.com/MoAlyousef). +`cmkr`, pronounced "cmaker", is a modern build system based on [CMake](https://cmake.org/) and [TOML](https://toml.io). It was originally created by [Mohammed Alyousef](https://github.com/MoAlyousef). `cmkr` parses `cmake.toml` files and generates a modern, idiomatic `CMakeLists.txt` for you. A minimal example: diff --git a/docs/run-wsl.sh b/docs/run-wsl.sh new file mode 100644 index 0000000..4cff5b8 --- /dev/null +++ b/docs/run-wsl.sh @@ -0,0 +1,2 @@ +bundle install +bundle exec jekyll serve --force_polling --livereload --incremental diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 07a6b28..1467cd1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,7 +12,7 @@ add_test( NAME basic WORKING_DIRECTORY - "${CMAKE_CURRENT_LIST_DIR}/basic" + basic COMMAND $ build @@ -20,9 +20,9 @@ add_test( add_test( NAME - conditions + interface WORKING_DIRECTORY - "${CMAKE_CURRENT_LIST_DIR}/conditions" + interface COMMAND $ build @@ -30,9 +30,19 @@ add_test( add_test( NAME - interface + fetch-content WORKING_DIRECTORY - "${CMAKE_CURRENT_LIST_DIR}/interface" + fetch-content + COMMAND + $ + build +) + +add_test( + NAME + conditions + WORKING_DIRECTORY + conditions COMMAND $ build diff --git a/tests/basic/cmake.toml b/tests/basic/cmake.toml index 33aab4d..75ae8a1 100644 --- a/tests/basic/cmake.toml +++ b/tests/basic/cmake.toml @@ -1,9 +1,11 @@ -[cmake] -version = "3.5" +# A minimal `cmake.toml` project: [project] name = "basic" +description = "Minimal example" [target.basic] type = "executable" sources = ["src/basic.cpp"] + +# Declares an executable target called `basic` with `src/basic.cpp` as a source file. Equivalent to CMake's [add_executable](https://cmake.org/cmake/help/latest/command/add_executable.html)`(basic src/basic.cpp)`. \ No newline at end of file diff --git a/tests/cmake.toml b/tests/cmake.toml index f5b57a7..b708184 100644 --- a/tests/cmake.toml +++ b/tests/cmake.toml @@ -1,17 +1,23 @@ [[test]] name = "basic" command = "$" -working-directory = "${CMAKE_CURRENT_LIST_DIR}/basic" +working-directory = "basic" arguments = ["build"] [[test]] -name = "conditions" +name = "interface" command = "$" -working-directory = "${CMAKE_CURRENT_LIST_DIR}/conditions" +working-directory = "interface" arguments = ["build"] [[test]] -name = "interface" +name = "fetch-content" +command = "$" +working-directory = "fetch-content" +arguments = ["build"] + +[[test]] +name = "conditions" command = "$" -working-directory = "${CMAKE_CURRENT_LIST_DIR}/interface" +working-directory = "conditions" arguments = ["build"] \ No newline at end of file diff --git a/tests/fetch-content/cmake.toml b/tests/fetch-content/cmake.toml new file mode 100644 index 0000000..ca5d912 --- /dev/null +++ b/tests/fetch-content/cmake.toml @@ -0,0 +1,15 @@ +# Downloads [fmt v7.1.3](https://fmt.dev/7.1.3/) from [GitHub](https://github.com) and links an `example` target to it: + +[project] +name = "fetch-content" +description = "Fetching from git" + +[fetch-content] +fmt = { git = "https://github.com/fmtlib/fmt", tag = "7.1.3" } + +[target.example] +type = "executable" +sources = ["src/main.cpp"] +link-libraries = ["fmt::fmt"] + +# This is equivalent to calling CMake's [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html). \ No newline at end of file diff --git a/tests/fetch-content/src/main.cpp b/tests/fetch-content/src/main.cpp new file mode 100644 index 0000000..4a2fe7e --- /dev/null +++ b/tests/fetch-content/src/main.cpp @@ -0,0 +1,6 @@ +#include + +int main() +{ + fmt::print("Hello, world!\n"); +} \ No newline at end of file diff --git a/tests/interface/cmake.toml b/tests/interface/cmake.toml index 67de6cf..1f1c0c7 100644 --- a/tests/interface/cmake.toml +++ b/tests/interface/cmake.toml @@ -1,5 +1,6 @@ [project] name = "interface" +description = "Header-only library" [target.mylib] type = "interface"