Building a combined static library

I have a project that builds a static library. It depends on a dozen other projects (some of 'em are mine, also building static libraries) that look up through find_package() and have imported targets. Giving a build option -DBUILD_COMBINED_STATIC=ON, I’d like to be able to build a combined static library that includes all dependent static libraries into a single one. With GNU ar, I need to render an MRI file and run ar -M combined.mri. With MSVC’s librarian, it’s possible by mentioning source libraries in CLI. Looks like a task for add_custom_command() + add_custom_target()

The most problematic part is getting a list of paths to all static libraries a project’s target should combine. First of all, it must include indirect dependencies as well. Secondly, LINK_LIBRARIES (or INTERFACE_LINK_LIBRARIES of dependencies) may have generator expressions. The latter means I have to use file(GENERATE…). Thanks to transitive $<TARGET_PROPERTY:…> genex evaluation, I can relatively easily get all dependencies recursively, having a list of all CMake targets at the end:

set_property(
    TARGET mylib
    PROPERTY
        TRANSITIVE_LINK_PROPERTIES INTERFACE_LINK_LIBRARIES
  )
file(
    GENERATE
    OUTPUT mylib-all-dependencies.lst
    CONTENT [=[
        $<LIST:REMOVE_DUPLICATES,$<TARGET_PROPERTY:mylib,INTERFACE_LINK_LIBRARIES>>
      ]=]
  )

The list of CMake targets needs to be resolved into particular file paths and then used to form an MRI file. It’ll be nice to use $<LIST:TRANSFORM…> somehow to replace a CMake target with $<TARGET_PROPERTY:ARCHIVE_OUTPUT_NAME> (with fallback to OUTPUT_NAME) for all list items… Apparently, it’s not possible for now anyway…

So, the maximum I can get after the initial CMake configuration step is this list of CMake targets that must be resolved into paths to particular static libraries (filtering out all non-static library dependencies in the middle). This also means that at build time, running cmake -P render-mri-file.cmake via add_custom_xxx command wouldn’t work cuz the script doesn’t have any targets, and even usage of imported targets is prohibited, so there is no way to resolve OUTPUT_NAME property into a filename. That eventually means add_custom_command() or …_target() should run cmake at build time to configure another “dummy” project that must perform all the same find_package calls, import all the same targets and reading the list file rendered by the top-level cmake configure step tries to render the MRI file…

That is the current “solution” I’m thinking about. I appreciate any advice (in case I’ve missed something).

The other approach I’ve tried, or I’m researching now, is to replace archive-creating commands somehow:

  • It looks like it is impossible to do this per target because these commands are not properties of a target but global scope variables (set by a toolchain file?).
  • Unlike shared libraries, static libraries do not have a “link” step, so overriding the CXX_LINKER_LAUNCHER property for the target won’t help as well ;-(
  • Maybe add an MRI “language” and redefine CMAKE_MRI_ARCHIVE_APPEND, CMAKE_MRI_ARCHIVE_CREATE, and CMAKE_MRI_ARCHIVE_FINISH into CMake scripts that will start rendering MRI file (adding CREATE <archive> preamble), append ADDLIB commands, finalize the MRI script w/ SAVE command and execute ar at the end. But, I’m not sure (yet) how to use add_library and specify this MRI “language”…

Any alternative suggestions are also very welcome.

PS However, in the future, I’d prefer to have a solution to build combined static libraries out of the box in CMake. I will research how to do it and make an MR. Any thoughts on this implementation are also very welcome.

PSS Casting @craig.scott into discussion :wink:

1 Like

I found a way of doing list-maps with generator expressions, by writing a generator expression to apply to each element, escaping the generator expression and feeding it through $<LIST:TRANSFORM> and $<GENEX_EVAL>:

1 Like