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