C Programming — All Over Again

Lior Dux
6 min readFeb 29, 2024

--

Intro

Writing code has always been a passion of mine. From the first time I wrote C# in high school, all the way to learning C in collage — I am always left astonished. The sheer power, knowledge base, and deep understanding needed; it’s heaven and hell altogether.

A new hobby I’ve picked on my embedded journey, is working with Arduino. So I’ve decided to get back on the horse and improve my C programming skills as well.

However, it’s been a couple of years since I last written C code, and I’ve learned a thing or two about being a better developer since I first entered the DevOps dimension.

The Goal

The main purpose of this article is to create a GitHub repository with everything needed to program C effectively and efficiently. By simply forking the repo, to begin with, my repository would be set up and ready to go for any other C project I (or anyone else) plan to do in the future.

The Plan

The plan is to create a “template” repository, generically set up with Docker, docker-compose, and pipelines ready to go. Therefore, in any future C projects I’d like to work on, I’ll have everything set up and leverage the ability to code from anywhere. In this article, I’ll go through:

  1. Development environment
  2. Ceedling framework
  3. CI/CD Pipelines using Github Actions

Develop using Docker

I can’t even count my fingers about how many times I’ve been to a college lecture and suddenly realized I’ve made an error on my assignment, and my laptop is not with me. I borrowed a friend’s laptop, which is obviously running a different platform than mine and does not contain the tools I needed. In the future, I will use Docker to avoid any of this.

So in order to develop C according to my flavor requires a linux distribution as base, GCC compiler, Unity for unit testing, GDB for debugging, and Valgrind for memory leaks.

Ceedling project owner does offer a docker image, however the tool and the image (official) are both out-of-date, and present many security risks. So I ended up building my own image, and publishing it over DockerHub.

How to use?

# This will start the container and run it interactivly,
# with /project-demo directory binded into the running container at /project
# edit files withing the container or use your host's editor
docker compose up --detach; docker exec --interactive --tt
y c-dev /bin/bash

Ceedling Framework

Ceedling offers more than just a plain building system, it combines multiple tools together, leveraging the powers of Unity, CMock & CExeption.

Unity

Unity is an xUnit-style test framework for unit testing C. It is written completely in C and is portable, quick, simple, expressive, and extensible. It is designed to especially be also useful for unit testing for embedded systems. Unity allows us to write unit test and assert the behaviour.

CMock

CMock is a framework for generating mocks/stubs based on a header API. Support for CMock is integrated into Unity. CMake automatically generates the required test runner file and the mock files.

CExeption

CException is a project released by Throw The Switch. CException is designed to provide simple exception handling in C using the familiar try/catch/throw syntax.

CException is implemented in ANSI C and is highly portable. As long as your system supports the standard library calls setjmp and longjmp, you can use CException in your project. If you’re looking for an exception library to use on embedded systems, CException is for you.

How to use?

# Create a new project using ceedling.
ceedling new project zMynxx

The following folder structure will be created, along wilth the configuration to build it correctly →project.yml.


zMynxx/
|-- project.yml
|-- src
`-- test
`-- support
`-- .gitkeep

3 directories, 2 files

Ceedling can also generate modules for us:

# Create a module (/src/demo_module.c, /src/demo_module.h, /test/test_demo_module.c) for us.
ceedling module:create[demo_module]

Run all test or create a release

# Run all test
ceedling test:all

# Test only the demo_module
ceedling test:demo_module

# Create a release - Make sure release is set to TRUE on your project.yml!
ceedling release

CI / CD Pipelines using GitHub Actions

Automation makes our lives easier, and helps us avoid mistakes and bugs. I’ll create a simple CI, CD pipeline using GitHub Actions.

CI:

#/.github/workflows/ci.yaml
---
name: CI

on:
# Manually triggered testing
workflow_dispatch:

# Check every PR to main
pull_request:
branches:
- main

jobs:
docker-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build locally
uses: docker/build-push-action@v5.1.0
with:
context: .
file: Dockerfile.ubuntu
platforms: linux/amd64
push: false
load: true
tags: ${{ github.repo }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Run all Tests using Docker
run: |
docker run \
--interactive \
--rm \
--volume ./project-demo:/project \
${{ github.repo }}:${{ github.sha }} \
ceedling test:all

CD:

#/.github/workflows/cd.yaml
---
name: CD

on:
# Publish on every approved PR
pull_request:
types:
- closed
branches:
- main

permissions:
id-token: write
contents: write
packages: write

jobs:
check:
name: check pr status
runs-on: ubuntu-22.04
steps:
- name: check if PR is merged
uses: zmynx/github-actions/.github/actions/git/check-merge@feature/gha

cd:
name: continuous-deployment
needs: [check]
runs-on: ubuntu-22.04
outputs:
new_tag: ${{ steps.semver.outputs.new_tag }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build locally
uses: docker/build-push-action@v5.1.0
with:
context: .
file: Dockerfile.ubuntu
platforms: linux/amd64
push: false
load: true
tags: ${{ github.repo }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Generate Release using Docker
run: |
docker run \
--interactive \
--rm \
--volume ./project-demo:/project \
${{ github.repo }}:${{ github.sha }} \
ceedling release

- name: Semantic Versioning
id: semverv2
uses: zmynx/github-actions/.github/actions/git/semver-v2@feature/gha
with:
fallback: "0.1.0"
prefix: "v"

- name: Downcase PR body
id: downcase
shell: bash
env:
PR_BODY: ${{ github.event.pull_request.body }}
run: echo "pr_body_lowercase=${PR_BODY,,}" >> $GITHUB_OUTPUT

- name: Parse the body for any '#major' or '#minor' keywords
id: new_tag
shell: bash
env:
NEW_TAG: ${{ contains(steps.downcase.outputs.pr_body_lowercase, '#major') && steps.semverv2.outputs.major || contains(steps.downcase.outputs.pr_body_lowercase, '#minor') && steps.semverv2.outputs.minor || steps.semverv2.outputs.patch }}
run: echo "new_tag=${NEW_TAG}" >> $GITHUB_OUTPUT

- name: Publish GitHub Release
uses: softprops/action-gh-release@v0.1.15
with:
tag_name: "${{ format('{0}{1}', 'v', steps.new_tag.outputs.new_tag) }}"
files: |
project-demo/build/artifacts/release/MyApp.out
ceedling-0.32.0-d76db35.gem
LICENSE

Bonus — Linting

Linting is the process of running a program that will analyse code for potential errors.

See lint on Wikipedia:

lint was the name originally given to a particular program that flagged some suspicious and non-portable constructs (likely to be bugs) in C language source code. The term is now applied generically to tools that flag suspicious usage in software written in any computer language.

We can take advantage of a tool named “Trunk”, to run linting checks as we wish:

#/.github/workflows/trunk-check.yaml
---
name: Pull Request Trunk Check
on: [pull_request]
concurrency:
group: ${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions: read-all

jobs:
trunk_check:
name: Trunk Check Runner
runs-on: ubuntu-latest
permissions:
checks: write # For trunk to post annotations
contents: read # For repo checkout

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Trunk Check
uses: trunk-io/trunk-action@v1.1.10
Happy Coding!

Summary

Our Repository is all setup and ready for replications.

Update: In another article named Dockerfile — Kick it up a notch! I have taken this template to another level. If you wish to use it, click here and select the following:

Use this as a template!

--

--

Lior Dux

27 years old DevOps Engineer living in Israel Loves computers, tech, security, automations and embedded.