[CMake] Difference between PRIVATE and PUBLIC with target_link_libraries

Craig Scott craig.scott at crascit.com
Sat Dec 28 20:15:10 EST 2019


This one has sat in my inbox for a long time - sorry I'm only getting to it
now!

On Wed, Sep 18, 2019 at 9:21 AM Alan W. Irwin <Alan.W.Irwin1234 at gmail.com>
wrote:

> Hi Craig:
>
> It appears you pretty much made the definitive statement about
> target_link_libraries (TLL) <INTERFACE|PUBLIC|PRIVATE> options at
> <https://cmake.org/pipermail/cmake/2016-May/063400.html>.  However,
> I have some further questions about this key section of your
> statement:
>
> "[...] when A links in B as PRIVATE, the include directories of B
> never propagate to something linking to A, but if A is a static
> library, then the *linking* of B behaves as though the relationship
> was PUBLIC. This PRIVATE-becomes-PUBLIC behaviour for static libraries
> only applies to the *linking*, not to the other dependencies (compiler
> options/flags and include search paths). The upshot of all this is
> that if you select PRIVATE, PUBLIC or INTERFACE based on the
> explanations in the dot points above, then CMake will ensure
> dependencies propagate through to where they are required, regardless
> of whether libraries are static or shared. This does, of course, rely
> on you the developer not missing any dependencies or specifying the
> wrong PRIVATE/PUBLIC/INTERFACE relationship."
>
> The issues I am concerned with are the following:
>
> * The target_include_directories, target_compile_definitions, and
>    target_compile_options (TID, TCD, and TCO) commands all have
>    <INTERFACE|PUBLIC|PRIVATE> options.  I am pretty sure those options
>    must take precedence over the TLL <INTERFACE|PUBLIC|PRIVATE> options,
>    but can you confirm that?
>

No that's not a correct interpretation. For target_include_directories(),
target_compile_definitions() and target_compile_options(), a PRIVATE item
only applies to that target, meaning it only affects how that target is
built. An INTERFACE item would affect how some other target that links to
that target is built. This is true even if that other target does a PRIVATE
link to this target.

The PRIVATE/INTERFACE/PUBLIC specification in target_link_libraries() is
somewhat like a next layer out. Let me try to explain via an example. Let's
say we have three libraries: inner, middle and outer, where outer links to
middle and middle links to inner. The outer library doesn't reference
anything from the inner library directly. We would express it something
like this:

target_compile_definitions(inner PUBLIC SomeSetting=true)   # Used in
discussion below
target_link_libraries(middle XXXX inner)
target_link_libraries(outer PRIVATE middle)


The inner library defines the SomeSetting=true definition as PUBLIC, so
both inner and middle will have this definition applied to them.

   - If XXXX is PRIVATE, the middle library links to inner as PRIVATE, so
   the SomeSetting=true definition remains hidden from the outer library.
   - If XXXX is PUBLIC, the middle library links to inner as PUBLIC, so the
   SomeSetting=true definition is applied transitively to anything that
   links to middle. That means that outer will now have the SomeSetting=true
    definition applied to it as well.

I recommend you play around with variations of the above example and see
the command lines that are generated. It is probably the most effective way
to solidify one's understanding and answer questions about how the
relationships work. For your convenience, here's a complete working example
that you can start with:

cmake_minimum_required(VERSION 3.16)
project(pubpriv)

add_library(inner inner.cpp)
add_library(middle middle.cpp)
add_library(outer outer.cpp)

target_compile_definitions(inner PUBLIC SomeSetting=true)
target_compile_definitions(middle PRIVATE OtherSetting=true)

target_link_libraries(middle PUBLIC inner)
target_link_libraries(outer PRIVATE middle)





>
> * It appears to me that if a CMake-based build system is configured
>    properly there is very little need for the PUBLIC option for TLL
>    because PRIVATE works well for shared libraries and the
>    PRIVATE-becomes-PUBLIC behaviour for static libraries you mentioned
>    above.  Can you confirm this statement is generally true both for
>    Unix and Windows?
>

Continuing with the example I used above, if the public headers of the
middle library refer to anything from the inner library, then
target_link_libraries() should specify PUBLIC, regardless of whether middle
or inner are static or shared libraries. Simon's reply already highlighted
cases involving inlined functions where this is important. The platform is
not relevant here, the same behavior applies to all platforms.




>
> I am concerned with the above issues because the PLplot build system
> currently does the following:
>
> * For shared libraries uses the PRIVATE TLL option for the Unix case
>    and PUBLIC TLL option for the Windows case.
>

I don't understand the rationale for this. Perhaps it is related to Visual
Studio making symbols hidden by default, but Clang and GCC making symbols
visible by default (see my CppCon 2019 talk
<https://crascit.com/2019/10/16/cppcon-2019-deep-cmake-for-library-authors/>
for a discussion of this and the CMake features related to it).



>
> * For static libraries always uses the PUBLIC TLL option.
>

The right choice of PUBLIC, PRIVATE or INTERFACE is independent of whether
the libraries are static or shared.



>
> These decisions were based on my own understanding of transitive
> linking needs for static Unix libraries and shared and static Windows
> libraries many years ago, but now it appears that understanding is out
> of date or else was wrong in the first place.
>
> For example, in the static Linux case there is a nasty leakage of
> compile and link flags between static libraries that I have just
> tripped over when dealing with the D ldc2 compiler which cannot
> understand those flags which are generated for the gcc compiler for a
> C library which our D library depends on.  So to stop that leakage,
> and in light of what you said three years ago, it appears to be a
> no-brainer to use PRIVATE TLL for the PLplot static libraries at least
> in the Unix case.  So assuming that for the PLplot build system I
> follow up and prove that PRIVATE TLL works for both the shared and
> static library cases on Unix, would you also recommend PLplot move to
> PRIVATE TLL for both the shared and static library cases on Windows?
>

For all the target_...() commands, my recommendation is to make things
PRIVATE by default and only use PUBLIC or INTERFACE if the relationship
genuinely requires the thing in question to be applied to whatever links to
that target. The platform is not a factor unless the API is different on
different platforms.


-- 
Craig Scott
Melbourne, Australia
https://crascit.com

Get the hand-book for every CMake user: Professional CMake: A Practical
Guide <https://crascit.com/professional-cmake/>
Consulting services (CMake, C++, build/release processes):
https://crascit.com/services
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://cmake.org/pipermail/cmake/attachments/20191229/05d53c9b/attachment.html>


More information about the CMake mailing list