From 11b6595cc3385f7249e6127b9e6a63217186f7c3 Mon Sep 17 00:00:00 2001 From: lisovskiy Date: Wed, 16 Jul 2025 22:20:35 +0500 Subject: [PATCH 1/5] feat: ability to statically link against C++ stdlib --- src/lib.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d4c2fe381..64dbee615 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -300,6 +300,7 @@ pub struct Build { files: Vec>, cpp: bool, cpp_link_stdlib: Option>>, + cpp_link_stdlib_static: bool, cpp_set_stdlib: Option>, cuda: bool, cudart: Option>, @@ -432,6 +433,7 @@ impl Build { static_flag: None, cpp: false, cpp_link_stdlib: None, + cpp_link_stdlib_static: false, cpp_set_stdlib: None, cuda: false, cudart: None, @@ -951,6 +953,27 @@ impl Build { self } + /// Force linker to statically link C++ stdlib. By default cc-rs will emit + /// rustc-link flag to link against system C++ stdlib (e.g. libstdc++.so, libc++.so) + /// Provide value of `true` if linking against system library is not desired + /// + /// Note that for `wasm32` target C++ stdlib will always be linked statically + /// + /// # Example + /// + /// ```no_run + /// cc::Build::new() + /// .file("src/foo.c") + /// .shared_flag(true) + /// .cpp_link_stdlib("stdc++") + /// .cpp_link_stdlib_static(true) + /// .compile("libfoo.so"); + /// ``` + pub fn cpp_link_stdlib_static(&mut self, is_static: bool) -> &mut Build { + self.cpp_link_stdlib_static = is_static; + self + } + /// Force the C++ compiler to use the specified standard library. /// /// Setting this option will automatically set `cpp_link_stdlib` to the same @@ -1482,8 +1505,15 @@ impl Build { // Add specific C++ libraries, if enabled. if self.cpp { if let Some(stdlib) = self.get_cpp_link_stdlib()? { - self.cargo_output - .print_metadata(&format_args!("cargo:rustc-link-lib={}", stdlib.display())); + if self.cpp_link_stdlib_static { + self.cargo_output.print_metadata(&format_args!( + "cargo:rustc-link-lib=static={}", + stdlib.display() + )); + } else { + self.cargo_output + .print_metadata(&format_args!("cargo:rustc-link-lib={}", stdlib.display())); + } } // Link c++ lib from WASI sysroot if target.arch == "wasm32" { From 574c1aa90bdc2c940217002e6e0eac4db413f450 Mon Sep 17 00:00:00 2001 From: lisovskiy Date: Tue, 29 Jul 2025 23:41:23 +0500 Subject: [PATCH 2/5] chore: add test for cpp_link_stdlib_static --- dev-tools/cc-test/build.rs | 29 +++++++++++++++++++++++++++++ src/lib.rs | 6 +++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/dev-tools/cc-test/build.rs b/dev-tools/cc-test/build.rs index 23c0a6f9a..917e3a5ab 100644 --- a/dev-tools/cc-test/build.rs +++ b/dev-tools/cc-test/build.rs @@ -173,6 +173,35 @@ fn main() { let out = cc::Build::new().file("src/expand.c").expand(); let out = String::from_utf8(out).unwrap(); assert!(out.contains("hello world")); + + // Test static linking of stdc++ on Linux + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + if target_os == "linux" { + // Rust linker has no problem linking against dynamic libraries, for instance + // it doesn't require any additional steps to link against system + // `libstdc++.so`, but if we emit `cargo:rustc-link-lib=static=stdc++`, it will + // not be able to find `libstdc++.a` file despite it almost always located next to + // `libstdc++.so`. So providing explicit `rustc-link-search` solves the error + let libstdc_path: PathBuf = String::from_utf8( + Command::new("g++") + .args(["--print-file-name=libstdc++.a"]) + .output() + .expect("Failed to run g++") + .stdout, + ) + .unwrap() + .into(); + let search_dir = libstdc_path.parent().unwrap(); + + println!("cargo:rustc-link-search={}", search_dir.display()); + + cc::Build::new() + .file("src/baz.cpp") + .cpp(true) + .cpp_link_stdlib("stdc++") + .cpp_link_stdlib_static(true) + .compile("baz"); + } } #[track_caller] diff --git a/src/lib.rs b/src/lib.rs index 64dbee615..4c97c298c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -963,11 +963,11 @@ impl Build { /// /// ```no_run /// cc::Build::new() - /// .file("src/foo.c") - /// .shared_flag(true) + /// .file("src/foo.cpp") + /// .cpp(true) /// .cpp_link_stdlib("stdc++") /// .cpp_link_stdlib_static(true) - /// .compile("libfoo.so"); + /// .compile("foo"); /// ``` pub fn cpp_link_stdlib_static(&mut self, is_static: bool) -> &mut Build { self.cpp_link_stdlib_static = is_static; From 15589eec70af5c5f1c30b48235a65075b1a80386 Mon Sep 17 00:00:00 2001 From: lisovskiy Date: Wed, 30 Jul 2025 00:06:24 +0500 Subject: [PATCH 3/5] symlink libstdc++.a to OUT dir --- dev-tools/cc-test/build.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/dev-tools/cc-test/build.rs b/dev-tools/cc-test/build.rs index 917e3a5ab..1b4bef858 100644 --- a/dev-tools/cc-test/build.rs +++ b/dev-tools/cc-test/build.rs @@ -12,24 +12,24 @@ fn main() { return; } - let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()); - fs::remove_dir_all(&out).unwrap(); - fs::create_dir(&out).unwrap(); + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + fs::remove_dir_all(&out_dir).unwrap(); + fs::create_dir(&out_dir).unwrap(); // The following are builds where we want to capture the output (i.e. stdout and // stderr). We do that by re-running _this_ executable and passing in the // action as the first argument. - run_forked_capture_output(&out, "metadata-on"); - run_forked_capture_output(&out, "metadata-off"); + run_forked_capture_output(&out_dir, "metadata-on"); + run_forked_capture_output(&out_dir, "metadata-off"); - run_forked_capture_output(&out, "warnings-off"); + run_forked_capture_output(&out_dir, "warnings-off"); if cc::Build::new().get_compiler().is_like_msvc() { // MSVC doesn't output warnings to stderr, so we can't capture them. // the test will use this env var to know whether to run the test. println!("cargo:rustc-env=TEST_WARNINGS_ON=0"); } else { println!("cargo:rustc-env=TEST_WARNINGS_ON=1"); - run_forked_capture_output(&out, "warnings-on"); + run_forked_capture_output(&out_dir, "warnings-on"); } let mut build = cc::Build::new(); @@ -104,7 +104,7 @@ fn main() { // Test that the `windows_registry` module will set PATH by looking for // nmake which runs vanilla cl, and then also test it after we remove all // the relevant env vars from our own process. - let out = out.join("tmp"); + let out = out_dir.join("tmp"); fs::create_dir(&out).unwrap(); println!("nmake 1"); let status = cc::windows_registry::find(&target, "nmake.exe") @@ -175,8 +175,8 @@ fn main() { assert!(out.contains("hello world")); // Test static linking of stdc++ on Linux - let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); - if target_os == "linux" { + #[cfg(target_os = "linux")] + { // Rust linker has no problem linking against dynamic libraries, for instance // it doesn't require any additional steps to link against system // `libstdc++.so`, but if we emit `cargo:rustc-link-lib=static=stdc++`, it will @@ -190,10 +190,11 @@ fn main() { .stdout, ) .unwrap() + .trim() .into(); - let search_dir = libstdc_path.parent().unwrap(); - println!("cargo:rustc-link-search={}", search_dir.display()); + let out_stdlib = out_dir.join("libstdc++.a"); + std::os::unix::fs::symlink(libstdc_path, out_stdlib).unwrap(); cc::Build::new() .file("src/baz.cpp") From 1b1799bda9459c9404f8c79dad66ecbed36cce3b Mon Sep 17 00:00:00 2001 From: lisovskiy Date: Wed, 30 Jul 2025 00:19:16 +0500 Subject: [PATCH 4/5] fix: linux32 build --- dev-tools/cc-test/build.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/dev-tools/cc-test/build.rs b/dev-tools/cc-test/build.rs index 1b4bef858..5a8eac808 100644 --- a/dev-tools/cc-test/build.rs +++ b/dev-tools/cc-test/build.rs @@ -182,16 +182,17 @@ fn main() { // `libstdc++.so`, but if we emit `cargo:rustc-link-lib=static=stdc++`, it will // not be able to find `libstdc++.a` file despite it almost always located next to // `libstdc++.so`. So providing explicit `rustc-link-search` solves the error - let libstdc_path: PathBuf = String::from_utf8( - Command::new("g++") - .args(["--print-file-name=libstdc++.a"]) - .output() - .expect("Failed to run g++") - .stdout, - ) - .unwrap() - .trim() - .into(); + + let mut cmd = Command::new("g++"); + cmd.arg("--print-file-name=libstdc++.a"); + if arch == "i686" { + cmd.arg("-m32"); + } + let libstdc_path: PathBuf = + String::from_utf8(cmd.output().expect("Failed to run g++").stdout) + .unwrap() + .trim() + .into(); let out_stdlib = out_dir.join("libstdc++.a"); std::os::unix::fs::symlink(libstdc_path, out_stdlib).unwrap(); From 16cd7b4024e11b68c6a3ec2709a7c1b8acf2e69e Mon Sep 17 00:00:00 2001 From: lisovskiy Date: Wed, 30 Jul 2025 00:28:25 +0500 Subject: [PATCH 5/5] fix comment --- dev-tools/cc-test/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-tools/cc-test/build.rs b/dev-tools/cc-test/build.rs index 5a8eac808..10422d6f1 100644 --- a/dev-tools/cc-test/build.rs +++ b/dev-tools/cc-test/build.rs @@ -181,7 +181,7 @@ fn main() { // it doesn't require any additional steps to link against system // `libstdc++.so`, but if we emit `cargo:rustc-link-lib=static=stdc++`, it will // not be able to find `libstdc++.a` file despite it almost always located next to - // `libstdc++.so`. So providing explicit `rustc-link-search` solves the error + // `libstdc++.so`. So symlinking to OUT dir solves the problem let mut cmd = Command::new("g++"); cmd.arg("--print-file-name=libstdc++.a");