bake
The Dutch IRS has a catchy slogan, which goes like this: "Leuker kunnen we 't niet maken, wel makkelijker". Roughly translated this means: "We can't make it more fun, but we can make it easier". Bake adopts a similar philosophy. Building C/C++ code will never be fun, but with bake you'll probably spend a little less time worrying about it.
Here's how bake tries to alleviate some of the pain:
- Minimal, platform independent project configuration (as in 2 lines of JSON minimal)
- Create new projects with a single command
- Builtin emscripten (webasm) support
- Zero-dependencies, calls msvc, gcc, clang and emcc compilers directly
- Refer to dependencies by logical names, no OS/environment dependent paths
- Automatically include headers from dependencies
- Out of the box macro's for exporting symbols
- Automatically discover, order and build projects in directories without additional config
- Recursively build projects & their dependencies for the right config with a single command
- Generate single source/header files from any project
- A test framework
- Clone, build and run projects with their dependencies with a single command
- Make projects warning free on all compilers/compiler versions with strict compilation mode
- Out of the box support for running projects with address sanitizers
- Manage projects & their git repositories with bake bundles
Bake is verified on the following platforms:
- Linux
- MacOS
- Windows
Bake is verified on the following compilers
- gcc (7, 8, 9, 10)
- clang (8, 9, 10)
- msvc
Bake is used as the primary build system for Flecs.
Contents
- Installation
- Getting Started
- FAQ
- Manual
- Introduction
- Building projects
- Running projects
- Project kinds
- Project layout
- Project configuration
- Project bundles
- Template functions
- Templates
- Configuring Bake
- Installing Miscellaneous Files
- Integrating Non-bake Projects
- The Bake Environment
- Environment Variables
- Command line interface
- Writing drivers
- Authors
- Legal stuff
Installation
Install bake using the following commands:
On Linux/MacOS:
git clone https://github.com/SanderMertens/bake
bake/setup.sh
On Windows:
Requires Visual Studio Build Tools or the full Visual Studio Community IDE installed with the C++ CMake tools for Windows
and Windows SDK
individual components included in the Desktop development with C++
workload:
git clone https://github.com/SanderMertens/bake
cd bake
setup
On MacOS/Linux bake will install a single script in /usr/local/bin
which calls the bake binary in ~/bake
, which may prompt for your password during installation. This script allows you to call bake from any location without having to make changes to your environment.
If you'd rather not install this script, you can install bake in local mode:
git clone https://github.com/SanderMertens/bake
cd bake
make -C build-$(uname) clean all
./bake setup --local
After the setup has finished, the bake executable will be stored in ~/bake
. Make sure to add this directory to your PATH
variable before using bake.
Upgrade bake
You can upgrade bake to the latest version by running this command:
bake upgrade
Getting started
The following commands are useful for getting started with bake. Also, check out the bake --help
command, which lists all the options and commands available in the bake tool.
Create and run new project
To create and run a new bake application project called my_app
, run the following commands:
bake new my_app
bake run my_app
You can also run projects in interactive mode. This will automatically rebuild and restart an application when a project file changes. To run in interactive mode, simply add --interactive
to the bake command:
bake run my_app --interactive
Basic configuration with dependency and configuration for C driver
This example shows a simple configuration with a dependency on the foo.bar
package and links with pthread
.
{
"id": "my_app",
"type": "application",
"value": {
"use": ["foo.bar"]
},
"lang.c": {
"lib": ["pthread"]
}
}
Build, rebuild and clean a project
bake
bake rebuild
bake clean
Specify a build configuration:
bake --cfg release
Clone & build a project from git
Build a project and its dependencies directly from a git repository using this command:
bake clone https://github.com/SanderMertens/example
Export an environment variable to the bake environment
Bake can manage environment variables that must be set during the build. To export an environment variable to the bake environment, use this command:
bake export VAR=value
Alternatively, if you want to add a path to an environment variable like PATH
or LD_LIBRARY_PATH
, use this:
bake export PATH+=/my/path
These variables are stored in a configuration file called bake.json
in the root of the bake environment, which by default is $HOME/bake
.
To export the bake environment to a terminal, use:
export `bake env`
FAQ
Bake is built under the GPL3.0 license. Does this mean I cannot use it for commercial projects?
No. As long as you do not distribute bake (either as source or binary) as part of your (closed source) deliverable, you can use bake for building your projects. This is no different than when you would use make for your projects, which is also GPL licensed.
I want my customers to use bake. Does the license allow for this?
Yes. As long as your customers use the open source version of bake, and you do not distribute bake binaries or source files with your product, your customers can use bake.
I noticed a premake file in the bake repository. Does bake need premake to be installed?
No. Bake uses premake to generate its makefiles (we would've used bake to build bake- but chicken & egg etc). The generated makefiles are included in the bake repository, so you won't need premake to use bake.
Why yet another build tool?
To put it bluntly, existing tools for C/C++ aren't great.
Is bake a package manager?
No. Bake has package-management like features, like resolving projects by logical name, automatic project discovery & associate projects with git repositories, but it does not pretend to be a full-fledged package manager. The only reason bake has these features is because it makes the build process easier.
Why are bake project configurations so simple?
All C/C++ project build configurations basically boil down to the same set of rules to translate source files into objects, and objects into shared objects or application binaries. Yet with most build systems you'll find yourself copy-pasting the same rules every time you create a new project.
Bake doesn't need to be told how to build C/C++ code. It just needs three pieces of information:
- The project name
- Whether it's an application or a package
- A
src
andinclude
directory
Why doesn't bake generate makefiles or visual studio files?
Makefiles and Visual Studio files are just elaborate front-ends for how to call compilers. Bake calls the compiler directly. It has builtin drivers for gcc, clang, msvc and emcc, and it automatically detects for which compiler it's building.
How does bake compare to make?
GNU make is a low-level tool that requires you to explicitly list all the rules required to translate code to binaries. Bake is a high-level tool that already knows how to build the code, and can do this with minimal configuration.
How does bake compare to CMake?
CMake is a tool that generates other build configurations. When you use CMake you still need to use another tool like Make or Visual Studio to actually build your project. Bake builds your code directly.
Another big difference is that CMake requires you to specify project configuration in a custom language, whereas Bake configuration is specified in JSON. Here is a CMake configuration and a Bake configuration for the same project:
CMake:
cmake_minimum_required (VERSION 2.6)
include_directories ("bar")
add_subdirectory (bar)
set (EXTRA_LIBS ${EXTRA_LIBS} bar)
project (foo)
add_executable(foo foo.c)
target_link_libraries (foo ${EXTRA_LIBS})
Bake:
{
"id": "foo",
"type": "application",
"value": {
"use": ["bar"]
}
}
A difference that jumps out from the examples is that the bake configuration is agnostic to its environment. It knows where to find project bar
, whereas in CMake this needs to be explicitly specified.
How does bake compare to premake?
Premake is a lot like CMake, but with a Lua-based project configuration that's slightly less verbose. Premake also generates build configurations, and requires additional tools to actually build your code.
How does bake compare to Bazel?
Out of all build systems, bake and bazel come closest in the way they approach building code. The biggest differences are:
- Bake is a much, much smaller project
- Bake is less controlling when it comes to your environment. If you want absolute control over which version of Python, make, gcc etc. you're using while building, Bazel can do a better job.
- Bake only builds C/C++ code.
- Bazel project identifiers are relative to a workspace. Bake project identifiers are universal.
- Bazel has a custom language for project configuration
Can I link with non-bake libraries?
Yes. This example shows how to link with libm
:
{
"id": "my_app",
"type": "application",
"lang.c": {
"lib": ["m"]
}
}
This can be improved by ensuring that libm
is only added on Linux (MacOS/Windows don't have libm
):
{
"id": "my_app",
"type": "application",
"${os linux}": {
"lang.c": {
"lib": ["m"]
}
}
}
I want to wrap a C library so I can use it as a bake dependency. How do I do this?
It would be nice if we could wrap libm.so
from the previous example in a bake math
package, so we don't have to repeat this configuration for every project. Bake lets us do this with the "dependee"
attribute:
{
"id": "math",
"type": "package",
"value": {
"language": "none"
},
"dependee": {
"${os linux}": {
"lang.c": {
"lib": ["m"]
}
}
}
}
This creates a new "math" package that you can now specify as regular bake dependency. The "language": "none"
attribute lets bake know that there is no code to build, and this is a configuration-only project. The dependee
attribute tells bake to not apply the settings inside the JSON object to the math
project, but to the projects that depend on math
.
We can now use the math package like this:
{
"id": "my_app",
"type": "application",
"value": {
"use": ["math"]
}
}
Where can I find the configuration options for C and C++ projects?
You can find language-specific configuration options in the README of the language driver projects:
For C: https://github.com/SanderMertens/bake/tree/master/drivers/lang/c
For C++: https://github.com/SanderMertens/bake/tree/master/drivers/lang/cpp
What is a driver?
All of the rules and instructions in bake that actually builds code is organized in bake "drivers". Drivers are shared libraries that bake loads when a project needs them. The most common used drivers are "language drivers", which contain all the build instructions for a specific language, like C or C++. Bake automatically loads the language drivers based on the "language"
attribute in your project.json
, as is specified here:
{
"id": "my_app",
"type": "application",
"value": {
"language": "c"
}
}
By default the language is set to "c", so if you do not specify a language, your project will build as a C project.
What does "lang.c" mean? When do I need to specify it?
In some cases you will want to provide configuration options that are specific to a language, like linking with C libraries on your system, or provide additional compiler flags. In that case, you have to tell bake that the configuration you are about to specify is for a specific driver. This is where "lang.c" comes in:
{
"id": "my_app",
"type": "application",
"value": {
"language": "c"
},
"lang.c": {
"lib": ["m"]
}
}
The "lang.c"
member uniquely identifies the bake driver responsible for building C code, and bake will make all of the attributes inside the object ("lib"
) available to the driver.
If you want to build a C++ project, instead of using the "lang.c"
attribute, you have to use the "lang.cpp"
attribute, which identifies the C++ driver:
{
"id": "my_app",
"type": "application",
"value": {
"language": "c++"
},
"lang.cpp": {
"lib": ["m"]
}
}
For C++ projects, should I specify cpp or c++ for the language attribute?
You can use either, but for specifying driver-specific configuration you always have to use lang.cpp
.
How can I see a list of the available drivers?
The following command will show you a list of the available drivers:
bake list bake.*
Everything except for bake.util
is a driver. If you just built bake for the first time, this will only show the "lang.c"
and "lang.cpp"
drivers.
Can I load more than one driver?
Yes! You can load as many drivers as you want. If you want to add a driver, simply add it to your configuration like this:
{
"id": "my_app",
"type": "application",
"my_custom_driver": { }
}
Are there any example drivers I can use as a template?
Driver documentation is a bit lacking at the moment, but we will eventually address that. In the meantime, you can take a look at the C driver to see what a fully fledged driver looks like:
https://github.com/SanderMertens/bake/tree/master/drivers/lang/c
How do I install bake packages?
Bake relies on git to store packages. To install a package, use the bake clone
command with a GitHub repository identifier:
bake clone SanderMertens/example
If your git repository is not hosted on GitHub, simply provide the full git URL:
bake clone https://my_git_server.com/example
Any URL that is accepted by git is accepted by bake.
How does bake find dependencies of cloned projects?
When bake clones a package with dependencies, it will try to also install those dependencies. It does this by taking the git URL specified to bake clone
, and replacing the package name with the dependency name. For example, if the https://github.com/SanderMertens/example git repository depends on project foobar
, bake would also look for https://github.com/SanderMertens/foobar.
Future versions of bake may provide more intelligent ways to locate packages.
Why use JSON for project configuration?
A number of people have asked me why I used JSON for project configuration. There are two reasons:
- It is a ubiquitous language that everyone understands,
- It has a C parser that can be easily embedded into bake without adding dependencies
A disadvantage of JSON is that while it is fine for trivial configurations, it can get a bit unwieldy once project configurations get more complex. In bake however, you can encapsulate complexity into a configuration-only project, and then include that project as a dependency in your project configuration (example).
Additionally, bake is not like traditional build tools where you specify rules with inputs and outputs in your project configuration. If you want to, for example, add a code generation step to your build, you write a driver for it, and then include the driver in your project configuration.
How can I specify a custom compiler?
The drivers for C & C++ projects by default use gcc/g++ (on Linux) and clang/clang++ (on MacOS). If you want to change the default compiler, you can set the CC
(for C) and CXX
(for C++) environment variables, as long as the command line options are compatible with gcc. Instead of setting the environment variables manually, you can make them part of a bake environment like this:
bake export CC=clang --env clang_env
To use the environment, and build with clang, you can then invoke bake like