Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you export a system library using cmake?

Tags:

c

cmake

How can I export the libraries that a cmake library depends on, such that an executable depending on that library does not have to manually depend on the dependencies of that library?

That's a bit of a mouthful, so here's an example:

dummy (application) ----> depends on liba

liba ----> depends on libpng

Compiling dummy generates errors:

-- Found LIBPNG
-- Found LIBA
-- Configuring done
-- Generating done
-- Build files have been written to: /home/doug/projects/dummy/build
Linking C executable dummy
../deps/liba/build/liba.a(a.c.o): In function `a_dummy':
/home/doug/projects/dummy/deps/liba/src/a.c:6: undefined reference to `png_sig_cmp'
collect2: ld returned 1 exit status
make[2]: *** [dummy] Error 1
make[1]: *** [CMakeFiles/dummy.dir/all] Error 2
make: *** [all] Error 2

I can fix that by adding this into CMakeLists.txt for dummy:

TARGET_LINK_LIBRARIES(dummy png)

However, dummy has no knowledge of how liba implements its api. At some point that may change to being libjpg, or something else, which will break the dummy application.

After getting some help from the cmake mailing list I've been directed to this example for exporting things: http://www.cmake.org/Wiki/CMake/Tutorials/How_to_create_a_ProjectConfig.cmake_file

However, following that approach leaves me stuck at this line:

export(TARGETS ${LIBPNG_LIBRARY} FILE "${PROJECT_BINARY_DIR}/ALibraryDepends.cmake")

Clearly I'm missing something here; this 'export' command looks like its designed to export sub-projects to a high level; ie. nested projects inside liba.

However, that is not the problem here.

When configuring liba (or any cmake library) I will always generate a list of dependencies which are not part of that project.

How can I export those so they appear as part of LIBA_LIBRARY when I use find_package() to resolve liba?

Using static libraries is not an option (static library for something that links to opengl? no.)

like image 579
Doug Avatar asked Aug 13 '11 02:08

Doug


People also ask

What is export in CMake?

Export targets or packages for outside projects to use them directly from the current project's build tree, without installation.

How do I add a library to CMake project?

To add a library in CMake, use the add_library() command and specify which source files should make up the library. Rather than placing all of the source files in one directory, we can organize our project with one or more subdirectories. In this case, we will create a subdirectory specifically for our library.

What does Add_subdirectory do in CMake?

Add a subdirectory to the build. Adds a subdirectory to the build. The source_dir specifies the directory in which the source CMakeLists.

What is a build system CMake?

CMake is not a build system itself; it generates another system's build files. It supports directory hierarchies and applications that depend on multiple libraries. It is used in conjunction with native build environments such as Make, Qt Creator, Ninja, Android Studio, Apple's Xcode, and Microsoft Visual Studio.


3 Answers

Given your comment to arrowdodger's answer about the fear of installing something would mess up your system I chose to give a conceptional comment in form of an answer because of its length.

Chaining cmake project works via find_package, which looks for *Config.cmake and *-config.cmake files.

Project A's CMakeLists.txt:

#CMakeLists.txt
project(A)
install(FILES 
  ${CMAKE_CURRENT_SOURCE_DIR}/AConfig.cmake share/A/cmake
)

#AConfig.cmake
message("Yepp, you've found me.")

$ mkdir build
$ cd build
$ cmake -DCMAKE_INSTALL_PREFIX=/tmp/test-install ..
$ make install

Project B's CMakeLists.txt:

project(B)
find_package(A)

Then

$ mkdir build
$ cd build
$ cmake -DCMAKE_INSTALL_PREFIX=/tmp/test-install ..
$ make install

results in

...
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
Yepp, you've found me.

B found A because it installed AConfig.cmake into a location where cmake will find it 'share/A/cmake' AND was given the same value for CMAKE_INSTALL_PREFIX.

Now this is that. Lets think about what you can do in AConfig.cmake: AFAIK everything you want to. But the most common task is to pull information about the targets of A via include(), do some additional find_package invoctions for 3rd party packages (HINT HINT) and create the variables

A_LIBRARIES
A_INCLUDE_DIRS

What you want to include is a file that was created by

install(EXPORT A-targets
   DESTINATION share/A/cmake
)

in A's CMakeLists.txt , where A-targets refers to a global cmake variable that accumulated all target informations when used in

install(TARGETS ...
  EXPORT A-targets
  ...
)

statments. What is created at make install is

/tmp/test-install/share/A/cmake/A-targets.cmake

which then resides alongside AConfig.cmake in the same directory. Please take another look at the wiki page on how to use this file within AConfig.cmake.

Regarding the export() command: This comes handy if your projects have gotten HUGE and it takes a considerable amount of time to install them. To speed things up, you want to use what's in A's build/ directory directly. It's an optimization and also explained in the wiki. It still works via find_package(), see

  • http://cmake.org/cmake/help/cmake-2-8-docs.html#command:export

But I strongly suggest that you go for the usual make install route for now.

like image 158
Maik Beckmann Avatar answered Sep 20 '22 02:09

Maik Beckmann


I found my own solution to this problem using the accepted solution above, which I leave here for others:

In liba/CMakeLists.txt:

# Self
set(A_INCLUDE_DIRS ${A_INCLUDE_DIRS} "${PROJECT_SOURCE_DIR}/include")
set(A_LIBRARIES ${A_LIBRARIES} "${PROJECT_BINARY_DIR}/liba.a")

# Libpng
FIND_PACKAGE(libpng REQUIRED)
set(A_INCLUDE_DIRS ${A_INCLUDE_DIRS} ${LIBPNG_INCLUDE_DIRS})
set(A_LIBRARIES ${A_LIBRARIES} ${LIBPNG_LIBRARIES})

ADD_LIBRARY(a ${SOURCES})

# Includes
INCLUDE_DIRECTORIES(${A_INCLUDE_DIRS})

# Allow other projects to use this
configure_file(AConfig.cmake.in "${PROJECT_BINARY_DIR}/AConfig.cmake")

In liba/AConfig.cmake:

set(A_LIBRARIES @A_LIBRARIES@)
set(A_INCLUDE_DIRS @A_INCLUDE_DIRS@)

In dummy/CMakeLists.txt:

FIND_PACKAGE(A REQUIRED)
INCLUDE_DIRECTORIES(${A_INCLUDE_DIRS})
TARGET_LINK_LIBRARIES(dummy ${A_LIBRARIES})

This yields an AConfig.cmake that reads:

set(A_LIBRARIES /home/doug/projects/dummy/deps/liba/build/liba.a;/usr/lib/libpng.so)
set(A_INCLUDE_DIRS /home/doug/projects/dummy/deps/liba/include;/usr/include)

And a verbose compile that reads:

/usr/bin/gcc  -std=c99 -g   CMakeFiles/dummy.dir/src/main.c.o  -o dummy -rdynamic ../deps/liba/build/liba.a -lpng 

Which is exactly what I was looking for.

like image 40
Doug Avatar answered Sep 19 '22 02:09

Doug


If liba doesn't provide any means to determine it's dependencies, you can't do anything. If liba is library developed by you and you are using CMake to build it, then you should install libaConfig.cmake file with liba itself, which would contain necessary definitions. Then you include libaConfig in dummy's CMakeLists.txt to obtain information about how liba have been built.

You can look how it's done in LLVM project, relevant files have cmake.in extension http://llvm.org/viewvc/llvm-project/llvm/trunk/cmake/modules/

In the end, in dummy project you should use

target_link_libraries( ${LIBA_LIBRARIES} )

include_directories( ${LIBA_INCLUDE_DIR} )

link_directories( ${LIBA_LIBRARY_DIR} )

If that liba is used only by dummy, you can build it from single CMake project. This is more convenient, since you don't need to install liba each time you recompile it and it will be rebuilt and relinked with dummy automatically every time you run make. If you liked this approach, the only thing you should do - define in liba' CMakeLists.txt variables you need with PARENT_SCOPE option (see set() command manual).

Finally, you can use shared libs, .so's don't have such problem.

like image 44
arrowd Avatar answered Sep 22 '22 02:09

arrowd