Skip to main content

openssl_src/
lib.rs

1extern crate cc;
2
3use std::env;
4use std::ffi::{OsStr, OsString};
5use std::fs;
6use std::path::{Path, PathBuf};
7use std::process::Command;
8
9pub fn source_dir() -> PathBuf {
10    Path::new(env!("CARGO_MANIFEST_DIR")).join("openssl")
11}
12
13pub fn version() -> &'static str {
14    env!("CARGO_PKG_VERSION")
15}
16
17pub struct Build {
18    out_dir: Option<PathBuf>,
19    target: Option<String>,
20    host: Option<String>,
21    // Only affects non-windows builds for now.
22    openssl_dir: Option<PathBuf>,
23}
24
25pub struct Artifacts {
26    include_dir: PathBuf,
27    lib_dir: PathBuf,
28    bin_dir: PathBuf,
29    libs: Vec<String>,
30    target: String,
31}
32
33impl Build {
34    pub fn new() -> Build {
35        Build {
36            out_dir: env::var_os("OUT_DIR").map(|s| PathBuf::from(s).join("openssl-build")),
37            target: env::var("TARGET").ok(),
38            host: env::var("HOST").ok(),
39            openssl_dir: Some(PathBuf::from("/usr/local/ssl")),
40        }
41    }
42
43    pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
44        self.out_dir = Some(path.as_ref().to_path_buf());
45        self
46    }
47
48    pub fn target(&mut self, target: &str) -> &mut Build {
49        self.target = Some(target.to_string());
50        self
51    }
52
53    pub fn host(&mut self, host: &str) -> &mut Build {
54        self.host = Some(host.to_string());
55        self
56    }
57
58    pub fn openssl_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
59        self.openssl_dir = Some(path.as_ref().to_path_buf());
60        self
61    }
62
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        )
77    }
78
79    #[cfg(windows)]
80    fn check_env_var(&self, var_name: &str) -> Option<bool> {
81        env::var_os(var_name).and_then(|s| {
82            if s == "1" {
83                // a message to stdout, let user know asm is force enabled
84                println!(
85                    "cargo:warning={}: nasm.exe is force enabled by the \
86                    'OPENSSL_RUST_USE_NASM' env var.",
87                    env!("CARGO_PKG_NAME")
88                );
89                Some(true)
90            } else if s == "0" {
91                // a message to stdout, let user know asm is force disabled
92                println!(
93                    "cargo:warning={}: nasm.exe is force disabled by the \
94                    'OPENSSL_RUST_USE_NASM' env var.",
95                    env!("CARGO_PKG_NAME")
96                );
97                Some(false)
98            } else {
99                println!(
100                    "cargo:warning=The environment variable {} is set to an unacceptable value: {:?}",
101                    var_name, s
102                );
103                None
104            }
105        })
106    }
107
108    #[cfg(windows)]
109    fn is_nasm_ready(&self) -> bool {
110        self.check_env_var("OPENSSL_RUST_USE_NASM")
111            .unwrap_or_else(|| {
112                // On Windows, use cmd `where` command to check if nasm is installed
113                Command::new("cmd")
114                    .args(&["/C", "where nasm"])
115                    .output()
116                    .map(|w| w.status.success())
117                    .unwrap_or(false)
118            })
119    }
120
121    #[cfg(not(windows))]
122    fn is_nasm_ready(&self) -> bool {
123        // We assume that nobody would run nasm.exe on a non-windows system.
124        false
125    }
126
127    /// Exits the process on failure. Use `try_build` to handle the error.
128    pub fn build(&mut self) -> Artifacts {
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")?;
143        let build_dir = out_dir.join("build");
144        let install_dir = out_dir.join("install");
145
146        if build_dir.exists() {
147            fs::remove_dir_all(&build_dir).map_err(|e| format!("build_dir: {e}"))?;
148        }
149        if install_dir.exists() {
150            fs::remove_dir_all(&install_dir).map_err(|e| format!("install_dir: {e}"))?;
151        }
152
153        let inner_dir = build_dir.join("src");
154        fs::create_dir_all(&inner_dir).map_err(|e| format!("{}: {e}", inner_dir.display()))?;
155        cp_r(&source_dir(), &inner_dir)?;
156
157        let perl_program =
158            env::var("OPENSSL_SRC_PERL").unwrap_or(env::var("PERL").unwrap_or("perl".to_string()));
159        let mut configure = Command::new(perl_program);
160        configure.arg("./Configure");
161
162        // Change the install directory to happen inside of the build directory.
163        if host.contains("pc-windows-gnu") {
164            configure.arg(&format!("--prefix={}", sanitize_sh(&install_dir)));
165        } else if host.contains("pc-windows-msvc") || host.contains("win7-windows-msvc") {
166            // On Windows, the prefix argument does not support \ path seperators
167            // when cross compiling.
168            // Always use / as a path seperator instead of \, since that works for both
169            // native and cross builds.
170            configure.arg(&format!(
171                "--prefix={}",
172                install_dir
173                    .to_str()
174                    .ok_or("bad install_dir")?
175                    .replace("\\", "/")
176            ));
177        } else {
178            configure.arg(&format!("--prefix={}", install_dir.display()));
179        }
180
181        // Specify that openssl directory where things are loaded at runtime is
182        // not inside our build directory. Instead this should be located in the
183        // default locations of the OpenSSL build scripts, or as specified by whatever
184        // configured this builder.
185        if target.contains("windows") {
186            configure.arg("--openssldir=SYS$MANAGER:[OPENSSL]");
187        } else {
188            let openssl_dir = self
189                .openssl_dir
190                .as_ref()
191                .ok_or("path to the openssl directory must be set")?;
192            let mut dir_arg: OsString = "--openssldir=".into();
193            dir_arg.push(openssl_dir);
194            configure.arg(dir_arg);
195        }
196
197        configure
198            // No shared objects, we just want static libraries
199            .arg("no-shared")
200            .arg("no-module")
201            // No need to build tests, we won't run them anyway
202            .arg("no-tests")
203            // Nothing related to zlib please
204            .arg("no-comp")
205            .arg("no-zlib")
206            .arg("no-zlib-dynamic")
207            // Avoid multilib-postfix for build targets that specify it
208            .arg("--libdir=lib");
209
210        if cfg!(feature = "no-dso") {
211            // engine requires DSO support
212            if cfg!(feature = "force-engine") {
213                println!("Feature 'force-engine' requires DSO, ignoring 'no-dso' feature.");
214            } else {
215                configure.arg("no-dso");
216            }
217        }
218
219        if cfg!(not(feature = "legacy")) {
220            configure.arg("no-legacy");
221        }
222
223        if cfg!(feature = "ssl3") {
224            configure.arg("enable-ssl3").arg("enable-ssl3-method");
225        } else {
226            // Should be off by default on OpenSSL 1.1.0, but let's be extra sure
227            configure.arg("no-ssl3");
228        }
229
230        if cfg!(feature = "weak-crypto") {
231            configure
232                .arg("enable-md2")
233                .arg("enable-rc5")
234                .arg("enable-weak-ssl-ciphers");
235        } else {
236            configure
237                .arg("no-md2")
238                .arg("no-rc5")
239                .arg("no-weak-ssl-ciphers");
240        }
241
242        if cfg!(not(feature = "camellia")) {
243            configure.arg("no-camellia");
244        }
245
246        if cfg!(not(feature = "idea")) {
247            configure.arg("no-idea");
248        }
249
250        if cfg!(not(feature = "seed")) {
251            configure.arg("no-seed");
252        }
253
254        if cfg!(feature = "ktls") {
255            configure.arg("enable-ktls");
256        }
257
258        if target.contains("musl") {
259            // Engine module fails to compile on musl (it needs linux/version.h
260            // right now) but we don't actually need this most of the time.
261            // Disable engine module unless force-engine feature specified
262            if !cfg!(feature = "force-engine") {
263                configure.arg("no-engine");
264            }
265        } else if target.contains("windows") {
266            // We can build the engine feature, but the build doesn't seem
267            // to correctly pick up crypt32.lib functions such as
268            // `__imp_CertOpenStore` when building the capieng engine.
269            // Let's disable just capieng.
270            configure.arg("no-capieng");
271        }
272
273        if target.contains("musl") {
274            // MUSL doesn't implement some of the libc functions that the async
275            // stuff depends on, and we don't bind to any of that in any case.
276            configure.arg("no-async");
277        }
278
279        // On Android it looks like not passing no-stdio may cause a build
280        // failure (#13), but most other platforms need it for things like
281        // loading system certificates so only disable it on Android.
282        if target.contains("android") {
283            configure.arg("no-stdio");
284        }
285
286        if target.contains("msvc") {
287            // On MSVC we need nasm.exe to compile the assembly files.
288            // ASM compiling will be enabled if nasm.exe is installed, unless
289            // the environment variable `OPENSSL_RUST_USE_NASM` is set.
290            if self.is_nasm_ready() {
291                // a message to stdout, let user know asm is enabled
292                println!(
293                    "{}: Enable the assembly language routines in building OpenSSL.",
294                    env!("CARGO_PKG_NAME")
295                );
296            } else {
297                configure.arg("no-asm");
298            }
299        }
300
301        let os = match target {
302            "aarch64-apple-darwin" => "darwin64-arm64-cc",
303            // Note that this, and all other android targets, aren't using the
304            // `android64-aarch64` (or equivalent) builtin target. That
305            // apparently has a crazy amount of build logic in OpenSSL 1.1.1
306            // that bypasses basically everything `cc` does, so let's just cop
307            // out and say it's linux and hope it works.
308            "aarch64-linux-android" => "linux-aarch64",
309            "aarch64-unknown-freebsd" => "BSD-generic64",
310            "aarch64-unknown-openbsd" => "BSD-generic64",
311            "aarch64-unknown-linux-gnu" => "linux-aarch64",
312            "aarch64-unknown-linux-musl" => "linux-aarch64",
313            "aarch64-alpine-linux-musl" => "linux-aarch64",
314            "aarch64-chimera-linux-musl" => "linux-aarch64",
315            "aarch64-unknown-netbsd" => "BSD-generic64",
316            "aarch64_be-unknown-netbsd" => "BSD-generic64",
317            "aarch64-pc-windows-msvc" => "VC-WIN64-ARM",
318            "aarch64-uwp-windows-msvc" => "VC-WIN64-ARM-UWP",
319            "arm-linux-androideabi" => "linux-armv4",
320            "armv7-linux-androideabi" => "linux-armv4",
321            "arm-unknown-linux-gnueabi" => "linux-armv4",
322            "arm-unknown-linux-gnueabihf" => "linux-armv4",
323            "arm-unknown-linux-musleabi" => "linux-armv4",
324            "arm-unknown-linux-musleabihf" => "linux-armv4",
325            "arm-chimera-linux-musleabihf" => "linux-armv4",
326            "armv5te-unknown-linux-gnueabi" => "linux-armv4",
327            "armv5te-unknown-linux-musleabi" => "linux-armv4",
328            "armv6-unknown-freebsd" => "BSD-generic32",
329            "armv6-alpine-linux-musleabihf" => "linux-armv6",
330            "armv7-unknown-freebsd" => "BSD-armv4",
331            "armv7-unknown-linux-gnueabi" => "linux-armv4",
332            "armv7-unknown-linux-musleabi" => "linux-armv4",
333            "armv7-unknown-linux-gnueabihf" => "linux-armv4",
334            "armv7-unknown-linux-musleabihf" => "linux-armv4",
335            "armv7-alpine-linux-musleabihf" => "linux-armv4",
336            "armv7-chimera-linux-musleabihf" => "linux-armv4",
337            "armv7-unknown-netbsd-eabihf" => "BSD-generic32",
338            "asmjs-unknown-emscripten" => "gcc",
339            "i586-unknown-linux-gnu" => "linux-elf",
340            "i586-unknown-linux-musl" => "linux-elf",
341            "i586-alpine-linux-musl" => "linux-elf",
342            "i586-unknown-netbsd" => "BSD-x86-elf",
343            "i686-apple-darwin" => "darwin-i386-cc",
344            "i686-linux-android" => "linux-elf",
345            "i686-pc-windows-gnu" => "mingw",
346            "i686-pc-windows-msvc" => "VC-WIN32",
347            "i686-win7-windows-msvc" => "VC-WIN32",
348            "i686-unknown-freebsd" => "BSD-x86-elf",
349            "i686-unknown-haiku" => "haiku-x86",
350            "i686-unknown-linux-gnu" => "linux-elf",
351            "i686-unknown-linux-musl" => "linux-elf",
352            "i686-unknown-netbsd" => "BSD-x86-elf",
353            "i686-uwp-windows-msvc" => "VC-WIN32-UWP",
354            "loongarch64-unknown-linux-gnu" => "linux-generic64",
355            "loongarch64-unknown-linux-musl" => "linux-generic64",
356            "mips-unknown-linux-gnu" => "linux-mips32",
357            "mips-unknown-linux-musl" => "linux-mips32",
358            "mips64-unknown-linux-gnuabi64" => "linux64-mips64",
359            "mips64-unknown-linux-muslabi64" => "linux64-mips64",
360            "mips64-openwrt-linux-musl" => "linux64-mips64",
361            "mips64el-unknown-linux-gnuabi64" => "linux64-mips64",
362            "mips64el-unknown-linux-muslabi64" => "linux64-mips64",
363            "mipsel-unknown-linux-gnu" => "linux-mips32",
364            "mipsel-unknown-linux-musl" => "linux-mips32",
365            "powerpc-unknown-freebsd" => "BSD-ppc",
366            "powerpc-unknown-linux-gnu" => "linux-ppc",
367            "powerpc-unknown-linux-gnuspe" => "linux-ppc",
368            "powerpc-chimera-linux-musl" => "linux-ppc",
369            "powerpc-unknown-netbsd" => "BSD-generic32",
370            "powerpc64-unknown-freebsd" => "BSD-ppc64",
371            "powerpc64-unknown-linux-gnu" => "linux-ppc64",
372            "powerpc64-unknown-linux-gnuelfv2" => "linux-ppc64",
373            "powerpc64-unknown-linux-musl" => "linux-ppc64",
374            "powerpc64-chimera-linux-musl" => "linux-ppc64",
375            "powerpc64le-unknown-freebsd" => "BSD-ppc64le",
376            "powerpc64le-unknown-linux-gnu" => "linux-ppc64le",
377            "powerpc64le-unknown-linux-musl" => "linux-ppc64le",
378            "powerpc64le-alpine-linux-musl" => "linux-ppc64le",
379            "powerpc64le-chimera-linux-musl" => "linux-ppc64le",
380            "riscv64gc-unknown-freebsd" => "BSD-riscv64",
381            "riscv64a23-unknown-linux-gnu" => "linux64-riscv64",
382            "riscv64gc-unknown-linux-gnu" => "linux64-riscv64",
383            "riscv64gc-unknown-linux-musl" => "linux64-riscv64",
384            "riscv64-alpine-linux-musl" => "linux64-riscv64",
385            "riscv64-chimera-linux-musl" => "linux64-riscv64",
386            "riscv64gc-unknown-netbsd" => "BSD-generic64",
387            "s390x-unknown-linux-gnu" => "linux64-s390x",
388            "sparc64-unknown-netbsd" => "BSD-generic64",
389            "sparc64-unknown-linux-gnu" => "linux64-sparcv9",
390            "s390x-unknown-linux-musl" => "linux64-s390x",
391            "s390x-alpine-linux-musl" => "linux64-s390x",
392            "sparcv9-sun-solaris" => "solaris64-sparcv9-gcc",
393            "thumbv7a-uwp-windows-msvc" => "VC-WIN32-ARM-UWP",
394            "x86_64-apple-darwin" => "darwin64-x86_64-cc",
395            "x86_64-linux-android" => "linux-x86_64",
396            "x86_64-linux" => "linux-x86_64",
397            "x86_64-pc-windows-gnu" => "mingw64",
398            "x86_64-pc-windows-gnullvm" => "mingw64",
399            "x86_64-pc-windows-msvc" => "VC-WIN64A",
400            "x86_64-win7-windows-msvc" => "VC-WIN64A",
401            "x86_64-unknown-freebsd" => "BSD-x86_64",
402            "x86_64-unknown-dragonfly" => "BSD-x86_64",
403            "x86_64-unknown-haiku" => "haiku-x86_64",
404            "x86_64-unknown-illumos" => "solaris64-x86_64-gcc",
405            "x86_64-unknown-linux-gnu" => "linux-x86_64",
406            "x86_64-unknown-linux-musl" => "linux-x86_64",
407            "x86_64-alpine-linux-musl" => "linux-x86_64",
408            "x86_64-chimera-linux-musl" => "linux-x86_64",
409            "x86_64-unknown-openbsd" => "BSD-x86_64",
410            "x86_64-unknown-netbsd" => "BSD-x86_64",
411            "x86_64-uwp-windows-msvc" => "VC-WIN64A-UWP",
412            "x86_64-pc-solaris" => "solaris64-x86_64-gcc",
413            "wasm32-unknown-emscripten" => "gcc",
414            "wasm32-unknown-unknown" => "gcc",
415            "wasm32-wasi" => "gcc",
416            "aarch64-apple-ios" => "ios64-cross",
417            "aarch64-apple-visionos" => "ios64-cross",
418            "x86_64-apple-ios" => "iossimulator-x86_64-xcrun",
419            "aarch64-apple-ios-sim" => "iossimulator-arm64-xcrun",
420            "aarch64-apple-visionos-sim" => "iossimulator-arm64-xcrun",
421            "aarch64-apple-ios-macabi" => "darwin64-arm64-cc",
422            "x86_64-apple-ios-macabi" => "darwin64-x86_64-cc",
423            "aarch64-unknown-linux-ohos" => "linux-aarch64",
424            "armv7-unknown-linux-ohos" => "linux-generic32",
425            "x86_64-unknown-linux-ohos" => "linux-x86_64",
426            _ => {
427                return Err(format!(
428                    "don't know how to configure OpenSSL for {}",
429                    target
430                ))
431            }
432        };
433
434        let mut ios_isysroot: std::option::Option<String> = None;
435
436        configure.arg(os);
437
438        // If we're not on MSVC we configure cross compilers and cross tools and
439        // whatnot. Note that this doesn't happen on MSVC b/c things are pretty
440        // different there and this isn't needed most of the time anyway.
441        if !target.contains("msvc") {
442            let mut cc = cc::Build::new();
443            cc.target(target).host(host).warnings(false).opt_level(2);
444            let compiler = cc.get_compiler();
445            let mut cc_env = compiler.cc_env();
446            if cc_env.is_empty() {
447                cc_env = compiler.path().to_path_buf().into_os_string();
448            }
449            configure.env("CC", cc_env);
450            let path = compiler.path().to_str().ok_or("compiler path")?;
451
452            // Both `cc::Build` and `./Configure` take into account
453            // `CROSS_COMPILE` environment variable. So to avoid double
454            // prefix, we unset `CROSS_COMPILE` for `./Configure`.
455            configure.env_remove("CROSS_COMPILE");
456
457            let ar = cc.get_archiver();
458            configure.env("AR", ar.get_program());
459            if ar.get_args().count() != 0 {
460                // On some platforms (like emscripten on windows), the ar to use may not be a
461                // single binary, but instead a multi-argument command like `cmd /c emar.bar`.
462                // We can't convey that through `AR` alone, and so also need to set ARFLAGS.
463                configure.env(
464                    "ARFLAGS",
465                    ar.get_args().collect::<Vec<_>>().join(OsStr::new(" ")),
466                );
467            }
468            let ranlib = cc.get_ranlib();
469            // OpenSSL does not support RANLIBFLAGS. Jam the flags in RANLIB.
470            let mut args = vec![ranlib.get_program()];
471            args.extend(ranlib.get_args());
472            configure.env("RANLIB", args.join(OsStr::new(" ")));
473
474            // Make sure we pass extra flags like `-ffunction-sections` and
475            // other things like ARM codegen flags.
476            let mut skip_next = false;
477            let mut is_isysroot = false;
478            for arg in compiler.args() {
479                // For whatever reason `-static` on MUSL seems to cause
480                // issues...
481                if target.contains("musl") && arg == "-static" {
482                    continue;
483                }
484
485                // cc includes an `-arch` flag for Apple platforms, but we've
486                // already selected an arch implicitly via the target above, and
487                // OpenSSL contains about the conflict if both are specified.
488                if target.contains("apple") {
489                    if arg == "-arch" {
490                        skip_next = true;
491                        continue;
492                    }
493                }
494
495                // cargo-lipo specifies this but OpenSSL complains
496                if target.contains("apple-ios") || target.contains("apple-visionos") {
497                    if arg == "-isysroot" {
498                        is_isysroot = true;
499                        continue;
500                    }
501
502                    if is_isysroot {
503                        is_isysroot = false;
504                        ios_isysroot = Some(arg.to_str().ok_or("isysroot arg")?.to_string());
505                        continue;
506                    }
507                }
508
509                if skip_next {
510                    skip_next = false;
511                    continue;
512                }
513
514                configure.arg(arg);
515            }
516
517            if target == "aarch64-apple-visionos" {
518                if let Some(ref isysr) = ios_isysroot {
519                    configure.env(
520                        "CC",
521                        &format!(
522                            "xcrun -sdk xros cc -isysroot {}",
523                            sanitize_sh(&Path::new(isysr))
524                        ),
525                    );
526                }
527            } else if target == "aarch64-apple-visionos-sim" {
528                if let Some(ref isysr) = ios_isysroot {
529                    configure.env(
530                        "CC",
531                        &format!(
532                            "xcrun -sdk xrsimulator cc -isysroot {}",
533                            sanitize_sh(&Path::new(isysr))
534                        ),
535                    );
536                }
537            } else if os.contains("iossimulator") {
538                if let Some(ref isysr) = ios_isysroot {
539                    configure.env(
540                        "CC",
541                        &format!(
542                            "xcrun -sdk iphonesimulator cc -isysroot {}",
543                            sanitize_sh(&Path::new(isysr))
544                        ),
545                    );
546                }
547            }
548
549            if target == "x86_64-pc-windows-gnu" {
550                // For whatever reason OpenSSL 1.1.1 fails to build on
551                // `x86_64-pc-windows-gnu` in our docker container due to an
552                // error about "too many sections". Having no idea what this
553                // error is about some quick googling yields
554                // https://github.com/cginternals/glbinding/issues/135 which
555                // mysteriously mentions `-Wa,-mbig-obj`, passing a new argument
556                // to the assembler. Now I have no idea what `-mbig-obj` does
557                // for Windows nor why it would matter, but it does seem to fix
558                // compilation issues.
559                //
560                // Note that another entirely unrelated issue -
561                // https://github.com/assimp/assimp/issues/177 - was fixed by
562                // splitting a large file, so presumably OpenSSL has a large
563                // file soemwhere in it? Who knows!
564                configure.arg("-Wa,-mbig-obj");
565            }
566
567            if target.contains("pc-windows-gnu") && path.ends_with("-gcc") {
568                // As of OpenSSL 1.1.1 the build system is now trying to execute
569                // `windres` which doesn't exist when we're cross compiling from
570                // Linux, so we may need to instruct it manually to know what
571                // executable to run.
572                let windres = format!("{}-windres", &path[..path.len() - 4]);
573                configure.env("WINDRES", &windres);
574
575                // Cross-compiling to MinGW apparently has different enough
576                // headers that QUIC no longer compiles. Defer fixing this to
577                // some future day...
578                if !cfg!(windows) {
579                    configure.arg("no-quic");
580                }
581            }
582
583            if target.contains("emscripten") {
584                // As of OpenSSL 1.1.1 the source apparently wants to include
585                // `stdatomic.h`, but this doesn't exist on Emscripten. After
586                // reading OpenSSL's source where the error is, we define this
587                // magical (and probably
588                // compiler-internal-should-not-be-user-defined) macro to say
589                // "no atomics are available" and avoid including such a header.
590                configure.arg("-D__STDC_NO_ATOMICS__");
591            }
592
593            if target.contains("wasi") {
594                configure.args([
595                    // Termios isn't available whatsoever on WASM/WASI so we disable that
596                    "no-ui-console",
597                    // WASI doesn't support UNIX sockets so we preemptively disable it
598                    "no-sock",
599                    // WASI doesn't have a concept of syslog, so we disable it
600                    "-DNO_SYSLOG",
601                    // WASI doesn't support (p)threads. Disabling preemptively.
602                    "no-threads",
603                    // WASI/WASM aren't really friends with ASM, so we disable it as well.
604                    "no-asm",
605                    // Disables the AFALG engine (AFALG-ENGine)
606                    // Since AFALG depends on `AF_ALG` support on the linux kernel side
607                    // it makes sense that we can't use it.
608                    "no-afalgeng",
609                    "-DOPENSSL_NO_AFALGENG=1",
610                    // wasm lacks signal support; to enable minimal signal emulation, compile with
611                    // -D_WASI_EMULATED_SIGNAL and link with -lwasi-emulated-signal
612                    // The link argument is output in the `Artifacts::print_cargo_metadata` method
613                    "-D_WASI_EMULATED_SIGNAL",
614                    // WASI lacks process-associated clocks; to enable emulation of the `times` function using the wall
615                    // clock, which isn't sensitive to whether the program is running or suspended, compile with
616                    // -D_WASI_EMULATED_PROCESS_CLOCKS and link with -lwasi-emulated-process-clocks
617                    // The link argument is output in the `Artifacts::print_cargo_metadata` method
618                    "-D_WASI_EMULATED_PROCESS_CLOCKS",
619                    // WASI lacks a true mmap; to enable minimal mmap emulation, compile
620                    // with -D_WASI_EMULATED_MMAN and link with -lwasi-emulated-mman
621                    // The link argument is output in the `Artifacts::print_cargo_metadata` method
622                    "-D_WASI_EMULATED_MMAN",
623                    // WASI lacks process identifiers; to enable emulation of the `getpid` function using a
624                    // placeholder value, which doesn't reflect the host PID of the program, compile with
625                    // -D_WASI_EMULATED_GETPID and link with -lwasi-emulated-getpid
626                    // The link argument is output in the `Artifacts::print_cargo_metadata` method
627                    "-D_WASI_EMULATED_GETPID",
628                    // WASI doesn't have chmod right now, so don't try to use it.
629                    "-DNO_CHMOD",
630                ]);
631            }
632
633            if target.contains("musl") {
634                // Hack around openssl/openssl#7207 for now
635                configure.arg("-DOPENSSL_NO_SECURE_MEMORY");
636            }
637        }
638
639        // And finally, run the perl configure script!
640        configure.current_dir(&inner_dir);
641        self.run_command(configure, "configuring OpenSSL build")?;
642
643        // On MSVC we use `nmake.exe` with a slightly different invocation, so
644        // have that take a different path than the standard `make` below.
645        if target.contains("msvc") {
646            let mut build =
647                cc::windows_registry::find(target, "nmake.exe").ok_or("failed to find nmake")?;
648            build.arg("build_libs").current_dir(&inner_dir);
649            self.run_command(build, "building OpenSSL")?;
650
651            let mut install =
652                cc::windows_registry::find(target, "nmake.exe").ok_or("failed to find nmake")?;
653            install.arg("install_dev").current_dir(&inner_dir);
654            self.run_command(install, "installing OpenSSL")?;
655        } else {
656            let mut depend = self.cmd_make()?;
657            depend.arg("depend").current_dir(&inner_dir);
658            self.run_command(depend, "building OpenSSL dependencies")?;
659
660            let mut build = self.cmd_make()?;
661            build.arg("build_libs").current_dir(&inner_dir);
662            if !cfg!(windows) {
663                if let Some(s) = env::var_os("CARGO_MAKEFLAGS") {
664                    build.env("MAKEFLAGS", s);
665                }
666            }
667
668            if let Some(ref isysr) = ios_isysroot {
669                let components: Vec<&str> = isysr.split("/SDKs/").collect();
670                build.env("CROSS_TOP", components[0]);
671                build.env("CROSS_SDK", components[1]);
672            }
673
674            self.run_command(build, "building OpenSSL")?;
675
676            let mut install = self.cmd_make()?;
677            install.arg("install_dev").current_dir(&inner_dir);
678            self.run_command(install, "installing OpenSSL")?;
679        }
680
681        let libs = if target.contains("msvc") {
682            vec!["libssl".to_string(), "libcrypto".to_string()]
683        } else {
684            vec!["ssl".to_string(), "crypto".to_string()]
685        };
686
687        fs::remove_dir_all(&inner_dir).map_err(|e| format!("{}: {e}", inner_dir.display()))?;
688
689        Ok(Artifacts {
690            lib_dir: install_dir.join("lib"),
691            bin_dir: install_dir.join("bin"),
692            include_dir: install_dir.join("include"),
693            libs: libs,
694            target: target.to_string(),
695        })
696    }
697
698    #[track_caller]
699    fn run_command(&self, mut command: Command, desc: &str) -> Result<(), String> {
700        println!("running {:?}", command);
701        let status = command.status();
702
703        let verbose_error = match status {
704            Ok(status) if status.success() => return Ok(()),
705            Ok(status) => format!(
706                "'{exe}' reported failure with {status}",
707                exe = command.get_program().to_string_lossy()
708            ),
709            Err(failed) => match failed.kind() {
710                std::io::ErrorKind::NotFound => format!(
711                    "Command '{exe}' not found. Is {exe} installed?",
712                    exe = command.get_program().to_string_lossy()
713                ),
714                _ => format!(
715                    "Could not run '{exe}', because {failed}",
716                    exe = command.get_program().to_string_lossy()
717                ),
718            },
719        };
720        println!("cargo:warning={desc}: {verbose_error}");
721        Err(format!(
722            "Error {desc}:
723    {verbose_error}
724    Command failed: {command:?}"
725        ))
726    }
727}
728
729fn cp_r(src: &Path, dst: &Path) -> Result<(), String> {
730    for f in fs::read_dir(src).map_err(|e| format!("{}: {e}", src.display()))? {
731        let f = match f {
732            Ok(f) => f,
733            _ => continue,
734        };
735        let path = f.path();
736        let name = path
737            .file_name()
738            .ok_or_else(|| format!("bad dir {}", src.display()))?;
739
740        // Skip git metadata as it's been known to cause issues (#26) and
741        // otherwise shouldn't be required
742        if name.to_str() == Some(".git") {
743            continue;
744        }
745
746        let dst = dst.join(name);
747        let ty = f.file_type().map_err(|e| e.to_string())?;
748        if ty.is_dir() {
749            fs::create_dir_all(&dst).map_err(|e| e.to_string())?;
750            cp_r(&path, &dst)?;
751        } else if ty.is_symlink() && path.iter().any(|p| p == "cloudflare-quiche") {
752            // not needed to build
753            continue;
754        } else {
755            let _ = fs::remove_file(&dst);
756            if let Err(e) = fs::copy(&path, &dst) {
757                return Err(format!(
758                    "failed to copy '{}' to '{}': {e}",
759                    path.display(),
760                    dst.display()
761                ));
762            }
763        }
764    }
765    Ok(())
766}
767
768fn sanitize_sh(path: &Path) -> String {
769    if !cfg!(windows) {
770        return path.to_string_lossy().into_owned();
771    }
772    let path = path.to_string_lossy().replace("\\", "/");
773    return change_drive(&path).unwrap_or(path);
774
775    fn change_drive(s: &str) -> Option<String> {
776        let mut ch = s.chars();
777        let drive = ch.next().unwrap_or('C');
778        if ch.next() != Some(':') {
779            return None;
780        }
781        if ch.next() != Some('/') {
782            return None;
783        }
784        Some(format!("/{}/{}", drive, &s[drive.len_utf8() + 2..]))
785    }
786}
787
788// Rust targets whose selected OpenSSL Configuration target adds `-latomic`;
789// based on openssl/Configurations/10-main.conf.
790const TARGETS_NEEDING_LATOMIC: &[&str] = &[
791    // linux-armv4 inherits linux-latomic.
792    "arm-linux-androideabi",
793    "armv7-linux-androideabi",
794    "arm-unknown-linux-gnueabi",
795    "arm-unknown-linux-gnueabihf",
796    "arm-unknown-linux-musleabi",
797    "arm-unknown-linux-musleabihf",
798    "arm-chimera-linux-musleabihf",
799    "armv5te-unknown-linux-gnueabi",
800    "armv5te-unknown-linux-musleabi",
801    "armv7-unknown-linux-gnueabi",
802    "armv7-unknown-linux-musleabi",
803    "armv7-unknown-linux-gnueabihf",
804    "armv7-unknown-linux-musleabihf",
805    "armv7-alpine-linux-musleabihf",
806    "armv7-chimera-linux-musleabihf",
807    // linux-mips32 inherits linux-latomic.
808    "mips-unknown-linux-gnu",
809    "mips-unknown-linux-musl",
810    "mipsel-unknown-linux-gnu",
811    "mipsel-unknown-linux-musl",
812    // linux-ppc inherits linux-latomic.
813    "powerpc-unknown-linux-gnu",
814    "powerpc-unknown-linux-gnuspe",
815    "powerpc-chimera-linux-musl",
816    // linux64-sparcv9 adds -latomic directly.
817    "sparc64-unknown-linux-gnu",
818];
819
820impl Artifacts {
821    pub fn include_dir(&self) -> &Path {
822        &self.include_dir
823    }
824
825    pub fn lib_dir(&self) -> &Path {
826        &self.lib_dir
827    }
828
829    pub fn libs(&self) -> &[String] {
830        &self.libs
831    }
832
833    pub fn needs_latomic(&self) -> bool {
834        TARGETS_NEEDING_LATOMIC.contains(&self.target.as_str())
835    }
836
837    pub fn print_cargo_metadata(&self) {
838        println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
839        for lib in self.libs.iter() {
840            println!("cargo:rustc-link-lib=static={}", lib);
841        }
842        if self.needs_latomic() {
843            println!("cargo:rustc-link-lib=atomic");
844        }
845        println!("cargo:include={}", self.include_dir.display());
846        println!("cargo:lib={}", self.lib_dir.display());
847        if self.target.contains("windows") {
848            println!("cargo:rustc-link-lib=user32");
849            println!("cargo:rustc-link-lib=crypt32");
850            println!("cargo:rustc-link-lib=advapi32");
851        } else if self.target == "wasm32-wasi" {
852            println!("cargo:rustc-link-lib=wasi-emulated-signal");
853            println!("cargo:rustc-link-lib=wasi-emulated-process-clocks");
854            println!("cargo:rustc-link-lib=wasi-emulated-mman");
855            println!("cargo:rustc-link-lib=wasi-emulated-getpid");
856        }
857    }
858}