# Build System Notes

This project is built using CMake. If you're not familiar with CMake, are unsure of how to tackle a task, or running into a confusing problem, feel free to [contact Phillip](mailto:phillip@embeddedartistry.com).

For the most part, the existing build structure should be suitable for the project, and you will primarily need to add new files to the build.

**Table of Contents:**
- [Structural Overview](#structural-overview)
- [Target Organization](#target-organization)
- [Adding New Files](#adding-new-files)
- [Adding a New External Dependency](#adding-a-new-external-dependency)

## Structural Overview

The code is organized according to the following layering hierarchy, starting from the "bottom" up. Layers can use the capabilities provided by lower layers, but not higher layers.

- [Interfaces](CPF/Interfaces) defines abstract interfaces that are safe to use throughout the system
- [Support](CPF/Support/) contains a catch-all for common, platform-independent code that can be utilized by multiple components in the system. This includes:
	+ Controls algorithms
	+ Utilities like endianness swapping, safer volatile-operations
	+ Logger
	+ Queues
- [HwPlatform](CPF/HwPlatform/) contains all of the code related to the Hardware Platform / bsp.
	+ This is where you will find things like STM32 processor configuration code, processor peripheral drivers, external component drivers, and the BSP abstractions.
	+ The Hardware Platform layer also has its own internal layered hierarchy:
		- [STM32H7](CPF/HwPlatform/), which contains startup code, core processor configuration, and interrupt/exception handler defaults
		- [Devices](CPF/HwPlatform/Devices), which contains drivers for non-processor components
		- [FatFs](CPF/HwPlatform/FatFs/), which contains device-specific code related to the FAT filesystem that is used on the SDCard
		- [SDLogger](CPF/HwPlatform/SDLogger), which contains the SD driver code and the SD logger implementation
		- [bsp](CPF/HwPlatform/bsp), which contains the implementation of the hardware-layer abstractions that are used by the hardware-independent portions of the system
			+ STM32 peripheral driver implementations are found here as well
- [ErrorHandler](CPF/ErrorHandler/) contains the system error handler implementation
- [State](CPF/State/), which contains the implementations for the system-level states and their actions

Top-level application logic is kept in the top-level CPF folder:

- main.cpp kicks off the application
- CPFStateProcessing.cpp contains the main event/state processing loop code

External dependencies are kept in a separate source tree within the `external` folder. This includes components like:

- cpputest for unit testing
- ETL for static-memory containers, useful embedded frameworks, and other items
- FatFs common code
- stm32h7 SDK
- SysProgsProfiler, which handles profiling and semihosting when debugging

## Target Organization

For the most part, each distinct folder creates three types of targets (listed below). The chosen structure allows for information hiding within the implementations of each layer. Layers can define include dependencies that are _private_ and only apply to its source files - they don't need to be forwarded on to those using the layer. This is especially critical when working with the BSP. The BSP implementations can have full access to STM32 processor-specific headers, as well as specific device driver implementations. But users of the BSP can only access the abstract device interfaces and public BSP headers. This structure, and the decision to hide processor- and hardware-specific headers, is what enables running tests on the build machine.

- A static or interface library target, which builds the source files for the given layer. This library target is linked in the final application targets. 
	+ Some files are linked with `INTERFACE` scope to defer compilation to the final application build step. This is an exception to the normal implementation, however.
```cmake
add_library(cpf_devices STATIC)
target_include_directories(cpf_devices PUBLIC . GPSUBlox LTC2946_EnergyMonitor)
target_sources(cpf_devices PRIVATE
	ctdSBE41.cpp
	ctdSBE41.hpp
	GPSUBlox/GPSUBlox.cpp
	GPSUBlox/GPSUBlox.hpp
	GPSUBlox/__GPSUBlox_helpers.cpp
	GPSUBlox/__GPSUBlox_helpers.hpp
	LTC2946_EnergyMonitor/LTC2946_EnergyMonitor.hpp
	LTC2946_EnergyMonitor/LTC2946_EnergyMonitor.cpp
	LTC2946_EnergyMonitor/__LTC2946_definitions.hpp
	LTC2946_EnergyMonitor/__LTC2946_LinearDriver.hpp
	LTC2946_EnergyMonitor/__LTC2946_LinearDriver.cpp
	TimerManager.hpp
	ElmoTwitter.cpp
	ElmoTwitter.hpp
	BellowsValve.cpp
	BellowsValve.hpp
	DevicePowerSwitch.cpp
	DevicePowerSwitch.hpp
	BellowsPositionADC2485.cpp
	BellowsPositionADC2485.hpp
)

if (CMAKE_CROSSCOMPILING)
	target_link_libraries(cpf_devices PRIVATE stm32h7_target_include_dep)
endif()
target_link_libraries(cpf_devices PUBLIC
	etl::etl
	logger_include_dep
	cpf_interfaces_include_dep
)
```
- An "include dependency" target, which is an `INTERFACE` library target that only specifies target include directories. This dependency can be linked in targets that need access to the headers provided by the layer.
```cmake
add_library(cpf_devices_include_dep INTERFACE)
target_include_directories(cpf_devices_include_dep INTERFACE . GPSUBlox LTC2946_EnergyMonitor)
target_link_libraries(cpf_devices_include_dep INTERFACE etl::etl)
```
- One or more "testing" library targets. These build the files used for the unit testing targets.
```cmake
add_library(cpf_devices_tests INTERFACE)
target_include_directories(cpf_devices_tests INTERFACE .)
target_sources(cpf_devices_tests INTERFACE
	TimerManager_tests.cpp
)

add_library(cpf_hw_driver_build_host_tests INTERFACE)
target_include_directories(cpf_hw_driver_build_host_tests INTERFACE .)
target_sources(cpf_hw_driver_build_host_tests INTERFACE
	ElmoTwitter_tests.cpp
	GPSUBlox/GPSUBlox_tests.cpp
	BellowsPositionADC2485_tests.cpp
)
target_link_libraries(cpf_hw_driver_build_host_tests INTERFACE cpf_devices)
```

## Adding New Files

When you create new `.cpp` and `.hpp` files, you need to add them to the appropriate target. That will be either the `CMakeLists.txt` in the same directory, or if there isn't one, in the file of the same name in the containing directory. The files will be added in a `target_sources` call for the appropriate build target. If you need to include a new directory to the include header search list (e.g., for a newly created subdirectory), you will need to add the directory name to the `target_include_directories` call for the appropriate build target.

> **Note:** It is not strictly necessary to add `.hpp` files to the `target_sources` call, but it does make the file appear in the CMake target view in VisualGDB.

For example, if you want to add a new abstract interface, create your files in `CPF/Interfaces` (or a subfolder) and add them to the `target_sources(cpf_interfaces ...)` function. The files should be specified as relative paths based on their position to the current `CMakeLists.txt` file. If you're in the same directory, just list the file name.

```cmake
target_sources(cpf_interfaces INTERFACE
	system_time.cpp
	system_time.hpp
	# ...
	events/event_base_types.hpp
	events/event_handler.hpp
	events/event_queue.hpp
	events/event_queue_interface.hpp
	events/CPFEventTypes.hpp
	
	# add your files to this list
)
```

If you've added the interface header in a subdirectory, you'll need to make sure the directory gets picked up in the include paths. In this case, you will need to modify the `target_include_directories(cpf_interfaces ...)` and `target_include_directories(cpf_interfaces_include_dep ...)` functions to reference the new directory. (You could, of course, not do this and instead reference the header via `#include <subdir/header_name.hpp>`.)

```cmake
target_include_directories(cpf_interfaces INTERFACE . events YOUR_NEW_DIRECTORY)

target_include_directories(cpf_interfaces_include_dep INTERFACE . events YOUR_NEW_DIRECTORY)
```

If you want to add a new device driver, you can create your files in `CPF/HwPlatform/devices` (or a subfolder) and add the files to the `target_sources(cpf_devices ...)` function.

```cmake
target_sources(cpf_devices PRIVATE
	ctdSBE41.cpp
	ctdSBE41.hpp
	GPSUBlox/GPSUBlox.cpp
	GPSUBlox/GPSUBlox.hpp
	GPSUBlox/__GPSUBlox_helpers.cpp
	GPSUBlox/__GPSUBlox_helpers.hpp
	LTC2946_EnergyMonitor/LTC2946_EnergyMonitor.hpp
	LTC2946_EnergyMonitor/LTC2946_EnergyMonitor.cpp
	LTC2946_EnergyMonitor/__LTC2946_definitions.hpp
	LTC2946_EnergyMonitor/__LTC2946_LinearDriver.hpp
	LTC2946_EnergyMonitor/__LTC2946_LinearDriver.cpp
	TimerManager.hpp
	ElmoTwitter.cpp
	ElmoTwitter.hpp
	BellowsValve.cpp
	BellowsValve.hpp
	DevicePowerSwitch.cpp
	DevicePowerSwitch.hpp
	BellowsPositionADC2485.cpp
	BellowsPositionADC2485.hpp
	
	# Add your new devices here.
)
```

If you put your device driver in a subfolder, you will need to modify the `target_include_directories(cpf_devices ...)` and `target_include_directories(cpf_devices_include_dep ...)` functions.

```cmake
target_include_directories(cpf_devices PUBLIC . GPSUBlox LTC2946_EnergyMonitor YOUR_NEW_DIRECTORY)

target_include_directories(cpf_devices_include_dep INTERFACE . GPSUBlox LTC2946_EnergyMonitor target_include_directories(cpf_devices_include_dep INTERFACE . GPSUBlox LTC2946_EnergyMonitor))
```

## Adding a New External Dependency

1. Make a new folder inside of `external/`, and add the external dependency's files there.
	- Example commit: e98807e0b40b42ee2333d5366757ca08efab1849
2. Build the dependency
	1. If the project is built with CMake, you can add it to the build by adding at least one `add_directory()` call in `external/CMakeLists.txt`.
		```cmake
		add_subdirectory(stm32h7)
		```
		+ You will need to place the call(s) in the appropriate conditional logic blocks depending on the intended use
			* Within `CMAKE_CROSSCOMPILING` if it will run on the device
			* Within the `USING_VISUAL_GDB` block if the dependency will only run when building with VisualGDB
				- You will likely add no more dependencies here
			* Within the top-level `else` block if it will run on the build machine (e.g. for test builds)
		+ Example commit: 8f7b47cb413e24007b9eada9f31ce8a8d0a4b627
	2. If the project is not built with CMake, you will need to add CMake build targets for the files.
		+ Create a `CMakeLists.txt` file within the dependency folder
		+ Recommended build structure is as follows, but this is not a requirement:
			* A static library target that builds the source files, which will be linked by the final application
			* An include dependency target that can be used to make available include header paths available to consumers:
			```cpp
			add_library(bsp_include_dep INTERFACE)
			target_include_directories(bsp_include_dep INTERFACE . STM32Drivers)
			```
3. Adjust compilation flags for the target to disable warnings that you don't plan to address. You can also disable necessary targets or options that you don't want to use.
	- You can directly modify the build files to set compile options that are required
		+ Example commits: 31aaf88e2d4f2ec69876fa93fc683f156fa3f044 00b53d1fa1bcd1bb69ed01bf8592818b9638531f 94477a4c6ab69712897440f1f901757b1c798096
	- You could also modify options externally, such as is done for CPPUTest. See [`external/cpputest/CMakeLists.txt`](../external/cpputest/CMakeLists.txt).
4. Link the new library (or include dependency) in the place(s) it will be used by adding it to the `target_link_libraries` calls for the appropriate targets.
5. Link the dependency with the application and test builds that it applies to.
	- Example commit: 77792b886585823c15d8ffe5c7ccb8cdc2b0603e

## Controlling Configurations

Sometimes, it is only appropriate to build specific files, build specific targets, or link against specific libraries in specific build configurations.

One common conditional you can use for is `CMAKE_CROSSCOMPILING`, which is true when building for the device.

```cmake
# This allows building/linking cpf_devices for unit tests that are run
# on the build host.
if (CMAKE_CROSSCOMPILING)
	target_link_libraries(cpf_devices PRIVATE stm32h7_target_include_dep)
endif()

# This test target is only supported on the build machine
if(NOT CMAKE_CROSSCOMPILING)
	add_subdirectory(mocks)

	add_executable(CPF_build_machine_tests)
	target_sources(CPF_build_machine_tests PRIVATE
		TestMain.cpp
		TestEventProcessing.cpp
	)
	target_link_libraries(CPF_build_machine_tests PRIVATE
		cpf_support_tests
		cpf_hw_driver_build_host_tests
		cpf_mock_tests
		cpf_testing_mocks
		cpf_offtarget_interfaces_tests
		etl
		CppUTest
		CppUTestExt
	)

	if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC"))
		target_compile_options(CPF_build_machine_tests PRIVATE -Wno-old-style-cast)
	endif()

	add_test(
		NAME CPF.offtarget
		COMMAND CPF_build_machine_tests
	)
endif()
```

If you need to handle a special case for a specific compiler, you can check the `CMAKE_CXX_COMPILER_ID` value.

```cmake
# Only add this build option if we're not compiling with MSVC
if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC"))
	target_compile_options(CPF_build_machine_tests PRIVATE -Wno-old-style-cast)
endif()
```

You can also condition the build based on whether or not you are building for Visual GDB

```cmake
if(USING_VISUAL_GDB)
	add_subdirectory(cpputest/cpputest-visualgdb)
	add_subdirectory(SysProgsProfiler)
endif()
```
