Bazel - how to deal with transitive dependencies of external .so libraries?

Viewed 1096

I'm trying to build a binary application with Bazel. This binary depends on an external, pre-compiled library, let's call it liba.so. In turn, liba.so depends on libb.so (this information I obtain via readelf -d liba.so | grep NEEDED.

To build this, I have the following setup in Bazel:

cc_import(
    name = "liba",
    shared_library = "liba.so",
    deps = [":libb"],
)

cc_import(
    name = "libb",
    shared_library = "libb.so",  
)

cc_binary(
    name = "my_app",
    srcs = ["main.cpp"],
    deps = [":liba"],
)

Building works fine, however when running (either via bazel run or directly) ldd fails to find libb.so.

I've been reading about this and the reason is that Bazel adds to the RUNPATH of the binary only its direct dependencies. Since libb.so is a transitive dependency, the binary cannot find it.

To solve this, I can think of the following hacks:

  • Add ugly linker flags to tell Bazel to add to the RPATH instead of RUNPATH. This is however deemed a bad idea, since RPATH is being deprecated and doesn't allow override via LD_LIBRARY_PATH.

  • Patch the third-party .so file to add to their RUNPATH. This works but it doesn't feel good to patch libraries I don't own.

  • Make the transitive dependencies direct dependencies of the binary. This is not good, each library should be responsible of its dependencies. The binary doesn't need to know what liba.so depends on.

Are there better ways to accomplish this? Other things I've tried without success:

  • Use cc_library instead of cc_import
  • Use data instead of deps.

Thanks!

1 Answers

I was wrestling with this problem for a long time. The first thing I would recommend you to try is to enable

copy_dynamic_libraries_to_binary

toolchain feature. I think it was primarily designed for Windows but should work for Linux as well.

Now for our purposes, I had to take a more complex, but in the end more reliable approach: I have implemented the aspect which for every binary traverses the dependency graph, collects everything which belongs to the binary output folder and generates file copy actions to place necessary files there when the binary is built. That includes transitive dependencies, but is flexible enough to copy other files we need in the output folder - C++ runtimes, data files, etc. If you are interested I can clip and share relevant code snippets.

Konstantin

Related