--- title: "My C Project Setup" date: 2024-03-28 --- For the last couple of months most of my projects have revolved around low-level C programming, with the most prominent one being [Lander](https://git.rustybever.be/Chewing_Bever/lander), my URL shortener. During this time I've developed a method for structuring my repositories in a way that works for me and my development style. In this post, I'll be detailing my approach! If you prefer looking at the structure directly, the basic structure's available as [a template](https://git.rustybever.be/Chewing_Bever/c-template) on my Gitea. ## Basic structure The basic structure for my repositories looks like this: ``` . ├── example ├── include │   └── project_name ├── src │   ├── _include │   │   └── project_name │   └── project_name └── test ``` Let's break it down. Naturally, `src` contains the actual source files, both those native to the project and those included from thirdparty libraries. `src/project_name` contains all source files native to the project, while thirdparty files are stored in their own subdirectories separated by library, or directly in `src`. For header files, we have two relevant directories. `include/project_name` contains all header files that are part of the public API for the library. `src/_include` on the other hand contains header files that are only used internally by the project. Here we once again have the same split where `src/_include/project_name` contains internal header files native to the project, while thirdparty header files can be placed either directly in `src/_include` or in their own subdirectories. Finally we have `test` and `example`. `test` contains unit tests, while `example` contains source files that illustrate how to use the library in a practical context. This setup seems to be fairly standard, and it works perfectly for me. To power a C project, we of course need some form of build system, so let's talk about *the Makefile*. ## The Makefile During my years of creating personal projects I started leaning more towards a lightweight development style. For a while I was a big fan of CMake, but for my projects it's way too complex. As a replacement, I opted for a hand-written Makefile. While I'm not going to go into detail on the specifics of the [Makefile](https://git.rustybever.be/Chewing_Bever/c-template), I will mention its most predominant features. First and foremost it supports compiling all required files and linking them into either a static library or a native binary, depending on the project. It allows all source files to include any header file from both `include` and `src/_include`. Unit tests and example binaries are compiled separately and linked with the static library. Unit tests are allowed to include any internal header file for more precise testing where needed, whereas example binaries only get access to the public API. The Makefile properly utilizes the `CC`, `CFLAGS` and `LDFLAGS` variables, allowing me to build release binaries and libraries simply by running `make CFLAGS='-O3' LDFLAGS='-flto'`. Make also allows running compilation in parallel using the `-j` flag, greatly speeding up compilation. A properly written Makefile really does make life a lot easier. It also solves a common issue with C compilation: header files. The usual bog-standard Makefile only defines the C source file as a dependency for its respective object file. Because to this, object files do not get recompiled whenever a header file included by its source file is changed. This can result in unexpected errors when linking. The Makefile solves this by setting the `-MMD -MP` compiler flags. `-MMD` tells the compiler to generate a Makefile in the build directory next to each source file's object file. These Makefiles define all included header files as a dependency for its respective object file. By importing these Makefiles into our main Makefile, our object files are automatically recompiled whenever a relevant header file is changed. The Makefile also contains some quality-of-life phony targets for stuff I use regularly: * `make lint` and `make fmt` use `clang-format` to lint and format the source files * `make check` runs `cppcheck` (and possibly other tools in the future) on the source code, notifying me of obvious memory leaks or mistakes * `make test` compiles all test binaries and runs the unit tests * `make run` compiles and runs the main binary * `make build-example` builds all examples * `make bear` generates a `compile_commands.json` file using [Bear](https://github.com/rizsotto/Bear) (the `clangd` LSP server requires this to work properly) * `make clean` removes all build artifacts ## Testing My setup currently only supports unit tests, as I haven't really had the need for anything more complex. For this, I use [acutest](https://github.com/mity/acutest), a simple and easy to use header-only testing framework that's perfect for my projects. It's fully contained within a single header file that gets imported by all test files under the `test` directory. By having the testing framework fully contained in the project it also becomes very easy to run tests in a CI. If the CI environment can compile the library it can also run the tests without any additional dependencies required. ## Combining projects My projects, specifically libraries, often start as part of a different project (e.g. [lnm](https://git.rustybever.be/Chewing_Bever/lnm) used to be part of [Lander](https://git.rustybever.be/Chewing_Bever/lander)). As the parent project grows, some sections start to grow into their own, self-contained unit. At this point, I take the time to properly decouple the codebases, moving the new library into its own subdirectory. This subdirectory then gets the same structure as described above, allowing the parent project to include it as a static library. This approach gives me a lot of flexibility when it comes to testing, as well as giving me the freedom to separate subprojects into their own repositories as desired. Each project functions exactly the same if it's a local subdirectory or a Git submodule, allowing me to easily use my libraries in multiple projects simply by including them as submodules. ## Outro That was my C project setup in a nutshell. Maybe this post could be of use to someone, giving them ideas on how to improve their existing setups. As is standard with this blog, this post was rather technical. If you got to this point, thank you very much for reading. Jef