Make C++ a piece of cake.
Cupcake is an all-in-one build tool and package manager for C++, like Cargo for Rust, npm for JavaScript, or Poetry for Python. It serves the complete package lifecycle, all from the command line, with no manual editing of configuration files required:
- Create a new project.
- Search, add, or remove package dependencies.
- Build, test, or install your project.
- Run or debug executables in your project.
- Publish your package.
Cupcake is implemented as a thin layer on top of CMake and Conan. It assembles and runs the commands that you would otherwise write yourself. It offers a smaller interface that is easier to understand and use, but it aims to support the most common project patterns. Cupcake does not support all of the same options as CMake and Conan, but the options that it does support are remembered in a local configuration file so that you don't have to repeat them.
Cupcake is designed to be easy to try, with no lock-in. If you create a project with Cupcake and later decide to stop using it, you are still left with a functional CMake and Conan project. If you find that you need an option that is missing from Cupcake, then you can always invoke Conan or CMake directly.
# You will need to install Conan and CMake. Cupcake will not do this for you.
pipx install conan cmake
# You need to configure Conan yourself.
conan profile detect # Default name is "default".
${EDITOR} $(conan profile path default)
# You'll need to add the Redirectory package server to get cupcake.cmake.
conan remote add redirectory https://conan.jfreeman.dev
# Now you're ready to use Cupcake.
# Create a project named "example" in a subdirectory of that name.
cupcake new example
cd example
# List known targets.
cupcake list
# The project will build and test out-of-the-box.
cupcake build # Build a release flavor by default.
cupcake test # Test the same flavor.
# Search for the latest fmt, install it, and link it as a dependency.
cupcake add fmt
# Search for available versions of scnlib.
cupcake search scn*
# Install a specific version of scnlib and link it as a dependency.
cupcake add scnlib@4.0.1
# They are ready to use immediately.
sed -i '1i #include <fmt/base.h>' src/example.cpp
sed -i '1i #include <scn/scan.h>' src/example.cpp
# Check the current flavor.
cupcake select
# Switch to and build the debug flavor.
# Cupcake will build the dependencies in that flavor too.
cupcake build --debug
cupcake test # Tests the current flavor.
# Switch back to the release flavor.
cupcake select --release
# Install into "./.install" by default.
cupcake install
tree .install/
There are two dimensions on which to divide Cupcake commands.
The first is general vs special commands.
General commands work with any CMake project,
whether or not it has a Conan recipe.
Special commands work only with conforming projects.
Many special commands require a Cupcake metadata file, cupcake.json,
to easily share metadata between the Conan recipe and the CMake configuration.
The easiest way to make a project conform is to create it with
cupcake new --special.
The second dimension is source vs build commands.
Source commands interact with only the project's source directory.
You can choose the source directory for any such command
with option --source-dir (-S).
Build commands interact with the project's build directory too.
You can choose the build directory for any such command
with option --build-dir (-B).
The Cupcake build directory is not the same thing
as the CMake binary directory.
The Cupcake build directory may house multiple CMake binary directories,
as well as a Conan output folder and anything else Cupcake wants to store.
It is not meant for public consumption except through Cupcake build commands:
conanandcmakeconfigure the build directory. You will typically never need to run these commands directly.cleanremoves the build directory.build,test, andinstalleffectively runcmake --build,ctest, andcmake --install, respectively.exeanddebugare special commands that runcmake --buildfor specific custom targets defined for executables by cupcake.cmake.
A Cupcake command may depend on other Cupcake commands.1
For example, build depends on cmake, which depends on conan.
test depends on cmake too, but not on build.
Each Cupcake command automatically runs the commands it depends on.
You can run just the final command you are interested in.
You do not have to walk through manually configuring your build directory
like you would with Conan and CMake.
You can skip straight to cupcake test in a fresh clone, for instance.
Cupcake commands inherit all of the options of their
direct and indirect dependencies.
You can pass Conan options to cupcake conan,
or you can pass them to cupcake build, cupcake test, and so on.
Options that affect the state of the build directory
are stored in a Cupcake configuration file.
The default is .cupcake.toml in the project source directory,
but you can override it with the option --config.
You can manually edit the configuration file if you want.
(Cupcake preserves any comments in it.)
Options that do not affect the state of the build directory
are typically not stored.
Exceptions, e.g. --jobs, are noted where they appear.
Whether or not you use a multi-config CMake generator,
Cupcake models a multi-config build directory.
Because the word "configuration" is used in so many contexts
with different meanings (sometimes it is called "build type"),
Cupcake calls CMake configurations flavors.
Flavors in Cupcake are case-insensitive, but lowercase is preferred.
Many commands, e.g. build and test, work on one flavor at a time,
the selected flavor.
You can override the selected flavor on the command line,
with option --flavor, and that selection will be saved for the next command.
When you pass options that affect the state of the build directory,
e.g. by selecting a different flavor,
Cupcake will automatically reconfigure the build directory
with those new options,
but only the parts that need to be reconfigured,
to preserve work from previous builds.
The same happens if you change configuration files,
e.g. CMakeLists.txt or the Conan recipe,
that affect the state of the build directory,
e.g. when you change branches in a Git repository.
You should never need to manually configure or reconfigure the build directory.
Let Cupcake figure out for you what is required.
But if Cupcake ever does make a mistake,
all of this is designed to let you blow away the build directory
and recreate it with exactly the same options just by running cupcake build.
buildBuild the selected flavor.cacheCopy a package to your local cache.cleanRemove the build directory.cmakeConfigure CMake for at least the selected flavor.conanConfigure Conan for all enabled flavors.installInstall the selected flavor.publishUpload a package.searchSearch for packages.selectSelect a flavor.testExecute tests.
addAdd one or more requirements.add:exeAdd one or more executables.add:headerAdd one or more public headers to an existing library.add:libAdd one or more libraries.add:testAdd one or more tests.debugDebug an executable.exeExecute an executable.linkLink a target to one or more libraries.listList targets and their links.newCreate a new project.removeRemove a requirement.remove:exeRemove one or more executables.remove:libRemove one or more libraries.remove:testRemove one or more tests.unlinkUnlink a target from one or more libraries.versionPrint or change the package version.
This section describes the common settings, used by multiple commands, that are persisted in the Cupcake configuration file. Other settings that are persisted in the configuration file but used by only a single command are documented under that command. The only two settings that cannot be persisted in the configuration file are the source directory and the configuration file path, but they are documented here anyways.
The way settings work in Cupcake is unique, I think, but easy to explain. If you do not override a setting with a command-line option, then its default is taken from the configuration file. If it is missing in the configuration file too, then the default is hard-coded in Cupcake (and visible in the help string for the option). This way, if you're in the habit of repeating long command lines full of options for Conan and CMake, then you can do the same with Cupcake and expect the same behavior. But with Cupcake, once you've assigned a setting through a command-line option, then you can just repeat the command with no options and trust it will "do the same thing as last time". You can build a command incrementally and repeat it without searching through history.
As a TOML file, the configuration file represents an object. In the table below, the Key column defines the path in that object to the property for the setting. The Options column lists all the command-line options that affect the setting. Not all settings can be controlled by a command-line option. They must be manually edited in the configuration file instead.2
| Setting | Key | Options | Type | Default |
|---|---|---|---|---|
| source directory | --source-dir, -S |
path | . |
|
| configuration file | --config |
path | .cupcake.toml |
|
| build directory | .directory |
--build-dir, -B |
path | ".build" |
| verbosity level | .verbosity |
--verbose, -v, --quiet, -q |
integer in range [0, 3] | 0 |
| selected flavor | .selection |
--flavor |
string | "release" |
| enabled flavors | .flavors |
list of strings | ["release"] |
|
| parallelism limit | .jobs |
--jobs, -j, --parallel |
positive integer | number of logical processors |
| Conan executable | .path.conan |
path | conan |
|
| CMake executable | .path.cmake |
path | cmake |
|
| CTest executable | .path.ctest |
path | ctest |
If the configuration file or build directory are relative paths,
then they are evaluated relative to the source directory.
If the executables are relative paths,
then they are evaluated like any other command,
i.e. relative to the PATH environment variable.
Verbosity is incremented by --verbose/-v
and decremented by --quiet/-q, and clamped to the range [0, 3].
Even though the level of parallelism does not affect the state of the build directory, it is persisted for a reason. Cupcake builds in parallel by default, unlike CMake. The default parallelism limit matches the number of logical processors detected by Cupcake. If your translation units are small, and your build is CPU-constrained, then this is typically the right choice. But if your translation units are large, and your build is memory-constrained instead, then this choice can lead to memory pressure and even crashing builds. The right choice for the limit depends on the machine and the project, and thus it is persisted in a setting.
To see the structure of a generated project, see the examples in project-template-cpp. The README there explains the design too.