facebook / buck2

Build system, successor to Buck

Home Page:https://buck2.build/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

C++ mixed linking

cbarrete opened this issue · comments

I'm trying to build a cxx_binary with mixed linking: third-party code is consumed as shared objects and internal libraries are consumed as static libraries.

Is this use case supported by the prelude, and if so, how?

It seems that setting link_style in a cxx_binary target affects all dependencies: if I set it to static, the third-party prebuilt_cxx_library shared objects are missing in the linker.argsfile, if I set it to dynamic, my own libraries are built as shared objects. Setting the link_style per cxx_library seems to have no effect.

Here's a minimal repro on top of a bare buck2 init --git with the 2024-02-15 release:

diff --git a/BUCK b/BUCK
index 1cb6b38..5d96647 100644
--- a/BUCK
+++ b/BUCK
@@ -1,7 +1,18 @@
-# A list of available rules and their signatures can be found here: https://buck2.build/docs/api/rules/
+cxx_binary(
+    name = "test",
+    srcs = ["main.cpp"],
+    deps = [":shared_lib", ":static_lib"],
+    link_style = None,
+)
+
+cxx_library(
+    name = "shared_lib",
+    srcs = ["shared_lib.cpp"],
+    link_style = "shared",
+)
 
-genrule(
-    name = "hello_world",
-    out = "out.txt",
-    cmd = "echo BUILT BY BUCK2> $OUT",
+cxx_library(
+    name = "static_lib",
+    srcs = ["static_lib.cpp"],
+    link_style = "static",
 )
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..d0b8ca7
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,8 @@
+void dynamic_lib_fn();
+void static_lib_fn();
+
+int main()
+{
+    dynamic_lib_fn();
+    static_lib_fn();
+}
diff --git a/shared_lib.cpp b/shared_lib.cpp
new file mode 100644
index 0000000..6451836
--- /dev/null
+++ b/shared_lib.cpp
@@ -0,0 +1,3 @@
+void dynamic_lib_fn()
+{
+}
diff --git a/static_lib.cpp b/static_lib.cpp
new file mode 100644
index 0000000..28ce184
--- /dev/null
+++ b/static_lib.cpp
@@ -0,0 +1,3 @@
+void static_lib_fn()
+{
+}

which I then check with readelf -d $(buck2 build :test --show-full-simple-output). In this case, both libraries are linked as dynamically:

 0x0000000000000001 (NEEDED)             Shared library: [lib_shared_lib.so]
 0x0000000000000001 (NEEDED)             Shared library: [lib_static_lib.so]

If I set the binary's link_style to either "static" or "shared", it overrides both libraries' link_style, which is not what I want. I've read the documentation for both cxx_binary and cxx_library and played with a few parameters, but got nowhere with that.

Is there something that I'm missing here/do you have any pointers for me?

I also forgot to mention, since the documentation states that:

link_style: Determines whether to build and link this rule's dependencies statically or dynamically. Can be either static, static_pic or shared.

(that is, the library itself might not be affected by the link_style, only its dependencies), I also tried adding an intermediate layer of wrappers and depending on those, but no dice:

cxx_library(
    name = "shared_lib_wrapper",
    exported_deps = [":shared_lib"],
    link_style = "shared",
)

cxx_library(
    name = "static_lib_wrapper",
    exported_deps = [":static_lib"],
    link_style = "static",
)

I think the missing piece here is preferred_linkage. link_style says how a target wants to consume its dependencies when it is being linked as a shared lib or executable, preferred_linkage indicates how a library wants to be consumed.

For A -> B -> C -> D

If these all have preferred_linkage="any" (the default), and the all have link_style="shared", you'll get:

A.so -> B.so -> C.so -> D.so

Change A to link_style = "static" and you get

A.so -> B.a -> C.a -> D.a

If you then change C to preferred_linkage = "shared" you'd get

A.so -> B.a -> C.so -> D.so

Note there that C got a D.so, not a D.a. A link_style attribute only affects the linking of the target itself. When C is built as a shared lib, it uses its own link_style, not the one from A.

If you changed C's link_style to "static", you'd get:

A.so -> B.a -> C.so -> D.a

This is exactly what I needed and was missing, thanks a lot!
The transitive subtleties are a bit of a gotcha, it would be nice for them to be documented (I'll look into this when I have some time after work).