PSA: Your Package Name and Target Namespace Should Match

October 15, 2024
Target Namespace, Package Name. Logo of CMake.

What’s in a Name[space]?

For some time, CMake has recommended using target namespaces for imported targets. This has the advantage of reducing name conflicts while providing an indicator as to what provides various targets. (There is also the advantage, when using the preferred and strongly recommended “::” namespace separator, that CMake enforces the existence of targets with qualified names, rather than silently assuming that an unknown name should be turned into a -l link argument.)

Although CMake permits targets exported to CMake’s native package information format to use any prefix whatsoever as the namespace name, we strongly recommend using the package name plus the recognized “::” separator as the prefix. (By “package name”, we mean the argument users provide to find_package. Note also that this name is partially case sensitive; however the namespace is capitalized is how consumers should capitalize the name given to find_package, even if the name of the package information file is lowercase.) This provides clarity for consumers as there is no need to guess what namespace a particular package uses for its exported targets, nor to guess what package provides a particular imported target.

Examples

Let’s consider a hypothetical “AcmeWidget” package that follows our recommendation. Here’s how someone might use it:

find_package(AcmeWidget ...)
...
target_link_libraries(MyProgram ... AcmeWidget::Widget)

Again, whether the project ships AcmeWidgetConfig.cmake or acmewidget-config.cmake doesn’t matter; the namespace capitalization should match the capitalization that the user uses in the find_package call.

Here are some examples of how “AcmeWidget” might export its targets:

# Not recommended; no NAMESPACE specified

install(EXPORT ...)

# Not recommended; does not use the recognized namespace separator

install(EXPORT ... NAMESPACE AcmeWidget)
install(EXPORT ... NAMESPACE AcmeWidget_)
install(EXPORT ... NAMESPACE AcmeWidget:)

# Not recommended; namespace does not match package name

install(EXPORT ... NAMESPACE Acme::)

# Recommended

install(EXPORT ... NAMESPACE AcmeWidget::)

# A backward-compatibility shim provided in AcmeWidgetConfig.cmake[.in]

add_library(Acme::Widget INTERFACE IMPORTED)
set_target_properties(Acme::Widget PROPERTIES
    INTERFACE_LINK_LIBRARIES AcmeWidget::Widget
    DEPRECATION "Use AcmeWidget::Widget instead")

Why is This Important Going Forward?

CMake has long had its own format for describing imported targets that offers many benefits… but only for consumers that also use CMake.

Some time ago, we proposed a Common Package Specification to improve package interoperability, while also striving to address shortfalls of existing formats, including aspects of CMake’s native format that present limitations and/or difficulties. More recently, significant efforts are underway to support CPS in CMake. (As of writing, partial, experimental export support is available in the latest development versions and is expected to be part of CMake 3.31.)

What does this have to do with target namespaces? CMake, in its native export format, allows anything to be used as an export prefix, even prefixes that don’t result in “qualified” names (that is, names consisting of more than one part separated by “::”). By contrast, CPS codifies the expression of external target (or “component”, as they are named in CPS) names to be specified in terms of the package name. The effect for CMake users is that imported targets always live in a namespace that is exactly the name of the package which provides them. When using CPS, the “advice” above is no longer advisory, it’s enforced. (As a small bonus, however, when exporting to CPS it is not necessary to separately specify the target namespace, since it is “baked in”.)

Hopefully your package, or the packages you consume, are already following our recommendation. However, this is not true of all packages. In particular, we are aware that some organizations may provide one or more packages using the organization name (e.g. “BobJones”, “FooProject”, “AcmeCorp”) as the target namespace. Other projects do not use namespaces at all.

Mitigations and Recommendation

For projects that strongly desire to retain the ability to namespace their imported targets using e.g. an organization namespace (such as “Acme” in our previous example), one possible alternative is to provide that namespace as a package in its own right. This package specification must not be owned by any individual project. Additionally, any version information will apply to the organization package, with no way for consumers to request versions of individual packages, except to the extent that those version identifiers map to the version of the organization package. The organization package itself does not need to describe each package owned by the organization, as individual packages can make their components available via package appendices which either provide libraries directly, or provide interface components that reference specifically named packages. However, in order to use such targets, consumers must request the organization package. While there are use cases for this approach (Qt and Boost come to mind), we do not recommend this approach for most projects, especially for projects not already doing something along these lines. (Qt is unique in that they have an explicit goal of binding multiple repositories together into a single logical package. Boost is unique in that the project is a unified whole that is subsequently distributed as many components which are generally separable.)

It is our sincere hope that CPS will be the future, enabling packages made by any participating build tool to be consumed by any other participating tool. (There are even efforts to provide pkg-config compatibility, though it is much easier and more reliable to translate CPS into pkg-config than the other way around.) Both for the sake of future CPS compatibility, as well as for the other reasons outlined above, we recommend that packages which currently have a mismatch between their package name and the namespace used for their exported targets be updated to remove this mismatch. Backwards compatibility can be accomplished by creating a CMake INTERFACE target using the old name that references the new name. (Refer to the preceding example. We strongly recommend setting the DEPRECATION property for such targets.)

Non-Issues

Some members of the community have mentioned the need for certain libraries to be accessible via generic names, when the implementation is not generic. (Some examples include OpenGL, BLAS, and MPI.) While mechanisms for providing interchangeable libraries such as these are still under investigation, this is not the same issue, since an implementation-agnostic user will not be requesting or referencing an implementation-specific package name. Instead, such a user might request the “OpenGL” package and link to the target OpenGL::GL. Although this target might actually resolve to e.g. a Mesa library, there is no mismatch between the target namespace and package name used by the consumer; the restriction described in this PSA does not apply.

(A possible mechanism for supporting such libraries is to provide a vendor-independent package specification that enumerates the available targets as interface libraries and for each vendor to supply supplemental files that map the vendor-agnostic names to vendor-specific requirements. Precisely how to accomplish this is beyond the scope of this blog, but we believe it can be mostly accomplished within the current specification. The major limitation involves how to prevent mixing implementations, which stems from a more general need for a mechanism to identify components “conflict”, i.e. must not be linked together. This is a known issue of the specification that is on our roadmap to address at some point in the future.)

Parting Thoughts

Although this post speaks to an issue that becomes more pertinent because of CPS, we feel that this recommendation is relevant for all projects using CMake. Accordingly, a detailed exploration of CPS and CMake support for the same is considered out of scope. Stay tuned for more comprehensive coverage.

Leave a Reply