Skip to content

Handling GLM's Config Macros and Header-Only Library Target #2

@lyrahgames

Description

@lyrahgames

Header-only C++ libraries like GLM make extensive use of configuration macros to control API, ABI, and general behavior. For binfull libraries, the handling is straightforward: they are compiled once with a fixed configuration, ensuring that all dependents link against a consistent binary. Tests also typically validate a single configuration and multiple build configs are used to test selected sets of configuration macros.

For header-only libraries, such as GLM, tests may consist of multiple unit test executables whereas different unit tests may define different configuration macros. This effectively means multiple configurations are exercised within the same build.
Unfortunately, using the standard approach (single configured dependency) becomes sub-optimal in this case. The tests would need to be filtered or split based on the chosen configuration. This introduces complexity and additional maintenance overhead and it does not scale well as the number of configuration combinations grows.

Instead, I’m considering to add an intermediate header-only library target, such as lib{glm-headers} or lib{glm-header-only} (name is still open to discussion), that contains only the library's headers and inclusion files and does not export any config macros. The main library target lib{glm} depends on header-only library target and reuses it. Additionally, it adds the module interface unit, applies config macros via build2 configuration variables, and exports the resulting macro definitions to consumers. This would allow tests (and even consumers) to depend directly on the header-only library target and define their own macros freely when explicitly needed. Still, consumers would by default depend on lib{glm} and rely on dependency configuration negotiation in the usual way. My current solution looks as follows:

# Main GLM Library Target
#
[rule_hint=cxx] lib{glm}:

# Bare Header-Only Library without Configuration Applied
#
lib{glm-headers}: glm/{hxx ixx}{**} glm/hxx{**.h}
{
  cxx.export.poptions = "-I$src_base"
}
cxx.poptions =+ "-I$src_base"

# Module Interface Unit
#
lib{glm}: glm/mxx{**}: include = $cxx.features.modules

# Re-Use Bare Header-Only Library Containing Headers
#
lib{glm}: lib{glm-headers}
lib{glm}: cxx.export.libs = lib{glm-headers}

# Build and Export Options
#
poptions = # Empty

if ($config.glm.explicit_ctor)
  poptions += -DGLM_FORCE_EXPLICIT_CTOR
# ...

lib{glm}:
{
  cxx.export.poptions = $poptions
  bin.binless = true
}
cxx.poptions += $poptions

@boris-kolpackov
What do you think about this practice for this package and maybe even for other header-only libraries with configuration variables?
Is there a better or more idiomatic way to handle this without introducing significant complexity in test handling?
Appreciate your thoughts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions