1extern crate cc;
2
3use std::env;
4use std::fs;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7
8pub fn source_dir() -> PathBuf {
9 Path::new(env!("CARGO_MANIFEST_DIR")).join("openssl")
10}
11
12pub fn version() -> &'static str {
13 env!("CARGO_PKG_VERSION")
14}
15
16pub struct Build {
17 out_dir: Option<PathBuf>,
18 target: Option<String>,
19 host: Option<String>,
20}
21
22pub struct Artifacts {
23 include_dir: PathBuf,
24 lib_dir: PathBuf,
25 libs: Vec<String>,
26}
27
28impl Build {
29 pub fn new() -> Build {
30 Build {
31 out_dir: env::var_os("OUT_DIR").map(|s| PathBuf::from(s).join("openssl-build")),
32 target: env::var("TARGET").ok(),
33 host: env::var("HOST").ok(),
34 }
35 }
36
37 pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
38 self.out_dir = Some(path.as_ref().to_path_buf());
39 self
40 }
41
42 pub fn target(&mut self, target: &str) -> &mut Build {
43 self.target = Some(target.to_string());
44 self
45 }
46
47 pub fn host(&mut self, host: &str) -> &mut Build {
48 self.host = Some(host.to_string());
49 self
50 }
51
52 fn cmd_make(&self) -> Command {
53 let host = &self.host.as_ref().expect("HOST dir not set")[..];
54 if host.contains("dragonfly")
55 || host.contains("freebsd")
56 || host.contains("solaris")
57 || host.contains("illumos")
58 {
59 Command::new("gmake")
60 } else {
61 Command::new("make")
62 }
63 }
64
65 pub fn build(&mut self) -> Artifacts {
66 let target = &self.target.as_ref().expect("TARGET dir not set")[..];
67 let host = &self.host.as_ref().expect("HOST dir not set")[..];
68 let out_dir = self.out_dir.as_ref().expect("OUT_DIR not set");
69 let build_dir = out_dir.join("build");
70 let install_dir = out_dir.join("install");
71
72 if build_dir.exists() {
73 fs::remove_dir_all(&build_dir).unwrap();
74 }
75 if install_dir.exists() {
76 fs::remove_dir_all(&install_dir).unwrap();
77 }
78
79 let inner_dir = build_dir.join("src");
80 fs::create_dir_all(&inner_dir).unwrap();
81 cp_r(&source_dir(), &inner_dir);
82 apply_patches(target, &inner_dir);
83
84 let perl_program =
85 env::var("OPENSSL_SRC_PERL").unwrap_or(env::var("PERL").unwrap_or("perl".to_string()));
86 let mut configure = Command::new(perl_program);
87 configure.arg("./Configure");
88 if host.contains("pc-windows-gnu") {
89 configure.arg(&format!("--prefix={}", sanitize_sh(&install_dir)));
90 } else {
91 configure.arg(&format!("--prefix={}", install_dir.display()));
92 }
93
94 configure
95 .arg("no-dso")
97 .arg("no-ssl3")
99 .arg("no-unit-test")
101 .arg("no-comp")
103 .arg("no-zlib")
104 .arg("no-zlib-dynamic");
105
106 if cfg!(not(feature = "weak-crypto")) {
107 configure
108 .arg("no-md2")
109 .arg("no-rc5")
110 .arg("no-weak-ssl-ciphers");
111 }
112
113 if cfg!(not(feature = "camellia")) {
114 configure.arg("no-camellia");
115 }
116
117 if cfg!(not(feature = "idea")) {
118 configure.arg("no-idea");
119 }
120
121 if cfg!(not(feature = "seed")) {
122 configure.arg("no-seed");
123 }
124
125 if target.contains("musl") || target.contains("windows") {
126 configure.arg("no-engine");
130 }
131
132 if target.contains("musl") {
133 configure.arg("no-async");
136 }
137
138 if target.contains("android") {
142 configure.arg("no-stdio");
143 }
144
145 if target.contains("msvc") {
146 configure.arg("no-asm");
149
150 let features = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or(String::new());
151 if features.contains("crt-static") {
152 configure.arg("no-shared");
153 }
154 } else {
155 configure.arg("no-shared");
157 }
158
159 let os = match target {
160 "aarch64-apple-darwin" => "darwin64-arm64-cc",
161 "aarch64-linux-android" => "linux-aarch64",
167 "aarch64-unknown-freebsd" => "BSD-generic64",
168 "aarch64-unknown-linux-gnu" => "linux-aarch64",
169 "aarch64-unknown-linux-musl" => "linux-aarch64",
170 "aarch64-pc-windows-msvc" => "VC-WIN64-ARM",
171 "arm-linux-androideabi" => "linux-armv4",
172 "armv7-linux-androideabi" => "linux-armv4",
173 "arm-unknown-linux-gnueabi" => "linux-armv4",
174 "arm-unknown-linux-gnueabihf" => "linux-armv4",
175 "arm-unknown-linux-musleabi" => "linux-armv4",
176 "arm-unknown-linux-musleabihf" => "linux-armv4",
177 "armv6-unknown-freebsd" => "BSD-generic32",
178 "armv7-unknown-freebsd" => "BSD-generic32",
179 "armv7-unknown-linux-gnueabihf" => "linux-armv4",
180 "armv7-unknown-linux-musleabihf" => "linux-armv4",
181 "asmjs-unknown-emscripten" => "gcc",
182 "i686-apple-darwin" => "darwin-i386-cc",
183 "i686-linux-android" => "linux-elf",
184 "i686-pc-windows-gnu" => "mingw",
185 "i686-pc-windows-msvc" => "VC-WIN32",
186 "i686-unknown-freebsd" => "BSD-x86-elf",
187 "i686-unknown-linux-gnu" => "linux-elf",
188 "i686-unknown-linux-musl" => "linux-elf",
189 "mips-unknown-linux-gnu" => "linux-mips32",
190 "mips-unknown-linux-musl" => "linux-mips32",
191 "mips64-unknown-linux-gnuabi64" => "linux64-mips64",
192 "mips64el-unknown-linux-gnuabi64" => "linux64-mips64",
193 "mipsel-unknown-linux-gnu" => "linux-mips32",
194 "mipsel-unknown-linux-musl" => "linux-mips32",
195 "powerpc-unknown-linux-gnu" => "linux-ppc",
196 "powerpc64-unknown-freebsd" => "BSD-generic64",
197 "powerpc64-unknown-linux-gnu" => "linux-ppc64",
198 "powerpc64le-unknown-linux-gnu" => "linux-ppc64le",
199 "riscv64gc-unknown-linux-gnu" => "linux-generic64",
200 "s390x-unknown-linux-gnu" => "linux64-s390x",
201 "x86_64-apple-darwin" => "darwin64-x86_64-cc",
202 "x86_64-linux-android" => "linux-x86_64",
203 "x86_64-pc-windows-gnu" => "mingw64",
204 "x86_64-pc-windows-msvc" => "VC-WIN64A",
205 "x86_64-unknown-freebsd" => "BSD-x86_64",
206 "x86_64-unknown-dragonfly" => "BSD-x86_64",
207 "x86_64-unknown-illumos" => "solaris64-x86_64-gcc",
208 "x86_64-unknown-linux-gnu" => "linux-x86_64",
209 "x86_64-unknown-linux-musl" => "linux-x86_64",
210 "x86_64-unknown-netbsd" => "BSD-x86_64",
211 "x86_64-sun-solaris" => "solaris64-x86_64-gcc",
212 "wasm32-unknown-emscripten" => "gcc",
213 "wasm32-unknown-unknown" => "gcc",
214 "aarch64-apple-ios" => "ios64-cross",
215 "x86_64-apple-ios" => "iossimulator-xcrun",
216 _ => panic!("don't know how to configure OpenSSL for {}", target),
217 };
218
219 let mut ios_isysroot: std::option::Option<String> = None;
220
221 configure.arg(os);
222
223 if !target.contains("msvc") {
227 let mut cc = cc::Build::new();
228 cc.target(target).host(host).warnings(false).opt_level(2);
229 let compiler = cc.get_compiler();
230 configure.env("CC", compiler.path());
231 let path = compiler.path().to_str().unwrap();
232
233 configure.env_remove("CROSS_COMPILE");
237
238 if path.ends_with("-gcc") && !target.contains("unknown-linux-musl") {
242 let path = &path[..path.len() - 4];
243 if env::var_os("RANLIB").is_none() {
244 configure.env("RANLIB", format!("{}-ranlib", path));
245 }
246 if env::var_os("AR").is_none() {
247 configure.env("AR", format!("{}-ar", path));
248 }
249 }
250
251 let mut skip_next = false;
254 let mut is_isysroot = false;
255 for arg in compiler.args() {
256 if target.contains("musl") && arg == "-static" {
259 continue;
260 }
261
262 if target.contains("apple") {
266 if arg == "-arch" {
267 skip_next = true;
268 continue;
269 }
270 }
271
272 if target.contains("apple-ios") {
274 if arg == "-isysroot" {
275 is_isysroot = true;
276 continue;
277 }
278
279 if is_isysroot {
280 is_isysroot = false;
281 ios_isysroot = Some(arg.to_str().unwrap().to_string());
282 continue;
283 }
284 }
285
286 if skip_next {
287 skip_next = false;
288 continue;
289 }
290
291 configure.arg(arg);
292 }
293
294 if os.contains("iossimulator") {
295 if let Some(ref isysr) = ios_isysroot {
296 configure.env(
297 "CC",
298 &format!(
299 "xcrun -sdk iphonesimulator cc -isysroot {}",
300 sanitize_sh(&Path::new(isysr))
301 ),
302 );
303 }
304 }
305
306 if target == "x86_64-pc-windows-gnu" {
307 configure.arg("-Wa,-mbig-obj");
322 }
323
324 if target.contains("pc-windows-gnu") && path.ends_with("-gcc") {
325 let windres = format!("{}-windres", &path[..path.len() - 4]);
330 configure.env("WINDRES", &windres);
331 }
332
333 if target.contains("emscripten") {
334 configure.arg("-D__STDC_NO_ATOMICS__");
341 }
342
343 if target.contains("musl") {
344 configure.arg("-DOPENSSL_NO_SECURE_MEMORY");
346 }
347 }
348
349 configure.current_dir(&inner_dir);
351 self.run_command(configure, "configuring OpenSSL build");
352
353 if target.contains("msvc") {
356 let mut build =
357 cc::windows_registry::find(target, "nmake.exe").expect("failed to find nmake");
358 build.arg("build_libs").current_dir(&inner_dir);
359 self.run_command(build, "building OpenSSL");
360
361 let mut install =
362 cc::windows_registry::find(target, "nmake.exe").expect("failed to find nmake");
363 install.arg("install_dev").current_dir(&inner_dir);
364 self.run_command(install, "installing OpenSSL");
365 } else {
366 let mut depend = self.cmd_make();
367 depend.arg("depend").current_dir(&inner_dir);
368 self.run_command(depend, "building OpenSSL dependencies");
369
370 let mut build = self.cmd_make();
371 build.arg("build_libs").current_dir(&inner_dir);
372 if !cfg!(windows) {
373 if let Some(s) = env::var_os("CARGO_MAKEFLAGS") {
374 build.env("MAKEFLAGS", s);
375 }
376 }
377
378 if let Some(ref isysr) = ios_isysroot {
379 let components: Vec<&str> = isysr.split("/SDKs/").collect();
380 build.env("CROSS_TOP", components[0]);
381 build.env("CROSS_SDK", components[1]);
382 }
383
384 self.run_command(build, "building OpenSSL");
385
386 let mut install = self.cmd_make();
387 install.arg("install_dev").current_dir(&inner_dir);
388 self.run_command(install, "installing OpenSSL");
389 }
390
391 let libs = if target.contains("msvc") {
392 vec!["libssl".to_string(), "libcrypto".to_string()]
393 } else {
394 vec!["ssl".to_string(), "crypto".to_string()]
395 };
396
397 fs::remove_dir_all(&inner_dir).unwrap();
398
399 Artifacts {
400 lib_dir: install_dir.join("lib"),
401 include_dir: install_dir.join("include"),
402 libs: libs,
403 }
404 }
405
406 fn run_command(&self, mut command: Command, desc: &str) {
407 println!("running {:?}", command);
408 let status = command.status().unwrap();
409 if !status.success() {
410 panic!(
411 "
412
413
414Error {}:
415 Command: {:?}
416 Exit status: {}
417
418
419 ",
420 desc, command, status
421 );
422 }
423 }
424}
425
426fn cp_r(src: &Path, dst: &Path) {
427 for f in fs::read_dir(src).unwrap() {
428 let f = f.unwrap();
429 let path = f.path();
430 let name = path.file_name().unwrap();
431
432 if name.to_str() == Some(".git") {
435 continue;
436 }
437
438 let dst = dst.join(name);
439 if f.file_type().unwrap().is_dir() {
440 fs::create_dir_all(&dst).unwrap();
441 cp_r(&path, &dst);
442 } else {
443 let _ = fs::remove_file(&dst);
444 fs::copy(&path, &dst).unwrap();
445 }
446 }
447}
448
449fn apply_patches(target: &str, inner: &Path) {
450 apply_patches_musl(target, inner);
451 apply_patches_aarch64_apple_darwin(target, inner);
452}
453
454fn apply_patches_musl(target: &str, inner: &Path) {
455 if !target.contains("musl") {
456 return;
457 }
458
459 let path = inner.join("crypto/rand/rand_unix.c");
462 let buf = fs::read_to_string(&path).unwrap();
463
464 let buf = buf
465 .replace("asm/unistd.h", "sys/syscall.h")
466 .replace("__NR_getrandom", "SYS_getrandom");
467
468 fs::write(path, buf).unwrap();
469}
470
471fn apply_patches_aarch64_apple_darwin(target: &str, inner: &Path) {
472 if target != "aarch64-apple-darwin" {
473 return;
474 }
475
476 let path = inner.join("Configurations/10-main.conf");
481 let mut buf = fs::read_to_string(&path).unwrap();
482
483 assert!(
484 !buf.contains("darwin64-arm64-cc"),
485 "{} already contains instructions for aarch64-apple-darwin",
486 path.display(),
487 );
488
489 const PATCH: &str = r#"
490 "darwin64-arm64-cc" => {
491 inherit_from => [ "darwin-common", asm("aarch64_asm") ],
492 CFLAGS => add("-Wall"),
493 cflags => add("-arch arm64"),
494 lib_cppflags => add("-DL_ENDIAN"),
495 bn_ops => "SIXTY_FOUR_BIT_LONG",
496 perlasm_scheme => "ios64",
497 },"#;
498
499 let x86_64_stanza = buf.find(r#" "darwin64-x86_64-cc""#).unwrap();
500 buf.insert_str(x86_64_stanza, PATCH);
501
502 fs::write(path, buf).unwrap();
503}
504
505fn sanitize_sh(path: &Path) -> String {
506 if !cfg!(windows) {
507 return path.to_str().unwrap().to_string();
508 }
509 let path = path.to_str().unwrap().replace("\\", "/");
510 return change_drive(&path).unwrap_or(path);
511
512 fn change_drive(s: &str) -> Option<String> {
513 let mut ch = s.chars();
514 let drive = ch.next().unwrap_or('C');
515 if ch.next() != Some(':') {
516 return None;
517 }
518 if ch.next() != Some('/') {
519 return None;
520 }
521 Some(format!("/{}/{}", drive, &s[drive.len_utf8() + 2..]))
522 }
523}
524
525impl Artifacts {
526 pub fn include_dir(&self) -> &Path {
527 &self.include_dir
528 }
529
530 pub fn lib_dir(&self) -> &Path {
531 &self.lib_dir
532 }
533
534 pub fn libs(&self) -> &[String] {
535 &self.libs
536 }
537
538 pub fn print_cargo_metadata(&self) {
539 println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
540 for lib in self.libs.iter() {
541 println!("cargo:rustc-link-lib=static={}", lib);
542 }
543 println!("cargo:include={}", self.include_dir.display());
544 println!("cargo:lib={}", self.lib_dir.display());
545 }
546}