How to make cmake find a shared library in a subfolder

11,327

I'll deal with your actual problem first and offer additional comments after that. Technically speaking, you are asking CMake to find a library named mycustomlib/mycustomlib, but what you really want to say is you want find mycustomlib and it can be found in a subdirectory called mycustomlib. A couple of alternative ways to call find_library() to achieve this for your second case would be:

find_library(MYCUSTOMLIB mycustomlib PATH_SUFFIXES mycustomlib)
find_library(MYCUSTOMLIB mycustomlib PATHS /usr/local/lib/mycustomlib)

The latter is making more assumptions than it should about where you have the library installed, so I'd favour the first option. The first option assumes CMake would already find libraries in /usr/local/lib, which it seems it is from your question. You can influence where CMake looks for libraries by modifying CMAKE_PREFIX_PATH and CMAKE_LIBRARY_PATH. I'd expect either of the above options to make your second case work.

Now to other observations. You've requested a very old minimum CMake version in the first line of each of your CMakeLists.txt files. You probably want to consider at the very least making this 2.8 (personally, I'd suggest more like 3.2 or later, but it depends on what your project needs to support).

You have used file globbing to obtain your list of sources and headers. This is not robust and should generally be avoided (see a discussion of this here). You will see plenty of example code use method this for simplicity, but it is not recommended for real world projects (the CMake documentation even says not to use it). Explicitly list out your source and header files individually if you want robust builds.

If you are happy to require CMake 2.8.11 or later (and you should be these days), rather than calling include_directories() which makes everything pick up the header search path you specified, you should prefer to attach the search path requirement to the target that needs it. You do this with target_include_directories(). The equivalent of your code above would be:

target_include_directories(${PROJECT_NAME} PUBLIC include)

This gives much better control of your inter-target dependencies as your project grows in size and complexity. For a more in-depth discussion of this topic, see this article and perhaps also this one (disclosure: I wrote both articles).

Are your library and program totally separate source code repositories? Can they be built in the same project? You can build multiple targets in one CMakeLists.txt file. The project name doesn't have to have any relationship to the names of any of the targets (you often see the PROJECT_NAME variable re-used for the target name in simple examples, which is unfortunate since it suggests a relationship between the two, but for all but simple projects this won't be the case). If they are in the same repository, building them together would be a much simpler build since you wouldn't have to install the library for the executable to find it and link to it.

If they must be built in separate projects, then something like the following for the application's project should get you close:

cmake_minimum_required(VERSION 2.8.11)
project(myprogram)

# List your program's sources here explicitly
add_executable(myprogram src/foo.cpp src/bar.cpp)

# Find and link library
find_library(MYCUSTOMLIB mycustomlib PATH_SUFFIXES mycustomlib)
target_link_libraries(myprogram PUBLIC ${MYCUSTOMLIB})

# Find library's headers and add it as a search path.
# Provide the name of one header file you know should
# be present in mycustomlib's include dir.
find_path(MCL_HEADER_PATH mycustomlib.h PATH_SUFFIXES mycustomlib)
target_include_directories(myprogram PUBLIC ${MCL_HEADER_PATH})

For extra points, you could try to confirm that the header path is in the same area as the library by checking the common path prefix, or you could just derive the MCL_HEADER_PATH from the MYCUSTOMLIB path by assuming a directory structure. Both approaches have advantages and drawbacks. If you want to explore the latter, the get_filename_component() command will be your friend.

Hopefully that points you in the right direction.

Share:
11,327

Related videos on Youtube

birgersp
Author by

birgersp

Updated on May 25, 2022

Comments

  • birgersp
    birgersp about 2 years

    I'm trying to learn how to make a shared library. And the following seems to work (please comment if you have some feedback to this method, I basically have no idea what I'm doing).

    In my library project, I've put the header files into a folder named "include", and the source files into "src".

    My library's CMakeLists.txt:

    cmake_minimum_required(VERSION 2.4.0)
    
    project(mycustomlib)
    
    # Find source files
    file(GLOB SOURCES src/*.cpp)
    
    # Include header files
    include_directories(include)
    
    # Create shared library
    add_library(${PROJECT_NAME} SHARED ${SOURCES})
    
    # Install library
    install(TARGETS ${PROJECT_NAME} DESTINATION lib)
    
    # Install library headers
    file(GLOB HEADERS include/*.h)
    install(FILES ${HEADERS} DESTINATION include)
    

    My application's CMakeLists.txt:

    cmake_minimum_required(VERSION 2.4.0)
    
    project(myprogram)
    
    # Find source files
    file(GLOB SOURCES src/*.cpp)
    
    # Create executable
    add_executable(${PROJECT_NAME} ${SOURCES})
    
    # Find and link library
    find_library(MYCUSTOMLIB mycustomlib)
    target_link_libraries(${PROJECT_NAME} ${MYCUSTOMLIB})
    

    And this is working. The problem is that I want to put both the headers and the library into subfolders (specifically: /usr/local/include/mycustomlib/ for the headers, and /usr/local/lib/mycustomlib/ for the library).

    So this is my attempt:

    My library's new CMakeLists.txt:

    cmake_minimum_required(VERSION 2.4.0)
    
    project(mycustomlib)
    
    # Find source files
    file(GLOB SOURCES src/*.cpp)
    
    # Include header files
    include_directories(include)
    
    # Create shared library
    add_library(${PROJECT_NAME} SHARED ${SOURCES})
    
    # Install library
    install(TARGETS ${PROJECT_NAME} DESTINATION lib/${PROJECT_NAME})
    
    # Install library headers
    file(GLOB HEADERS include/*.h)
    install(FILES ${HEADERS} DESTINATION include/${PROJECT_NAME})
    

    My application's new CMakeLists.txt:

    cmake_minimum_required(VERSION 2.4.0)
    
    project(myprogram)
    
    # Find source files
    file(GLOB SOURCES src/*.cpp)
    
    # Create executable
    add_executable(${PROJECT_NAME} ${SOURCES})
    
    # Find and link library
    find_library(MYCUSTOMLIB mycustomlib/mycustomlib)
    target_link_libraries(${PROJECT_NAME} ${MYCUSTOMLIB})
    

    And this is not working. Now, I'm forced to specify the .so file of the library like this:

    find_library(MYCUSTOMLIB mycustomlib/libmycustomlib.so)

    How come?

  • birgersp
    birgersp almost 8 years
    Thank you! How can I include the header subdirectory?
  • Craig Scott
    Craig Scott almost 8 years
    That's what target_include_directories() does. In the above, I've shown how to add the include directory to the header search path when building the ${PROJECT_NAME} target. If the directory you are adding is specified as a relative path, the CMake docs don't actually say what they are relative to for the build (they only mention what they are relative to for an install). You may need to experiment to see what the behaviour is. Try ${CMAKE_CURRENT_SOURCE_DIR}/include instead of just include if you are not getting the behaviour you expect.
  • birgersp
    birgersp almost 8 years
    Okay, I guess that command is meant for my library's CMakeLists.txt. But I still don't understand how to specify the library header subdirectory in my application's CMakeLists.txt.
  • Craig Scott
    Craig Scott almost 8 years
    Answer updated to demonstrate one way to add your library's header directory to the program's header search path.
  • birgersp
    birgersp almost 8 years
    Awesome, thank you! Wish I could give more than 1 upvote.
  • birgersp
    birgersp almost 8 years
    From my understanding, target_include_directories enables a package to "save" the location of the headers it needs, so the application using my library does not have to include this directory for the library to run. Is this correct? Because it seems that if my library needs some headers, I am required to include these headers in my application even though I used target_include_directories in my library...
  • Craig Scott
    Craig Scott almost 8 years
    If the library and program were part of the same build, that would be the case. When they are separate builds, to get that behaviour you would have to modify your library project to create a package file which your program's CMakeLists.txt would pick up by using find_package() instead of find_library(). That's a whole other set of learning for you to get up to speed on though and I think goes beyond the scope of your original question. A good starting point would be the CMake docs on packages.