DDS
How Do I Use dds in a CMake Project?

How Do I Use dds in a CMake Project?

If you have a CMake project and you wish to pull your dependencies via dds, you’re in luck: Such a process is explicitly supported. Here’s the recommended approach:

  1. Download PMM and place and commit the PMM script into your CMake project. 1

  2. In your CMakeLists.txt, include() pmm.cmake.

  3. Call pmm(DDS) and list your dependencies.

Below, we’ll walk through this in more detail.

Note

You don’t even have to have dds downloaded and present on your system to use dds in PMM! Read on…

Using PMM

PMM is the Package Manager Manager for CMake, and is designed to offer greater integration between a CMake build and an external package management tool. PMM supports Conan, vcpkg, and, of course, dds.

See also

Refer to the README.md file in the PMM repo for information on how to use PMM.

Getting PMM

To use PMM, you need to download one only file and commit it to your project: pmm.cmake, the entrypoint for PMM 1. It is not significant where the pmm.cmake script is placed, but it should be noted for inclusion.

pmm.cmake should be committed to the project because it contains version pinning settings for PMM and can be customized on a per-project basis to alter its behavior for a particular project’s needs.

Including PMM

Suppose I have downloaded and committed pmm.cmake into the tools/ subdirectory of my CMake project. To use it in CMake, I first need to include() the script. The simplest way is to simply include() the file

CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(MyApplication VERSION 2.1.3)

include(tools/pmm.cmake)

The include() command should specify the path to pmm.cmake, including the file extension, relative to the directory that contains the CMake script that contains the include() command.

Running PMM

Simply include()-ing PMM won’t do much, because we need to actually invoke it.

PMM’s main CMake command is pmm(). It takes a variety of options and arguments for the package managers it supports, but we’ll only focus on dds for now.

The basic signature of the pmm(DDS) command looks like this:

pmm(DDS [DEP_FILES [filepaths...]]
        [DEPENDS [dependencies...]]
        [TOOLCHAIN file-or-id])

The most straightforward usage is to use only the DEPENDS argument. For example, if we want to import {fmt}:

pmm(DDS DEPENDS "fmt^7.0.3")

When CMake executes the pmm(DDS ...) line above, PMM will download the appropriate dds executable for your platform, generate a dds toolchain based on the CMake environment, and then invoke dds build-deps to build the dependencies that were listed in the pmm() invocation. The results from build-deps are then imported into CMake as IMPORTED targets that can be used by the containing CMake project.

See also

For more in-depth discussion on dds build-deps, refer to Building and Using dds in Another Build System.

Note

The _deps directory and generated CMake imports file will be placed in the CMake build directory, out of the way of the rest of the project.

Note

The version of dds that PMM downloads depends on the version of PMM that is in use.

Using the IMPORTED Targets

Like with dds, CMake wants us to explicitly declare how our build targets use other libraries. After pmm(DDS) executes, there will be IMPORTED targets that can be linked against.

In dds (and in libman), a library is identified by a combination of namespace and name, joined together with a slash / character. This qualified name of a library is decided by the original package author or maintainer, and should be documented. In the case of fmt, the only library is fmt/fmt.

When pmm(DDS) imports a library, it creates a qualified name using a double-colon “::” instead of a slash. As such, our fmt/fmt is imported in CMake as fmt::fmt. We can link against it as we would with any other target:

add_executable(my-application app.cpp)
target_link_libraries(my-application PRIVATE fmt::fmt)

This will allow us to use {fmt} in our CMake project as an external dependency.

In all, this is our final CMakeLists.txt:

CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(MYApplication VERSION 2.1.3)

include(tools/pmm.cmake)
pmm(DDS DEPENDS fmt^7.0.3)

add_executable(my-application app.cpp)
target_link_libraries(my-application PRIVATE fmt::fmt)

Changing Compile Options

dds supports setting compilation options using toolchains. PMM supports specifying a toolchain using the TOOLCHAIN argument:

pmm(DDS DEPENDS fmt^7.0.3 TOOLCHAIN my-toolchain.json5)

Of course, writing a separate toolchain file just for your dependencies can be tedious. For this reason, PMM will write a toolchain file on-the-fly when it executes dds. The generated toolchain is created based on the current CMake settings when pmm() was executed.

To add compile options, simply add_compile_options:

add_compile_options(-fsanitize=address)
pmm(DDS ...)

The above will cause all dds-built dependencies to compile with -fsanitize=address as a command-line option.

The following CMake variables and directory properties are used to generate the dds toolchain:

COMPILE_OPTIONS

Adds additional compiler options. Should be provided by add_compile_options.

COMPILE_DEFINITIONS

Add preprocessor definitions. Should be provided by add_compile_definitions

CXX_STANDARD

Control the cxx_version in the toolchian

CMAKE_MSVC_RUNTIME_LIBRARY

Sets the runtime option. This option has limited support for generator expressions.

CMAKE_C_FLAGS and CMAKE_CXX_FLAGS, and their per-config variants

Set the basic compile flags for the respective file sypes

CXX_COMPILE_LAUNCHER

Allow providing a compiler launcher, e.g. ccache.

Note

Calls to add_compile_options, add_compile_definitions, or other CMake settings should appear before calling pmm(DDS), since the toolchain file is generated and dependencies are built at that point.

add_link_options has no effect on the dds toolchain, as dds does not generate any runtime binaries.

Footnotes

1(1,2)

Do not use file(DOWNLOAD) to “automatically” obtain pmm.cmake. The pmm.cmake script is already built to do this for the rest of PMM. The pmm.cmake script itself is very small and is designed to be copy-pasted and committed into other projects.