Professional CMake:

A Practical Guide

Learn to use CMake effectively with practical advice from a CMake co-maintainer. You can also have the author work directly with your team!

Enhanced source file handling with target_sources()

Updated December 2018: Parts of this article have been reworked to account for improvements made with the CMake 3.13.0 release. Key updates are noted within the article.

In all but trivial CMake projects, it is common to find targets built from a large number of source files. These files may be distributed across various subdirectories, which may themselves be nested multiple levels deep. In such projects, traditional approaches usually either list all source files at the top-most level or build up the list of source files in a variable and pass that to add_library(), add_executable(), etc. With CMake 3.1, a new command target_sources() was introduced which provides the missing piece among the various target_... commands. While the CMake documentation succintly describes what target_sources() does, it fails to highlight just how useful the new command is and why it promotes better CMake projects:

  • It can lead to cleaner and more concise CMakeLists.txt project files.
  • Dependency information can be specified closer to where the actual dependencies exist in the directory hierarchy.
  • Source files gain the ability to become part of a target’s interface.
  • Source files can be added to third party project targets without having to modify the third party project files.

Life Before target_sources()

Typically, developers first learn CMake in a very simple manner, defining a target by listing the source files directly in the add_executable() or add_library() command itself. Eg:

add_executable(myApp src1.cpp src2.cpp)

When the number of source files grows large and they get distributed over a number of subdirectories, possibly nested to multiple levels, this quickly becomes unwieldly. It also results in having to repeat the directory structure, which reduces the benefit of structuring source files into directories in the first place.

The logical improvement many developers then make is to build up the list of source files in a variable as each subdirectory is pulled in via include(). Then after all the subdirectories have been included, add_executable() or add_library() is called, but this time passing just the variable instead of an explicit list of files. The top level CMakeLists.txt file then looks something like this:

# The name of the included file could be anything,
# it doesn't have to be called CMakeLists.txt
include(foo/CMakeLists.txt)
include(bar/CMakeLists.txt)

add_executable(myApp ${myApp_SOURCES})

with the subdirectory files structured something like this:

list(APPEND myApp_SOURCES
    ${CMAKE_CURRENT_LIST_DIR}/foo.cpp
    ${CMAKE_CURRENT_LIST_DIR}/foo_p.cpp
)

This allows each subdirectory to define just the sources it provides and to delegate any further nested subdirectories with another include(). It also keeps the top level CMakeLists.txt file quite small and the CMakeLists.txt in each subdirectory also tends to be reasonably uncomplicated and focussed just on the things in that directory.

As an alternative to explicitly building up a list of source files in a variable, some developers instead choose to let CMake find the source files and generate the contents of that variable automatically with the file(GLOB_RECURSE ...) command. While at first this may seem very attractive for its simplicity, this technique has a number of drawbacks and is actively discouraged by the CMake documentation. Nevertheless, it is often used by developers new to CMake until they experience first hand the problems the technique introduces.

target_sources(): All The Advantages Without The Drawbacks

The down sides of the above approach may not be immediately obvious. One drawback is that the source files are built up in a variable and then that variable is passed to an add_library() or add_executable() call at the top level CMakeLists.txt file. Variables used in this way are not a particularly robust way to record the source file list. For example, if many targets are being built up throughout a directory heirarchy, then the number and naming of variables can get out of hand. This can be somewhat addressed by sticking to some kind of naming convention associated with the target a variable is used with, but this relies on all developers knowing and adhering to that convention. Furthermore, if a developer inadvertently tries to re-use a variable name in a deeper directory level, sources could end up being added to unintended targets. CMake won’t typically issue any sort of diagnostic message, since it won’t know you didn’t intend to do that.

But perhaps the bigger drawback of using variables is that it precludes having the CMake target defined when descending into the subdirectories. This in turn means that subdirectories cannot directly call target_compile_definitions(), target_compile_options(), target_include_directories() or target_link_libraries() either. In order to associate compiler flags, options, header search paths and other libraries to be linked, more variables have to be defined to pass this information back up to the top level. Extra care has to be taken to properly handle quoting when doing this too. If you want to take full advantage of the PUBLIC, PRIVATE and INTERFACE capabilities of these various target_... commands too, the number of variables required just for one target alone already starts getting a bit silly. You can imagine the explosion of variables if many targets are defined throughout your project’s directory structure!

NOTE: The advice and examples below have been updated from the original article to account for new capabilities added in CMake 3.13.0

An example should help to highlight why target_sources() leads to much more robust and concise CMakeLists.txt files. Let’s say we have a project with two subdirectories foo and bar. The top level CMakeLists.txt file can be as simple as this:

cmake_minimum_required(VERSION 3.13)
project(MyProj)

add_library(myLib "")

add_subdirectory(foo)
add_subdirectory(bar)

The empty quotes in the add_library() call are necessary because that command requires a list of source files, even if that list is empty. If there were sources to be added from this top level directory, they could be listed there.

Let’s now assume the source files in the foo subdirectory use features from some external third party library called barry. Don’t worry about where barry comes from or what it represents, that’s not important for this discussion. It might come from some other part of the project, or be found somewhere on the system (the example leaves out details for the latter case, but again, this isn’t the main focus of the article). Now, because the source files of myLib use things from barry, myLib has to link against the barry library. For the sake of discussion, let’s also assume we need to define a compiler symbol called USE_BARRY both when building myLib and also for any code that includes headers from myLib. Assuming a minimum CMake version of 3.13.0 or later, the CMakeLists.txt file within the foo subdirectory might then look something like this:

target_sources(myLib
    PRIVATE
        foo.cpp
        foo_p.cpp
        foo_p.h
    PUBLIC
        foo.h  # poor PUBLIC example, see discussion below for why
)

find_library(BARRY_LIB barry)
# This call requires CMake 3.13 or later, see next section
target_link_libraries(myLib PUBLIC ${BARRY_LIB})

target_compile_definitions(myLib PUBLIC USE_BARRY)
target_include_directories(myLib PUBLIC ${CMAKE_CURRENT_LIST_DIR})

In the above example, note that .h header files were specified as sources too, not just the .cpp implementation files. Headers listed as sources don’t get compiled directly on their own, but the effect of adding them is for the benefit of IDE generators like Visual Studio, Xcode, Qt Creator, etc. This causes those headers to be listed in the project’s file list within the IDE, even if no source file refers to it via #include. This can make those headers easier to find during development and potentially aid things like refactoring functionality, etc. UPDATE: With CMake 3.23 or later, file sets are a better way to achieve this.

The PRIVATE and PUBLIC keywords specify where those corresponding sources should be used. PRIVATE simply means those sources should only be added to myLib, whereas PUBLIC means those sources should be added to myLib and to any target that links to myLib. An INTERFACE keyword can be used for sources that should not be added to myLib but should be added to anything that links to myLib. In practice, sources will almost always be PRIVATE, since they shouldn’t generally be added to anything that links against the target. Header-only interface libraries are one exception because sources can only be added as INTERFACE for interface libraries. Do not confuse the PRIVATE , PUBLIC and INTERFACE keywords with whether a header is part of the public API for the library or not, the keywords are specifically for controlling which target(s) the sources are added to in this case. There are also some less common cases where some files (eg resources, images, data files) may need to be compiled directly into targets linking against a library for them to be found at runtime. Listing such sources as PUBLIC or INTERFACE can help address such situations. Note though that installing a non-private source can be somewhat problematic (we will return to this topic further below).

The same meaning for PRIVATE, PUBLIC and INTERFACE apply to the other target_...() commands too, although it is more common to see things as non-private. The above example shows how easy it is to specify that myLib and any target that links to it also needs to link to the barry library. Similarly, with just that one target_compile_definitions() call, both myLib and anything linking against it will have the USE_BARRY symbol defined. Lastly, the target_include_directories() command adds the foo subdirectory to the header search path for both myLib and anything linking to it. Therefore, any other source file in another directory that needs to #include the foo.h header will also be able to find it.

To illustrate just how powerful these target_...() commands are, let’s consider what the CMakeLists.txt file for the bar subdirectory might look like. In this case, let’s just assume bar needs to add a few sources files and that some of bar‘s sources or headers will include foo.h.

target_sources(myLib
    PRIVATE
        bar.cpp
        bar.h
        gumby.cpp
        gumby.h
)

Note the complete absence of anything other than simply listing the source files. All the work was done in the foo directory already, so there’s nothing left for us to do here. This highlights one of the biggest advantages of using target_sources(), namely that dependencies can be listed right where they are most relevant and all other directories don’t need to care. This localisation of dependency details leads to much more robust and more concise CMakeLists.txt files throughout a project. Without target_sources(), we would not be able to use target_compile_definitions(), target_compile_options(), target_include_directories() or target_link_libraries() in this way because the CMake target myLib would not be defined when we descend into each subdirectory.

Supporting CMake 3.12 And Earlier

The above comments about using CMake 3.13.0 or later relate to restrictions that were removed in that release. Prior to 3.13.0, target_link_libraries() could only be called on a target that was created in the same directory scope. This means that in the example for the foo subdirectory, the target_link_libraries(myLib ...) call would cause an error with CMake 3.12 or earlier because the myLb target was created in the parent scope. None of the other target_...() commands have ever had this restriction, only target_link_libraries(). We will address this point shortly.

Another change in CMake 3.13.0 relates to how target_sources() interprets relative paths to source files. In CMake 3.12 or earlier, relative paths were treated as being relative to the target to which sources were being added. This was unintuitive, so CMake 3.13.0 changed the default behavior to treating relative paths as being relative to the current source directory instead. If the project sets 3.13.0 as its minimum CMake version requirement, it automatically gets the new behavior by default.

For projects that need to support CMake 3.12 or earlier, they can use absolute paths to source files to avoid the change in behavior and to avoid any policy warnings. For example:

target_sources(myLib
    PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}/foo.cpp
        ${CMAKE_CURRENT_LIST_DIR}/foo_p.cpp
        ${CMAKE_CURRENT_LIST_DIR}/foo_p.h
    PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}/foo.h
)

This is less convenient and less readable, so it may be helpful to define a helper function to give something close to the new behavior, but which also works for earlier CMake versions. CMake has a strong requirement on preserving backward compatibility, so the change in behavior of how relative paths are treated is guarded by a policy, CMP0076. We can take advantage of that in the helper function:

# NOTE: This helper function assumes no generator expressions are used
#       for the source files
function(target_sources_local target)
  if(POLICY CMP0076)
    # New behavior is available, so just forward to it by ensuring
    # that we have the policy set to request the new behavior, but
    # don't change the policy setting for the calling scope
    cmake_policy(PUSH)
    cmake_policy(SET CMP0076 NEW)
    target_sources(${target} ${ARGN})
    cmake_policy(POP)
    return()
  endif()

  # Must be using CMake 3.12 or earlier, so simulate the new behavior
  unset(_srcList)
  get_target_property(_targetSourceDir ${target} SOURCE_DIR)

  foreach(src ${ARGN})
    if(NOT src STREQUAL "PRIVATE" AND
       NOT src STREQUAL "PUBLIC" AND
       NOT src STREQUAL "INTERFACE" AND
       NOT IS_ABSOLUTE "${src}")
      # Relative path to source, prepend relative to where target was defined
      file(RELATIVE_PATH src "${_targetSourceDir}" "${CMAKE_CURRENT_LIST_DIR}/${src}")
    endif()
    list(APPEND _srcList ${src})
  endforeach()
  target_sources(${target} ${_srcList})
endfunction()

Now we can call the above helper function just like the builtin command and get the CMake 3.13 behavior even with CMake 3.12 or earlier:

target_sources_local(myLib
    PRIVATE
        foo.cpp
        foo_p.cpp
        foo_p.h
    PUBLIC
        foo.h
)

When using CMake 3.12 or earlier, working around the restriction with target_link_libraries() is harder. The choices are either to move the target_link_libraries() call up to the same directory in which the target is defined, or avoid creating new directory scopes by using include() instead of add_subdirectory(). The second of these options would only require changing the top level CMakeLists.txt file to something like the following (which is the original method suggested by this article before it was updated for CMake 3.13.0):

cmake_minimum_required(VERSION 3.1)
project(MyProj)

add_library(myLib "")

# Using include() avoids creating a new directory scope, so these directories
# are able to call target_link_libraries(myLib ...)
include(foo/CMakeLists.txt)
include(bar/CMakeLists.txt)

The target_sources_local() helper function is defined in such a way that it will work inside foo or any other directory, regardless of whether we use add_subdirectory() or include().

Most developers find add_subdirectory() more natural and it does tend to give more intuitive handling of variables like CMAKE_CURRENT_SOURCE_DIR, CMAKE_CURRENT_BINARY_DIR, etc. Therefore, if the subdirectories don’t need to call target_link_libraries(), prefer to use the add_subdirectory() approach rather than the above include() workaround.

Complications For Installing

Specifying PRIVATE sources is relatively easy and has few difficulties. The location of each source file is clear and only needs to be considered within the build. Any PUBLIC or INTERFACE sources give rise to additional factors which must be considered. Within the build, paths to non-private sources are handled just like private ones, but when the project is installed, non-private paths have to make sense not only in the project’s own build, but also in the build of anything consuming the installed project. The paths used for the project’s own build will be specific to the machine on which that build is performed and to the directory in which it is built. Once installed, those directories will likely not be accessible, so the paths would need to be replaced with something else that makes sense for the installed set of files. This is made possible using the BUILD_INTERFACE and INSTALL_INTERFACE generator expressions. The following example shows how to provide different paths for the same file in the two different contexts:

target_sources(myHeaderOnly
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/algo.h>
        $<INSTALL_INTERFACE:include/algo.h>
)
install(FILES algo.h DESTINATION include)

While the above solves the build/install differences, it also has the drawback of requiring us to spell out every BUILD_INTERFACE path as absolute once again. We can no longer use our target_sources_local() helper function that we defined earlier. This is the cost of supporting the installation of non-private source files. Projects should weigh up the loss of convenience and added complexity against the expected benefits to be gained by making sources non-private.

Key Points

Taking a step back, what target_sources() is doing for us is to remove the need for variables by allowing us to use CMake targets directly. This gives us these key advantages:

  • It allows the CMake target to be defined early, which in turn enables calling the various other target_... commands in any of the subdirectories pulled in with add_subdirectory() after the target is defined.
  • Subdirectories cannot inadvertently add sources to the wrong targets.
  • Dependency information can be fully and robustly defined at the point where those dependencies are introduced. The PRIVATE, PUBLIC and INTERFACE keywords give precise control over the nature of those dependencies and they also promote better integration with IDE environments able to take advantage of this information.

The following points should also be kept in mind:

  • Non-private sources require much more verbose and less convenient syntax, so consider whether the gains from making them non-private are worth it.
  • If you need to support CMake 3.12 or older, you will need to either pull up any target_link_libraries() calls to the same directory as the target they operate on, or else use include() rather than add_subdirectory() to avoid introducing a new directory scope. Prefer the former where possible, since it is likely to be more intuitive for developers.

The target_sources() command also has one unique advantage over and above anything that a variable-based approach possesses. It allows additional sources to be added to targets regardless of where they were defined (except for imported targets). This is especially useful when code from an external project is being incorporated into a build with add_subdirectory() or include() (see an earlier article showing how to incorporate GoogleTest directly into your main build with these commands). This can be used to add headers, images, etc. from the external project without affecting the way the target is built. For the really adventurous, you could even potentially use this technique to add your own implementation for a weak symbol such that your implementation overrides the one that the external project’s target would normally use. This may prove useful during testing or to provide a more efficient implementation of a specific function, etc.


Have a CMake maintainer work on your project

Get the book for more CMake content

58 thoughts on “Enhanced source file handling with target_sources()”

  1. Hi! Are you using some kind of new version of cmake? I found that I can’t use target_*() set of commands in subdirs. I’m getting errors:

    CMake Error at targets/cc32xx/CMakeLists.txt:89 (target_link_libraries):
    Attempt to add link library “cc32xx_nonos” to target “demo_client” which is
    not built in this directory.

    cc32xx_nonos is a imported library in cc32xx subdir.
    demo_client is an application inside top-level CMakeList

    Reply
    • Indeed you are correct, I thought I had checked that. I’ve modified the article to highlight how target_link_libraries() is treated differently by CMake. The short version is that you need to use include() instead of add_subdirectory() if you want to be able to use target_link_libraries() in a subdirectory for a target defined in a parent directory. Thanks for reporting the problem.

      Reply
  2. Using include() instead of add_subdirectory() is slightly less convenient since you have to use full paths to sources anywhere you reference them, even when adding sources to a target defined in the same directory, but that’s the simplest workaround if you need to use target_link_libraries() to add a library to a target defined in a different directory.

    I don’t get this. If you use add_library() in the subdirectory’s CMakeLists.txt, you can then use that library inside parent’s CMakeLists.txt with target_link_libraries(). Why wound that be a problem? I’m using cmake 3.5.1

    Reply
    • Let’s say you have a subdirectory sub and you add it from its parent using add_subdirectory(sub). Inside sub, you call add_library(foo ...). From the parent of sub, you cannot then call target_link_libraries(foo ...), you can only call that within the sub directory. If, however, you added sub using include(sub/CMakeLists.txt), then you can call target_link_libraries(foo ...) from the parent because sub and the parent are in the same directory scope as far as CMake is concerned. Calling include() does not introduce a new directory scope, whereas add_subdirectory() does.

      Reply
      • But…I currently using cmake like what I said, and that is why I think that is not a problem.

        Root CMakeLists.txt:

        # ...
        set(EXTRA_LIBS ${EXTRA_LIBS} c01_hello)
        set(EXTRA_INCLUDE ${EXTRA_INCLUDE} chapter01)
        include_directories("${PROJECT_BINARY_DIR}")
        include_directories("${EXTRA_INCLUDE}")
        add_subdirectory(chapter01)

        target_link_libraries(<some executable here> ${EXTRA_LIBS})

        ...

        Sub CMakeLists.txt in chapter01:

        add_library(c01_hello SHARED HelloWorld.c)

        Or do I did this with some consequences?

        Reply
        • Using your latest example, what I’m trying to highlight is that <some executable here> cannot be co1_hello. You cannot define co1_hello in one directory scope and then call target_link_libraries(co1_hello ...) in a different scope.

          Reply
          • Why would you call target_link_libraries(co1_hello …) in a different scope?
            That is a bad practice. It goes against modularity. You should aime to have your co1_hello entirely defined in the chapter01 CMakeLists.txt.
            Note that if you are worried that you may want to link co1_hello with something imported in the parent, you do not have worries there either, it will work.
            Example. I have a HPC engineering code I work on. It relies on the Eigen3 library and most sub-libraries that compose the main software need to include the Eigen3 directory.

            The main CMakeLists.txt has:

            […]
            find_package(Eigen3 3.3 REQUIRED NO_MODULES)
            […]
            add_subdirectory(SomeLibrary)
            […]
            add_executable(main_exec Main.cpp)
            […]

            target_link_library(main_exec some_lib […])

            And in SomeLibrary, CMakeLists.txt has:

            add_library(some_lib)
            target_sources(some_lib
            PRIVATE some_source1.cpp some_source2.cpp
            PUBLIC some_header.h)
            target_link_library(some_lib Eigen3::Eigen)

            ====================================
            Everything works fine and is modularized nicely.

          • “Why would you call target_link_libraries(co1_hello …) in a different scope?
            That is a bad practice. It goes against modularity. You should aime to have your co1_hello entirely defined in the chapter01 CMakeLists.txt.”

            This was just an example for illustration. For a real world example, consider a library or executable that has many source files and where some functionality is optional and/or depends on the availability of some external toolkit. Code related to such an optional feature can be put in its own sub directory and conditionally included in the library or executable. That sub directory can hold all the logic related to that feature, including any external libraries that need to be linked in. This is good modularisation since it localised the logic instead of polluting the main CMakeLists.txt file.

            It isn’t always possible or desirable to split out such an optional feature to its own library (eg to ensure aspects of the implementation are not revealed by exported symbol names or similar concerns). Thus, being able to incorporate it directly into the main library or executable could be a requirement. Being able to still isolate everything related to that feature in its own sub directory can help keep things organised and easy to manage. I work on projects which do exactly this and without the capabilities target_sources() provides, it would be much harder and messier.

  3. Thanks, fascinating.

    How are the headers for library BARRY found? I suppose lib BARRY is installed properly and the headers installed in a well-known (to cmake) location?

    I am trying to use a library that is not installed, just in a separate project. I need to do more reading and can probably figure it out.

    Reply
    • You need to distinguish between the build tree and an installed set of files. In a build tree, assuming barry is built just like the rest of the project, then the barry target should define any PUBLIC include directories, compiler flags, etc. that consumers would need. This should take care of ensuring barry‘s headers can be found. Things are a little less clear-cut for an installed case because it’s really up to you where you install things, and then it’s up to whatever wants to use those installed things how they bring them into their own build. If you’ve used CMake’s install(EXPORT) functionality to provide support for other projects to use your installed package via find_package(), then that’s ideal and will behave very similarly to the build tree case. I’ve skipped over a lot of detail in that brief description, but that’s at least touching on a few of the important considerations.

      It also seems like your situation is somewhere in between. The library you want to use is not part of your main project but is also not installed somewhere. I guess you’ve just got its build tree? Maybe consider looking into the export() command from within the other library’s project as a way to make its build tree available to your main project (just one choice, there would be various other ways, each with their own advantages and disadvantages).

      Reply
  4. Thanks for the clear explanation. However, how can you do so and keep the same hierarchy in an IDE and on your filesystem? All the ways I have found on Google to keep the hierarchy in an IDE rely at some point on a variable listing all source files that your method avoid so neatly.

    Reply
    • I think different IDEs group files differently by default. If you are using CMake 3.8 or later though, the source_group(TREE) command probably does what you want. You may be able to get the list of sources from the SOURCES target property but you’d need to get that list after you’ve added all the sources to the target.

      Reply
      • Thanks! I am somewhat new to CMake and didn’t know about properties; I’m indeed able to recover all the source files from a target using get_property(v TARGET my_target PROPERTY SOURCES).

        Reply
  5. This is useful. Now I am using target_sources in myLib, with a PUBLIC hello.h file. My other app testApp links to myLib. When cmake parses add_executable(testApp, “”), it reports error: “Cannot find source file hello.h”. So how to correctly understand and use the PUBLIC files in target_sources?

    Reply
    • target_sources() doesn’t automatically convert files to absolute. If you list a file as a PUBLIC or INTERFACE, you should use an absolute path to avoid precisely the problem you’ve encountered. Note, however, that listing files as PUBLIC or INTERFACE should not be done if you intend to install/export the target, since the path when installed will be different.

      Reply
      • I want to point out that, while your second sentence works for “included targets”, the third sentence is plain wrong. PUBLIC and INTERFACE can be used together with install/export but care needs to be taken. In particular, you want to utilize the generator expressions to properly reference the path in the right “phase”:

        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>$<INSTALL_INTERFACE:include/mylib>/relative/path/to/hello.h

        this properly resolves the path in both cases, noting that the file should be installed properly. My own experimentation shows that the export will assume the include directory.

        Reply
        • Thanks for highlighting the inaccuracy. I’ve just done a fairly significant update to the article to account for changes in CMake 3.13, so while I was at it I also added a new section specifically discussing the issues around installation of non-private sources. Hopefully it makes this area a bit clearer.

          Reply
    • I’ve already stated how you can fix this particular case, but I want to point out that it’s generally more preferred to place the include files in a separate directory, and reference them through target_include_directories. This has the benefit of simplifying the header install by simply saying “install(DIRECTORY include …)”. Doing all that will ensure your library can be installed and referenced externally no matter where it comes from.

      Now, I’m assuming you might be using an IDE which benefits from listing the header files among the sources. If that’s the case, you might want to look at source_group to list your headers in an IDE friendly way.

      Reply
  6. Thank you very much for the amazing post! Why do you use CMAKE_CURRENT_SOURCE_DIR for the bar catalog but not CMAKE_CURRENT_LIST_DIR as for the foo directory?

    Reply
    • Thanks, yes, the CMake 3.13 release removes the remaining minor inconveniences for target_sources() and really makes the approach discussed here that much better. Once the 3.13 reaches official status, I intend to update the article to account for these improvements.

      Reply
      • Now that CMake 3.13 is officially out, I’ve updated the article to account for the changes in behavior. Not only can target_link_libraries() operate on targets defined elsewhere, the target_sources() command also handles relative paths to sources in a more intuitive manner.

        Reply
  7. Is there any advantage in specifying the header files in the target_sources command? To me it looks easiers to specify the header files in PUBLIC_HEADER of the set_target_properties command. This way I don’t need to use BUILD_INTERFACE and INSTALL_INTERFACE generator expressions and an additional install statement. I’ve made an example project here: cmake_library_example. The master branch uses the PUBLIC_HEADER approach and the target_sources_properties uses the target_sources approach.

    Reply
    • Thanks for the comments. I can see that using headers as the example for how PRIVATE, PUBLIC and INTERFACE behave for target_sources() may have created some confusion. I’ve modified the article to clarify why sources will generally only be PRIVATE except perhaps for header-only interface libraries. The small discussion about headers that are part of the library’s public API potentially being listed as PUBLIC was misleading and I’ve removed it. For some IDE tools, if you don’t list a header as a source file, it won’t show up in the IDE (this is really the main reason for listing headers as sources at all).

      Regarding the use of the PUBLIC_HEADER target property, it is only suitable if all the headers you list for a given target should be installed to the same directory. Once you need to have the target’s headers install to different directories, you have to use install(FILES).

      Reply
      • I have also just reconfirmed that for targets that are built as Apple frameworks, simply listing headers in the target’s PUBLIC_HEADER property is not sufficient on its own. You also have to list that header as a source of the target before the header will be copied into the framework.

        Reply
  8. Hello, Craig! I’m starting to use CMake and I can’t find how to install the header files set as PUBLIC in target_sources. Have I to make a separate list of public headers to install? Is it possible to extract the target_sources PUBLIC headers and install those?

    Now I have something like this:

    Here installs the .dll/.so

    install (TARGETS Endavant
    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    )

    And here i want to install/copy all the header files listed in target_sources as PUBLIC but how?

    #install(FILES ${MY_HEADER_FILES} DESTINATION include)

    Thank you!

    Reply
    • First up, you most likely don’t want to make any of the files given to target_sources() PUBLIC. It is much more likely they should be PRIVATE. The PRIVATE, PUBLIC and INTERFACE keywords are about whether those particular files should be part of that target and/or part of a target that links to it. You almost never want to force targets that link to it to have to incorporate a source file like this, so PUBLIC and INTERFACE will be rarely used with target_sources().

      There’s a couple of ways to install headers. One way is to add the header(s) to either the PUBLIC_HEADER or PRIVATE_HEADER target property. Then when the target is installed, specify PUBLIC_HEADER, PRIVATE_HEADER and probably INCLUDES DESTINATION sections in the install(TARGETS) command. For example:

      set_target_properties(Endavant PROPERTIES
          PUBLIC_HEADER "${MY_PUBLIC_HEADER_FILES}"    # Note the quotes
          PRIVATE_HEADER "${MY_PRIVATE_HEADER_FILES}"  # Note the quotes
      )
      
      install(TARGETS Endavant
          RUNTIME DESTINATION bin
          LIBRARY DESTINATION lib
          ARCHIVE DESTINATION lib
          PUBLIC_HEADER DESTINATION include
          PRIVATE_HEADER DESTINATION include
          INCLUDES DESTINATION include
      )
      

      The above is likely to be the better method if the library is going to be a framework on Apple platforms, since you’d have to use PUBLIC_HEADER and PRIVATE_HEADER to get the headers into the framework. If you need to install headers to different paths rather than all to one location though, the above won’t work. For that, you’d have to install the headers using install(FILES). The following example shows how you might install different sets of headers into different subdirectories:

      install(FILES ${SUBPROJA_HEADERS} DESTINATION include/subprojA)
      install(FILES ${SUBPROJB_HEADERS} DESTINATION include/subprojB)
      

      For either method, you need to populate the relevant ..._HEADERS variables yourself.

      Reply
  9. I didn’t really get why foo.h is a # poor PUBLIC example, see discussion below for why
    Can you please elaborate?

    Reply
    • Making foo.h PUBLIC is really an abuse of what target_sources() is meant to do. The foo.h file belongs to the myLib target, not to anything that links against it. In general, headers shouldn’t need to be added to a target as sources at all because they are not themselves directly compiled (although some special cases like AUTOMOC handling might require it), but we often see them added as sources so that they show up in IDE tools under the target they are added to. In this respect, making foo.h a PRIVATE source of myLib would be okay, since it would make it show up in an IDE as one of myLib‘s source files. By making it PUBLIC, it is also trying to force it to become part of anything that links to myLib, which isn’t reflecting the real situation. Other targets may use foo.h, but they certainly don’t own it.

      The situation for header-only interface libraries is a bit special. For those, they don’t have any compiled objects of their own. Instead, they are acting as a container for the headers that are then consumed by other targets. Since interface libraries don’t allow you to add a source as anything other than INTERFACE, you can’t make them PRIVATE (or PUBLIC). For this special situation, since there’s not really any other convenient way to get headers to show up in IDEs, it is fairly common to add them as INTERFACE sources of an interface library. I’m personally on the fence about that, I can see both sides of the debate on whether that’s a good idea or not.

      Reply
  10. Can we access somewhere the full code? I have another question. Doesn’t bar CMakeLists.txt file also need to include a target_include_directories(myLib PUBLIC ${CMAKE_CURRENT_LIST_DIR})

    I am thinking that this is a bit repetitive if you have to include it to each folder. Is there a way to automatically add this to the end of the file?

    Reply
    • Can we access somewhere the full code?

      Not sure what you mean by the “full code”. All relevant code is here in the article. There is no other code relating to that already presented.

      I have another question. Doesn’t bar CMakeLists.txt file also need to include a target_include_directories(myLib PUBLIC ${CMAKE_CURRENT_LIST_DIR})

      Only if headers in the bar directory are intended to be used by anything outside of the bar directory.

      I am thinking that this is a bit repetitive if you have to include it to each folder. Is there a way to automatically add this to the end of the file?

      Well, if you have headers distributed over a lot of directories and you want those headers to be made available to things outside of those directories, then structurally you have to do this if you want other code to simply be able to #include "myheader.h". In such cases, it may suggest that the wrong strategy is being used though. Some projects only add a common base point to the header search path and then expect code to include the path relative to that base point when they do their #include. For example, if headers are in src/subA and src/subB, then src would be added as a header search path and code would need to do #include "subA/someheader.h" and #include "subB/anotherheader.h".

      Another strategy is to collect all public headers in their own dedicated include directory of the source tree. Then you only add that include directory to the header search path. An advantage of this is that it is very clear which headers are intended to be public and the header search path for the build will follow closely how it will look for when everything is installed. A downside is that you distribute closely related things across (potentially widely separated) different parts of the source directory hierarchy, which can make it more difficult to work with in day-to-day development. In the past, some tooling also expected headers and their related implementation files to be in the same directory to find related files, but I’m not sure if this is still so much the case.

      Reply
      • First of all thank you very much for your article, it really puts some things in place and also thanks for taking the time to reply.
        However something doesn’t make sense to me. If bar is not intended to be used by anything outside of it, then what is the point of adding the source files to the target?

        Reply
        • If bar is not intended to be used by anything outside of it, then what is the point of adding the source files to the target?

          For the developer working on the project so that the header shows up in their IDE. Like I’ve said in another comment though, today’s IDEs might already be automatically finding headers in most cases even without this.

          Reply
  11. I tested adding the header files in target_sources for the Eclipse IDE and it didn’t seem to have any effect. Whether I explicitly added the header files (both those included by source files and others that are not included) or I didn’t, the result was that Eclipse created a referenced folder named Source directory which held all the source files regardless (both sources and headers). What is your reference for this rule?

    Reply
    • The behaviour varies from one IDE to another. It is up to them how they implement displaying source files. My comments above are about maximising the chances that any given IDE will show the source file, but it is by no means guaranteed. In the past, some IDEs would only show files that were explicitly added to the source file lists (sorry, I don’t have specific IDEs I’m willing to name, it’s too long ago for me to recall where I last saw this happen). Some IDEs have improved over time and now automatically add headers for any associated implementation file, but note that people may stay with older versions of IDEs for quite a few years (for various reasons).

      Reply
  12. Thank you for this great article Craig.

    One observation: I’m able to use add_library(mylib) without specifying any source files (not even an empty list). Looking at the CMake documentation, version 3.11 seems to have lifted the requirement for a list of sources. Since you are using 3.13, it should be possible for you to skip the (empty) list of source files too?

    Reply
  13. Yes you are correct. I still explicitly put "" in calls to add_library() and add_executable() mostly because people may copy and paste the code and use it in their own projects with a minimum CMake version requirement earlier than 3.11. When this article was first written, it also used a minimum CMake version earlier than 3.11, so it was a requirement for the original text too.

    Reply
  14. Thank you very much for the great article.

    What would be the correct way of encapsulating things in the case of the following scenario:

    The project has 3 components (targets): myLib as above, demoExample executable showing how to use foo.h, unitTestExecutable with tons of unit tests. I can’t figure out how to make demoExample see only PUBLIC include files and unitTestExecutable having access also to PRIVATE section, to test also foo_p.h. Thank you!

    Reply
  15. It sounds like you may be confusing what PUBLIC means for sources. Based on your description, none of those three targets should need to share sources. The demoExample executable should just have whatever sources are needed to build it. All sources in myLib would be added as PRIVATE to that library. Similarly, unitTestExecutable should only have its own sources and none from myLib. The two executables link to myLib, but that doesn’t mean they should have any of myLib‘s sources added to them. The fact that unitTestExecutable needs to include the foo_p.h header doesn’t mean that foo_p.h should be added to unitTestExecutable as a source file.

    It sounds more like you want to work out how to structure your directories such that public headers are in one location and private headers are in a different location. This would allow you to only add the directory of the public headers to the demoExample header search path and add the directories of both the public and private headers to the unitTestExecutable header search path.

    Reply
  16. Thank you for your reply. Indeed, I just want to restructure public and private headers in such a way, that unit tests executable have access to all headers, while demo executable sees only public part. I have created demo repository: https://github.com/gpospelov/cmake-sandbox.
    There I just add myLib private directories manually to unit test executable target. Thanks again.

    Reply
  17. Thanks for taking the time to write this and leaving comments open.

    As best I can tell, though, the new behavior doesn’t encompass set_property(SOURCE) until sometime in version 3.18 with the addition of TARGET_DIRECTORY. That makes target_sources() far more useful as I can now both add files and control their properties in the same subdirectory where those files exist. I found that new capability by accident. If you have a few minutes, could you update this article again with the new information?

    As an aside, I’ve resisted learning cmake for years. I’ve recently started work on a 450k line simulation whose makefiles had accreted over thirty years and decided to port it to cmake as a way of getting some insight into the build process. This article was immensely helpful in getting that done.

    Reply
  18. Hi Craig:

    Great info, so much so that I bought your book, which is also a huge help.

    I’m migrating some embedded c++ code from Eclipse to CLion, and suddenly found myself needing a CMake crash course.

    I was confused for a bit since I was thinking the only way to use the target_sources() paradigm was with a library, but now see I can just do the same thing with an add_executable(name “”), and it all works great.

    Thank you so much.

    Reply
  19. Absolute HORRIFIC article!
    “find_library(BARRY_LIB barry)”
    Where is this stray ‘barry’ coming from?
    Don’t mix stray elements into a tutorial! EXPLAIN IN THE GREATEST DETAIL ! STE-BY-STEP!

    Reply
    • The paragraph immediately preceding the example where find_library(BARRY_LIB barry) is mentioned explains the context for this:

      Let’s now assume the source files in the foo subdirectory use features from some external third party library called barry. This requires myLib to link against the barry library…

      The barry library is not the focus of the article, which is why it only mentions it as “some external third party library” so as not to distract from the flow. Where it comes from isn’t important for the topic being discussed. That said, feel free to suggest alternative wording for that preceding paragraph which addresses your concerns but doesn’t detract from the article’s focus.

      Reply
      • I was thinking about the BARRY_LIB as well and was a bit confused, why the dependency on the BARRY_LIB is in foo CMakeLists.txt and not in the top level CMakeLists.txt file. I guess the target here is to say “We need to link the BARRY_LIB into myLib, because foo requires it”, right? So you are putting some sort of semantics into the CMakeLists?

        Just a stupid case, when bar would also rely on BARRY_LIB, this would become a irrelevant and most probably it would be best to add the “target_link_libraries( )” into the main CMakeLists, potentially with a comment, which module needs that library, right?

        Reply
        • One of the things the article points out is that you can define things as locally as possible. For the example discussed, the only part of myLib that needs barry are the sources under the foo subdirectory. All of the myLib settings that relate to barry are therefore added within the foo subdirectory. If we extended the example such that foo contained code that was optional, meaning the inclusion of the subdirectory might be conditional based on a CMake cache variable like MYLIB_ENABLE_FOO, then we would only be adding the dependency on barry if it was actually needed (i.e. only if foo was added as part of myLib).

          But if other subdirectories like bar also used barry, then it would be appropriate to move the target_link_libraries() and target_compile_definitions() call up to the top level CMakeLists.txt file where the add_library(myLib) is made. Again, the principle is you define things as locally as possible. When multiple subdirectories need barry, then the calls about barry should be made from the directory that is above all subdirectories that rely on barry (i.e. the top level CMakeLists.txt in this case).

          Reply
  20. The example in “Complications For Installing” seem to be garbled in HTML because of the generator expression syntax is left un-quoted. Looks silly in my browser at least.

    Reply
    • Thanks Johan. I’ve tracked this down to a bug in one of the WordPress plugins used by the site. The bug results in the generator expressions being treated as HTML tags by the browser instead of as preformatted code that should be displayed as is.

      Reply
  21. Hello Craig,

    Excellent article! Thanks so much for the time dedicated to it.

    I would like to ask you… I cannot find any use case in which to add a source file as a PUBLIC, so that the cpp file is added to the consumer target.

    In which situations is that suppose to be useful?

    Thanks!

    Reply
    • I haven’t encountered a scenario where PUBLIC would be reasonable (but see below). The only reason PUBLIC was originally included was for consistency with the other target_...() commands.

      CMake 3.23 added support for file sets. These are specified using a different form of the target_sources() command. For this new form, PUBLIC certainly does make sense.

      Reply
  22. Hi Craig

    Excellent and really helpful article – thanks.

    I have been trying to divide a larger project into subfolders and followed your instructions, but I have come up with a problem that I am sure is trivial but I am not sure how to fix it.
    My project has “foo.cpp” in the root folder “./” and includes “bar.h” from a subfolder “./bar”
    Everything seems to be fine, but the include file path from the root folder seems not to have been inherited in the subfolder

    Super cut-down example to illustrate what I mean:

    ./foo.cpp:
    #include “pico/sdlib.h”
    #include “bar/bar.h”

    ./bar/bar.cpp:
    #include “pico/sdlib.h” — not found?
    #include “bar.h”

    I know I’m missing something, can’t put my finger on what? any suggestions?

    Many thanks
    – Graeme

    Reply
    • Most likely you are missing a target_include_directories(foo PUBLIC ...) call in your root folder, or you have used PRIVATE where you need to use PUBLIC. You’d have to provide more details of your project to be able to comment further, but the CMake forums would be a better place to ask this sort of question.

      Reply
  23. Many thanks; for the benefit of anyone else, I was missing
    target_link_libraries(foo LINK_PUBLIC pico_stdlib)
    in the foo folder.
    Cheers

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.