Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the ideal cmake installation directory structure for a relocatable multiple platform installation?

CMake install takes destination dirs, often using GNUInstallDirs to load standard values for the destination names. For example:

include(GNUInstallDirs)
install(TARGETS Foo
    EXPORT Foo
    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

However it doesn't provide different paths built for different platforms, or architectures. I've been installing to a platform specific folder within my project via CMAKE_INSTALL_PREFIX like this:

CMAKE_INSTALL_PREFIX=dist/${CMAKE_SYSTEM_NAME}/${CMAKE_SYSTEM_PROCESSOR}

This has some problems though:

  • It duplicates the includes which are the same across platforms.
  • If I install the different platforms to the same root but change the lib dir, cmake wouldn't find the cmake config targets in lib/non/standard/path/cmake/FooConfig.cmake
  • And Module mode searches also don't find the libraries properly, which is problematic when MODULE mode searches are done. This can happen if the library happens to also have a cmake module which requires every find_package to have to specify "CONFIG" which starts to get weird, especially when 3rd party libraries are told to consider this installation directory for a common dependency, and don't specify CONFIG in the find_package, because why would they?

I'm looking for a structure that works with find_package module mode and config mode; something like this:

<install_prefix>/
  include/
    foo/
      foo.h
  lib/
    <PLAT x ARCH x CONFIG>/
      cmake/
        foo/
          FooConfig.cmake
      libFoo.a

Goal is:

  • libs can be co-installed for different platforms, architectures and configs
  • includes can be shared
  • finding the includes should work in typical module mode searches
  • should just work with vanilla find_package(Foo REQUIRED), but for the appropriate platform and arch.

Considering I'd be installing mostly 3rd party libraries here, if this can be done by overriding the vars from GNUInstallDirs, it'd probably work for many libraries. The rest I guess would either have to be edited or I just give up and use separate installation dirs that include platform and arch.

like image 409
johnb003 Avatar asked Jun 30 '18 05:06

johnb003


2 Answers

I'll post as far as I've gotten so far, and maybe it'll be useful. If someone has missing pieces I'll update this.

The documentation describing the search order is here: https://cmake.org/cmake/help/v3.12/command/find_package.html?highlight=%3Cprefix%3E

There are quite a few supported permutations, so I'll start by eliminating some:

  • Ignore the Apple variations because that's for frameworks.
  • Ignore the Windows only paths, because it's not really idiomatic for any other platform.
  • Finally each has variations related to where the cmake script are found. I'll just pick the .../cmake/<name>*/ style.

That leaves (**extra space added to show similarity):

<prefix>/        (lib/<arch>|lib*|share)/cmake/<name>*/         (U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/cmake/<name>*/         (W/U)

The docs state that certain search paths are meant for certain platforms, but technically I think all of the search paths are tried. It's just a matter of what's idiomatic for the platform. As such, however, you can't use the different styles as platform differentiator. In fact, this should mean the Unix option is also valid for Windows.

As you can see above the only difference for the Windows friendly Unix format is an extra prefix for the name. Either are valid choices, so for now I'll just refer to the Unix only style as it fits my preference. Finally, I don't really care about the "share" folder, because we're talking about C/C++ libraries.

So finally we're down to these 2 choices:

<prefix>/lib/<arch>/cmake/<name>*/
<prefix>/lib*/cmake/<name>*/

Option 1: Multi-arch

Paths with lib/<arch> are enabled if the CMAKE_LIBRARY_ARCHITECTURE variable is set. CMAKE_<LANG>_LIBRARY_ARCHITECTURE states:

If the <LANG> compiler passes to the linker an architecture-specific system library search directory such as <prefix>/lib/<arch> this variable contains the <arch> name if/as detected by CMake.

I know this is more specifically for certain distros that support multiarch, and is meant to be set automatically. However, I can't quite figure out if this is something that can easily be leveraged with cmake to achieve the goal here; what would you set to control this in cmake? On a multiarch system, libraries Foo and Bar could look like:

<prefix>/lib/x86_64-linux-gnu/
    foo-1.1/
        cmake/FooConfig.cmake
    bar/
        cmake/BarConfig.cmake
    foo-1.1.so
    foo-1.1.lib
    foo-1.1.dll
    foo-1.1.dylib
    bar.so
    bar.lib
    bar.dll
    bar.dylib

If we can control what the multiarch value is, this option could easily be used for other platforms like: Darwin_x86_64, Windows_x86_64, etc. And it would probably be compatible with find_package module mode where the includes would all be found, without needing config mode to redirect to some non-standard dir.

Option 2: lib variations

A partial solution is to keep the platforms completely separate in the prefix, but at least the 64-bit and 32-bit architectures can be combined just splitting the libs.

lib* includes one or more of the values lib64, lib32, libx32 or lib (searched in that order).

  • Paths with lib64 are searched on 64 bit platforms if the FIND_LIBRARY_USE_LIB64_PATHS property is set to TRUE.
  • Paths with lib32 are searched on 32 bit platforms if the FIND_LIBRARY_USE_LIB32_PATHS property is set to TRUE.
  • Paths with libx32 are searched on platforms using the x32 ABI if the FIND_LIBRARY_USE_LIBX32_PATHS property is set to TRUE.
  • The lib path is always searched.

This at least partially helps. I'd continue to use lib as 64-bit, and then just use lib32 for 32-bit needs.

like image 58
johnb003 Avatar answered Sep 29 '22 11:09

johnb003


If you expect user to set some standard directory as CMAKE_PREFIX_PATH, like

/usr/local # installation prefix

but your project installs things into non-standard, platform-specific subdirectories, like

/usr/local/linux/x86/ # actual root directory where things are installed

you may place FooConfig.cmake into standard subdirectory:

/usr/local/lib/FooConfig.cmake

but write it so it searches appropriate .cmake from platform-specific directory:

# File: FooConfig.cmake
# Location: lib/
#
# This is platform-independent script.
#
# Redirect configuration to the platform-specific script <system>/<cpu>/lib/FooConfig.cmake.
include(${CMAKE_CURRENT_LIST_DIR}/../${CMAKE_SYSTEM_NAME}/${CMAKE_SYSTEM_PROCESSOR}/lib/FooConfig.cmake)

Platform-specific *Config.cmake script can be written in a usual way.

So, if a user will write

find_package(Foo)

in an environment set for Linux/x86, it will setup imported targets for that platform.

like image 25
Tsyvarev Avatar answered Sep 29 '22 11:09

Tsyvarev