Installing headers the modern way, regurgitated and revisited

Some weeks ago, under Installing headers the modern way, I posed a question regarding the relationship (or lack thereof) between target_sources(mylib PUBLIC ...) and install(TARGETS mylib PUBLIC_HEADER ...). This is a part of CMake’s current design/evolution that just doesn’t seem especially cohesive.

Well, I recently undertook writing/generating my first CMake package config file. And while I’m at it, hey, let’s make this thing relocatable, too.

Oh, boy.

So, let’s say we have:

target_sources(
  mylib
  PUBLIC
    myfirstpublicheader.h
    mysecondpublicheader.h
)

Well, that just won’t do. To satisfy relocatability requirements, the $<INSTALL_INTERFACE:> generator expression must be used. For each header.

target_sources(
  mylib
  PUBLIC
    $<INSTALL_INTERFACE:myfirstpublicheader.h>
    $<INSTALL_INTERFACE:mysecondpublicheader.h>
)

Um, ew. But okay.

Oh, wait… that little bit of code I used to copy the INTERFACE_SOURCES property to the PUBLIC_HEADER property?

get_target_property(MYLIB_PUBLIC_HEADERS mylib INTERFACE_SOURCES)
set_target_properties(
  mylib
  PROPERTIES
    PUBLIC_HEADER "${MYLIB_PUBLIC_HEADERS}"
)

Well, after decorating things with $<INSTALL_INTERFACE:>, my headers aren’t getting installed anymore.

Hm. Okay. Well, it seems that has a perfectly trivial solution: just use the $<BUILD_INTERFACE:> generator expression:

target_sources(
  mylib
  PUBLIC
    $<INSTALL_INTERFACE:myfirstpublicheader.h>
    $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/myfirstpublicheader.h>
    $<INSTALL_INTERFACE:mysecondpublicheader.h>
    $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/mysecondpublicheader.h>
)

And now my headers install! Yay!

Oh, dear. What have I done? See, I don’t have just a couple of public headers like this contrived example code; I have tens of them, like many, many other moderately-sized C++ projects. And this thing has just exploded.

I am hoping that someone will tell me that I’ve missed a trick here. But failing that, I respectfully suggest that something has gone off the rails with this design.

1 Like

My 2 cents sharing my experiences/test (with the help of Sarcasm)
disclaimer: tests was performed during October 2018 and I didn’t check if CMake/QtCreator change their behaviour
disclaimer 2: I don’t think it is a necro posting since it can be seen as a follow up of the author investigation but done few years before ^^;

TLDR: Don’t use “PUBLIC” in target source it will pollute your IDE project layout (for good reason)

The main problem with this solution is myfirstpublicheader.h and mysecondpublicheader.h will now be part of any dependent target sources.

e.g.
Suppose I have an App depending on A and B libraries.
B/CMakeLists.txt:

...
target_sources(B
  PRIVATE
    "src/B.cc"
  PUBLIC
    $<BUILD_INTERFACE:"${CMAKE_CURRENT_SOURCE_DIR}/include/b/B.h">
    $<INSTALL_INTERFACE:"include/b/B.h">)
...
set_target_properties(B PROPERTIES
  PUBLIC_HEADER "include/b/B.h")

But for A I’ve used:
A/CMakeLists.txt:

target_sources(A
  PRIVATE
    "include/a/A.h"
    "src/A.cc"
  )
set_target_properties(A PROPERTIES
  PUBLIC_HEADER "include/a/A.h")

and finally
App/CMakelists.txt:

target_link_libraries(App PRIVATE A B)

note: You can found the full project here: GitHub - Mizux/target_sources: Test target_source(tgt PUBLIC ...) and PUBLIC_HEADER

so here B is like your proposal/investigation unfortunately this will add the source file b/B.h as a source of App, fortunately it is a header so it will be ignored by the compiler but it is not what you want.
Also in ide (QtCreator you’ll see B.h duplicate everywhere in the project layout see: target_sources/qtcreator.png at master · Mizux/target_sources · GitHub)

I guess, PUBLIC source is only needed when you need to build several time the same .cc BUT with options/definition provided by the dependent target itself…
e.g. you have AppFoo executable which need B.cc compiled with define FOO, while you have AppBar executable which need B.cc to be compiled with define BAR in this case you want to inject the B.cc source in each executable source list so it will be compiled with the correct define but it’s a very “niche” scenario IMHO…

so it will be better to use:

set(MYLIB_HDRS
myfirstpublicheader.h
mysecondpublicheader.h
)

target_sources(mylib PRIVATE
 myprivatesrc.cc
 ${MYLIB_HRS}
)
set_property(TARGET mylib
 PROPERTY PUBLIC_HEADER ${MYLIB_HDRS})

note: here we need to use set_property(TARGET) since it allow multiple values [key, values…] (aka list of headers file) while set_target_property() while being more “modern” only support pairs [key, value]
note: I don’t know/test with PRIVATE and BUILD_INTERFACE/INSTALL_INTERFACE and use your get_target_property() trick instead.

For digging deeper, I really enjoy you to read the issue #1 open by Sarcasm (github discussion was not a thing at this time) and all the links provided…