TriBITSTriBITS
Docs
Guides
Reference
Pipelines
Downloads
About
Changelog
Docs
Guides
Reference
Pipelines
Downloads
About
Changelog
  • Legacy Documentation

    • Legacy Documentation - TriBITS
    • TriBITS Developers Guide
    • TriBITS Maintainers Guide
    • TriBITS Build Reference

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.

TriBITS developer workflow showing configure, build, test, and iterate cycle

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

  1. Create the directory structure (package dir, cmake/Dependencies.cmake, src/, test/).
  2. Write the package CMakeLists.txt with the TriBITS macros.
  3. Declare dependencies in Dependencies.cmake.
  4. Add the package to PackagesList.cmake.
  5. 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.

Multi-repository dependency graph showing packages across three Git repositories

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.

Prev
Legacy Documentation - TriBITS
Next
TriBITS Maintainers Guide