Skip to content

Commit 3f77322

Browse files
author
Jon Gjengset
authored
Expose get_archiver and get_ranlib (#763)
1 parent cf0a78b commit 3f77322

File tree

1 file changed

+221
-74
lines changed

1 file changed

+221
-74
lines changed

src/lib.rs

+221-74
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ pub struct Build {
115115
env: Vec<(OsString, OsString)>,
116116
compiler: Option<PathBuf>,
117117
archiver: Option<PathBuf>,
118+
ranlib: Option<PathBuf>,
118119
cargo_metadata: bool,
119120
link_lib_modifiers: Vec<String>,
120121
pic: Option<bool>,
@@ -320,6 +321,7 @@ impl Build {
320321
env: Vec::new(),
321322
compiler: None,
322323
archiver: None,
324+
ranlib: None,
323325
cargo_metadata: true,
324326
link_lib_modifiers: Vec::new(),
325327
pic: None,
@@ -916,6 +918,17 @@ impl Build {
916918
self.archiver = Some(archiver.as_ref().to_owned());
917919
self
918920
}
921+
922+
/// Configures the tool used to index archives.
923+
///
924+
/// This option is automatically determined from the target platform or a
925+
/// number of environment variables, so it's not required to call this
926+
/// function.
927+
pub fn ranlib<P: AsRef<Path>>(&mut self, ranlib: P) -> &mut Build {
928+
self.ranlib = Some(ranlib.as_ref().to_owned());
929+
self
930+
}
931+
919932
/// Define whether metadata should be emitted for cargo allowing it to
920933
/// automatically link the binary. Defaults to `true`.
921934
///
@@ -2094,7 +2107,11 @@ impl Build {
20942107
// Non-msvc targets (those using `ar`) need a separate step to add
20952108
// the symbol table to archives since our construction command of
20962109
// `cq` doesn't add it for us.
2097-
let (mut ar, cmd) = self.get_ar()?;
2110+
let (mut ar, cmd, _any_flags) = self.get_ar()?;
2111+
2112+
// NOTE: We add `s` even if flags were passed using $ARFLAGS/ar_flag, because `s`
2113+
// here represents a _mode_, not an arbitrary flag. Further discussion of this choice
2114+
// can be seen in https://github.com/rust-lang/cc-rs/pull/763.
20982115
run(ar.arg("s").arg(dst), &cmd)?;
20992116
}
21002117

@@ -2105,12 +2122,16 @@ impl Build {
21052122
let target = self.get_target()?;
21062123

21072124
if target.contains("msvc") {
2108-
let (mut cmd, program) = self.get_ar()?;
2125+
let (mut cmd, program, any_flags) = self.get_ar()?;
2126+
// NOTE: -out: here is an I/O flag, and so must be included even if $ARFLAGS/ar_flag is
2127+
// in use. -nologo on the other hand is just a regular flag, and one that we'll skip if
2128+
// the caller has explicitly dictated the flags they want. See
2129+
// https://github.com/rust-lang/cc-rs/pull/763 for further discussion.
21092130
let mut out = OsString::from("-out:");
21102131
out.push(dst);
2111-
cmd.arg(out).arg("-nologo");
2112-
for flag in self.ar_flags.iter() {
2113-
cmd.arg(flag);
2132+
cmd.arg(out);
2133+
if !any_flags {
2134+
cmd.arg("-nologo");
21142135
}
21152136
// If the library file already exists, add the library name
21162137
// as an argument to let lib.exe know we are appending the objs.
@@ -2120,7 +2141,7 @@ impl Build {
21202141
cmd.args(objs);
21212142
run(&mut cmd, &program)?;
21222143
} else {
2123-
let (mut ar, cmd) = self.get_ar()?;
2144+
let (mut ar, cmd, _any_flags) = self.get_ar()?;
21242145

21252146
// Set an environment variable to tell the OSX archiver to ensure
21262147
// that all dates listed in the archive are zero, improving
@@ -2145,9 +2166,10 @@ impl Build {
21452166
// In any case if this doesn't end up getting read, it shouldn't
21462167
// cause that many issues!
21472168
ar.env("ZERO_AR_DATE", "1");
2148-
for flag in self.ar_flags.iter() {
2149-
ar.arg(flag);
2150-
}
2169+
2170+
// NOTE: We add cq here regardless of whether $ARFLAGS/ar_flag have been used because
2171+
// it dictates the _mode_ ar runs in, which the setter of $ARFLAGS/ar_flag can't
2172+
// dictate. See https://github.com/rust-lang/cc-rs/pull/763 for further discussion.
21512173
run(ar.arg("cq").arg(dst).args(objs), &cmd)?;
21522174
}
21532175

@@ -2639,81 +2661,206 @@ impl Build {
26392661
}
26402662
}
26412663

2642-
fn get_ar(&self) -> Result<(Command, String), Error> {
2643-
if let Some(ref p) = self.archiver {
2644-
let name = p.file_name().and_then(|s| s.to_str()).unwrap_or("ar");
2645-
return Ok((self.cmd(p), name.to_string()));
2664+
fn get_ar(&self) -> Result<(Command, String, bool), Error> {
2665+
self.try_get_archiver_and_flags()
2666+
}
2667+
2668+
/// Get the archiver (ar) that's in use for this configuration.
2669+
///
2670+
/// You can use [`Command::get_program`] to get just the path to the command.
2671+
///
2672+
/// This method will take into account all configuration such as debug
2673+
/// information, optimization level, include directories, defines, etc.
2674+
/// Additionally, the compiler binary in use follows the standard
2675+
/// conventions for this path, e.g. looking at the explicitly set compiler,
2676+
/// environment variables (a number of which are inspected here), and then
2677+
/// falling back to the default configuration.
2678+
///
2679+
/// # Panics
2680+
///
2681+
/// Panics if an error occurred while determining the architecture.
2682+
pub fn get_archiver(&self) -> Command {
2683+
match self.try_get_archiver() {
2684+
Ok(tool) => tool,
2685+
Err(e) => fail(&e.message),
2686+
}
2687+
}
2688+
2689+
/// Get the archiver that's in use for this configuration.
2690+
///
2691+
/// This will return a result instead of panicing;
2692+
/// see [`get_archiver()`] for the complete description.
2693+
pub fn try_get_archiver(&self) -> Result<Command, Error> {
2694+
Ok(self.try_get_archiver_and_flags()?.0)
2695+
}
2696+
2697+
fn try_get_archiver_and_flags(&self) -> Result<(Command, String, bool), Error> {
2698+
let (mut cmd, name) = self.get_base_archiver()?;
2699+
let flags = self.envflags("ARFLAGS");
2700+
let mut any_flags = !flags.is_empty();
2701+
cmd.args(flags);
2702+
for flag in &self.ar_flags {
2703+
any_flags = true;
2704+
cmd.arg(flag);
26462705
}
2647-
if let Ok(p) = self.get_var("AR") {
2648-
return Ok((self.cmd(&p), p));
2706+
Ok((cmd, name, any_flags))
2707+
}
2708+
2709+
fn get_base_archiver(&self) -> Result<(Command, String), Error> {
2710+
if let Some(ref a) = self.archiver {
2711+
return Ok((self.cmd(a), a.to_string_lossy().into_owned()));
26492712
}
2650-
let target = self.get_target()?;
2651-
let default_ar = "ar".to_string();
2652-
let program = if target.contains("android") {
2653-
format!("{}-ar", target.replace("armv7", "arm"))
2654-
} else if target.contains("emscripten") {
2655-
// Windows use bat files so we have to be a bit more specific
2656-
if cfg!(windows) {
2657-
let mut cmd = self.cmd("cmd");
2658-
cmd.arg("/c").arg("emar.bat");
2659-
return Ok((cmd, "emar.bat".to_string()));
2660-
}
26612713

2662-
"emar".to_string()
2663-
} else if target.contains("msvc") {
2664-
let compiler = self.get_base_compiler()?;
2665-
let mut lib = String::new();
2666-
if compiler.family == (ToolFamily::Msvc { clang_cl: true }) {
2667-
// See if there is 'llvm-lib' next to 'clang-cl'
2668-
// Another possibility could be to see if there is 'clang'
2669-
// next to 'clang-cl' and use 'search_programs()' to locate
2670-
// 'llvm-lib'. This is because 'clang-cl' doesn't support
2671-
// the -print-search-dirs option.
2672-
if let Some(mut cmd) = which(&compiler.path) {
2673-
cmd.pop();
2674-
cmd.push("llvm-lib.exe");
2675-
if let Some(llvm_lib) = which(&cmd) {
2676-
lib = llvm_lib.to_str().unwrap().to_owned();
2714+
self.get_base_archiver_variant("AR", "ar")
2715+
}
2716+
2717+
/// Get the ranlib that's in use for this configuration.
2718+
///
2719+
/// You can use [`Command::get_program`] to get just the path to the command.
2720+
///
2721+
/// This method will take into account all configuration such as debug
2722+
/// information, optimization level, include directories, defines, etc.
2723+
/// Additionally, the compiler binary in use follows the standard
2724+
/// conventions for this path, e.g. looking at the explicitly set compiler,
2725+
/// environment variables (a number of which are inspected here), and then
2726+
/// falling back to the default configuration.
2727+
///
2728+
/// # Panics
2729+
///
2730+
/// Panics if an error occurred while determining the architecture.
2731+
pub fn get_ranlib(&self) -> Command {
2732+
match self.try_get_ranlib() {
2733+
Ok(tool) => tool,
2734+
Err(e) => fail(&e.message),
2735+
}
2736+
}
2737+
2738+
/// Get the ranlib that's in use for this configuration.
2739+
///
2740+
/// This will return a result instead of panicing;
2741+
/// see [`get_ranlib()`] for the complete description.
2742+
pub fn try_get_ranlib(&self) -> Result<Command, Error> {
2743+
let mut cmd = self.get_base_ranlib()?;
2744+
cmd.args(self.envflags("RANLIBFLAGS"));
2745+
Ok(cmd)
2746+
}
2747+
2748+
fn get_base_ranlib(&self) -> Result<Command, Error> {
2749+
if let Some(ref r) = self.ranlib {
2750+
return Ok(self.cmd(r));
2751+
}
2752+
2753+
Ok(self.get_base_archiver_variant("RANLIB", "ranlib")?.0)
2754+
}
2755+
2756+
fn get_base_archiver_variant(&self, env: &str, tool: &str) -> Result<(Command, String), Error> {
2757+
let target = self.get_target()?;
2758+
let mut name = String::new();
2759+
let tool_opt: Option<Command> = self
2760+
.env_tool(env)
2761+
.map(|(tool, _wrapper, args)| {
2762+
let mut cmd = self.cmd(tool);
2763+
cmd.args(args);
2764+
cmd
2765+
})
2766+
.or_else(|| {
2767+
if target.contains("emscripten") {
2768+
// Windows use bat files so we have to be a bit more specific
2769+
if cfg!(windows) {
2770+
let mut cmd = self.cmd("cmd");
2771+
name = format!("em{}.bat", tool);
2772+
cmd.arg("/c").arg(&name);
2773+
Some(cmd)
2774+
} else {
2775+
name = format!("em{}", tool);
2776+
Some(self.cmd(&name))
26772777
}
2778+
} else {
2779+
None
26782780
}
2679-
}
2680-
if lib.is_empty() {
2681-
lib = match windows_registry::find(&target, "lib.exe") {
2682-
Some(t) => return Ok((t, "lib.exe".to_string())),
2683-
None => "lib.exe".to_string(),
2684-
}
2685-
}
2686-
lib
2687-
} else if target.contains("illumos") {
2688-
// The default 'ar' on illumos uses a non-standard flags,
2689-
// but the OS comes bundled with a GNU-compatible variant.
2690-
//
2691-
// Use the GNU-variant to match other Unix systems.
2692-
"gar".to_string()
2693-
} else if self.get_host()? != target {
2694-
match self.prefix_for_target(&target) {
2695-
Some(p) => {
2696-
// GCC uses $target-gcc-ar, whereas binutils uses $target-ar -- try both.
2697-
// Prefer -ar if it exists, as builds of `-gcc-ar` have been observed to be
2698-
// outright broken (such as when targetting freebsd with `--disable-lto`
2699-
// toolchain where the archiver attempts to load the LTO plugin anyway but
2700-
// fails to find one).
2701-
let mut ar = default_ar;
2702-
for &infix in &["", "-gcc"] {
2703-
let target_ar = format!("{}{}-ar", p, infix);
2704-
if Command::new(&target_ar).output().is_ok() {
2705-
ar = target_ar;
2706-
break;
2781+
});
2782+
2783+
let default = tool.to_string();
2784+
let tool = match tool_opt {
2785+
Some(t) => t,
2786+
None => {
2787+
if target.contains("android") {
2788+
name = format!("{}-{}", target.replace("armv7", "arm"), tool);
2789+
self.cmd(&name)
2790+
} else if target.contains("msvc") {
2791+
// NOTE: There isn't really a ranlib on msvc, so arguably we should return
2792+
// `None` somehow here. But in general, callers will already have to be aware
2793+
// of not running ranlib on Windows anyway, so it feels okay to return lib.exe
2794+
// here.
2795+
2796+
let compiler = self.get_base_compiler()?;
2797+
let mut lib = String::new();
2798+
if compiler.family == (ToolFamily::Msvc { clang_cl: true }) {
2799+
// See if there is 'llvm-lib' next to 'clang-cl'
2800+
// Another possibility could be to see if there is 'clang'
2801+
// next to 'clang-cl' and use 'search_programs()' to locate
2802+
// 'llvm-lib'. This is because 'clang-cl' doesn't support
2803+
// the -print-search-dirs option.
2804+
if let Some(mut cmd) = which(&compiler.path) {
2805+
cmd.pop();
2806+
cmd.push("llvm-lib.exe");
2807+
if let Some(llvm_lib) = which(&cmd) {
2808+
lib = llvm_lib.to_str().unwrap().to_owned();
2809+
}
2810+
}
2811+
}
2812+
2813+
if lib.is_empty() {
2814+
name = String::from("lib.exe");
2815+
match windows_registry::find(&target, "lib.exe") {
2816+
Some(t) => t,
2817+
None => self.cmd("lib.exe"),
2818+
}
2819+
} else {
2820+
name = lib;
2821+
self.cmd(&name)
2822+
}
2823+
} else if target.contains("illumos") {
2824+
// The default 'ar' on illumos uses a non-standard flags,
2825+
// but the OS comes bundled with a GNU-compatible variant.
2826+
//
2827+
// Use the GNU-variant to match other Unix systems.
2828+
name = format!("g{}", tool);
2829+
self.cmd(&name)
2830+
} else if self.get_host()? != target {
2831+
match self.prefix_for_target(&target) {
2832+
Some(p) => {
2833+
// GCC uses $target-gcc-ar, whereas binutils uses $target-ar -- try both.
2834+
// Prefer -ar if it exists, as builds of `-gcc-ar` have been observed to be
2835+
// outright broken (such as when targetting freebsd with `--disable-lto`
2836+
// toolchain where the archiver attempts to load the LTO plugin anyway but
2837+
// fails to find one).
2838+
//
2839+
// The same applies to ranlib.
2840+
let mut chosen = default;
2841+
for &infix in &["", "-gcc"] {
2842+
let target_p = format!("{}{}-{}", p, infix, tool);
2843+
if Command::new(&target_p).output().is_ok() {
2844+
chosen = target_p;
2845+
break;
2846+
}
2847+
}
2848+
name = chosen;
2849+
self.cmd(&name)
2850+
}
2851+
None => {
2852+
name = default;
2853+
self.cmd(&name)
27072854
}
27082855
}
2709-
ar
2856+
} else {
2857+
name = default;
2858+
self.cmd(&name)
27102859
}
2711-
None => default_ar,
27122860
}
2713-
} else {
2714-
default_ar
27152861
};
2716-
Ok((self.cmd(&program), program))
2862+
2863+
Ok((tool, name))
27172864
}
27182865

27192866
fn prefix_for_target(&self, target: &str) -> Option<String> {

0 commit comments

Comments
 (0)