Fix complete_static_lib to include Rust libraries.
When a C++ `static_library` has `complete_static_lib = true` and depends
on a `rust_library`, the Rust library's symbols need to be available to
downstream consumers of the static library.
This change propagates `rust_library` dependencies through
`complete_static_lib` targets to downstream linking targets (like
executables), similar to how `shared_library` dependencies are handled.
This ensures the `.rlib` files are passed directly to the final linker
via the Ninja `rlibs` variable (using `{{rlibs}}` substitution).
Reproduction error before this change (with mixed C++/Rust example):
```
FAILED: c_main
g++ -o c_main -Wl,--start-group @c_main.rsp -Wl,--end-group
/usr/bin/ld: obj/c_main.c_main.o: in function `main':
c_main.cc:(.text+0x5): undefined reference to `call_from_c'
collect2: error: ld returned 1 exit status
```
Verification success after this change:
```
[1/2] AR libc_lib.a
[2/2] LINK c_main
$ examples/rust_example/out/c_main
Value from c_lib (via Rust): 42
SUCCESS!
```
Bug: 42440276
Change-Id: I7ea3d6df444a25faf2847f59de5303884e0933a1
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/22100
Reviewed-by: Petr Hosek <phosek@google.com>
Commit-Queue: Evan Shrubsole <eshr@google.com>
diff --git a/examples/rust_example/BUILD.gn b/examples/rust_example/BUILD.gn
index 964b331..48da170 100644
--- a/examples/rust_example/BUILD.gn
+++ b/examples/rust_example/BUILD.gn
@@ -1,3 +1,17 @@
group("default") {
- deps = [ "//hello_world/src:hello_world" ]
+ deps = [
+ "//hello_world/src:hello_world",
+ ":c_main",
+ ]
+}
+
+static_library("c_lib") {
+ complete_static_lib = true
+ deps = [ "//hello_world/foo/src:foo" ]
+ sources = [ "c_lib.cc" ]
+}
+
+executable("c_main") {
+ deps = [ ":c_lib" ]
+ sources = [ "c_main.cc" ]
}
diff --git a/examples/rust_example/build/BUILD.gn b/examples/rust_example/build/BUILD.gn
index ebfaa64..567cc15 100644
--- a/examples/rust_example/build/BUILD.gn
+++ b/examples/rust_example/build/BUILD.gn
@@ -2,7 +2,7 @@
tool("rust_bin") {
depfile = "{{target_out_dir}}/{{crate_name}}.d"
outfile = "{{target_out_dir}}/{{crate_name}}"
- command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link -Z dep-info-omit-d-target {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
+ command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
description = "RUST $outfile"
outputs = [ outfile ]
}
@@ -10,7 +10,7 @@
tool("rust_staticlib") {
depfile = "{{target_out_dir}}/{{crate_name}}.d"
outfile = "{{target_out_dir}}/{{crate_name}}.a"
- command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link -Z dep-info-omit-d-target {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
+ command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
description = "RUST $outfile"
outputs = [ outfile ]
}
@@ -18,7 +18,7 @@
tool("rust_rlib") {
depfile = "{{target_out_dir}}/{{crate_name}}.d"
outfile = "{{target_out_dir}}/lib{{crate_name}}.rlib"
- command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link -Z dep-info-omit-d-target {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
+ command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
description = "RUST $outfile"
outputs = [ outfile ]
}
@@ -26,7 +26,7 @@
tool("rust_cdylib") {
depfile = "{{target_out_dir}}/{{crate_name}}.d"
outfile = "{{target_out_dir}}/lib{{crate_name}}.so"
- command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link -Z dep-info-omit-d-target {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
+ command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
description = "RUST $outfile"
outputs = [ outfile ]
}
@@ -40,6 +40,32 @@
command = "cp -af {{source}} {{output}}"
description = "COPY {{source}} {{output}}"
}
+
+ tool("cxx") {
+ depfile = "{{output}}.d"
+ command = "clang++ -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
+ depsformat = "gcc"
+ description = "CXX {{output}}"
+ outputs = [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
+ }
+
+ tool("alink") {
+ command = "llvm-ar rcs {{output}} {{inputs}}"
+ description = "AR {{target_output_name}}{{output_extension}}"
+ outputs = [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+ default_output_extension = ".a"
+ output_prefix = "lib"
+ }
+
+ tool("link") {
+ outfile = "{{target_output_name}}{{output_extension}}"
+ rspfile = "$outfile.rsp"
+ command = "clang++ {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} {{libs}} {{rlibs}} -Wl,--end-group"
+ description = "LINK $outfile"
+ default_output_dir = "{{root_out_dir}}"
+ rspfile_content = "{{inputs}}"
+ outputs = [ outfile ]
+ }
}
config("rust_defaults") {
diff --git a/examples/rust_example/c_lib.cc b/examples/rust_example/c_lib.cc
new file mode 100644
index 0000000..ec8b33e
--- /dev/null
+++ b/examples/rust_example/c_lib.cc
@@ -0,0 +1,5 @@
+extern "C" int call_from_c();
+
+int c_lib_get_value() {
+ return call_from_c();
+}
diff --git a/examples/rust_example/c_main.cc b/examples/rust_example/c_main.cc
new file mode 100644
index 0000000..23a868e
--- /dev/null
+++ b/examples/rust_example/c_main.cc
@@ -0,0 +1,13 @@
+#include <stdio.h>
+
+int c_lib_get_value();
+
+int main() {
+ int val = c_lib_get_value();
+ printf("Value from c_lib (via Rust): %d\n", val);
+ if (val == 42) {
+ printf("SUCCESS!\n");
+ return 0;
+ }
+ return 1;
+}
diff --git a/examples/rust_example/hello_world/foo/src/lib.rs b/examples/rust_example/hello_world/foo/src/lib.rs
index 73c615c..d4d2060 100644
--- a/examples/rust_example/hello_world/foo/src/lib.rs
+++ b/examples/rust_example/hello_world/foo/src/lib.rs
@@ -8,4 +8,9 @@
pub fn new(s: &'static str) -> Foo {
Foo{s: s, i: "foo"}
}
+}
+
+#[no_mangle]
+pub extern "C" fn call_from_c() -> i32 {
+ 42
}
\ No newline at end of file
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
index 161845c..01df8b5 100644
--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -615,6 +615,121 @@
}
}
+TEST_F(NinjaCBinaryTargetWriterTest, CompleteStaticLibraryWithRustDep) {
+ TestWithScope setup;
+ Err err;
+
+ Target rust_lib(setup.settings(), setup.ParseLabel("//foo:rust_lib"));
+ rust_lib.set_output_type(Target::RUST_LIBRARY);
+ rust_lib.visibility().SetPublic();
+ rust_lib.sources().push_back(SourceFile("//foo/lib.rs"));
+ rust_lib.source_types_used().Set(SourceFile::SOURCE_RS);
+ rust_lib.rust_values().crate_name() = "rust_lib";
+ SourceFile crate_root("//foo/lib.rs");
+ rust_lib.rust_values().set_crate_root(crate_root);
+ rust_lib.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(rust_lib.OnResolved(&err));
+
+ TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.source_types_used().Set(SourceFile::SOURCE_CPP);
+ target.set_complete_static_lib(true);
+ target.public_deps().push_back(LabelTargetPair(&rust_lib));
+
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_gen_dir = gen/foo\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libbar\n"
+ "\n"
+ "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+ " source_file_part = input1.cc\n"
+ " source_name_part = input1\n"
+ "\n"
+ "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o | "
+ "obj/foo/librust_lib.rlib\n"
+ " arflags =\n"
+ " output_extension =\n"
+ " output_dir =\n"
+ " rlibs = obj/foo/librust_lib.rlib\n";
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest,
+ CompleteStaticLibraryWithRustDepPropagation) {
+ TestWithScope setup;
+ Err err;
+
+ Target rust_lib(setup.settings(), setup.ParseLabel("//foo:rust_lib"));
+ rust_lib.set_output_type(Target::RUST_LIBRARY);
+ rust_lib.visibility().SetPublic();
+ rust_lib.sources().push_back(SourceFile("//foo/lib.rs"));
+ rust_lib.source_types_used().Set(SourceFile::SOURCE_RS);
+ rust_lib.rust_values().crate_name() = "rust_lib";
+ SourceFile crate_root("//foo/lib.rs");
+ rust_lib.rust_values().set_crate_root(crate_root);
+ rust_lib.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(rust_lib.OnResolved(&err));
+
+ Target c_lib(setup.settings(), setup.ParseLabel("//foo:c_lib"));
+ c_lib.set_output_type(Target::STATIC_LIBRARY);
+ c_lib.visibility().SetPublic();
+ c_lib.sources().push_back(SourceFile("//foo/lib.cc"));
+ c_lib.source_types_used().Set(SourceFile::SOURCE_CPP);
+ c_lib.set_complete_static_lib(true);
+ c_lib.public_deps().push_back(LabelTargetPair(&rust_lib));
+ c_lib.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(c_lib.OnResolved(&err));
+
+ TestTarget target(setup, "//foo:main", Target::EXECUTABLE);
+ target.sources().push_back(SourceFile("//foo/main.cc"));
+ target.source_types_used().Set(SourceFile::SOURCE_CPP);
+ target.private_deps().push_back(LabelTargetPair(&c_lib));
+
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_gen_dir = gen/foo\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = main\n"
+ "\n"
+ "build obj/foo/main.main.o: cxx ../../foo/main.cc\n"
+ " source_file_part = main.cc\n"
+ " source_name_part = main\n"
+ "\n"
+ "build ./main: link obj/foo/main.main.o obj/foo/libc_lib.a | "
+ "obj/foo/librust_lib.rlib\n"
+ " ldflags =\n"
+ " libs =\n"
+ " frameworks =\n"
+ " swiftmodules =\n"
+ " output_extension =\n"
+ " output_dir =\n"
+ " rlibs = obj/foo/librust_lib.rlib\n";
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+}
+
// This tests that output extension and output dir overrides apply, and input
// dependencies are applied.
TEST_F(NinjaCBinaryTargetWriterTest, OutputExtensionAndInputDeps) {
diff --git a/src/gn/resolved_target_data.cc b/src/gn/resolved_target_data.cc
index 7b4a495..628e6a4 100644
--- a/src/gn/resolved_target_data.cc
+++ b/src/gn/resolved_target_data.cc
@@ -177,7 +177,8 @@
// inherited.
const TargetInfo* dep_info = GetTargetInheritedLibs(dep);
for (const auto& pair : dep_info->inherited_libs) {
- if (pair.target()->IsFinal())
+ if (pair.target()->IsFinal() ||
+ pair.target()->output_type() == Target::RUST_LIBRARY)
inherited_libraries->Append(pair.target(),
is_public && pair.is_public());
}