Cup is a build system implemented in C that uses C as its scripting language. Write your build logic in C — loops, functions, complex logic — with full support from your favorite tools (LSP, debugger).
Cup was originally inspired by the build system of Unreal Engine.
Example build script. For more real-world examples, search for the self-built entry function in this project: ENTRY(
- Native C Build Scripts:
build.cis the build system. No DSL. - Self-Hosting: Only depends on a C compiler. The initial build directly invokes the compiler, after which Cup supports automatic self-updating, even in debug mode.
- Clear command-line output: No build commands are hidden, input and output are highlighted for better readability, without losing the compiler’s colored output.
- Platform Support: Windows, Linux, and macOS.
- Support for multiple toolchains:
llvm,msvc,gcc,zig - IDE Support:
- Generates
compile_commands.jsonfor Clangd. - Generates VS Code
launch.jsonandtasks.json. - Generates Visual Studio
.sln/.slnxprojects.
- Generates
- Caching: Only rebuilds what changed.
- Parallel Builds: Executes independent commands simultaneously.
- Callback-Based Build Commands: Supports custom callback functions integrated into the build graph.
- Header Dependencies: Parses MSVC
/showIncludesand GCC-Moutput. - C++20 Modules: Supports automatic scanning and dependency resolution for named modules. Automatically builds
stdandstd.compat. Modules can also be declared manually viac_compile_cmd_set_export_name(cc, name)and imported withc_compile_cmd_add_import(cc, name). - Lightweight: Optimized
cup/cup.exebuilds are ~780 KB, while the standalonecup.his ~540 KB.
- A package manager.
- Support for non-C/C++ languages. To keep the project small and focused, native support for other languages is not considered. Although it can invoke external commands, its abstractions are intended only to simplify C or C++ projects.
Cup can be used in two ways:
-
Single Header Mode (
cup.h): Amalgamated single header. Include it in your build script. Zero dependencies besides a C compiler. Recompiles the build logic every time. -
Binary Mode (
cup/cup.exe): Compile Cup once into a standalone executable. The executable loads yourbuild.cas a dynamic library. Faster thanSingle Header Mode.
- Download the latest release for your platform (
cup,cup.exe, orcup.h) from the Releases page. - Run the executable or compile
cup.hwith-DMAIN_ENTRY(see compile commands at the end ofcup.h). - If no
build.cexists, Cup generates a default one. Edit it as needed. - You can run
cup -hto see more command-line options.
Pre-built skills for AI coding agents (OpenCode, Claude Code, etc.) are available in the skills/ directory. Install by copying a skill folder into .opencode/skills/ (or the equivalent directory for your agent). Once installed, if your project contains cup.h or cup.exe, you can ask the agent to write build.c build scripts, write tests, and use Cup's built-in testing framework to build and verify the program — the skill provides it with the correct macros, recipes, and command-line flags.
For example, you can ask the agent:
"Write some examples proving you already know how to use Cup."
Run bootstrap.bat (Windows) or bootstrap.sh (Linux/macOS) to generate a cup / cup.exe executable in the project root. Then run it (e.g. .\cup.exe on Windows, ./cup on Linux/macOS) to build the project — this produces the embedded binary in build/embedded/, the single-header distribution in build/header_only/.
#include "cup/test.h"
TEST(my_test_case) {
ASSERT(1 + 1 == 2);
}Use cup -test to run all registered tests.
You may also be interested in Cup for VS Code a VS Code extension supports Cup. provides enhanced Test Explorer UI for discovery, execution, and debugging.
#include "cup.h"
// The `ENTRY` macro declares a build entry point. All entry points are executed when `execute()` is called.
ENTRY(build_hello) {
// Declare a source file participating in the build.
Node *src = SRC("src/hello.c");
// Declare an object file participating in the build.
Node *obj = OBJ(src);
// Declare a compile command that compiles `src` into `obj`.
CC(src, obj);
Node *dep = OBJ(SRC("src/core/allocator.c"));
// Specify that all commands depending on `obj` must also depend on `dep`.
obj_add_link_node(obj, dep);
// Declare an executable file to be generated. `build/hello.exe` on Windows
Node *exe = EXE("{out_dir}/hello");
// Declare a link command with `exe` as its primary output.
Node *link = LINK(exe);
// Add `obj` as an input to the link command.
link_cmd_add_input(link, obj);
}
// Manual source list
ENTRY(iterate_by_array) {
char const *sources[] = { "a.c", "b.c", "c.c" };
Node *exe = EXE("e");
Node *link = LINK(exe);
for (size_t i = 0; i != static_array_size(sources); i++) {
Node *src = SRC(sources[i]);
Node *obj = OBJ(src);
CC(src, obj);
link_cmd_add_input(link, obj);
}
}
int main(int argc, char **argv) {
// Enable built-in testing framework
set_test_enabled(true);
// Generate VS Code launch.json and tasks.json.
set_generate_vscode_files_enabled(true);
// Disable debug information for non-debug builds.
if (get_default_optimization() != OPTIMIZATION_TYPE_DEBUG)
{
set_debug_info_enabled(false);
}
// Search for build entry files under the src directory.
add_build_script_search_directory("src");
return execute();
}Issues and Pull Requests are welcome to improve Cup!