conan icon indicating copy to clipboard operation
conan copied to clipboard

[question] Is there a way to create a conan package that combines both Release and Debug in the same package?

Open RochaStratovan opened this issue 1 year ago • 3 comments

What is your question?

Is there a way to create a conan package that combines both Release and Debug in the same package?

Conan's package methodology of one build_type (Release, Debug, RelWithDebugInfo, MinSize) per package, breaks CMake's methodology of many configurations may be in a single CMake relocatable package.

This is problematic when we use the relocatable CMake package files that we generate for ourselves and our customers, or when using the CMake package files that 3rd party open source library's provide in combination with Visual Studio's multi-configuration IDE.

I know about the suggested method of

conan install . -s build_type=Release
conan install . -s build_type=Debug
<cmake commands next>

works for Windows Visual Studio multi-configuration when using the conan generated CMake files. However, it does not work when using standard relocatable CMake packages.

I did some digging into this and found that this appears to be a fundamental mismatch between conan and CMake packages.

When we create a relocatable CMake package for release and debug images it is typically done with a folder hierarchy such as:

<foo-package-root>/
    +--cmake/
    |    +--foo-config-debug.cmake
    |    +--foo-config-release.cmake
    |    +--foo-config.cmake
    |
    +--include/
    |
    +--lib/
         +--fooD.lib
         +--fooR.lib

To find the foo package it would either be installed to a common root location, or the CMAKE_MODULE_PATH value is updated to include the path to <foo-package-root>.

When find_package(foo...) is invoked it finds this location and has the ability to return one of the two configurations listed.


If we tried this in the conan world where we have one configuration per package we end up with two separate paths/repos where each one has just one configuration. Such as the following:

<conan-debug-path>
|   +--cmake/
|   |    +--foo-config-debug.cmake
|   |    +--foo-config.cmake
|   |
|   +--include/
|   |
|   +--lib/
|        +--fooD.lib
<conan-release-path>
    +--cmake/
    |    +--foo-config-release.cmake
    |    +--foo-config.cmake
    |
    +--include/
    |
    +--lib/
         +--fooR.lib

This mechanism can never work for a Visual Studio multi-configuration situation because cmakes find_package(foo...) stops when it finds the first cmake package with the correct name.

If CMAKE_MODULE_PATH has <conan-debug-path> listed first then it will stop when it finds <conan-debug-path>/cmake/foo-config.cmake and will always and only return the debug version of the library.

This is because it doesn't know about the existence or location of the release library.


Why don't we just use the conan generated CMake files?

We don't use the conan generated CMake files because conan doesn't know about any extra logic or variables that we or 3rd party libraries place in our relocatable CMake package files.


Workaround

I've found and confirmed the following workaround.

  1. Start with a leaf level project that doesn't have any requirements

  2. Use the conan way to start a multi configuration toolchain.

    conan install . -s build_type=Release
    conan install . -s build_type=Debug
    
  3. Generate the cmake build files

    cmake . -B build -A x64 ^
        -DCMAKE_TOOLCHAIN_FILE="<path to toolchain file" ^
        -DCMAKE_POLICY_DEFAULT_CMP0091=NEW
    
  4. Compile R and D and install to the local INSTALL folder

    cmake --build build --config Release
    cmake --install build --config Release --prefix "%CD%\INSTALL"
    cmake --build build --config Debug
    cmake --install build --config Debug --prefix "%CD%\INSTALL"
    

    After this point the INSTALL folder has both R and D libraries as well as the relocatable package to work for R and D libraries.

  5. Set the conanfile.py logic to package the contents of the INSTALL folder and to use the relocatable CMake package logic.

  6. Export the package

    conan export-pkg . -s build_type=Release
    conan export-pkg . -s build_type=Debug
    

At this point we have two conan packages for Release and Debug in the local cache, where each package is a duplicate of the other with both Release and Debug libraries and cmake logic.

When consumers of this package try to set up multi configuration following the conan way

conan install . -s build_type=Release
conan install . -s build_type=Debug

The local toolchain is set up for Release and Debug and the package folders that are searched will be able to provide both Debug and Release images.


Is there a better conan way to do this?

I've confirmed that this workaround works. But it feels hacky. I don't like it. Is there an better way to do this? A conan way to do this?

Have you read the CONTRIBUTING guide?

  • [X] I've read the CONTRIBUTING guide

RochaStratovan avatar Feb 07 '24 00:02 RochaStratovan

Hi @RochaStratovan

Thanks for your question.

In short, no, the Conan model is for 1 binary configuration per-package, for multiple reasons:

  • It is the only approach that really scales. For example many users also manage other configs like RelWithDebInfo. Adding all possible configs inside a package is not possible.
  • It is important that packages are immutable. That means that both the Release and Debug configurations must be built at the same time, which is very inefficient in many cases
  • Packaging Debug artifacts together with Release ones is even considered a security risk, with risk of leaking the wrong files to production or to users that would have Debug information unveiling proprietary IP
  • Debug artifacts are large. This makes this approach very inefficient for many cases that really only need to build the production (Release) version

The CMake is really a build-model not a package-model. Having real binary packages that package more than 1 config inside the same package does not really works in most real world package management scenarios. We know that there are compromises that should be made, like the issue with modules embedded in packages, but there could be other approaches to that, depending on the functionality in those modules. In many cases there could be extracted to a separate tool_require packages, the idea is that packages should be build-system agnostic, if you compile a package, that package should be usable in Meson or MSBuild too, not only CMake.

memsharded avatar Feb 07 '24 00:02 memsharded

Could you please elaborate a bit more how your files

+--cmake/
    |    +--foo-config-debug.cmake
    |    +--foo-config-release.cmake
    |    +--foo-config.cmake

look like? Wouldn't it be possible to have the foo-config-debug.cmake in the debug package, the foo-config-release.cmake in the release package and the foo-config.cmake would be just a simple wrapper that could be generated in the consumer side? Or there could have something injected from the consumer side that would help locating the other binary in the other cache folder?

memsharded avatar Feb 08 '24 00:02 memsharded

Wouldn't it be possible to have the foo-config-debug.cmake in the debug package, the foo-config-release.cmake in the release package and the foo-config.cmake would be just a simple wrapper that could be generated in the consumer side? Or there could have something injected from the consumer side that would help locating the other binary in the other cache folder?

I don't know. But my first reaction is I don't think so. These files are generated by the provider, not the consumer.


What do the config files look like?

The files we're using are autogenerated using CMake Relocatable Packages. There have been times that we've hand crafted similar file sets when we wanted more than CMake's basic files, but the core usage is the same. I've seen 3rd party libraries use both scenarios.

The CMake documentation for this is a pretty quick read.

The section Creating Relocatable Packages under their Importing and Exporting Guide is the basis for how we generate most of our packages.

I've created a simple example github repository for this conan-cmake-relocatable-package.

Disclaimer: I am comfortable with CMake, and really new to conan. I've taken some short cuts to get this simple example working as easily as possible.

The example defines a library that provides a function simple().

The simple() function prints the following for a Debug build

This is DEBUG mode

and

This is NOT-DEBUG mode.

otherwise.

These are the steps to view the auto generated relocatable CMake Package files

These commands are done on a Windows machine with VS2022 installed and I'm using git bash because I prefer the Linux CLI.

# Ensure the workspace is clean
git clean -xfd .


# Use cmake to generate the 64 bit VS2022 VS compile files
cmake . -B build -G "Visual Studio 17 2022" -A x64


# build the release binaries
cmake --build build --config Release


# build the debug binaries
cmake --build build --config Debug


# install the release binaries + cmake files to my local INSTALL
# folder
cmake --install build --prefix "${PWD}/INSTALL" --config Release

# install the debug binaries + cmake files to my local INSTALL
# folder
cmake --install build --prefix "${PWD}/INSTALL" --config Debug

I've also wrapped all of this in conan. You may have lots of WTF did you do that for moments. Remember, I'm just starting with conan.

After doing the above you can test this using the conan test_pkg using

conan export-pkg . -s build_type=Release
conan export-pkg . -s build_type=Debug

RochaStratovan avatar Feb 08 '24 19:02 RochaStratovan

TL;DR No, not officially

This isn't officially possible, but the method describe above could be done but it breaks conan rules since a conan package typically only has one binary configuration in it.

RochaStratovan avatar Mar 15 '24 20:03 RochaStratovan

Thanks for following up @RochaStratovan and sorry I didn't respond back then.

Yes, Conan might support multiple configurations inside a package, but most build-system integrations are designed to handle 1 configuration, so doing that might require quite some effort replacing or customizing different parts of the toolchains and build system integrations.

memsharded avatar Mar 15 '24 22:03 memsharded