My template project for writing in the C programming language in 2024.
As a C beginner, it took me longer than expected to come up with a base project, after having enjoyed the modern toolchains of languages like Golang and Rust. Maybe you find this useful, too. Enjoy!
- Works on macOS, Debian/Ubuntu, and Windows 11 with Ubuntu in WSL.
- Works with neovim and Visual Studio Code as code editors, and likely with others, too.
- This project implements a main.c application that uses our toy
library helloc.h, implemented in helloc.c.
- For starters there are also additional examples.
- C language standard is C17
(a bug-fix version of the C11 standard), see
CMAKE_C_STANDARD
in CMakeLists.txt. - Compiler:
- Uses clang as the pre-configured compiler (see .env), along with tools such as clang-format and clang-tidy.
- You can also use gcc as the compiler. Simply set
the environment variable
CC
accordingly, e.g. in .env or in the shell environment withCC=gcc
orCC=gcc-14
.
- Build and dependency management:
- cmake with ninja , using a multi-config generator setup for ninja.
- Testing:
- Our toy library is tested with Unity , see unity_tests.c.
- Detects memory leaks, undefined behavior, and more with tools such as AddressSanitizer (ASAN) UndefinedBehaviorSanitizer (UBSan), and valgrind (valgrind is only supported on Linux). See valgrind.yml.
- Code coverage reports can be generated locally, see coverage.sh and the section below.
- Continuous Integration:
- GitHub Action workflows for CI/CD support. See workflow definitions.
- Code style:
- Uses clang-format and is configured in .clang-format. The GitHub Action definition at clang-format-check.yml checks (but does not enforce) this project's formatting conventions for source code automatically when code is pushed to the repository or when a pull request is created.
- Code quality:
- Code linting with clang-tidy as configured in .clang-tidy.
- Documenting the project:
- Docker support:
- Create and run Docker images for your C app. The Docker build uses a multi-stage build setup to minimize the size of the generated Docker image, which is only 9MB.
- Tooling:
See justfile if you want to run the commands manually, without
just
.
# Show available commands, including build targets
$ just
# TL;DR
$ just clean configure build
$ just run main
$ just test
-
Install the C toolchain used by this project.
# macOS $ brew install cmake llvm ninja $ brew install ccache # optional, for faster builds $ brew install gcc lcov # optional, for generating coverage reports $ brew install doxygen # optional, for generating documentation # Debian/Ubuntu $ sudo apt-get install -y clang clang-tidy cmake lldb ninja-build $ sudo apt install -y ccache # optional, for faster builds $ sudo apt-get install -y build-essential lcov # optional, for generating coverage reports $ sudo apt-get install -y doxygen # optional, for generating documentation $ sudo apt-get install -y valgrind # optional, for detecting errors with valgrind
Dependencies are managed with cmake. The entry point is the top-level CMakeLists.txt.
Manually managed dependencies are stored under the external/ folder. Example dependencies in this project are:
Some dependencies can also be installed via the operating system, e.g., with apt on Debian/Ubuntu or homebrew on macOS. This project does not use any such dependencies at the moment.
The code style is defined in .clang-format. See Clang-Format Style Options for details.
For example, if you want to indent your code with 2 spaces instead of the default 4 spaces for the "LLVM" style, update .clang-format as follows:
# .clang-format
BasedOnStyle: LLVM
IndentWidth: 2
TabWidth: 2
Some further interesting settings:
AccessModifierOffset: 0
IndentAccessModifiers: false # requires clang-format-13 or newer
Try
just coverage
first before manually running coverage.sh. Most likely, it will work out-of-the-box for you.
You can generate code coverage reports with coverage.sh.
Even though this project defaults to clang
as the compiler, generating code
coverage requires the gcc
toolchain as well as
lcov.
Install via:
# macOS
$ brew install gcc lcov
# Debian/Ubuntu
$ sudo apt-get install -y build-essential lcov
Then run the coverage script with the CC
environment variable set to your
GCC installation:
$ CC=gcc-14 ./tools/coverage.sh
Example output:
$ ./tools/coverage.sh
...
Generating output.
Processing file src/helloc.c
Writing directory view page.
Overall coverage rate:
lines......: 100.0% (1 of 1 line)
functions..: 100.0% (1 of 1 function)
Reading tracefile coverage.info
|Lines |Functions |Branches
Filename |Rate Num|Rate Num|Rate Num
==================================================
[/home/miguno/git/helloc/src/]
helloc.c | 100% 1| 100% 1| - 0
==================================================
Total:| 100% 1| 100% 1| - 0
The script also generates a report in HTML format.
Generate the documentation as per Doxyfile:
$ just docs
Then browse the documentation under generated-docs/
.
Man pages can be displayed with:
$ man generated-docs/man/man3/helloc.h.3
See Dockerfile for details. Requires Docker to be installed locally.
Step 1: Create the Docker image.
# Alternatively, run `./tools/docker/create_image.sh`.
$ just docker-image-create
Optionally, you can check the size of the generated Docker image:
# Alternatively, run `docker images miguno/helloc`.
$ just docker-image-size
REPOSITORY TAG IMAGE ID CREATED SIZE
miguno/helloc latest 0e55e8877994 31 minutes ago 8.45MB
Step 2: Run a container for the image.
# Alternatively, run `./tools/docker/start_container.sh`.
$ just docker-image-run
- Configure the project: From the command palette in VS Code
(Cmd+Shift+P),
run the
CMake: Configure
command. - Build the project: From the command palette in VS Code
(Cmd+Shift+P),
run the
CMake: Build
command, press the keyboard shortcut F7, or select theBuild
button in the status bar at the bottom.
- Getting the maximum of your C compiler, for security — security-related flags and options for C compilers