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

    • Guides - TriBITS

Guides

Practical walkthroughs for the tasks you actually do. Each guide is written to get you from start to finish without detours.

Step-by-step workflow illustration for TriBITS package setup

How to Use These Guides

Each guide targets a single task. They assume you have a working TriBITS project and want to do something specific: add a package, fix a dependency, write a test, or configure a build variant. If you need background on how the framework works, start with the Documentation section instead.

The guides are ordered roughly by how often people need them. The first few cover things you will do repeatedly. The later ones handle less common situations.

Adding a New Package

This is the most common task for teams working with TriBITS. A new package means a new directory, a few CMake files, and some declarations.

Step 1: Create the Directory Structure

Every TriBITS package follows a standard layout:

MyPackage/
  CMakeLists.txt
  cmake/
    Dependencies.cmake
  src/
    CMakeLists.txt
  test/
    CMakeLists.txt

The top-level CMakeLists.txt registers the package. The Dependencies.cmake file declares what this package needs. The src/ and test/ directories contain the actual code and tests.

Step 2: Declare the Package

In the package's top-level CMakeLists.txt:

tribits_package(MyPackage)
tribits_add_library(mypackage
  HEADERS mypackage.hpp
  SOURCES mypackage.cpp
)
tribits_add_test_directories(test)
tribits_package_postprocess()

The tribits_package() call registers the package name with the framework. Everything between that and tribits_package_postprocess() defines what the package contains.

Step 3: Declare Dependencies

In cmake/Dependencies.cmake:

tribits_package_define_dependencies(
  LIB_REQUIRED_PACKAGES SomeUpstreamPackage
  LIB_OPTIONAL_PACKAGES AnotherPackage
  TEST_REQUIRED_PACKAGES TestingUtils
)

Required dependencies are always enabled when your package is enabled. Optional dependencies are used if available but do not prevent your package from building.

Step 4: Register with the Project

Add the package name to the project's package list file. The exact mechanism depends on your project structure, but typically there is a PackagesList.cmake or similar file in the project root.

Step 5: Build and Test

Configure with your package enabled:

cmake -D MyProject_ENABLE_MyPackage=ON -D MyProject_ENABLE_TESTS=ON ..
make -j8
ctest

If everything compiles and tests pass, the package is integrated.

Watch Out

The most common mistake is forgetting to list a dependency. If your package uses headers from another package but does not declare the dependency, the build will work on some machines (where the headers happen to be on the include path) and fail on others. Always be explicit about what you need.

Configuring Dependencies

Dependency management is the core of TriBITS. Getting it right means clean builds, reliable tests, and minimal recompilation times. Getting it wrong means mysterious failures and "works on my machine" problems.

Required vs Optional Dependencies

A required dependency is one your package cannot function without. If it is not available, your package should not be enabled at all. An optional dependency adds features but is not essential.

The decision matters because required dependencies create hard edges in the dependency graph. If A requires B, then disabling B disables A. Optional dependencies are softer: A works without B but can do more with it.

Subpackage Dependencies

For large packages, you can define subpackages. Each subpackage has its own dependency list. This gives finer-grained control over what gets enabled and tested.

tribits_package(BigPackage)
tribits_add_subpackages(
  Core
  IO
  Solvers
)
tribits_package_postprocess()

Subpackages can depend on each other (within the same parent package) or on external packages. The dependency graph handles them like any other package.

Writing Effective Tests

TriBITS uses CTest under the hood, so you have all of CTest's features available. The TriBITS macros add conventions on top:

tribits_add_executable_and_test(
  MyTest
  SOURCES my_test.cpp
  NUM_MPI_PROCS 4
  CATEGORIES BASIC
)

The CATEGORIES field maps to the test categories (BASIC, NIGHTLY, HEAVY) that control when the test runs in CI.

Test Design Principles

Keep BASIC tests fast. Under 30 seconds each. These run on every PR.

Test interfaces, not implementations. If a refactor breaks your test but not any real user, the test is too coupled to implementation details.

Use meaningful failure messages. When a test fails in CI, the developer looking at the log should understand what went wrong without reading the test source.

Test cross-package interactions. If your package depends on another, write at least one integration test that exercises the interface between them.

Setting Up Multi-Repository Builds

Multi-repo builds are one of TriBITS's distinguishing features. The setup involves defining an extra-repos file and configuring the superbuild.

Define the Repository List

Create an extra repos file (for example, cmake/ExtraRepositoriesList.cmake):

tribits_project_define_extra_repositories(
  ExtraRepo1 packages git@github.com:org/repo1.git main NOPACKAGES
  ExtraRepo2 packages git@github.com:org/repo2.git main NOPACKAGES
)

Configure the Superbuild

cmake -D MyProject_EXTRA_REPOSITORIES="ExtraRepo1;ExtraRepo2" \
      -D MyProject_ENABLE_SomePackageFromRepo2=ON \
      ..

TriBITS clones or updates the repos, discovers packages, resolves cross-repo dependencies, and configures everything as one build.

Keeping Repos in Sync

The trickiest part of multi-repo builds is keeping the repos compatible. A change in Repo 1 might break Repo 2. There is no magic solution here. You need CI that tests the integration and a team culture that treats cross-repo breakage as a high-priority issue.

Multi-repository workflow showing dependency flow between separate Git repositories

Build Variants

Sometimes you need multiple build configurations: debug and release, with and without MPI, different compiler versions. TriBITS handles this through CMake cache variables, just like standard CMake, but the package enable system adds a layer.

# Debug build with MPI
cmake -D CMAKE_BUILD_TYPE=Debug \
      -D TPL_ENABLE_MPI=ON \
      -D MyProject_ENABLE_ALL_PACKAGES=ON \
      ..

The recommended approach is to use separate build directories for each variant. Do not try to switch configurations in the same build directory because CMake cache variables can interact in unexpected ways.

Common Mistakes and How to Fix Them

Circular dependencies. If package A depends on B and B depends on A, the build will fail. Refactor one of them to break the cycle. Often this means extracting shared code into a third package.

Testing against disabled packages. If a test needs a package that might be disabled, guard it with a conditional:

if (MyProject_ENABLE_OptionalDep)
  tribits_add_test(...)
endif()

Overly broad dependencies. Listing every package in the project as a dependency makes the graph useless. Only depend on what you directly use.

Forgetting to call tribits_package_postprocess(). This finalizes the package registration. Without it, tests and install rules may not work correctly.