Cannot find path to transitive library during linking (find_package()/target_link_libraries())

I am having trouble finding a grandchild library in a transitive link scenario that we have. Actually, I’m not even sure what the correct terminology for a lot of this is, so I may be overly detailed in the explanation.

Here goes…

I am using:

  • cmake version 3.16.4
  • on a windows 10 machine
  • compiling with Visual Studio 2017

I have a scenario where:

  • LIB_A stands alone
  • LIB-B publicly links with LIB-A
  • EXE-D links with LIB-B
        EXE-D --> LIB-B --> LIB-A

Since LIB-B links with LIB-A, that means EXE-D will need access to both LIB-B and LIB-A.

LIB-A, and LIB-B are all exported libraries, as per the example/explanation under CMake’s online help for Importing and Exporting Guide

I used the PUBLIC scope when linking the child library so that they would be used transitively by downstream linking. (i.e. makes both LIB-A & B available to EXE-D)


LIB-A and LIB-B compile fine. They’re pretty simple. However, compiling EXE-D fails with the message

LINK : fatal error LNK1181: cannot open input file 'Lib_A_64.lib' [C:\TEST\
CMAKE-TRANSITIVE-COMPILE\EXE-D\Build\build64\VS2017\Exe_D_64.vcxproj]

I tried some debugging on my own and printed values from the EXE-D CmakeLists.txt file and I found the following:

Exe_D_64-LINK_LIBRARIES:   Lib_B_64


Lib_B_64-IMPORTED:   TRUE
Lib_B_64-IMPORTED_CONFIGURATIONS:   DEBUG;RELEASE;RELWITHDEBINFO
Lib_B_64-IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG:   CXX
Lib_B_64-IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE:   CXX
Lib_B_64-IMPORTED_LINK_INTERFACE_LANGUAGES_RELWITHDEBINFO:   CXX
Lib_B_64-IMPORTED_LOCATION_DEBUG:   C:/TEMP/CMAKE-TEST/LIB-B/Win-VS2017/lib/Lib_B_64D.lib
Lib_B_64-IMPORTED_LOCATION_RELEASE:   C:/TEMP/CMAKE-TEST/LIB-B/Win-VS2017/lib/Lib_B_64R.lib
Lib_B_64-IMPORTED_LOCATION_RELWITHDEBINFO:   C:/TEMP/CMAKE-TEST/LIB-B/Win-VS2017/lib/Lib_B_64RD.lib
Lib_B_64-INTERFACE_LINK_LIBRARIES:   Lib_A_64

I noticed that the error is that it cannot find Lib_A_64.lib and Lib_B_64 has Lib_A_64 as it’s INTERFACE_LINK_LIBRARIES. It feels like I only have half the solution. It has the Lib_A_64 imported library name, but isn’t expanding to it’s location for the downstream linkage.

I also looked at the Visual Studio IDE project parameters for linker input and it pretty much says the same thing.

C:\TEMP\CMAKE-TEST\LIB-B\Win-VS2017\lib\Lib_B_64R.lib
Lib_A_64.lib
kernel32.lib
user32.lib
gdi32.lib
winspool.lib
shell32.lib
ole32.lib
oleaut32.lib
uuid.lib
comdlg32.lib
advapi32.lib

How do I get transitive linking so that it shares the path of the grandchild library? What are the correct words for describiing this challenge?

Is there a Lib_A_64 imported target as well? If not, finding Lib_B_64 should find whatever package provides that target. It will then be seen as a target and its IMPORTED_LOCATION will then be used.

Hello @ben.boeckel ,

Yes. Lib_A_64 is an imported target too.

When creating Lib_A_64 and Lib_B_64 they use the example/explanation under CMake’s online help for Importing and Exporting Guide.

This creates the following CMake files that have the imported library information

# using git-bash on windows to find the files
$ find LIB-A -ipath "*/cmake*"
LIB-A/Win-VS2017/cmake
LIB-A/Win-VS2017/cmake/Lib_A_64Config.cmake
LIB-A/Win-VS2017/cmake/Lib_A_64Targets-debug.cmake
LIB-A/Win-VS2017/cmake/Lib_A_64Targets-release.cmake
LIB-A/Win-VS2017/cmake/Lib_A_64Targets-relwithdebinfo.cmake
LIB-A/Win-VS2017/cmake/Lib_A_64Targets.cmake

When creating LIB-B the logic is:

list(APPEND CMAKE_PREFIX_PATH "C:/TEMP/CMAKE-TEST/LIB-A/Win-VS2017")
find_package(Lib_A_64 REQUIRED)
target_link_libraries(${TARGET_NAME_LIBRARY} PUBLIC Lib_A_64)

Notice that LIB-A is added as PUBLIC scope so that it will transitively link.

This creates the following CMake files that have the imported library information

$ find LIB-B -ipath "*/cmake*"
LIB-B/Win-VS2017/cmake
LIB-B/Win-VS2017/cmake/Lib_B_64Config.cmake
LIB-B/Win-VS2017/cmake/Lib_B_64Targets-debug.cmake
LIB-B/Win-VS2017/cmake/Lib_B_64Targets-release.cmake
LIB-B/Win-VS2017/cmake/Lib_B_64Targets-relwithdebinfo.cmake
LIB-B/Win-VS2017/cmake/Lib_B_64Targets.cmake

Finally incorporating LIB-B into EXE-D is similar, only the HINT path and package names are different

list(APPEND CMAKE_PREFIX_PATH "C:/TEMP/CMAKE-TEST/LIB-B/Win-VS2017")
find_package(Lib_B_64 REQUIRED)
target_link_libraries(${TARGET_NAME_LIBRARY} PRIVATE Lib_B_64)

It should also be noted that these are entirely orthogonal CMakeLists.txt files for the libraries. They are not part of some larger project inclueded with add_subdiretory().

In this setup Lib_B_64Config.cmake should do a find_package(Lib_A_64) to get the targets A is providing in its scope. You can do something like this:

if (helpers_wanted) # TODO: make your own condition here
  set(Lib_B_64_CMAKE_PREFIX_PATH_save "${CMAKE_PREFIX_PATH}")
  list(APPEND CMAKE_PREFIX_PATH
    "${where_Lib_B_64_found_Lib_A}") # TODO: this should be configured in
endif ()

include(CMakeFindDependencyMacro)
find_dependency(Lib_A_64)
if (NOT Lib_A_64_FOUND)
  set(Lib_B_64_FOUND 0)
  set(Lib_B_64_NOT_FOUND_MESSAGE "Lib_A_64 not found: ${Lib_A_64_NOT_FOUND_MESSAGE}")
endif ()

if (helpers_wanted) # TODO: make your own condition here
  set(CMAKE_PREFIX_PATH "${Lib_B_64_CMAKE_PREFIX_PATH_save}")
  unset(Lib_B_64_CMAKE_PREFIX_PATH_save)
endif ()

Thank you for the suggestion @ben.boeckel

I was just starting to toy with an idea similar to that, but was really nervous because so many other experiments had failed.

If I understand the overall logic flow:


  1. Lib_B_64Config.cmake is included/pulled into scope when the caller uses find_package(Lib_B_64...)

  2. The first chunk of code saves a backup of the current CMAKE_PREFIX_PATH.

    It also updates the prefix path so that it can find Lib_A_64.

  3. The second chunk of code looks for Lib_A_64 and I’m guessing part of that is getting the path to the appropriate library files.

    If it cannot find Lib_A_64 then it sets appropriate error messages, etc.

  4. The final chucnk then restores the CMAKE_PREFIX_PATH


Is my understanding correct?

Is this really the easiest way to do this? I can see the logic on how this would work, and I don’t mean to second guess you. However, it feels like a lot of hoops and loops to get my own libraries to work transitiviely.

I’ve noticed it works without problem if it’s a system library such as pthread or dl or some other general library. Why does this break down when using my own deployed library?

Does it have something to do with the fact that in my case I’m adding an imported library name instead of an actual library?

Thanks again! I appreciate the help!

System libraries can be assumed to exist and can just be referred to by name. They’re “special” in that way (generally, they’re always there and in the compiler’s default search paths).

Other libraries might live in other places (e.g., I regularly install things to ~/misc/root/$proj), installed via module systems (e.g., module load mpi/mpich-x86_64), or generally live in “odd” locations. This is why you need to “find” them again at install time: where it was during the build might not be the same during the transitive-use time. Think of finding a Python then being used via a venv: the paths are not going to be the same, but are still viable, so re-finding Python at use time allows you to be flexible in this way.

And yes, this is generally the easiest way to do it. Restoring CMAKE_PREFIX_PATH is just a courtesy; you could just append to it, but without documentation, someone might silently start depending on that behavior. It’s easier to just not expose it so that if you ever drop Lib_A_64, you don’t get issue reports about “well, it worked before”[1].

[1]Sure, the imported targets already exist, but using targets directly that you don’t find yourself is messy anyways; it’s like relying on <vector> to come in through your external dependency’s include files for you.

1 Like

Thank you.

I appreciate the detailed follow up.

Cheers!