From dc931d10cf074cfe823371c555f851a4f5001834 Mon Sep 17 00:00:00 2001 From: Manuel Ziegler <113091917+krealyt@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:25:21 +0100 Subject: [PATCH 01/75] This adds a documentation skeleton to everest-core in its docs/ directory. (#1380) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is the foundation for the integration of more documentation bits from https://github.com/EVerest/EVerest into this structure. Changes include: * Docs build process and Sphinx configuration * Diataxis skeleton dividing every part of the documentation into: Explaination, How-to-guide, Tutorial, Reference * Added instructions how to build the documentation to everest-core README * Generation of reference documentation Signed-off-by: Krealyt Signed-off-by: Andreas Heinrich Signed-off-by: Piet Gömpel Co-authored-by: Piet Gömpel <37657534+Pietfried@users.noreply.github.com> --- CMakeLists.txt | 4 + THIRD_PARTY.md | 1 + cmake/everest-generate.cmake | 60 ++ cmake/trailbook-ext-everest/README.md | 17 + .../add-module-explanation.cmake | 138 ++++ .../create-snapshot.cmake | 135 ++++ .../generate-rst-from-interface.cmake | 127 ++++ .../generate-rst-from-manifest.cmake | 126 ++++ .../generate-rst-from-types.cmake | 117 +++ .../trailbook-ext-everest/process_template.py | 140 ++++ .../templates/interface.rst.jinja | 19 + .../templates/macros.jinja | 338 +++++++++ .../templates/module.rst.jinja | 30 + .../templates/types.rst.jinja | 10 + ...trailbook-ext-everest-config-version.cmake | 13 + .../trailbook-ext-everest-config.cmake | 12 + cmake/trailbook/EXTENDING.md | 181 +++++ cmake/trailbook/README.md | 137 ++++ cmake/trailbook/add-trailbook.cmake | 705 ++++++++++++++++++ cmake/trailbook/check_path_exists.py | 107 +++ cmake/trailbook/check_requirements_txt.py | 76 ++ cmake/trailbook/create_metadata_yaml.py | 85 +++ cmake/trailbook/filelist_manager.py | 223 ++++++ cmake/trailbook/render_redirect_template.py | 80 ++ cmake/trailbook/setup-trailbook.cmake | 46 ++ cmake/trailbook/target_observer.py | 123 +++ cmake/trailbook/templates/redirect.html.jinja | 11 + .../trailbook/trailbook-config-version.cmake | 13 + cmake/trailbook/trailbook-config.cmake | 5 + dependencies.yaml | 2 +- docs/CMakeLists.txt | 32 + docs/README.md | 85 +++ docs/requirements.txt | 14 + docs/source/404.rst | 13 + docs/source/_ext/staticpages/LICENSE | 21 + docs/source/_ext/staticpages/__init__.py | 1 + docs/source/_ext/staticpages/extension.py | 329 ++++++++ docs/source/_ext/staticpages/utils.py | 79 ++ docs/source/_templates/versions_index.html | 9 + docs/source/conf.py | 93 +++ docs/source/explanation/index.rst | 29 + docs/source/explanation/modules/index.rst | 9 + docs/source/how-to-guides/index.rst | 18 + docs/source/index.rst | 138 ++++ docs/source/qa/index.rst | 5 + docs/source/reference/index.rst | 11 + docs/source/reference/interfaces_index.rst | 9 + docs/source/reference/modules_index.rst | 9 + docs/source/reference/snapshot.rst | 8 + docs/source/reference/types_index.rst | 9 + docs/source/tutorials/index.rst | 14 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 4 +- .../system_API/{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../EV/EvManager/{doc.rst => docs/index.rst} | 2 + modules/EVSE/Auth/docs/index.rst | 2 + .../EvseManager/{doc.rst => docs/index.rst} | 2 + .../EvseSecurity/{doc.rst => docs/index.rst} | 2 + .../EVSE/EvseSlac/{doc.rst => docs/index.rst} | 4 + .../EVSE/EvseV2G/{doc.rst => docs/index.rst} | 2 + .../EVSE/Iso15118InternetVas/docs/index.rst | 2 + modules/EVSE/OCPP/{doc.rst => docs/index.rst} | 2 + .../images}/device_model_class_diagram.png | Bin .../images}/device_model_class_diagram.puml | 0 .../sequence_config_service_and_ocpp.png | Bin .../sequence_config_service_and_ocpp.puml | 0 .../EVSE/OCPP201/{doc.rst => docs/index.rst} | 6 +- .../EnergyManager/docs/index.rst | 2 + .../CppExamples/OCPPExtensionExample/doc.rst | 22 - .../OCPPExtensionExample/docs/index.rst | 2 + .../TerminalCostAndPriceMessage/doc.rst | 22 - .../docs/index.rst | 2 + .../TerminalDisplayMessage/doc.rst | 22 - .../TerminalDisplayMessage/docs/index.rst | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../HardwareDrivers/EV/YetiEvDriver/doc.rst | 22 - .../EV/YetiEvDriver/docs/index.rst | 2 + .../{doc.rst => docs/index.rst} | 2 + .../HardwareDrivers/EVSE/PhyVersoBSP/doc.rst | 22 - .../EVSE/PhyVersoBSP/docs/index.rst | 2 + .../TIDA010939/{doc.rst => docs/index.rst} | 2 + .../YetiDriver/{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../DZG_GSH01/{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../{doc.rst => docs/index.rst} | 2 + .../LemDCBM400600/{doc.rst => docs/index.rst} | 2 + modules/Misc/Linux_Systemd_Rauc/doc.rst | 22 - .../Misc/Linux_Systemd_Rauc/docs/index.rst | 2 + modules/Testing/DummyTokenProvider/doc.rst | 22 - .../Testing/DummyTokenProvider/docs/index.rst | 2 + .../Testing/DummyTokenProviderManual/doc.rst | 22 - .../DummyTokenProviderManual/docs/index.rst | 2 + modules/Testing/DummyTokenValidator/doc.rst | 22 - .../DummyTokenValidator/docs/index.rst | 2 + 114 files changed, 4120 insertions(+), 202 deletions(-) create mode 100644 cmake/trailbook-ext-everest/README.md create mode 100644 cmake/trailbook-ext-everest/add-module-explanation.cmake create mode 100644 cmake/trailbook-ext-everest/create-snapshot.cmake create mode 100644 cmake/trailbook-ext-everest/generate-rst-from-interface.cmake create mode 100644 cmake/trailbook-ext-everest/generate-rst-from-manifest.cmake create mode 100644 cmake/trailbook-ext-everest/generate-rst-from-types.cmake create mode 100755 cmake/trailbook-ext-everest/process_template.py create mode 100644 cmake/trailbook-ext-everest/templates/interface.rst.jinja create mode 100644 cmake/trailbook-ext-everest/templates/macros.jinja create mode 100644 cmake/trailbook-ext-everest/templates/module.rst.jinja create mode 100644 cmake/trailbook-ext-everest/templates/types.rst.jinja create mode 100644 cmake/trailbook-ext-everest/trailbook-ext-everest-config-version.cmake create mode 100644 cmake/trailbook-ext-everest/trailbook-ext-everest-config.cmake create mode 100644 cmake/trailbook/EXTENDING.md create mode 100644 cmake/trailbook/README.md create mode 100644 cmake/trailbook/add-trailbook.cmake create mode 100755 cmake/trailbook/check_path_exists.py create mode 100755 cmake/trailbook/check_requirements_txt.py create mode 100755 cmake/trailbook/create_metadata_yaml.py create mode 100755 cmake/trailbook/filelist_manager.py create mode 100755 cmake/trailbook/render_redirect_template.py create mode 100644 cmake/trailbook/setup-trailbook.cmake create mode 100755 cmake/trailbook/target_observer.py create mode 100644 cmake/trailbook/templates/redirect.html.jinja create mode 100644 cmake/trailbook/trailbook-config-version.cmake create mode 100644 cmake/trailbook/trailbook-config.cmake create mode 100644 docs/CMakeLists.txt create mode 100644 docs/README.md create mode 100644 docs/requirements.txt create mode 100644 docs/source/404.rst create mode 100644 docs/source/_ext/staticpages/LICENSE create mode 100644 docs/source/_ext/staticpages/__init__.py create mode 100644 docs/source/_ext/staticpages/extension.py create mode 100644 docs/source/_ext/staticpages/utils.py create mode 100644 docs/source/_templates/versions_index.html create mode 100644 docs/source/conf.py create mode 100644 docs/source/explanation/index.rst create mode 100644 docs/source/explanation/modules/index.rst create mode 100644 docs/source/how-to-guides/index.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/qa/index.rst create mode 100644 docs/source/reference/index.rst create mode 100644 docs/source/reference/interfaces_index.rst create mode 100644 docs/source/reference/modules_index.rst create mode 100644 docs/source/reference/snapshot.rst create mode 100644 docs/source/reference/types_index.rst create mode 100644 docs/source/tutorials/index.rst rename modules/API/auth_consumer_API/{doc.rst => docs/index.rst} (97%) rename modules/API/auth_token_provider_API/{doc.rst => docs/index.rst} (97%) rename modules/API/auth_token_validator_API/{doc.rst => docs/index.rst} (97%) rename modules/API/dc_external_derate_consumer_API/{doc.rst => docs/index.rst} (97%) rename modules/API/display_message_API/{doc.rst => docs/index.rst} (97%) rename modules/API/error_history_consumer_API/{doc.rst => docs/index.rst} (97%) rename modules/API/evse_board_support_API/{doc.rst => docs/index.rst} (97%) rename modules/API/evse_manager_consumer_API/{doc.rst => docs/index.rst} (98%) rename modules/API/external_energy_limits_consumer_API/{doc.rst => docs/index.rst} (97%) rename modules/API/generic_error_raiser_API/{doc.rst => docs/index.rst} (97%) rename modules/API/isolation_monitor_API/{doc.rst => docs/index.rst} (97%) rename modules/API/ocpp_consumer_API/{doc.rst => docs/index.rst} (97%) rename modules/API/over_voltage_monitor_API/{doc.rst => docs/index.rst} (97%) rename modules/API/power_supply_DC_API/{doc.rst => docs/index.rst} (97%) rename modules/API/powermeter_API/{doc.rst => docs/index.rst} (96%) rename modules/API/session_cost_API/{doc.rst => docs/index.rst} (82%) rename modules/API/system_API/{doc.rst => docs/index.rst} (96%) rename modules/BringUp/BUDCExternalDerate/{doc.rst => docs/index.rst} (98%) rename modules/EV/EvManager/{doc.rst => docs/index.rst} (99%) rename modules/EVSE/EvseManager/{doc.rst => docs/index.rst} (99%) rename modules/EVSE/EvseSecurity/{doc.rst => docs/index.rst} (99%) rename modules/EVSE/EvseSlac/{doc.rst => docs/index.rst} (95%) rename modules/EVSE/EvseV2G/{doc.rst => docs/index.rst} (99%) rename modules/EVSE/OCPP/{doc.rst => docs/index.rst} (99%) rename modules/EVSE/OCPP201/{doc => docs/images}/device_model_class_diagram.png (100%) rename modules/EVSE/OCPP201/{doc => docs/images}/device_model_class_diagram.puml (100%) rename modules/EVSE/OCPP201/{doc => docs/images}/sequence_config_service_and_ocpp.png (100%) rename modules/EVSE/OCPP201/{doc => docs/images}/sequence_config_service_and_ocpp.puml (100%) rename modules/EVSE/OCPP201/{doc.rst => docs/index.rst} (99%) delete mode 100644 modules/Examples/CppExamples/OCPPExtensionExample/doc.rst delete mode 100644 modules/Examples/CppExamples/TerminalCostAndPriceMessage/doc.rst delete mode 100644 modules/Examples/CppExamples/TerminalDisplayMessage/doc.rst rename modules/Examples/error-framework/ExampleErrorGlobalSubscriber/{doc.rst => docs/index.rst} (98%) rename modules/Examples/error-framework/ExampleErrorRaiser/{doc.rst => docs/index.rst} (98%) rename modules/Examples/error-framework/ExampleErrorSubscriber/{doc.rst => docs/index.rst} (98%) delete mode 100644 modules/HardwareDrivers/EV/YetiEvDriver/doc.rst rename modules/HardwareDrivers/EVSE/AdAcEvse22KwzKitBSP/{doc.rst => docs/index.rst} (99%) delete mode 100644 modules/HardwareDrivers/EVSE/PhyVersoBSP/doc.rst rename modules/HardwareDrivers/EVSE/TIDA010939/{doc.rst => docs/index.rst} (99%) rename modules/HardwareDrivers/EVSE/YetiDriver/{doc.rst => docs/index.rst} (99%) rename modules/HardwareDrivers/IsolationMonitors/Bender_isoCHA425HV/{doc.rst => docs/index.rst} (99%) rename modules/HardwareDrivers/NfcReaders/NxpNfcFrontendTokenProvider/{doc.rst => docs/index.rst} (99%) rename modules/HardwareDrivers/NfcReaders/PN7160TokenProvider/{doc.rst => docs/index.rst} (99%) rename modules/HardwareDrivers/PowerMeters/DZG_GSH01/{doc.rst => docs/index.rst} (97%) rename modules/HardwareDrivers/PowerMeters/GenericPowermeter/{doc.rst => docs/index.rst} (99%) rename modules/HardwareDrivers/PowerMeters/IsabellenhuetteIemDcr/{doc.rst => docs/index.rst} (99%) rename modules/HardwareDrivers/PowerMeters/LemDCBM400600/{doc.rst => docs/index.rst} (99%) delete mode 100644 modules/Misc/Linux_Systemd_Rauc/doc.rst delete mode 100644 modules/Testing/DummyTokenProvider/doc.rst delete mode 100644 modules/Testing/DummyTokenProviderManual/doc.rst delete mode 100644 modules/Testing/DummyTokenValidator/doc.rst diff --git a/CMakeLists.txt b/CMakeLists.txt index b39d442c67..b1d8eea479 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,7 @@ option(CMAKE_RUN_CLANG_TIDY "Run clang-tidy" OFF) option(EVEREST_BUILD_API_DOCS "Build EVerest API documentation" OFF) option(ISO15118_2_GENERATE_AND_INSTALL_CERTIFICATES "Automatically generate and install certificates for development purposes" ON) option(EVEREST_ENABLE_RUN_SCRIPT_GENERATION "Enables the generation of run scripts (convenience scripts for starting available configurations)" ON) +option(EVEREST_BUILD_DOCS "Build EVerest documentation" OFF) option(${PROJECT_NAME}_BUILD_TESTING "Build unit tests, used if included as dependency" OFF) option(BUILD_TESTING "Build unit tests, used if standalone project" OFF) option(EVEREST_ENABLE_COMPILE_WARNINGS "Enable compile warnings set in the EVEREST_COMPILE_OPTIONS flag" OFF) @@ -154,6 +155,9 @@ else() find_package(sdbus-c++ REQUIRED) endif() +if(EVEREST_BUILD_DOCS) + add_subdirectory(docs) +endif() include(ev-project-bootstrap) diff --git a/THIRD_PARTY.md b/THIRD_PARTY.md index a72831426b..19b784c768 100644 --- a/THIRD_PARTY.md +++ b/THIRD_PARTY.md @@ -2,3 +2,4 @@ - [CodeCoverage.cmake](https://github.com/bilke/cmake-modules/blob/master/CodeCoverage.cmake) licensed under [The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause) - [Nanopb - Protocol Buffers for Embedded Systems](https://github.com/nanopb/nanopb) licensed under [The zlib License](https://opensource.org/licenses/Zlib) +- [sphinx-notfound-page](https://github.com/readthedocs/sphinx-notfound-page) licensed under [The MIT License](https://opensource.org/licenses/MIT) diff --git a/cmake/everest-generate.cmake b/cmake/everest-generate.cmake index 9c75c7b13e..e96e511398 100644 --- a/cmake/everest-generate.cmake +++ b/cmake/everest-generate.cmake @@ -59,6 +59,21 @@ function(_ev_add_project) ${TYPES_DIR}/*.yaml ) + if(EVEREST_BUILD_DOCS) + find_package( + trailbook-ext-everest + 0.1.0 + REQUIRED + PATHS "${CMAKE_SOURCE_DIR}/cmake" + ) + foreach(TYPES_FILE ${TYPES_FILES}) + trailbook_ev_generate_rst_from_types( + TRAILBOOK_NAME "everest" + TYPES_FILE "${TYPES_FILE}" + ) + endforeach() + endif() + _ev_add_types(${TYPES_FILES}) if (CALLED_FROM_WITHIN_PROJECT) @@ -91,6 +106,21 @@ function(_ev_add_project) ${INTERFACES_DIR}/*.yaml ) + if(EVEREST_BUILD_DOCS) + find_package( + trailbook-ext-everest + 0.1.0 + REQUIRED + PATHS "${CMAKE_SOURCE_DIR}/cmake" + ) + foreach(INTERFACE_FILE ${INTERFACE_FILES}) + trailbook_ev_generate_rst_from_interface( + TRAILBOOK_NAME "everest" + INTERFACE_FILE "${INTERFACE_FILE}" + ) + endforeach() + endif() + _ev_add_interfaces(${INTERFACE_FILES}) if (CALLED_FROM_WITHIN_PROJECT) @@ -420,6 +450,36 @@ function (ev_add_module) endforeach() endif() + if (EVEREST_BUILD_DOCS) + find_package( + trailbook-ext-everest + 0.1.0 + REQUIRED + PATHS "${CMAKE_SOURCE_DIR}/cmake" + ) + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE_NAME}/docs/") + trailbook_ev_add_module_explanation( + TRAILBOOK_NAME "everest" + MODULE_NAME "${MODULE_NAME}" + EXPLANATION_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE_NAME}/docs" + ) + endif() + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE_NAME}/doc.rst") + message( + FATAL_ERROR + "Module ${MODULE_NAME} contains a doc.rst file" + " this is not supported anymore, please move to" + " docs/index.rst, then it will be picked up automatically." + " For now this file will be ignored." + ) + endif() + trailbook_ev_generate_rst_from_manifest( + TRAILBOOK_NAME "everest" + MANIFEST_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE_NAME}/manifest.yaml" + ) + endif() + + # check if python module string(FIND ${MODULE_NAME} "Py" MODULE_PREFIX_POS) if (MODULE_PREFIX_POS EQUAL 0) diff --git a/cmake/trailbook-ext-everest/README.md b/cmake/trailbook-ext-everest/README.md new file mode 100644 index 0000000000..a4cdd1bd4b --- /dev/null +++ b/cmake/trailbook-ext-everest/README.md @@ -0,0 +1,17 @@ +# Trailbook Extension for Everest + +This CMake package is an extension for the Trailbook CMake package +that provides additional functionality specifically for building +documentation for the Everest project. + +The following additional features are provided: + +* cmake function: `trailbook_ev_add_module_explanation()` +* cmake function: `trailbook_ev_create_snapshot()` +* cmake function: `trailbook_ev_generate_rst_from_manifest()` +* cmake function: `trailbook_ev_generate_rst_from_interface()` +* cmake function: `trailbook_ev_generate_rst_from_types()` + +Check out the inline documentation of the functions for more details +on how to use them. Each function is defined in its own CMake file +located in this package directory. diff --git a/cmake/trailbook-ext-everest/add-module-explanation.cmake b/cmake/trailbook-ext-everest/add-module-explanation.cmake new file mode 100644 index 0000000000..d43daa1bd1 --- /dev/null +++ b/cmake/trailbook-ext-everest/add-module-explanation.cmake @@ -0,0 +1,138 @@ +# This macro is for internal use only +# +# It is used in the function trailbook_ev_add_module_explanation(). +# It adds a custom command to copy the explanation module files to the explanation modules directory. +macro(_trailbook_ev_add_module_explanation_copy_explanation_command) + file( + GLOB_RECURSE + MODULE_EXPLANATION_SOURCE_FILES + CMAKE_CONFIGURE_DEPENDS + "${args_EXPLANATION_DIR}/*" + ) + + set(MODULE_EXPLANATION_TARGET_FILES "") + foreach(source_file IN LISTS MODULE_EXPLANATION_SOURCE_FILES) + file(RELATIVE_PATH rel_path "${args_EXPLANATION_DIR}" "${source_file}") + set(target_file "${TRAILBOOK_EV_EXPLANATION_MODULES_DIRECTORY}/${args_MODULE_NAME}/${rel_path}") + list(APPEND MODULE_EXPLANATION_TARGET_FILES "${target_file}") + endforeach() + + add_custom_command( + OUTPUT + ${MODULE_EXPLANATION_TARGET_FILES} + DEPENDS + ${MODULE_EXPLANATION_SOURCE_FILES} + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} + COMMENT + "Copying explanation module ${args_MODULE_NAME} files to: ${TRAILBOOK_EV_EXPLANATION_MODULES_DIRECTORY}/${args_MODULE_NAME}" + COMMAND + ${CMAKE_COMMAND} -E rm -rf + ${MODULE_EXPLANATION_TARGET_FILES} + COMMAND + ${CMAKE_COMMAND} -E make_directory + ${TRAILBOOK_EV_EXPLANATION_MODULES_DIRECTORY}/${args_MODULE_NAME} + COMMAND + ${CMAKE_COMMAND} -E copy_directory + ${args_EXPLANATION_DIR} + ${TRAILBOOK_EV_EXPLANATION_MODULES_DIRECTORY}/${args_MODULE_NAME} + ) +endmacro() + +# This function adds a module explanation to a trailbook. +# It takes the following parameters: +# TRAILBOOK_NAME (required): The name of the trailbook to add the +# module explanation to. +# MODULE_NAME (required): The name of the module explanation. +# EXPLANATION_DIR (required): The absolute path to the directory +# containing the module explanation files. +# +# Usage: +# trailbook_ev_add_module_explanation( +# TRAILBOOK_NAME +# MODULE_NAME +# EXPLANATION_DIR +# ) +function(trailbook_ev_add_module_explanation) + set(options) + set(one_value_args + TRAILBOOK_NAME + MODULE_NAME + EXPLANATION_DIR + ) + set(multi_value_args) + cmake_parse_arguments( + "args" + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN} + ) + + # Parameter TRAILBOOK_NAME + # - is required + # - there should be a target named trailbook_ + if(NOT args_TRAILBOOK_NAME) + message(FATAL_ERROR "trailbook_ev_add_module_explanation: TRAILBOOK_NAME argument is required") + endif() + if(NOT TARGET trailbook_${args_TRAILBOOK_NAME}) + message( + FATAL_ERROR + "trailbook_ev_add_module_explanation: No target named trailbook_${args_TRAILBOOK_NAME} found." + " Did you forget to call add_trailbook() first?" + ) + endif() + + # Parameter MODULE_NAME + # - is required + if(NOT args_MODULE_NAME) + message(FATAL_ERROR "trailbook_ev_add_module_explanation: MODULE_NAME argument is required") + endif() + + # Parameter EXPLANATION_DIR + # - is required + # - must be a absolute path + # - must exist + if(NOT args_EXPLANATION_DIR) + message(FATAL_ERROR "trailbook_ev_add_module_explanation: EXPLANATION_DIR argument is required") + endif() + if(NOT IS_ABSOLUTE "${args_EXPLANATION_DIR}") + message(FATAL_ERROR "trailbook_ev_add_module_explanation: EXPLANATION_DIR must be an absolute path") + endif() + if(NOT EXISTS "${args_EXPLANATION_DIR}") + message(FATAL_ERROR "trailbook_ev_add_module_explanation: EXPLANATION_DIR does not exist") + endif() + + + get_target_property( + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY + trailbook_${args_TRAILBOOK_NAME} + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY + ) + get_target_property( + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + trailbook_${args_TRAILBOOK_NAME} + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + ) + + set(TRAILBOOK_EV_EXPLANATION_DIRECTORY "${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY}/explanation") + set(TRAILBOOK_EV_EXPLANATION_MODULES_DIRECTORY "${TRAILBOOK_EV_EXPLANATION_DIRECTORY}/modules") + + _trailbook_ev_add_module_explanation_copy_explanation_command() + + add_custom_target( + trailbook_${args_TRAILBOOK_NAME}_explanation_module_${args_MODULE_NAME} + DEPENDS + ${MODULE_EXPLANATION_TARGET_FILES} + COMMENT + "Explanation module ${args_MODULE_NAME} for trailbook ${args_TRAILBOOK_NAME} is available." + ) + set_property( + TARGET + trailbook_${args_TRAILBOOK_NAME} + APPEND + PROPERTY + ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE + ${MODULE_EXPLANATION_TARGET_FILES} + trailbook_${args_TRAILBOOK_NAME}_explanation_module_${args_MODULE_NAME} + ) +endfunction() diff --git a/cmake/trailbook-ext-everest/create-snapshot.cmake b/cmake/trailbook-ext-everest/create-snapshot.cmake new file mode 100644 index 0000000000..e3d44a1cf7 --- /dev/null +++ b/cmake/trailbook-ext-everest/create-snapshot.cmake @@ -0,0 +1,135 @@ +if(NOT DEFINED _TRAILBOOK_EXT_EVEREST_CREATE_SNAPSHOT_SETUP) + if(NOT DEFINED everest-utils_SOURCE_DIR) + message(FATAL_ERROR "everest-utils not found. Did you forget to add it to your dependencies.yaml?") + endif() + set(_TRAILBOOK_EXT_EVEREST_CREATE_SNAPSHOT_SCRIPT + "${everest-utils_SOURCE_DIR}/scripts/create_snapshot.py" + ) + if(NOT EXISTS "${_TRAILBOOK_EXT_EVEREST_CREATE_SNAPSHOT_SCRIPT}") + message(FATAL_ERROR "everest-utils found, but create_snapshot.py script is missing at ${_TRAILBOOK_EXT_EVEREST_CREATE_SNAPSHOT_SCRIPT}") + endif() + set(_TRAILBOOK_EXT_EVEREST_CREATE_SNAPSHOT_SETUP TRUE) +endif() + + +# This function creates a snapshot file and adds it +# to the given trailbook +# Parameters: +# EVEREST_WORKSPACE_DIRECTORY (required): Absolute path to the EVerest workspace +# directory +# TRAILBOOK_NAME (required): Name of the trailbook (the +# target must exist) +# OUTPUT_FILE (required): Absolute path to the output +# snapshot file +# Usage: +# trailbook_ev_create_snapshot( +# EVEREST_WORKSPACE_DIRECTORY +# TRAILBOOK_NAME +# OUTPUT_FILE +# ) +function(trailbook_ev_create_snapshot) + set(options) + set(one_value_args + EVEREST_WORKSPACE_DIRECTORY + TRAILBOOK_NAME + OUTPUT_FILE + ) + set(multi_value_args) + cmake_parse_arguments( + "args" + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN} + ) + + # Parameter EVEREST_WORKSPACE_DIRECTORY + # - is required + # - must be a absolute path + # - must exist + if(NOT EVEREST_WORKSPACE_DIRECTORY) + message(FATAL_ERROR "trailbook_ev_create_snapshot: EVEREST_WORKSPACE_DIRECTORY argument is required") + endif() + if(NOT IS_ABSOLUTE "${EVEREST_WORKSPACE_DIRECTORY}") + message(FATAL_ERROR "trailbook_ev_create_snapshot: EVEREST_WORKSPACE_DIRECTORY must be an absolute path") + endif() + if(NOT EXISTS "${EVEREST_WORKSPACE_DIRECTORY}") + message(FATAL_ERROR "trailbook_ev_create_snapshot: EVEREST_WORKSPACE_DIRECTORY must exist") + endif() + + # Parameter TRAILBOOK_NAME + # - is required + # - there should be a target named trailbook_ + if(NOT args_TRAILBOOK_NAME) + message(FATAL_ERROR "trailbook_ev_create_snapshot: TRAILBOOK_NAME argument is required") + endif() + if(NOT TARGET trailbook_${args_TRAILBOOK_NAME}) + message( + FATAL_ERROR + "trailbook_ev_create_snapshot: No target named trailbook_${args_TRAILBOOK_NAME} found." + " Did you forget to call add_trailbook() first?" + ) + endif() + + # Parameter OUTPUT_FILE + # - is required + # - must be a absolute path + if(NOT args_OUTPUT_FILE) + message(FATAL_ERROR "trailbook_ev_create_snapshot: OUTPUT_FILE argument is required") + endif() + if(NOT IS_ABSOLUTE "${args_OUTPUT_FILE}") + message(FATAL_ERROR "trailbook_ev_create_snapshot: OUTPUT_FILE must be an absolute path") + endif() + + get_target_property( + TRAILBOOK_CURRENT_BINARY_DIR + trailbook_${args_TRAILBOOK_NAME} + TRAILBOOK_CURRENT_BINARY_DIR + ) + get_target_property( + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + trailbook_${args_TRAILBOOK_NAME} + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + ) + set(CREATE_SNAPSHOT_TEMP_DIR "${TRAILBOOK_CURRENT_BINARY_DIR}/create_snapshot_temp") + add_custom_command( + OUTPUT + ${args_OUTPUT_FILE} + DEPENDS + ${_TRAILBOOK_EXT_EVEREST_CREATE_SNAPSHOT_SCRIPT} + trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} + USES_TERMINAL + COMMAND + ${CMAKE_COMMAND} -E rm -rf + ${CREATE_SNAPSHOT_TEMP_DIR} + COMMAND + ${Python3_EXECUTABLE} + ${_TRAILBOOK_EXT_EVEREST_CREATE_SNAPSHOT_SCRIPT} + --working-dir ${args_EVEREST_WORKSPACE_DIRECTORY} + --temp-dir ${CREATE_SNAPSHOT_TEMP_DIR} + --allow-relative-to-working-dir + COMMAND + ${CMAKE_COMMAND} -E copy + ${CREATE_SNAPSHOT_TEMP_DIR}/snapshot.yaml + ${args_OUTPUT_FILE} + ) + add_custom_target( + trailbook_${args_TRAILBOOK_NAME}_create_snapshot + DEPENDS + trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} + ${args_OUTPUT_FILE} + COMMENT + "Target to create snapshot file ${args_OUTPUT_FILE} for trailbook ${args_TRAILBOOK_NAME}" + ) + set_property( + TARGET + trailbook_${args_TRAILBOOK_NAME} + APPEND + PROPERTY + ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE + ${args_OUTPUT_FILE} + trailbook_${args_TRAILBOOK_NAME}_create_snapshot + ) +endfunction() diff --git a/cmake/trailbook-ext-everest/generate-rst-from-interface.cmake b/cmake/trailbook-ext-everest/generate-rst-from-interface.cmake new file mode 100644 index 0000000000..8241b98128 --- /dev/null +++ b/cmake/trailbook-ext-everest/generate-rst-from-interface.cmake @@ -0,0 +1,127 @@ +# This macro is for internal use only +# +# It is used in the function trailbook_ev_generate_rst_from_interface(). +# It adds an custom command to generate the RST file from the interface definition file +macro(_trailbook_ev_generate_rst_from_interface_generate_command) + get_filename_component(INTERFACE_NAME ${args_INTERFACE_FILE} NAME_WE) + set(GENERATED_FILE "${TRAILBOOK_EV_REFERENCE_INTERFACES_DIRECTORY}/${INTERFACE_NAME}.rst") + set(TEMPLATES_DIRECTORY "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/templates") + add_custom_command( + OUTPUT + ${GENERATED_FILE} + DEPENDS + trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/process_template.py + ${args_INTERFACE_FILE} + ${TEMPLATES_DIRECTORY}/interface.rst.jinja + ${TEMPLATES_DIRECTORY}/macros.jinja + COMMENT + "Generating RST file ${GENERATED_FILE} from interface definition ${args_INTERFACE_FILE}" + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/process_template.py + --template-dir "${TEMPLATES_DIRECTORY}" + --template-file "${TEMPLATES_DIRECTORY}/interface.rst.jinja" + --name "${INTERFACE_NAME}" + --data-file "${args_INTERFACE_FILE}" + --target-file "${GENERATED_FILE}" + ) +endmacro() + + +# This function generates an RST file from an interface definition file. +# +# Arguments: +# TRAILBOOK_NAME (required): Name of the trailbook instance. +# INTERFACE_FILE (required): Path to the interface definition file +# Usage: +# trailbook_ev_generate_rst_from_interface( +# TRAILBOOK_NAME +# INTERFACE_FILE +# ) +function(trailbook_ev_generate_rst_from_interface) + set(options) + set(one_value_args + TRAILBOOK_NAME + INTERFACE_FILE + ) + set(multi_value_args) + cmake_parse_arguments( + "args" + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN} + ) + + # Parameter TRAILBOOK_NAME + # - is required + # - there should be a target named trailbook_ + if(NOT args_TRAILBOOK_NAME) + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_interface: TRAILBOOK_NAME argument is required") + endif() + if(NOT TARGET trailbook_${args_TRAILBOOK_NAME}) + message( + FATAL_ERROR + "trailbook_ext_ev_generate_rst_from_interface: No target named trailbook_${args_TRAILBOOK_NAME} found." + " Did you forget to call add_trailbook() first?" + ) + endif() + + # Parameter INTERFACE_FILE + # - is required + # - must be a absolute path + # - must exist + if(NOT args_INTERFACE_FILE) + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_interface: INTERFACE_FILE argument is required") + endif() + if(NOT IS_ABSOLUTE "${args_INTERFACE_FILE}") + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_interface: INTERFACE_FILE must be an absolute path") + endif() + if(NOT EXISTS "${args_INTERFACE_FILE}") + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_interface: INTERFACE_FILE must exist") + endif() + + get_target_property( + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY + trailbook_${args_TRAILBOOK_NAME} + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY + ) + get_target_property( + TRAILBOOK_CURRENT_BINARY_DIR + trailbook_${args_TRAILBOOK_NAME} + TRAILBOOK_CURRENT_BINARY_DIR + ) + get_target_property( + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + trailbook_${args_TRAILBOOK_NAME} + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + ) + + + set(TRAILBOOK_EV_REFERENCE_DIRECTORY "${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY}/reference") + set(TRAILBOOK_EV_REFERENCE_INTERFACES_DIRECTORY "${TRAILBOOK_EV_REFERENCE_DIRECTORY}/interfaces") + + + _trailbook_ev_generate_rst_from_interface_generate_command() + + add_custom_target( + trailbook_${args_TRAILBOOK_NAME}_generate_rst_from_interface_${INTERFACE_NAME} + DEPENDS + ${GENERATED_FILE} + trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} + COMMENT + "Target to generate RST file ${GENERATED_FILE} from interface definition ${args_INTERFACE_FILE}" + ) + set_property( + TARGET + trailbook_${args_TRAILBOOK_NAME} + APPEND + PROPERTY + ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE + ${GENERATED_FILE} + trailbook_${args_TRAILBOOK_NAME}_generate_rst_from_interface_${INTERFACE_NAME} + ) +endfunction() diff --git a/cmake/trailbook-ext-everest/generate-rst-from-manifest.cmake b/cmake/trailbook-ext-everest/generate-rst-from-manifest.cmake new file mode 100644 index 0000000000..395d4446c3 --- /dev/null +++ b/cmake/trailbook-ext-everest/generate-rst-from-manifest.cmake @@ -0,0 +1,126 @@ +# This macro is for internal use only +# +# It is used in the function trailbook_ev_generate_rst_from_manifest(). +# It adds an custom command to generate the RST file from the manifest file +macro(_trailbook_ev_generate_rst_from_manifest_generate_command) + set(GENERATED_FILE "${TRAILBOOK_EV_REFERENCE_MODULES_DIRECTORY}/${MODULE_NAME}.rst") + set(TEMPLATES_DIRECTORY "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/templates") + if(EXISTS "${MODULE_DIR}/docs/") + set(HAS_MODULE_EXPLANATION "--has-module-explanation") + endif() + add_custom_command( + OUTPUT + ${GENERATED_FILE} + DEPENDS + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/process_template.py + ${args_MANIFEST_FILE} + ${TEMPLATES_DIRECTORY}/module.rst.jinja + ${TEMPLATES_DIRECTORY}/macros.jinja + trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} + COMMENT + "Generating RST file ${GENERATED_FILE} from manifest ${args_MANIFEST_FILE}" + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/process_template.py + --template-dir "${TEMPLATES_DIRECTORY}" + --template-file "${TEMPLATES_DIRECTORY}/module.rst.jinja" + --name "${MODULE_NAME}" + --data-file "${args_MANIFEST_FILE}" + --target-file "${GENERATED_FILE}" + ${HAS_MODULE_EXPLANATION} + ) +endmacro() + + +# This function generates an RST file from a manifest definition file. +# It takes the following arguments: +# TRAILBOOK_NAME (required): The name of the trailbook. +# MANIFEST_FILE (required): The absolute path to the manifest +# definition file. +# Usage: +# trailbook_ev_generate_rst_from_manifest( +# TRAILBOOK_NAME +# MANIFEST_FILE +# ) +function(trailbook_ev_generate_rst_from_manifest) + set(options) + set(one_value_args + TRAILBOOK_NAME + MANIFEST_FILE + ) + set(multi_value_args) + cmake_parse_arguments( + "args" + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN} + ) + + # Parameter TRAILBOOK_NAME + # - is required + # - there should be a target named trailbook_ + if(NOT args_TRAILBOOK_NAME) + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_manifest: TRAILBOOK_NAME argument is required") + endif() + if(NOT TARGET trailbook_${args_TRAILBOOK_NAME}) + message( + FATAL_ERROR + "trailbook_ext_ev_generate_rst_from_manifest: No target named trailbook_${args_TRAILBOOK_NAME} found." + " Did you forget to call add_trailbook() first?" + ) + endif() + + # Parameter MANIFEST_FILE + # - is required + # - must be a absolute path + # - must exist + if(NOT args_MANIFEST_FILE) + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_manifest: MANIFEST_FILE argument is required") + endif() + if(NOT IS_ABSOLUTE "${args_MANIFEST_FILE}") + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_manifest: MANIFEST_FILE must be an absolute path") + endif() + if(NOT EXISTS "${args_MANIFEST_FILE}") + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_manifest: MANIFEST_FILE must exist") + endif() + + get_target_property( + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY + trailbook_${args_TRAILBOOK_NAME} + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY + ) + get_target_property( + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + trailbook_${args_TRAILBOOK_NAME} + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + ) + + set(TRAILBOOK_EV_REFERENCE_DIRECTORY "${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY}/reference") + set(TRAILBOOK_EV_REFERENCE_MODULES_DIRECTORY "${TRAILBOOK_EV_REFERENCE_DIRECTORY}/modules") + get_filename_component(MODULE_DIR ${args_MANIFEST_FILE} DIRECTORY) + get_filename_component(MODULE_NAME ${MODULE_DIR} NAME_WE) + + + _trailbook_ev_generate_rst_from_manifest_generate_command() + + add_custom_target( + trailbook_${args_TRAILBOOK_NAME}_generate_rst_from_manifest_${MODULE_NAME} + DEPENDS + ${GENERATED_FILE} + trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} + COMMENT + "Target to generate RST file ${GENERATED_FILE} from manifest definition ${args_MANIFEST_FILE}" + ) + set_property( + TARGET + trailbook_${args_TRAILBOOK_NAME} + APPEND + PROPERTY + ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE + ${GENERATED_FILE} + trailbook_${args_TRAILBOOK_NAME}_generate_rst_from_manifest_${MODULE_NAME} + ) +endfunction() diff --git a/cmake/trailbook-ext-everest/generate-rst-from-types.cmake b/cmake/trailbook-ext-everest/generate-rst-from-types.cmake new file mode 100644 index 0000000000..b78182173a --- /dev/null +++ b/cmake/trailbook-ext-everest/generate-rst-from-types.cmake @@ -0,0 +1,117 @@ +# This macro is for internal use only +# +# It is used in the function trailbook_ev_generate_rst_from_types(). +# It adds an custom command to generate the RST file from the types definition file +macro(_trailbook_ev_generate_rst_from_types_generate_command) + get_filename_component(TYPES_NAME ${args_TYPES_FILE} NAME_WE) + set(GENERATED_FILE "${TRAILBOOK_EV_REFERENCE_TYPES_DIRECTORY}/${TYPES_NAME}.rst") + set(TEMPLATES_DIRECTORY "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/templates") + add_custom_command( + OUTPUT + ${GENERATED_FILE} + DEPENDS + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/process_template.py + ${args_TYPES_FILE} + ${TEMPLATES_DIRECTORY}/types.rst.jinja + ${TEMPLATES_DIRECTORY}/macros.jinja + COMMENT + "Generating RST file ${GENERATED_FILE} from types definition ${args_TYPES_FILE}" + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/process_template.py + --template-dir "${TEMPLATES_DIRECTORY}" + --template-file "${TEMPLATES_DIRECTORY}/types.rst.jinja" + --name "${TYPES_NAME}" + --data-file "${args_TYPES_FILE}" + --target-file "${GENERATED_FILE}" + ) +endmacro() + + +# This function generates an RST file from a types definition file. +# It takes the following arguments: +# TRAILBOOK_NAME (required): The name of the trailbook. +# TYPES_FILE (required): The absolute path to the types definition file. +# Usage: +# trailbook_ev_generate_rst_from_types( +# TRAILBOOK_NAME +# TYPES_FILE +# ) +function(trailbook_ev_generate_rst_from_types) + set(options) + set(one_value_args + TRAILBOOK_NAME + TYPES_FILE + ) + set(multi_value_args) + cmake_parse_arguments( + "args" + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN} + ) + + # Parameter TRAILBOOK_NAME + # - is required + # - there should be a target named trailbook_ + if(NOT args_TRAILBOOK_NAME) + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_types: TRAILBOOK_NAME argument is required") + endif() + if(NOT TARGET trailbook_${args_TRAILBOOK_NAME}) + message( + FATAL_ERROR + "trailbook_ext_ev_generate_rst_from_types: No target named trailbook_${args_TRAILBOOK_NAME} found." + " Did you forget to call add_trailbook() first?" + ) + endif() + + # Parameter TYPES_FILE + # - is required + # - must be a absolute path + # - must exist + if(NOT args_TYPES_FILE) + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_types: TYPES_FILE argument is required") + endif() + if(NOT IS_ABSOLUTE "${args_TYPES_FILE}") + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_types: TYPES_FILE must be an absolute path") + endif() + if(NOT EXISTS "${args_TYPES_FILE}") + message(FATAL_ERROR "trailbook_ext_ev_generate_rst_from_types: TYPES_FILE must exist") + endif() + + get_target_property( + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY + trailbook_${args_TRAILBOOK_NAME} + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY + ) + get_target_property( + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + trailbook_${args_TRAILBOOK_NAME} + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + ) + + set(TRAILBOOK_EV_REFERENCE_DIRECTORY "${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY}/reference") + set(TRAILBOOK_EV_REFERENCE_TYPES_DIRECTORY "${TRAILBOOK_EV_REFERENCE_DIRECTORY}/types") + + _trailbook_ev_generate_rst_from_types_generate_command() + + add_custom_target( + trailbook_${args_TRAILBOOK_NAME}_generate_rst_from_types_${TYPES_NAME} + DEPENDS + ${GENERATED_FILE} + trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} + COMMENT + "Target to generate RST file ${GENERATED_FILE} from types definition ${args_TYPES_FILE}" + ) + set_property( + TARGET + trailbook_${args_TRAILBOOK_NAME} + APPEND + PROPERTY + ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE + ${GENERATED_FILE} + trailbook_${args_TRAILBOOK_NAME}_generate_rst_from_types_${TYPES_NAME} + ) +endfunction() diff --git a/cmake/trailbook-ext-everest/process_template.py b/cmake/trailbook-ext-everest/process_template.py new file mode 100755 index 0000000000..b47690323f --- /dev/null +++ b/cmake/trailbook-ext-everest/process_template.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright Pionix GmbH and Contributors to EVerest +# +""" +author: andreas.heinrich@pionix.de +This script processes a template file with Jinja2 and YAML data. +""" + + +import argparse +import jinja2 +import yaml +from pathlib import Path + + +def rst_indent(input): + lines = input.splitlines() + lines = [f"| {line}\r\n" for line in lines] + return "".join(lines) + + +def make_rst_ref(input): + output = input.replace("/", "") + output = output.replace("#", "-") + return output + + +def main(): + parser = argparse.ArgumentParser(description='Processes a template file with Jinja2 and YAML data.') + parser.add_argument( + '--template-dir', + type=Path, + dest='template_dir', + action='store', + required=True, + help='Directory containing the Jinja2 template files' + ) + parser.add_argument( + '--template-file', + type=Path, + dest='template_file', + action='store', + required=True, + help='Jinja2 template file to process' + ) + parser.add_argument( + '--name', + type=str, + dest='name', + action='store', + required=True, + help='Name to be used in the template rendering' + ) + parser.add_argument( + '--data-file', + type=Path, + dest='data_file', + action='store', + required=True, + help='YAML file containing data for the template' + ) + parser.add_argument( + '--has-module-explanation', + dest='has_module_explanation', + action='store_true', + help='Flag indicating if the module explanation should be referenced' + ) + parser.add_argument( + '--target-file', + type=Path, + dest='target_file', + action='store', + required=True, + help='Output file for the processed template' + ) + parser.set_defaults( + has_module_explanation=False, + ) + args = parser.parse_args() + + if not args.template_dir.is_absolute(): + raise ValueError("Template directory path must be absolute") + if not args.template_dir.exists(): + raise ValueError("Template directory does not exist") + if not args.template_dir.is_dir(): + raise ValueError("Template directory path is not a directory") + + if not args.template_file.is_absolute(): + raise ValueError("Template file path must be absolute") + if not args.template_file.exists(): + raise ValueError("Template file does not exist") + if not args.template_file.is_file(): + raise ValueError("Template file path is not a file") + if not args.template_file.is_relative_to(args.template_dir): + raise ValueError("Template file path is not relative to template directory") + + if not args.data_file.is_absolute(): + raise ValueError("Data file path must be absolute") + if not args.data_file.exists(): + raise ValueError("Data file does not exist") + if not args.data_file.is_file(): + raise ValueError("Data file path is not a file") + if args.data_file.suffix not in ['.yml', '.yaml']: + raise ValueError("Data file must have a .yml or .yaml extension") + + if not args.target_file.is_absolute(): + raise ValueError("Target file path must be absolute") + if args.target_file.suffix != '.rst': + raise ValueError("Target file must have a .rst extension") + + if not args.target_file.parent.exists(): + args.target_file.parent.mkdir(parents=True, exist_ok=True) + + env = jinja2.Environment( + loader=jinja2.FileSystemLoader(args.template_dir), + trim_blocks=True, + lstrip_blocks=True + ) + env.filters['rst_indent'] = rst_indent + env.filters['make_rst_ref'] = make_rst_ref + + template_file_name = args.template_file.relative_to(args.template_dir) + template = env.get_template(str(template_file_name)) + output = template.render( + name=args.name, + data=yaml.safe_load(args.data_file.read_text()), + has_module_explanation=args.has_module_explanation, + ) + args.target_file.write_text(output) + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Error: {e}") + exit(1) diff --git a/cmake/trailbook-ext-everest/templates/interface.rst.jinja b/cmake/trailbook-ext-everest/templates/interface.rst.jinja new file mode 100644 index 0000000000..e8de509937 --- /dev/null +++ b/cmake/trailbook-ext-everest/templates/interface.rst.jinja @@ -0,0 +1,19 @@ +{% import 'macros.jinja' as funcs %} +:orphan: + +{{ funcs.explicit_target('everest_interfaces_' + name) }} +{{ funcs.h1(name) }} +{{ data.description | rst_indent() }} +{% if 'documentation' in interface %} +{{ funcs.documentation(data.documentation) | rst_indent() -}} +{% endif %} + +{% if data.vars %} +{{ funcs.h2('Variables') -}} +{{ funcs.vars(data.vars.items(), False) | rst_indent() -}} +{% endif %} + +{% if data.cmds %} +{{ funcs.h2('Commands') -}} +{{ funcs.cmds(data.cmds.items()) | rst_indent() -}} +{% endif %} diff --git a/cmake/trailbook-ext-everest/templates/macros.jinja b/cmake/trailbook-ext-everest/templates/macros.jinja new file mode 100644 index 0000000000..3edb11f552 --- /dev/null +++ b/cmake/trailbook-ext-everest/templates/macros.jinja @@ -0,0 +1,338 @@ +{#################################} +{### General json macros ###} +{#################################} + +{### Renders a key-value-pair ###} +{% macro keyvalue(key, value) %} +{{ key }}:{{ value }} +{% endmacro %} + +{### Renders a sequence ###} +{% macro sequence(key, data) %} +{{ key }}: +{% for entry in data %} + - {{ entry }} +{% endfor %} +{% endmacro %} + +{### Renders a mapping ###} +{% macro mapping(key, data, ignore_keys, render_key) %} +{% set indent_width = 0 %} +{% if render_key %} +{% set indent_width = 1 %} +{{ key }}: +{% endif %} +{% for sub_key, sub_data in data %} +{% if not sub_key in ignore_keys %} +{% if sub_data is mapping %} +{{ mapping(sub_key, sub_data.items(), [], True) | indent(indent_width, True) -}} +{% elif sub_data is string %} +{{ keyvalue(sub_key, sub_data) | indent(indent_width, True) -}} +{% elif sub_data is sequence %} +{{ sequence(sub_key, sub_data) | indent(indent_width, True) -}} +{% endif %} +{% endif %} +{% endfor %} +{% endmacro %} + +{#################################} +{### General RST macros ###} +{#################################} + +{### Make H1 headline ###} +{% macro h1(title) %} +{% set title_length = title|length %} +{{ '#' * title_length }} +{{ title }} +{{ '#' * title_length }} +{% endmacro %} + +{### Make H2 headline ###} +{% macro h2(title) %} +{% set title_length = title|length %} +{{ '*' * title_length }} +{{ title }} +{{ '*' * title_length }} +{% endmacro %} + +{### Make H3 headline ###} +{% macro h3(title) %} +{% set title_length = title|length %} +{{ title }} +{{ '=' * title_length }} +{% endmacro %} + +{### Make H4 headline ###} +{% macro h4(title) %} +{% set title_length = title|length %} +{{ title }} +{{ '-' * title_length }} +{% endmacro %} + +{### Make H5 headline ###} +{% macro h5(title) %} +{% set title_length = title|length %} +{{ title }} +{{ '^' * title_length }} +{% endmacro %} + +{### Make H6 headline ###} +{% macro h6(title) %} +{% set title_length = title|length %} +{{ title }} +{{ '"' * title_length }} +{% endmacro %} + +{### Make explicit target ###} +{% macro explicit_target(target_name) %} + +.. _{{ target_name | make_rst_ref() }}: + +{% endmacro %} + +{### References an explicit target ###} +{% macro ref(target_name, text) %} +:ref:`{{ text }} <{{ target_name | make_rst_ref() }}>` +{%- endmacro %} + +{#################################} +{### Interface.json macros ###} +{#################################} + +{### Renders a multiline documentation ###} +{% macro documentation(lines) %} +{% for line in lines %} +{{ line }} +{% endfor %} +{% endmacro %} + +{### Renders a single var ###} +{% macro var(var_name, var_data, show_required=true, required=None) %} +{% if required == None %} +{% set required = 'default' not in var_data %} +{% endif %} +{% set optional = '' %} +{% if show_required == true %} +{% if required == true %} +{% set optional = '' %} +{% elif required == false %} +{% set optional = '' %} +{% else %} +{% include "required needs to be set" %} +{% endif %} +{% endif %} +{% set var_type = var_data.type %} +{% if not var_data.type %} +{% set var_type = "string/object" %} +{% endif %} +**{{ var_name }}**:*{{ var_type }}* {{ optional }} +{%- if '$ref' in var_data %}{{ ' (' + ref(var_data['$ref'], var_data['$ref'] | make_rst_ref()) + ')' }}{% endif +%} +{# Add default value for config entries #} +{% if 'default' in var_data %} +{% if var_data.default is string %} +{% set var_default = "\"" + var_data.default + "\"" %} +{% else %} +{% set var_default = var_data.default %} +{% endif %} +*default: {{ var_default }}* +{% endif %} + {{ var_data.description }} +{% if 'documentation' in var_data %} +{{ documentation(var_data['documentation']) | indent(1, True) -}} +{% endif %} +{% set ignore_keys = ['default', 'description', 'type', 'properties', 'documentation', '$ref', 'required', 'items'] %} +{% set mapping_result = mapping( var_name, var_data.items(), ignore_keys, False) %} +{% if mapping_result != '' %} +{{ mapping_result | indent(1, True) -}} +{% endif %} +{% if var_data.type == 'object' and 'properties' in var_data %} + properties: +{% if not 'required' in var_data %} +{% set all_required = True %} +{% set required_array = [] %} +{% else %} +{% set all_required = False %} +{% set required_array = var_data.required %} +{% endif %} +{{ vars(var_data.properties.items(), True, required_array, all_required) | indent(2, True) -}} +{% endif %} +{% if var_data.type == 'array' and 'items' in var_data %} +{{ var('array_item', var_data['items'], False, True) | indent(1, True) }} +{% endif %} +{% if '$ref' in var_data %} + **There is an extended definition for this object** {{ ref(var_data['$ref'], 'here') }}. +{% endif %} +{% endmacro %} + +{### Renders a list of vars ###} +{% macro vars(vars, show_required=true, required=[], all_required=False) %} +{% for var_name, var_data in vars %} +{% if show_required == true %} +{% if all_required %} +{% set is_required = True %} +{% else %} +{% set is_required = var_name in required %} +{% endif %} +{% else %} +{% set is_required = None %} +{% endif %} +{{ var(var_name, var_data, show_required, is_required) -}} +{% endfor %} +{% endmacro %} + + +{### Renders cmd result ###} +{% macro cmd_result(result_data) %} +{{ var('Result', result_data, False) -}} +{% endmacro %} + +{### Renders single cmd argument ###} +{% macro cmd_argument(arg_name, arg_data) %} +{{ var(arg_name, arg_data, True) -}} +{% endmacro %} + +{### Renders cmd arguments ###} +{% macro cmd_arguments(args) %} +{% for arg_name, arg_data in args %} +{{ cmd_argument(arg_name, arg_data) -}} +{% endfor %} +{% endmacro %} + +{### Renders a single cmd ###} +{% macro cmd(cmd_name, cmd_data) %} +{% if 'result' in cmd_data %} +{% set type_string = cmd_data.result.type %} +{% else %} +{% set type_string = 'void' %} +{% endif %} +**{{ cmd_name }}**:*{{ type_string }}* + {{ cmd_data.description }} +{% if 'arguments' in cmd_data %} +{{ cmd_arguments(cmd_data.arguments.items()) | indent(1, True) -}} +{% endif %} +{% if 'result' in cmd_data %} +{{ cmd_result(cmd_data['result']) | indent(1, True) -}} +{% endif %} +{% if 'documentation' in cmd_data %} +{{ documentation(cmd_data['documentation']) | indent(1, True) -}} +{% endif %} +{% endmacro %} + +{### Renders a list of cmds ###} +{% macro cmds(cmds) %} +{% for cmd_name, cmd_data in cmds %} +{{ cmd(cmd_name, cmd_data) -}} +{% endfor %} +{% endmacro %} + +{#################################} +{### types.json macros ###} +{#################################} + +{### Renders a single type ###} +{% macro type(type_name, type_data, file_name) %} +{% set target_name = '/' + file_name + '#/' + type_name %} +{{ explicit_target(target_name) -}} +{{ var(type_name, type_data, False) | rst_indent() -}} +{% endmacro %} + +{### Renders a list of types ###} +{% macro types(types, file_name) %} +{% for type_name, type_data in types %} +{{ type(type_name, type_data, file_name) -}} +{% endfor %} +{% endmacro %} + +{#################################} +{### manifest.json macros ###} +{#################################} + +{### Renders a single config entry ###} +{% macro config_entry(name, data) %} +{{ var(name, data, True) -}} +{% endmacro %} + +{### Renders a list of config entries ###} +{% macro config(config_data) %} +{% for entry_name, entry_data in config_data %} +{{ config_entry(entry_name, entry_data) -}} +{% endfor %} +{% endmacro %} + +{### Renders a single impl ###} +{% macro impl(name, data) %} +{% set interface_target = 'everest_interfaces_' + data.interface %} +**{{ name }}**:{{ ref(interface_target, data.interface) }} + {{ data.description }} +{% if 'documentation' in data %} +{{ documentation(data['documentation']) | indent(1, True) -}} +{% endif %} +{% set ignore_keys = ['description', 'interface', 'documentation'] %} +{% set mapping_result = mapping( name, data.items(), ignore_keys, False) %} +{% if mapping_result != '' %} +{{ mapping_result | indent(1, True) -}} +{% endif %} +{% if 'config' in data %} + config: +{{ config(data.config.items()) | indent(2, True) -}} +{% endif %} +{% endmacro %} + +{### Renders a list of impls ###} +{% macro impls(impls) %} +{% for impl_name, impl_data in impls %} +{{ impl(impl_name, impl_data) -}} +{% endfor %} +{% endmacro %} + +{### Renders a single requirement ###} +{% macro req(name, data) %} +{% if not 'min_connections' in data %} +{% set min_conns = 1 %} +{% else %} +{% set min_conns = data.min_connections %} +{% endif %} +{% if not 'max_connections' in data %} +{% set max_conns = 1 %} +{% else %} +{% set max_conns = data.max_connections %} +{% endif %} +{% set conns = min_conns|string + ".." + max_conns|string %} +{% if min_conns == max_conns %} +{% set conns = min_conns|string %} +{% endif %} +{% set interface_target = 'everest_interfaces_' + data.interface %} +**{{ name }}**:{{ ref(interface_target, data.interface) }} {{conns}} +{% set ignore_keys = ['interface'] %} +{% set mapping_result = mapping( name, data.items(), ignore_keys, False) %} +{% if mapping_result != '' %} +{{ mapping_result | indent(1, True) -}} +{% endif %} +{% endmacro %} + +{### Renders a list of requirements ###} +{% macro reqs(reqs) %} +{% for req_name, req_data in reqs %} +{{ req(req_name, req_data) -}} +{% endfor %} +{% endmacro %} + +{### Renders metadata ###} +{% macro metadata(data) %} +{{ h2('Metadata') -}} +{{ h3('Authors') -}} +{% for author in data['authors'] %} +| {{ author }} +{% endfor %} + +{{ h3('License') -}} +| {{ data['license'] }} + +{% set ignore_keys = ['authors', 'license'] %} +{% set mapping_result = mapping('metadata', data.items(), ignore_keys, False) %} +{% if mapping_result != '' %} +{{ h3('Misc') -}} +{{ mapping_result | indent(1, True) | rst_indent() -}} + +{% endif %} +{% endmacro %} diff --git a/cmake/trailbook-ext-everest/templates/module.rst.jinja b/cmake/trailbook-ext-everest/templates/module.rst.jinja new file mode 100644 index 0000000000..b1b7d87d1e --- /dev/null +++ b/cmake/trailbook-ext-everest/templates/module.rst.jinja @@ -0,0 +1,30 @@ +{% import 'macros.jinja' as funcs %} +:orphan: + +{{ funcs.explicit_target('everest_modules_' + name) -}} +{{ funcs.h1(name) -}} +{{ data.description | rst_indent() }} +{% if 'documentation' in manifest %} +{{ funcs.documentation(data.documentation) | rst_indent() -}} +{% endif %} + +{% if has_module_explanation %} +For a detailed handwritten documentation see {{ funcs.ref("everest_modules_handwritten_" + name, "here")}} +{% endif %} + +{% if data.config %} +{{ funcs.h2('Module Configuration') -}} +{{ funcs.config(data.config.items()) | rst_indent() -}} +{% endif %} + +{% if data.provides %} +{{ funcs.h2('Provides') -}} +{{ funcs.impls(data.provides.items()) | rst_indent() -}} +{% endif %} + +{% if data.requires %} +{{ funcs.h2('Requirements') -}} +{{ funcs.reqs(data.requires.items()) | rst_indent() -}} +{% endif %} + +{{ funcs.metadata(data.metadata) -}} diff --git a/cmake/trailbook-ext-everest/templates/types.rst.jinja b/cmake/trailbook-ext-everest/templates/types.rst.jinja new file mode 100644 index 0000000000..aa0c4b72f2 --- /dev/null +++ b/cmake/trailbook-ext-everest/templates/types.rst.jinja @@ -0,0 +1,10 @@ +{% import 'macros.jinja' as funcs %} +:orphan: + +{{ funcs.explicit_target('everest_types_' + name) }} +{{ funcs.h1(name) }} +{{ data.description}} +{% if 'documentation' in types %} +{{ funcs.documentation(data.documentation) -}} +{% endif %} +{{ funcs.types(data.types.items(), name) }} diff --git a/cmake/trailbook-ext-everest/trailbook-ext-everest-config-version.cmake b/cmake/trailbook-ext-everest/trailbook-ext-everest-config-version.cmake new file mode 100644 index 0000000000..cf62352471 --- /dev/null +++ b/cmake/trailbook-ext-everest/trailbook-ext-everest-config-version.cmake @@ -0,0 +1,13 @@ +set(PACKAGE_VERSION 0.1.0) + +if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) +elseif(PACKAGE_FIND_VERSION_MAJOR STREQUAL "0") + if(PACKAGE_FIND_VERSION_MINOR GREATER "1") + set(PACKAGE_VERSION_UNSUITABLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + endif() +else() + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/cmake/trailbook-ext-everest/trailbook-ext-everest-config.cmake b/cmake/trailbook-ext-everest/trailbook-ext-everest-config.cmake new file mode 100644 index 0000000000..6e616a046c --- /dev/null +++ b/cmake/trailbook-ext-everest/trailbook-ext-everest-config.cmake @@ -0,0 +1,12 @@ +find_package( + trailbook + 0.1.0 + REQUIRED + PATHS "${CMAKE_SOURCE_DIR}/cmake" +) + +include("${CMAKE_CURRENT_LIST_DIR}/add-module-explanation.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/generate-rst-from-interface.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/generate-rst-from-types.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/generate-rst-from-manifest.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/create-snapshot.cmake") diff --git a/cmake/trailbook/EXTENDING.md b/cmake/trailbook/EXTENDING.md new file mode 100644 index 0000000000..7b37b0273b --- /dev/null +++ b/cmake/trailbook/EXTENDING.md @@ -0,0 +1,181 @@ +# Extending the trailbook package + +The trailbook package provides a set of targets and target properties +that can be used to hook into the build process of the trailbook documentation +and extend it with custom functionality. + +## Important Note + +Since the trailbook packages work a lot with custom CMake targets and +custom CMake commands, it is important to set dependencies correctly +when extending the trailbook package. + +This means that it is not sufficient to just depend on the targets +and extend target dependencies with `add_dependencies()`. Instead, +you should also make sure to extend the file-level dependencies. For this +a set of custom target properties is provided that can be used +to add additional dependencies to the custom commands used in the +trailbook build process. + +## Available Stages to Hook Into + +To hook into the build process custom commands can be placed in between +stages + +### Hook in before stage: Prepare Sphinx Source + +If you want to hook into the build process before the Sphinx source +is prepared, you can define a custom command that doesn't need to +depend on any files, but the created files and targets should be appended to +the target list property `ADDITIONAL_DEPS_STAGE_PREPARE_SPHINX_SOURCE_BEFORE`. + +This can be done by using the following code snippet: + +```cmake +# Your custom cmake code here +add_custom_command( + OUTPUT + + + COMMAND + + DEPENDS + +) + +add_custom_target( + + DEPENDS + + +) + +# Hook into the trailbook build process +set_property( + TARGET trailbook_ + APPEND + PROPERTY + ADDITIONAL_DEPS_STAGE_PREPARE_SPHINX_SOURCE_BEFORE + + + +) +``` + +* ``, `` can be any custom files created + by your command. +* `` is a custom target that + wraps your command for example. +* `` should be replaced with the name of your trailbook + provided in the `add_trailbook()` function call. + +With this target-level dependencies and file-level dependencies can be added. +If there is a target that depends on the output files, the file-level +dependencies should be added as well. + +### Hook in before stage: Build Sphinx + +If you want to hook in before the Sphinx build process starts, +you can use the target list property `ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE`. +and `DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER` to add file-level dependencies +to the stage before. + +This can be done by using the following code snippet: + +```cmake +# Hook into the trailbook build process after the prepare stage +get_target_property( + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + trailbook_ + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER +) + +# Your custom cmake code here +add_custom_command( + OUTPUT + + + DEPENDS + + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} + COMMAND + +) +add_custom_target( + + DEPENDS + + + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} +) + +# Hook into the trailbook build process before the build stage +set_property( + TARGET trailbook_ + APPEND + PROPERTY + ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE + + + +) +``` + +* ``, `` can be any custom files created + by your command. +* `` is a custom target that + wraps your command for example. +* `` should be replaced with the name of your trailbook + provided in the `add_trailbook()` function call. + +With the `get_target_property()` call the file-level dependencies +from the previous stage are retrieved and added to the custom command +and the custom target. This ensures that the custom command is executed +after the previous stage is completed. + +With the `set_property()` call the custom target and the output files +are added to the target-level dependencies of the build stage. +This ensures that the build stage waits for the custom command +to complete before starting the Sphinx build process. + +### Hook in before stage: Post Process Sphinx + +This can be done analogously to the previous stage, but using the target list property +`ADDITIONAL_DEPS_STAGE_POSTPROCESS_SPHINX_BEFORE` and `DEPS_STAGE_BUILD_SPHINX_AFTER`. + +```cmake +# Hook into the trailbook build process after the build stage +get_target_property( + DEPS_STAGE_BUILD_SPHINX_AFTER + trailbook_ + DEPS_STAGE_BUILD_SPHINX_AFTER +) +# Your custom cmake code here +add_custom_command( + OUTPUT + + + DEPENDS + + ${DEPS_STAGE_BUILD_SPHINX_AFTER} + COMMAND + +) +add_custom_target( + + DEPENDS + + + ${DEPS_STAGE_BUILD_SPHINX_AFTER} +) +# Hook into the trailbook build process before the post process stage +set_property( + TARGET trailbook_ + APPEND + PROPERTY + ADDITIONAL_DEPS_STAGE_POSTPROCESS_SPHINX_BEFORE + + + +) +``` diff --git a/cmake/trailbook/README.md b/cmake/trailbook/README.md new file mode 100644 index 0000000000..a699afb312 --- /dev/null +++ b/cmake/trailbook/README.md @@ -0,0 +1,137 @@ +# CMake Package trailbook + +This package provides CMake functions and macros to include +the build of a trailbook documentation in a CMake-based project. + +## Usage in CMake + +To use this package in your CMake project, include the following line in your `CMakeLists.txt` file: + +```cmake +find_package( + trailbook + 0.1.0 + REQUIRED + PATHS "${CMAKE_SOURCE_DIR}/" +) +``` + +* Specify the version to make sure you are using +a compatible version of the package. +* If the package is not found, CMake will +stop with an error due to the `REQUIRED` keyword. +* If the package is not installed in a standard +location, you can specify the path to the package using the `PATHS` option. + +After finding the package, you can use the provided functions. +At the moment, the package provides the following functions: + +### `add_trailbook()` + +This function is the initial call for your trailbook documentation. +It can be called as follows: + +```cmake +add_trailbook( + NAME + [STEM_DIRECTORY ] + [REQUIREMENTS_TXT ] + INSTANCE_NAME + [DEPLOYED_DOCS_REPO_URL ] + [DEPLOYED_DOCS_REPO_BRANCH ] +) +``` + +* This function needs to be called once per trailbook. +* The `NAME` argument specifies the name of the trailbook. + This name will be used to create unique target names. +* The optional `STEM_DIRECTORY` argument specifies the + directory containing the Sphinx source files. + If not provided, it defaults to `${CMAKE_CURRENT_SOURCE_DIR}` +* The optional `REQUIREMENTS_TXT` argument specifies the path to a + `requirements.txt` file for Python dependencies. + If not provided, it defaults to `${STEM_DIRECTORY}/requirements.txt`, + if this file exists. + This requirements file will be used to check if the required Python packages are installed and if not to install them, if a + python virtual environment is active +* The `INSTANCE_NAME` argument specifies the name that is used for + the version in the multiversion structure. +* The optional `DEPLOYED_DOCS_REPO_URL` argument specifies the URL of the + repository where the already deployed documentation is located. + It is required if `TRAILBOOK__DOWNLOAD_ALL_VERSIONS` is set to `ON`. +* The optional `DEPLOYED_DOCS_REPO_BRANCH` argument + specifies the branch of the deployed documentation repository. + It defaults to `main` if not provided. + +## Configuring + +There are several options that can be configured +for each trailbook by setting CMake variables. + +### `TRAILBOOK__DOWNLOAD_ALL_VERSIONS` + +* `` should be replaced with the trailbook name provided + in the `add_trailbook()` function call. + +If `TRAILBOOK__DOWNLOAD_ALL_VERSIONS` is set to `ON`, +the build process will attempt to download all previously deployed versions +of the trailbook from the specified repository. And then embed the +new version into the multiversion structure. + +If `TRAILBOOK__DOWNLOAD_ALL_VERSIONS` is set to `OFF` (default), +only the current version of the trailbook will be built. For this +an empty multiversion skeleton will be created. + +This configuration shouldn'T be changed after the first build. + +### `TRAILBOOK__IS_RELEASE` + +* `` should be replaced with the trailbook name provided + in the `add_trailbook()` function call. + +If `TRAILBOOK__IS_RELEASE` is set to `ON` (default), +the trailbook will be built as a release version. This means +that the `latest` version is updated, and the `index.html` and +`404.html` files are updated. + +If `TRAILBOOK__IS_RELEASE` is set to `OFF`, +the mentioned files are not updated, and the `latest` version +is not changed. This can be used for example to build +nightly versions without affecting the released version. + +## Building + +To build the trailbook documentation, simply run the following command, after configuring the project with CMake: + +```bash +cmake --build --target trailbook_ +``` + +* Replace `` with the path to your CMake build directory. +* Replace `` with the name of your trailbook + provided in the `add_trailbook()` function call. + +This target will trigger the full build of the trailbook documentation + +Furthermore, you can use the following additional targets: + +```bash +cmake --build --target trailbook__preview +``` + +This target will start a local server to preview the built documentation. + +```bash +cmake --build --target trailbook__live_preview +``` +This target will start a local server that watches for changes +in the source files and automatically rebuilds the documentation +and refreshes the preview in the browser. + +## How to build a extension for the trailbook package + +The trailbook package provides a set of targets and target properties +that can be used to hook into the build process of the trailbook documentation +and extend it with custom functionality. + +See the full explanation in the [EXTENDING.md](EXTENDING.md) file. diff --git a/cmake/trailbook/add-trailbook.cmake b/cmake/trailbook/add-trailbook.cmake new file mode 100644 index 0000000000..f2c101685f --- /dev/null +++ b/cmake/trailbook/add-trailbook.cmake @@ -0,0 +1,705 @@ + +# This macro is for internal use only +# +# It is used in the function add_trailbook. +# It checks the requirements defined by the requirements.txt file +# and installs any missing packages into the current Python virtual environment. +# It checks during the configuration phase. +macro(_add_trailbook_check_requirements_txt) + if(EXISTS ${args_REQUIREMENTS_TXT}) + execute_process( + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_requirements_txt.py + ${args_REQUIREMENTS_TXT} + --fix-in-venv + RESULT_VARIABLE _CHECK_REQUIREMENTS_TXT_RESULT + ) + + if(NOT _CHECK_REQUIREMENTS_TXT_RESULT EQUAL 0) + message(FATAL_ERROR "Trailbook: ${args_NAME} - ${args_REQUIREMENTS_TXT} not satisfied.") + else() + message(STATUS "Trailbook: ${args_NAME} - ${args_REQUIREMENTS_TXT} satisfied.") + endif() + else() + message(STATUS "Trailbook: ${args_NAME} - No requirements.txt found.") + endif() +endmacro() + +# This macro is for internal use only +# +# It is used in the function add_trailbook. +# It sets up the trailbook build directory where the multiversion HTML docs will be located. +# If TRAILBOOK_INSTANCE_DOWNLOAD_ALL_VERSIONS is ON, it clones the deployed docs repo. +# Otherwise, it creates an empty skeleton directory. +# This configuration is checked during the configuration phase and should not be switched +macro(_add_trailbook_setup_build_directory) + set(CHECK_DONE_FILE_SETUP_BUILD_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/setup_build_directory.check_done") + set(SETUP_BUILD_DIRECTORY_FILE_LIST "${CMAKE_CURRENT_BINARY_DIR}/setup_build_directory_filelist.yaml") + set(DEPLOYED_DOCS_REPO_DIR "${CMAKE_CURRENT_BINARY_DIR}/deployed_docs_repo/") + + if(TRAILBOOK_INSTANCE_DOWNLOAD_ALL_VERSIONS) + if(_SETUP_BUILD_DIRECTORY_LAST_CONFIGURATION STREQUAL "EMPTY_SKELETON") + message(FATAL_ERROR "add_trailbook: Cannot switch between DOWNLOAD_ALL_VERSIONS and EMPTY_SKELETON configurations for trailbook ${args_NAME} without cleaning build directory") + endif() + else() + if(_SETUP_BUILD_DIRECTORY_LAST_CONFIGURATION STREQUAL "DOWNLOAD_ALL_VERSIONS") + message(FATAL_ERROR "add_trailbook: Cannot switch between DOWNLOAD_ALL_VERSIONS and EMPTY_SKELETON configurations for trailbook ${args_NAME} without cleaning build directory") + endif() + endif() + + if(TRAILBOOK_INSTANCE_DOWNLOAD_ALL_VERSIONS) + find_program( + GIT_EXECUTABLE + NAMES git + REQUIRED + ) + + set(CONDITIONAL_DELETE_LATEST_DIR_COMMAND "") + if(TRAILBOOK_INSTANCE_IS_RELEASE) + set(CONDITIONAL_DELETE_LATEST_DIR_COMMAND + COMMAND + ${CMAKE_COMMAND} -E rm -rf + ${CMAKE_CURRENT_BINARY_DIR}/tmp_repo_download/docs/latest + ) + endif() + + add_custom_command( + OUTPUT + ${CHECK_DONE_FILE_SETUP_BUILD_DIRECTORY} + DEPENDS + trailbook_${args_NAME}_stage_prepare_sphinx_source_before + $ + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + COMMENT + "Trailbook: ${args_NAME} - Downloading all versions repo" + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + remove + --data-file ${SETUP_BUILD_DIRECTORY_FILE_LIST} + --root-directory ${DEPLOYED_DOCS_REPO_DIR} + COMMAND + ${GIT_EXECUTABLE} clone + -b ${args_DEPLOYED_DOCS_REPO_BRANCH} + --depth 1 + ${args_DEPLOYED_DOCS_REPO_URL} + ${CMAKE_CURRENT_BINARY_DIR}/tmp_repo_download/ + ${CONDITIONAL_DELETE_LATEST_DIR_COMMAND} + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + create + --data-file ${SETUP_BUILD_DIRECTORY_FILE_LIST} + --root-directory ${CMAKE_CURRENT_BINARY_DIR}/tmp_repo_download + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + move + --data-file ${SETUP_BUILD_DIRECTORY_FILE_LIST} + --root-directory ${CMAKE_CURRENT_BINARY_DIR}/tmp_repo_download + --target-root-directory ${DEPLOYED_DOCS_REPO_DIR}/ + COMMAND + ${CMAKE_COMMAND} -E rm -rf + ${CMAKE_CURRENT_BINARY_DIR}/tmp_repo_download/ + COMMAND + ${CMAKE_COMMAND} -E create_symlink + ${DEPLOYED_DOCS_REPO_DIR}/docs/ + ${TRAILBOOK_BUILD_DIRECTORY} + COMMAND + ${CMAKE_COMMAND} -E touch ${CHECK_DONE_FILE_SETUP_BUILD_DIRECTORY} + ) + set(_SETUP_BUILD_DIRECTORY_LAST_CONFIGURATION "DOWNLOAD_ALL_VERSIONS") + else() + set(CONDITIONAL_CLEANUP_COMMAND "") + add_custom_command( + OUTPUT + ${CHECK_DONE_FILE_SETUP_BUILD_DIRECTORY} + DEPENDS + trailbook_${args_NAME}_stage_prepare_sphinx_source_before + $ + COMMENT + "Trailbook: ${args_NAME} - Creating empty skeleton multiversion root directory" + COMMAND + ${CMAKE_COMMAND} -E make_directory + ${TRAILBOOK_BUILD_DIRECTORY}/ + COMMAND + ${CMAKE_COMMAND} -E touch ${CHECK_DONE_FILE_SETUP_BUILD_DIRECTORY} + ) + set(_SETUP_BUILD_DIRECTORY_LAST_CONFIGURATION "EMPTY_SKELETON") + endif() +endmacro() + +# This macro is for internal use only +# +# It is used in the function add_trailbook. +# It adds a custom command to copy the trailbook stem files to the build directory. +# To be used a base for the tailbook instance source directory. +macro(_add_trailbook_copy_stem_command) + file( + GLOB_RECURSE + STEM_FILES_SOURCE_DIR + CONFIGURE_DEPENDS + "${args_STEM_DIRECTORY}/*" + ) + + set(STEM_FILES_BUILD_DIR "") + foreach(file_path IN LISTS STEM_FILES_SOURCE_DIR) + file(RELATIVE_PATH rel_path "${args_STEM_DIRECTORY}" "${file_path}") + list(APPEND STEM_FILES_BUILD_DIR "${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY}/${rel_path}") + endforeach() + + add_custom_command( + OUTPUT + ${STEM_FILES_BUILD_DIR} + DEPENDS + ${STEM_FILES_SOURCE_DIR} + trailbook_${args_NAME}_stage_prepare_sphinx_source_before + $ + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + COMMENT + "Trailbook: ${args_NAME} - Copying stem files to build directory" + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + remove + --data-file ${CMAKE_CURRENT_BINARY_DIR}/copy_stem_filelist.yaml + --root-directory ${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY} + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + create + --data-file ${CMAKE_CURRENT_BINARY_DIR}/copy_stem_filelist.yaml + --root-directory ${args_STEM_DIRECTORY} + COMMAND + ${CMAKE_COMMAND} -E copy_directory + ${args_STEM_DIRECTORY} + ${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY} + ) +endmacro() + +# This macro is for internal use only +# +# It is used in the function add_trailbook. +# It adds a custom command to create the metadata YAML file for the trailbook instance. +# The metadata YAML file is used by Sphinx during the build process. +# It contains a list of all versions available in the multiversion root directory. +macro(_add_trailbook_create_metadata_yaml_command) + set(METADATA_YAML_FILE "${CMAKE_CURRENT_BINARY_DIR}/metadata_${args_NAME}.yaml") + + add_custom_command( + OUTPUT + ${METADATA_YAML_FILE} + DEPENDS + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/create_metadata_yaml.py + ${STEM_FILES_BUILD_DIR} + ${CHECK_DONE_FILE_SETUP_BUILD_DIRECTORY} + trailbook_${args_NAME}_stage_prepare_sphinx_source_before + $ + COMMENT + "Trailbook: ${args_NAME} - Creating metadata YAML file" + COMMAND + ${CMAKE_COMMAND} -E rm -f ${METADATA_YAML_FILE} + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/create_metadata_yaml.py + --multiversion-root-directory "${TRAILBOOK_BUILD_DIRECTORY}" + "--output-path" "${METADATA_YAML_FILE}" + --additional-version "${args_INSTANCE_NAME}" + ) +endmacro() + +# This macro is for internal use only +# +# It is used in the function add_trailbook. +# It adds a custom command to build the Sphinx HTML documentation for the trailbook instance. +# It builds from the trailbook instance source directory to the trailbook instance build directory. +macro(_add_trailbook_sphinx_build_command) + set(CHECK_DONE_FILE_SPHINX_BUILD_COMMAND "${CMAKE_CURRENT_BINARY_DIR}/build_html.check_done") + + add_custom_command( + OUTPUT + ${CHECK_DONE_FILE_SPHINX_BUILD_COMMAND} + DEPENDS + trailbook_${args_NAME}_stage_build_sphinx_before + $ + ${STEM_FILES_BUILD_DIR} + ${METADATA_YAML_FILE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + COMMENT + "Trailbook: ${args_NAME} - Building HTML documentation with Sphinx" + USES_TERMINAL + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + remove + --data-file ${CMAKE_CURRENT_BINARY_DIR}/sphinx_build_filelist.yaml + --root-directory ${TRAILBOOK_INSTANCE_BUILD_DIRECTORY}/ + COMMAND + EVEREST_METADATA_YAML_PATH=${METADATA_YAML_FILE} + ${_SPHINX_BUILD_EXECUTABLE} + -b html + ${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY} + ${CMAKE_CURRENT_BINARY_DIR}/sphinx_build_temp/ + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + create + --data-file ${CMAKE_CURRENT_BINARY_DIR}/sphinx_build_filelist.yaml + --root-directory ${CMAKE_CURRENT_BINARY_DIR}/sphinx_build_temp/ + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py + move + --data-file ${CMAKE_CURRENT_BINARY_DIR}/sphinx_build_filelist.yaml + --root-directory ${CMAKE_CURRENT_BINARY_DIR}/sphinx_build_temp/ + --target-root-directory ${TRAILBOOK_INSTANCE_BUILD_DIRECTORY}/ + COMMAND + ${CMAKE_COMMAND} -E echo + "Trailbook: ${args_NAME} - HTML documentation built at ${TRAILBOOK_INSTANCE_BUILD_DIRECTORY}" + COMMAND + ${CMAKE_COMMAND} -E touch ${CHECK_DONE_FILE_SPHINX_BUILD_COMMAND} + ) +endmacro() + +# This macro is for internal use only +# +# It is used in the function add_trailbook. +# It adds a custom command to replace the 'latest' copy in the multiversion root directory +# It should be only called if TRAILBOOK_INSTANCE_IS_RELEASE is ON. +macro(_add_trailbook_replace_latest_command) + set(CHECK_DONE_FILE_REPLACE_LATEST "${CMAKE_CURRENT_BINARY_DIR}/replace_latest.check_done") + add_custom_command( + OUTPUT + ${CHECK_DONE_FILE_REPLACE_LATEST} + DEPENDS + trailbook_${args_NAME}_stage_postprocess_sphinx_before + $ + ${CHECK_DONE_FILE_SPHINX_BUILD_COMMAND} + COMMENT + "Trailbook: ${args_NAME} - Replacing 'latest' copy with copy of current instance" + COMMAND + ${CMAKE_COMMAND} -E rm -rf ${TRAILBOOK_BUILD_DIRECTORY}/latest + COMMAND + ${CMAKE_COMMAND} -E copy_directory + ${TRAILBOOK_INSTANCE_BUILD_DIRECTORY} + ${TRAILBOOK_BUILD_DIRECTORY}/latest + COMMAND + ${CMAKE_COMMAND} -E touch ${CHECK_DONE_FILE_REPLACE_LATEST} + ) +endmacro() + +# This macro is for internal use only +# +# It is used in the function add_trailbook. +# It copies the 404.html file from the trailbook instance build directory +# to the multiversion root directory. +# It should only be called if TRAILBOOK_INSTANCE_IS_RELEASE is ON. +macro(_add_trailbook_copy_404_command) + set(CHECK_DONE_FILE_COPY_404 "${CMAKE_CURRENT_BINARY_DIR}/copy_404.check_done") + set(TRAILBOOK_404_FILE "${TRAILBOOK_BUILD_DIRECTORY}/404.html") + set(TRAILBOOK_INSTANCE_404_FILE "${TRAILBOOK_INSTANCE_BUILD_DIRECTORY}/404.html") + add_custom_command( + OUTPUT + ${TRAILBOOK_INSTANCE_404_FILE} + DEPENDS + trailbook_${args_NAME}_stage_postprocess_sphinx_before + $ + ${CHECK_DONE_FILE_SPHINX_BUILD_COMMAND} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_path_exists.py + COMMENT + "Trailbook: ${args_NAME} - Checking for 404.html in built documentation" + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_path_exists.py + --file "${TRAILBOOK_INSTANCE_404_FILE}" + --return-zero-if-exists + ) + add_custom_command( + OUTPUT + ${CHECK_DONE_FILE_COPY_404} + DEPENDS + trailbook_${args_NAME}_stage_postprocess_sphinx_before + $ + ${CHECK_DONE_FILE_SPHINX_BUILD_COMMAND} + ${TRAILBOOK_INSTANCE_404_FILE} + COMMENT + "Trailbook: ${args_NAME} - Copying 404.html to multiversion root directory" + COMMAND + ${CMAKE_COMMAND} -E rm -f ${TRAILBOOK_404_FILE} + COMMAND + ${CMAKE_COMMAND} -E copy + ${TRAILBOOK_INSTANCE_404_FILE} + ${TRAILBOOK_404_FILE} + COMMAND + ${CMAKE_COMMAND} -E touch ${CHECK_DONE_FILE_COPY_404} + ) +endmacro() + +# This macro is for internal use only +# +# It is used in the function add_tailbook. +# It adds a custom command to render the redirect template. The rendered file +# will be used as the index.html in the multiversion root directory. +# This macro should only be called if TRAILBOOK_INSTANCE_IS_RELEASE is ON. +macro(_add_trailbook_render_redirect_template_command) + set(CHECK_DONE_FILE_RENDER_REDIRECT_TEMPLATE "${CMAKE_CURRENT_BINARY_DIR}/render_redirect_template.check_done") + set(REDIRECT_TEMPLATE_FILE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/templates/redirect.html.jinja") + set(TRAILBOOK_REDIRECT_FILE "${TRAILBOOK_BUILD_DIRECTORY}/index.html") + add_custom_command( + OUTPUT + ${CHECK_DONE_FILE_RENDER_REDIRECT_TEMPLATE} + DEPENDS + trailbook_${args_NAME}_stage_postprocess_sphinx_before + $ + ${CHECK_DONE_FILE_SPHINX_BUILD_COMMAND} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/render_redirect_template.py + COMMENT + "Trailbook: ${args_NAME} - Rendering redirect.html from template" + COMMAND + ${CMAKE_COMMAND} -E rm -f ${TRAILBOOK_REDIRECT_FILE} + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/render_redirect_template.py + --redirect-template "${REDIRECT_TEMPLATE_FILE}" + "--target-path" "${TRAILBOOK_REDIRECT_FILE}" + COMMAND + ${CMAKE_COMMAND} -E touch ${CHECK_DONE_FILE_RENDER_REDIRECT_TEMPLATE} + ) +endmacro() + +# This macro is for internal use only +# +# It is used in the function add_trailbook. +# It adds a custom command to copy the versions_index.html file to the multiversion root directory +macro(_add_trailbook_copy_versions_index_command) + set(CHECK_DONE_FILE_COPY_VERSIONS_INDEX "${CMAKE_CURRENT_BINARY_DIR}/copy_versions_index.check_done") + set(TRAILBOOK_VERSIONS_INDEX_FILE "${TRAILBOOK_BUILD_DIRECTORY}/versions_index.html") + set(TRAILBOOK_INSTANCE_VERSIONS_INDEX_FILE "${TRAILBOOK_INSTANCE_BUILD_DIRECTORY}/versions_index.html") + set(CHECK_DONE_FILE_CHECK_LATEST_INSTANCE "${CMAKE_CURRENT_BINARY_DIR}/check_latest_instance.check_done") + add_custom_command( + OUTPUT + ${TRAILBOOK_INSTANCE_VERSIONS_INDEX_FILE} + DEPENDS + trailbook_${args_NAME}_stage_postprocess_sphinx_before + $ + ${CHECK_DONE_FILE_SPHINX_BUILD_COMMAND} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_path_exists.py + COMMENT + "Trailbook: ${args_NAME} - Checking for versions_index.html in built documentation" + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_path_exists.py + --file "${TRAILBOOK_INSTANCE_VERSIONS_INDEX_FILE}" + --return-zero-if-exists + ) + add_custom_command( + OUTPUT + ${CHECK_DONE_FILE_CHECK_LATEST_INSTANCE} + DEPENDS + trailbook_${args_NAME}_stage_postprocess_sphinx_before + $ + ${CHECK_DONE_FILE_SPHINX_BUILD_COMMAND} + ${CHECK_DONE_FILE_REPLACE_LATEST} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_path_exists.py + COMMENT + "Trailbook: ${args_NAME} - Checking for latest/ in multiversion root directory" + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_path_exists.py + --directory ${TRAILBOOK_BUILD_DIRECTORY}/latest + --return-zero-if-exists + COMMAND + ${CMAKE_COMMAND} -E touch ${CHECK_DONE_FILE_CHECK_LATEST_INSTANCE} + ) + add_custom_command( + OUTPUT + ${CHECK_DONE_FILE_COPY_VERSIONS_INDEX} + DEPENDS + trailbook_${args_NAME}_stage_postprocess_sphinx_before + $ + ${CHECK_DONE_FILE_SPHINX_BUILD_COMMAND} + ${TRAILBOOK_INSTANCE_VERSIONS_INDEX_FILE} + ${CHECK_DONE_FILE_CHECK_LATEST_INSTANCE} + COMMENT + "Trailbook: ${args_NAME} - Copying versions_index.html to multiversion root directory" + COMMAND + ${CMAKE_COMMAND} -E rm -f ${TRAILBOOK_VERSIONS_INDEX_FILE} + COMMAND + ${CMAKE_COMMAND} -E copy + ${TRAILBOOK_INSTANCE_VERSIONS_INDEX_FILE} + ${TRAILBOOK_VERSIONS_INDEX_FILE} + COMMAND + ${CMAKE_COMMAND} -E touch ${CHECK_DONE_FILE_COPY_VERSIONS_INDEX} + ) +endmacro() + +# This macro is for internal use only +# +# It is used in the function add_tailbook. +# It adds a custom target to serve the built HTML documentation via a simple HTTP server. +macro(_add_trailbook_preview_target) + add_custom_target( + trailbook_${args_NAME}_preview + DEPENDS + trailbook_${args_NAME} + COMMENT + "Trailbook: ${args_NAME} - Serve HTML documentation" + USES_TERMINAL + COMMAND + ${CMAKE_COMMAND} -E echo + "Trailbook: ${args_NAME} - Serving HTML output at http://localhost:8000/" + COMMAND + ${Python3_EXECUTABLE} -m http.server --directory ${TRAILBOOK_BUILD_DIRECTORY} 8000 + ) +endmacro() + +# This macro is for internal use only +# +# It is used in the function add_tailbook. +# It adds a custom target to watch the trailbook instance target for changes +# and automatically rebuild the HTML documentation with Sphinx and serve it. +macro(_add_trailbook_live_preview_target) + add_custom_target( + trailbook_${args_NAME}_live_preview + DEPENDS + trailbook_${args_NAME} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/target_observer.py + COMMENT + "Trailbook: ${args_NAME} - Auto-build HTML documentation with Sphinx and serve" + USES_TERMINAL + COMMAND + ${CMAKE_COMMAND} -E echo + "Trailbook: ${args_NAME} - Auto-building HTML output and serving at http://localhost:8000/" + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/target_observer.py + "trailbook_${args_NAME}" + "trailbook_${args_NAME}_preview" + --build-dir ${CMAKE_BINARY_DIR} + --interval-ms 2000 + ) +endmacro() + +# This is the main function to add a trailbook to the build system. +# It sets up the necessary build commands and targets +# to build the trailbook documentation. +# It takes the following parameters: +# NAME (required): The name of the trailbook. +# STEM_DIRECTORY (optional): The directory containing the trailbook stem files. +# Defaults to CMAKE_CURRENT_SOURCE_DIR. +# REQUIREMENTS_TXT (optional): The path to the requirements.txt file. +# Defaults to CMAKE_CURRENT_SOURCE_DIR/requirements.txt if exists. +# INSTANCE_NAME (required): The instance name for the trailbook. +# Needs to be lowercase alphanumeric and underscores only. +# DEPLOYED_DOCS_REPO_URL (optional): The URL of the deployed docs repository. +# Required if TRAILBOOK__DOWNLOAD_ALL_VERSIONS is ON. +# DEPLOYED_DOCS_REPO_BRANCH (optional): The branch of the deployed docs repository. +# Defaults to 'main'. +# Usage: +# add_trailbook( +# NAME +# [STEM_DIRECTORY ] +# [REQUIREMENTS_TXT ] +# INSTANCE_NAME +# [DEPLOYED_DOCS_REPO_URL ] +# [DEPLOYED_DOCS_REPO_BRANCH ] +# ) +function(add_trailbook) + set(options) + set(one_value_args + NAME + STEM_DIRECTORY + REQUIREMENTS_TXT + INSTANCE_NAME + DEPLOYED_DOCS_REPO_URL + DEPLOYED_DOCS_REPO_BRANCH + ) + set(multi_value_args) + cmake_parse_arguments( + "args" + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN} + ) + + option(TRAILBOOK_${args_NAME}_DOWNLOAD_ALL_VERSIONS "Download all versions for trailbook ${args_NAME} and build complete trailbook" OFF) + option(TRAILBOOK_${args_NAME}_IS_RELEASE "If enabled, the trailbook ${args_NAME} will be marked as release version in versions index" ON) + if(NOT TRAILBOOK_${args_NAME}_DOWNLOAD_ALL_VERSIONS AND NOT TRAILBOOK_${args_NAME}_IS_RELEASE) + message(FATAL_ERROR "add_trailbook: TRAILBOOK_${args_NAME}_DOWNLOAD_ALL_VERSIONS and TRAILBOOK_${args_NAME}_IS_RELEASE cannot both be OFF") + endif() + + + # Parameter NAME + # is required + if("${args_NAME}" STREQUAL "") + message(FATAL_ERROR "add_trailbook: NAME argument is required") + endif() + + # Parameter STEM_DIRECTORY + # - defaults to CMAKE_CURRENT_SOURCE_DIR + # - needs to be absolute path + if("${args_STEM_DIRECTORY}" STREQUAL "") + set(args_STEM_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + if(NOT IS_ABSOLUTE "${args_STEM_DIRECTORY}") + message(FATAL_ERROR "add_trailbook: STEM_DIRECTORY needs to be an absolute path") + endif() + cmake_path(SET args_STEM_DIRECTORY NORMALIZE ${args_STEM_DIRECTORY}) + + # Parameter REQUIREMENTS_TXT + # - defaults to ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt if exists + # - needs to be absolute path if set + if("${args_REQUIREMENTS_TXT}" STREQUAL "") + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt") + set(args_REQUIREMENTS_TXT "${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt") + endif() + endif() + if(NOT "${args_REQUIREMENTS_TXT}" STREQUAL "") + if(NOT IS_ABSOLUTE "${args_REQUIREMENTS_TXT}") + message(FATAL_ERROR "add_trailbook: REQUIREMENTS_TXT needs to be an absolute path") + endif() + if(NOT EXISTS "${args_REQUIREMENTS_TXT}") + message(FATAL_ERROR "add_trailbook: REQUIREMENTS_TXT file does not exist: ${args_REQUIREMENTS_TXT}") + endif() + endif() + + # Parameter INSTANCE_NAME + # - required + # - needs to be lowercase alphanumeric and underscores only + if("${args_INSTANCE_NAME}" STREQUAL "") + message(FATAL_ERROR "add_trailbook: INSTANCE_NAME argument is required") + endif() + string(REGEX MATCH "^[a-z0-9_]+$" _valid_instance_name "${args_INSTANCE_NAME}") + if("${_valid_instance_name}" STREQUAL "") + message(FATAL_ERROR "add_trailbook: INSTANCE_NAME needs to be lowercase alphanumeric and underscores only") + endif() + + # Parameter DEPLOYED_DOCS_REPO_URL + # - required if TRAILBOOK__DOWNLOAD_ALL_VERSIONS is ON + if(TRAILBOOK_${args_NAME}_DOWNLOAD_ALL_VERSIONS AND "${args_DEPLOYED_DOCS_REPO_URL}" STREQUAL "") + message(FATAL_ERROR "add_trailbook: DEPLOYED_DOCS_REPO_URL argument is required if TRAILBOOK_${args_NAME}_DOWNLOAD_ALL_VERSIONS is ON") + endif() + + # Parameter DEPLOYED_DOCS_REPO_BRANCH + # - defaults to 'main' + if("${args_DEPLOYED_DOCS_REPO_BRANCH}" STREQUAL "") + set(args_DEPLOYED_DOCS_REPO_BRANCH "main") + endif() + + set(TRAILBOOK_INSTANCE_SOURCE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/trailbook_${args_NAME}_source") + set(TRAILBOOK_BUILD_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/trailbook_${args_NAME}_build") + set(TRAILBOOK_INSTANCE_BUILD_DIRECTORY "${TRAILBOOK_BUILD_DIRECTORY}/${args_INSTANCE_NAME}") + set(TRAILBOOK_INSTANCE_IS_RELEASE "${TRAILBOOK_${args_NAME}_IS_RELEASE}") + set(TRAILBOOK_INSTANCE_DOWNLOAD_ALL_VERSIONS "${TRAILBOOK_${args_NAME}_DOWNLOAD_ALL_VERSIONS}") + + message(STATUS "Adding trailbook: ${args_NAME}") + message(STATUS " Stem directory: ${args_STEM_DIRECTORY}") + message(STATUS " Build directory: ${TRAILBOOK_BUILD_DIRECTORY}") + message(STATUS " Instance source directory: ${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY}") + message(STATUS " Instance build directory: ${TRAILBOOK_INSTANCE_BUILD_DIRECTORY}") + if(NOT "${args_REQUIREMENTS_TXT}" STREQUAL "") + message(STATUS " Requirements.txt: ${args_REQUIREMENTS_TXT}") + else() + message(STATUS " Requirements.txt: ") + endif() + message(STATUS " Deployed docs repo url: ${args_DEPLOYED_DOCS_REPO_URL}") + message(STATUS " Deployed docs repo branch: ${args_DEPLOYED_DOCS_REPO_BRANCH}") + + _add_trailbook_check_requirements_txt() + + add_custom_target( + trailbook_${args_NAME}_stage_prepare_sphinx_source_before + DEPENDS + $ + ) + + _add_trailbook_setup_build_directory() + _add_trailbook_copy_stem_command() + _add_trailbook_create_metadata_yaml_command() + set(DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER + trailbook_${args_NAME}_stage_prepare_sphinx_source_before + ${CHECK_DONE_FILE_SETUP_BUILD_DIRECTORY} + ${STEM_FILES_BUILD_DIR} + ${METADATA_YAML_FILE} + ) + add_custom_target( + trailbook_${args_NAME}_stage_prepare_sphinx_source_after + DEPENDS + ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER} + COMMENT + "Prepare Sphinx source for trailbook: ${args_NAME}" + ) + add_custom_target( + trailbook_${args_NAME}_stage_build_sphinx_before + DEPENDS + $ + trailbook_${args_NAME}_stage_prepare_sphinx_source_after + ) + _add_trailbook_sphinx_build_command() + set(DEPS_STAGE_BUILD_SPHINX_AFTER + trailbook_${args_NAME}_stage_build_sphinx_before + ${CHECK_DONE_FILE_SPHINX_BUILD_COMMAND} + ) + add_custom_target( + trailbook_${args_NAME}_stage_build_sphinx_after + DEPENDS + ${DEPS_STAGE_BUILD_SPHINX_AFTER} + COMMENT + "Build Sphinx documentation for trailbook: ${args_NAME}" + ) + add_custom_target( + trailbook_${args_NAME}_stage_postprocess_sphinx_before + DEPENDS + $ + trailbook_${args_NAME}_stage_build_sphinx_after + ) + if(TRAILBOOK_INSTANCE_IS_RELEASE) + _add_trailbook_replace_latest_command() + _add_trailbook_copy_404_command() + _add_trailbook_render_redirect_template_command() + endif() + _add_trailbook_copy_versions_index_command() + + set(DEPS_STAGE_POSTPROCESS_SPHINX_AFTER + trailbook_${args_NAME}_stage_postprocess_sphinx_before + ${CHECK_DONE_FILE_REPLACE_LATEST} + ${CHECK_DONE_FILE_COPY_404} + ${CHECK_DONE_FILE_COPY_VERSIONS_INDEX} + ${CHECK_DONE_FILE_RENDER_REDIRECT_TEMPLATE} + ) + add_custom_target( + trailbook_${args_NAME}_stage_postprocess_sphinx_after + DEPENDS + ${DEPS_STAGE_POSTPROCESS_SPHINX_AFTER} + COMMENT + "Post-process Sphinx documentation for trailbook: ${args_NAME}" + ) + add_custom_target( + trailbook_${args_NAME} ALL + DEPENDS + trailbook_${args_NAME}_stage_postprocess_sphinx_after + COMMENT + "Build trailbook: ${args_NAME}" + ) + + _add_trailbook_preview_target() + _add_trailbook_live_preview_target() + + set_target_properties( + trailbook_${args_NAME} + PROPERTIES + TRAILBOOK_INSTANCE_BUILD_DIRECTORY "${TRAILBOOK_INSTANCE_BUILD_DIRECTORY}" + TRAILBOOK_BUILD_DIRECTORY "${TRAILBOOK_BUILD_DIRECTORY}" + TRAILBOOK_INSTANCE_NAME "${args_INSTANCE_NAME}" + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY "${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY}" + TRAILBOOK_CURRENT_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" + ADDITIONAL_DEPS_STAGE_PREPARE_SPHINX_SOURCE_BEFORE "" + ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE "" + ADDITIONAL_DEPS_STAGE_POSTPROCESS_SPHINX_BEFORE "" + DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER "${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER}" + DEPS_STAGE_BUILD_SPHINX_AFTER "${DEPS_STAGE_BUILD_SPHINX_AFTER}" + DEPS_STAGE_POSTPROCESS_SPHINX_AFTER "${DEPS_STAGE_POSTPROCESS_SPHINX_AFTER}" + ) +endfunction() diff --git a/cmake/trailbook/check_path_exists.py b/cmake/trailbook/check_path_exists.py new file mode 100755 index 0000000000..0bb7190848 --- /dev/null +++ b/cmake/trailbook/check_path_exists.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright Pionix GmbH and Contributors to EVerest +# +""" +author: andreas.heinrich@pionix.de +This script checks whether a directory exists or not and returns zero based on the flags provided. +""" + + +import argparse +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser(description='Checks whether a directory exists or not and returns zero based on the flags provided') + parser.add_argument( + '--directory', + type=Path, + dest='directory', + action='store', + required=False, + help='Directory to check for existence' + ) + parser.add_argument( + '--file', + type=Path, + dest='file', + action='store', + required=False, + help='Path to a file to check for existence' + ) + parser.add_argument( + '--return-zero-if-exists', + action='store_true', + help='Return zero if the file/directory exists', + dest='return_zero_if_exists', + ) + parser.add_argument( + '--return-zero-if-not-exists', + action='store_true', + help='Return zero if the file/directory does not exist', + dest='return_zero_if_not_exists', + ) + args = parser.parse_args() + + if not args.directory and not args.file: + raise ValueError("Either --directory or --file must be specified") + if args.return_zero_if_exists and args.return_zero_if_not_exists: + raise ValueError("Cannot use both --return-zero-if-exists and --return-zero-if-not-exists at the same time") + + if args.file: + if not args.file.is_absolute(): + raise ValueError("File path must be absolute") + if args.return_zero_if_exists: + if not args.file.exists(): + print(f"❌ File does not exist at {args.file}") + exit(1) + if not args.file.is_file(): + print(f"❌ Path exists but is not a file at {args.file}") + exit(2) + print(f"✅ File exists at {args.file}") + exit(0) + elif args.return_zero_if_not_exists: + if args.file.is_file(): + print(f"❌ File exists at {args.file}") + exit(1) + if args.file.exists(): + print(f"❌ Path exists but is not a file at {args.file}") + exit(2) + print(f"✅ File does not exist at {args.file}") + exit(0) + else: + raise ValueError("Either --return-zero-if-exists or --return-zero-if-not-exists must be specified") + else: + if not args.directory.is_absolute(): + raise ValueError("Directory path must be absolute") + if args.return_zero_if_exists: + if not args.directory.exists(): + print(f"❌ Directory does not exist at {args.directory}") + exit(1) + if not args.directory.is_dir(): + print(f"❌ Path exists but is not a directory at {args.directory}") + exit(2) + print(f"✅ Directory exists at {args.directory}") + exit(0) + elif args.return_zero_if_not_exists: + if args.directory.is_dir(): + print(f"❌ Directory exists at {args.directory}") + exit(1) + if args.directory.exists(): + print(f"❌ Path exists but is not a directory at {args.directory}") + exit(2) + print(f"✅ Directory does not exist at {args.directory}") + exit(0) + else: + raise ValueError("Either --return-zero-if-exists or --return-zero-if-not-exists must be specified") + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Error: {e}") + exit(1) diff --git a/cmake/trailbook/check_requirements_txt.py b/cmake/trailbook/check_requirements_txt.py new file mode 100755 index 0000000000..74afdf9057 --- /dev/null +++ b/cmake/trailbook/check_requirements_txt.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright Pionix GmbH and Contributors to EVerest +# +""" +author: andreas.heinrich@pionix.de +This script checks whether the packages in a requirements.txt are satisfied. +If run inside a virtual environment, it can optionally fix unmet requirements by running pip install -r. +""" + + +import argparse +import sys +from importlib.metadata import version, PackageNotFoundError +import re +import subprocess + + +def parse_requirement(req_line: str): + req_line = req_line.strip() + if not req_line or req_line.startswith("#"): + return None + match = re.match(r"([a-zA-Z0-9_\-]+)==([0-9\.]+)", req_line) + if match: + return match.groups() + return (req_line, None) + + +def check_requirements(file_path: str, fix_in_venv: bool = False): + errors = [] + with open(file_path, "r") as f: + for line in f: + parsed = parse_requirement(line) + if not parsed: + continue + pkg, req_version = parsed + try: + installed_version = version(pkg) + if req_version and installed_version != req_version: + errors.append(f"{pkg}=={req_version} (installed: {installed_version})") + except PackageNotFoundError: + errors.append(f"{pkg}=={req_version or 'any version'} (not installed)") + + if fix_in_venv and errors: + if sys.prefix != sys.base_prefix: + print(f"Attempting to fix requirements in the current venv: {sys.prefix}") + subprocess.run([sys.executable, "-m", "pip", "install", "-r", file_path], check=True) + return check_requirements(file_path, fix_in_venv=False) + else: + print("Not in a virtual environment. Cannot fix requirements automatically.") + + if not errors: + print("✅ All requirements are met.") + else: + print("❌ There are unmet requirements:") + for e in errors: + print(" ", e) + sys.exit(1) + + +def main(): + parser = argparse.ArgumentParser(description="Checks if the packages in a requirements.txt are satisfied.") + parser.add_argument("requirements_file", type=str, help="Path to the requirements.txt") + parser.add_argument("--fix-in-venv", action="store_true", help="Run pip install -r in the current venv if there are unmet requirements") + args = parser.parse_args() + check_requirements(args.requirements_file, args.fix_in_venv) + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Error: {e}") + exit(1) diff --git a/cmake/trailbook/create_metadata_yaml.py b/cmake/trailbook/create_metadata_yaml.py new file mode 100755 index 0000000000..1230c1dd8b --- /dev/null +++ b/cmake/trailbook/create_metadata_yaml.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright Pionix GmbH and Contributors to EVerest +# +""" +author: andreas.heinrich@pionix.de +This script creates a trailbook_metadata.yaml file +based on the versions found in the multiversion root directory. +""" + + +import argparse +from pathlib import Path +import yaml + + +def main(): + parser = argparse.ArgumentParser(description='Creates a trailbook_metadata.yaml file') + + parser.add_argument( + '--multiversion-root-directory', + type=Path, + dest='multiversion_root_dir', + action='store', + required=True, + help='Path to the root directory of the multiversion documentation' + ) + parser.add_argument( + '--output-path', + type=Path, + dest='output_path', + action='store', + required=True, + help='Path where the trailbook_metadata.yaml file will be created' + ) + parser.add_argument( + '--additional-version', + type=str, + dest='additional_versions', + action='append', + default=[], + help='Additional version to include in the metadata (can be used multiple times)' + ) + args = parser.parse_args() + + if not args.multiversion_root_dir.is_absolute(): + raise ValueError("Multiversion root directory must be absolute") + if not args.multiversion_root_dir.is_dir(): + print(f"\033[33mWarning: {args.multiversion_root_dir} does not exist or is not a directory, it is treated as an empty multiversion root dir\033[0m") + if not args.output_path.is_absolute(): + raise ValueError("Output path must be absolute") + if args.output_path.exists(): + raise FileExistsError("Output path already exists") + + versions_list = [] + if args.multiversion_root_dir.is_dir(): + for instance_dir in args.multiversion_root_dir.iterdir(): + if not instance_dir.is_dir(): + continue + if not (instance_dir / 'index.html').is_file(): + continue + versions_list.append(instance_dir.name) + versions_list.extend(args.additional_versions) + versions_list = list(set(versions_list)) + if len(versions_list) == 0: + raise ValueError("No versions found in the specified multiversion root directory") + versions_list.sort() + + # create yaml content + data = { + 'versions': versions_list + } + # render yaml content + with args.output_path.open('w') as f: + yaml.dump(data, f, default_flow_style=False) + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Error: {e}") + exit(1) diff --git a/cmake/trailbook/filelist_manager.py b/cmake/trailbook/filelist_manager.py new file mode 100755 index 0000000000..e08c05db0d --- /dev/null +++ b/cmake/trailbook/filelist_manager.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright Pionix GmbH and Contributors to EVerest +# +""" +author: andreas.heinrich@pionix.de +This script provides command to manage a list of file paths +It can be used for custom cmake commands to track created files and directories +and later remove or move them. +""" + + +import argparse +from pathlib import Path +import yaml + + +def create_filelist(args): + if not args.root_dir.exists(): + raise ValueError("Root directory does not exist") + if not args.root_dir.is_dir(): + raise ValueError("Root directory must be a directory") + + if args.data_file.exists(): + raise FileExistsError("Data file already exists") + + file_paths = [] + directory_paths = [] + for item in args.root_dir.rglob('*'): + relative_path = item.relative_to(args.root_dir) + if item.is_dir(): + directory_paths.append(str(relative_path)) + elif item.is_file(): + file_paths.append(str(relative_path)) + else: + raise ValueError(f"Unknown file type: {item}") + + data = { + 'files': file_paths, + 'directories': directory_paths + } + + args.data_file.parent.mkdir(parents=True, exist_ok=True) + with args.data_file.open('w') as f: + yaml.dump(data, f) + exit(0) + + +def remove_filelist(args): + if not args.data_file.exists(): + exit(0) + if not args.data_file.is_file(): + raise ValueError("Data file path is not a file") + + with args.data_file.open('r') as f: + data = yaml.safe_load(f) + + for file_path in data.get('files', []): + full_path = args.root_dir / file_path + if not full_path.exists(): + raise FileNotFoundError(f"File does not exist: {full_path}") + if not full_path.is_file(): + raise ValueError(f"Path is not a file: {full_path}") + full_path.unlink() + + for dir_path in data.get('directories', []): + full_path = args.root_dir / dir_path + if not full_path.exists(): + raise FileNotFoundError(f"Directory does not exist: {full_path}") + if not full_path.is_dir(): + raise ValueError(f"Path is not a directory: {full_path}") + + if len(list(full_path.iterdir())) > 0: + continue + + full_path.rmdir() + + args.data_file.unlink() + + exit(0) + + +def move_filelist(args): + if not args.root_dir.exists(): + raise ValueError("Root directory does not exist") + if not args.root_dir.is_dir(): + raise ValueError("Root directory must be a directory") + + if not args.data_file.exists(): + raise FileNotFoundError("Data file does not exist") + if not args.data_file.is_file(): + raise ValueError("Data file path is not a file") + + if not args.target_root_dir.is_absolute(): + raise ValueError("Target root directory must be absolute") + if args.target_root_dir.exists(): + if not args.target_root_dir.is_dir(): + raise ValueError("Target root directory must be a directory") + + with args.data_file.open('r') as f: + data = yaml.safe_load(f) + + for file_path in data.get('files', []): + source_file = args.root_dir / file_path + target_file = args.target_root_dir / file_path + target_file.parent.mkdir(parents=True, exist_ok=True) + source_file.rename(target_file) + + for dir_path in data.get('directories', []): + source_dir = args.root_dir / dir_path + target_dir = args.target_root_dir / dir_path + if not target_dir.exists(): + source_dir.rename(target_dir) + exit(0) + + +def main(): + parser = argparse.ArgumentParser(description='This script provides command to manage a list of file paths') + + subparsers = parser.add_subparsers() + + create_parser = subparsers.add_parser( + "create", + description="Creates the file with a list of all paths in it", + add_help=True, + ) + create_parser.add_argument( + '--data-file', + type=Path, + dest='data_file', + action='store', + required=True, + help='File to read/write from/to filelist' + ) + create_parser.add_argument( + '--root-directory', + type=Path, + dest='root_dir', + action='store', + required=True, + help='Path to the directory to list' + ) + create_parser.set_defaults( + action_handler=create_filelist + ) + + remove_parser = subparsers.add_parser( + "remove", + description="Removes all files and directories listed in the filelist", + add_help=True, + ) + remove_parser.add_argument( + '--data-file', + type=Path, + dest='data_file', + action='store', + required=True, + help='File to read/write from/to filelist' + ) + remove_parser.add_argument( + '--root-directory', + type=Path, + dest='root_dir', + action='store', + required=True, + help='Path to the directory to list' + ) + remove_parser.set_defaults( + action_handler=remove_filelist + ) + + move_parser = subparsers.add_parser( + "move", + description="Moves all files and directories listed in the filelist to a new root directory", + add_help=True, + ) + move_parser.add_argument( + '--data-file', + type=Path, + dest='data_file', + action='store', + required=True, + help='File to read/write from/to filelist' + ) + move_parser.add_argument( + '--root-directory', + type=Path, + dest='root_dir', + action='store', + required=True, + help='Path to the directory to list' + ) + move_parser.add_argument( + '--target-root-directory', + type=Path, + dest='target_root_dir', + action='store', + required=True, + help='Path to the target root directory to move files to' + ) + move_parser.set_defaults( + action_handler=move_filelist + ) + + args = parser.parse_args() + + if not args.root_dir.is_absolute(): + raise ValueError("Root directory must be absolute") + + if not args.data_file.is_absolute(): + raise ValueError("Data file path must be absolute") + + if 'action_handler' not in args: + raise ValueError("No action specified") + + args.action_handler(args) + exit(0) + + +if __name__ == "__main__": + main() diff --git a/cmake/trailbook/render_redirect_template.py b/cmake/trailbook/render_redirect_template.py new file mode 100755 index 0000000000..c00089b8f9 --- /dev/null +++ b/cmake/trailbook/render_redirect_template.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright Pionix GmbH and Contributors to EVerest +# +""" +author: andreas.heinrich@pionix.de +This script processes a redirect template and generates a .html file +""" + + +import argparse +import jinja2 +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser(description='Process versions_index.html.jinja and place redirect.html in the output directory') + parser.add_argument( + '--redirect-template', + type=Path, + dest='redirect_template', + action='store', + required=True, + help="Redirect jinja template file" + ) + parser.add_argument( + '--target-path', + type=Path, + dest='target_path', + action='store', + required=True, + help="Target path for the output" + ) + parser.add_argument( + '--latest-release-name', + type=str, + dest='latest_release_name', + action='store', + default="latest", + help="Name of the latest release" + ) + args = parser.parse_args() + + if not args.redirect_template.is_absolute(): + raise ValueError("Redirect template path must be absolute") + if not args.redirect_template.exists(): + raise FileNotFoundError( + "Redirect template path: '" + + str(args.redirect_template) + + "' doesn't exist" + ) + if not args.redirect_template.is_file(): + raise FileNotFoundError( + f"Redirect template path: '{args.redirect_template}' is not a file" + ) + + template_dir = args.redirect_template.parent + template_name = args.redirect_template.name + + env = jinja2.Environment( + loader=jinja2.FileSystemLoader(template_dir), + trim_blocks=True, + lstrip_blocks=True + ) + + template = env.get_template(template_name) + output = template.render( + latest_release=args.latest_release_name + ) + args.target_path.write_text(output) + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"Error: {e}") + exit(1) diff --git a/cmake/trailbook/setup-trailbook.cmake b/cmake/trailbook/setup-trailbook.cmake new file mode 100644 index 0000000000..9463608c76 --- /dev/null +++ b/cmake/trailbook/setup-trailbook.cmake @@ -0,0 +1,46 @@ +# Internal macro to find the sphinx-build executable. +macro(_find_sphinx_build) + find_program( + _SPHINX_BUILD_EXECUTABLE + NAMES + "${Python3_EXECUTABLE} -m sphinx.cmd.build" + "sphinx-build" + DOC "Path to the sphinx-build executable" + OPTIONAL + PATHS + $ENV{VIRTUAL_ENV}/bin + ) +endmacro() + +# Internal macro to find sphinx-build, and if not found, try to install it in an active python venv. +macro(_find_and_fix_sphinx_build) + _find_sphinx_build() + + if("${_SPHINX_BUILD_EXECUTABLE}" STREQUAL "_SPHINX_BUILD_EXECUTABLE-NOTFOUND") + ev_is_python_venv_active( + RESULT_VAR IS_PYTHON_VENV_ACTIVE + ) + if(IS_PYTHON_VENV_ACTIVE) + message(STATUS "sphinx-build executable not found in system, but python venv is active. Trying to use 'python3 -m pip install sphinx'.") + execute_process( + COMMAND ${Python3_EXECUTABLE} -m pip install sphinx + ) + _find_sphinx_build() + endif() + endif() + + if("${_SPHINX_BUILD_EXECUTABLE}" STREQUAL "_SPHINX_BUILD_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "sphinx-build executable not found. Please install Sphinx. You can install it via pip: pip install sphinx") + endif() + + message(STATUS "Found sphinx-build: ${_SPHINX_BUILD_EXECUTABLE}") +endmacro() + +# Internal macro to set up the trailbook environment. +macro(_setup_trailbook) + if(NOT _TRAILBOOK_SETUP_DONE) + _find_and_fix_sphinx_build() + + set(_TRAILBOOK_SETUP_DONE TRUE) + endif() +endmacro() diff --git a/cmake/trailbook/target_observer.py b/cmake/trailbook/target_observer.py new file mode 100755 index 0000000000..fa407556cf --- /dev/null +++ b/cmake/trailbook/target_observer.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright Pionix GmbH and Contributors to EVerest +# +""" +author: andreas.heinrich@pionix.de +This script starts a CMake target http server and triggers +regular rebuilds of a specified CMake target upon changes. +""" + + +import subprocess +import sys +import time +import argparse +import signal +from pathlib import Path +from rich.live import Live +from rich.console import Console +from rich.panel import Panel +from rich.layout import Layout +from threading import Thread + + +console = Console() + + +def run_target(build_dir: Path, target: str, live_panel, panel_size: int) -> None: + process = subprocess.Popen( + [ + "cmake", + "--build", build_dir.as_posix(), + "--target", target + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True + ) + output_lines = [] + output_lines.append(f"Running target {target} at {time.strftime('%X')}\n") + for line in iter(process.stdout.readline, ""): + line = line.rstrip() + output_lines.append(line) + output_lines = output_lines[-panel_size:] + live_panel.update(Panel("\n".join(output_lines), title=f"{target} output")) + process.wait() + + +def start_server(build_dir: Path, server_target: str, server_lines: list, live_panel, panel_size: int) -> subprocess.Popen: + print(f"Starting server target {server_target}...") + process = subprocess.Popen( + [ + "cmake", + "--build", str(build_dir), + "--target", server_target + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + + def read_server_output(): + for line in iter(process.stdout.readline, ""): + line = line.rstrip() + server_lines.append(line) + server_lines[:] = server_lines[-panel_size:] + live_panel.update(Panel("\n".join(server_lines), title=f"{server_target} output")) + + t = Thread(target=read_server_output, daemon=True) + t.start() + return process + + +def stop_server(proc: subprocess.Popen) -> None: + if proc and proc.poll() is None: + print("Stopping server...") + proc.send_signal(signal.SIGINT) + try: + proc.wait(timeout=5) + except subprocess.TimeoutExpired: + proc.kill() + + +def main(): + parser = argparse.ArgumentParser(description="Watch CMake target and manage server target") + parser.add_argument("watch_target", help="CMake target to monitor and rerun if changed") + parser.add_argument("server_target", help="CMake target that runs the server") + parser.add_argument("--build-dir", default="build", help="CMake build directory") + parser.add_argument("--interval-ms", type=int, default=2000, help="Check interval in milliseconds") + args = parser.parse_args() + + build_dir = Path(args.build_dir) + watch_target = args.watch_target + server_target = args.server_target + + panel_size = 10 + layout = Layout() + layout.split_column( + Layout(name="server", size=panel_size+2), + Layout(name="watch", size=panel_size+2) + ) + with Live(layout, console=console, refresh_per_second=1): + server_lines = [] + server_lines.append("Starting server...") + server_panel = Panel("\n".join(server_lines), title=f"{server_target} output") + layout["server"].update(server_panel) + watch_panel = Panel("\n\n\n", title=f"{watch_target} output") + layout["watch"].update(watch_panel) + + server_proc = start_server(build_dir, server_target, server_lines, layout["server"], panel_size) + try: + while True: + time.sleep(args.interval_ms / 1000) + run_target(build_dir, watch_target, layout["watch"], panel_size) + except KeyboardInterrupt: + stop_server(server_proc) + print("\n Exiting.") + + +if __name__ == "__main__": + main() diff --git a/cmake/trailbook/templates/redirect.html.jinja b/cmake/trailbook/templates/redirect.html.jinja new file mode 100644 index 0000000000..4a9a012881 --- /dev/null +++ b/cmake/trailbook/templates/redirect.html.jinja @@ -0,0 +1,11 @@ + + + + + Redirecting... + + + + + + diff --git a/cmake/trailbook/trailbook-config-version.cmake b/cmake/trailbook/trailbook-config-version.cmake new file mode 100644 index 0000000000..cf62352471 --- /dev/null +++ b/cmake/trailbook/trailbook-config-version.cmake @@ -0,0 +1,13 @@ +set(PACKAGE_VERSION 0.1.0) + +if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) +elseif(PACKAGE_FIND_VERSION_MAJOR STREQUAL "0") + if(PACKAGE_FIND_VERSION_MINOR GREATER "1") + set(PACKAGE_VERSION_UNSUITABLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + endif() +else() + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/cmake/trailbook/trailbook-config.cmake b/cmake/trailbook/trailbook-config.cmake new file mode 100644 index 0000000000..be54a385eb --- /dev/null +++ b/cmake/trailbook/trailbook-config.cmake @@ -0,0 +1,5 @@ +include("${CMAKE_CURRENT_LIST_DIR}/setup-trailbook.cmake") +_setup_trailbook() + + +include("${CMAKE_CURRENT_LIST_DIR}/add-trailbook.cmake") diff --git a/dependencies.yaml b/dependencies.yaml index 7218b674e3..a0772abcb5 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -86,7 +86,7 @@ Josev: git: https://github.com/EVerest/ext-switchev-iso15118.git git_tag: c31c4e7f8004dc63cec28ad113bd059cbabbe321 cmake_condition: "EVEREST_ENABLE_PY_SUPPORT AND EVEREST_DEPENDENCY_ENABLED_JOSEV" -# everest-testing and ev-dev-tools +# everest-testing and ev-dev-tools and scripts everest-utils: git: https://github.com/EVerest/everest-utils.git git_tag: v0.7.3 diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 0000000000..fefb7c96a5 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,32 @@ +find_package( + trailbook + 0.1.0 + REQUIRED + PATHS "${CMAKE_SOURCE_DIR}/cmake" +) +find_package( + trailbook-ext-everest + 0.1.0 + REQUIRED + PATHS "${CMAKE_SOURCE_DIR}/cmake" +) + +add_trailbook( + NAME "everest" + STEM_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/source + INSTANCE_NAME "local" + DEPLOYED_DOCS_REPO_URL "https://github.com/everest/everest.github.io" + DEPLOYED_DOCS_REPO_BRANCH "main" +) + +get_target_property( + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY + trailbook_everest + TRAILBOOK_INSTANCE_SOURCE_DIRECTORY +) +get_filename_component(EVEREST_WORKSPACE_DIRECTORY ${CMAKE_SOURCE_DIR} DIRECTORY) +trailbook_ev_create_snapshot( + EVEREST_WORKSPACE_DIRECTORY "${EVEREST_WORKSPACE_DIRECTORY}" + TRAILBOOK_NAME "everest" + OUTPUT_FILE "${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY}/reference/assets/snapshot.yaml" +) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..6b630447f4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,85 @@ +# trailbook: EVerest + +The EVerest documentation uses the CMake package `trailbook` +to build and manage its documentation. +Additional it uses the extension package `trailbook-ext-everest` + +## Configure CMake + +There are three CMake variables you need to know about. + +### `EVEREST_BUILD_DOCS` + +The CMake variable `EVEREST_BUILD_DOCS` enables or disables the building of the +EVerest documentation. It is disabled by default. To enable it, set the variable to `ON` when configuring CMake: + +```bash +cmake -D EVEREST_BUILD_DOCS=ON +``` + +### `TRAILBOOK_everest_DOWNLOAD_ALL_VERSIONS` + +The CMake variable `TRAILBOOK_everest_DOWNLOAD_ALL_VERSIONS` controls whether +all available versions of the EVerest documentation are downloaded during the build process. +By default, this variable is set to `OFF`, meaning an empty multiversion skeleton is created +during the build. + +To enable the downloading of all available versions, set the variable to `ON` when configuring CMake: + +```bash +cmake -D EVEREST_BUILD_DOCS=ON -D TRAILBOOK_everest_DOWNLOAD_ALL_VERSIONS=ON +``` + +With this the `everest.github.io` repository cloned and the new built documentation is embedded into +the exisiting multiversion structure. + +### `TRAILBOOK_everest_IS_RELEASE` + +If you don't want to deploy the documentation you probably don't need to care about this variable. + +The CMake variable `TRAILBOOK_everest_IS_RELEASE` indicates whether the current build is a release build. It defaults to `ON` which means that files in the root of the multiversion structure +as `index.html` and `404.html` become updated. Additionally the `latest` symlink is updated to point to the current version. + +In case of the need to build the documentation as nightly for example the variable can +be set to `OFF` when configuring CMake: + +```bash +cmake -D EVEREST_BUILD_DOCS=ON -D TRAILBOOK_everest_DOWNLOAD_ALL_VERSIONS=ON -D TRAILBOOK_everest_IS_RELEASE=OFF +``` + +## Build + +There are three targets available to work with the EVerest documentation: + +```bash +cmake --build --target trailbook_everest +``` +Builds the EVerest documentation. + +```bash +cmake --build --target trailbook_everest_preview +``` +Builds the EVerest documentation and serves it with a local web server +for previewing. + +```bash +cmake --build --target trailbook_everest_live_preview +``` +Builds the EVerest documentation and serves it with a local web server +for previewing. Additionally it watches for changes in the source files +and automatically rebuilds the documentation and refreshes the preview +in the browser. + +## How things work + +The trailbook is initialized in `${CMAKE_SOURCE_DIR}/docs/CMakeLists.txt` +with the `add_trailbook()` function call. + +At the same file an edm snapshot yaml file is added to the documentation by +calling the function `trailbook_ev_create_snapshot()`. + +Module explanations, modules references, type references and interface references are added automatically in `${CMAKE_SOURCE_DIR}/cmake/everest-generate.cmake`, by calling the functions +* `trailbook_ev_generate_rst_from_manifest()`, +* `trailbook_ev_generate_rst_from_types()`, +* `trailbook_ev_generate_rst_from_interface()`, and +* `trailbook_ev_add_module_explanation()`. diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000000..76aa4e718d --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,14 @@ +# Main Sphinx library +sphinx + +# The Furo theme +furo + +# Provides modern design elements like grids, cards, and tabs +sphinx-design + +# Adds a "copy" button to code blocks for a better user experience +sphinx-copybutton + +# Required for processing templates +PyYAML diff --git a/docs/source/404.rst b/docs/source/404.rst new file mode 100644 index 0000000000..5ef56d9c22 --- /dev/null +++ b/docs/source/404.rst @@ -0,0 +1,13 @@ +:orphan: + +########################################## +Page not found +########################################## + +Your request could not be processed because the page you requested does not exist. + +.. note:: + + Since the 2023.1.0 release of the documentation. + The structure of the documentation has changed. + You can find the latest build here: https://everest.github.io/latest diff --git a/docs/source/_ext/staticpages/LICENSE b/docs/source/_ext/staticpages/LICENSE new file mode 100644 index 0000000000..2407d3f070 --- /dev/null +++ b/docs/source/_ext/staticpages/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Manuel Kaufmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/source/_ext/staticpages/__init__.py b/docs/source/_ext/staticpages/__init__.py new file mode 100644 index 0000000000..d3ec452c31 --- /dev/null +++ b/docs/source/_ext/staticpages/__init__.py @@ -0,0 +1 @@ +__version__ = "0.2.0" diff --git a/docs/source/_ext/staticpages/extension.py b/docs/source/_ext/staticpages/extension.py new file mode 100644 index 0000000000..740e945e62 --- /dev/null +++ b/docs/source/_ext/staticpages/extension.py @@ -0,0 +1,329 @@ +import docutils +import os +import sphinx +import warnings + +from sphinx.environment.collectors import EnvironmentCollector +from sphinx.errors import ExtensionError + +from . import __version__ +from .utils import replace_uris + + +class BaseURIError(ExtensionError): + """Exception for malformed base URI.""" + pass + + +# https://www.sphinx-doc.org/en/stable/extdev/appapi.html#event-html-collect-pages +def html_collect_pages(app): + """ + Create for each ```` a html page. + + Uses ``