1

I am using cmake to create my static libraries with something along the lines of

add_library(library library.cpp)
install(TARGETS library DESTINATION lib)

which creates liblibrary.a which is what I want. However I would like to bundle that with a library, let's say vendor/proprietary.a by doing something custom like

tmp=$(mktemp -d)
cd $tmp
ar -x $<TARGET_FILE:library>
ar -x vendor/proprietary.a
ar -qc $<TARGET_FILE:library> *
rm -rf $tmp

Can I do that with cmake without it forgetting that the target library is actually a library (eg by using add_custom_command/add_custom_target).

1 Answers1

3

This is, disappointingly, quite hard. We managed to do it on the Halide team, but only because it was a hard requirement from a corporate client. To other readers without such constraints, I say this: here be dragons. Use CMake's usual targets and dependencies and let it put all the static libraries on the end-product's link line.

To OP, I say, try this:

First, create a CMakeLists.txt in your vendor directory with the following content:

# 0. Convenience variable
set(proprietary_lib "${CMAKE_CURRENT_SOURCE_DIR}/proprietary.a")

# 1. Get list of objects inside static lib
execute_process(COMMAND "${CMAKE_AR}" -t "${proprietary_lib}"
                WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
                OUTPUT_VARIABLE proprietary_objects)

string(STRIP "${proprietary_objects}" proprietary_objects)
string(REPLACE "\n" ";" proprietary_objects "${proprietary_objects}")

# 2. Attach configure dependency to the static lib
set_property(DIRECTORY . APPEND PROPERTY
             CMAKE_CONFIGURE_DEPENDS proprietary.a)

# 3. Extract the lib at build time
add_custom_command(
    OUTPUT ${proprietary_objects}
    COMMAND "${CMAKE_AR}" -x "${proprietary_lib}"
    DEPENDS "${proprietary_lib}")

# 4. Get absolute paths to the extracted objects
list(TRANSFORM proprietary_objects
     PREPEND "${CMAKE_CURRENT_BINARY_DIR}/")

# 5. Attach the objects to a driver target so the
# custom command doesn't race
add_custom_target(proprietary.extract DEPENDS ${proprietary_objects})

# 6. Add a target to encapsulate this
add_library(proprietary OBJECT IMPORTED GLOBAL)
set_target_properties(proprietary PROPERTIES
                      IMPORTED_OBJECTS "${proprietary_objects}")
# TODO: add usage requirements
# target_include_directories(proprietary INTERFACE ...)

# 7. Force proprietary to run completely after extraction
add_dependencies(proprietary proprietary.extract)

There's a lot going on here, but ultimately the steps are straightforward and the complications are with explaining the dependencies to CMake. Also, this comes with the caveat that it is Linux-only (or at least GNU-ar-compatible archiver only). It is possible to do something similar for MSVC, but it would be too much for this answer.

So first we ask the archiver which objects are in the library and we lightly process its one-object-per-line output into a CMake list. That's step 1 above.

Step 2 tells CMake that if the timestamp on proprietary.a is ever updated, then it will need to re-run CMake (and thereby get a new list of objects).

Step 3 creates a custom command which will, at build time, run the archiver tool to extract the objects into the vendor build directory.

Step 4 turns the (relative) list of objects into a list of absolute paths to those objects after the custom command runs. This is for the benefit of add_custom_target which expects absolute paths (or rather, does weird things with relative paths if certain policies are enabled).

Step 5 creates a custom target to drive the archive extraction.

Step 6 creates an imported object library to encapsulate the extracted library. It has to be global because imported targets are directory-scoped by default and this is an abuse of the imported-library feature. You can add additional usage requirements here.

Finally, step 7 puts a dependency to the driver target on the object library.

This can then be used transparently. Here's an example:

cmake_minimum_required(VERSION 3.16)
project(example)

add_subdirectory(vendor)

add_library(library library.cpp)
target_link_libraries(library PRIVATE proprietary)
Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
  • 1
    Thanks so much for putting in the effort! – fakedrake Jan 31 '21 at 23:42
  • Perfect! it works! Just as an aside, when linking I am getting `relocation R_X86_64_32 against '.rodata.str1.8' can not be used when making a PIE object; recompile with '-fPIC'`. Is there a way to tell cmake "whenever you link with `propertiary` or any of it's dependants use the compiler flag `-no-pie`"? – Christos Perivolaropoulos Feb 01 '21 at 10:17
  • You can disable PIC for `library` by setting the target property `POSITION_INDEPENDENT_CODE` to `OFF` with `set_target_properties` – Alex Reinking Feb 01 '21 at 10:21
  • https://cmake.org/cmake/help/latest/prop_tgt/POSITION_INDEPENDENT_CODE.html – Alex Reinking Feb 01 '21 at 10:22