6

I am very new to C++ and all the associated terms and toolchains. I am trying to build a static library that customers can use in their own projects. Ideally, I would like to send them nothing but an .a and a .lib file, plus an .h file.

Right now, my CMake file looks like this:

project(ava-engine-client)
cmake_minimum_required(VERSION 3.9.6)
set(CMAKE_BUILD_TYPE Release)
set(CMAKE_FIND_LIBRARY_SUFFIXES .a )

add_compile_options(-std=c++11)

# GRPC and Protocol Buffers libraries location
list(APPEND CMAKE_PREFIX_PATH "/opt/grpc" "/opt/protobuf")

# CMake find modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")


# Recurse and find all the generated Protobuf .cc files
file(GLOB_RECURSE PROTO_GEN_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/ava_engine/ava/*.cc)

include_directories("${CMAKE_CURRENT_SOURCE_DIR}")

# Building the library
add_library(ava_engine_client STATIC src/AvaEngineClient.cc src/AvaEngineClient.h ${PROTO_GEN_SRCS})

target_link_libraries(ava_engine_client ${PROTOBUF_LIBRARIES} ${GRPC_LIBRARY})


## Building Playground
add_executable(playground src/Playground.cc)

target_link_libraries(playground ava_engine_client)

Now this fails at the linking stage, because I don't link the playground target, with the dependencies inside the ava_engine_client library:

Undefined symbols for architecture x86_64:
  "grpc::ClientContext::ClientContext()", referenced from:
      ...
  "grpc::ClientContext::~ClientContext()", referenced from:
      ...
  "grpc::CreateChannel(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::shared_ptr<grpc::ChannelCredentials> const&)", referenced from:
      ...
  "grpc::g_core_codegen_interface", referenced from:
      ...

This isn't what I want, because it would require the customer to link with dependencies in my library (which doesn't' seem right to me).

Now, I have read a few Stack Overflow posts like this one: (CMake: include library dependencies in a static library) that suggest using CMAKE_CXX_ARCHIVE_CREATE to create an archive file. Is this the approach I should take? Is what I want even possible?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dominic Bou-Samra
  • 14,799
  • 26
  • 100
  • 156

4 Answers4

3

If you are bound to create a static library, the solution you linked in your original post is probably the best (CMake: include library dependencies in a static library). Using ar or library tools to combine the static libraries seems to be the only way to go. This is a pretty popular question on Stack Overflow and all the answers seem to come down to that.

However, if you are able, the easiest solution by far is to create a shared library and link your static libraries into it (as mentioned by jszpilewski in the comments). Yes, it does mean distributing the shared library for runtime. Whether that is practical or not depends on your project.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
avariant
  • 2,234
  • 5
  • 25
  • 33
  • So I would be responsible for distributing the shared library? As in, including GRPC and Protobuf somehow? Or do I just say "you need these installed" ? – Dominic Bou-Samra Feb 15 '18 at 02:21
  • Assuming you have static libraries for GRPC and Protobuf, by linking them into a shared library (such as a dll on Windows) they are bundled into the dll. There is no need to distribute them separately. – avariant Feb 16 '18 at 01:36
  • On Windows, you will need to distribute the shared library (the .dll) and a link library (.lib). The consumer will link against the .lib and the .dll will need to be placed in the same directory (typically) as their executable. On OSX (I think) the consumer can link directly against the shared library (a .dylib). Again, the .dylib will need to be in the executable dir. There are some big functional differences in how classes are exposed between static and shared libraries though. https://stackoverflow.com/questions/6840576/how-to-export-a-c-class-from-a-dll – avariant Feb 16 '18 at 01:46
1

The clause add_executable will always try to produce a binary ready to be used by the operating system so it is not suitable for producing a static library.

You may use as an inspiration or even borrow some code from the Google Test unit test framework. It is using CMake to generate the UT framework as configurable static or dynamic libraries using the final syntax like:

cxx_library(gtest "${cxx_strict}" src/gtest-all.cc)

But it defines cxx_library and some other functions internally. So you should take a look at their CMake include file internal_utils.cmake. Google Test comes with the BSD software license.

jszpilewski
  • 1,632
  • 1
  • 21
  • 19
  • I think the add_executable is just to test the linkage. The previous call to add_library is the actual call to produce the library. Correct me if I'm wrong. – avariant Feb 14 '18 at 16:09
  • add_executable without IMPORT creates an executable, it is just a test client in this case. The original problem of providing dependencies of a static library still holds anyway and they must be explicitly added to the library project or maybe creating a dynamic library would be a better solution in this case. – jszpilewski Feb 14 '18 at 17:21
  • @avariant that's correct yeah. Playground is just a test program – Dominic Bou-Samra Feb 14 '18 at 22:41
1

As stated here:
Add -c option in compiler flags and use it like this:

 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -c")

You have to look at linking step if there's missing some items; maybe a library, or an object file.

The first step in debugging unexpected build failures with CMake generated Makefiles is to run:

❯ make VERBOSE=1

This will give you insight into what CMake is doing behind the scenes.

Ref: Symbol(s) not found for architecture x86_64 - Cmake - Mac sierra

Community
  • 1
  • 1
1

Packing all dependencies into your library may be tempting at first, because it should result in the highest compatibility for your users. Often, though, it's not such a good idea because also the size of your library can grow very quickly. You also need to make sure to have a proper static library, i.e., you would need to cram in all the typical system libraries that come with every C or C++ installation. Otherwise there might be incompatibilites further down the road.

If you compile a static library you should also use -fPIC to allow your library to be used within other shared libraries or binaries:

set_target_properties(ava_engine_client PROPERTIES POSITION_INDEPENDENT_CODE on)
mattmilten
  • 6,242
  • 3
  • 35
  • 65