5

I'm trying to use FetchContent CMake helper to automate building required dependencies in case they are not installed/available through find_package, but hitting a wall at how to handle exporting the build targets.

My own project e.g. needs TinyXML2 as a dependency:

## TinyXML2
find_package(tinyxml2 QUIET)
if (${tinyxml2_FOUND})
    message(STATUS "tinyxml2 FOUND!(${tinyxml2_LIBRARIES})")
    echo_all_cmake_variable_values()
else()
    message(STATUS "tinyxml2 NOT FOUND, fetching from source!")
    FetchContent_Declare(tinyxml2
      GIT_REPOSITORY     https://github.com/leethomason/TinyXML2
      GIT_TAG            9.0.0
    )
    FetchContent_MakeAvailable(tinyxml2)
    set(tinyxml2_LIBRARIES tinyxml2)
endif()

Which is then used to link the project targets:

# ...
target_link_libraries(${PROJECT_NAME} PUBLIC ${Boost_LIBRARIES}
                                             pthread
                                             ${tinyxml2_LIBRARIES})

And the linked target is then exported:

# ...
export(EXPORT ${PROJECT_NAME}Targets
       FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake
       NAMESPACE ${PROJECT_NAME}::)

The issue is that, when fetching from source, the export step fails:

CMake Error in src/myproject/CMakeLists.txt:
    export called with target "myproject" which requires target "tinyxml2" that
    is not in any export set.

Is there a way to also auto-export tinyxml2 as a required dependency?

joaocandre
  • 1,621
  • 4
  • 25
  • 42
  • 2
    "Is there a way to also auto-export `tinyxml2` as a required dependency?" - No, there is no "auto-export" feature in CMake. You need to export dependent libraries manually. – Tsyvarev Mar 19 '22 at 20:36
  • 1
    Note that tinyxml2 is a modern CMake package and does not export package variables (`tinyxml2_LIBRARIES`). Link to `tinyxml2::tinyxml2` instead. It will work in both FetchContent and `find_package` because the build defines an alias target to make things line up. No need for a variable here at all. – Alex Reinking Mar 20 '22 at 10:20

1 Answers1

3

Is there a way to also auto-export tinyxml2 as a required dependency?

Not as of CMake 3.23 (pre-release).

When you use FetchContent, it's as if you wrote the "third-party" code yourself. CMake has no idea that it's external. This is called "vendoring" code.

Because you aren't going through find_package, you will need to install() "your" target like any other dependency that you will export. It will export into your namespace, too, to avoid conflicts with other copies of tinyxml2 that might be floating around.

I also find this tedious, which is why I shy away from FetchContent for anything that might be installed or exported and use proper source-based package management tools like vcpkg instead. Such tools let you use find_package to locate your dependencies, which is (imo) what you should be doing 100% of the time, anyway.


Also, your code is wrong (see my top level comment). Here's a more correct version (I would just say find_package(tinyxml2 REQUIRED), personally).

## TinyXML2
find_package(tinyxml2 QUIET)
if (NOT tinyxml2_FOUND)
  message(STATUS "tinyxml2 NOT FOUND, fetching from source!")
  FetchContent_Declare(tinyxml2
    GIT_REPOSITORY     https://github.com/leethomason/TinyXML2
    GIT_TAG            9.0.0
  )
  FetchContent_MakeAvailable(tinyxml2)
  install(
    TARGETS tinyxml2 
    EXPORT ${PROJECT_NAME}Targets
    # More arguments as necessary...
  )
endif ()

# ... usage ...
target_link_libraries(${PROJECT_NAME} PUBLIC ${Boost_LIBRARIES}
                                             pthread
                                             tinyxml2::tinyxml2)

This is less typing than going through a variable, too.

Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
  • Fair enough, I'll admit I'm not well versed in CMake, so traditionally I just use variables that are usually set up by the required packages. But one question though, how does one get the required include path in case the dependency is not installed in standard locations, in order to pass it to `target_include_path()` – joaocandre Mar 20 '22 at 19:18
  • Also, how can users be sure that the library target is exported using `tinyxml2` namespace - while it would be strange if it wasn't, afaik any namespace can be set by the dependency developer? – joaocandre Mar 20 '22 at 19:22
  • Regarding the include path... `tinyxml2::tinyxml2` has set the `INTERFACE_INCLUDE_DIRECTORIES` property with the correct path. That will get propagated to linkees (like `${PROJECT_NAME}`) just by virtue of being linked. This technique is the cornerstone of what's called "modern CMake". You shouldn't be using variables when a target is provided. – Alex Reinking Mar 20 '22 at 20:09
  • Regarding namespacing... there are lots of buggy CMake packages. I took great care when I wrote tinyxml2's build to make it usable this way. Still, with FetchContent, pretty much anything goes. Well designed CMake libraries align their build interfaces with their install interfaces so the same target names can be used regardless. – Alex Reinking Mar 20 '22 at 20:11
  • Similarly, there's nothing to stop a find module from setting an unexpectedly named variable. Actually, quite a few find modules will capitalize their package name so you write `find_package(PkgName)` but then get variables named `PKGNAME_VAR`... unexpected names are a problem no matter what. – Alex Reinking Mar 20 '22 at 20:13