Skip to content

Commit 09f1a28

Browse files
authored
Surface the failing command's name from the build error dump (#253)
* Display more relevant code location on failure * Prominently display name of the failing command * Expose fallible try_build * Remove panics * Exit directly instead of panicking
1 parent 0c286ba commit 09f1a28

File tree

1 file changed

+115
-75
lines changed

1 file changed

+115
-75
lines changed

src/lib.rs

+115-75
Original file line numberDiff line numberDiff line change
@@ -60,44 +60,47 @@ impl Build {
6060
self
6161
}
6262

63-
fn cmd_make(&self) -> Command {
64-
let host = &self.host.as_ref().expect("HOST dir not set")[..];
65-
if host.contains("dragonfly")
66-
|| host.contains("freebsd")
67-
|| host.contains("openbsd")
68-
|| host.contains("solaris")
69-
|| host.contains("illumos")
70-
{
71-
Command::new("gmake")
72-
} else {
73-
Command::new("make")
74-
}
63+
fn cmd_make(&self) -> Result<Command, &'static str> {
64+
let host = &self.host.as_ref().ok_or("HOST dir not set")?[..];
65+
Ok(
66+
if host.contains("dragonfly")
67+
|| host.contains("freebsd")
68+
|| host.contains("openbsd")
69+
|| host.contains("solaris")
70+
|| host.contains("illumos")
71+
{
72+
Command::new("gmake")
73+
} else {
74+
Command::new("make")
75+
},
76+
)
7577
}
7678

7779
#[cfg(windows)]
7880
fn check_env_var(&self, var_name: &str) -> Option<bool> {
79-
env::var_os(var_name).map(|s| {
81+
env::var_os(var_name).and_then(|s| {
8082
if s == "1" {
8183
// a message to stdout, let user know asm is force enabled
8284
println!(
83-
"{}: nasm.exe is force enabled by the \
85+
"cargo:warning={}: nasm.exe is force enabled by the \
8486
'OPENSSL_RUST_USE_NASM' env var.",
8587
env!("CARGO_PKG_NAME")
8688
);
87-
true
89+
Some(true)
8890
} else if s == "0" {
8991
// a message to stdout, let user know asm is force disabled
9092
println!(
91-
"{}: nasm.exe is force disabled by the \
93+
"cargo:warning={}: nasm.exe is force disabled by the \
9294
'OPENSSL_RUST_USE_NASM' env var.",
9395
env!("CARGO_PKG_NAME")
9496
);
95-
false
97+
Some(false)
9698
} else {
97-
panic!(
98-
"The environment variable {} is set to an unacceptable value: {:?}",
99+
println!(
100+
"cargo:warning=The environment variable {} is set to an unacceptable value: {:?}",
99101
var_name, s
100102
);
103+
None
101104
}
102105
})
103106
}
@@ -107,11 +110,11 @@ impl Build {
107110
self.check_env_var("OPENSSL_RUST_USE_NASM")
108111
.unwrap_or_else(|| {
109112
// On Windows, use cmd `where` command to check if nasm is installed
110-
let wherenasm = Command::new("cmd")
113+
Command::new("cmd")
111114
.args(&["/C", "where nasm"])
112115
.output()
113-
.expect("Failed to execute `cmd`.");
114-
wherenasm.status.success()
116+
.map(|w| w.status.success())
117+
.unwrap_or(false)
115118
})
116119
}
117120

@@ -121,23 +124,35 @@ impl Build {
121124
false
122125
}
123126

127+
/// Exits the process on failure. Use `try_build` to handle the error.
124128
pub fn build(&mut self) -> Artifacts {
125-
let target = &self.target.as_ref().expect("TARGET dir not set")[..];
126-
let host = &self.host.as_ref().expect("HOST dir not set")[..];
127-
let out_dir = self.out_dir.as_ref().expect("OUT_DIR not set");
129+
match self.try_build() {
130+
Ok(a) => a,
131+
Err(e) => {
132+
println!("cargo:warning=openssl-src: failed to build OpenSSL from source");
133+
eprintln!("\n\n\n{e}\n\n\n");
134+
std::process::exit(1);
135+
}
136+
}
137+
}
138+
139+
pub fn try_build(&mut self) -> Result<Artifacts, String> {
140+
let target = &self.target.as_ref().ok_or("TARGET dir not set")?[..];
141+
let host = &self.host.as_ref().ok_or("HOST dir not set")?[..];
142+
let out_dir = self.out_dir.as_ref().ok_or("OUT_DIR not set")?;
128143
let build_dir = out_dir.join("build");
129144
let install_dir = out_dir.join("install");
130145

131146
if build_dir.exists() {
132-
fs::remove_dir_all(&build_dir).unwrap();
147+
fs::remove_dir_all(&build_dir).map_err(|e| format!("build_dir: {e}"))?;
133148
}
134149
if install_dir.exists() {
135-
fs::remove_dir_all(&install_dir).unwrap();
150+
fs::remove_dir_all(&install_dir).map_err(|e| format!("install_dir: {e}"))?;
136151
}
137152

138153
let inner_dir = build_dir.join("src");
139-
fs::create_dir_all(&inner_dir).unwrap();
140-
cp_r(&source_dir(), &inner_dir);
154+
fs::create_dir_all(&inner_dir).map_err(|e| format!("{}: {e}", inner_dir.display()))?;
155+
cp_r(&source_dir(), &inner_dir)?;
141156

142157
let perl_program =
143158
env::var("OPENSSL_SRC_PERL").unwrap_or(env::var("PERL").unwrap_or("perl".to_string()));
@@ -154,7 +169,10 @@ impl Build {
154169
// native and cross builds.
155170
configure.arg(&format!(
156171
"--prefix={}",
157-
install_dir.to_str().unwrap().replace("\\", "/")
172+
install_dir
173+
.to_str()
174+
.ok_or("bad install_dir")?
175+
.replace("\\", "/")
158176
));
159177
} else {
160178
configure.arg(&format!("--prefix={}", install_dir.display()));
@@ -170,7 +188,7 @@ impl Build {
170188
let openssl_dir = self
171189
.openssl_dir
172190
.as_ref()
173-
.expect("path to the openssl directory must be set");
191+
.ok_or("path to the openssl directory must be set")?;
174192
let mut dir_arg: OsString = "--openssldir=".into();
175193
dir_arg.push(openssl_dir);
176194
configure.arg(dir_arg);
@@ -391,7 +409,12 @@ impl Build {
391409
"aarch64-unknown-linux-ohos" => "linux-aarch64",
392410
"armv7-unknown-linux-ohos" => "linux-generic32",
393411
"x86_64-unknown-linux-ohos" => "linux-x86_64",
394-
_ => panic!("don't know how to configure OpenSSL for {}", target),
412+
_ => {
413+
return Err(format!(
414+
"don't know how to configure OpenSSL for {}",
415+
target
416+
))
417+
}
395418
};
396419

397420
let mut ios_isysroot: std::option::Option<String> = None;
@@ -410,7 +433,7 @@ impl Build {
410433
cc_env = compiler.path().to_path_buf().into_os_string();
411434
}
412435
configure.env("CC", cc_env);
413-
let path = compiler.path().to_str().unwrap();
436+
let path = compiler.path().to_str().ok_or("compiler path")?;
414437

415438
// Both `cc::Build` and `./Configure` take into account
416439
// `CROSS_COMPILE` environment variable. So to avoid double
@@ -464,7 +487,7 @@ impl Build {
464487

465488
if is_isysroot {
466489
is_isysroot = false;
467-
ios_isysroot = Some(arg.to_str().unwrap().to_string());
490+
ios_isysroot = Some(arg.to_str().ok_or("isysroot arg")?.to_string());
468491
continue;
469492
}
470493
}
@@ -574,26 +597,26 @@ impl Build {
574597

575598
// And finally, run the perl configure script!
576599
configure.current_dir(&inner_dir);
577-
self.run_command(configure, "configuring OpenSSL build");
600+
self.run_command(configure, "configuring OpenSSL build")?;
578601

579602
// On MSVC we use `nmake.exe` with a slightly different invocation, so
580603
// have that take a different path than the standard `make` below.
581604
if target.contains("msvc") {
582605
let mut build =
583-
cc::windows_registry::find(target, "nmake.exe").expect("failed to find nmake");
606+
cc::windows_registry::find(target, "nmake.exe").ok_or("failed to find nmake")?;
584607
build.arg("build_libs").current_dir(&inner_dir);
585-
self.run_command(build, "building OpenSSL");
608+
self.run_command(build, "building OpenSSL")?;
586609

587610
let mut install =
588-
cc::windows_registry::find(target, "nmake.exe").expect("failed to find nmake");
611+
cc::windows_registry::find(target, "nmake.exe").ok_or("failed to find nmake")?;
589612
install.arg("install_dev").current_dir(&inner_dir);
590-
self.run_command(install, "installing OpenSSL");
613+
self.run_command(install, "installing OpenSSL")?;
591614
} else {
592-
let mut depend = self.cmd_make();
615+
let mut depend = self.cmd_make()?;
593616
depend.arg("depend").current_dir(&inner_dir);
594-
self.run_command(depend, "building OpenSSL dependencies");
617+
self.run_command(depend, "building OpenSSL dependencies")?;
595618

596-
let mut build = self.cmd_make();
619+
let mut build = self.cmd_make()?;
597620
build.arg("build_libs").current_dir(&inner_dir);
598621
if !cfg!(windows) {
599622
if let Some(s) = env::var_os("CARGO_MAKEFLAGS") {
@@ -607,11 +630,11 @@ impl Build {
607630
build.env("CROSS_SDK", components[1]);
608631
}
609632

610-
self.run_command(build, "building OpenSSL");
633+
self.run_command(build, "building OpenSSL")?;
611634

612-
let mut install = self.cmd_make();
635+
let mut install = self.cmd_make()?;
613636
install.arg("install_dev").current_dir(&inner_dir);
614-
self.run_command(install, "installing OpenSSL");
637+
self.run_command(install, "installing OpenSSL")?;
615638
}
616639

617640
let libs = if target.contains("msvc") {
@@ -620,46 +643,58 @@ impl Build {
620643
vec!["ssl".to_string(), "crypto".to_string()]
621644
};
622645

623-
fs::remove_dir_all(&inner_dir).unwrap();
646+
fs::remove_dir_all(&inner_dir).map_err(|e| format!("{}: {e}", inner_dir.display()))?;
624647

625-
Artifacts {
648+
Ok(Artifacts {
626649
lib_dir: install_dir.join("lib"),
627650
bin_dir: install_dir.join("bin"),
628651
include_dir: install_dir.join("include"),
629652
libs: libs,
630653
target: target.to_string(),
631-
}
654+
})
632655
}
633656

634-
fn run_command(&self, mut command: Command, desc: &str) {
657+
#[track_caller]
658+
fn run_command(&self, mut command: Command, desc: &str) -> Result<(), String> {
635659
println!("running {:?}", command);
636660
let status = command.status();
637661

638-
let (status_or_failed, error) = match status {
639-
Ok(status) if status.success() => return,
640-
Ok(status) => ("Exit status", format!("{}", status)),
641-
Err(failed) => ("Failed to execute", format!("{}", failed)),
662+
let verbose_error = match status {
663+
Ok(status) if status.success() => return Ok(()),
664+
Ok(status) => format!(
665+
"'{exe}' reported failure with {status}",
666+
exe = command.get_program().to_string_lossy()
667+
),
668+
Err(failed) => match failed.kind() {
669+
std::io::ErrorKind::NotFound => format!(
670+
"Command '{exe}' not found. Is {exe} installed?",
671+
exe = command.get_program().to_string_lossy()
672+
),
673+
_ => format!(
674+
"Could not run '{exe}', because {failed}",
675+
exe = command.get_program().to_string_lossy()
676+
),
677+
},
642678
};
643-
panic!(
644-
"
645-
646-
647-
Error {}:
648-
Command: {:?}
649-
{}: {}
650-
651-
652-
",
653-
desc, command, status_or_failed, error
654-
);
679+
println!("cargo:warning={desc}: {verbose_error}");
680+
Err(format!(
681+
"Error {desc}:
682+
{verbose_error}
683+
Command failed: {command:?}"
684+
))
655685
}
656686
}
657687

658-
fn cp_r(src: &Path, dst: &Path) {
659-
for f in fs::read_dir(src).unwrap() {
660-
let f = f.unwrap();
688+
fn cp_r(src: &Path, dst: &Path) -> Result<(), String> {
689+
for f in fs::read_dir(src).map_err(|e| format!("{}: {e}", src.display()))? {
690+
let f = match f {
691+
Ok(f) => f,
692+
_ => continue,
693+
};
661694
let path = f.path();
662-
let name = path.file_name().unwrap();
695+
let name = path
696+
.file_name()
697+
.ok_or_else(|| format!("bad dir {}", src.display()))?;
663698

664699
// Skip git metadata as it's been known to cause issues (#26) and
665700
// otherwise shouldn't be required
@@ -668,27 +703,32 @@ fn cp_r(src: &Path, dst: &Path) {
668703
}
669704

670705
let dst = dst.join(name);
671-
let ty = f.file_type().unwrap();
706+
let ty = f.file_type().map_err(|e| e.to_string())?;
672707
if ty.is_dir() {
673-
fs::create_dir_all(&dst).unwrap();
674-
cp_r(&path, &dst);
708+
fs::create_dir_all(&dst).map_err(|e| e.to_string())?;
709+
cp_r(&path, &dst)?;
675710
} else if ty.is_symlink() && path.iter().any(|p| p == "cloudflare-quiche") {
676711
// not needed to build
677712
continue;
678713
} else {
679714
let _ = fs::remove_file(&dst);
680715
if let Err(e) = fs::copy(&path, &dst) {
681-
panic!("failed to copy {path:?} to {dst:?}: {e}");
716+
return Err(format!(
717+
"failed to copy '{}' to '{}': {e}",
718+
path.display(),
719+
dst.display()
720+
));
682721
}
683722
}
684723
}
724+
Ok(())
685725
}
686726

687727
fn sanitize_sh(path: &Path) -> String {
688728
if !cfg!(windows) {
689-
return path.to_str().unwrap().to_string();
729+
return path.to_string_lossy().into_owned();
690730
}
691-
let path = path.to_str().unwrap().replace("\\", "/");
731+
let path = path.to_string_lossy().replace("\\", "/");
692732
return change_drive(&path).unwrap_or(path);
693733

694734
fn change_drive(s: &str) -> Option<String> {

0 commit comments

Comments
 (0)