Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[question] How to tell binary in one dependency where to find shared library in another one? #15155

Closed
1 task done
ottmar-zittlau opened this issue Nov 22, 2023 · 8 comments
Closed
1 task done
Assignees

Comments

@ottmar-zittlau
Copy link

What is your question?

Hi,

I'm trying to build my own grpc recipe.
This works well, however, when I build its dependency protobuf as shared library and then try to use an executable contained in the grpc package (the grpc cpp plugin for protobuf) from within CMake, there is an error complaining that a shared library contained in the protobuf package cannot be found.

I tried to reduce it to an example that uses the grpc recipe from the conan center (cf. https://github.com/conan-io/conan-center-index/blob/master/recipes/grpc/all/conanfile.py).

I downloaded this recipe and forced protobuf to always be built as shared library (and grpc still as static library - in my opinion this shouldn't make a difference) by adding a line to the end of the configure step in the grpc recipe:

def configure(self):
    if self.options.shared:
        self.options.rm_safe("fPIC")
        self.options["protobuf"].shared = True

        if cross_building(self):
            self.options["grpc"].shared = True

    # This is the line I added.
    self.options["protobuf"].shared = True

I locally built this package by running conan create . 1.54.3@test/test.

Then I set up a simple example:
conanfile.txt

[requires]
grpc/1.54.3@test/test
protobuf/3.21.12

[generators]
CMakeDeps

[options]
grpc:shared=False
protobuf:shared=True

CMakeLists.txt:

cmake_minimum_required(VERSION 3.16...3.25 FATAL_ERROR)

project(test_conan VERSION 0.0.1 LANGUAGES CXX)

if(NOT EXISTS "${PROJECT_BINARY_DIR}/conan.cmake")
    file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/73734d0b7ff3c9bf7e0661e2c6c709b8520926cd/conan.cmake"
           "${PROJECT_BINARY_DIR}/conan.cmake" TLS_VERIFY ON
    )
endif()

include(${PROJECT_BINARY_DIR}/conan.cmake)

conan_cmake_install(PATH_OR_REFERENCE ${PROJECT_SOURCE_DIR} BUILD missing)

list(PREPEND CMAKE_PREFIX_PATH ${PROJECT_BINARY_DIR})

find_package(gRPC CONFIG REQUIRED MODULES cpp_plugiasdn)
find_package(protobuf CONFIG REQUIRED)


set(_protos test.proto)
add_library(test-conan STATIC ${_protos} )

target_link_libraries(test-conan PUBLIC protobuf::libprotobuf  gRPC::grpc)

set(GENERATED_PROTO_DIR "${CMAKE_CURRENT_BINARY_DIR}/include")
file(MAKE_DIRECTORY "${GENERATED_PROTO_DIR}")

# generate datatypes from proto 
protobuf_generate(
    TARGET test-conan
    OUT_VAR PROTO_GENERATED_FILES
    IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
    PROTOC_OUT_DIR "${GENERATED_PROTO_DIR}"
)

# generate grpc services from proto   
protobuf_generate(
    TARGET test-conan
    OUT_VAR PROTO_GENERATED_FILES
    LANGUAGE grpc
    GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc
    PLUGIN "protoc-gen-grpc=${GRPC_CPP_PLUGIN_PROGRAM}"
    IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
    PROTOC_OUT_DIR "${GENERATED_PROTO_DIR}"
)

test.proto

syntax = "proto3";
package test.proto;


message Empty {}

message Flag {
    bool active = 1;
}

When I try to build the cmake project I get the error:

[build] .../.conan/data/grpc/1.54.3/test/test/package/f753eb55d7835e7de1b45861c2f5500409a77b2c/bin/grpc_cpp_plugin: error while loading shared libraries: libprotoc.so.32: cannot open shared object file: No such file or directory
[build] --grpc_out: protoc-gen-grpc: Plugin failed with status code 127.

I added the virtualrunenv generator to the list of generators in the conanfile.txt with no success.
I see that the grpc recipe uses VirtualRunEnv and also does some DYLD_LIBRARY magic on MacOS.

How can I correctly set the environment variables on Linux and Windows?

Thanks and best regards
oz

Have you read the CONTRIBUTING guide?

  • I've read the CONTRIBUTING guide
@ottmar-zittlau
Copy link
Author

I tried building grpc as shared library by setting the respective option in the conanfile.txt

...
grpc:shared=True
...

Then I get the error:

[build] .../.conan/data/grpc/1.54.3/test/test/package/18ebcc00b78b57ebbee448923ef3872dfa68c1c0/bin/grpc_cpp_plugin: error while loading shared libraries: libgrpc_plugin_support.so.1.54: cannot open shared object file: No such file or directory

Now the executable doesn't find a lib from the same package - which makes sense, when the environment variables are not properly set.

@memsharded memsharded self-assigned this Nov 22, 2023
@memsharded
Copy link
Member

Hi @ottmar-zittlau

Thanks for your question.

I think you are seeing a limitation of the CMake-conan flow. This flow is not capable of setting the environment or enviroment variables that might be necessary to locate dependencies at runtime.
If parts of your tools are defined to be shared then the path to those tools must be injected at build time, but CMake cannot do that easily. With the standard conan install + activate env + cmake ... flow, things work without issues.

There can be different approaches:

  • The dependencies for tool_requires are generally better if they are statically linked. There is not much benefit in having protoc use libprotobuf from a shared library, while it can be very problematic, specially in cross-build scenarios
  • It is not clear if you are using already the 2 profiles for build and host. It is extremely recommended in Conan 1.X for any scenario that uses tool_requires, and this seems the case for protoc.
  • When using the 2 profiles, it is perfectly valid, and recommended to have protobuf/*:shared=True in the host profile, but protobuf/*:shared=False (or nothing, as it is the default) in the build profile. This way there shouldn't be any issues when calling protoc from your CMakeLists.txt, because it will be in the build context and statically linked.

@ottmar-zittlau
Copy link
Author

Hi @memsharded,

and again, thanks for helping me out!

So far, we are using protobuf statically, but due to some issues (more details here) when using it through shared libraries and directly, we wanted to test if using it as shared library could be beneficial.

Some further questions concerning your advice:

  • we are currently not using 2 profiles and I also don't really understand the concept behind it. For my case, does your suggestion mean, that if I build the package the build configuration (protobuf static library) is used and when someone downloads the pre-built package the host configuration (shared library) is used? But what happens, if I build a shared library as part of my package - isn't then protobuf linked statically into this library? I would like to avoid this.

A solution I could think of, would be to build part of grpc (the executables) with static protobuf library, while the other part (the libraries) should depend on the shared protobuf library. But that sounds like a horrible idea.

  • concerning the conan install + activate env + cmake ... approach you outlined.
    I did the following in the folder containing all my files and it did actually work (I had to add the virtualrunenv generator to the conanfile.txt):
mkdir build
cd build
conan install ..
source activate_run.sh
cmake .. -DCMAKE_BUILD_TYPE=RELEASE
cmake --build .

I don't think that this approach is viable for me, because it would mean that the consumers of my packages would always need to manually invoke Conan and activate the environment by themselves. I like that everything is currently integrated into CMake and people don't need to do anything. Do I understand correctly, that it's not possible to integrate running the activation script into CMake?

I tried using a custom target

add_custom_target(set_env_variables COMMAND /bin/sh ${PROJECT_BINARY_DIR}/activate_run.sh COMMENT "Set environment variables")
...
add_dependencies(test-conan set_env_variables)

..but that didn't work. Probably because the scope in which the script is executed (and the environment variables set) is different from the one in which cmake runs.

I found a stackoverflow answer claiming that toolchain-files are an approach to set environment variables. So I added CMakeToolchain to the list of generators in my conanfile.txt. The generated tool-chain-file sets environment variables (ENV{PKG_CONFIG_PATH}) - is it also possible to include the paths from activate_run.sh here?

Thanks and best regards,
oz

@memsharded
Copy link
Member

we are currently not using 2 profiles and I also don't really understand the concept behind it. For my case, does your suggestion mean, that if I build the package the build configuration (protobuf static library) is used and when someone downloads the pre-built package the host configuration (shared library) is used? But what happens, if I build a shared library as part of my package - isn't then protobuf linked statically into this library? I would like to avoid this.

The concept can be illustrated more clearly if you are cross compiling, lets say from a Windows laptop, to an embedded Linux based target:

  • The "build" machine and context is Windows
  • The "host" machine and context is Linux

If you want to use libprotobuf and protoc in this context 3 things are needed:

  • Define a requires = "protobuf/version" to depend on the libprotobuf library in the host context
  • Define a ``tool_requires = "protobuf/version" to depend on protoc in the build context
  • Pass -pr:h=mylinuxprofile -pr:b=mywindowsprofile

Because when you need to run protoc you cannot run the protoc that comes in the Linux package, it will simply not run in your machine. And you cannot link with libprotobuf from the Windows package. So this setup actually requires 2 different binaries of the protobuf/version package, one in "build" context and one in "host" context. Conan can manage this scenario without problems.

This is exactly the same scenario than when using "shared". You want your project to be linked with libprotobuf as shared library, you don't necessarily want to have protoc compiler to have to locate a shared library just because you need to call protoc, and it is better to link protoc statically.

I found a stackoverflow answer claiming that toolchain-files are an approach to set environment variables. So I added CMakeToolchain to the list of generators in my conanfile.txt. The generated tool-chain-file sets environment variables (ENV{PKG_CONFIG_PATH}) - is it also possible to include the paths from activate_run.sh here?

It is not that straightforward, but we have some plans to try to add env-vars directly to CMake, so it is not necessary to use environment activation scripts like the ones generated now.

In any case, it seems you are using the legacy enviroment, the correct ones are the ones generated with VirtualBuildEnv and VirtualRunEnv, that will be named conanbuild.sh and conanrun.sh.

@ottmar-zittlau
Copy link
Author

I changed my conanfile.txt to

[requires]
grpc/1.54.3
protobuf/3.21.12

[tool_requires]
protobuf/3.21.12

[generators]
CMakeDeps
CMakeToolchain
VirtualRunEnv

[options]
grpc:shared=True
protobuf:shared=True

And then added two profiles:

host.profile

[options]
protobuf:shared=True

and

build.profile

[settings]
arch = x86_64
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=11
os=Linux
build_type=Release


[options]
protobuf:shared=False

Then I ran

mkdir build
cd build
conan install --build=missing -pr=default -pr:b=../build.profile -pr:h=../host.profile ..
cmake .. -DCMAKE_BUILD_TYPE=RELEASE
cmake --build .

The same error as before occured.

I have to admit that I don't understand the two-profiles approach at all. If you're motivated trying to explain it to me again, then I would be very grateful. If not, feel free to close this issue. I will stick to static libraries and work around the limitations.

Thanks again for your efforts.

@memsharded
Copy link
Member

Have you had the chance to do the tutorial?
This concept is explained, with hands-on exercises in https://docs.conan.io/2/tutorial/consuming_packages/cross_building_with_conan.html

It is a bit difficult to reproduce what you are describing. cmake .. -DCMAKE_BUILD_TYPE=Release should be failing if you are not passing the -DCMAKE_TOOLCHAIN_FILE=..../conan_toolchain.cmake argument. So I'd say that you are still relying on the cmake-conan integration, and you didn't remove it from your CMakeLists.txt, so that integration is doing another conan install without the profiles, so the previous conan install with the 2 profiles that you are doing before is irrelevant, because it is not being used.

If you want to use the cmake-conan integration, you need to follow https://github.com/conan-io/cmake-conan and make sure that the different profiles are passed, etc. What you do in conan install before is not used at all. Or if you drop the cmake-conan integration, you will need to pass the toolchain file to CMake invocation.

@ottmar-zittlau
Copy link
Author

Thanks for the hint concerning the conan-cmake mechanism.
I think I start to understand your idea. I could use static versions of the libraries in the build context and the shared versions in the host context. If my project would only generate C++-code from proto-files, I can imagine that this idea works.

However, my project also contains libraries that link to protobuf and grpc. If I now build the project, the static version of protobuf is linked, but this is what I would like to avoid.

Ideally, I want to use the shared version of libprotobuf for everything except the protoc-grpc-plugins (so that they don't need to find anything at runtime) - but I don't think that I can pass two different versions of protobuf to the grpc package (a static one for the plugins and a shared one for everything else) - or can conan figure this separation out, somehow?

I could create a separate conan package that contains the protobuf compiler and the grpc plugins, but that doesn't contain any libraries. Then I could add this package as a tool requirement to convert proto-files to C++-code and add grpc- and protobuf-library packages as normal requirements.

I don't see an easy solution. It would be great if at some point the tool chain file sets the environment variables, because this would solve my issues without any additional effort from the user.

Thanks a lot for your patience - I fear that I will bother you with more questions in the future.

@memsharded
Copy link
Member

Thanks for following up and closing

I don't see an easy solution. It would be great if at some point the tool chain file sets the environment variables, because this would solve my issues without any additional effort from the user.

CMakeToolchain is not generating environment variables in the generated CMakePresets.json files. I guess this definitely solved your issue?

Don't hesitate to open new tickets or ping in old ones, sometimes we cannot follow up on everything, so resurfacing things is good too. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants