clang-format

Embedded Artistry "format" Repository

We recently learned how to use clang-format and created our own formatting style. I also shared clang-format wrapper scripts that I use on my projects.

I've created an Embedded Artistry format repository which contains my clang-format rules, helpful wrapper scripts, and an installation script.

The format repository is meant to be included in other repositories as a submodule. In most of my repositories, I usually include this in a tools/ directory. Once you have submodule'd the repository, call the setup.sh script. This script will search up the directory tree to find a parent .git repository. When found, that is assumed to be the root of the source tree and the .clang-format reference file is installed.

Fork this repository and create your own style rules to use with your projects.

A Strategy for Enforcing Formatting with Your Build Server

Now that we've learned to use clang-format and created style rules for our project, we need to make sure we're enforcing clang-format usage.

In an optimal flow, we would enforce formatting before allowing changes to be checked in. A common strategy would be to enforce formatting using a git commit hook. However, I have two problems with enforcing formatting with a git hook.

First, it can be a major disruption to the development flow. I don't necessarily want my code to be auto-formatted whenever I commit, as I may want to go back and disable clang-format around some sections of specifically formatted code. I also don't want commits to be rejected while I'm initially working through changes and testing for functionality.

Second, the use of git hooks requires users to install the hooks on their end. It's hard to fully enforce that flow, especially since you're relying on developers to remember that hooks need to be installed.

Instead, I've decided to take it out of the developer's hands and enforce it using my build server. By combining Jenkins with GitHub protected branches and status checks, we can ensure that all changes are checked for formatting before they are merged into master.

When a continuous integration build is triggered (due to submitting a pull request or by committing a new change to a monitored branch), we run a format step as part of the build pipeline. The format step uses the clang-format-patch wrapper script from the format repository. After the build and test stages complete, we use the clang-format-patch script and check for the existence of a patch file. If there are formatting changes needed, the build will fail and the patch file will be archived for the developer to reference.

The format build stage also reports in to GitHub using its own status string. This allows us to monitor pull requests and see which stage of the CI build failed and to block merges until formatting is acceptable. If you are interested in reporting a specific formating status for your own projects, please refer to my reportFormatStatus function in the jenkins-pipeline-lib.

I have been using this flow on my managed projects for the past few months and have found that it is a great middle ground between enforcing formatting and letting developers keep their own flow.

Further Reading

clang-format Wrapper Script Examples

We recently learned how to use clang-format and created our own formatting style.

Today I'd like to show you some of the scripts I use to wrap clang-format for different purposes. For any non-trivial use of clang-format, you probably want to use a wrapper.

Some Notes on Options

You'll see some common arguments used in the script samples below, so I want to explain them up front.

The style argument below always indicates that I want to pull my formatting rules from the .clang-format file in my repository:

-style=file

I use the in-place argument to indicate that I want files to be modified:

-i

I also set the fallback-style to none so that clang-format will fail if there is no .clang-format file in the tree.

-fallback-style=none

Formatting all C and C++ Files in a Repo

In this example, I use the find command to get all the .h, .c, .hpp, and .cpp files in my tree. The list of files is then passed to clang-format.

#!/bin/bash

find . -iname *.h -o -iname *.c -o -iname *.cpp -o -iname *.hpp \
    | xargs clang-format -style=file -i -fallback-style=none

exit 0

Formatting all C and C++ Files in a Repo, with Exclusions

In this example, I supply a list of directory paths to find that I want excluded from my list. This is useful for handling parts of the source tree that you don't want to reformat, such as external libraries.

#!/bin/bash
find . -type d \( -path ./lib/external -o -path ./os -o -path ./lib/libc -o -path ./lib/libmemory \) -prune \
    -o -iname *.h -o -iname *.c -o -iname *.cpp -o -iname *.hpp \
    | xargs clang-format -style=file -i -fallback-style=none

exit 0

Formatting all C and C++ Files in a Directory

This example uses the find command with a directory name supplied as a script argument. All source files in that directory will be formatted.

#!/bin/bash

if [ -z "$1" ]
then
    echo "Please supply a directory or path with this script."
    exit 1
fi

find $1 -iname *.h -o -iname *.c -o -iname *.cpp -o -iname *.hpp \
    | xargs clang-format -style=file -i -fallback-style=none

exit 0

Format a List of Files (Supplied as Arguments)

This example allows you to provide a list of files as an argument to the script. Arguments are simply forwarded to clang-format.

#!/bin/bash

# This script propagates all arguments to the clang-format command
clang-format -style=file -i -fallback-style=none $@

exit 0

Format Files in a git diff

This example gets the list of modified files from git ls-files and formats them. This is a useful script to run as a pre-commit exercise.

#!/bin/bash

clang-format -style=file -fallback-style=none -i `git ls-files -om "*.[ch]" "*.[hc]pp"`

exit 0

Run clang-format and Create a Patch

This example is used by my continuous integration build to verify formatting compliance. After clang-format is run, we use git diff to generate a patch file. If the file is empty, formatting is OK and we delete the file. The build server uses the presence of the patch file to determine if formatting should pass or fail.

#!/bin/bash

find . -iname *.h -o -iname *.c -o -iname *.cpp -o -iname *.hpp \
    | xargs clang-format -style=file -i -fallback-style=none

git diff > clang_format.patch

# Delete if 0 size
if [ ! -s clang_format.patch ]
then
    rm clang_format.patch
fi

exit 0

exit 0

Further Reading