From putup
Contributing to putup development. Use when building putup, running tests, writing E2E tests, understanding the architecture, or following the code style.
How this skill is triggered — by the user, by Claude, or both
Slash command
/putup:contributingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Putup is a build system using Tup's Tupfile syntax, written in C++20. It builds itself (self-hosting).
Putup is a build system using Tup's Tupfile syntax, written in C++20. It builds itself (self-hosting).
Full reference: https://github.com/typeless/putup/blob/main/docs/reference.md
Putup requires putup in PATH for self-hosting builds.
| Command | Action |
|---|---|
make | Configure and build (runs putup configure + putup build) |
make V=1 | Build with verbose output |
make test | Run unit tests + E2E tests |
make tidy | Run clang-tidy |
make iwyu | Detect dead includes via clang-include-cleaner |
make format | Format with clang-format |
make check | Full CI: format-check + tidy + test |
make clean | Clean build artifacts |
make distclean | Full reset: remove build/ |
Direct test execution:
./build/test/unit/putup_test # All tests
./build/test/unit/putup_test -s # Verbose output
./build/test/unit/putup_test '[e2e]' # E2E tests only
./build/test/unit/putup_test '[tag]' # Specific tag
./build/test/unit/putup_test '~[e2e]~[shell]' # Exclude tags
Or build and run directly:
putup configure -B build
putup -B build
./build/putup
Tests come first. Always.
make format && make tidy./build/test/unit/putup_test '[new_feature]' # RED: fails
# ... implement ...
./build/test/unit/putup_test '[new_feature]' # GREEN: passes
make test # No regressions
make format && make tidy # Clean up
For bug fixes: write a test that reproduces the bug first, then fix.
All tests live in test/unit/ and use Catch2 with BDD-style macros (SCENARIO/GIVEN/WHEN/THEN).
Unit: [lexer] [parser] [eval] [builder] [graph] [index] [exec] [glob] [hash] [path_utils] [dep_scanner]
E2E: [e2e] [build] [clean] [configure] [groups] [import] [incremental] [variant] [multi-variant] [scope] [strict] [shell]
auto f = E2EFixture { "fixture_name" }; // Copies test/e2e/fixtures/fixture_name/ to temp dir
// Putup commands (return result with .success(), .stdout_output, .stderr_output)
f.init(); f.build(); f.build({ "-v" }); f.clean(); f.distclean(); f.parse();
f.pup({ "show", "compdb" });
// Filesystem checks
f.exists("path"); f.is_file("path"); f.is_directory("path"); f.is_executable("path");
// File I/O
f.read_file("path"); f.write_file("path", "content");
f.append_file("path", "content"); f.remove_file("path");
// Other
f.run("./program"); f.run_pup_in_dir("subdir"); f.mkdir("path");
f.create_symlink("target", "link");
// Environment variables (RAII -- restores original on scope exit)
auto env = EnvGuard { "VAR_NAME", "value" };
SCENARIO("Feature description", "[e2e][feature_tag]")
{
GIVEN("an initialized project")
{
auto f = E2EFixture { "fixture_name" };
REQUIRE(f.init().success());
WHEN("the project is built")
{
auto result = f.build();
THEN("the output exists")
{
REQUIRE(result.success());
REQUIRE(f.is_executable("output"));
}
}
}
}
Fixtures use Tupfile.fixture naming. The E2EFixture constructor copies fixtures to a temp dir and renames Tupfile.fixture to Tupfile. This prevents fixtures from being parsed during putup's own build.
test/e2e/fixtures/
simple_c/
hello.c
Tupfile.fixture # Renamed to Tupfile during test
multi_dir/
Tupfile.ini # Project root marker
lib/
Tupfile.fixture
app/
Tupfile.fixture
| Type | Structure | Use case |
|---|---|---|
| Simple | Single dir with Tupfile.fixture | Single-rule tests |
| Multi-directory | Tupfile.ini + subdirs | Cross-directory deps, groups |
| Shell | Includes test.sh | Complex multi-step scenarios |
auto result = run_shell_fixture("fixture_name"); // Runs test.sh in fixture dir
REQUIRE(result.success());
The test.sh receives $PUP pointing to the putup binary:
#!/bin/bash
set -e
$PUP configure
$PUP
test -f expected_output.txt
Keep the work directory for inspection:
KEEP_WORKDIR=1 ./build/test/unit/putup_test '[failing_test]'
# Inspect /tmp/claude/e2e_* for the test directory
Print output inside tests:
INFO("stdout: " << result.stdout_output);
INFO("stderr: " << result.stderr_output);
Pass -v for verbose putup output:
auto result = f.build({ "-v" });
pup/
include/pup/
cli/ # Command-line interface, options, output
core/ # Core types, hash, result, platform
parser/ # Lexer, parser, AST, evaluator, depfile
graph/ # Dependency DAG, builder, topological sort, rule patterns
index/ # Binary index format, reader/writer
exec/ # Scheduler, command runner
src/ # Implementation files (mirrors include/ layout)
test/
unit/ # Catch2 tests (test_*.cpp) + e2e_fixture.{hpp,cpp}
e2e/fixtures/ # Test fixture data
third_party/ # expected-lite, fmt, sha256, Catch2
Makefile # Workflow wrapper
Tupfile # Build configuration
Tuprules.tup # Shared build rules
-nostdlib++, zero runtime dependencyStringId (4-byte interned string), Vec<T> (growable array), Function<Sig> (type-erased callable), SteadyClock (monotonic timer)-MD flags for header trackingResult<T>, never throw exceptionsnpx claudepluginhub typeless/putup --plugin putupCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.