tikv_openssl_src/
lib.rs

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            // No shared objects, we just want static libraries
96            .arg("no-dso")
97            // Should be off by default on OpenSSL 1.1.0, but let's be extra sure
98            .arg("no-ssl3")
99            // No need to build tests, we won't run them anyway
100            .arg("no-unit-test")
101            // Nothing related to zlib please
102            .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            // This actually fails to compile on musl (it needs linux/version.h
127            // right now) but we don't actually need this most of the time.
128            // API of engine.c ld fail in Windows.
129            configure.arg("no-engine");
130        }
131
132        if target.contains("musl") {
133            // MUSL doesn't implement some of the libc functions that the async
134            // stuff depends on, and we don't bind to any of that in any case.
135            configure.arg("no-async");
136        }
137
138        // On Android it looks like not passing no-stdio may cause a build
139        // failure (#13), but most other platforms need it for things like
140        // loading system certificates so only disable it on Android.
141        if target.contains("android") {
142            configure.arg("no-stdio");
143        }
144
145        if target.contains("msvc") {
146            // On MSVC we need nasm.exe to compile the assembly files, but let's
147            // just pessimistically assume for now that's not available.
148            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            // Never shared on non-MSVC
156            configure.arg("no-shared");
157        }
158
159        let os = match target {
160            "aarch64-apple-darwin" => "darwin64-arm64-cc",
161            // Note that this, and all other android targets, aren't using the
162            // `android64-aarch64` (or equivalent) builtin target. That
163            // apparently has a crazy amount of build logic in OpenSSL 1.1.1
164            // that bypasses basically everything `cc` does, so let's just cop
165            // out and say it's linux and hope it works.
166            "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 we're not on MSVC we configure cross compilers and cross tools and
224        // whatnot. Note that this doesn't happen on MSVC b/c things are pretty
225        // different there and this isn't needed most of the time anyway.
226        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            // Both `cc::Build` and `./Configure` take into account
234            // `CROSS_COMPILE` environment variable. So to avoid double
235            // prefix, we unset `CROSS_COMPILE` for `./Configure`.
236            configure.env_remove("CROSS_COMPILE");
237
238            // Infer ar/ranlib tools from cross compilers if the it looks like
239            // we're doing something like `foo-gcc` route that to `foo-ranlib`
240            // as well.
241            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            // Make sure we pass extra flags like `-ffunction-sections` and
252            // other things like ARM codegen flags.
253            let mut skip_next = false;
254            let mut is_isysroot = false;
255            for arg in compiler.args() {
256                // For whatever reason `-static` on MUSL seems to cause
257                // issues...
258                if target.contains("musl") && arg == "-static" {
259                    continue;
260                }
261
262                // cc includes an `-arch` flag for Apple platforms, but we've
263                // already selected an arch implicitly via the target above, and
264                // OpenSSL contains about the conflict if both are specified.
265                if target.contains("apple") {
266                    if arg == "-arch" {
267                        skip_next = true;
268                        continue;
269                    }
270                }
271
272                // cargo-lipo specifies this but OpenSSL complains
273                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                // For whatever reason OpenSSL 1.1.1 fails to build on
308                // `x86_64-pc-windows-gnu` in our docker container due to an
309                // error about "too many sections". Having no idea what this
310                // error is about some quick googling yields
311                // https://github.com/cginternals/glbinding/issues/135 which
312                // mysteriously mentions `-Wa,-mbig-obj`, passing a new argument
313                // to the assembler. Now I have no idea what `-mbig-obj` does
314                // for Windows nor why it would matter, but it does seem to fix
315                // compilation issues.
316                //
317                // Note that another entirely unrelated issue -
318                // https://github.com/assimp/assimp/issues/177 - was fixed by
319                // splitting a large file, so presumably OpenSSL has a large
320                // file soemwhere in it? Who knows!
321                configure.arg("-Wa,-mbig-obj");
322            }
323
324            if target.contains("pc-windows-gnu") && path.ends_with("-gcc") {
325                // As of OpenSSL 1.1.1 the build system is now trying to execute
326                // `windres` which doesn't exist when we're cross compiling from
327                // Linux, so we may need to instruct it manually to know what
328                // executable to run.
329                let windres = format!("{}-windres", &path[..path.len() - 4]);
330                configure.env("WINDRES", &windres);
331            }
332
333            if target.contains("emscripten") {
334                // As of OpenSSL 1.1.1 the source apparently wants to include
335                // `stdatomic.h`, but this doesn't exist on Emscripten. After
336                // reading OpenSSL's source where the error is, we define this
337                // magical (and probably
338                // compiler-internal-should-not-be-user-defined) macro to say
339                // "no atomics are available" and avoid including such a header.
340                configure.arg("-D__STDC_NO_ATOMICS__");
341            }
342
343            if target.contains("musl") {
344                // Hack around openssl/openssl#7207 for now
345                configure.arg("-DOPENSSL_NO_SECURE_MEMORY");
346            }
347        }
348
349        // And finally, run the perl configure script!
350        configure.current_dir(&inner_dir);
351        self.run_command(configure, "configuring OpenSSL build");
352
353        // On MSVC we use `nmake.exe` with a slightly different invocation, so
354        // have that take a different path than the standard `make` below.
355        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        // Skip git metadata as it's been known to cause issues (#26) and
433        // otherwise shouldn't be required
434        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    // Undo part of https://github.com/openssl/openssl/commit/c352bd07ed2ff872876534c950a6968d75ef121e on MUSL
460    // since it doesn't have asm/unistd.h
461    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    // Apply build system changes to allow configuring and building
477    // for Apple's ARM64 platform.
478    // https://github.com/openssl/openssl/pull/12369
479
480    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}