Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,105 @@
# SPDX-FileCopyrightText: 2003-2018 University of Illinois at Urbana-Champaign.
#
# SPDX-License-Identifier: BSD-3-Clause
#[=======================================================================[.rst:
CheckAtomic
-----------
Check if the compiler supports std:atomic out of the box or if libatomic is
needed for atomic support. If it is needed, ``HAVE_CXX_ATOMICS_WITH_LIB``
or ``HAVE_CXX_ATOMICS64_WITH_LIB`` set to ``ON``.
Since 5.75.0.
#]=======================================================================]
include(CheckCXXSourceCompiles)
include(CheckLibraryExists)
# Sometimes linking against libatomic is required for atomic ops, if
# the platform doesn't support lock-free atomics.
function(check_working_cxx_atomics varname)
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11")
check_cxx_source_compiles("
#include <atomic>
std::atomic<int> x;
std::atomic<short> y;
std::atomic<char> z;
int main() {
++z;
++y;
return ++x;
}
" ${varname})
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
endfunction()
function(check_working_cxx_atomics64 varname)
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}")
check_cxx_source_compiles("
#include <atomic>
#include <cstdint>
std::atomic<uint64_t> x (0);
int main() {
uint64_t i = x.load(std::memory_order_relaxed);
x.is_lock_free();
return 0;
}
" ${varname})
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
endfunction()
# Check for (non-64-bit) atomic operations.
if(MSVC)
set(HAVE_CXX_ATOMICS_WITHOUT_LIB True)
else()
# First check if atomics work without the library.
if (CMAKE_COMPILER_IS_GNUCXX
OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang"
OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"
OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel")
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB)
endif()
# If not, check if the library exists, and atomics work with it.
if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC)
if(HAVE_LIBATOMIC)
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB)
if (NOT HAVE_CXX_ATOMICS_WITH_LIB)
message(FATAL_ERROR "Host compiler must support std::atomic!")
endif()
else()
message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.")
endif()
endif()
endif()
# Check for 64 bit atomic operations.
if(MSVC)
set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True)
else()
# First check if atomics work without the library.
if (CMAKE_COMPILER_IS_GNUCXX
OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang"
OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"
OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel")
check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB)
endif()
# If not, check if the library exists, and atomics work with it.
if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64)
if(HAVE_CXX_LIBATOMICS64)
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB)
if (NOT HAVE_CXX_ATOMICS64_WITH_LIB)
message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!")
endif()
else()
message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.")
endif()
endif()
endif()

View File

@@ -0,0 +1,24 @@
list(APPEND AUX_DIRS interfaces)
foreach(IT IN LISTS AUX_DIRS)
set(IT_DST "${CMAKE_INSTALL_PREFIX}/${IT}")
if(IS_DIRECTORY ${IT_DST} AND NOT IS_SYMLINK ${IT_DST})
message(
FATAL_ERROR "\
I won't be able create the symlink, because the link target ${IT_DST} \
already exists and I don't want to delete it. Probably you executed \
the INSTALL target already without symlinks. Please remove the \
directory manually.\
"
)
endif()
install(
CODE "
execute_process(COMMAND cmake -E create_symlink
${CMAKE_CURRENT_SOURCE_DIR}/${IT}
${IT_DST})
"
)
endforeach()

View File

@@ -0,0 +1,20 @@
set(MODULE_LINK_PATH "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}")
if(IS_DIRECTORY ${MODULE_LINK_PATH} AND NOT IS_SYMLINK ${MODULE_LINK_PATH})
message(
FATAL_ERROR "\
I won't be able create the symlink, because the link target \
${MODULE_LINK_PATH} already exists and I don't want to delete \
it. Probably you executed the INSTALL target already without \
symlinks. Please remove the directory manually.\
"
)
endif()
install(
CODE "
execute_process(COMMAND cmake -E create_symlink
${CMAKE_CURRENT_SOURCE_DIR}
${MODULE_LINK_PATH})
"
)

View File

@@ -0,0 +1,2 @@
exports_files(["logging.ini"])

View File

@@ -0,0 +1,24 @@
# for documentation on this file format see:
# https://www.boost.org/doc/libs/1_54_0/libs/log/doc/html/log/detailed/utilities.html#log.detailed.utilities.setup.filter_formatter
[Core]
DisableLogging=false
# To get debug logs of only one module, add the "%Process% contains" filter, e.g.:
#
# "(%Process% contains OCPP201 and %Severity% >= DEBG)"
#
# whereas "OCPP201" is the value of the field `active_modules.NAME.module` in the respective /config/config-*.yaml.
Filter="%Severity% >= INFO"
[Sinks.Console]
Destination=Console
# Filter="%Target% contains \"MySink1\""
Format="%TimeStamp% [%Severity%] \033[1;32m%Process%\033[0m \033[1;36m%function%\033[0m \033[1;30m%file%:\033[0m\033[1;32m%line%\033[0m: %Message%"
Asynchronous=false
AutoFlush=true
SeverityStringColorDebug="\033[1;30m"
SeverityStringColorInfo="\033[1;37m"
SeverityStringColorWarning="\033[1;33m"
SeverityStringColorError="\033[1;31m"
SeverityStringColorCritical="\033[1;35m"

View File

@@ -0,0 +1,17 @@
# In the following a volume is created to contain the nodered config, this is done to
# allow starting the nodered container from inside a devcontainer
# Create docker volume to contain nodered config
docker volume create everest-nodered-config-volume
# Create temporarily container to copy nodered config into the created volume
docker run --name everest-nodered-config-container -v everest-nodered-config-volume:/data debian:12-slim chown -R 1000:1000 /data
# Copy nodered config to the created volume with the temporarily created container
docker cp @FLOW_FILE@ everest-nodered-config-container:/data/flows.json
# Remove temporarily container
docker rm everest-nodered-config-container
# Start nodered container with the volume mounted to /data
docker run -it --rm --network host --name everest_nodered --mount type=volume,source=everest-nodered-config-volume,target=/data ghcr.io/everest/everest-dev-environment/nodered:docker-images-v0.1.0

View File

@@ -0,0 +1,7 @@
LD_LIBRARY_PATH=@LD_LIBRARY_VAR@:$LD_LIBRARY_PATH \
PATH=@PATH_VAR@:$PATH \
manager \
--prefix @CMAKE_INSTALL_PREFIX@ \
--conf @CONFIG_FILE@ \
@ADDITIONAL_ARGUMENTS@
$@

View File

@@ -0,0 +1,2 @@
#!/bin/bash
@HELPER_PREFIX@/run_tmux_helper.sh @CONFIG_FILE@ @CMAKE_INSTALL_PREFIX@

View File

@@ -0,0 +1,25 @@
# compatibility for Boost >= 1.89 while keeping backwards compatibility with existing code
if(Boost_VERSION_STRING VERSION_LESS "1.69.0")
# Boost.System is a header only library only from 1.69.0
find_package(Boost
COMPONENTS
system
REQUIRED
)
endif()
if(Boost_VERSION_STRING VERSION_GREATER_EQUAL "1.89.0")
# starting from Boost 1.89.0 the included compatibility layer is removed
# here we re-introduce a very simplified version of it
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/compatibility/boost_systemConfig.cmake"
${CMAKE_BINARY_DIR}/boost_system/boost_systemConfig.cmake
COPYONLY
)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/compatibility/boost_systemConfigVersion.cmake"
${CMAKE_BINARY_DIR}/boost_system/boost_systemConfigVersion.cmake
COPYONLY
)
set(boost_system_DIR ${CMAKE_BINARY_DIR}/boost_system CACHE PATH "")
endif()

View File

@@ -0,0 +1,4 @@
if(NOT TARGET Boost::system)
add_library(everest_boost_system_target INTERFACE)
add_library(Boost::system ALIAS everest_boost_system_target)
endif()

View File

@@ -0,0 +1,4 @@
set(boost_system_VERSION "${Boost_VERSION_STRING}")
set(PACKAGE_VERSION "${Boost_VERSION_STRING}")
set(PACKAGE_VERSION_COMPATIBLE TRUE)
set(PACKAGE_VERSION_EXACT TRUE)

View File

@@ -0,0 +1,37 @@
function(generate_nodered_run_script)
if (NOT EVEREST_ENABLE_RUN_SCRIPT_GENERATION)
return ()
endif ()
set(options "")
set(one_value_args
FLOW
OUTPUT
)
set(multi_value_args "")
cmake_parse_arguments(OPTNS "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})
if (OPTNS_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} got unknown argument(s): ${OPTNS_UNPARSED_ARGUMENTS}")
endif()
if (NOT OPTNS_FLOW)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} requires FLOW parameter for the flow name")
endif()
set(FLOW_FILE "${CMAKE_CURRENT_SOURCE_DIR}/config-${OPTNS_FLOW}-flow.json")
if (NOT EXISTS ${FLOW_FILE})
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}: flow file '${FLOW_FILE}' does not exist")
endif()
set(SCRIPT_OUTPUT_PATH "${CMAKE_BINARY_DIR}/run-scripts")
set(SCRIPT_OUTPUT_FILE "${SCRIPT_OUTPUT_PATH}/nodered-${OPTNS_FLOW}.sh")
if (OPTNS_OUTPUT)
set(SCRIPT_OUTPUT_FILE "${SCRIPT_OUTPUT_PATH}/nodered-${OPTNS_OUTPUT}.sh")
endif()
configure_file("${EVEREST_CONFIG_ASSET_DIR}/run_nodered_template.sh.in" ${SCRIPT_OUTPUT_FILE})
endfunction()

View File

@@ -0,0 +1,61 @@
set(EVEREST_CONFIG_ASSET_DIR "${CMAKE_CURRENT_LIST_DIR}/assets" CACHE INTERNAL "")
function(generate_config_run_script)
if (NOT EVEREST_ENABLE_RUN_SCRIPT_GENERATION)
return ()
endif ()
set(options "")
set(one_value_args
CONFIG
LOGGING_CONFIG
OUTPUT
)
set(multi_value_args
ADDITIONAL_ARGUMENTS
)
cmake_parse_arguments(OPTNS "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})
if (OPTNS_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} got unknown argument(s): ${OPTNS_UNPARSED_ARGUMENTS}")
endif()
if (NOT OPTNS_CONFIG)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} requires CONFIG parameter for the config name")
endif()
set(CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/config-${OPTNS_CONFIG}.yaml")
if (NOT EXISTS ${CONFIG_FILE})
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}: config file '${CONFIG_FILE}' does not exist")
endif()
set(LOGGING_CONFIG_FILE "${EVEREST_CONFIG_ASSET_DIR}/logging.ini")
if (OPTNS_LOGGING_CONFIG)
set(LOGGING_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${OPTNS_LOGGING_CONFIG}.ini")
endif()
if (NOT EXISTS ${LOGGING_CONFIG_FILE})
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}: logging config file '${LOGGING_CONFIG_FILE}' does not exist")
endif()
foreach (ARG ${OPTNS_ADDITIONAL_ARGUMENTS})
string(APPEND ADDITIONAL_ARGUMENTS "${ARG} ")
endforeach()
string(APPEND ADDITIONAL_ARGUMENTS "\\")
set(SCRIPT_OUTPUT_PATH "${CMAKE_BINARY_DIR}/run-scripts")
set(SCRIPT_OUTPUT_FILE "${SCRIPT_OUTPUT_PATH}/run-${OPTNS_CONFIG}.sh")
if (OPTNS_OUTPUT)
set(SCRIPT_OUTPUT_FILE "${SCRIPT_OUTPUT_PATH}/run-${OPTNS_OUTPUT}.sh")
endif()
# other necessary variables
set(LD_LIBRARY_VAR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
set(PATH_VAR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}")
configure_file("${EVEREST_CONFIG_ASSET_DIR}/run_template.sh.in" ${SCRIPT_OUTPUT_FILE})
endfunction()

View File

@@ -0,0 +1,60 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
set(EVEREST_CONFIG_ASSET_DIR "${CMAKE_CURRENT_LIST_DIR}/assets" CACHE INTERNAL "")
function(generate_config_tmux_run_script)
if (NOT EVEREST_ENABLE_RUN_SCRIPT_GENERATION)
return ()
endif ()
set(options "")
set(one_value_args
CONFIG
LOGGING_CONFIG
OUTPUT
)
set(multi_value_args
ADDITIONAL_ARGUMENTS
)
cmake_parse_arguments(OPTNS "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})
if (OPTNS_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} got unknown argument(s): ${OPTNS_UNPARSED_ARGUMENTS}")
endif()
if (NOT OPTNS_CONFIG)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} requires CONFIG parameter for the config name")
endif()
set(HELPER_PREFIX ${CMAKE_INSTALL_PREFIX}/etc/everest)
set(CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/config-${OPTNS_CONFIG}.yaml")
if (NOT EXISTS ${CONFIG_FILE})
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}: config file '${CONFIG_FILE}' does not exist")
endif()
set(LOGGING_CONFIG_FILE "${EVEREST_CONFIG_ASSET_DIR}/logging.ini")
if (OPTNS_LOGGING_CONFIG)
set(LOGGING_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${OPTNS_LOGGING_CONFIG}.ini")
endif()
if (NOT EXISTS ${LOGGING_CONFIG_FILE})
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}: logging config file '${LOGGING_CONFIG_FILE}' does not exist")
endif()
set(SCRIPT_OUTPUT_PATH "${CMAKE_BINARY_DIR}/run-scripts")
set(SCRIPT_OUTPUT_FILE "${SCRIPT_OUTPUT_PATH}/run-tmux-${OPTNS_CONFIG}.sh")
if (OPTNS_OUTPUT)
set(SCRIPT_OUTPUT_FILE "${SCRIPT_OUTPUT_PATH}/run-tmux-${OPTNS_OUTPUT}.sh")
endif()
# other necessary variables
set(LD_LIBRARY_VAR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
set(PATH_VAR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}")
configure_file("${EVEREST_CONFIG_ASSET_DIR}/run_tmux_template.sh.in" ${SCRIPT_OUTPUT_FILE})
endfunction()

View File

@@ -0,0 +1,101 @@
function(setup_ev_cli)
if(NOT TARGET ev-cli)
add_custom_target(ev-cli)
endif()
if(NOT ${${PROJECT_NAME}_USE_PYTHON_VENV})
message(STATUS "Using system ev-cli instead of installing it in the build venv.")
find_program(EV_CLI ev-cli REQUIRED)
else()
ev_is_python_venv_active(
RESULT_VAR IS_PYTHON_VENV_ACTIVE
)
if(NOT ${IS_PYTHON_VENV_ACTIVE})
message(FATAL_ERROR "Python venv is not active. Please activate the python venv before running this command.")
endif()
get_target_property(SOURCE_DIRECTORY ev_pip_package_ev-dev-tools SOURCE_DIRECTORY)
message(STATUS "Installing ev-cli from: ${SOURCE_DIRECTORY}")
ev_pip_install_local(
PACKAGE_NAME "ev-dev-tools"
PACKAGE_SOURCE_DIRECTORY "${SOURCE_DIRECTORY}"
)
unset(EV_CLI CACHE)
find_program(EV_CLI ev-cli HINTS ${EV_ACTIVATE_PYTHON_VENV_PATH_TO_VENV}/bin REQUIRED)
message(STATUS "Using ev-cli from: ${EV_CLI}")
endif()
get_property(EVEREST_REQUIRED_EV_CLI_VERSION
GLOBAL
PROPERTY EVEREST_REQUIRED_EV_CLI_VERSION
)
require_ev_cli_version(${EVEREST_REQUIRED_EV_CLI_VERSION})
set_ev_cli_template_properties()
endfunction()
function(require_ev_cli_version EV_CLI_VERSION_REQUIRED)
execute_process(
COMMAND ${EV_CLI} --version
OUTPUT_VARIABLE EV_CLI_VERSION_FULL
OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REPLACE "ev-cli " "" EV_CLI_VERSION "${EV_CLI_VERSION_FULL}")
if ("${EV_CLI_VERSION}" STREQUAL "")
message(FATAL_ERROR "Could not determine a ev-cli version from the provided version '${EV_CLI_VERSION_FULL}'")
endif()
if("${EV_CLI_VERSION}" VERSION_GREATER_EQUAL "${EV_CLI_VERSION_REQUIRED}")
message("Found ev-cli version '${EV_CLI_VERSION}' which satisfies the requirement of ev-cli version '${EV_CLI_VERSION_REQUIRED}'")
else()
message(FATAL_ERROR "ev-cli version ${EV_CLI_VERSION_REQUIRED} or higher is required. However your ev-cli version is '${EV_CLI_VERSION}'. Please upgrade ev-cli.")
endif()
endfunction()
function(set_ev_cli_template_properties)
message(STATUS "Setting template properties for ev-cli target")
get_target_property(EVEREST_SCHEMA_DIR generate_cpp_files EVEREST_SCHEMA_DIR)
execute_process(
COMMAND ${EV_CLI} interface get-templates --separator=\; --schemas-dir "${EVEREST_SCHEMA_DIR}"
OUTPUT_VARIABLE INTERFACE_TEMPLATES
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE
INTERFACE_TEMPLATES_RESULT
)
if(INTERFACE_TEMPLATES_RESULT)
message(FATAL_ERROR "Could not get interface templates from ev-cli.")
endif()
execute_process(
COMMAND ${EV_CLI} module get-templates --separator=\; --schemas-dir "${EVEREST_SCHEMA_DIR}"
OUTPUT_VARIABLE MODULE_TEMPLATES
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE
MODULE_TEMPLATES_RESULT
)
if(MODULE_TEMPLATES_RESULT)
message(FATAL_ERROR "Could not get module loader templates from ev-cli.")
endif()
execute_process(
COMMAND ${EV_CLI} types get-templates --separator=\; --schemas-dir "${EVEREST_SCHEMA_DIR}"
OUTPUT_VARIABLE TYPES_TEMPLATES
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE
TYPES_TEMPLATES_RESULT
)
if(TYPES_TEMPLATES_RESULT)
message(FATAL_ERROR "Could not get module loader templates from ev-cli.")
endif()
set_target_properties(ev-cli
PROPERTIES
INTERFACE_TEMPLATES "${INTERFACE_TEMPLATES}"
MODULE_TEMPLATES "${MODULE_TEMPLATES}"
TYPES_TEMPLATES "${TYPES_TEMPLATES}"
)
endfunction()

View File

@@ -0,0 +1,103 @@
function(_everest_exclude_modules_loop EVEREST_INTERNAL_DEPENDENT_MODULES_LIST OUTPUT_VARIABLE)
# iterate over the provided list of dependent modules and check if all of these modules are included in EVEREST_EXCLUDE_MODULES
set(EVEREST_INTERNAL_DEPENDENCY_EXCLUDE_COUNT 0)
foreach(EVEREST_INTERNAL_DEPENDENT_MODULE IN LISTS EVEREST_INTERNAL_DEPENDENT_MODULES_LIST)
if("${EVEREST_INTERNAL_DEPENDENT_MODULE}" IN_LIST EVEREST_EXCLUDE_MODULES)
# a dependent module is excluded, exclude the dependency
math(EXPR EVEREST_INTERNAL_DEPENDENCY_EXCLUDE_COUNT "${EVEREST_INTERNAL_DEPENDENCY_EXCLUDE_COUNT}+1")
endif()
endforeach()
list(LENGTH EVEREST_EXCLUDE_MODULES EVEREST_INTERNAL_EXCLUDE_MODULES_LEN)
if(EVEREST_INTERNAL_DEPENDENCY_EXCLUDE_COUNT GREATER 0 AND EVEREST_INTERNAL_DEPENDENCY_EXCLUDE_COUNT EQUAL EVEREST_INTERNAL_EXCLUDE_MODULES_LEN)
# all modules that need this dependency are excluded
set("${OUTPUT_VARIABLE}" OFF PARENT_SCOPE)
endif()
endfunction()
function(_everest_include_modules_loop EVEREST_INTERNAL_DEPENDENT_MODULES_LIST OUTPUT_VARIABLE)
# iterate over the provided list of dependent modules and check if one of these modules is included in EVEREST_INCLUDE_MODULES
foreach(EVEREST_INTERNAL_DEPENDENT_MODULE IN LISTS EVEREST_INTERNAL_DEPENDENT_MODULES_LIST)
if("${EVEREST_INTERNAL_DEPENDENT_MODULE}" IN_LIST EVEREST_INCLUDE_MODULES)
# a dependent module is being build, include the dependency
set("${OUTPUT_VARIABLE}" ON PARENT_SCOPE)
endif()
endforeach()
endfunction()
function(ev_define_dependency)
#
# handle passed arguments
#
set(options "")
set(one_value_args
DEPENDENCY_NAME
OUTPUT_VARIABLE_SUFFIX
)
set(multi_value_args
DEPENDENT_MODULES_LIST
)
cmake_parse_arguments(OPTNS "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})
if (OPTNS_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} got unknown argument(s): ${OPTNS_UNPARSED_ARGUMENTS}")
endif()
if (NOT OPTNS_DEPENDENCY_NAME)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} requires DEPENDENCY_NAME parameter for the dependency name")
endif()
set(DEPENDENCY_NAME "${OPTNS_DEPENDENCY_NAME}")
string(TOUPPER "${OPTNS_DEPENDENCY_NAME}" EVEREST_INTERNAL_DEPENDENCY_NAME_UPPER)
if (NOT OPTNS_DEPENDENT_MODULES_LIST)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} requires DEPENDENT_MODULES_LIST parameter for the dependent modules list")
endif()
set(EVEREST_INTERNAL_DEPENDENT_MODULES_LIST "${OPTNS_DEPENDENT_MODULES_LIST}")
list(LENGTH EVEREST_INTERNAL_DEPENDENT_MODULES_LIST EVEREST_INTERNAL_DEPENDENT_MODULES_LIST_LEN)
if (NOT OPTNS_OUTPUT_VARIABLE_SUFFIX)
# use the uppercase dependency name
set(EVEREST_INTERNAL_DEPENDENCY_CONDITION "EVEREST_DEPENDENCY_ENABLED_${EVEREST_INTERNAL_DEPENDENCY_NAME_UPPER}")
else()
# set output variable suffix
set(EVEREST_INTERNAL_DEPENDENCY_CONDITION "EVEREST_DEPENDENCY_ENABLED_${OPTNS_OUTPUT_VARIABLE_SUFFIX}")
endif()
#
# handle the various dependency conditions
#
if(DEFINED "${EVEREST_INTERNAL_DEPENDENCY_CONDITION}")
# always enable a externally set dependency
message(STATUS "${EVEREST_INTERNAL_DEPENDENCY_CONDITION} externally set to: ${${EVEREST_INTERNAL_DEPENDENCY_CONDITION}}")
else()
# disable dependency by default
set("${EVEREST_INTERNAL_DEPENDENCY_CONDITION}" OFF)
# find out if dependency is excluded or uncluded
if(NOT "${DEPENDENCY_NAME}" IN_LIST EVEREST_EXCLUDE_DEPENDENCIES)
if(NOT EVEREST_INCLUDE_MODULES)
# all modules are being build, at the moment we cannot know which dependencies are needed
set("${EVEREST_INTERNAL_DEPENDENCY_CONDITION}" ON)
# find out if all modules that need this dependency are excluded
_everest_exclude_modules_loop("${EVEREST_INTERNAL_DEPENDENT_MODULES_LIST}" "${EVEREST_INTERNAL_DEPENDENCY_CONDITION}")
else()
# EVEREST_INCLUDE_MODULES takes precendece over EVEREST_EXCLUDE_MODULES in everest-generate as well, reflect this here
_everest_include_modules_loop("${EVEREST_INTERNAL_DEPENDENT_MODULES_LIST}" "${EVEREST_INTERNAL_DEPENDENCY_CONDITION}")
endif()
else()
# dependency disabled because it is listed in EVEREST_EXCLUDE_DEPENDENCIES
set("${EVEREST_INTERNAL_DEPENDENCY_CONDITION}" OFF)
endif()
endif()
# log the result
if(NOT ${EVEREST_INTERNAL_DEPENDENCY_CONDITION})
message(STATUS "Dependency ${DEPENDENCY_NAME} NOT enabled:")
else()
message(STATUS "Dependency ${DEPENDENCY_NAME} enabled:")
endif()
message(STATUS " ${EVEREST_INTERNAL_DEPENDENCY_CONDITION}=${${EVEREST_INTERNAL_DEPENDENCY_CONDITION}}")
# propagate the result to the parent scope:
set("${EVEREST_INTERNAL_DEPENDENCY_CONDITION}" "${${EVEREST_INTERNAL_DEPENDENCY_CONDITION}}" PARENT_SCOPE)
endfunction()

View File

@@ -0,0 +1,140 @@
#
# Selective library inclusion for everest-core.
#
# When EVEREST_INCLUDE_LIBS is set (allowlist mode), only listed libraries
# and their transitive internal dependencies are built.
# When EVEREST_EXCLUDE_LIBS is set (blocklist mode), listed libraries are skipped.
# When neither is set, all libraries are built (default / backwards-compatible).
#
# Usage from an external project via EDM/CPM:
# options:
# - "EVEREST_LIBS_ONLY ON"
# - "EVEREST_INCLUDE_LIBS log;util;io"
#
# Adding a new library:
# 1. Create lib/everest/<name>/ with its CMakeLists.txt
# 2. Add <name> to EVEREST_LIB_SUBDIRS below
# 3. If it depends on other everest-core libraries, add:
# set(EVEREST_LIB_DEPS_<name> "dep1;dep2")
#
# --- Master list of library subdirectories ---
# This is the single source of truth for which libraries exist under lib/everest/.
# Used by both the build-variable computation here and lib/everest/CMakeLists.txt.
set(EVEREST_LIB_SUBDIRS
can_dpm1000
cbv2g
conversions
crc
everest_api_module_helpers
everest_api_types
evse_security
external_energy_limits
framework
fsm
helpers
ieee2030_1_1
io
iso15118
log
ocpp
run_application
slac
sqlite
timer
tls
util
yaml
)
# --- Internal dependency map ---
# Each variable lists the everest-core libraries that the key library depends on.
# Only internal (lib/everest/) dependencies are tracked here; external deps
# (Boost, OpenSSL, etc.) are handled by each library's own CMakeLists.txt.
# Libraries with no internal deps do not need an entry.
# Tier 1
set(EVEREST_LIB_DEPS_yaml "log")
set(EVEREST_LIB_DEPS_io "util")
set(EVEREST_LIB_DEPS_run_application "log")
set(EVEREST_LIB_DEPS_evse_security "cbv2g")
# Tier 2
set(EVEREST_LIB_DEPS_ocpp "log;timer;evse_security;sqlite")
set(EVEREST_LIB_DEPS_iso15118 "cbv2g")
set(EVEREST_LIB_DEPS_ieee2030_1_1 "framework")
# Tier 3 (framework-coupled)
set(EVEREST_LIB_DEPS_tls "util;evse_security;framework")
set(EVEREST_LIB_DEPS_helpers "tls;framework")
set(EVEREST_LIB_DEPS_external_energy_limits "framework")
set(EVEREST_LIB_DEPS_everest_api_types "")
set(EVEREST_LIB_DEPS_conversions "framework;evse_security")
set(EVEREST_LIB_DEPS_slac "tls")
# --- Transitive dependency resolver ---
# Given a list of library names, computes the full transitive closure
# of internal dependencies and stores the result in OUTPUT_VAR.
function(_ev_resolve_lib_transitive_deps INPUT_LIBS OUTPUT_VAR)
set(_resolved "${INPUT_LIBS}")
set(_frontier "${INPUT_LIBS}")
while(_frontier)
set(_next_frontier "")
foreach(_lib IN LISTS _frontier)
if(DEFINED EVEREST_LIB_DEPS_${_lib})
foreach(_dep IN LISTS EVEREST_LIB_DEPS_${_lib})
if(NOT _dep IN_LIST _resolved)
list(APPEND _resolved "${_dep}")
list(APPEND _next_frontier "${_dep}")
endif()
endforeach()
endif()
endforeach()
set(_frontier "${_next_frontier}")
endwhile()
set(${OUTPUT_VAR} "${_resolved}" PARENT_SCOPE)
endfunction()
# --- Resolve the library set once at configure time ---
if(EVEREST_INCLUDE_LIBS)
_ev_resolve_lib_transitive_deps("${EVEREST_INCLUDE_LIBS}" EVEREST_RESOLVED_LIBS)
message(STATUS "EVEREST_INCLUDE_LIBS set. Requested: ${EVEREST_INCLUDE_LIBS}")
message(STATUS " Resolved (with transitive deps): ${EVEREST_RESOLVED_LIBS}")
elseif(EVEREST_EXCLUDE_LIBS)
message(STATUS "EVEREST_EXCLUDE_LIBS set: ${EVEREST_EXCLUDE_LIBS}")
endif()
# Set per-library build variables (used by cmake_condition in dependencies.yaml)
# When EVEREST_INCLUDE_LIBS is empty these are all ON (build everything).
# Includes the conditional libraries (gpio, system) that have additional guards.
set(_EVEREST_ALL_LIBS ${EVEREST_LIB_SUBDIRS} gpio system)
foreach(_lib IN LISTS _EVEREST_ALL_LIBS)
if(EVEREST_INCLUDE_LIBS)
if(_lib IN_LIST EVEREST_RESOLVED_LIBS)
set(EVEREST_BUILD_LIB_${_lib} ON)
else()
set(EVEREST_BUILD_LIB_${_lib} OFF)
endif()
elseif(EVEREST_EXCLUDE_LIBS)
if(_lib IN_LIST EVEREST_EXCLUDE_LIBS)
set(EVEREST_BUILD_LIB_${_lib} OFF)
else()
set(EVEREST_BUILD_LIB_${_lib} ON)
endif()
else()
set(EVEREST_BUILD_LIB_${_lib} ON)
endif()
endforeach()
# --- Query function ---
# ev_should_build_lib(<lib_name> <output_var>)
# Sets <output_var> to ON or OFF in the caller's scope.
function(ev_should_build_lib LIB_NAME OUTPUT_VAR)
if(DEFINED EVEREST_BUILD_LIB_${LIB_NAME})
set(${OUTPUT_VAR} "${EVEREST_BUILD_LIB_${LIB_NAME}}" PARENT_SCOPE)
else()
# Unknown library name — default to ON for forwards compatibility
set(${OUTPUT_VAR} ON PARENT_SCOPE)
endif()
endfunction()

View File

@@ -0,0 +1,14 @@
set_property(
GLOBAL
PROPERTY EVEREST_REQUIRED_EV_CLI_VERSION "0.6.2"
)
# FIXME (aw): clean up this inclusion chain
include(${CMAKE_CURRENT_LIST_DIR}/ev-cli.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/config-run-script.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/config-run-nodered-script.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/config-tmux-run-script.cmake)
# source generate scripts / setup
include(${CMAKE_CURRENT_LIST_DIR}/ev-targets.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/everest-generate.cmake)

View File

@@ -0,0 +1,47 @@
include_guard(GLOBAL)
add_custom_target(everest_targets)
set_target_properties(everest_targets
PROPERTIES
LIBRARIES ""
MODULES ""
TESTS ""
)
function(_ev_register_target TYPE NAME)
if (NOT TARGET ${NAME})
if(EVEREST_LIBS_ONLY OR EVEREST_INCLUDE_LIBS OR EVEREST_EXCLUDE_LIBS)
message(STATUS "Skipping registration of target ${NAME} (not built in selective mode)")
return()
endif()
message(FATAL_ERROR "The supplied name ${NAME} of type ${TYPE} is not a valid target")
endif()
set_property(
TARGET everest_targets
APPEND
PROPERTY ${TYPE} ${NAME}
)
endfunction()
function(ev_register_library_target NAME)
_ev_register_target(LIBRARIES ${NAME})
endfunction()
function(ev_register_module_target NAME)
_ev_register_target(MODULES ${NAME})
endfunction()
function(ev_register_test_target NAME)
_ev_register_target(TESTS ${NAME})
endfunction()
function(ev_get_targets NAME TYPE)
get_target_property(tmp everest_targets ${TYPE})
if (NOT tmp STREQUAL "" AND NOT tmp)
message(FATAL_ERROR "There is no target of type ${TYPE} defined")
endif()
set(${NAME} ${tmp} PARENT_SCOPE)
endfunction()

View File

@@ -0,0 +1,897 @@
if (NOT EVEREST_SCHEMA_DIR)
get_filename_component(CUR_FILE_NAME ${CMAKE_CURRENT_LIST_FILE} NAME)
message(FATAL_ERROR "\
The variable EVEREST_SCHEMA_DIR is not set, this needs to be done, \
before including \"${CUR_FILE_NAME}\"\
")
endif()
set (EV_CORE_CMAKE_SCRIPT_DIR ${CMAKE_CURRENT_LIST_DIR} CACHE FILEPATH "")
# FIXME (aw): where should this go, should it be global?
string(ASCII 27 ESCAPE)
set(FMT_RESET "${ESCAPE}[m")
set(FMT_BOLD "${ESCAPE}[1m")
# NOTE (aw): maybe this could be also implemented as an IMPORTED target?
add_custom_target(generate_cpp_files)
set_target_properties(generate_cpp_files
PROPERTIES
EVEREST_SCHEMA_DIR "${EVEREST_SCHEMA_DIR}"
EVEREST_GENERATED_OUTPUT_DIR "${CMAKE_BINARY_DIR}/generated"
EVEREST_GENERATED_INCLUDE_DIR "${CMAKE_BINARY_DIR}/generated/include"
EVEREST_PROJECT_DIRS ""
)
#
# out-of-tree interfaces/types/modules support
#
function(_ev_add_project)
set(options SKIP_DOC_GENERATION)
set(oneValueArgs EV_PROJECT_DIRECTORY EV_PROJECT_NAME)
set(multiValueArgs "")
cmake_parse_arguments(args "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if (args_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "_ev_add_project function got unknown argument(s): ${args_UNPARSED_ARGUMENTS}")
endif()
if(args_KEYWORDS_MISSING_VALUES)
message(FATAL_ERROR "ev_add_project() keyword(s) missing values: ${args_KEYWORDS_MISSING_VALUES}")
endif()
if(args_EV_PROJECT_DIRECTORY AND args_EV_PROJECT_NAME)
set (EVEREST_PROJECT_DIR ${args_EV_PROJECT_DIRECTORY})
set (EVEREST_PROJECT_NAME ${args_EV_PROJECT_NAME})
elseif(NOT args_PROJECT_NAME AND NOT args_PROJECT_DIRECTORY)
# if we don't get a directory, we're assuming project directory
set (EVEREST_PROJECT_DIR ${PROJECT_SOURCE_DIR})
set (CALLED_FROM_WITHIN_PROJECT TRUE)
set (EVEREST_PROJECT_NAME ${PROJECT_NAME})
else()
message(FATAL_ERROR "ev_add_project() can only be called with ALL or NONE of: 'EV_PROJECT_DIRECTORY', 'EV_PROJECT_NAME'")
endif()
if (NOT EXISTS ${EVEREST_PROJECT_DIR})
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} got non-existing project path: ${EVEREST_PROJECT_DIR}")
endif ()
message(STATUS "APPENDING ${EVEREST_PROJECT_DIR} to EVEREST_PROJECT_DIRS")
set_property(TARGET generate_cpp_files
APPEND PROPERTY EVEREST_PROJECT_DIRS ${EVEREST_PROJECT_DIR}
)
get_target_property(EVEREST_PROJECT_DIRS generate_cpp_files EVEREST_PROJECT_DIRS)
# check for types
set(TYPES_DIR "${EVEREST_PROJECT_DIR}/types")
if (EXISTS ${TYPES_DIR})
message(STATUS "Adding type definitions from ${TYPES_DIR}")
file(GLOB TYPES_FILES
${TYPES_DIR}/*.yaml
)
if(EVEREST_BUILD_DOCS AND NOT args_SKIP_DOC_GENERATION)
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)
install(
DIRECTORY ${TYPES_DIR}
DESTINATION "${CMAKE_INSTALL_DATADIR}/everest"
FILES_MATCHING PATTERN "*.yaml"
)
endif ()
endif ()
# check for API
set(API_DIR "${EVEREST_PROJECT_DIR}/docs/source/reference/EVerest_API")
if (EXISTS ${API_DIR})
if (${EVEREST_SKIP_BUILD_API_DOC})
message(WARNING "Skipping the generation of the EVerest API AsyncAPI html documentation")
else()
message(STATUS "Adding API definitions from ${API_DIR}")
file(GLOB API_FILES
${API_DIR}/*.yaml
)
if(EVEREST_BUILD_DOCS AND NOT args_SKIP_DOC_GENERATION)
find_package(
trailbook-ext-everest
0.1.0
REQUIRED
PATHS "${CMAKE_SOURCE_DIR}/cmake"
)
trailbook_ev_generate_api_doc(
TRAILBOOK_NAME "everest"
API_FILES ${API_FILES}
)
endif()
if (CALLED_FROM_WITHIN_PROJECT)
install(
DIRECTORY ${API_DIR}
DESTINATION "${CMAKE_INSTALL_DATADIR}/everest"
FILES_MATCHING PATTERN "*.yaml"
)
endif ()
endif ()
endif ()
# check for errors
set(ERRORS_DIR "${EVEREST_PROJECT_DIR}/errors")
if (EXISTS ${ERRORS_DIR})
message(STATUS "Adding error definitions from ${ERRORS_DIR}")
if (CALLED_FROM_WITHIN_PROJECT)
install(
DIRECTORY ${ERRORS_DIR}
DESTINATION "${CMAKE_INSTALL_DATADIR}/everest"
FILES_MATCHING PATTERN "*.yaml"
)
endif ()
endif ()
# check for interfaces
set (INTERFACES_DIR "${EVEREST_PROJECT_DIR}/interfaces")
if (EXISTS ${INTERFACES_DIR})
message(STATUS "Adding interface definitions from ${INTERFACES_DIR}")
file(GLOB INTERFACE_FILES
${INTERFACES_DIR}/*.yaml
)
if(EVEREST_BUILD_DOCS AND NOT args_SKIP_DOC_GENERATION)
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)
install(
DIRECTORY ${INTERFACES_DIR}
DESTINATION "${CMAKE_INSTALL_DATADIR}/everest"
FILES_MATCHING PATTERN "*.yaml"
)
endif ()
endif ()
# check for modules
set (MODULES_DIR "${EVEREST_PROJECT_DIR}/modules")
if (EXISTS "${MODULES_DIR}/CMakeLists.txt")
# FIXME (aw): default handling of building all modules?
if (EVC_MAIN_PROJECT OR NOT EVEREST_DONT_BUILD_ALL_MODULES)
add_subdirectory(${MODULES_DIR})
endif()
endif ()
get_property(EVEREST_MODULES
GLOBAL
PROPERTY EVEREST_MODULES
)
message(STATUS "${EVEREST_PROJECT_NAME} modules that will be built: ${EVEREST_MODULES}")
# generate and install version information
evc_generate_version_information()
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/generated/version_information.txt
DESTINATION "${CMAKE_INSTALL_DATADIR}/everest"
)
endfunction()
macro(ev_add_project)
set(options SKIP_DOC_GENERATION)
set(oneValueArgs EV_PROJECT_DIRECTORY EV_PROJECT_NAME)
set(multiValueArgs "")
cmake_parse_arguments(macro_args "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if (macro_args_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "ev_add_project macro got unknown argument(s): ${macro_args_UNPARSED_ARGUMENTS}")
endif()
if(macro_args_KEYWORDS_MISSING_VALUES)
message(FATAL_ERROR "ev_add_project() keyword(s) missing values: ${macro_args_KEYWORDS_MISSING_VALUES}")
endif()
if(macro_args_EV_PROJECT_DIRECTORY AND NOT macro_args_EV_PROJECT_NAME)
message(FATAL_ERROR "ev_add_project() was called with EV_PROJECT_DIRECTORY but is missing EV_PROJECT_NAME.")
elseif(NOT macro_args_EV_PROJECT_DIRECTORY AND macro_args_EV_PROJECT_NAME)
message(FATAL_ERROR "ev_add_project() was called with EV_PROJECT_NAME but is missing EV_PROJECT_DIRECTORY.")
endif()
ev_setup_cmake_variables_python_wheel()
set(${PROJECT_NAME}_PYTHON_VENV_PATH "${CMAKE_BINARY_DIR}/venv" CACHE PATH "Path to python venv")
ev_setup_python_executable(
USE_PYTHON_VENV ${${PROJECT_NAME}_USE_PYTHON_VENV}
PYTHON_VENV_PATH ${${PROJECT_NAME}_PYTHON_VENV_PATH}
)
setup_ev_cli()
if (${macro_args_SKIP_DOC_GENERATION})
set (macro_fwd_OPTION "SKIP_DOC_GENERATION")
else()
set (macro_fwd_OPTION "")
endif()
if (macro_args_EV_PROJECT_DIRECTORY AND macro_args_EV_PROJECT_NAME)
_ev_add_project(
EV_PROJECT_DIRECTORY ${macro_args_EV_PROJECT_DIRECTORY}
EV_PROJECT_NAME ${macro_args_EV_PROJECT_NAME}
${macro_fwd_OPTION}
)
else()
_ev_add_project(${macro_fwd_OPTION})
endif ()
endmacro()
#
# rust support
#
# FIXME (aw): move this stuff to some other cmake file for more modularity
if (EVEREST_ENABLE_RS_SUPPORT)
find_program(CARGO_EXECUTABLE cargo REQUIRED)
# FIXME (aw): the RUST_WORKSPACE_DIR could be user setable!
set(RUST_WORKSPACE_DIR ${PROJECT_BINARY_DIR}/rust_workspace)
set(RUST_WORKSPACE_CARGO_FILE ${RUST_WORKSPACE_DIR}/Cargo.toml)
if (NOT EXISTS ${RUST_WORKSPACE_DIR})
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory "${RUST_WORKSPACE_DIR}")
message(STATUS "Creating rust workspace at ${RUST_WORKSPACE_DIR}")
endif ()
# NOTE (aw): we could also write a small python script, which would do that for us
add_custom_command(OUTPUT ${RUST_WORKSPACE_CARGO_FILE}
COMMAND
echo "[workspace]" > Cargo.toml
COMMAND
echo "resolver = \"2\"" >> Cargo.toml
COMMAND
echo "members = [" >> Cargo.toml
COMMAND
echo " \"$<JOIN:$<TARGET_PROPERTY:generate_rust,RUST_MODULE_LIST>,\", \">\"," >> Cargo.toml # :)
COMMAND
echo "]" >> Cargo.toml && echo "" >> Cargo.toml
COMMAND
echo "[workspace.dependencies]" >> Cargo.toml
COMMAND
echo "everestrs = { path = \"$<TARGET_PROPERTY:everest::everestrs_sys,EVERESTRS_DIR>\" }" >> Cargo.toml
COMMAND
echo "everestrs-build = { path = \"$<TARGET_PROPERTY:everest::everestrs_sys,EVERESTRS_BUILD_DIR>\" }" >> Cargo.toml
WORKING_DIRECTORY
${RUST_WORKSPACE_DIR}
VERBATIM
DEPENDS
${RUST_WORKSPACE_DIR}
)
# Put the resulting file in the top-level build directory so that it can be easily accessed without CMake
set(RUST_LINK_DEPENDENCIES_FILE ${CMAKE_BINARY_DIR}/everestrs-link-dependencies.txt)
set(RUST_LINK_DEPENDENCIES "$<TARGET_GENEX_EVAL:everest::everestrs_sys,$<TARGET_PROPERTY:everest::everestrs_sys,EVERESTRS_LINK_DEPENDENCIES>>")
add_custom_command(OUTPUT ${RUST_LINK_DEPENDENCIES_FILE}
COMMAND_EXPAND_LISTS
VERBATIM
COMMAND
echo -e $<LIST:JOIN,${RUST_LINK_DEPENDENCIES},\\n> > "${RUST_LINK_DEPENDENCIES_FILE}"
)
add_custom_target(generate_rust
DEPENDS
${RUST_WORKSPACE_CARGO_FILE}
${RUST_LINK_DEPENDENCIES_FILE}
)
# Store the workspace directory as a target property so that it is accessible in different scopes
set_property(TARGET generate_rust
PROPERTY
RUST_WORKSPACE_DIR "${RUST_WORKSPACE_DIR}"
)
# FIXME (aw): use generator expressions here, but this first needs to be fixed in the build.rs file ...
add_custom_target(build_rust_modules ALL
USES_TERMINAL
COMMENT
"Build rust modules"
COMMAND
${CMAKE_COMMAND} -E env
EVEREST_CORE_ROOT="${CMAKE_CURRENT_SOURCE_DIR}"
EVEREST_RS_LINK_DEPENDENCIES="${RUST_LINK_DEPENDENCIES_FILE}"
${CARGO_EXECUTABLE} build
$<IF:$<STREQUAL:$<CONFIG>,Release>,--release,>
# explicitly set the linker to match what we're using for C++ to avoid the following issue when cross compiling:
# https://github.com/rust-lang/rust/issues/28924
--config 'target.$<TARGET_PROPERTY:build_rust_modules,RUST_TARGET_TRIPLE>.linker = \"${CMAKE_CXX_COMPILER}\"'
--target $<TARGET_PROPERTY:build_rust_modules,RUST_TARGET_TRIPLE>
WORKING_DIRECTORY
${RUST_WORKSPACE_DIR}
DEPENDS
everest::everestrs_sys
generate_rust
)
# FIXME: cleaning up doesn't work on the first run
set_property(TARGET build_rust_modules
APPEND
PROPERTY
ADDITIONAL_CLEAN_FILES ${RUST_WORKSPACE_DIR}/target ${RUST_WORKSPACE_DIR}/Cargo.lock
)
set_property(TARGET build_rust_modules
PROPERTY
# FIXME: Don't assume the glibc ABI here. This won't respect musl builds.
RUST_TARGET_TRIPLE "${CMAKE_SYSTEM_PROCESSOR}-unknown-linux-gnu"
)
function (ev_add_rs_module MODULE_NAME)
if(NOT ${EVEREST_ENABLE_RS_SUPPORT})
message(STATUS "Excluding Rust module ${MODULE_NAME} because EVEREST_ENABLE_RS_SUPPORT=${EVEREST_ENABLE_RS_SUPPORT}")
return()
elseif ("${MODULE_NAME}" IN_LIST EVEREST_EXCLUDE_MODULES)
message(STATUS "Excluding module ${MODULE_NAME}")
return()
elseif (EVEREST_INCLUDE_MODULES AND NOT ("${MODULE_NAME}" IN_LIST EVEREST_INCLUDE_MODULES))
message(STATUS "Excluding module ${MODULE_NAME}")
return()
endif ()
set(MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE_NAME}")
if (NOT IS_DIRECTORY ${MODULE_PATH})
message(FATAL "Rust module ${MODULE_NAME} does not exist at ${MODULE_PATH}")
return()
endif ()
message(STATUS "Setting up Rust module ${MODULE_NAME}")
# FIXME (aw): we might also look for a CMakeFiles.txt in the module folder for custom logic
set_property(
TARGET generate_rust
APPEND
PROPERTY RUST_MODULE_LIST "${MODULE_NAME}"
)
get_target_property(RUST_WORKSPACE_DIR generate_rust RUST_WORKSPACE_DIR)
add_custom_command(OUTPUT ${RUST_WORKSPACE_DIR}/${MODULE_NAME}
COMMAND
${CMAKE_COMMAND} -E create_symlink ${MODULE_PATH} ${MODULE_NAME}
COMMENT
"Create symlink for rust module ${MODULE_NAME}"
VERBATIM
WORKING_DIRECTORY
${RUST_WORKSPACE_DIR}
)
add_custom_target(rust_symlink_module_${MODULE_NAME}
DEPENDS ${RUST_WORKSPACE_DIR}/${MODULE_NAME}
COMMENT "Create symlink for rust module ${MODULE_NAME}"
)
add_dependencies(generate_rust rust_symlink_module_${MODULE_NAME})
set(EVEREST_MODULE_INSTALL_PREFIX "${CMAKE_INSTALL_LIBEXECDIR}/everest/modules")
set(BIN_PREFIX "target/$<TARGET_PROPERTY:build_rust_modules,RUST_TARGET_TRIPLE>/$<IF:$<STREQUAL:$<CONFIG>,Release>,release,debug>")
install(PROGRAMS ${RUST_WORKSPACE_DIR}/${BIN_PREFIX}/${MODULE_NAME}
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
)
# FIXME (aw): this should go into a general function for all add_module_* flavours
install(FILES ${MODULE_PATH}/manifest.yaml
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
)
endfunction()
endif () # EVEREST_ENABLE_RS_SUPPORT
#
# interfaces
#
function (_ev_add_interfaces)
# FIXME (aw): check for duplicates here!
get_target_property(GENERATED_OUTPUT_DIR generate_cpp_files EVEREST_GENERATED_OUTPUT_DIR)
set(CHECK_DONE_FILE "${GENERATED_OUTPUT_DIR}/.interfaces_generated_${EVEREST_PROJECT_NAME}")
add_custom_command(
OUTPUT
"${CHECK_DONE_FILE}"
DEPENDS
${ARGV}
"$<TARGET_PROPERTY:ev-cli,INTERFACE_TEMPLATES>"
COMMENT
"Generating/updating interface files ..."
VERBATIM
COMMAND
${EV_CLI} interface generate-headers
--disable-clang-format
--schemas-dir "$<TARGET_PROPERTY:generate_cpp_files,EVEREST_SCHEMA_DIR>"
--output-dir "$<TARGET_PROPERTY:generate_cpp_files,EVEREST_GENERATED_INCLUDE_DIR>/generated/interfaces"
--everest-dir ${EVEREST_PROJECT_DIRS}
COMMAND
${CMAKE_COMMAND} -E touch "${CHECK_DONE_FILE}"
WORKING_DIRECTORY
${PROJECT_SOURCE_DIR}
)
add_custom_target(generate_interfaces_cpp_${EVEREST_PROJECT_NAME}
DEPENDS "${CHECK_DONE_FILE}"
)
add_dependencies(generate_cpp_files
generate_interfaces_cpp_${EVEREST_PROJECT_NAME}
)
endfunction()
#
# types
#
function (_ev_add_types)
# FIXME (aw): check for duplicates here!
get_target_property(GENERATED_OUTPUT_DIR generate_cpp_files EVEREST_GENERATED_OUTPUT_DIR)
set(CHECK_DONE_FILE "${GENERATED_OUTPUT_DIR}/.types_generated_${EVEREST_PROJECT_NAME}")
add_custom_command(
OUTPUT
"${CHECK_DONE_FILE}"
DEPENDS
${ARGV}
"$<TARGET_PROPERTY:ev-cli,TYPES_TEMPLATES>"
COMMENT
"Generating/updating type files ..."
VERBATIM
COMMAND
${EV_CLI} types generate-headers
--disable-clang-format
--schemas-dir "$<TARGET_PROPERTY:generate_cpp_files,EVEREST_SCHEMA_DIR>"
--output-dir "$<TARGET_PROPERTY:generate_cpp_files,EVEREST_GENERATED_INCLUDE_DIR>/generated/types"
--everest-dir ${EVEREST_PROJECT_DIRS}
COMMAND
${CMAKE_COMMAND} -E touch "${CHECK_DONE_FILE}"
WORKING_DIRECTORY
${PROJECT_SOURCE_DIR}
)
add_custom_target(generate_types_cpp_${EVEREST_PROJECT_NAME}
DEPENDS
${CHECK_DONE_FILE}
)
add_dependencies(generate_cpp_files
generate_types_cpp_${EVEREST_PROJECT_NAME}
)
endfunction()
#
# modules
#
function(ev_setup_cpp_module)
# no-op to not break API
endfunction()
function (ev_add_module)
#
# handle passed arguments
#
set(options SKIP_DOC_GENERATION)
set(one_value_args "")
set(multi_value_args
DEPENDENCIES
)
if (${ARGC} LESS 1)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}() missing module name")
endif ()
set (MODULE_NAME ${ARGV0})
cmake_parse_arguments(PARSE_ARGV 1 OPTNS "${options}" "${one_value_args}" "${multi_value_args}")
if (OPTNS_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}() got unknown argument(s): ${OPTNS_UNPARSED_ARGUMENTS}")
endif()
if (OPTNS_DEPENDENCIES)
foreach(DEPENDENCY_NAME ${OPTNS_DEPENDENCIES})
set(DEPENDENCY_VALUE ${${DEPENDENCY_NAME}})
if (NOT DEPENDENCY_VALUE)
message(STATUS "${FMT_BOLD}Skipping${FMT_RESET} module ${MODULE_NAME} (${DEPENDENCY_NAME} is false)")
return()
endif()
endforeach()
endif()
if (EVEREST_BUILD_DOCS AND NOT OPTNS_SKIP_DOC_GENERATION)
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_handwritten_doc(
TRAILBOOK_NAME "everest"
MODULE_NAME "${MODULE_NAME}"
HANDWRITTEN_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.inc, 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)
ev_add_py_module(${MODULE_NAME})
return()
endif()
# check if javascript module
string(FIND ${MODULE_NAME} "Js" MODULE_PREFIX_POS)
if (MODULE_PREFIX_POS EQUAL 0)
ev_add_js_module(${MODULE_NAME})
return()
endif()
# check if rust module
string(FIND ${MODULE_NAME} "Rs" MODULE_PREFIX_POS)
if (MODULE_PREFIX_POS EQUAL 0)
if (NOT EVEREST_ENABLE_RS_SUPPORT)
return() # NOTE (aw): could log here
endif ()
ev_add_rs_module(${MODULE_NAME})
return()
endif()
# otherwise, should be cpp module
ev_add_cpp_module(${MODULE_NAME})
endfunction()
function (ev_add_cpp_module MODULE_NAME)
set(EVEREST_MODULE_INSTALL_PREFIX "${CMAKE_INSTALL_LIBEXECDIR}/everest/modules")
set(EVEREST_MODULE_DIR ${PROJECT_SOURCE_DIR}/modules)
file(RELATIVE_PATH MODULE_PARENT_DIR ${EVEREST_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
set(RELATIVE_MODULE_DIR ${MODULE_NAME})
if (MODULE_PARENT_DIR)
set(RELATIVE_MODULE_DIR ${MODULE_PARENT_DIR}/${MODULE_NAME})
endif()
set(MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE_NAME}")
get_property(EVEREST_MODULES
GLOBAL
PROPERTY EVEREST_MODULES
)
# TODO(hikinggrass): This code is duplicated in ev_add_*_module and should be refactored.
if(IS_DIRECTORY ${MODULE_PATH})
if(${EVEREST_EXCLUDE_CPP_MODULES})
message(STATUS "Excluding C++ module ${MODULE_NAME} because EVEREST_EXCLUDE_CPP_MODULES=${EVEREST_EXCLUDE_CPP_MODULES}")
return()
elseif("${MODULE_NAME}" IN_LIST EVEREST_EXCLUDE_MODULES)
message(STATUS "Excluding module ${MODULE_NAME}")
return()
elseif(EVEREST_INCLUDE_MODULES AND NOT ("${MODULE_NAME}" IN_LIST EVEREST_INCLUDE_MODULES))
message(STATUS "Excluding module ${MODULE_NAME}")
return()
else()
message(STATUS "Setting up C++ module ${MODULE_NAME}")
get_target_property(GENERATED_OUTPUT_DIR generate_cpp_files EVEREST_GENERATED_OUTPUT_DIR)
set(GENERATED_MODULE_DIR "${GENERATED_OUTPUT_DIR}/modules")
set(MODULE_LOADER_DIR ${GENERATED_MODULE_DIR}/${MODULE_NAME})
add_custom_command(
OUTPUT
${MODULE_LOADER_DIR}/ld-ev.hpp
${MODULE_LOADER_DIR}/ld-ev.cpp
COMMAND
${EV_CLI} module generate-loader
--disable-clang-format
--schemas-dir "$<TARGET_PROPERTY:generate_cpp_files,EVEREST_SCHEMA_DIR>"
--output-dir ${GENERATED_MODULE_DIR}
${RELATIVE_MODULE_DIR}
DEPENDS
${MODULE_PATH}/manifest.yaml
"$<TARGET_PROPERTY:ev-cli,MODULE_TEMPLATES>"
WORKING_DIRECTORY
${PROJECT_SOURCE_DIR}
COMMENT
"Generating ld-ev for module ${MODULE_NAME}"
)
add_custom_target(ld-ev_${MODULE_NAME}
DEPENDS ${MODULE_LOADER_DIR}/ld-ev.cpp
)
add_dependencies(generate_cpp_files ld-ev_${MODULE_NAME})
add_executable(${MODULE_NAME})
set_target_properties(${MODULE_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${MODULE_NAME}"
)
target_include_directories(${MODULE_NAME}
PRIVATE
${MODULE_PATH}
"$<TARGET_PROPERTY:generate_cpp_files,EVEREST_GENERATED_INCLUDE_DIR>"
${MODULE_LOADER_DIR}
)
target_sources(${MODULE_NAME}
PRIVATE
${MODULE_PATH}/${MODULE_NAME}.cpp
"${MODULE_LOADER_DIR}/ld-ev.cpp"
)
target_link_libraries(${MODULE_NAME}
PRIVATE
everest::framework
${ATOMIC_LIBS}
)
if(EVEREST_ENABLE_COMPILE_WARNINGS)
message(STATUS "Building ${MODULE_NAME} with the following compile options: ${EVEREST_COMPILE_OPTIONS}")
target_compile_options(${MODULE_NAME}
PRIVATE ${EVEREST_COMPILE_OPTIONS}
)
endif()
add_dependencies(${MODULE_NAME} generate_cpp_files)
ev_register_module_target(${MODULE_NAME})
install(TARGETS ${MODULE_NAME}
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
)
install(FILES ${MODULE_PATH}/manifest.yaml
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
)
list(APPEND EVEREST_MODULES ${MODULE_NAME})
add_subdirectory(${MODULE_PATH})
endif()
else()
message(WARNING "C++ module ${MODULE_NAME} does not exist at ${MODULE_PATH}")
return()
endif()
# this will override EVEREST_MODULES, but that is ok because we appended the list earlier
# rename EVEREST_MODULES to EVEREST_MODULES
# use set_property APPEND
set_property(
GLOBAL
PROPERTY EVEREST_MODULES ${EVEREST_MODULES}
)
endfunction()
function (ev_add_js_module MODULE_NAME)
set(EVEREST_MODULE_INSTALL_PREFIX "${CMAKE_INSTALL_LIBEXECDIR}/everest/modules")
set(MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE_NAME}")
get_property(EVEREST_MODULES
GLOBAL
PROPERTY EVEREST_MODULES
)
if(IS_DIRECTORY ${MODULE_PATH})
if(NOT ${EVEREST_ENABLE_JS_SUPPORT})
message(STATUS "Excluding JavaScript module ${MODULE_NAME} because EVEREST_ENABLE_JS_SUPPORT=${EVEREST_ENABLE_JS_SUPPORT}")
return()
elseif("${MODULE_NAME}" IN_LIST EVEREST_EXCLUDE_MODULES)
message(STATUS "Excluding module ${MODULE_NAME}")
return()
elseif(EVEREST_INCLUDE_MODULES AND NOT ("${MODULE_NAME}" IN_LIST EVEREST_INCLUDE_MODULES))
message(STATUS "Excluding module ${MODULE_NAME}")
return()
else()
message(STATUS "Setting up JavaScript module ${MODULE_NAME}")
add_custom_target(${MODULE_NAME} ALL)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/package.json")
message(STATUS "JavaScript module ${MODULE_NAME} contains a package.json file with dependencies that will be installed")
add_dependencies(${MODULE_NAME} ${MODULE_NAME}_INSTALL_NODE_MODULES)
find_program(
RSYNC
rsync
REQUIRED
)
find_program(
NPM
npm
REQUIRED
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/package.json
MAIN_DEPENDENCY package.json
COMMENT "Copy package.json of module ${MODULE_NAME} to build dir"
COMMAND ${RSYNC} -avq ${MODULE_PATH}/package.json ${CMAKE_CURRENT_BINARY_DIR}/package.json
)
add_custom_command(
OUTPUT .installed
MAIN_DEPENDENCY ${CMAKE_CURRENT_BINARY_DIR}/package.json
COMMENT "Installing dependencies of module ${MODULE_NAME} from package.json"
COMMAND ${NPM} install > npm.log 2>&1 || ${CMAKE_COMMAND} -E cat ${CMAKE_CURRENT_BINARY_DIR}/npm.log
COMMAND ${CMAKE_COMMAND} -E touch .installed
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
add_custom_target(
${MODULE_NAME}_INSTALL_NODE_MODULES
DEPENDS .installed
)
install(
DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/node_modules
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
)
endif()
# install the whole js project
if(CREATE_SYMLINKS)
include("CreateModuleSymlink")
else()
install(
DIRECTORY ${MODULE_PATH}/
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
PATTERN "CMakeLists.txt" EXCLUDE
PATTERN "CMakeFiles" EXCLUDE)
endif()
list(APPEND EVEREST_MODULES ${MODULE_NAME})
add_subdirectory(${MODULE_PATH})
endif()
else()
message(WARNING "JavaScript module ${MODULE_NAME} does not exist at ${MODULE_PATH}")
return()
endif()
# this will override EVEREST_MODULES, but that is ok because we appended the list earlier
set_property(
GLOBAL
PROPERTY EVEREST_MODULES ${EVEREST_MODULES}
)
endfunction()
function (ev_add_py_module MODULE_NAME)
set(EVEREST_MODULE_INSTALL_PREFIX "${CMAKE_INSTALL_LIBEXECDIR}/everest/modules")
set(MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE_NAME}")
get_property(EVEREST_MODULES
GLOBAL
PROPERTY EVEREST_MODULES
)
if(IS_DIRECTORY ${MODULE_PATH})
if(NOT ${EVEREST_ENABLE_PY_SUPPORT})
message(STATUS "Excluding Python module ${MODULE_NAME} because EVEREST_ENABLE_PY_SUPPORT=${EVEREST_ENABLE_PY_SUPPORT}")
return()
elseif("${MODULE_NAME}" IN_LIST EVEREST_EXCLUDE_MODULES)
message(STATUS "Excluding module ${MODULE_NAME}")
return()
elseif(EVEREST_INCLUDE_MODULES AND NOT ("${MODULE_NAME}" IN_LIST EVEREST_INCLUDE_MODULES))
message(STATUS "Excluding module ${MODULE_NAME}")
return()
else()
message(STATUS "Setting up Python module ${MODULE_NAME}")
add_custom_target(${MODULE_NAME} ALL)
# TODO: figure out how to properly install python dependencies
# install the whole python project
install(
DIRECTORY ${MODULE_PATH}/
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
PATTERN "CMakeLists.txt" EXCLUDE
PATTERN "CMakeFiles" EXCLUDE)
list(APPEND EVEREST_MODULES ${MODULE_NAME})
add_subdirectory(${MODULE_PATH})
endif()
else()
message(WARNING "Python module ${MODULE_NAME} does not exist at ${MODULE_PATH}")
return()
endif()
# this will override EVEREST_MODULES, but that is ok because we appended the list earlier
set_property(
GLOBAL
PROPERTY EVEREST_MODULES ${EVEREST_MODULES}
)
endfunction()
function(ev_install_project)
set (LIBRARY_PACKAGE_NAME ${PROJECT_NAME})
set (LIBRARY_PACKAGE_CMAKE_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/${LIBRARY_PACKAGE_NAME})
include(CMakePackageConfigHelpers)
set (EVEREST_DATADIR "${CMAKE_INSTALL_DATADIR}/everest")
configure_package_config_file(
${EV_CORE_CMAKE_SCRIPT_DIR}/project-config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_PACKAGE_NAME}-config.cmake
INSTALL_DESTINATION
${LIBRARY_PACKAGE_CMAKE_INSTALL_DIR}
PATH_VARS
EVEREST_DATADIR
)
install(
EXPORT everest-core-targets
FILE "everest-core-targets.cmake"
NAMESPACE everest::
DESTINATION ${LIBRARY_PACKAGE_CMAKE_INSTALL_DIR}
)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_PACKAGE_NAME}-config.cmake
${EV_CORE_CMAKE_SCRIPT_DIR}/everest-generate.cmake
${EV_CORE_CMAKE_SCRIPT_DIR}/ev-cli.cmake
${EV_CORE_CMAKE_SCRIPT_DIR}/project-config.cmake.in
${EV_CORE_CMAKE_SCRIPT_DIR}/ev-project-bootstrap.cmake
${EV_CORE_CMAKE_SCRIPT_DIR}/ev-targets.cmake
${EV_CORE_CMAKE_SCRIPT_DIR}/config-run-script.cmake
${EV_CORE_CMAKE_SCRIPT_DIR}/config-run-nodered-script.cmake
${EV_CORE_CMAKE_SCRIPT_DIR}/config-tmux-run-script.cmake
DESTINATION
${CMAKE_INSTALL_LIBDIR}/cmake/${LIBRARY_PACKAGE_NAME}
)
endfunction()
set(EVEREST_EXCLUDE_MODULES "" CACHE STRING "A list of modules that will not be built")
set(EVEREST_INCLUDE_MODULES "" CACHE STRING "A list of modules that will be built. If the list is empty, all modules will be built.")
option(EVEREST_EXCLUDE_CPP_MODULES "Exclude all C++ modules from the build" OFF)

View File

@@ -0,0 +1,22 @@
set(@LIBRARY_PACKAGE_NAME@_VERSION @PROJECT_VERSION@)
@PACKAGE_INIT@
set(EVEREST_SCHEMA_DIR "@PACKAGE_EVEREST_DATADIR@/schemas")
find_package(everest-log REQUIRED)
find_package(everest-sqlite REQUIRED)
find_package(everest-timer REQUIRED)
find_package(everest-evse_security REQUIRED)
find_package(everest-ocpp REQUIRED)
find_dependency(ryml REQUIRED)
include(${CMAKE_CURRENT_LIST_DIR}/ev-project-bootstrap.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/everest-core-targets.cmake)
find_package(everest-framework REQUIRED)
include(CMakeFindDependencyMacro)
ev_add_project(
EV_PROJECT_DIRECTORY @PACKAGE_EVEREST_DATADIR@
EV_PROJECT_NAME everest-core
SKIP_DOC_GENERATION
)

View File

@@ -0,0 +1,18 @@
# 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_api_doc()`
* 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.

View File

@@ -0,0 +1,214 @@
# This macro is for internal use only
#
# It is used in the function trailbook_ev_add_module_handwritten_doc().
# It adds a custom command to copy the handwritten module files to the reference modules directory.
macro(_trailbook_ev_add_module_reference_copy_handwritten_command)
file(
GLOB_RECURSE
MODULE_HANDWRITTEN_SOURCE_FILES
RELATIVE "${args_HANDWRITTEN_DIR}"
CONFIGURE_DEPENDS
"${args_HANDWRITTEN_DIR}/*"
)
set(EXPECTED_DEST_FILES "")
set(COPY_DEPENDENCIES "")
foreach(SOURCE_FILE IN LISTS MODULE_HANDWRITTEN_SOURCE_FILES)
set(SRC_FILE_PATH "${args_HANDWRITTEN_DIR}/${SOURCE_FILE}")
if(IS_DIRECTORY "${SRC_FILE_PATH}")
continue()
endif()
get_filename_component(RELATIVE_SUBDIR "${SOURCE_FILE}" DIRECTORY)
get_filename_component(FILE_NAME "${SOURCE_FILE}" NAME)
# when copying 'index.rst' then rename it
if("${FILE_NAME}" STREQUAL "index.rst")
set(DEST_FILENAME "index.inc")
else()
set(DEST_FILENAME "${FILE_NAME}")
endif()
if("${RELATIVE_SUBDIR}" STREQUAL "")
set(DEST_FILE_PATH "${TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY}/${DEST_FILENAME}")
set(DEST_DIR "${TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY}")
else()
set(DEST_FILE_PATH "${TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY}/${RELATIVE_SUBDIR}/${DEST_FILENAME}")
set(DEST_DIR "${TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY}/${RELATIVE_SUBDIR}")
endif()
list(APPEND EXPECTED_DEST_FILES "${DEST_FILE_PATH}")
# One command per file
add_custom_command(
OUTPUT "${DEST_FILE_PATH}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${DEST_DIR}"
COMMAND ${CMAKE_COMMAND} -E copy "${SRC_FILE_PATH}" "${DEST_FILE_PATH}"
DEPENDS "${SRC_FILE_PATH}"
COMMENT "Processing doc file: ${SOURCE_FILE} -> ${RELATIVE_SUBDIR}/${DEST_FILENAME}"
VERBATIM
)
list(APPEND COPY_DEPENDENCIES "${DEST_FILE_PATH}")
endforeach()
# Remove files if they were deleted in the source tree
if(EXISTS "${TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY}")
file(GLOB_RECURSE EXISTING_DEST_FILES "${TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY}/*")
foreach(EXISTING_FILE IN LISTS EXISTING_DEST_FILES)
if(IS_DIRECTORY "${EXISTING_FILE}")
continue()
endif()
list(FIND EXPECTED_DEST_FILES "${EXISTING_FILE}" FILE_INDEX)
if(FILE_INDEX EQUAL -1)
message(STATUS " Removing orphaned doc file: ${EXISTING_FILE}")
file(REMOVE "${EXISTING_FILE}")
endif()
endforeach()
endif()
if(COPY_DEPENDENCIES)
set(ASSET_TARGET "${TARGET_NAME_PREFIX}_assets")
# Guard against multiple definitions
if(NOT TARGET trailbook_${args_TRAILBOOK_NAME}_handwritten_doc_module_${args_MODULE_NAME})
add_custom_target(
trailbook_${args_TRAILBOOK_NAME}_handwritten_doc_module_${args_MODULE_NAME}
DEPENDS
${COPY_DEPENDENCIES}
COMMENT
"Handwritten documentation of 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
${COPY_DEPENDENCIES}
trailbook_${args_TRAILBOOK_NAME}_handwritten_doc_module_${args_MODULE_NAME}
)
endif()
endif()
# set(MODULE_HANDWRITTEN_TARGET_FILES "")
# foreach(source_file IN LISTS MODULE_HANDWRITTEN_SOURCE_FILES)
# file(RELATIVE_PATH rel_path "${args_HANDWRITTEN_DIR}" "${source_file}")
# set(target_file "${TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY}/${rel_path}")
# list(APPEND MODULE_HANDWRITTEN_TARGET_FILES "${target_file}")
# endforeach()
# add_custom_command(
# OUTPUT
# ${MODULE_HANDWRITTEN_TARGET_FILES}
# DEPENDS
# ${MODULE_HANDWRITTEN_SOURCE_FILES}
# ${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER}
# COMMENT
# "Copying handwritten documentation files of module ${args_MODULE_NAME} to: ${TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY}/"
# COMMAND
# ${CMAKE_COMMAND} -E rm -rf
# ${MODULE_HANDWRITTEN_TARGET_FILES}
# COMMAND
# ${CMAKE_COMMAND} -E make_directory
# ${TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY}/
# COMMAND
# ${CMAKE_COMMAND} -E copy_directory
# ${args_HANDWRITTEN_DIR}
# ${TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY}/
# )
endmacro()
# This function adds a handwritten module documentation to a trailbook.
# It takes the following parameters:
# TRAILBOOK_NAME (required): The name of the trailbook to add the
# documentation to.
# MODULE_NAME (required): The name of the module.
# HANDWRITTEN_DIR (required): The absolute path to the directory
# containing the module's handwritten files.
#
# Usage:
# trailbook_ev_add_module_handwritten_doc(
# TRAILBOOK_NAME <trailbook_name>
# MODULE_NAME <module_name>
# HANDWRITTEN_DIR <absolute_path_to_handwritten_docs_directory>
# )
function(trailbook_ev_add_module_handwritten_doc)
set(options)
set(one_value_args
TRAILBOOK_NAME
MODULE_NAME
HANDWRITTEN_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_<TRAILBOOK_NAME>
if(NOT args_TRAILBOOK_NAME)
message(FATAL_ERROR "trailbook_ev_add_module_handwritten_doc: TRAILBOOK_NAME argument is required")
endif()
if(NOT TARGET trailbook_${args_TRAILBOOK_NAME})
message(
FATAL_ERROR
"trailbook_ev_add_module_handwritten_doc: 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_handwritten_doc: MODULE_NAME argument is required")
endif()
# Parameter HANDWRITTEN_DIR
# - is required
# - must be a absolute path
# - must exist
if(NOT args_HANDWRITTEN_DIR)
message(FATAL_ERROR "trailbook_ev_add_module_handwritten_doc: HANDWRITTEN_DIR argument is required")
endif()
if(NOT IS_ABSOLUTE "${args_HANDWRITTEN_DIR}")
message(FATAL_ERROR "trailbook_ev_add_module_handwritten_doc: HANDWRITTEN_DIR must be an absolute path")
endif()
if(NOT EXISTS "${args_HANDWRITTEN_DIR}")
message(FATAL_ERROR "trailbook_ev_add_module_handwritten_doc: HANDWRITTEN_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
)
file(RELATIVE_PATH RELATIVE_PATH_HANDWRITTEN_DIR
"${CMAKE_SOURCE_DIR}/modules"
"${args_HANDWRITTEN_DIR}"
)
get_filename_component(RELATIVE_PATH_MODULE "${RELATIVE_PATH_HANDWRITTEN_DIR}" DIRECTORY)
set(TRAILBOOK_EV_REFERENCE_DIRECTORY "${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY}/reference")
set(TRAILBOOK_EV_HANDWRITTEN_MODULE_DOC_DIRECTORY "${TRAILBOOK_EV_REFERENCE_DIRECTORY}/modules/${RELATIVE_PATH_MODULE}")
_trailbook_ev_add_module_reference_copy_handwritten_command()
endfunction()

View File

@@ -0,0 +1,140 @@
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 <path_to_everest_workspace_directory>
# TRAILBOOK_NAME <trailbook_name>
# OUTPUT_FILE <absolute_path_to_output_snapshot_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_<TRAILBOOK_NAME>
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
--exclude-dir build/
--exclude-dir .vscode/
--exclude-dir dist/
--exclude-dir cache/
--exclude-dir scripts/
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()

View File

@@ -0,0 +1,41 @@
if(asyncapi-cli_DIR)
message(STATUS "Using asyncapi-cli at this location: ${asyncapi-cli_DIR}")
else()
message(STATUS "Retrieving asyncapi-cli using FetchContent")
include(FetchContent)
FetchContent_Declare(
asyncapi-cli
GIT_REPOSITORY https://github.com/asyncapi/cli.git
GIT_TAG v2.7.1
)
FetchContent_MakeAvailable(asyncapi-cli)
set(asyncapi-cli_DIR "${asyncapi-cli_SOURCE_DIR}")
set(asyncapi-cli_FIND_COMPONENTS "bundling")
endif()
set(ASYNCAPI_CLI_INSTALL_SENTINEL_PATH "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(ASYNCAPI_CLI_INSTALL_SENTINEL "${ASYNCAPI_CLI_INSTALL_SENTINEL_PATH}/asyncapi_cli_install_done")
if(NOT TARGET asyncapi_cli_install_target)
add_custom_command(
OUTPUT ${ASYNCAPI_CLI_INSTALL_SENTINEL}
# Do installation
COMMAND ${CMAKE_COMMAND} -E chdir ${asyncapi-cli_DIR} npm install
COMMAND ${CMAKE_COMMAND} -E chdir ${asyncapi-cli_DIR} npm run build
# Create sentinel file
COMMAND ${CMAKE_COMMAND} -E make_directory ${ASYNCAPI_CLI_INSTALL_SENTINEL_PATH}
COMMAND ${CMAKE_COMMAND} -E touch ${ASYNCAPI_CLI_INSTALL_SENTINEL}
COMMENT "AsyncApi/cli Install once only"
)
add_custom_target(asyncapi_cli_install_target
DEPENDS ${ASYNCAPI_CLI_INSTALL_SENTINEL}
)
else()
message(STATUS "Skipping definition of 'asyncapi_cli_install_target'; already exists.")
endif()
set(ASYNCAPI_CMD ${asyncapi-cli_DIR}/bin/run)

View File

@@ -0,0 +1,45 @@
if(asyncapi-html-template_DIR)
message(STATUS "Using existing asyncapi-html-template location: ${asyncapi-html-template_DIR}")
else()
find_package(asyncapi-html-template
COMPONENTS bundling
PATHS ../asyncapi-html-template
)
if(NOT asyncapi-html-template_FOUND)
message(STATUS "Retrieving asyncapi-html-template using FetchContent")
include(FetchContent)
FetchContent_Declare(
asyncapi-html-template
GIT_REPOSITORY https://github.com/asyncapi/html-template.git
GIT_TAG v3.0.0
)
FetchContent_MakeAvailable(asyncapi-html-template)
set(asyncapi-html-template_DIR "${asyncapi-html-template_SOURCE_DIR}")
set(asyncapi-html-template_FIND_COMPONENTS "bundling")
endif()
endif()
set(ASYNCAPI_HTML_TEMPLATE_INSTALL_SENTINEL_PATH "${CMAKE_CURRENT_BINARY_DIR}/generate")
set(ASYNCAPI_HTML_TEMPLATE_INSTALL_SENTINEL "${ASYNCAPI_HTML_TEMPLATE_INSTALL_SENTINEL_PATH}/asyncapi_html_template_install_done")
if(NOT TARGET asyncapi_html_template_install_target)
add_custom_command(
OUTPUT ${ASYNCAPI_HTML_TEMPLATE_INSTALL_SENTINEL}
# Do installation
COMMAND ${CMAKE_COMMAND} -E chdir ${asyncapi-html-template_DIR} npm install
# Create sentinel file
COMMAND ${CMAKE_COMMAND} -E make_directory ${ASYNCAPI_HTML_TEMPLATE_INSTALL_SENTINEL_PATH}
COMMAND ${CMAKE_COMMAND} -E touch ${ASYNCAPI_HTML_TEMPLATE_INSTALL_SENTINEL}
COMMENT "AsyncApi/html-template Install once only"
)
add_custom_target(asyncapi_html_template_install_target
DEPENDS ${ASYNCAPI_HTML_TEMPLATE_INSTALL_SENTINEL}
)
else()
message(STATUS "Skipping definition of 'asyncapi_html_template_install_target'; already exists.")
endif()

View File

@@ -0,0 +1,231 @@
include(${CMAKE_CURRENT_LIST_DIR}/fetch_async_api_html_template.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/fetch_async_api.cmake)
function(_trailbook_ev_generate_html_from_api)
set(options)
set(one_value_args
TRAILBOOK_NAME
API_FILE
HTML_PATH
)
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_<TRAILBOOK_NAME>
if(NOT args_TRAILBOOK_NAME)
message(FATAL_ERROR "trailbook_ev_generate_rst_from_api: TRAILBOOK_NAME argument is required")
endif()
if(NOT TARGET trailbook_${args_TRAILBOOK_NAME})
message(
FATAL_ERROR
"trailbook_ev_generate_rst_from_api: No target named trailbook_${args_TRAILBOOK_NAME} found."
" Did you forget to call add_trailbook() first?"
)
endif()
# Parameter API_FILE
# - is required
# - must be a absolute path
# - must exist
if(NOT args_API_FILE)
message(FATAL_ERROR "trailbook_ev_generate_rst_from_api: API_FILE argument is required")
endif()
if(NOT IS_ABSOLUTE "${args_API_FILE}")
message(FATAL_ERROR "trailbook_ev_generate_rst_from_api: API_FILE must be an absolute path")
endif()
if(NOT EXISTS "${args_API_FILE}")
message(FATAL_ERROR "trailbook_ev_generate_rst_from_api: API_FILE must exist")
endif()
# Parameter HTML_PATH
# - is required
# - must be a relative path
if(NOT args_HTML_PATH)
message(FATAL_ERROR "trailbook_ev_generate_html_from_api: HTML_PATH argument is required")
endif()
if(IS_ABSOLUTE "${args_HTML_PATH}")
message(FATAL_ERROR "trailbook_ev_generate_html_from_api: HTML_PATH must be an relative path: ${args_HTML_PATH}")
endif()
get_target_property(
TRAILBOOK_INSTANCE_BUILD_DIRECTORY
trailbook_everest
TRAILBOOK_INSTANCE_BUILD_DIRECTORY
)
get_target_property(
TRAILBOOK_BUILD_DIRECTORY
trailbook_everest
TRAILBOOK_BUILD_DIRECTORY
)
get_filename_component(API_NAME ${args_API_FILE} NAME_WE)
set(ASYNC_TARGET_NAME ${API_NAME}_AsyncApi)
set(GENERATED_HTML_PATH ${TRAILBOOK_INSTANCE_BUILD_DIRECTORY}/${args_HTML_PATH}/${API_NAME})
set(PREVIEW_HTML_PATH ${TRAILBOOK_BUILD_DIRECTORY}/latest/reference/api/${API_NAME})
set(COPY_FOR_PREVIEW_SENTINEL ${GENERATED_HTML_PATH}/copy_for_preview_sentinel)
add_custom_command(
OUTPUT
${GENERATED_HTML_PATH}/index.html
COMMAND
${ASYNCAPI_CMD} generate fromTemplate ${args_API_FILE} ${asyncapi-html-template_SOURCE_DIR} --force-write --use-new-generator --output=${GENERATED_HTML_PATH}
DEPENDS
${args_API_FILE}
COMMENT
"${API_NAME}: Generate AsyncApi HTML documentation"
)
add_custom_target(
trailbook_${args_TRAILBOOK_NAME}_generate_html_from_api_${ASYNC_TARGET_NAME}
DEPENDS
${GENERATED_HTML_PATH}/index.html
trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after
${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER}
asyncapi_cli_install_target
asyncapi_html_template_install_target
COMMENT
"Target to generate HTML files in ${GENERATED_HTML_PATH} from api definition ${args_API_FILE}"
)
set_property(
TARGET
trailbook_${args_TRAILBOOK_NAME}
APPEND
PROPERTY
ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE
${GENERATED_HTML_PATH}/index.html
trailbook_${args_TRAILBOOK_NAME}_generate_html_from_api_${ASYNC_TARGET_NAME}
)
add_custom_command(
OUTPUT ${COPY_FOR_PREVIEW_SENTINEL}
COMMAND ${CMAKE_COMMAND} -E remove_directory "${PREVIEW_HTML_PATH}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${PREVIEW_HTML_PATH}"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${GENERATED_HTML_PATH}" "${PREVIEW_HTML_PATH}"
COMMAND ${CMAKE_COMMAND} -E touch ${COPY_FOR_PREVIEW_SENTINEL}
DEPENDS
${GENERATED_HTML_PATH}/index.html
COMMENT "Copy autogenerated AsyncAPI HTML files to a path where it is available for preview"
)
add_custom_target(
trailbook_${args_TRAILBOOK_NAME}_copy_asyncapi_html_to_preview_${ASYNC_TARGET_NAME}
DEPENDS ${COPY_FOR_PREVIEW_SENTINEL}
)
add_dependencies(trailbook_${args_TRAILBOOK_NAME}_copy_asyncapi_html_to_preview_${ASYNC_TARGET_NAME} trailbook_${args_TRAILBOOK_NAME}_generate_html_from_api_${ASYNC_TARGET_NAME})
add_dependencies(trailbook_${args_TRAILBOOK_NAME}_preview trailbook_${args_TRAILBOOK_NAME}_copy_asyncapi_html_to_preview_${ASYNC_TARGET_NAME})
endfunction()
function(trailbook_ev_generate_api_doc)
set(options)
set(one_value_args
TRAILBOOK_NAME
)
set(multi_value_args
API_FILES)
cmake_parse_arguments(
"args"
"${options}"
"${one_value_args}"
"${multi_value_args}"
${ARGN}
)
# Parameter TRAILBOOK_NAME
# - is required
# - there should be a target named trailbook_<TRAILBOOK_NAME>
if(NOT args_TRAILBOOK_NAME)
message(FATAL_ERROR "trailbook_ev_generate_api_dock: TRAILBOOK_NAME argument is required")
endif()
if(NOT TARGET trailbook_${args_TRAILBOOK_NAME})
message(
FATAL_ERROR
"trailbook_ev_generate_api_dock: No target named trailbook_${args_TRAILBOOK_NAME} found."
" Did you forget to call add_trailbook() first?"
)
endif()
# Parameter API_FILES
# - is required
if(NOT API_FILES)
message(FATAL_ERROR "trailbook_ev_generate_api_dock: API_FILES argument is required")
endif()
get_target_property(
TRAILBOOK_INSTANCE_SOURCE_DIRECTORY
trailbook_everest
TRAILBOOK_INSTANCE_SOURCE_DIRECTORY
)
# Make a list of all API's names
set(API_NAMES "")
foreach(API_FILE ${API_FILES})
get_filename_component(API_NAME ${API_FILE} NAME_WE)
set(DESTINATION_SUBFOLDER "reference/api/")
list(APPEND API_NAMES "${API_NAME}")
_trailbook_ev_generate_html_from_api(
TRAILBOOK_NAME "everest"
API_FILE "${API_FILE}"
HTML_PATH "${DESTINATION_SUBFOLDER}"
)
endforeach()
list(JOIN API_NAMES "," CSV_API_NAMES)
set(INDEX_RST_FILE "${TRAILBOOK_INSTANCE_SOURCE_DIRECTORY}/${DESTINATION_SUBFOLDER}/autogenerated_api_index.rst")
set(PYTHON_SCRIPT "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/process_api_template.py")
set(TEMPLATES_DIRECTORY "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/templates")
set(TEMPLATES_FILE "${TEMPLATES_DIRECTORY}/api_index.rst.jinja")
add_custom_command(
OUTPUT ${INDEX_RST_FILE}
COMMAND ${Python3_EXECUTABLE}
${PYTHON_SCRIPT}
--template-dir "${TEMPLATES_DIRECTORY}"
--template-file "${TEMPLATES_FILE}"
--apis "${CSV_API_NAMES}"
--target-file "${INDEX_RST_FILE}"
DEPENDS
${PYTHON_SCRIPT}
${TEMPLATES_FILE}
${TEMPLATES_DIRECTORY}/macros.jinja
${API_FILES}
COMMENT
"Generating RST index for API doc"
)
add_custom_target(
trailbook_${args_TRAILBOOK_NAME}_generate_rst_api_index
DEPENDS
${INDEX_RST_FILE}
trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after
${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER}
COMMENT
"Target to generate RST file ${INDEX_RST_FILE}"
)
set_property(
TARGET
trailbook_${args_TRAILBOOK_NAME}
APPEND
PROPERTY
ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE
${INDEX_RST_FILE}
trailbook_${args_TRAILBOOK_NAME}_generate_rst_api_index
)
endfunction()

View File

@@ -0,0 +1,128 @@
# 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}"
--errors-yaml-path "${CMAKE_SOURCE_DIR}/errors/"
--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 <trailbook_name>
# INTERFACE_FILE <path_to_interface_definition_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_<TRAILBOOK_NAME>
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()

View File

@@ -0,0 +1,212 @@
# 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)
string(REPLACE "/manifest.yaml" ".rst" GENERATED_FILE "${TRAILBOOK_EV_REFERENCE_MODULES_DIRECTORY}/${RELATIVE_PATH_MANIFEST}")
get_filename_component(RELATIVE_PATH ${RELATIVE_PATH_MANIFEST} DIRECTORY)
set(GENERATED_FILE "${TRAILBOOK_EV_REFERENCE_MODULES_DIRECTORY}/${RELATIVE_PATH}/autogenerated.rst")
get_filename_component(GENERATED_PATH ${GENERATED_FILE} DIRECTORY)
set(TEMPLATES_DIRECTORY "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/templates")
if(EXISTS "${MODULE_DIR}/docs/")
set(HANDWRITTEN_MODULE_DOC "--module-handwritten-doc" "index.inc")
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
${CMAKE_COMMAND} -E make_directory "${GENERATED_PATH}"
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}"
${HANDWRITTEN_MODULE_DOC}
)
set(TOP_LEVEL_MODULE_DIRECTORY "${CMAKE_SOURCE_DIR}")
get_filename_component(CURRENT_RELATIVE_FOLDER_TMP "${RELATIVE_PATH_MANIFEST}" DIRECTORY)
get_filename_component(CURRENT_RELATIVE_FOLDER "modules/${CURRENT_RELATIVE_FOLDER_TMP}" DIRECTORY)
while(NOT CURRENT_RELATIVE_FOLDER STREQUAL "modules")
set(CURRENT_INDEX_DIRECTORY_CONCAT "${CMAKE_SOURCE_DIR}/${CURRENT_RELATIVE_FOLDER}")
get_filename_component(CURRENT_INDEX_DIRECTORY "${CURRENT_INDEX_DIRECTORY_CONCAT}" ABSOLUTE)
set(CURRENT_DEST_DIRECTORY_CONCAT "${TRAILBOOK_EV_REFERENCE_DIRECTORY}/${CURRENT_RELATIVE_FOLDER}")
get_filename_component(CURRENT_DEST_DIRECTORY "${CURRENT_DEST_DIRECTORY_CONCAT}" ABSOLUTE)
set(INDEX_FILE "${CURRENT_DEST_DIRECTORY}/autogenerated.rst")
get_filename_component(CURRENT_FOLDER_NAME "${CURRENT_INDEX_DIRECTORY}" NAME)
string(REPLACE "/" "_" INDEX_TARGET_SUFFIX "${CURRENT_RELATIVE_FOLDER}")
get_property(is_create_index_cmd_added DIRECTORY "${CURRENT_INDEX_DIRECTORY}" PROPERTY did_add_create_index_cmd SET)
if (NOT ${is_create_index_cmd_added})
message(VERBOSE "ADDING DEFINITION OF add_custom_command FOR THE INDEX FILE OF ${CURRENT_DEST_DIRECTORY} FOR ${INDEX_FILE}")
set(INDEX_TEMPLATE_FILE "${TEMPLATES_DIRECTORY}/module_ref_index.rst.jinja")
set(CUSTOM_TEMPLATE_SUBSTITUTION_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/DOCS_process_index_template_${INDEX_TARGET_SUFFIX}.py")
configure_file(
"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/process_index_template.py.in"
"${CUSTOM_TEMPLATE_SUBSTITUTION_SCRIPT}"
@ONLY
)
add_custom_command(
OUTPUT
"${INDEX_FILE}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CURRENT_DEST_DIRECTORY}"
COMMAND ${Python3_EXECUTABLE} "${CUSTOM_TEMPLATE_SUBSTITUTION_SCRIPT}"
DEPENDS
"${INDEX_TEMPLATE_FILE}"
"${CUSTOM_TEMPLATE_SUBSTITUTION_SCRIPT}"
trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after
${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER}
COMMENT "Rendering Jinja2 template for ${INDEX_TARGET_SUFFIX}"
VERBATIM
)
set_property(
DIRECTORY "${CURRENT_INDEX_DIRECTORY}"
PROPERTY did_add_create_index_cmd
"TRUE"
)
add_custom_target(
trailbook_${args_TRAILBOOK_NAME}_generate_reference_index_for_module_${INDEX_TARGET_SUFFIX}
DEPENDS
"${INDEX_FILE}"
trailbook_${args_TRAILBOOK_NAME}_stage_prepare_sphinx_source_after
${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER}
COMMENT
"Target to generate RST file ${INDEX_FILE}"
)
set_property(
TARGET
trailbook_${args_TRAILBOOK_NAME}
APPEND
PROPERTY
ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE
"${INDEX_FILE}"
trailbook_${args_TRAILBOOK_NAME}_generate_reference_index_for_module_${INDEX_TARGET_SUFFIX}
)
else()
message(VERBOSE "SKIPPING DEFINITION OF add_custom_command FOR THE INDEX FILE OF ${CURRENT_FOLDER_NAME}")
endif()
# go up one level
get_filename_component(CURRENT_RELATIVE_FOLDER "${CURRENT_RELATIVE_FOLDER}" DIRECTORY)
endwhile()
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 <trailbook_name>
# MANIFEST_FILE <path_to_manifest_definition_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_<TRAILBOOK_NAME>
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)
file(RELATIVE_PATH RELATIVE_PATH_MANIFEST
"${CMAKE_SOURCE_DIR}/modules"
"${args_MANIFEST_FILE}"
)
_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()

View File

@@ -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 <trailbook_name>
# TYPES_FILE <absolute_path_to_types_definition_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_<TRAILBOOK_NAME>
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()

View File

@@ -0,0 +1,112 @@
import argparse
import jinja2
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="Generate RST index from file list")
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(
'--apis',
type=str,
dest='apis',
action='store',
required=True,
help='Comma separated list of api names'
)
parser.add_argument(
'--target-file',
type=Path,
dest='target_file',
action='store',
required=True,
help='Output file for the processed template'
)
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.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)
# Split comma-separated string back into a list
api_list = args.apis.split(',')
# turn list into dict
apis = []
for api_name in api_list:
if not api_name:
continue # Skip empty strings
apis.append({"name": api_name, "path": api_name})
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(
apis=apis
)
args.target_file.write_text(output)
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"Error: {e}")
exit(1)

View File

@@ -0,0 +1,14 @@
import jinja2
template_file = r"@INDEX_TEMPLATE_FILE@"
output_file = r"@INDEX_FILE@"
substitutions = {
"HEADLINE": "@CURRENT_FOLDER_NAME@",
}
with open(template_file, 'r') as f:
template = jinja2.Template(f.read())
with open(output_file, 'w') as f:
f.write(template.render(**substitutions))

View File

@@ -0,0 +1,192 @@
#!/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 literal_rst_filter(value):
"""Wraps a string in double backticks to treat it as a literal in RST."""
str_value = str(value)
has_trailing_whitespace = (str_value != str_value.rstrip())
cleaned_value = str_value.rstrip()
parts = cleaned_value.split(':', 1)
if len(parts) == 2 and parts[0] == "pattern":
formatted_value = f"{parts[0]}:``{parts[1].lstrip()}``"
else:
formatted_value = cleaned_value
if has_trailing_whitespace:
return formatted_value + '\r\n'
else:
return formatted_value
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(
'--module-handwritten-doc',
type=Path,
dest='module_handwritten_doc',
action='store',
help='Path to the handwritten module documentation if it exists'
)
parser.add_argument(
'--errors-yaml-path',
type=Path,
dest='errors_path',
action='store',
help='Path to the error definition yaml files'
)
parser.add_argument(
'--target-file',
type=Path,
dest='target_file',
action='store',
required=True,
help='Output file for the processed template'
)
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 args.errors_path:
if not args.errors_path.is_absolute():
raise ValueError("Errors yaml directory path must be absolute")
if not args.errors_path.exists():
raise ValueError(f"Errors yaml directory '{args.errors_path}' does not exist")
if not args.errors_path.is_dir():
raise ValueError("Errors yaml directory path is not a directory")
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
env.filters['literal_rst'] = literal_rst_filter
template_file_name = args.template_file.relative_to(args.template_dir)
template = env.get_template(str(template_file_name))
data=yaml.safe_load(args.data_file.read_text())
data["errors_sanitized"] = {}
data['error_definitions'] = {}
if args.errors_path and "errors" in data.keys():
for err in data["errors"]:
error_path = err['reference'].split('#')[0]
filename = Path(args.errors_path, error_path.split('/')[-1])
with open(filename.with_suffix(".yaml")) as f:
text = f.read()
yaml_content = yaml.safe_load(text)
data['error_definitions'][error_path] = {}
for err_def in yaml_content['errors']:
data['error_definitions'][error_path][err_def['name']] = err_def['description']
if not error_path in data["errors_sanitized"]:
data["errors_sanitized"][error_path] = []
if len(err['reference'].split('#')) > 1:
data["errors_sanitized"][error_path].append(err['reference'].split('#')[1][1:])
else:
for error in data['error_definitions'][error_path].keys():
data["errors_sanitized"][error_path].append(error)
output = template.render(
name=args.name,
handwritten_module_doc=args.module_handwritten_doc,
data=data,
)
args.target_file.write_text(output)
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"Error: {e}")
exit(1)

View File

@@ -0,0 +1,12 @@
{% import 'macros.jinja' as funcs %}
.. toctree::
:maxdepth: 1
:glob:
{{ funcs.explicit_target('everest_api') }}
{{ funcs.h1("EVerest API Specification") }}
{% for item in apis %}
* `{{ item.name }} <{{ item.path }}/index.html>`_
{% endfor %}

View File

@@ -0,0 +1,24 @@
{% 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 %}
{% if data.errors %}
{{ funcs.h2('Errors') -}}
{{ funcs.error_categories(data.errors_sanitized, data.error_definitions) | rst_indent() -}}
{% endif %}

View File

@@ -0,0 +1,359 @@
{#################################}
{### 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 = '<required>' %}
{% elif required == false %}
{% set optional = '<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 %}
{% if var_data.description %}
{% for line in var_data.description.split('\n') %}
{% if line != '' %}
{{ line }}
{% endif %}
{% endfor %}
{% endif %}
{% 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 | literal_rst | 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 %}
{% 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 %}
{### Renders a list of errors ###}
{% macro errors(errs, definitions) %}
{% for err in errs %}
**{{ err }}** : {{ definitions[err] }}
{% endfor %}
{% endmacro %}
{### Renders a list of error_categories ###}
{% macro error_categories(categories, error_definitions) %}
{% for cat in categories %}
**{{ cat }}** :
{{ errors(categories[cat], error_definitions[cat]) | indent(1, True) -}}
{% 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', 'config'] %}
{% set mapping_result = mapping( name, data.items(), ignore_keys, False) %}
{% if mapping_result != '' %}
X
{{ mapping_result | indent(1, True) -}}
Y
{% 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 %}

View File

@@ -0,0 +1,32 @@
{% import 'macros.jinja' as funcs %}
{{ 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 handwritten_module_doc %}
{{ funcs.h2("Handwritten Documentation") }}
.. include:: {{ handwritten_module_doc }}
{% endif %}
{{ funcs.h2("Auto-Generated Reference") }}
{% if data.config %}
{{ funcs.h3('Module Configuration') -}}
{{ funcs.config(data.config.items()) | rst_indent() -}}
{% endif %}
{% if data.provides %}
{{ funcs.h3('Provides') -}}
{{ funcs.impls(data.provides.items()) | rst_indent() -}}
{% endif %}
{% if data.requires %}
{{ funcs.h3('Requirements') -}}
{{ funcs.reqs(data.requires.items()) | rst_indent() -}}
{% endif %}
{{ funcs.metadata(data.metadata) -}}

View File

@@ -0,0 +1,9 @@
=========================================================
{{ HEADLINE }}
=========================================================
.. toctree::
:maxdepth: 1
:glob:
*/autogenerated

View File

@@ -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) }}

View File

@@ -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()

View File

@@ -0,0 +1,13 @@
find_package(
trailbook
0.1.0
REQUIRED
PATHS "${CMAKE_SOURCE_DIR}/cmake"
)
include("${CMAKE_CURRENT_LIST_DIR}/add-module-handwritten-doc.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/generate-api-docs.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")

View File

@@ -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
<output_file1>
<output_file2>
COMMAND
<your_command_here>
DEPENDS
<your_dependencies_here>
)
add_custom_target(
<wrapper_target_name>
DEPENDS
<output_file1>
<output_file2>
)
# Hook into the trailbook build process
set_property(
TARGET trailbook_<trailbook_name>
APPEND
PROPERTY
ADDITIONAL_DEPS_STAGE_PREPARE_SPHINX_SOURCE_BEFORE
<wrapper_target_name>
<output_file1>
<output_file2>
)
```
* `<output_file1>`, `<output_file2>` can be any custom files created
by your command.
* `<wrapper_target_name>` is a custom target that
wraps your command for example.
* `<trailbook_name>` 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_<trailbook_name>
DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER
)
# Your custom cmake code here
add_custom_command(
OUTPUT
<output_file1>
<output_file2>
DEPENDS
<your_dependencies_here>
${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER}
COMMAND
<your_command_here>
)
add_custom_target(
<wrapper_target_name>
DEPENDS
<output_file1>
<output_file2>
${DEPS_STAGE_PREPARE_SPHINX_SOURCE_AFTER}
)
# Hook into the trailbook build process before the build stage
set_property(
TARGET trailbook_<trailbook_name>
APPEND
PROPERTY
ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE
<wrapper_target_name>
<output_file1>
<output_file2>
)
```
* `<output_file1>`, `<output_file2>` can be any custom files created
by your command.
* `<wrapper_target_name>` is a custom target that
wraps your command for example.
* `<trailbook_name>` 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_<trailbook_name>
DEPS_STAGE_BUILD_SPHINX_AFTER
)
# Your custom cmake code here
add_custom_command(
OUTPUT
<output_file1>
<output_file2>
DEPENDS
<your_dependencies_here>
${DEPS_STAGE_BUILD_SPHINX_AFTER}
COMMAND
<your_command_here>
)
add_custom_target(
<wrapper_target_name>
DEPENDS
<output_file1>
<output_file2>
${DEPS_STAGE_BUILD_SPHINX_AFTER}
)
# Hook into the trailbook build process before the post process stage
set_property(
TARGET trailbook_<trailbook_name>
APPEND
PROPERTY
ADDITIONAL_DEPS_STAGE_POSTPROCESS_SPHINX_BEFORE
<wrapper_target_name>
<output_file1>
<output_file2>
)
```

View File

@@ -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}/<path-to-the-package>"
)
```
* 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 <trailbook_name>
[STEM_DIRECTORY <stem_directory>]
[REQUIREMENTS_TXT <requirements_txt>]
INSTANCE_NAME <instance_name>
[DEPLOYED_DOCS_REPO_URL <deployed_docs_repo_url>]
[DEPLOYED_DOCS_REPO_BRANCH <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_<NAME>_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_<NAME>_DOWNLOAD_ALL_VERSIONS`
* `<NAME>` should be replaced with the trailbook name provided
in the `add_trailbook()` function call.
If `TRAILBOOK_<NAME>_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_<NAME>_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_<NAME>_IS_RELEASE`
* `<NAME>` should be replaced with the trailbook name provided
in the `add_trailbook()` function call.
If `TRAILBOOK_<NAME>_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_<NAME>_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 <build_directory> --target trailbook_<trailbook_name>
```
* Replace `<build_directory>` with the path to your CMake build directory.
* Replace `<trailbook_name>` 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 <build_directory> --target trailbook_<trailbook_name>_preview
```
This target will start a local server to preview the built documentation.
```bash
cmake --build <build_directory> --target trailbook_<trailbook_name>_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.

View File

@@ -0,0 +1,721 @@
# 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()
set(CONDITIONAL_DELETE_INSTANCE_DIR_OR_FAIL_COMMAND "")
if(TRAILBOOK_${args_NAME}_OVERWRITE_EXISTING_INSTANCE)
set(CONDITIONAL_DELETE_INSTANCE_DIR_OR_FAIL_COMMAND
COMMAND
${CMAKE_COMMAND} -E rm -rf
${CMAKE_CURRENT_BINARY_DIR}/tmp_repo_download/docs/${TRAILBOOK_${args_NAME}_INSTANCE_NAME}
)
else()
# check if instance directory already exists and fail if it does
set(CONDITIONAL_DELETE_INSTANCE_DIR_OR_FAIL_COMMAND
COMMAND
${Python3_EXECUTABLE} ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_path_exists.py
--directory ${CMAKE_CURRENT_BINARY_DIR}/tmp_repo_download/docs/${TRAILBOOK_${args_NAME}_INSTANCE_NAME}
--return-zero-if-not-exists
)
endif()
add_custom_command(
OUTPUT
${CHECK_DONE_FILE_SETUP_BUILD_DIRECTORY}
DEPENDS
trailbook_${args_NAME}_stage_prepare_sphinx_source_before
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_STAGE_PREPARE_SPHINX_SOURCE_BEFORE>
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/filelist_manager.py
${Python3_EXECUTABLE} ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_path_exists.py
COMMENT
"Trailbook: ${args_NAME} - Downloading all versions repo"
COMMAND # Remove existing files in deployed docs repo directory from previous builds
${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 # Clone deployed docs repo
${GIT_EXECUTABLE} clone
-b ${args_DEPLOYED_DOCS_REPO_BRANCH}
--depth 1
${args_DEPLOYED_DOCS_REPO_URL}
${CMAKE_CURRENT_BINARY_DIR}/tmp_repo_download/
# Remove latest directory if this is a release instance
${CONDITIONAL_DELETE_LATEST_DIR_COMMAND}
# Remove existing instance directory if overwrite is enabled or fail if it exists
${CONDITIONAL_DELETE_INSTANCE_DIR_OR_FAIL_COMMAND}
COMMAND # Create file list of existing files in deployed docs repo directory after clone
${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 # Move cloned files to deployed docs repo directory
${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 # Delete temporary clone directory
${CMAKE_COMMAND} -E rm -rf
${CMAKE_CURRENT_BINARY_DIR}/tmp_repo_download/
COMMAND # Create convenience symlink to docs/ in build directory
${CMAKE_COMMAND} -E create_symlink
${DEPLOYED_DOCS_REPO_DIR}/docs/
${TRAILBOOK_BUILD_DIRECTORY}
COMMAND # Create done file
${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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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 "${TRAILBOOK_${args_NAME}_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_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_<NAME>_DOWNLOAD_ALL_VERSIONS is ON.
# DEPLOYED_DOCS_REPO_BRANCH (optional): The branch of the deployed docs repository.
# Defaults to 'main'.
# Usage:
# add_trailbook(
# NAME <trailbook_name>
# [STEM_DIRECTORY <stem_directory>]
# [REQUIREMENTS_TXT <requirements_txt_path>]
# INSTANCE_NAME <instance_name>
# [DEPLOYED_DOCS_REPO_URL <deployed_docs_repo_url>]
# [DEPLOYED_DOCS_REPO_BRANCH <deployed_docs_repo_branch>]
# )
function(add_trailbook)
set(options)
set(one_value_args
NAME
STEM_DIRECTORY
REQUIREMENTS_TXT
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)
set(TRAILBOOK_${args_NAME}_INSTANCE_NAME "local" CACHE STRING "Instance name for trailbook ${args_NAME}")
option(TRAILBOOK_${args_NAME}_OVERWRITE_EXISTING_INSTANCE "Overwrite existing instance with name ${TRAILBOOK_${args_NAME}_INSTANCE_NAME} if it exists" OFF)
# Check that at least one of DOWNLOAD_ALL_VERSIONS or IS_RELEASE is 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()
# Check that instance name is lowercase alphanumeric and underscores only
string(REGEX MATCH "^[a-z0-9_]+$" _valid_instance_name "${TRAILBOOK_${args_NAME}_INSTANCE_NAME}")
if("${_valid_instance_name}" STREQUAL "")
message(FATAL_ERROR "add_trailbook: TRAILBOOK_${args_NAME}_INSTANCE_NAME needs to be lowercase alphanumeric and underscores only")
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 DEPLOYED_DOCS_REPO_URL
# - required if TRAILBOOK_<NAME>_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}/${TRAILBOOK_${args_NAME}_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: <none>")
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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_STAGE_PREPARE_SPHINX_SOURCE_BEFORE>
)
_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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_STAGE_BUILD_SPHINX_BEFORE>
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
$<TARGET_PROPERTY:trailbook_${args_NAME},ADDITIONAL_DEPS_STAGE_POSTPROCESS_SPHINX_BEFORE>
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 "${TRAILBOOK_${args_NAME}_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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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 <target_file_name>.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)

View File

@@ -0,0 +1,46 @@
# Internal macro to find the sphinx-build executable.
macro(_find_sphinx_build)
execute_process(
COMMAND
${Python3_EXECUTABLE} -m sphinx.cmd.build --version
RESULT_VARIABLE RESULT_SPHINX_VERSION
)
if("${RESULT_SPHINX_VERSION}" STREQUAL "0")
set(_SPHINX_BUILD_EXECUTABLE "${Python3_EXECUTABLE}" "-m" "sphinx.cmd.build")
else()
set(_SPHINX_BUILD_EXECUTABLE "_SPHINX_BUILD_EXECUTABLE-NOTFOUND")
endif()
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()

View File

@@ -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()

View File

@@ -0,0 +1,11 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>
Redirecting...
</title>
<meta http-equiv="refresh" content="0;url=./{{ latest_release }}">
</head>
<body>
</body>
</html>

View File

@@ -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()

View File

@@ -0,0 +1,5 @@
include("${CMAKE_CURRENT_LIST_DIR}/setup-trailbook.cmake")
_setup_trailbook()
include("${CMAKE_CURRENT_LIST_DIR}/add-trailbook.cmake")