When to use PRIVATE vs INTERFACE compile options/definitions on a target in CMake?
15:38 18 Aug 2022

I have a C++ CMake project with 3 targets:

  1. An Interface Library target MyProjectInterfaces (defines some interfaces in hpp files)
  2. Library MyProjectFoo (implements 1/2 of the interfaces in MyProjectInterfaces)
  3. Library MyProjectBar (implements the other 1/2 of interfaces in MyProjectInterfaces)

Downstreams projects that consume my library may want to implement the MyProjectInterfaces interfaces themselves, or use the Foo implementations, or the Bar implementations, or both.

I want to add some compile features/options/definitions to all of the targets in my project while minimizing code duplication. I know of two ways to do this.

METHOD 1: The easiest way is to add them to MyProjectInterfaces with INTERFACE scope:

    set(DEBUG_COMPILE_OPTIONS
        -Wall -Werror
    )
    set(RELEASE_COMPILE_OPTIONS
    
    )
            
    target_compile_features(MyProjectInterfaces INTERFACE
        cxx_std_17
    )
    
    target_compile_definitions(MyProjectInterfaces INTERFACE
        SDK_VERSION_MAJOR=${CMAKE_PROJECT_VERSION_MAJOR}
        SDK_VERSION_MINOR=${CMAKE_PROJECT_VERSION_MINOR}
        SDK_VERSION_PATCH=${CMAKE_PROJECT_VERSION_PATCH}
        )
    
    target_compile_options(MyProjectInterfaces INTERFACE
        $<$:${DEBUG_COMPILE_OPTIONS}>
        $<$:${RELEASE_COMPILE_OPTIONS}>
    )

METHOD 2: Alternatively I can change the scope to PRIVATE, wrap the target_compile_XXX calls in a macro/function, and call it for each target.

    macro(set_options myTarget)
    target_compile_features(${myTarget} PRIVATE
        cxx_std_17
    )
    
    target_compile_definitions(${myTarget} PRIVATE
        SDK_VERSION_MAJOR=${CMAKE_PROJECT_VERSION_MAJOR}
        SDK_VERSION_MINOR=${CMAKE_PROJECT_VERSION_MINOR}
        SDK_VERSION_PATCH=${CMAKE_PROJECT_VERSION_PATCH}
        )
    
    target_compile_options(${myTarget} PRIVATE
        $<$:${DEBUG_COMPILE_OPTIONS}>
        $<$:${RELEASE_COMPILE_OPTIONS}>
    )
    endmacro()
    
    # This probably won't work or be necessary because its an interface library target?
    # set_options(MyProjectInterface) 
    
    # Set compile options on actual compiled libraries
    set_options(MyProjectFoo)
    set_options(MyProjectBar)

As far as I'm aware both methods propagate all the definitions/options I want to each target in my project. However, METHOD 1 will also propagate these options/definitions to consuming projects. Is that desirable?

There's lots of documentation on the internet on how to pass the PRIVATE/PUBLIC/INTERFACE arguments into target_compile_XXX(...), but I have been able to find very little on how/when to choose between them.

c++ cmake