If you're unfamiliar with C++ or large programming projects in general, navigating the file structure can be confusing and difficult at first, but it should become much easier to use once you are familiar. Across the team, we use pretty much the same basic structure for all of our board-specific projects but have a somewhat different structure for EVT-core. These structures are described below:
.github - Source code for GitHub actions
build - Files created during compilation
cmake - CMake utility files
docs - All files related to Documentation Generation (see below)
build - Generated documentation files
source - Source for some high-level documentation
include - Header files corresponding to files in src (What are header files?)
Directly mirrors the inner structure of src
libs - Libraries used throughout EVT-core
samples - Samples for all the features of EVT-core
example-sample - Example of how a sample folder should be structured
CMakeLists.txt - Compiler information
main.cpp - Source code
CMakeLists.txt - Compiler information
src - Source code, mirroring diagram in the Architectural Overview
dev - Specific device implementations
platform - MCU-specific device implementations
storage - Implementations for storage devices
io - Input/output implementations
platform - MCU-specific I/O implementations
platform - MCU-specific code
utils - Utility functionality used across the codebase
This section describes only the parts of the folder structure that are different from the EVT-core structure
src - Source code
dev - Specific device implementations that are only needed for this board
targets
board-name - Source for the target that will be run on the board during normal use
CMakeLists.txt - Compiler information
main.cpp - Source code
utility-task/dev-example
CMakeLists.txt - Compiler information
main.cpp - Source code
README.md - Explanation of the purpose of this target
CMakeLists.txt
Once you have a good grasp of the folder structure, you're likely going to want to start contributing some code to it yourself. To make sure you can do so successfully, it's best to follow some standard procedure. This does not apply to all files added to the projects we work on, but most of the source code you will add can be grouped into two categories: targets and classes. Targets are all the main.cpp files that exist in the targets and samples folders. These are the entry points that can be used to actually start executing code. Classes are blueprints for the objects used in the code of these targets to hold together related data and abstract some of the necessary logic away from the main program, according to the design patterns of Object-Oriented Programming. The procedure for creating these files is described below:
Create a new folder with your new target name in UpperCamelCase; for example : ExampleTarget
Create a C++ file inside the new folder called main.cpp with a single function: int main()
Create a text file inside the new folder called CMakeLists.txt and put the following inside it
If it's a sample for EVT-core:
include(${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/evt-core_build.cmake)
make_exe(ExampleTarget main.cpp)
If it's a program or sample for a specific board:
include(${EVT_CORE_DIR}/cmake/evt-core_build.cmake)
project(ExampleTarget)
cmake_minimum_required(VERSION 3.15)
make_exe(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC ${BOARD_LIB_NAME})
For the main program of a board, the folder is normally named BoardName and the target is defined as project(BoardNamemain)
Modify the CMakeLists.txt in the parent folder of the ExampleTarget folder (either samples or targets)
Add add_subdirectory(ExampleTarget)
This should be added in the correct place such that the targets remain in alphabetical order
Build the target
CLion
Run File → Reload CMake project
Select the target from the dropdown
Hit the Build button
Command Line
Run cmake ../
Run make ExampleTarget
Figure out where in the hierarchy of folders the class falls (dev, io, utils, etc.)
In the src hierarchy, create a C++ file with the same name as the class you intend to make using UpperCamelCase; for example: SampleClass.cpp
In the include hierarchy, create a corresponding header file with the same name: SampleClass.hpp
In the top level CMakeLists.txt add the new cpp file to the list of source files in the target_sources function call
target_sources(${PROJECT_NAME} PRIVATE
...
src/SampleClass.cpp
...
)
As a team, we use a modified form of Git Flow to develop EVT-core and our board-specific projects. This means that whenever we develop a new feature or fix a bug, we create a branch for that specific bug or feature off of the main branch. This branch is named following a standard format: (bug/feature)/(GitHubUsername)/(name-of-bug-or-feature) Ex: feature/mjmagee991/clang-format All the changes needed to implement the feature or fix the bug are then committed to this branch. Once completed, the person working on the branch makes a Pull Request to pull that branch into main. Once the pull request is made, other members of the team will review the code, following the following the steps described under "Using GitHub" on this page . For the Pull Request to be allowed to merge into main, it must have no requested changes and at least two approvals from other team members. It must also successfully pass all tests set up through GitHub actions. Once these requirements are met, the branch should be merged into main and then deleted to avoid clutter in the repo.
Our team has two main locations for documentation: this google site and the codebase itself. While other subteams of EVT generally rely solely on the google site, as programmers, it is much more convenient for us to keep some of our lower-level documentation in the code itself, as you would see in any other programming project. However, having two sources of information introduces opportunities for conflict as they can present conflicting information. If there is some information shared by both, one could be updated with newer information while the other is forgotten, or two different people could update each with information that derives from conflicting sources. Although some information is shared as of now, we have done our best to separate the content in each location to avoid duplication as much as possible. In the future, we should investigate moving to a completely in-code documentation method that can update the google site or another website, but our documentation generation (see below) will need to be fleshed out further to make that a reality.
This google site is home to all our high-level documentation: system-level descriptions, team organization, getting started guides, etc. It is primarily the role of the firmware lead to maintain this information, as it doesn't pertain directly to the code itself, although other members can assist in adding information, suggesting changes, or updating outdated guides. Before the google site, all of this documentation was kept in a Confluence wiki, which was archived before RIT stopped paying for Confluence in February of 2024. Although many of the previous practices have been abandoned, this archive does provide a great source of ideas for the future, both technical and administrative. Their workflow was very different than the one we use now, but basic ideas like the CAN Bootloader and meeting notes can still be reintroduced today. Once you get familiar with the current operations of the team, be sure to look back through these documents to see if there are any ideas you think would help the team work better. If you find something that might be helpful, start a conversation with the firmware lead to discuss how it might be integrated with current practices.
The code itself is another extremely important source of documentation for the team. By integrating clear documentation within the code, we can significantly reduce the learning curve for using EVT-core and maintaining our board-specific code. To ensure the clarity of this documentation, we enforce standards across the codebase. Generally, we use Doxygen comments to document our code, which allows us to use Doxygen for code generation. Doxygen commands offer a superset over Javadoc commands, but the majority of the commands we use are Javadoc. In addition to these standardized comment blocks, we also use some inline documentation, where it is needed, but that is not stressed nearly as much.
Although there are other times you should add documentation, below is a list of some common locations that documentation should always be written:
Targets
Every main.cpp file should have a docstring on the first line before the include statements to explain the purpose of that target
Classes
In its hpp file, every class should have a Doxygen comment explaining at a high level how it is used, why it exists, and any other relevant information that is not obvious
Every function declaration should have Doxygen comment following the format below:
/**
* Brief summary of the function which can go onto multiple
* lines if necessary
* NOTE: Any other important information someone using this
* function should know
*
* @param[in] var1 Description of an input
* @param[out] var2 Description of an output
* @return Description of the return value, unnecessary if
* the return type is void
*/
int foo(int var1, bool& var2);
How can a parameter be an output? (Read more here)
Every member variable should have a comment one line above it formatted /** like this */
cpp files only need inline documentation because their associated hpp file should contain all the necessary Doxygen documentation
Instead of relying only on the code itself to provide all the documentation developers need, we also host auto-generated documentation on a website that is publicly available. We use Doxygen to generate documentation automatically from the docstrings written in the code itself. Then, we use Sphinx to combine that documentation with some higher-level documentation written in reStructuredText that gives an overall explanation of the code. Lastly, we use Read the Docs to host the documentation online for free on sites like this. Although this is a somewhat convoluted combination of open source documentation technologies, when put together, they produce a very nice user and developer experience because so much of the documentation creation process is done automatically. Some Sphinx reStructuredText files do need to be written by hand, but in those files, you can reference the Doxygen-generated documentation to provide detail. If the repository you're working on already has Read the Docs set up, you can just create a branch to edit the necessary rst files, and the Read the Docs website will be updated when the branch is merged into main. If the repository doesn't have Read the Docs set up, you can follow the steps below:
Go to the Read the Docs website.
If you don't already have an account, sign up for one and make sure to connect it to your GitHub account, which should be a member of the RIT-EVT organization.
2. Navigate to the "My Projects" page.
3. Select "Import a Project" and select the repository you'd like to start hosting from the dropdown.
4. Most of the default project details should be fine, but be sure to double-check them to ensure everything is accurate.
5. On the project overview page, be sure to copy and paste one of the short URLs into the GitHub repository About section.
6. Back in Read the Docs, go to the the project Admin settings page and add evtbot as a maintainer of the project.
7. After some delay, the Overview page should update and confirm that "Your documentation is ready to use."