Great article, thank you! I think the most important thing is getting a consistent version reference on all the tools (Ceedling, clang-tidy, etc.). I also appreciate the acknowledgments of Docker’s limitations and pointing them out up front. No, Dockerfiles are not forever. Pre-Docker, some companies have been creating VM images (VMWare or VirtualBox) and cataloguing that as their “dev environment”. In the case of Docker, depending on security and longevity concerns, I think it means that you have the image captured on a local server.
As far as multiple tools in the same dev environment, I have been exploring with the possibility of Docker Compose for bringing in multiple tools, but it doesn’t work. It has to all be in one container. I think that one needs to have different Docker images for CI vs. local development. The issue is that in CI the jobs are running separately and you don’t want to pull the same container multiple times for separate jobs (unit tests, clang-format, build, etc.). The local dev environment is different. In this case, you do want it all mashed into one container. I don’t like this solution as I feel it violates DRY, but I’m still looking at what is possible here.
A few points I want to touch on.
In the section Building outside of vscode and mounting volumes, the mount is not a Docker volume mount. It is a bind-mount, even when using the VOLUME instruction in your Dockerfile. Microsoft VS Code dev containers specifically bind-mount the working directory of the project into the container. The VOLUME
instruction itself creates a mount point, not a volume. The -v
parameter with two arguments is still creating a bind-mount.
I would also recommend sticking with the same directory naming as the dev container when building from the command line. Instead of /builder/mnt
, I would use /workspaces/cproject
. The issue is that you’re changing the path names that appear in the compile_commands.json
, the CMake output files, and the ELF file. This will screw up IntelliSense and incremental builds when you go back into the container.
The second issue is this statement:
For Windows, at the time of writing, I don’t know of a similar solution so you might have to dig a little deeper in case you run into performance problems.
This has a simple solution: use WSL. On Windows, Docker is native to WSL2. Building from WSL2 blows away the performance building natively from (NTFS) Windows even before considering Docker. And mounting from a WSL2 instance into a Docker container has no performance impact. I have done empirical testing on this. We had a project recently at my company that was over 300 steps according to Ninja. (I say steps as opposed to compilation units because this includes intermediate static library generation, the linking stage, and OBJCOPY
stages.). Here are my the results of my tests for just the Ninja portion of this build (i.e. the equivalent of the cmake --build build
step in this example).
- WSL2 native: 6 seconds
- WSL2 bind-mounted into dev container: 6 seconds
- Windows (NTFS) native: 18 seconds
- Windows (NTFS) bind-mounted into dev container: 1 minute
Bind-mounting from WSL2 is a wash and building from WSL2 is already super fast. I don’t have a direct measurement against Mac OS, but based on watching builds, my impression is that it’s faster. The other benefit of WSL2 is that it’s a true Linux environment, which brings me to my third point.
Building from the Docker environment on Linux will build with different user and group permissions. In this case, the builder-run
target in the Makefile is building as root
, which means access is denied to these files once you exit the container.
Sample output:
aaronf@DESKTOP-2HU5QJM:~/Learning/cproject$ make builder-run
docker run \
--rm \
-it \
--platform linux/amd64 \
--workdir /builder/mnt \
-v .:/builder/mnt \
cproject-builder:latest \
/bin/bash
root ➜ /builder/mnt $ rm -rf build
root ➜ /builder/mnt $ cmake -B build
-- The C compiler identification is GNU 10.2.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /builder/mnt/build
root ➜ /builder/mnt $ cmake --build build
Scanning dependencies of target Dummy
[ 50%] Building C object CMakeFiles/Dummy.dir/src/dummy.c.o
[100%] Linking C static library libDummy.a
[100%] Built target Dummy
root ➜ /builder/mnt $ exit
exit
aaronf@DESKTOP-2HU5QJM:~/Learning/cproject$ rm -rf build
rm: cannot remove 'build/CMakeFiles/Dummy.dir/depend.internal': Permission denied
rm: cannot remove 'build/CMakeFiles/Dummy.dir/DependInfo.cmake': Permission denied
rm: cannot remove 'build/CMakeFiles/Dummy.dir/link.txt': Permission denied
rm: cannot remove 'build/CMakeFiles/Dummy.dir/flags.make': Permission denied
rm: cannot remove 'build/CMakeFiles/Dummy.dir/progress.make': Permission denied
rm: cannot remove 'build/CMakeFiles/Dummy.dir/cmake_clean.cmake': Permission denied
rm: cannot remove 'build/CMakeFiles/Dummy.dir/cmake_clean_target.cmake': Permission denied
rm: cannot remove 'build/CMakeFiles/Dummy.dir/C.includecache': Permission denied
rm: cannot remove 'build/CMakeFiles/Dummy.dir/depend.make': Permission denied
rm: cannot remove 'build/CMakeFiles/Dummy.dir/build.make': Permission denied
rm: cannot remove 'build/CMakeFiles/Dummy.dir/src/dummy.c.o': Permission denied
rm: cannot remove 'build/CMakeFiles/CMakeDirectoryInformation.cmake': Permission denied
rm: cannot remove 'build/CMakeFiles/progress.marks': Permission denied
rm: cannot remove 'build/CMakeFiles/CMakeTmp': Permission denied
rm: cannot remove 'build/CMakeFiles/cmake.check_cache': Permission denied
rm: cannot remove 'build/CMakeFiles/Makefile2': Permission denied
rm: cannot remove 'build/CMakeFiles/3.18.4/CMakeCCompiler.cmake': Permission denied
rm: cannot remove 'build/CMakeFiles/3.18.4/CMakeSystem.cmake': Permission denied
rm: cannot remove 'build/CMakeFiles/3.18.4/CMakeDetermineCompilerABI_C.bin': Permission denied
rm: cannot remove 'build/CMakeFiles/3.18.4/CompilerIdC/tmp': Permission denied
rm: cannot remove 'build/CMakeFiles/3.18.4/CompilerIdC/a.out': Permission denied
rm: cannot remove 'build/CMakeFiles/Makefile.cmake': Permission denied
rm: cannot remove 'build/CMakeFiles/CMakeOutput.log': Permission denied
rm: cannot remove 'build/libDummy.a': Permission denied
rm: cannot remove 'build/CMakeCache.txt': Permission denied
rm: cannot remove 'build/Makefile': Permission denied
rm: cannot remove 'build/cmake_install.cmake': Permission denied
rm: cannot remove 'build/compile_commands.json': Permission denied
aaronf@DESKTOP-2HU5QJM:~/Learning/cproject$ code .