beman-project / exemplar

Example Beman project
Other
10 stars 16 forks source link

Support for multi-config generators and different binary configurations #18

Open ComicSansMS opened 2 months ago

ComicSansMS commented 2 months ago

I hope this is the right place for this, since the example project seems to be the only spot where we define conventions like this.

Currently the example always builds and installs the example library under the same name:

The library binary file will always be libexample.a, example.lib, or similar

add_library(example) 

The binary is always installed to the default directory as determined by GNUInstallDirs:

install(TARGETS example
  EXPORT example
  ARCHIVE
    COMPONENT libexample-dev
  FILE_SET HEADERS
    COMPONENT libexample-dev
  LIBRARY
    COMPONENT libexample
    NAMELINK_COMPONENT libexample-dev
)

This is very inconvenient for multi-config generators, where you may want to provide multiple builds for different configurations at the same time. It is particularly problematic for MSVC builds, as the Debug runtime there is incompatible with the Release runtime, so you want to be able to provide at least two different sets of binaries.

Traditionally, libraries solved this by adding a Debug postfix to the binary name:

set_property(TARGET example PROPERTY DEBUG_POSTFIX d)

This would then cause Debug builds to produce a binary exampled.lib, which coexists alongside the example.lib in the install tree.

Generator expressions allow for an alternative approach, where each configuration gets its own subdirectory in the install tree:

install(TARGETS example
  EXPORT example
  ARCHIVE
    COMPONENT libexample-dev
  ARCHIVE DESTINATION lib/$<CONFIG>  # note this line that was added
  FILE_SET HEADERS
    COMPONENT libexample-dev
  LIBRARY
    COMPONENT libexample
    NAMELINK_COMPONENT libexample-dev
)

This will result in the following install tree on MSVC:

+-${CMAKE_INSTALL_PREFIX}
  +-include
  +-lib
    +-cmake
    +-Debug
      +-example.lib
    +-MinSizeRel
      +-example.lib
    +-Release
      +-example.lib
    +-RelWithDebInfo
      +-example.lib

I personally prefer the second solution, but I would like to hear other opinions.

In addition to this, there is other potential conflicts for binary configurations. x86 vs x64 is an obvious example, but there is also the issue of incompatible static and dll runtimes on Windows. Boost solved this by using an elaborate naming convention for binaries (e.g. boost_atomic-vc143-mt-gd-x64-1_86.lib).

I would like to start a discussion how to solve this:

camio commented 2 months ago

Hey @ComicSansMS, welcome and thanks for the issue!

Whether we want to support [installed libraries using multi-config generator] use cases and to what extent

This is important to support. Is there a "most common" layout for installed C++ libraries? Does it make sense to use the same layout on Windows and UNIX platforms?

What conventions for naming and directory structure to adopt to address this

I don't know, but we should prioritize ergonomics for the library's user. I think it is safe to assume consumers will be using CMake (with or without or a package manager) to consume the library so perhaps the layout won't be exposed to them.

ComicSansMS commented 2 months ago

This is important to support. Is there a "most common" layout for installed C++ libraries? Does it make sense to use the same layout on Windows and UNIX platforms?

In my experience, the most popular version is the d suffix, which is also the one that has been around the longest. Traditionally CMake only distinguished between debug and optimised binaries for library find scripts. The one-subdirectory-per-config approach has only become possible with the modern introduction of packaged CMake config scripts, so it is not super popular. Many libraries choose to ignore the issue altogether, requiring two different install trees for Debug and Release on MSVC. Here's a list of some popular libraries that do take measures to support this:

Also:

Regarding the trade-offs between the different options for accomodating debug and release builds on MSVC:

What conventions for naming and directory structure to adopt to address this

I don't know, but we should prioritize ergonomics for the library's user. I think it is safe to assume consumers will be using CMake (with or without or a package manager) to consume the library so perhaps the layout won't be exposed to them.

This is correct, when using CMake or a package manager, you are unlikely to notice any difference. The biggest impact is that with per-config subdirectories, your binaries will take up disk space for up to three distinct release configurations, while with the debug suffix it will be at most one.

camio commented 2 months ago

@bbrown105 @bretbrownjr , it would be great to get your opinion here.

camio commented 2 months ago

Also @steve-downey

bretbrownjr commented 1 month ago

I've been giving this some thought for the last week. There are some competing priorities to balance when considering the use cases descried here:

I don't have a simple solution now. I was talking to @foonathan and some others and it seems probable that we will need to bite the bullet and have one or more Beman CMake modules that provide abstractions over these workflows that allow powerful but DRY CMakeLists.txt for each Beman repo.

I expect the next step is for someone to describe the concerns and a recommended approach in a more fully considered proposal. I hope to get to that myself at some point, but I would be happy to see someone else take a stab at coming up with a an approach and starting a discussion on the Beman discourse and/or in the weekly meeting.

camio commented 1 month ago

@ComicSansMS, would it make sense to use multiple subdirectories for multi-config generators and place archive artifacts directly in lib/ for single-config generators?

ComicSansMS commented 1 month ago

@ComicSansMS, would it make sense to use multiple subdirectories for multi-config generators and place archive artifacts directly in lib/ for single-config generators?

I guess the desire to put artifacts directly in lib is so that it follows common GNU directory layout conventions?

That would be fine, I guess. It solves the multi-config issue in MSVC, which is probably the most important use case.

It would be a bit awkward in that a Ninja Multi-Config build will produce a different install tree than e.g. a single-config Ninja build. I'm personally not too worried about this. Also, installing from differently configurated build trees to the same install location will overwrite any previously installed configurations. This is not unusual on Unix, so I would be surprised if it raised any eyebrows.

bretbrownjr commented 1 month ago

I believe vcpkg has expectations or at least defaults for where to put the debug build of a library. I believe if you provide Debug as a CMAKE_BUILD_TYPE, your find_package(foobar) will resolve to a CMake module that points you at ${libdir}/debug/libfoobar.lib, etc. I also believe they remove share and include directories, assuming those are identical across Debug and Release builds.

Anyway, this function tends to be called in vcpkg portfile.cmake files for each library: https://github.com/microsoft/vcpkg/blob/master/ports/vcpkg-cmake-config/vcpkg_cmake_config_fixup.cmake#L3

camio commented 1 month ago

It sounds like we have consensus here. @ComicSansMS would you be willing to put together a PR for this?

ComicSansMS commented 1 month ago

I created a simple PR that will mirror the vcpkg directory layout that @bretbrownjr described above:

https://github.com/beman-project/exemplar/pull/39