C Programming — All Over Again
Intro
Writing code has always been a passion of mine. From the first time I wrote C# in high school to learning C in college, 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 up 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 wrote C code, and I’ve learned a thing or two about being a better developer since I first entered the DevOps dimension. For example, I learned the importance of testing and why pipelines are crucial to avoid mistakes.
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:
- Development environment
- Ceedling framework
- 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, to develop C according to my flavor, I need a Linux distribution as a base, a 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 interactively,
# 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. Specially designed for unit testing embedded systems, it is also useful for other types of testing. Unity allows us to write unit test and assert the behaviour of programs.
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 for 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 with 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
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:
Screenshot 2024-03-18 at 14.49.18.png
Read more about our advanced DevOps solutions