I created an example project so you guys can have a basic idea of how to structure your code. There are a few pieces of advice for your projects that will make your life much better in the long run.
For Version control the industry standard is Git. Learn it at a basic level, the rest will come with time.
For our build system we will be using CMake. It supports pretty much everything out there, but its main focus is C and C++.
For testing, we will be using CTest. This is a simple testing interface that is incorporated with CMake. It merely runs the tests.
For the test themselves you can either write a bare-bones wrapper by hand, or use a library like Unity (What I use) to test your code.
For Documentation there is your good ole README file, and that’s always a requirement. F auto-documentation we’ll use Doxygen.
Doxygen can also output to Sphinx which is the tool I used to make my python doc sites, but for now we’ll make a bare-bones site with Doxygen itself.
Git pretty much keeps track of changes in your project, and it allows you to revert changes if you screw up. It also allows other people to work on the project with you, and to merge your code without causing as many problems.
Here’s the basic rundown of how to use Git
My basic workflow goes like this. Create a git directory with
cd directory
git init
Then I create a .gitignore file and add any files I don’t want tracked to it.
From there on out my workflow is very simple.
# Add everything in this directory recursively to be added with this commit
git add .
git commit -m "Message detailing what I did, what I changed, or what I absolutely just broke :)"
# And if I need to push remotely I do
git push -u origin main
That’s it. If I screw up I can always fix it later. I don’t bother showing you how to merge or anything because for now I just want you to have a backups of your changes, and keep everything tracked properly.
CMake is a cross-platform build tool for blah blah blah.
It allows you to compile your libraries and your code easily. It also has testing incorporated, and it’s a necessity for C or C++ projects. There are other tools, but CMake is the industry standard.
At the top level of your project you will have a CMakeList.txt file
This is pretty much the master control file for the project. In your sub folders you’ll have files under the same name with more CMake commands. You pretty much do this recursively till the build tool has your entire project mapped out.
Here’s the top level CMakeList.txt for this project commented up for you.
#Set min version
cmake_minimum_required(VERSION 3.20)
#Set project name, version, and support languages
project(c_example VERSION 0.1 LANGUAGES C)
#For example to support C and C++
#project(c_example C C++)
#Use C 11 and disable compiler extensions
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
#Or set the standard for a particular library
#set_target_properties(lib_a PROPERTIES
# C_STANDARD 11
# C_STANDARD_REQUIRED ON
# C_EXTENSIONS OFF
#)
#Set compiler flags for all versions
set(CMAKE_C_FLAGS "-Wall -Wextra")
#include(Ctest)
enable_testing()
add_subdirectory(src/libs)
add_subdirectory(src)
add_subdirectory(tests)
#Example setup for GNU Make for the release directory
#cmake -G "Unix Makefiles" -S . -B ../build/release/
#cd ../build/release
#make
#Example setup for Ninja for the debug directory
#cmake -G Ninja -S . -B ../build/debug/
#cd ../build/debug
#ninja
#Or just use CMake to avoid the low level build tools for either
#cmake --build ../build/debug/ --config Release --target tes
This file doesn’t do much for you in my case, all my building is handled inside the subdirectories that I add.
It’s important to set an up-to-date CMake required version, and to set the proper C standard.Compiler flags can be changed as needed.
Let’s take a look at the src directories CMakeList.txt file
#Set compiler flags
set(CMAKE_C_FLAGS "-Wall -Wextra")
add_executable(main main.c)
target_link_libraries(main PRIVATE lib_b)
add_subdirectory(libs/Unity
This sets flags for this scope, creates an executable main that depends on main.c, and creates a link telling the compiler main depends on lib_b.
I add the Unity Subdirectory, and it builds and installs the Unity test framework.
lib_b is added in the subdirectory below that I added in the top level file. Here is that file.
add_library(lib_a example_lib_a.c)
add_library(lib_b example_lib_b.c)
target_link_libraries(lib_b PRIVATE lib_a
You can see here that I create 2 libraries called lib_a, and lib_b from their respective source files. I tell the build tool that lib_b depends on lib_a, and the build tool handles the rest for me.
When this is all said and done you can run the configuration commands shown in the top level CMake to set up the build system, and then to build your code.
The only CMakeList.txt file I failed to mention is in tests. We’ll go over that here.
#Create executables for tests
enable_testing()
#Basic testing without a framework
add_executable(test_pass test_pass.c)
add_test(test_pass test_pass)
add_executable(test_fail test_fail.c)
add_test(test_fail test_fail)
#Testing using the Unity Framework
add_executable(test_example_lib_b test_example_lib_b.c)
target_link_libraries(test_example_lib_b PRIVATE unity)
target_link_libraries(test_example_lib_b PRIVATE lib_b)
add_test(test_example_lib_b test_example_lib_b
When it comes to testing you need to enable at the top level, and at the sub-level that you’ll be running the test.
At a basic level all you need to do is add executables for your test, and list the executable as a test.
If you do this as shown, CTest will run them all for you.
CTest is very simple, if the program returns 0, it passes. If it doesn’t, it fails. The first two examples show how simple tests really are, and the third example shows testing using a framework.
Unity is the framework that we’ll be using in this case.
Unity pretty much just allows you to assert some condition, and it will return zero if true, or false if not. It also has a lot of niceties like error messaging and comparing in certain bases like hex comparison, etc.
You can learn how to use their framework here.
In the end, as long as your code is built all you need to do is move into the build directory and run
ctest
You’ll get output like this
patrick@patrick-desktop:~/ws/club/projects/c_example/build/debug$ ctest
Test project /home/patrick/ws/club/projects/c_example/build/debug
Start 1: test_pass
1/3 Test #1: test_pass ........................ Passed 0.00 sec
Start 2: test_fail
2/3 Test #2: test_fail ........................***Failed 0.00 sec
Start 3: test_example_lib_b
3/3 Test #3: test_example_lib_b ...............***Failed 0.00 sec
33% tests passed, 2 tests failed out of 3
Total Test time (real) = 0.00 sec
The following tests FAILED:
2 - test_fail (Failed)
3 - test_example_lib_b (Failed)
Errors while running CTest
Output from these tests are in: /home/patrick/ws/club/projects/c_example/build/debug/Testing/Temp...
Use "--rerun-failed --output-on-failure" to re-run the failed cases verbosely.
Don’t worry, those fails are meant to occur. Those are just to show you how fails work.
There are two levels of documentation. A simple README showing what your code does, and how to use it is always required. This is the first thing people see when it comes to your project.
When it comes to working on a large project though it’s often useful to have an autodocumentation tool. For this, we’ll use Doxygen. Doxygen is an autoparser that will read through your code, document the functions, classes, structs, pretty much anything in there and build a website to show you all the information.
To install it run
sudo apt install doxygen
sudo apt install doxygen-gui
Simply go into your docs directory and run doxygui, it’s pretty self explanatory when your in the application. Make sure to save the config to a Doxyfile, and when your ready to build do
cd docs
doxygui
doxygen Doxyfile
Inside the documents folder you will now have a build folder that contains a html site documentation your project. Open the index.html to see the homepage.
You can either select to document all code automatically, or only things commented in Doxygen style when you’re in the GUI.
To learn how to use Doxygen style comments check this out.
This write-up by no means goes into depth on any of these things. This is a high-level overview and you’re meant to download the repo and look at it yourself to get a better understanding.
What I do want you to understand is that you need a build system, you need some level of documentation, and if you’re making a substantial project, you need testing.
The more you invest at startup, the less painful it will be to have to implement this stuff down the road. Best of luck with your projects :)