简体   繁体   中英

Need help understanding linking with cmake

Need some help understanding libraries and linking with cmake. Sorry for the wall of text.

I create two static libraries, foo and bar , bar depends on foo . bar is just an extension of foo . I then install bar somewhere for another project to use. When I got to compile this other project, I run into linker issues. The issues are undefined references to functions. Functions that are defined in foo but simply not used in bar

I had a basic understanding that libraries were a collection of objects/symbols, so I had assumed that by linking foo to bar , bar would also have those objects/symbols, but that is not the case, so I am wondering if I am missing something or if I am doing something wrong.

This question was helpful and a little bit similar: CMake: how create a single shared library from all static libraries of subprojects?

Like the link suggested, I tried adding the -Wl,--whole-archive flag like so

set_target_properties(bar PROPERTIES LINK_FLAGS "-Wl,--whole-archive")

But setting those link flags did not work.

I do not want to use SHARED libraries, I would prefer to stay with STATIC libraries, how can I have my libraries keep all the objects/symbols? Am I forced to link both bar and foo ?

Or is the only solution combining libraries using a custom command like it is suggested in this question: Combining several static libraries into one using CMake


Here I try to explain with an example:

I have a project with the following structure:

src/
|
|- foo/
|   | - foo.h
|   | - foo.c
|   | - CMakeLists.txt
|- bar/
|   | - bar.h
|   | - bar.c
|   | - CMakeLists.txt

Here is the source code for the files under foo

////////////////// foo.c

#include <stdio.h>

#include <Python.h>

#include "foo.h"

static PyObject * module_global;

void init_interpreter()
{
  printf("Initializing interpreter\n");
  Py_Initialize();
}

void fin_interpreter()
{
  printf("Finalizing interpreter\n");
  Py_FinalizeEx();
}

void import_module(const char* module_name)
{
  if(module_name)
  {
    module_global = PyImport_ImportModule(module_name);
  }
  else
  {
    printf("No good module provided\n");
  }
}


////////////////// foo.h

#ifndef FOO_H
#define FOO_H

void init_interpreter();
void fin_interpreter();
void import_module(const char* module_name);

#endif // FOO_H

/////////////////// foo/CMakeLists.txt

find_package (Python REQUIRED COMPONENTS Development)

add_library(foo STATIC
  foo.c
)

target_link_libraries(foo PUBLIC
  ${Python_LIBRARIES}
)

target_include_directories(foo PUBLIC
  ${Python_INCLUDE_DIRS}
  ${CMAKE_CURRENT_SOURCE_DIR}
)

Here is the source code for the files under bar

////////////////// bar.c

#include <stdio.h>

#include "foo.h"

void pretty_init_interpreter()
{
  printf("This is a very pretty call!!\n");
  init_interpreter();
}

////////////////// bar.h

#ifndef BAR_H
#define BAR_H

void pretty_init_interpreter();

#endif // BAR_H

/////////////////// bar/CMakeLists.txt

add_library(bar STATIC
  bar.c
)

target_link_libraries(bar PUBLIC
  foo
)

target_include_directories(bar PUBLIC
  $<TARGET_PROPERTY:foo,INCLUDE_DIRECTORIES>
  ${CMAKE_CURRENT_SOURCE_DIR}
)

I build those libraries and install the library bar.a on another project that looks like:

src/
|
|- main.c
|- CMakeLists.txt
|- install/
|   | - lib
|   |   | - libbar.a
|   | - include
|   |   | - foo.h
|   |   | - bar.h

Source code in this project:

/////////////// main.c
#include <stdio.h>
#include "foo.h"
#include "bar.h"

int main(int argc, char** argv)
{
  pretty_init_interpreter();

  import_module(NULL);

  fin_interpreter();

  return 0;
}
//////////////// CMakeLists.txt

cmake_minimum_required(VERSION 3.20)

set (CMAKE_C_STANDAR 99)

project(foo-project LANGUAGES C)

find_package (Python REQUIRED COMPONENTS Development)

add_executable(foo-exec main.c)

find_library(BAR_LIB
NAMES
  bar
HINTS
  ${PROJECT_SOURCE_DIR}/install/lib/
NO_DEFAULT_PATH
)

target_link_libraries(foo-exec PUBLIC
  ${BAR_LIB}
  ${Python_LIBRARIES}
)

target_include_directories(foo-exec PUBLIC
  ${Python_INCLUDE_DIRS}
  ${PROJECT_SOURCE_DIR}/install/include/
)

Compiling the executable with mingw, I get the following error:

c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: CMakeFiles\foo-exec.dir/objects.a(main.c.obj): in function `main':
main.c:9: undefined reference to `import_module'
c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: main.c:11: undefined reference to `fin_interpreter'
c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: ../../install/lib/libbar.a(bar.c.obj): in function `pretty_init_interpreter':
bar/bar.c:8: undefined reference to `init_interpreter'
collect2.exe: error: ld returned 1 exit status

Using nm to look at the symbols of bar shows that the functions are missing

$ nm install/lib/libbar.a

bar.c.obj:
00000000 b .bss
00000000 d .data
00000000 N .debug_abbrev
00000000 N .debug_aranges
00000000 N .debug_info
00000000 N .debug_line
00000000 r .eh_frame
00000000 r .rdata
00000000 r .rdata$zzz
00000000 t .text
         U _init_interpreter
00000000 T _pretty_init_interpreter
         U _puts

I had a basic understanding that libraries were a collection of objects/symbols, so I had assumed that by linking foo to bar, bar would also have those objects/symbols, but that is not the case, so I am wondering if I am missing something or if I am doing something wrong.

Indeed this is not quite the case . The C/C++ language standards do not define the contents of static libraries, but on most platforms they are archives containing specific object files . If those object files collectively depend on external symbols, then that static library depends on some other library or object file and all must be linked into the final executable.

"Linking" one static library to another in CMake merely declares the dependency between them. There is no platform-independent mechanism for merging them nor, within CMake, is there a pressing need to do so.

I do not want to use SHARED libraries, I would prefer to stay with STATIC libraries, how can I have my libraries keep all the objects/symbols? Am I forced to link both bar and foo?

There are many questions and answers on StackOverflow detailing how to use OBJECT libraries to accomplish this. Here's, say, five:

  1. https://stackoverflow.com/a/11448878/2137996
  2. https://stackoverflow.com/a/10635366/2137996
  3. https://stackoverflow.com/a/68866472/2137996
  4. https://stackoverflow.com/a/67435497/2137996
  5. https://stackoverflow.com/a/68406028/2137996

Yet, I would say that all of these questions are misguided. CMake handles linking library dependencies just fine.

I fear, you might be thinking in too many details. CMake is handling the linker flags for you.

You want to define two library targets, foo and bar, right? @Tsyvarev is absolutely right, static libraries don't involve linking, but it's more the creation of an archive (in Linux ar is used). Only if you create a binary, an executable - be it a real executable with a main function or a dll/shared object library - you will have a linking process.

But dependencies in CMake make sense even for static libraries, because CMake propagates not only the actual library, but also include directories and other properties you pass to your target as public.

So, what you want in your project with the executable: a CMake target for all of your libraries, which can be imported to other projects. This means an export CMake-file defining your library targets with

add_library(foo STATIC IMPORTED)
add_library(bar STATIC IMPORTED)
target_link_libraries(bar PUBLIC foo)

( https://cmake.org/cmake/help/latest/command/add_library.html?highlight=add_library#imported-libraries ) And you might want to populate some target properties, like include directories. additional public compiler/linker flags, etc.

Many external libraries install their CMake-config files to one of the default locations (on Linux for example that's in /usr/lib/cmake/, or if you are using Qt, you can find the CMake config files on Windows in the Qt installation directory in a cmake directory next to the static libraries; do a search for *.cmake files in doubt). You can put your own CMake-config files there or let yourself be inspired by the way other components do it.

Here are some links, you might find interesting:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM