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,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")