Is target specific stuff transitive to dependencies?

We have some libraries that are to be consumed by multiple applications, but with different compile definitions or flags. For example, when compiled with app-A, we need -DappA. When compiled with app-B, we need -DappB. In tranditional Makefile, we do it with target specific variables:

app-A: CFAGS+=-DappA
app-A: commonLib

app-B: CFLAGS+=-DappB
app-B: commonLib

With CMake, I expected target specific commands like target_compile_definitions to be transitive to dependencies, so I did this:

add_subdirectory(commonLib) #where there is add_library(commonLib …)
add_executable(app-A …)
target_compile_definitions(app-A PRIVATE appA)
target_link_libraries(app-A commonLib)

But I didn’t see -DappA was used when compiling the commonLib. Did I do anything wrong? Or it is not transitive to dependencies at all? I have quite a few “common” libraries, and quite a few apps, so it’ll be nice if it is transitive.

Unless I missed something transitivity goes the other way around, if A depends on B then public INTERFACE on B will propagate to A.

In your description app-A depends on commonLib and target_compile_definitions(app-A PRIVATE appA)
applies to target app-A so source any file in app-A will be compiled with -DappA.

But why/how would this propagate to the commonLib compilation?
If this was propagated this way and commonLib was used by both app-A and app-B you’ll get both -DappA and -DappB ?

From my point of view there is 2 options:

  • either commonLib is a header only lib and the current scheme should work because -DappA will be defined when compiling app-A
  • or commonLib needs to be compiled twice, once for appA and once for appB.
1 Like

But why/how would this propagate to the commonLib compilation?

It’s because there is some conditional code in commonLib like:

#ifdef appA

#endif
#ifdef appB

#endif

I know this is ugly. It could be organized better, but it is a project with long history, I cannot avoid it for now.

So CMake does not have a feature similar to GNU make’s target-specific variables: GNU make?

So CMake does not have a feature similar to GNU make’s target-specific variables

Yes CMake does just as make. The doc make says:

This feature allows you to define different values for the same variable, based on the target that make is currently building.

target_compile_definitions(app-A PRIVATE appA)

does that as well.

It’s because there is some conditional code in commonLib like

Like I said this will work when app-A source file in #including part of commonLib.

The following example works for me:
target_specific_define.zip (1.5 KB)

may be you can provide us with an example of your that does not work as expected?

Hi, @erk, I understand it works if commonLib is a header only lib. Unfortunately, it is not my case. my commonLib has *.c files, in which there is:

#ifdef appA

#endif
#ifdef appB

#endif

Thanks
-Oscar

Then commonLib shall be compiled twice, once for AppA and once for AppB.
Did you mean that make is doing that automagically ?

Do you have a complete example (using make) that does that?

I’ll compose an example tomorrow.
What make can do is to pass -DappA down to commonLib. For example:

app-A: CFLAGS+=-DappA

app-A: commonLib

In your commonLib’s makefile:

commonLib: x.c y.c
CC …

You’ll see, when x.c is compiled, -DappA is in the compiling command line. Thus, I can choose different feature to compile from the top app’s makefile.

Not sure if this makes sense to you. I’ll get you an example tomorrow.

It makes perfectly sense (I did use makefile a lot some time ago), but I bet that in that case commonLib is not really a “library” since it shall be recompiled for different app. What you describe is a way to bring in a new commonLib-A into A by specifying a set of source file that need to be recompiled for A.

So you need to create your interface library, with appropriate associated source files in it:

add_library(common INTERFACE)
target_include_directories(common INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_sources(common INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/common.cpp)

then:

add_executable(A-app a.cpp)
target_link_libraries(A-app common)
target_compile_definitions(A-app PRIVATE withA)

add_executable(B-app b.cpp)
target_link_libraries(B-app common)
target_compile_definitions(B-app PRIVATE withB)

and you’ll get what you want (I guess).
see:

$ ninja -v 
[1/6 (4) - 0.155] /usr/bin/c++  -DwithB -I../  -MD -MT CMakeFiles/B-app.dir/common.cpp.o -MF CMakeFiles/B-app.dir/common.cpp.o.d -o CMakeFiles/B-app.dir/common.cpp.o -c ../common.cpp
[2/6 (3) - 0.220] /usr/bin/c++  -DwithA -I../  -MD -MT CMakeFiles/A-app.dir/common.cpp.o -MF CMakeFiles/A-app.dir/common.cpp.o.d -o CMakeFiles/A-app.dir/common.cpp.o -c ../common.cpp
[3/6 (2) - 0.240] /usr/bin/c++  -DwithB -I../  -MD -MT CMakeFiles/B-app.dir/b.cpp.o -MF CMakeFiles/B-app.dir/b.cpp.o.d -o CMakeFiles/B-app.dir/b.cpp.o -c ../b.cpp
[4/6 (2) - 0.285] /usr/bin/c++  -DwithA -I../  -MD -MT CMakeFiles/A-app.dir/a.cpp.o -MF CMakeFiles/A-app.dir/a.cpp.o.d -o CMakeFiles/A-app.dir/a.cpp.o -c ../a.cpp
[5/6 (2) - 0.309] : && /usr/bin/c++     CMakeFiles/B-app.dir/b.cpp.o CMakeFiles/B-app.dir/common.cpp.o  -o B-app   && :
[6/6 (1) - 0.357] : && /usr/bin/c++     CMakeFiles/A-app.dir/a.cpp.o CMakeFiles/A-app.dir/common.cpp.o  -o A-app   && :

Here comes the archive of this:
target_specific_define.zip (1.7 KB)

2 Likes

Even with GNU Make, I don’t understand how you get the expected behavior. The GNU Make documentation specifies, for target specific variables values:

Be aware that a given prerequisite will only be built once per invocation of make, at most. If the same file is a prerequisite of multiple targets, and each of those targets has a different value for the same target-specific variable, then the first target to be built will cause that prerequisite to be built and the prerequisite will inherit the target-specific value from the first target. It will ignore the target-specific values from any other targets. 

So CommonLib will not be compiled twice!

Hi, @erk
Your example demonstrated exactly what I want. Thank yo so much.

1 Like

@marc.chevrier, let me ellaborate a little bit more. We do compile twice. We have separate makefiles for appA and appB, which include commonLib’s makefile “commonLib.mk”. With GNU make’s target-specific variable feature, the CFLAGS used to compile commonLib can be tuned by its dependant (appA or appB here)