[CMake] Difference between PRIVATE and PUBLIC with target_link_libraries

Craig Scott craig.scott at crascit.com
Wed May 11 07:58:34 EDT 2016


Hopefully the explanation that follows helps clarify what PRIVATE, PUBLIC
and INTERFACE mean and do. From my understanding of things, I think there
may have been some subtle inaccuracies in some of the discussions so far,
so hopefully the following is helpful and if I've got something wrong, then
by all means please point out the inaccuracies.


   - When A links in B as *PRIVATE*, it is saying that A uses B in its
   implementation, but B is not used in any part of A's public API. Any code
   that makes calls into A would not need to refer directly to anything from
   B. An example of this could be a networking library A which can be built to
   use one of a number of different SSL libraries internally (which B
   represents). A presents a unified interface for client code which does not
   reference any of the internal SSL data structures or functions. Client code
   would have no idea what SSL implementation (B) is being used by A, nor does
   that client code need to care.
   - When A links in B as *INTERFACE*, it is saying that A does not use B
   in its implementation, but B is used in A's public API. Code that calls
   into A may need to refer to things from B in order to make such calls. One
   example of this is an interface library which simply forwards calls along
   to another library but doesn't actually reference the objects on the way
   through other than by a pointer or reference. Another example is where A is
   defined in CMake as an interface library, meaning it has no actual
   implementation itself, it is effectively just a collection of other
   libraries (I'm probably over-simplifying here, but you get the picture).
   - When A links in B as *PUBLIC*, it is essentially a combination of
   PRIVATE and INTERFACE. It says that A uses B in its implementation and B is
   also used in A's public API.


Consider first what this means for include search paths. If something links
against A, it will also need any include search paths from B if B is in A's
public API. Thus, if A links in B either as PUBLIC or INTERFACE, then any
header search paths defined for target B will also apply to anything that
links to A. Any PRIVATE header search path for B will NOT be carried
through to anything that links only to A. The target_include_directories()
command handles this. The situation with compile flags is analogously
handled with target_compile_definitions() and target_compile_options().

Now consider the situation for the actual libraries involved. If A is a
shared library, then A will have encoded into it a dependency on B. This
information can be inspected with tools like ldd on Linux, otool on Mac and
something like Dependency Walker (a.k.a. depends.exe) on Windows. If other
code links directly to A, then it also will have encoded into it a
dependency on A. It will not, however, have a dependency on B unless A
links in B as PUBLIC or INTERFACE. So far, so good. If, however, A is a
static library, the situation changes. Static libraries do not carry
information about other libraries they depend on. For this reason, when A
links in B as PRIVATE and another target C links in A, CMake will still add
B to the list of libraries to be linked for C because parts of B are needed
by A, but A itself doesn't have that dependency encoded into it. So even
though B is an internal implementation detail of A, C still needs B added
to the linker command, which CMake conveniently handles for you.

If you were paying careful attention, you would have noticed that 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.

As a final note, if you call target_link_libraries() and do not specify any
of PRIVATE, PUBLIC or INTERFACE, you may be tempted to believe that it will
be treated as PUBLIC. The situation is actually more complicated than that
though. It may be treated as PUBLIC or PRIVATE, depending on what other
target_link_library() calls and/or target property manipulations have been
performed. The documentation for target_link_libraries() talks a bit about
this, but you have to go digging into the documentation for the target
properties it mentions to get an understanding of what circumstances lead
to PRIVATE or PUBLIC behaviour.

Hope that helps clarify some things. Sorry if this has gone off on a
tangent from the original enquiry, I'm coming in late to this thread.


On Wed, May 11, 2016 at 8:33 PM, iosif neitzke <
iosif.neitzke+cmake at gmail.com> wrote:

> >> I *think* that these public/private rules behave a bit differently
> >> for static libraries than they do for shared ones.
>
> They do.  Assuming main calls a() and b() defined in A_lib and B_lib
> respectively, for:
> add_library(A_lib STATIC a.c)
> add_library(B_lib STATIC b.c)
> target_link_libraries(A_lib PRIVATE B_lib)
> add_executable(main main.c)
> target_link_libraries(main A_lib)
>
> The PRIVATE in "target_link_libraries(A_lib PRIVATE B_lib)" is
> useless.  It is the same as writing "target_link_libraries(A_lib
> PUBLIC B_lib)", only more confusing to the reader. Static libraries
> always link to their dependencies publically.
>
>
> https://cmake.org/cmake/help/v3.5/command/target_link_libraries.html#libraries-for-a-target-and-or-its-dependents
>
> However, if you change A_lib to be a shared library with
> "add_library(A_lib SHARED a.c)" and left the rest of the code the
> same, you would now get link errors for main not able to find b(),
> because A_lib now does not pass on its dependency on B, it hides it
> from main.   Change the last line to "target_link_libraries(main A_lib
> B_lib)" and main builds again.
> --
>
> Powered by www.kitware.com
>
> Please keep messages on-topic and check the CMake FAQ at:
> http://www.cmake.org/Wiki/CMake_FAQ
>
> Kitware offers various services to support the CMake community. For more
> information on each offering, please visit:
>
> CMake Support: http://cmake.org/cmake/help/support.html
> CMake Consulting: http://cmake.org/cmake/help/consulting.html
> CMake Training Courses: http://cmake.org/cmake/help/training.html
>
> Visit other Kitware open-source projects at
> http://www.kitware.com/opensource/opensource.html
>
> Follow this link to subscribe/unsubscribe:
> http://public.kitware.com/mailman/listinfo/cmake
>



-- 
Craig Scott
Melbourne, Australia
http://crascit.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://public.kitware.com/pipermail/cmake/attachments/20160511/e69cbd1e/attachment-0001.html>


More information about the CMake mailing list