Components, Targets and Symbols
By now, hopefully you have heard about the Common Package Specification (“CPS”). If not, there is some introductory information here. In short, CPS aims to provide the sort of feature-rich package descriptions needed by modern build tooling in a format that is usable by many tools.
Because CPS deals with already-compiled packages, the decision was made to refer to the individual “bits” that make up packages as “components” rather than “targets”. However, when we talk about importing and exporting these to or from CMake, this distinction is essentially moot; that is, a CPS “component” and a CMake “target” are generally synonymous.
This, however, presents a small issue when importing packages described using CPS, because CMake’s find_package has its own idea about what “components” are, and because CPS has no direct correspondence with the way “components” are implemented in CMake-script package descriptions.
Instead, when find_package finds a CPS package description, any optional or required COMPONENTS mentioned in the call are mapped directly to CPS “components”, which are just “targets” in CMake’s world.
This actually has several advantages. First, if a consumer knows the names of the targets it will use from a package, it can simply list them in its find_package call without needing to know how the package author set up partial imports (which, since packages are CMake script, could be implemented in any number of more or less sane manners).
Second, while the ability of traditional CMake script package descriptions to do almost anything limits CMake’s ability to “pre-inspect” a package before committing to trying to import it, this is not the case for CPS. When CMake finds a CMake-script package description, it has a very limited window, corresponding to the version file (which also runs bespoke CMake script to determine whether the package version is acceptable according to the parameters supplied to the find_package call), in which to decide whether to “commit” to the candidate package or keep looking. This means that, while an import may succeed or fail based on component-related checks, a failed CMake-script import cannot be safely “unwound” and results in a configuration error. At this point, the user must manually supply an alternate package location or otherwise arrange for the inadequate package to be skipped during search.
Because CPS is declarative rather than functional, CMake can check whether a candidate CPS package description provides the components declared required by the caller, and if not, CMake can continue searching. This affords a much better chance of finding a suitable package without intervention required from the user.
However, it is often easier for consumers to express their requirements as logical entities rather than target names.
Introducing Symbolic Targets
CPS anticipated this problem and addresses it with the concept of symbolic components. A symbolic component is one which has no properties beyond existing; nevertheless, these can have several uses. The one which is most obvious from the previous discussion is to provide logical components which don’t correspond to targets that consumers actually link. For example, a UI library that provides a combination of low-level “core” functions and various optional groupings of widgets might supply very specific targets such as ListWidget and TreeWidget that it wishes to group under the logical banner “Views”. By making these available in a CPS “appendix”, consumers that do not need these widgets can skip importing them (and, more interestingly, can skip importing any dependencies that are unique to those components). Because appendices are the granular unit of import, the package can also provide a symbolic View component in the same appendix which provides ListWidget and TreeWidget. This enables consumers to require the View component in order to obtain the ListWidget and TreeWidget targets.
At the find_package level, this has worked for some time, but CMake projects had no way of actually creating such components in their exported CPS files. CMake 4.2 introduced SYMBOLIC targets — a subtype of INTERFACE targets — as first-class citizens, allowing projects to create and export them in the same manner as any other target type, to fully import them when provided by CPS packages, and to query for their existence using if(TARGET). The ability to export symbolic targets and have them used to select which optional portions of a segmented package to import will be especially useful for migration of packages that implement component requests in CMake-script package descriptions. The use-case for querying symbolic targets will become more clear in the next section.
Symbols as Features
The use of symbolic targets is not limited to segmented packages; they can also be used for feature identification and selection. For example, consider a library which provides a generic interface for loading image data from various formats, which depend on external libraries for individual formats and thus implements them as optional components. This library could create symbolic components (targets) for each format it supports (e.g. JPEG, PNG, TIFF). Consumers can then test for these, or, more interestingly, specify one or more as required components. While there are other ways to implement this, the obvious alternative mechanism for describing support relies on setting CMake variables, and the mechanism for requiring support is both necessarily bespoke and has the same “commitment” problem as any form of component requirements for CMake-script package descriptions. Symbolic components in combination with CPS provide a cross-build-tool means of both expressing and requiring such “optional features” and allow CMake to “try harder” to find a suitable package.
Closing Remarks
Symbolic targets are not going to immediately change the world. However, they are an important mechanism that improves the expressiveness of CPS packages. As we strive to move toward a world of more interoperable package descriptions, symbolic targets will become more interesting as a standardized, portable mechanism for expressing logical package features and/or component groups that do not correspond directly to meaningful build targets.
CMake 4.2 has experimental support for CPS export and import, including symbolic targets (which can also be exported and imported by CMake-script package descriptions, although component requirements must still be manually implemented).
As always, CPS and CMake’s CPS support are ongoing projects. CMake users are encouraged to experiment with CPS support and symbolic targets, and we encourage users of all build systems to share any suggestions or concerns. Issues or discussion of CPS are best raised at the CPS repository, while CMake matters should be posted at the CMake repository. For more general topics, or to request feedback before filing an issue, the somewhat-unofficial C++ Ecosystem Evolution group has a mailing list and can also be contacted via the #ecosystem_evolution channel in the C++ Slack.