Provides a pre-canned C++ project layout along with automation, containerized tools and fill-in-the-blanks documentation.
- Top level Makefile framework for launching targets
- Uses containers for build tools (compiler, CMake, auto-docs, etc)
- CMake used only for C++ compilation/linking
- Production and debug trees in same workspace simultaneously
- Setup with Conan v2.0 C++ libary management
- Configured as a consumer of Conan libraries (not a producer)
- Also setup for two locally developed internal-only libraries
- Automated documentation generation with deployment to GitHub Pages
- Documentation tools - Sphinx, Doxygen, PlantUML
- All documentation organized together under a single static website
- Spell checking on docs, in batch or interactive mode
- Doctest unit testing framework
- Code coverage using lcov
- Static code analysis via cppcheck
- Single "version" file in top level folder drives all targets
- Clean unpolluted top level folder
- GitHub Workflows for builds, releases, and document publishing
See the sample auto-generated project documentation here on Github Pages.
RedFlame is used as the name of the hypothetical application that is built.
- GNU Makefile
- Podman or Docker
- Some typical Linux command line utilities
Jump ahead to Getting Started if you're so inclined.
Top Level View
├── src/
├── docs/
├── tools/
├── etc/
├── _build/
├── Makefile
├── version
└── Readme.md
Two Level View
├── Makefile
├── Readme.md
├── version
├── src/
│ ├── CMakeLists.txt
│ ├── main
│ │ ├── include
│ │ ├── src
│ │ ├── utest
│ │ └── CMakeLists.txt
│ ├── lib-gen
│ │ ├── include
│ │ ├── src
│ │ ├── utest
│ │ └── CMakeLists.txt
│ └── lib-codec
│ ├── include
│ ├── src
│ ├── utest
│ └── CMakeLists.txt
├── docs
│ ├── src
│ ├── site
│ └── docs.mak
├── tools
│ ├── cmake
│ ├── conan
│ ├── containers
│ ├── scripts
│ ├── static-analysis
│ └── submakes
└── etc
├── Contributing.md
└── License
Generally conforms to PitchFork project layout.
Manage production and debug builds.
make # build default tree (default is initially debug)
make prod # build production tree, default is still debug
make debug # build debug tree, default is still debug
make set-prod # set production tree to be default - sticky setting
make # build default tree (production tree is default)
make debug # build debug tree, default is still prod
make prod # build prod tree, default is still prod
make both # build both prod and debug trees
make both -j # build both prod and debug trees in parallel
make set-debug # set debug tree to be default - sticky setting
make clean # deletes entire _build folder (removes debug and prod)
make clean-debug # removes the debug tree
make clean-prod # removes the prod tree
make show-default-build # show the default build typeAssuming you have the handy container aliases defined, and you are sitting in the top folder, then:
bd bin/RedFlame # run the app out of debug tree
bp bin/RedFlame # run the app out of production treeAlternatively you could exec into the build container.
podman exec -it -w /work/cpp-bootstrap gcc14-tools bash
root#./_build/debug/bin/RedFlame # run the debug built app
# Or if you have the bbash alias defined
bbash
root#./_build/debug/bin/RedFlame # run the debug built app
Executables are built for the build container's OS, not the host OS, and are therefore only runnable on the build container. See discussion below on Containers.
make unit-test # runs unit tests for default build
make unit-test-debug # runs unit tests for debug build
make unit-test-prod # runs unit tests for prod build
make unit-test-both # runs unit tests for prod and debug buildOr directly run a unit test executable. Assuming you have the handy container aliases defined, and you are sitting in the top folder, then:
bd bin/lib-codec-ut # run unit tests for library debug tree
bp bin/lib-codec-ut # run unit tests for library production treeExecutables, in this case unit tests, are built for the build container's OS, not the host OS, and are therefore only runnable on the build container. See discussion below on Containers.
make docs
firefox _build/site/index.htmlSee the same auto-generated documentation here on Github Pages.
- Uses doctest
- Unit tests are kept separate from source code, even though doctest allows unit test in the source file itself
- Unconditionally compiles unit tests along with each build
- Single version file in project root, supports repeatable builds
- Semantic versioning
cat version
1.0.0- All built artifacts obtain version information from this one file
- Auto-documentation and containers use this version file
- CMake is configured to use this version file
- In addition, C++ Bootstrap supplies its own
buildinfodefs.cmaketo auto generate additional build info. Versioning is then made available to the application via the BuildInfo class in lib-gen
> ./bin/RedFlame
RedFlame v1.0.0-1728572288
Built by: root
Build date: 2024-10-10T07:58:08-07:00
Build epoch: 1728572288
Build branch: ProjectRestructure
Last commit hash: 932a53f3827f1e1d- There are pre-canned auto documentation samples for:
- manpage
- user guide
- design doc
- doxygen output
- licenses
- Uses Sphinx with read-the-docs theme
- Doxygen for internal API
- PlantUML to auto build diagrams
- Makefile auto-generates PlantUML files into PNG files
- Create or modify PlantUML files in
docs/src/images/src - Suffix for PlantUML files must be
.puml - Place or find auto created
.png's indocs/src/images/pub - Recommend creating diagrams with Drawio and
maintain drawio source files in
docs/src/images/src - Export drawio diagram as a PNG into
docs/src/images/pub
From top level folder, invoke:
make docs
firefox _build/site/index.htmlWhen a release build is triggered, docs are built in the normal
_build/site location and then copied to the docs/site folder so it can
be checked in to Git. After a successful build, the deploy-gh-pages
workflow runs and publishes the documentation to GH pages.
You may need to configure GitHub pages in your repository. Not sure if
the GitHub pages settings come across from the template repo. To
configure it, visit your /Settings->Pages->GitHub
Pages. Ensure the Build and deployment section, under
Source is selected for GitHub Actions.
Doxygen config file is setup to treat warnings as errors. This is easy
to maintain on new projects, but this setting might be too strict for
some projects with already existing code. Change the
docs/src/doxygen/Doxyfile setting from WARN_AS_ERROR = FAIL_ON_WARNINGS to WARN_AS_ERROR = NO.
Not every single function or class is documented in doxygen style, nor should they be. Only those functions and classes that are meant to be public need doxygen comments, because you want these to appear in the published API. Private functions should still be documented, in general at your discretion, but do not need doxygen style comments.
- Spell checking can be performed against a given file or a tree of files
- Spell checking is invoked via Makefile target
- Spell checking can be run in interactive or batch mode
- Interactive mode lets you fix spelling in-place, file by file
- Underlying (containerized) tool is hunspell
Makefile targets for spelling:
spelling-clean - Deletes spelling artifacts
spelling-help - Displays help for spelling usage
spelling-it - Spell checks all docs in interactive mode
spelling - Spell checks all docs in batch mode
<filepath>.bspell - Spell checks given file in batch mode
<filepath>.ispell - Spell checks given file interactively
For full details invoke:
make spelling-helpFour workflows manage this repo.
- build.yml - builds on any push
- release.yml - manually triggered when a release is desired
- shared-build.yml - performs both build and release activities
- deploy-gh-pages.yml - updates GitHub pages after a release
Workflows support developer controls to skip various parts of the build as desired.
- Just add
[skip-<keyword>]somewhere in your commit message - See file
.github/workflows/shared-build.ymlfor keywords
- Workflow for "CI build" - triggers upon merge to main
- Workflow for "Branch build" - triggers upon checkin to branch
- Branch build supports developer controls for skipping various parts
- Uses containers for build tools (compiler, auto-docs, etc)
- Supports Docker or Podman, prefers Podman over Docker if found
- Containers mount local host workspace, no copying into container
- GCC container is auto-started once and remains active
- Re-compiles start immediately, no re-loading of GCC container
- Same containers and tools on desktop and in CI pipelines
- Pipelines invoke same make targets as developer does on desktop
- Convenient Makefile targets abstract away container commands
- The Build container uses docker.io/library/gcc as its base image which is a Debian distro, therefore executables you create are for Debian Linux. If you want to build for a different distro then you will want to switch out the Build container with your own.
- Automated login to container registries
- Supports multiple container registries simultaneously
Why Containers
- Avoids complex tool management on host, avoids tool version pollution
- Consistent predictable identical environment and workflow on both host and pipeline/runner machines
- Avoids dependency issues and version conflicts on the host and pipeline/runner machines
- Reproducible builds the set of containers used in a build captures the entire environment as coherent tool set
- Prevents conflicts between host and runners that might use different tools and/or libraries for other activities
The build container houses executables for the compiler, CMake, and Conan along with additional utilities.
C++ Bootstrap provides a build container already setup for gcc14, C++20,
CMake and Conan 2.0 -
ghcr.io/kingsolomon1954/containers/gcc14-tools:14.2.0. The makefile
has targets to build, push and pull the build container.
make help
# filtered output
cntr-build-gcc14-tools - Creates gcc14-tools image
cntr-pull-gcc14-tools - Pulls gcc14-tools from ghcr.io
cntr-push-gcc14-tools - Pushes gcc14-tools to ghcr.ioSee the docker file here:
tools/containers/spec-files/dockerfile-gcc14-tools.
The build container, if it is not already running, is automatically
started upon issuing any make target that involves compiling or
linking. The build container runs in detached mode and hangs around for
further future commands which execute immediately since the container is
already running. The makefile framework arranges commands to be issued
to the build container using docker/podman exec command. The build
container is special in that it hangs around. Other containers, such as
those used for building documentation, start and exit after running
their commands.
The strategy used with the build container is to mount your local workspace. The compiler operates against your local workspace files without any copying. You are thus free to use any editors/IDE, Git tools, etc., on your host computer as normal.
Be aware that the Conan registry and Conan library cache live on the build container, not on your workspace. If the container is removed and restarted then the Conan setup will be applied again and libraries will be retrieved again. This is purposely setup in this fashion (using the container to hold the Conan cache) so that the same build container instance, and thus all the Conan libraries, can be shared across different repositories, resulting in significant efficiencies in multi-repo projects, even though not much benefit for this single repo C++ Bootstrap project.
Here's several handy bash aliases that make working with the Build Container easier. These are meant to be invoked while you're sitting in the top project folder on your host.
alias bd="podman exec -w /work/\$(basename \$(pwd))/_build/debug gcc14-tools"
alias bp="podman exec -w /work/\$(basename \$(pwd))/_build/prod gcc14-tools"
alias bt="podman exec -w /work/\$(basename \$(pwd)) gcc14-tools"
alias bbash='echo '\''Use ctrl-p ctrl-q to quit'\''; podman exec -it -w /work/$(basename $(pwd)) gcc14-tools bash'Mnemonically they can be interpreted as:
bd- run a command in the container from the_build/debug folderbp- run a command in the container from the_build/prod folderbt- run a command in the build container from the top folderbbash- bash into to the build container at the shell prompt
These aliases are also available in a script. You will want to change the value of _CPP_BOOTSTRAP_HOME in there first to agree with your environment.
source tools/scripts/devenv.bash
Supports automated and manual login into container registries.
Each container image can come from a different registry. The registry
that C++ Bootstrap uses for a given container image is specified in the
tools/submakes/container-names-<tool>.mak. Each containerized tool has
its own container-names file.
Currently supports:
- localhost
- docker.io
- ghcr.io
- artifactory.io
Login credentials are read from the following locations on your host in the order shown:
- environment variables
- from files
- otherwise command line prompt
Reads credentials (personal access token(PAT) or password and user name) from these environment variables if found:
- reads env variable
<REGISTRY>_PAT("." turned into underscore) - reads env variable
<REGISTRY>_USERNAME
For example, if the container registry is docker.io then looks
for these environment variables:
DOCKER_IO_PAT # personal access token / password
DOCKER_IO_USERNAME # login user name for this registryReads credentials (personal access token(PAT) or password and user name) from these files if found:
- reads access token file:
$HOME/.ssh/<REGISTRY>-token - reads username file:
$HOME/.ssh/<REGISTRY>-username
For example, if container registry is docker.io then looks
for these files:
$HOME/.ssh/docker.io-token # personal access token / password
$HOME/.ssh/docker.io-username # login user name for this registryThese files should have just a single line each. For example:
> cat $HOME/.ssh/docker.io-token
dhub_675b9Jam99721
> cat $HOME/.ssh/docker.io-username
Elvis- if no env var or file, then prompts for PAT/password
- if no env var or file, then prompts for username
- Setup as a consumer of Conan libraries, not a producer
- Conan library cache is held on the build container, not the host machine
- Automated Conan registry login scheme, supports multiple registries
- Uses Conan lockfiles for locking down library versions for stable repeatable builds
- Makefile provides convenient commands to manipulate Conan on the container
Conan 2.0 supports multiple Conan registries. C++ Bootstrap comes prepared with two Conan Registry files already filled out.
tools/conan/registry-conancenter.propertiestools/conan/registry-aws-arty.properties
The first one is a real registry. conancenter exists. The second one is a made up registry for the purpose of demonstration.
C++ Bootstrap populates Conan registries into Conan just once, at build initialization time, before any library retrieval is attempted. And it re-populates these later if/when a new container is started, or if a registry file changes.
To add or remove registries, just add or delete a file having the following naming pattern:
tools/conan/registry-*.properties
Properties found in these files are then used to setup each registry in Conan. The parsing is not sophisticated or flexible, uses simple greps, so please adhere closely to the layout in the files.
A Conan registry file looks like this:
> cat tools/conan/registry-aws-arty.properties
name: aws-arty
url: https://aws.artifactory.io
login: no
enable: yes
publish: yes
name: <registry> the name you give to this Conan registry. Name is one word. No quotes around it. Must be separated from the ":" with a space. Best to avoid dashes and periods in the name, although these will be turned into an underscore for env-var names.
url: <url> the registry server. url is one word. No quotes around it. Must be separated from the ":" with a space.
login: [yes|no] whether to perform automated login. See next section for more details. One word. No quotes around it. Must be separated from the ":" with a space.
enable: [yes|no] whether to use or hide this registry. One word. No quotes around it. Must be separated from the ":" with a space. This is builtin Conan feature, not a C++ Bootstrap addition.
publish: [yes|no] identify this registry as the one registry to use for publishing your own Conan library packages. One word. No quotes around it. Must be separated from the ":" with a space. Only one registry can be be selected for publishing. If multiple property files indicate "yes", C++ Bootstrap will silently accept the first one it finds. It is not an error if no registry is identified for publishing as would be case if your project is a Conan consumer-only project.
A registry can be configured in Conan with or without having Conan establish a login to it. If your project is a Conan library consumer, then generally you don't need a login. conancenter works this way. But some registries require a login even for consumer-only operations.
The two registry property files that come with C++ Bootstrap currently
specify login: no. If a registry requires a login, change the login
attribute to login: yes.
C++ Bootstrap supports automated as well as manual login.
For automation, login credentials are read from the following locations
in the given order, keying off of the name of the registry in
the property file (i.e., name: <registry>).
- from environment variables
- from files
- from command line prompt
Reads credentials (personal access token(PAT) or password and user name) from these environment variables if found:
<REGISTRY>_USERNAME
<REGISTRY>_PAT ("-" turned into underscore)
For example, if the Conan registry is aws-arty then looks
for these environment variables:
AWS_ARTY_USERNAME # login user name for this registry
AWS_ARTY_PAT # personal access token / password
Reads credentials (personal access token(PAT) or password and user name) from files if found:
~/.ssh/<REGISTRY>-username
~/.ssh/<REGISTRY>-token
For example, if container registry is aws-arty then looks
for these files:
$HOME/.ssh/aws-arty-username # login user name for this registry
$HOME/.ssh/aws-arty-token # personal access token / password
These files have just a single line each. For example:
> cat $HOME/.ssh/aws-arty-username
ElvisTheDeveloper
> cat $HOME/.ssh/aws-arty-token
aws_regy_675b9Jam99721if no env-var or file, then prompts for PAT/password
if no env-var or file, then prompts for username
TBS
Static code analysis can be performed against all source code in the
repository or against a single file. Built-in support for error
suppression-list. Underlying tool is a containerized cppcheck.
The following makefile targets are available:
static-analysis - Runs C++ static analysis against repo
static-analysis-clean - Deletes C++ static analysis artifacts
static-analysis-help - Displays help for C++ static analysis
<filepath>.sta - Runs C++ static analysis on given file
Find results in _build/static-analysis/report/index.html.
make static-analysis
firefox _build/static-analysis/report/index.htmlThe static analysis report is included and published as part of the site
documentation. A newer static analysis report replaces a previous one
provided the newer static analysis report is available at the time make docs is invoked. If no static analysis report is available at that
time, then the last published static analysis report remains.
To get a code coverage report, invoke the following makefile target.
make code-coverage
firefox _build/debug/coverage/index.htmlThis target rebuilds the debug tree with flags enabling coverage
and profiling, then invokes the CMake coverage target which
invokes unit tests along with gcov. Find the report in the
_build/debug/coverage folder.
Note that normal debug builds do not enable coverage / profiling flags. These flags slow down compiles dramatically. Therefore this separate makefile target is available for when you want focus on this area.
The code coverage report is included and published as part of the site
documentation. A newer code coverage report replaces a previous one
provided the newer coverage report is available at the time make docs
is invoked. If no coverage report is available at that time, then the
last published code coverage report remains.
- Host machine: Install docker/podman
- Host machine: Install standard linux utilities, bash, etc
Grab the repo as a template.
See if your host environment is suitable enough for make help.
make helpmake
make unit-test
bd bin/RedFlameSetup Container and Conan login credentials in ~/.ssh. See
Container Registry Login and
Conan Auto-Login.
Customize the project to be your own.
Assuming you want to use your own locally built build container,
modify file tools/submakes/container-names-gcc14.mak as follows:
Change:
CNTR_GCC_14_TOOLS_REPO := ghcr.io
CNTR_GCC_14_TOOLS_IMAGE := kingsolomon1954/containers/gcc14-tools
To:
CNTR_GCC_14_TOOLS_REPO := localhost
CNTR_GCC_14_TOOLS_IMAGE := gcc14-tools
If you haven't already built your own build container image, you can
invoke the following makefile target. This target uses the docker
spec file at tools/containers/container-files/dockerfile-gcc14-tools.
make cntr-build-gcc14-toolsYou should now have a container image in your local registry. Looks something like this:
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/gcc14-tools latest ec7fcbd0727b 2 months ago 1.78 GB
Assuming you want to use your own locally built Sphinx container,
modify file tools/submakes/container-names-sphinx.mak as follows:
Change:
CNTR_SPHINX_REPO := ghcr.io
CNTR_SPHINX_IMAGE := kingsolomon1954/containers/sphinx
To:
CNTR_SPHINX_REPO := localhost
CNTR_SPHINX_IMAGE := sphinx
If you haven't already built your own Sphinx container image, you can
invoke the following makefile target. This target uses the docker
spec file at tools/containers/container-files/dockerfile-sphinx.
make cntr-build-sphinx-toolsYou should now have a container image in your local registry. Looks something like this:
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/sphinx 7.3.7 cd0fbf8fe687 3 months ago 367 MB
Assuming you have the container aliases defined above:
bd make -C main src/Properties.o
bd make -C lib-codec src/CodecFast.oThis invokes the CMake generated Makefile on the build container specifying the file to compile. Note this works only after a build has taken place and thus CMake has already been configured.
Often it is preferable and more efficient to compile only the target under change as opposed to invoking the entire build.
Assuming you have the container aliases defined above:
bd make help # See the CMake targets
bd make lib-gen # Build just the lib-gen library target