Common Package Specification is Out the Gate
After years of development and community collaboration, a major milestone has been reached in CMake’s packaging story: the Common Package Specification (“CPS”) is no longer experimental. What began as an effort to modernize and standardize how build systems describe and exchange package information is now ready for broader adoption. The world of software development has often struggled with interoperability, unclear semantics, and poor dependency handling. With first-class support in CMake, and active development efforts in several other build and packaging systems, CPS represents an important step forward, not just for CMake users, but for the wider ecosystem.
What is the Common Package Specification?
Most software libraries don’t exist as individual files. Many projects consist of multiple libraries, sometimes with accompanying data or other resources. C/C++ libraries in particular almost always include headers. Most projects also ship metadata such as license and/or authorship information. These files are often bundled and distributed together as a “package”. These can be literal files, or imaginary constructs which encompass those files belonging to the package, even if the “package” itself does not have an obvious representation in the file system.
Simply having a package, however, is often inadequate for developers that wish to use a library. Build systems need to be able to locate the relevant components and understand how to use them. Some of this can be accomplished by specifying where and how components of software packages should be installed. Describing how to use packages has a long history with many, competing implementations which we have previously discussed.
CPS is an attempt to bridge the need for more expressive package descriptions (particularly, as required by CMake) with the inherent limitation of expressing those descriptions in the bespoke, turing-complete language of CMake script, in order to create a superior package description format which improves interoperability between heterogeneous build systems. CPS also provides opportunities for integration between build systems and package managers, as well as the possibility for a common language that could enable competing package managers to be aware of packages that are available on the system but supplied by different providers.
CPS describes a system for describing packages (which is notionally transport-independent, but extant implementations are built on JSON). It originated several years ago as an idea that didn’t attract much interest at first, but more recently, a number of parties have been working to make CPS a practical reality. In particular, CMake 3.31 introduced experimental support for exporting to CPS, and CMake 4.0 added experimental support for importing from CPS.
In addition to the obvious interoperability benefits, CPS also attempts to address some other issues of CMake-script package descriptions. One major improvement is that handling of transitive dependencies has been baked in from the start and strives to improve on the problematic find_dependency. CPS transitive dependencies are also included automatically, whereas support for automatic transitive dependency expression in CMake-script exports, first introduced in CMake 3.29, remains experimental, is subject to various limitations, and often requires users to supply additional information. Another issue relates to target names, and in particular, the potential difficulty in identifying which package provides a particular target. CPS addresses this by enforcing qualified target names, where the root “namespace” of the target is exactly the name of the package which provides that target. (This is also why existing CMake projects are strongly encouraged to use their package name as their CMake namespace when generating CMake-script exports, as this will reduce porting friction when transitioning to CPS.)
CPS also generalizes CMake’s historic version compatibility logic. Whereas classic CMake is limited to matching one or two leading version components (“same major”, “same major and minor”), specifying exact matching, or requiring users to supply their own logic, CPS generalizes this with the idea of a “compatibility version”; that is, an arbitrary version which allows packages to express backwards compatibility without needing to adhere strictly to semantic versioning. (While adherence is preferable, this recognizes the reality that many packages in practice are non-adhering.) Acceptance testing for requests specifying a range of acceptable versions is also improved.
Additional discussion on the rationale for CPS’s creation can be found here.
What’s Happening?
As already noted, CPS-related work has been happening for some time. CMake 4.3, however, represents an exciting milestone, because it removes the “experimental” status from CMake’s CPS support. Going forward, CMake intends to support CPS as a first-class mechanism for describing packages. Accordingly, we recommend that projects generate CPS in addition to CMake-script package descriptions, with the intention that the latter is for compatibility with users of older CMake versions, and that the former is to be preferred going forward. This enables non-CMake build systems to take advantage of the additional features available from CMake-generated packages. For projects not built with CMake, CPS enables the creation of feature-rich package descriptions that align with CMake’s expectations without the need to write CMake script.
How do I use CPS?
find_package(Example)
Consuming CPS is easy, as it should Just Work™ when using find_package (for .cps files in standard search locations; otherwise, you may need to set <package>_DIR to the directory containing a package’s .cps file(s), just as you would for package .cmake files). If you are using a build system other than CMake, refer to your build system’s documentation.
For providers of packages built with CMake, adding install(PACKAGE_INFO) in your projects will enable new features and will make your project more accessible to a wider array of build systems. Additional instructions can be found in the documentation or our previous blog.
# Install CMake Language package description
# Keep this for compatibility with older CMake
install(
EXPORT Example-targets
NAMESPACE Example::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/example
)
install(
FILES
Example-config.cmake
Example-config-version.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/example
)
# Install CPS package description
install(
PACKAGE_INFO Example
EXPORT Example-targets
VERSION 1.2.0
)
As always, please use CMake’s issue tracker to report any problems you encounter. Please be aware that there are some limitations which you may encounter, especially for projects whose usage interface relies on CMake generator expressions, as these are not supported by CPS. (However, see next section.)
CMake / CPS Roadmap
Removal of the experimental gate does not mean that there is no work left to be done. CPS itself is still in its “pre-release” phase (version 0.14.1 as of writing), and CMake’s implementation still has a few rough edges. Work remains ongoing on both the specification and on CMake. Here, we try to summarize some of the remaining issues. Note that these are not just “known limitations”; everything listed here represents future intended work that will improve CMake’s capabilities and enhance CPS integration. If you or your organization is interested in sponsoring any of this work, please contact us.
- Generator Expression Support. While there are no immediate plans to support generator expressions as such in CPS, this means that CMake exports that depend on generator expressions cannot be exported as CPS. Some of these limitations, however, are artificial, as a number of “generator expressions” are not actually dynamic (that is, they could be evaluated at configure-time) or vary only on axes that CPS otherwise models (namely, configuration or compile language).
- Version Schema Support. CPS has a notion of “version schema”, which allows packages to specify the manner in which complex version specifiers are to be matched and compared. Some well-known schemas include RPM and PEP440. At present, however, CMake only supports the “simple” schema, and in some cases further restricts this by only supporting a maximum of four version components.
- Requirement Hints. CPS provides mechanisms for packages to specify their version requirements for transitive dependencies, as well as to provide “hints” where such dependencies might be located. (The latter is particularly useful for build-tree exports.) CMake does not yet fully utilize these features.
- File Sets. CPS itself does not currently provide a mechanism for describing file sets, which would be useful for e.g. being able to associate headers with specific components.
- Foreign Providers. Realistically speaking, many packages are going to have transitive dependencies on packages that aren’t (yet) described by CPS. A system for interfacing with these would be tremendously beneficial. Investigation into CPS / pkg-config interop is actively underway.
This is not intended to be a “complete” list, and there are larger areas for work that will require collaboration between CPS and build systems. For example, one of CPS’s original goals was to be able to describe platform compatibility. This has largely been shelved in favor of the status quo “if you can see it, assume it’s compatible” because the originally proposed system (which is technically still part of the specification, but, by informal agreement among developers working on or with CPS, is ignored by existing implementations) is believed to be inadequate; however, no satisfactory solution exists as of writing. Other possible areas of future interest include run-time requirements, a “provides” mechanism to better support instances where multiple packages may provide API-compatible interfaces, and a “conflicts” mechanism to help ensure coherent build environments.
As earlier noted, please use CMake’s issue tracker to report problems with CMake’s implementation of CPS. Issues or discussion of CPS itself are best raised at the CPS 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. We also hold monthly meetings over Zoom; please contact us via one of the aforementioned channels if you are interested in attending.