diff --git a/src/command_helpers.rs b/src/command_helpers.rs index a34067f4..89dc1a33 100644 --- a/src/command_helpers.rs +++ b/src/command_helpers.rs @@ -46,7 +46,7 @@ impl CargoOutput { warnings: true, output: OutputKind::Forward, debug: match std::env::var_os("CC_ENABLE_DEBUG_OUTPUT") { - Some(v) => v != "0" && v != "false" && v != "", + Some(v) => v != "0" && v != "false" && !v.is_empty(), None => false, }, checked_dbg_var: Arc::new(AtomicBool::new(false)), diff --git a/src/flags.rs b/src/flags.rs index ed552e6e..91e9470a 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -185,7 +185,7 @@ impl<'this> RustcCodegenFlags<'this> { }; let clang_or_gnu = - matches!(family, ToolFamily::Clang { .. }) || matches!(family, ToolFamily::Gnu { .. }); + matches!(family, ToolFamily::Clang { .. }) || matches!(family, ToolFamily::Gnu); // Flags shared between clang and gnu if clang_or_gnu { @@ -315,7 +315,7 @@ impl<'this> RustcCodegenFlags<'this> { } } } - ToolFamily::Gnu { .. } => {} + ToolFamily::Gnu => {} ToolFamily::Msvc { .. } => { // https://learn.microsoft.com/en-us/cpp/build/reference/guard-enable-control-flow-guard if let Some(value) = self.control_flow_guard { diff --git a/src/lib.rs b/src/lib.rs index 85f15745..9359dff1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -332,6 +332,8 @@ pub struct Build { shell_escaped_flags: Option, build_cache: Arc, inherit_rustflags: bool, + link_shared_flag: bool, + shared_lib_out_dir: Option>, } /// Represents the types of errors that may occur while using cc-rs. @@ -459,6 +461,8 @@ impl Build { shell_escaped_flags: None, build_cache: Arc::default(), inherit_rustflags: true, + link_shared_flag: false, + shared_lib_out_dir: None, } } @@ -1227,6 +1231,22 @@ impl Build { self } + /// Configure whether cc should build dynamic library and link with `rustc-link-lib=dylib` + /// + /// This option defaults to `false`. + pub fn link_shared_flag(&mut self, link_shared_flag: bool) -> &mut Build { + self.link_shared_flag = link_shared_flag; + self + } + + /// Configures the output directory where shared library will be located. + /// + /// This option defaults to `out_dir`. + pub fn shared_lib_out_dir>(&mut self, out_dir: P) -> &mut Build { + self.shared_lib_out_dir = Some(out_dir.as_ref().into()); + self + } + #[doc(hidden)] pub fn __set_env(&mut self, a: A, b: B) -> &mut Build where @@ -1380,6 +1400,28 @@ impl Build { Ok(is_supported) } + /// Get canonical library names for `output` + fn get_canonical_library_names<'a>( + &self, + output: &'a str, + ) -> Result<(&'a str, String, String), Error> { + let lib_striped = output.strip_prefix("lib").unwrap_or(output); + let static_striped = lib_striped.strip_suffix(".a").unwrap_or(lib_striped); + + let dyn_ext = if self.get_target()?.env == "msvc" { + ".dll" + } else { + ".so" + }; + let lib_name = lib_striped.strip_suffix(dyn_ext).unwrap_or(static_striped); + + Ok(( + lib_name, + format!("lib{lib_name}.a"), + format!("lib{lib_name}{dyn_ext}"), + )) + } + /// Run the compiler, generating the file `output` /// /// This will return a result instead of panicking; see [`Self::compile()`] for @@ -1396,21 +1438,26 @@ impl Build { } } - let (lib_name, gnu_lib_name) = if output.starts_with("lib") && output.ends_with(".a") { - (&output[3..output.len() - 2], output.to_owned()) - } else { - let mut gnu = String::with_capacity(5 + output.len()); - gnu.push_str("lib"); - gnu.push_str(output); - gnu.push_str(".a"); - (output, gnu) - }; + let (lib_name, static_name, dynlib_name) = self.get_canonical_library_names(output)?; let dst = self.get_out_dir()?; let objects = objects_from_files(&self.files, &dst)?; self.compile_objects(&objects)?; - self.assemble(lib_name, &dst.join(gnu_lib_name), &objects)?; + + if self.link_shared_flag { + let objects = objects.iter().map(|o| o.dst.clone()).collect::>(); + + let mut cmd = self.try_get_compiler()?.to_command(); + let dynlib_path = dst.join(&dynlib_name); + cmd.args(["-shared", "-o"]).arg(&dynlib_path).args(&objects); + run(&mut cmd, &self.cargo_output)?; + if let Some(out_dir) = &self.shared_lib_out_dir { + fs::copy(dynlib_path, out_dir.join(&dynlib_name))?; + } + } else { + self.assemble(lib_name, &dst.join(static_name), &objects)?; + } let target = self.get_target()?; if target.env == "msvc" { @@ -1435,8 +1482,13 @@ impl Build { } if self.link_lib_modifiers.is_empty() { - self.cargo_output - .print_metadata(&format_args!("cargo:rustc-link-lib=static={}", lib_name)); + if self.link_shared_flag { + self.cargo_output + .print_metadata(&format_args!("cargo:rustc-link-lib=dylib={}", lib_name)); + } else { + self.cargo_output + .print_metadata(&format_args!("cargo:rustc-link-lib=static={}", lib_name)); + } } else { self.cargo_output.print_metadata(&format_args!( "cargo:rustc-link-lib=static:{}={}", diff --git a/src/target/parser.rs b/src/target/parser.rs index 931ad03e..25ec8195 100644 --- a/src/target/parser.rs +++ b/src/target/parser.rs @@ -477,13 +477,13 @@ mod tests { let (full_arch, _rest) = target.split_once('-').expect("target to have arch"); let mut target = TargetInfo { - full_arch: full_arch.into(), - arch: "invalid-none-set".into(), - vendor: "invalid-none-set".into(), - os: "invalid-none-set".into(), - env: "invalid-none-set".into(), + full_arch, + arch: "invalid-none-set", + vendor: "invalid-none-set", + os: "invalid-none-set", + env: "invalid-none-set", // Not set in older Rust versions - abi: "".into(), + abi: "", }; for cfg in cfgs.lines() { diff --git a/tests/cc_env.rs b/tests/cc_env.rs index 7eed9542..65015f78 100644 --- a/tests/cc_env.rs +++ b/tests/cc_env.rs @@ -117,7 +117,7 @@ fn clang_cl() { for exe_suffix in ["", ".exe"] { let test = Test::clang(); let bin = format!("clang{exe_suffix}"); - env::set_var("CC", &format!("{bin} --driver-mode=cl")); + env::set_var("CC", format!("{bin} --driver-mode=cl")); let test_compiler = |build: cc::Build| { let compiler = build.get_compiler(); assert_eq!(compiler.path(), Path::new(&*bin)); diff --git a/tests/test.rs b/tests/test.rs index d1c11e93..4e2642c1 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -354,6 +354,37 @@ fn gnu_shared() { test.cmd(0).must_have("-shared").must_not_have("-static"); } +#[test] +#[cfg(target_os = "linux")] +fn gnu_link_shared() { + use std::process::Command; + + let output = Command::new("rustc").arg("-vV").output().unwrap(); + let stdout_buf = String::from_utf8(output.stdout).unwrap(); + let target = stdout_buf + .lines() + .find_map(|l| l.strip_prefix("host: ")) + .unwrap(); + + reset_env(); + let test = Test::gnu(); + let root_dir = env!("CARGO_MANIFEST_DIR"); + let src = format!("{root_dir}/dev-tools/cc-test/src/foo.c"); + + cc::Build::new() + .host(target) + .target(target) + .opt_level(2) + .out_dir(test.td.path()) + .file(&src) + .shared_flag(true) + .static_flag(false) + .link_shared_flag(true) + .compile("foo"); + + assert!(test.td.path().join("libfoo.so").exists()); +} + #[test] fn gnu_flag_if_supported() { reset_env(); @@ -532,8 +563,8 @@ fn gnu_apple_sysroot() { test.shim("fake-gcc") .gcc() .compiler("fake-gcc") - .target(&target) - .host(&target) + .target(target) + .host(target) .file("foo.c") .compile("foo");