1 extern crate cc;
2
3 use std::env;
4 use std::fs;
5 use std::path::{Path, PathBuf};
6 use std::process::Command;
7
source_dir() -> PathBuf8 pub fn source_dir() -> PathBuf {
9 Path::new(env!("CARGO_MANIFEST_DIR")).join("openssl")
10 }
11
version() -> &'static str12 pub fn version() -> &'static str {
13 env!("CARGO_PKG_VERSION")
14 }
15
16 pub struct Build {
17 out_dir: Option<PathBuf>,
18 target: Option<String>,
19 host: Option<String>,
20 }
21
22 pub struct Artifacts {
23 include_dir: PathBuf,
24 lib_dir: PathBuf,
25 bin_dir: PathBuf,
26 libs: Vec<String>,
27 target: String,
28 }
29
30 impl Build {
new() -> Build31 pub fn new() -> Build {
32 Build {
33 out_dir: env::var_os("OUT_DIR").map(|s| PathBuf::from(s).join("openssl-build")),
34 target: env::var("TARGET").ok(),
35 host: env::var("HOST").ok(),
36 }
37 }
38
out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build39 pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
40 self.out_dir = Some(path.as_ref().to_path_buf());
41 self
42 }
43
target(&mut self, target: &str) -> &mut Build44 pub fn target(&mut self, target: &str) -> &mut Build {
45 self.target = Some(target.to_string());
46 self
47 }
48
host(&mut self, host: &str) -> &mut Build49 pub fn host(&mut self, host: &str) -> &mut Build {
50 self.host = Some(host.to_string());
51 self
52 }
53
cmd_make(&self) -> Command54 fn cmd_make(&self) -> Command {
55 let host = &self.host.as_ref().expect("HOST dir not set")[..];
56 if host.contains("dragonfly")
57 || host.contains("freebsd")
58 || host.contains("openbsd")
59 || host.contains("solaris")
60 || host.contains("illumos")
61 {
62 Command::new("gmake")
63 } else {
64 Command::new("make")
65 }
66 }
67
68 #[cfg(windows)]
check_env_var(&self, var_name: &str) -> Option<bool>69 fn check_env_var(&self, var_name: &str) -> Option<bool> {
70 env::var_os(var_name).map(|s| {
71 if s == "1" {
72 // a message to stdout, let user know asm is force enabled
73 println!(
74 "{}: nasm.exe is force enabled by the \
75 'OPENSSL_RUST_USE_NASM' env var.",
76 env!("CARGO_PKG_NAME")
77 );
78 true
79 } else if s == "0" {
80 // a message to stdout, let user know asm is force disabled
81 println!(
82 "{}: nasm.exe is force disabled by the \
83 'OPENSSL_RUST_USE_NASM' env var.",
84 env!("CARGO_PKG_NAME")
85 );
86 false
87 } else {
88 panic!(
89 "The environment variable {} is set to an unacceptable value: {:?}",
90 var_name, s
91 );
92 }
93 })
94 }
95
96 #[cfg(windows)]
is_nasm_ready(&self) -> bool97 fn is_nasm_ready(&self) -> bool {
98 self.check_env_var("OPENSSL_RUST_USE_NASM")
99 .unwrap_or_else(|| {
100 // On Windows, use cmd `where` command to check if nasm is installed
101 let wherenasm = Command::new("cmd")
102 .args(&["/C", "where nasm"])
103 .output()
104 .expect("Failed to execute `cmd`.");
105 wherenasm.status.success()
106 })
107 }
108
109 #[cfg(not(windows))]
is_nasm_ready(&self) -> bool110 fn is_nasm_ready(&self) -> bool {
111 // We assume that nobody would run nasm.exe on a non-windows system.
112 false
113 }
114
build(&mut self) -> Artifacts115 pub fn build(&mut self) -> Artifacts {
116 let target = &self.target.as_ref().expect("TARGET dir not set")[..];
117 let host = &self.host.as_ref().expect("HOST dir not set")[..];
118 let out_dir = self.out_dir.as_ref().expect("OUT_DIR not set");
119 let build_dir = out_dir.join("build");
120 let install_dir = out_dir.join("install");
121
122 if build_dir.exists() {
123 fs::remove_dir_all(&build_dir).unwrap();
124 }
125 if install_dir.exists() {
126 fs::remove_dir_all(&install_dir).unwrap();
127 }
128
129 let inner_dir = build_dir.join("src");
130 fs::create_dir_all(&inner_dir).unwrap();
131 cp_r(&source_dir(), &inner_dir);
132
133 let perl_program =
134 env::var("OPENSSL_SRC_PERL").unwrap_or(env::var("PERL").unwrap_or("perl".to_string()));
135 let mut configure = Command::new(perl_program);
136 configure.arg("./Configure");
137 if host.contains("pc-windows-gnu") {
138 configure.arg(&format!("--prefix={}", sanitize_sh(&install_dir)));
139 } else {
140 configure.arg(&format!("--prefix={}", install_dir.display()));
141 }
142
143 configure
144 // No shared objects, we just want static libraries
145 .arg("no-dso")
146 .arg("no-shared")
147 // Should be off by default on OpenSSL 1.1.0, but let's be extra sure
148 .arg("no-ssl3")
149 // No need to build tests, we won't run them anyway
150 .arg("no-tests")
151 // Nothing related to zlib please
152 .arg("no-comp")
153 .arg("no-zlib")
154 .arg("no-zlib-dynamic")
155 // Avoid multilib-postfix for build targets that specify it
156 .arg("--libdir=lib")
157 // No support for multiple providers yet
158 .arg("no-legacy");
159
160 if cfg!(not(feature = "weak-crypto")) {
161 configure
162 .arg("no-md2")
163 .arg("no-rc5")
164 .arg("no-weak-ssl-ciphers");
165 }
166
167 if cfg!(not(feature = "camellia")) {
168 configure.arg("no-camellia");
169 }
170
171 if cfg!(not(feature = "idea")) {
172 configure.arg("no-idea");
173 }
174
175 if cfg!(not(feature = "seed")) {
176 configure.arg("no-seed");
177 }
178
179 if target.contains("musl") {
180 // This actually fails to compile on musl (it needs linux/version.h
181 // right now) but we don't actually need this most of the time.
182 configure.arg("no-engine");
183 } else if target.contains("windows") {
184 // We can build the engine feature, but the build doesn't seem
185 // to correctly pick up crypt32.lib functions such as
186 // `__imp_CertOpenStore` when building the capieng engine.
187 // Let's disable just capieng.
188 configure.arg("no-capieng");
189 }
190
191 if target.contains("musl") {
192 // MUSL doesn't implement some of the libc functions that the async
193 // stuff depends on, and we don't bind to any of that in any case.
194 configure.arg("no-async");
195 }
196
197 // On Android it looks like not passing no-stdio may cause a build
198 // failure (#13), but most other platforms need it for things like
199 // loading system certificates so only disable it on Android.
200 if target.contains("android") {
201 configure.arg("no-stdio");
202 }
203
204 if target.contains("msvc") {
205 // On MSVC we need nasm.exe to compile the assembly files.
206 // ASM compiling will be enabled if nasm.exe is installed, unless
207 // the environment variable `OPENSSL_RUST_USE_NASM` is set.
208 if self.is_nasm_ready() {
209 // a message to stdout, let user know asm is enabled
210 println!(
211 "{}: Enable the assembly language routines in building OpenSSL.",
212 env!("CARGO_PKG_NAME")
213 );
214 } else {
215 configure.arg("no-asm");
216 }
217 }
218
219 let os = match target {
220 "aarch64-apple-darwin" => "darwin64-arm64-cc",
221 // Note that this, and all other android targets, aren't using the
222 // `android64-aarch64` (or equivalent) builtin target. That
223 // apparently has a crazy amount of build logic in OpenSSL 1.1.1
224 // that bypasses basically everything `cc` does, so let's just cop
225 // out and say it's linux and hope it works.
226 "aarch64-linux-android" => "linux-aarch64",
227 "aarch64-unknown-freebsd" => "BSD-generic64",
228 "aarch64-unknown-linux-gnu" => "linux-aarch64",
229 "aarch64-unknown-linux-musl" => "linux-aarch64",
230 "aarch64-pc-windows-msvc" => "VC-WIN64-ARM",
231 "aarch64-uwp-windows-msvc" => "VC-WIN64-ARM-UWP",
232 "arm-linux-androideabi" => "linux-armv4",
233 "armv7-linux-androideabi" => "linux-armv4",
234 "arm-unknown-linux-gnueabi" => "linux-armv4",
235 "arm-unknown-linux-gnueabihf" => "linux-armv4",
236 "arm-unknown-linux-musleabi" => "linux-armv4",
237 "arm-unknown-linux-musleabihf" => "linux-armv4",
238 "armv5te-unknown-linux-gnueabi" => "linux-armv4",
239 "armv5te-unknown-linux-musleabi" => "linux-armv4",
240 "armv6-unknown-freebsd" => "BSD-generic32",
241 "armv7-unknown-freebsd" => "BSD-generic32",
242 "armv7-unknown-linux-gnueabi" => "linux-armv4",
243 "armv7-unknown-linux-musleabi" => "linux-armv4",
244 "armv7-unknown-linux-gnueabihf" => "linux-armv4",
245 "armv7-unknown-linux-musleabihf" => "linux-armv4",
246 "asmjs-unknown-emscripten" => "gcc",
247 "i586-unknown-linux-gnu" => "linux-elf",
248 "i586-unknown-linux-musl" => "linux-elf",
249 "i686-apple-darwin" => "darwin-i386-cc",
250 "i686-linux-android" => "linux-elf",
251 "i686-pc-windows-gnu" => "mingw",
252 "i686-pc-windows-msvc" => "VC-WIN32",
253 "i686-unknown-freebsd" => "BSD-x86-elf",
254 "i686-unknown-linux-gnu" => "linux-elf",
255 "i686-unknown-linux-musl" => "linux-elf",
256 "i686-uwp-windows-msvc" => "VC-WIN32-UWP",
257 "mips-unknown-linux-gnu" => "linux-mips32",
258 "mips-unknown-linux-musl" => "linux-mips32",
259 "mips64-unknown-linux-gnuabi64" => "linux64-mips64",
260 "mips64-unknown-linux-muslabi64" => "linux64-mips64",
261 "mips64el-unknown-linux-gnuabi64" => "linux64-mips64",
262 "mips64el-unknown-linux-muslabi64" => "linux64-mips64",
263 "mipsel-unknown-linux-gnu" => "linux-mips32",
264 "mipsel-unknown-linux-musl" => "linux-mips32",
265 "powerpc-unknown-freebsd" => "BSD-generic32",
266 "powerpc-unknown-linux-gnu" => "linux-ppc",
267 "powerpc64-unknown-freebsd" => "BSD-generic64",
268 "powerpc64-unknown-linux-gnu" => "linux-ppc64",
269 "powerpc64-unknown-linux-musl" => "linux-ppc64",
270 "powerpc64le-unknown-freebsd" => "BSD-generic64",
271 "powerpc64le-unknown-linux-gnu" => "linux-ppc64le",
272 "powerpc64le-unknown-linux-musl" => "linux-ppc64le",
273 "riscv64gc-unknown-linux-gnu" => "linux-generic64",
274 "s390x-unknown-linux-gnu" => "linux64-s390x",
275 "s390x-unknown-linux-musl" => "linux64-s390x",
276 "sparcv9-sun-solaris" => "solaris64-sparcv9-gcc",
277 "thumbv7a-uwp-windows-msvc" => "VC-WIN32-ARM-UWP",
278 "x86_64-apple-darwin" => "darwin64-x86_64-cc",
279 "x86_64-linux-android" => "linux-x86_64",
280 "x86_64-pc-windows-gnu" => "mingw64",
281 "x86_64-pc-windows-msvc" => "VC-WIN64A",
282 "x86_64-unknown-freebsd" => "BSD-x86_64",
283 "x86_64-unknown-dragonfly" => "BSD-x86_64",
284 "x86_64-unknown-illumos" => "solaris64-x86_64-gcc",
285 "x86_64-unknown-linux-gnu" => "linux-x86_64",
286 "x86_64-unknown-linux-musl" => "linux-x86_64",
287 "x86_64-unknown-openbsd" => "BSD-x86_64",
288 "x86_64-unknown-netbsd" => "BSD-x86_64",
289 "x86_64-uwp-windows-msvc" => "VC-WIN64A-UWP",
290 "x86_64-sun-solaris" => "solaris64-x86_64-gcc",
291 "wasm32-unknown-emscripten" => "gcc",
292 "wasm32-unknown-unknown" => "gcc",
293 "wasm32-wasi" => "gcc",
294 "aarch64-apple-ios" => "ios64-cross",
295 "x86_64-apple-ios" => "iossimulator-xcrun",
296 _ => panic!("don't know how to configure OpenSSL for {}", target),
297 };
298
299 let mut ios_isysroot: std::option::Option<String> = None;
300
301 configure.arg(os);
302
303 // If we're not on MSVC we configure cross compilers and cross tools and
304 // whatnot. Note that this doesn't happen on MSVC b/c things are pretty
305 // different there and this isn't needed most of the time anyway.
306 if !target.contains("msvc") {
307 let mut cc = cc::Build::new();
308 cc.target(target).host(host).warnings(false).opt_level(2);
309 let compiler = cc.get_compiler();
310 configure.env("CC", compiler.path());
311 let path = compiler.path().to_str().unwrap();
312
313 // Both `cc::Build` and `./Configure` take into account
314 // `CROSS_COMPILE` environment variable. So to avoid double
315 // prefix, we unset `CROSS_COMPILE` for `./Configure`.
316 configure.env_remove("CROSS_COMPILE");
317
318 // Infer ar/ranlib tools from cross compilers if the it looks like
319 // we're doing something like `foo-gcc` route that to `foo-ranlib`
320 // as well.
321 if path.ends_with("-gcc") && !target.contains("unknown-linux-musl") {
322 let path = &path[..path.len() - 4];
323 if env::var_os("RANLIB").is_none() {
324 configure.env("RANLIB", format!("{}-ranlib", path));
325 }
326 if env::var_os("AR").is_none() {
327 configure.env("AR", format!("{}-ar", path));
328 }
329 }
330
331 // Make sure we pass extra flags like `-ffunction-sections` and
332 // other things like ARM codegen flags.
333 let mut skip_next = false;
334 let mut is_isysroot = false;
335 for arg in compiler.args() {
336 // For whatever reason `-static` on MUSL seems to cause
337 // issues...
338 if target.contains("musl") && arg == "-static" {
339 continue;
340 }
341
342 // cc includes an `-arch` flag for Apple platforms, but we've
343 // already selected an arch implicitly via the target above, and
344 // OpenSSL contains about the conflict if both are specified.
345 if target.contains("apple") {
346 if arg == "-arch" {
347 skip_next = true;
348 continue;
349 }
350 }
351
352 // cargo-lipo specifies this but OpenSSL complains
353 if target.contains("apple-ios") {
354 if arg == "-isysroot" {
355 is_isysroot = true;
356 continue;
357 }
358
359 if is_isysroot {
360 is_isysroot = false;
361 ios_isysroot = Some(arg.to_str().unwrap().to_string());
362 continue;
363 }
364 }
365
366 if skip_next {
367 skip_next = false;
368 continue;
369 }
370
371 configure.arg(arg);
372 }
373
374 if os.contains("iossimulator") {
375 if let Some(ref isysr) = ios_isysroot {
376 configure.env(
377 "CC",
378 &format!(
379 "xcrun -sdk iphonesimulator cc -isysroot {}",
380 sanitize_sh(&Path::new(isysr))
381 ),
382 );
383 }
384 }
385
386 if target == "x86_64-pc-windows-gnu" {
387 // For whatever reason OpenSSL 1.1.1 fails to build on
388 // `x86_64-pc-windows-gnu` in our docker container due to an
389 // error about "too many sections". Having no idea what this
390 // error is about some quick googling yields
391 // https://github.com/cginternals/glbinding/issues/135 which
392 // mysteriously mentions `-Wa,-mbig-obj`, passing a new argument
393 // to the assembler. Now I have no idea what `-mbig-obj` does
394 // for Windows nor why it would matter, but it does seem to fix
395 // compilation issues.
396 //
397 // Note that another entirely unrelated issue -
398 // https://github.com/assimp/assimp/issues/177 - was fixed by
399 // splitting a large file, so presumably OpenSSL has a large
400 // file soemwhere in it? Who knows!
401 configure.arg("-Wa,-mbig-obj");
402 }
403
404 if target.contains("pc-windows-gnu") && path.ends_with("-gcc") {
405 // As of OpenSSL 1.1.1 the build system is now trying to execute
406 // `windres` which doesn't exist when we're cross compiling from
407 // Linux, so we may need to instruct it manually to know what
408 // executable to run.
409 let windres = format!("{}-windres", &path[..path.len() - 4]);
410 configure.env("WINDRES", &windres);
411 }
412
413 if target.contains("emscripten") {
414 // As of OpenSSL 1.1.1 the source apparently wants to include
415 // `stdatomic.h`, but this doesn't exist on Emscripten. After
416 // reading OpenSSL's source where the error is, we define this
417 // magical (and probably
418 // compiler-internal-should-not-be-user-defined) macro to say
419 // "no atomics are available" and avoid including such a header.
420 configure.arg("-D__STDC_NO_ATOMICS__");
421 }
422
423 if target.contains("musl") {
424 // Hack around openssl/openssl#7207 for now
425 configure.arg("-DOPENSSL_NO_SECURE_MEMORY");
426 }
427 }
428
429 // And finally, run the perl configure script!
430 configure.current_dir(&inner_dir);
431 self.run_command(configure, "configuring OpenSSL build");
432
433 // On MSVC we use `nmake.exe` with a slightly different invocation, so
434 // have that take a different path than the standard `make` below.
435 if target.contains("msvc") {
436 let mut build =
437 cc::windows_registry::find(target, "nmake.exe").expect("failed to find nmake");
438 build.arg("build_libs").current_dir(&inner_dir);
439 self.run_command(build, "building OpenSSL");
440
441 let mut install =
442 cc::windows_registry::find(target, "nmake.exe").expect("failed to find nmake");
443 install.arg("install_dev").current_dir(&inner_dir);
444 self.run_command(install, "installing OpenSSL");
445 } else {
446 let mut depend = self.cmd_make();
447 depend.arg("depend").current_dir(&inner_dir);
448 self.run_command(depend, "building OpenSSL dependencies");
449
450 let mut build = self.cmd_make();
451 build.arg("build_libs").current_dir(&inner_dir);
452 if !cfg!(windows) {
453 if let Some(s) = env::var_os("CARGO_MAKEFLAGS") {
454 build.env("MAKEFLAGS", s);
455 }
456 }
457
458 if let Some(ref isysr) = ios_isysroot {
459 let components: Vec<&str> = isysr.split("/SDKs/").collect();
460 build.env("CROSS_TOP", components[0]);
461 build.env("CROSS_SDK", components[1]);
462 }
463
464 self.run_command(build, "building OpenSSL");
465
466 let mut install = self.cmd_make();
467 install.arg("install_dev").current_dir(&inner_dir);
468 self.run_command(install, "installing OpenSSL");
469 }
470
471 let libs = if target.contains("msvc") {
472 vec!["libssl".to_string(), "libcrypto".to_string()]
473 } else {
474 vec!["ssl".to_string(), "crypto".to_string()]
475 };
476
477 fs::remove_dir_all(&inner_dir).unwrap();
478
479 Artifacts {
480 lib_dir: install_dir.join("lib"),
481 bin_dir: install_dir.join("bin"),
482 include_dir: install_dir.join("include"),
483 libs: libs,
484 target: target.to_string(),
485 }
486 }
487
run_command(&self, mut command: Command, desc: &str)488 fn run_command(&self, mut command: Command, desc: &str) {
489 println!("running {:?}", command);
490 let status = command.status().unwrap();
491 if !status.success() {
492 panic!(
493 "
494
495
496 Error {}:
497 Command: {:?}
498 Exit status: {}
499
500
501 ",
502 desc, command, status
503 );
504 }
505 }
506 }
507
cp_r(src: &Path, dst: &Path)508 fn cp_r(src: &Path, dst: &Path) {
509 for f in fs::read_dir(src).unwrap() {
510 let f = f.unwrap();
511 let path = f.path();
512 let name = path.file_name().unwrap();
513
514 // Skip git metadata as it's been known to cause issues (#26) and
515 // otherwise shouldn't be required
516 if name.to_str() == Some(".git") {
517 continue;
518 }
519
520 let dst = dst.join(name);
521 if f.file_type().unwrap().is_dir() {
522 fs::create_dir_all(&dst).unwrap();
523 cp_r(&path, &dst);
524 } else {
525 let _ = fs::remove_file(&dst);
526 fs::copy(&path, &dst).unwrap();
527 }
528 }
529 }
530
sanitize_sh(path: &Path) -> String531 fn sanitize_sh(path: &Path) -> String {
532 if !cfg!(windows) {
533 return path.to_str().unwrap().to_string();
534 }
535 let path = path.to_str().unwrap().replace("\\", "/");
536 return change_drive(&path).unwrap_or(path);
537
538 fn change_drive(s: &str) -> Option<String> {
539 let mut ch = s.chars();
540 let drive = ch.next().unwrap_or('C');
541 if ch.next() != Some(':') {
542 return None;
543 }
544 if ch.next() != Some('/') {
545 return None;
546 }
547 Some(format!("/{}/{}", drive, &s[drive.len_utf8() + 2..]))
548 }
549 }
550
551 impl Artifacts {
552 pub fn include_dir(&self) -> &Path {
553 &self.include_dir
554 }
555
556 pub fn lib_dir(&self) -> &Path {
557 &self.lib_dir
558 }
559
560 pub fn libs(&self) -> &[String] {
561 &self.libs
562 }
563
564 pub fn print_cargo_metadata(&self) {
565 println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
566 for lib in self.libs.iter() {
567 println!("cargo:rustc-link-lib=static={}", lib);
568 }
569 println!("cargo:include={}", self.include_dir.display());
570 println!("cargo:lib={}", self.lib_dir.display());
571 if self.target.contains("msvc") {
572 println!("cargo:rustc-link-lib=user32");
573 }
574 }
575 }
576