Skip to content

Commit 20c06cf

Browse files
authored
Proposal: ability to statically link against C++ stdlib (#1497)
1 parent e435ac9 commit 20c06cf

File tree

2 files changed

+71
-10
lines changed

2 files changed

+71
-10
lines changed

dev-tools/cc-test/build.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,24 @@ fn main() {
1212
return;
1313
}
1414

15-
let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
16-
fs::remove_dir_all(&out).unwrap();
17-
fs::create_dir(&out).unwrap();
15+
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
16+
fs::remove_dir_all(&out_dir).unwrap();
17+
fs::create_dir(&out_dir).unwrap();
1818

1919
// The following are builds where we want to capture the output (i.e. stdout and
2020
// stderr). We do that by re-running _this_ executable and passing in the
2121
// action as the first argument.
22-
run_forked_capture_output(&out, "metadata-on");
23-
run_forked_capture_output(&out, "metadata-off");
22+
run_forked_capture_output(&out_dir, "metadata-on");
23+
run_forked_capture_output(&out_dir, "metadata-off");
2424

25-
run_forked_capture_output(&out, "warnings-off");
25+
run_forked_capture_output(&out_dir, "warnings-off");
2626
if cc::Build::new().get_compiler().is_like_msvc() {
2727
// MSVC doesn't output warnings to stderr, so we can't capture them.
2828
// the test will use this env var to know whether to run the test.
2929
println!("cargo:rustc-env=TEST_WARNINGS_ON=0");
3030
} else {
3131
println!("cargo:rustc-env=TEST_WARNINGS_ON=1");
32-
run_forked_capture_output(&out, "warnings-on");
32+
run_forked_capture_output(&out_dir, "warnings-on");
3333
}
3434

3535
let mut build = cc::Build::new();
@@ -104,7 +104,7 @@ fn main() {
104104
// Test that the `windows_registry` module will set PATH by looking for
105105
// nmake which runs vanilla cl, and then also test it after we remove all
106106
// the relevant env vars from our own process.
107-
let out = out.join("tmp");
107+
let out = out_dir.join("tmp");
108108
fs::create_dir(&out).unwrap();
109109
println!("nmake 1");
110110
let status = cc::windows_registry::find(&target, "nmake.exe")
@@ -173,6 +173,37 @@ fn main() {
173173
let out = cc::Build::new().file("src/expand.c").expand();
174174
let out = String::from_utf8(out).unwrap();
175175
assert!(out.contains("hello world"));
176+
177+
// Test static linking of stdc++ on Linux
178+
#[cfg(target_os = "linux")]
179+
{
180+
// Rust linker has no problem linking against dynamic libraries, for instance
181+
// it doesn't require any additional steps to link against system
182+
// `libstdc++.so`, but if we emit `cargo:rustc-link-lib=static=stdc++`, it will
183+
// not be able to find `libstdc++.a` file despite it almost always located next to
184+
// `libstdc++.so`. So symlinking to OUT dir solves the problem
185+
186+
let mut cmd = Command::new("g++");
187+
cmd.arg("--print-file-name=libstdc++.a");
188+
if arch == "i686" {
189+
cmd.arg("-m32");
190+
}
191+
let libstdc_path: PathBuf =
192+
String::from_utf8(cmd.output().expect("Failed to run g++").stdout)
193+
.unwrap()
194+
.trim()
195+
.into();
196+
197+
let out_stdlib = out_dir.join("libstdc++.a");
198+
std::os::unix::fs::symlink(libstdc_path, out_stdlib).unwrap();
199+
200+
cc::Build::new()
201+
.file("src/baz.cpp")
202+
.cpp(true)
203+
.cpp_link_stdlib("stdc++")
204+
.cpp_link_stdlib_static(true)
205+
.compile("baz");
206+
}
176207
}
177208

178209
#[track_caller]

src/lib.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ pub struct Build {
314314
files: Vec<Arc<Path>>,
315315
cpp: bool,
316316
cpp_link_stdlib: Option<Option<Arc<str>>>,
317+
cpp_link_stdlib_static: bool,
317318
cpp_set_stdlib: Option<Arc<str>>,
318319
cuda: bool,
319320
cudart: Option<Arc<str>>,
@@ -446,6 +447,7 @@ impl Build {
446447
static_flag: None,
447448
cpp: false,
448449
cpp_link_stdlib: None,
450+
cpp_link_stdlib_static: false,
449451
cpp_set_stdlib: None,
450452
cuda: false,
451453
cudart: None,
@@ -965,6 +967,27 @@ impl Build {
965967
self
966968
}
967969

970+
/// Force linker to statically link C++ stdlib. By default cc-rs will emit
971+
/// rustc-link flag to link against system C++ stdlib (e.g. libstdc++.so, libc++.so)
972+
/// Provide value of `true` if linking against system library is not desired
973+
///
974+
/// Note that for `wasm32` target C++ stdlib will always be linked statically
975+
///
976+
/// # Example
977+
///
978+
/// ```no_run
979+
/// cc::Build::new()
980+
/// .file("src/foo.cpp")
981+
/// .cpp(true)
982+
/// .cpp_link_stdlib("stdc++")
983+
/// .cpp_link_stdlib_static(true)
984+
/// .compile("foo");
985+
/// ```
986+
pub fn cpp_link_stdlib_static(&mut self, is_static: bool) -> &mut Build {
987+
self.cpp_link_stdlib_static = is_static;
988+
self
989+
}
990+
968991
/// Force the C++ compiler to use the specified standard library.
969992
///
970993
/// Setting this option will automatically set `cpp_link_stdlib` to the same
@@ -1496,8 +1519,15 @@ impl Build {
14961519
// Add specific C++ libraries, if enabled.
14971520
if self.cpp {
14981521
if let Some(stdlib) = self.get_cpp_link_stdlib()? {
1499-
self.cargo_output
1500-
.print_metadata(&format_args!("cargo:rustc-link-lib={}", stdlib.display()));
1522+
if self.cpp_link_stdlib_static {
1523+
self.cargo_output.print_metadata(&format_args!(
1524+
"cargo:rustc-link-lib=static={}",
1525+
stdlib.display()
1526+
));
1527+
} else {
1528+
self.cargo_output
1529+
.print_metadata(&format_args!("cargo:rustc-link-lib={}", stdlib.display()));
1530+
}
15011531
}
15021532
// Link c++ lib from WASI sysroot
15031533
if target.arch == "wasm32" {

0 commit comments

Comments
 (0)