A build system using Tupfile syntax.
User Documentation: See docs/reference.md for command reference, Tupfile syntax, and configuration.
Claude Code Plugin: Load with
claude --plugin-dir plugins/putupfor skills on Tupfile authoring, project setup, cross-compilation, composable libraries, and contributing. External install:/plugin marketplace add typeless/putup.
- Uses Tup's Tupfile/Tuprules.tup format
- Git-inspired binary index (not SQLite)
- SHA-256 content hashing
- No FUSE, no Lua
- No libstdc++ (
-nostdlib++); custom primitives:StringId,Vec<T>,Function<Sig>
Design Coherence: Ensure consistency between design documentation and implementation. No special-case handling or workarounds—if something doesn't fit the architecture, fix the design.
Architecture Details: See DESIGN.md for internal architecture, data structures, and design rationale.
Putup builds itself (self-hosting). Requires putup in PATH.
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/Or use putup directly:
putup configure -B build # Generate build/tup.config from configs/
putup -B build # Build
./build/putup # Run the built binaryBuild artifacts go to build/.
Testing Guide: See TESTING.md for E2E fixture conventions, test tags, and debugging tips.
make test # Run all tests
./build/test/unit/putup_test # All tests (unit + E2E)
./build/test/unit/putup_test -s # Verbose output
./build/test/unit/putup_test '[e2e]' # E2E tests only
./build/test/unit/putup_test '[build]' # Build tests only
./build/test/unit/putup_test '[groups]' # Group semantics tests ({group}, <group>)
./build/test/unit/putup_test '[clean]' # Clean/distclean tests only
./build/test/unit/putup_test '[incremental]' # Incremental rebuild tests
./build/test/unit/putup_test '[variant]' # Out-of-tree/variant tests
./build/test/unit/putup_test '[multi-variant]' # Multi-variant parallel builds
./build/test/unit/putup_test '[scope]' # Scoped build tests
./build/test/unit/putup_test '[target]' # Target parsing tests
./build/test/unit/putup_test '[shell]' # Shell fixture tests
./build/test/unit/putup_test '[configure]' # Two-pass config generation tests
./build/test/unit/putup_test '[strict]' # Convention checker (--strict) testsE2E tests use BDD style with SCENARIO/GIVEN/WHEN/THEN macros. Use the E2EFixture class to manage test fixtures:
SCENARIO("Feature description", "[e2e][tag]")
{
GIVEN("an initialized project")
{
auto f = E2EFixture { "fixture_name" }; // From test/e2e/fixtures/
REQUIRE(f.init().success());
WHEN("something happens")
{
auto result = f.build();
THEN("expected outcome")
{
REQUIRE(result.success());
REQUIRE(f.is_executable("output"));
}
}
}
}E2EFixture methods:
init(),build(),clean(),distclean(),parse(),pup()- Run putup commandsexists(),is_file(),is_directory(),is_executable()- Check pathsread_file(),write_file(),append_file(),remove_file()- File I/Orun()- Execute a program and capture outputrun_pup_in_dir()- Run putup from a subdirectorycreate_symlink(),mkdir()- Filesystem operations
Environment variables:
auto env = EnvGuard { "VAR_NAME", "value" }; // RAII - auto-restores on scope exitThis project follows Test-Driven Development (TDD) with BDD-style tests.
Tests come first. Always.
- Write a failing test first - Define expected behavior before implementation
- Run the test - Verify it fails for the right reason
- Write minimal code - Just enough to make the test pass
- Run the test - Verify it passes
- Refactor - Clean up while keeping tests green
- Format and lint - Run clang-format and clang-tidy before committing
# TDD cycle
./build/test/unit/putup_test "[new_feature]" # Run specific test (fails)
# ... implement ...
./build/test/unit/putup_test "[new_feature]" # Run again (passes)
make test # Verify no regressions
make format # Format code
make tidy # Run clang-tidy
make iwyu # Check for dead includesFor bug fixes: Write a test that reproduces the bug first, then fix.
For new features: Write tests expressing the expected behavior before any implementation. Use BDD-style SCENARIO/GIVEN/WHEN/THEN for E2E tests.
Always run make format, make tidy, and make iwyu before finalizing changes.
See STYLE.md for the complete C++ style guide.
Key points:
- Use
autowith trailing return types - Right-side const (
auto const&) - Anonymous namespaces for internal linkage
- WebKit-based formatting (see
.clang-format)
pup/
├── build/ # Build output (tup variant directory)
│ ├── tup.config # Variant configuration
│ ├── putup # Main binary
│ └── test/unit/putup_test
├── 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
├── test/
│ ├── unit/ # Catch2 unit + E2E tests (BDD style)
│ │ ├── test_*.cpp # Test files
│ │ └── e2e_fixture.{hpp,cpp} # E2E test infrastructure
│ └── e2e/fixtures/ # Test fixture data (Tupfiles, sources)
├── third_party/ # expected-lite, fmt, sha256, Catch2
├── Makefile # Workflow wrapper (make test, make tidy, etc.)
├── Tupfile # Build configuration
└── Tuprules.tup # Shared build rules
For development context, the original tup source (C) can be found at https://github.com/gittup/tup
Putup supports large multi-directory projects with cross-compilation. Key features tested with real-world projects:
- ✅ Multi-directory Tupfile scanning (75+ Tupfiles, 600+ commands)
- ✅ Demand-driven parsing with cycle detection
- ✅ Cross-directory order-only groups
- ✅ Variant build path resolution
- ✅
importdirective (environment variables from SDK) - ✅
exportdirective (for subprocess calls like pkg-config) - ✅ Bang macros
- ✅ Ghost nodes for cross-directory generated file dependencies
project/
├── Tupfile.ini # Project root marker
├── Tuprules.tup # Shared rules with import/export
├── build-variant/ # Variant output directory
│ └── tup.config # Variant configuration
└── subsystems/ # Subdirectories with Tupfiles
├── daemon1/Tupfile
├── daemon2/Tupfile
└── ...
# Import from SDK environment
import TARGET_PREFIX
import CC
import CXX
import CFLAGS
import LDFLAGS
# Exports for pkg-config subprocess calls
export PKG_CONFIG_SYSROOT_DIR
export PKG_CONFIG_PATH
# Bang macros for cross-compilation
!cc = |> ^ CC %o^ $(CC) $(CFLAGS) -c -o %o %f |>
- ✅ Foundation - Core types, hash, result, platform
- ✅ Parser - Lexer, AST, parser, evaluator, depfile
- ✅ Graph - DAG, builder, topological sort
- ✅ Index - Binary format, reader/writer, implicit deps
- ✅ Execution - Scheduler, command runner, incremental builds
- 🔄 Polish - Edge cases, error handling, performance
See DESIGN.md for internal architecture, data structures, and design rationale.