TriBITS Developers Guide
This guide covers everything a developer needs to work effectively within a TriBITS-managed project. It starts with the mental model, moves through project structure and daily workflows, and ends with debugging techniques and common pitfalls. If you are new to TriBITS, read the first three sections in order. After that, use the remaining sections as reference.

What TriBITS Is and What Problems It Solves
TriBITS is a set of CMake macros and conventions that turn a collection of loosely related CMake projects into a managed, dependency-aware build system. The name stands for Tribal Build, Integrate, and Test System.
Without TriBITS, a large project with many components requires significant custom CMake infrastructure to handle dependency resolution, cross-component testing, and multi-repository builds. TriBITS provides that infrastructure out of the box, so teams can focus on their actual code rather than on build system plumbing.
The framework is not a replacement for CMake. It builds on top of CMake, adding a package layer, a dependency resolution engine, and test categorization. Everything CMake can do is still available. TriBITS extends CMake rather than replacing it.
The Core Idea
The central concept is the package. A package is a unit of code with declared dependencies, libraries, and tests. The framework knows about all packages in the project, resolves their dependencies, and configures, builds, and tests them in the correct order.
This is valuable because it turns implicit relationships ("package A happens to use headers from package B") into explicit, tool-visible declarations. When the dependency graph is explicit, tools can analyze it, CI can use it to run the right tests, and developers can reason about the impact of changes.
Mental Model: Packages, Dependencies, Configuration, Test Gates
Packages
A package is the smallest independently configurable unit in a TriBITS project. Each package has:
- A name, unique within the project.
- A set of declared dependencies (other packages or external libraries it needs).
- Libraries it builds.
- Tests it provides.
- Optionally, subpackages for finer-grained decomposition.
Think of packages like modules in a well-structured codebase. They have clear boundaries, clear inputs (dependencies), and clear outputs (libraries and headers).
Dependencies
Dependencies are declared explicitly in each package's cmake/Dependencies.cmake file. There are several types:
- Required library dependencies must be available or the package cannot be enabled.
- Optional library dependencies add features when available but are not essential.
- Test dependencies are needed to build and run tests but not the library itself.
- TPL (third-party library) dependencies point to external software not managed by TriBITS.
The framework builds a directed acyclic graph (DAG) from these declarations and uses it to determine build order, enable/disable propagation, and test selection.
Configuration
Configuration is driven by CMake cache variables. The most important ones control which packages are enabled:
cmake -D MyProject_ENABLE_PackageA=ON \
-D MyProject_ENABLE_TESTS=ON \
-B build
When you enable a package, the framework automatically enables its required dependencies. This means you do not need to manually figure out the transitive closure of dependencies. Enable what you want to work on, and TriBITS handles the rest.
Test Gates
TriBITS categorizes tests as BASIC, NIGHTLY, or HEAVY. CI pipelines use these categories to balance speed and coverage:
- BASIC tests run on every commit. They should be fast (under 30 seconds each) and catch the most common breakage.
- NIGHTLY tests run on a schedule. They can take minutes each and cover more scenarios.
- HEAVY tests run on demand. They can take hours and cover edge cases, stress testing, or full-system integration.
This categorization is part of the test declaration, not an afterthought. It ensures that CI can make intelligent decisions about what to run and when.
Project Layout Patterns
A typical TriBITS project has this structure:
ProjectRoot/
CMakeLists.txt # Project-level, calls tribits_project()
ProjectName.cmake # Project settings
PackagesList.cmake # Lists available packages
TPLsList.cmake # Lists available TPLs
cmake/
tribits/ # TriBITS framework files
packages/
PackageA/
CMakeLists.txt # Calls tribits_package(PackageA)
cmake/
Dependencies.cmake # Declares dependencies
src/
CMakeLists.txt # Library targets
test/
CMakeLists.txt # Test targets
PackageB/
...
The key files are:
PackagesList.cmake lists every package in the project with its directory location and default enable state. This is how the framework discovers packages.
Dependencies.cmake in each package declares what that package needs. This is the input to the dependency resolution engine.
CMakeLists.txt in each package follows a standard pattern: tribits_package() at the top, target declarations in the middle, tribits_package_postprocess() at the bottom.
Subpackage Structure
Large packages can be decomposed into subpackages. Each subpackage has its own dependency list and can be enabled independently:
BigPackage/
CMakeLists.txt # tribits_package(BigPackage) + subpackage declarations
cmake/
Dependencies.cmake
Core/
CMakeLists.txt
cmake/
Dependencies.cmake
IO/
CMakeLists.txt
cmake/
Dependencies.cmake
Subpackages are useful when a package has clearly separable concerns but the components are not independent enough to be full packages.
Common Workflows
Configure, Build, Test
The daily cycle:
# Configure with your packages enabled
cmake -D MyProject_ENABLE_MyPackage=ON \
-D MyProject_ENABLE_TESTS=ON \
-B build
# Build
cmake --build build -j8
# Test
cd build && ctest -j8 --output-on-failure
The configure step is where TriBITS does its work: resolving dependencies, enabling required packages, setting up the build graph. Build and test are standard CMake/CTest.
Adding a New Package
- Create the directory structure (package dir,
cmake/Dependencies.cmake,src/,test/). - Write the package
CMakeLists.txtwith the TriBITS macros. - Declare dependencies in
Dependencies.cmake. - Add the package to
PackagesList.cmake. - Configure, build, and test.
The Guides section has a detailed walkthrough of this process.
Enabling a Subset of Packages
For focused development:
# Enable just what you need
cmake -D MyProject_ENABLE_MyPackage=ON \
-D MyProject_ENABLE_TESTS=ON \
-B build
TriBITS enables the required dependencies automatically. Optional dependencies are controlled by <Project>_ENABLE_ALL_OPTIONAL_PACKAGES (ON by default).
Running Specific Test Categories
# Basic tests only (fast feedback)
ctest -L BASIC
# Nightly tests
ctest -L NIGHTLY
# Everything
ctest
Working with Multiple Repositories and Superbuilds
Real-world projects often span multiple Git repositories. TriBITS handles this through its multi-repo support.
Setting Up Extra Repositories
Define an extra repos file:
tribits_project_define_extra_repositories(
Repo1 packages git@github.com:org/repo1.git main NOPACKAGES
Repo2 packages git@github.com:org/repo2.git main NOPACKAGES
)
Then configure with:
cmake -D MyProject_EXTRA_REPOSITORIES="Repo1;Repo2" \
-D MyProject_ENABLE_SomePackageFromRepo2=ON \
-B build
The framework clones (or updates) the repos, discovers their packages, and includes them in the dependency graph as if they were part of the main project.
Cross-Repository Dependencies
Packages in different repos can depend on each other normally. The dependency graph spans all repositories. This means a change in Repo1 that breaks a package in Repo2 will be caught by the superbuild CI.
Keeping Things in Sync
The challenge with multi-repo builds is version compatibility. Pin extra repos to specific commits or tags in CI to ensure reproducibility. Let developers use branch tips for day-to-day work, but run integration tests against pinned versions.

Debugging and Trace Techniques
When something goes wrong in the build configuration, these techniques help.
Verbose Configure
cmake -D MyProject_VERBOSE_CONFIGURE=ON -B build
This prints detailed information about what the framework is doing during configuration: which packages are being enabled, which dependencies are being resolved, and what variables are being set.
Dependency Graph Visualization
TriBITS can generate dependency graph files that can be visualized with Graphviz:
cmake -D MyProject_DEPS_DEFAULT_DOT_FILE=deps.dot -B build
dot -Tpng deps.dot -o deps.png
Looking at the actual dependency graph often reveals surprises: cycles you did not know about, transitive dependencies that should be direct, or packages that depend on everything.
CTest Verbose Output
ctest -V --output-on-failure
The -V flag shows the full command line for each test. --output-on-failure shows stdout/stderr only for tests that fail.
Common Debugging Scenarios
Package not found. Check that it is listed in PackagesList.cmake and the directory exists.
Dependency not resolved. Check Dependencies.cmake for typos. Enable verbose configure to see what the framework is doing.
Test passes locally, fails in CI. Check for environment assumptions: hard-coded paths, reliance on specific compiler versions, or tests that depend on execution order.
Practical Examples and Anti-Patterns
Good: Small, Focused Packages
A package that does one thing and declares exactly what it needs. Dependencies are minimal. Tests are fast and focused on the package's own behavior.
Anti-Pattern: The God Package
A package that depends on everything and provides everything. It cannot be built incrementally. A change anywhere triggers a full recompilation. Tests take hours. Break it into subpackages or independent packages.
Good: Explicit Optional Dependencies
tribits_package_define_dependencies(
LIB_REQUIRED_PACKAGES Core
LIB_OPTIONAL_PACKAGES AdvancedSolver
)
The code guards against the optional dependency:
#ifdef HAVE_ADVANCEDSOLVER
// Use advanced solver
#else
// Use fallback
#endif
Anti-Pattern: Hidden Dependencies
Using headers from another package without declaring the dependency. Works when both packages happen to be enabled, fails when they are not. Always declare what you use.
Good: Meaningful Test Names and Categories
tribits_add_test(myapp
NAME integration_cross_package_data_flow
CATEGORIES NIGHTLY
NUM_MPI_PROCS 4
)
A developer reading the test dashboard knows exactly what this test covers and why it runs nightly.
Anti-Pattern: Test1, Test2, Test3
Unnamed or numerically named tests tell you nothing when they fail. Take 10 seconds to write a descriptive name.
FAQ
Q: Can I use TriBITS for a project with only 2 or 3 packages? A: You can, but the overhead might not be worth it. TriBITS shines when the number of packages is large enough that manual dependency management becomes error-prone. For small projects, plain CMake with add_subdirectory() is simpler.
Q: Does TriBITS work with Ninja? A: Yes. TriBITS generates standard CMake build files. Use -G Ninja at configure time and everything works as expected.
Q: Can I mix TriBITS packages with regular CMake targets? A: Yes, with care. TriBITS packages can depend on external CMake targets through the TPL mechanism. Going the other direction (a regular CMake target depending on a TriBITS package) requires that the TriBITS package is installed and found via find_package().
Q: How do I handle a dependency that is not available on all platforms? A: Use an optional dependency. Declare it as LIB_OPTIONAL_PACKAGES or LIB_OPTIONAL_TPLS, and use preprocessor guards or CMake conditionals in your code and tests.
Q: What happens if I enable a package whose dependencies are not available? A: TriBITS will either auto-enable the required dependencies (if they are internal packages) or print an error (if they are TPLs that cannot be found). It will not silently build with missing dependencies.