Jason R
Jason R

Reputation: 11696

How can I group target dependencies in CMake?

I have a pretty complicated make-based project that I'm trying to migrate to modern CMake usage instead. In the Makefile, I have several similar shared library targets that share mostly-identical compile flags. So, it would look something like this:

add_library(lib1 SHARED src1.cpp src2.cpp)
target_compile_options(lib1 PUBLIC -public-flag1 -public-flag2 -unique-flag3 PRIVATE -private-flag)

add_library(lib2 SHARED src3.cpp src4.cpp) 
target_compile_options(lib2 PUBLIC -public-flag -public-flag2 PRIVATE -private-flag)

Is there some way I can abstract out the common dependencies so I don't have to repeat them under each target? It would seem that an Interface Library would be the way to do this, something like:

add_library(common_options INTERFACE)
target_compile_options(common_options 
    PUBLIC -public-flag1 -public-flag2
    PRIVATE -private-flag
)
add_library(lib1 SHARED src1.cpp src2.cpp)
target_compile_options(lib1 PUBLIC -unique-flag3)
target_link_libraries(lib1 common_options)

add_library(lib2 SHARED src3.cpp src4.cpp) 
target_link_libraries(lib1 common_options)

which would be a net win for maintainability in my opinion. However, CMake throws an error at this, complaining that an interface target can only carry INTERFACE options (I can't add the PRIVATE options to it). Is there a different way to do this that would allow me to encapsulate the PUBLIC and PRIVATE flags that are shared among targets?

Upvotes: 1

Views: 493

Answers (1)

thomas_f
thomas_f

Reputation: 1993

You're on the right track. Consider this:

add_library(public  INTERFACE)
add_library(private INTERFACE)
target_compile_options(public  INTERFACE -Werror)
target_compile_options(private INTERFACE -Wall)

target_link_libraries(lib1
    PUBLIC  # <-- public's flags will be applied to lib1 and any targets linking to lib1
        public
    PRIVATE # <-- private's flags will only be applied to lib1
        private
)

In short, you forgot to add scopes in your calls to target_link_libraries.

Update:

So I think the direct answer to my question is that I can't do exactly what I want, which would be to encompass both private and public compile option dependencies into a single entity. Instead, I just have to make a public one and a private one. Not quite as clean as what I'd hoped, but manageable.

Not really, you will always have to specify PUBLIC and PRIVATE scope, but if you are willing to use target_compile_options instead of interface libraries to achieve this, then this would also work:

set(COMMON_FLAGS
    PUBLIC
        -Werror
        -ffast-math
    PRIVATE
        -Wall
)

target_compile_options(lib1 ${COMMON_FLAGS})
target_compile_options(lib2
    ${COMMON_FLAGS}
    PUBLIC # <-- you can still set scoped flags
        -fno-fast-math
)

In my opinion, however, using interface libraries and just deal with the fact that you have to use PUBLIC and PRIVATE is both cleaner and more maintainable in the longer run.

Btw, if you want to compact COMMON_FLAGS to a single line: set(COMMON_FLAGS "PUBLIC;-Werror;-ffast-math;PRIVATE;-Wall")

Upvotes: 1

Related Questions