1 // Copyright 2015-2016 Brian Smith.
2 //
3 // Permission to use, copy, modify, and/or distribute this software for any
4 // purpose with or without fee is hereby granted, provided that the above
5 // copyright notice and this permission notice appear in all copies.
6 //
7 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
10 // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 
15 //! Build the non-Rust components.
16 
17 // It seems like it would be a good idea to use `log!` for logging, but it
18 // isn't worth having the external dependencies (one for the `log` crate, and
19 // another for the concrete logging implementation). Instead we use `eprintln!`
20 // to log everything to stderr.
21 
22 // In the `pregenerate_asm_main()` case we don't want to access (Cargo)
23 // environment variables at all, so avoid `use std::env` here.
24 
25 use std::{
26     fs::{self, DirEntry},
27     path::{Path, PathBuf},
28     process::Command,
29     time::SystemTime,
30 };
31 
32 const X86: &str = "x86";
33 const X86_64: &str = "x86_64";
34 const AARCH64: &str = "aarch64";
35 const ARM: &str = "arm";
36 
37 #[rustfmt::skip]
38 const RING_SRCS: &[(&[&str], &str)] = &[
39     (&[], "crypto/fipsmodule/aes/aes_nohw.c"),
40     (&[], "crypto/fipsmodule/bn/montgomery.c"),
41     (&[], "crypto/fipsmodule/bn/montgomery_inv.c"),
42     (&[], "crypto/limbs/limbs.c"),
43     (&[], "crypto/mem.c"),
44     (&[], "crypto/poly1305/poly1305.c"),
45 
46     (&[AARCH64, ARM, X86_64, X86], "crypto/crypto.c"),
47     (&[AARCH64, ARM, X86_64, X86], "crypto/curve25519/curve25519.c"),
48     (&[AARCH64, ARM, X86_64, X86], "crypto/fipsmodule/ec/ecp_nistz.c"),
49     (&[AARCH64, ARM, X86_64, X86], "crypto/fipsmodule/ec/ecp_nistz256.c"),
50     (&[AARCH64, ARM, X86_64, X86], "crypto/fipsmodule/ec/gfp_p256.c"),
51     (&[AARCH64, ARM, X86_64, X86], "crypto/fipsmodule/ec/gfp_p384.c"),
52 
53     (&[X86_64, X86], "crypto/cpu-intel.c"),
54 
55     (&[X86], "crypto/fipsmodule/aes/asm/aesni-x86.pl"),
56     (&[X86], "crypto/fipsmodule/aes/asm/vpaes-x86.pl"),
57     (&[X86], "crypto/fipsmodule/bn/asm/x86-mont.pl"),
58     (&[X86], "crypto/chacha/asm/chacha-x86.pl"),
59     (&[X86], "crypto/fipsmodule/ec/asm/ecp_nistz256-x86.pl"),
60     (&[X86], "crypto/fipsmodule/modes/asm/ghash-x86.pl"),
61 
62     (&[X86_64], "crypto/fipsmodule/aes/asm/aesni-x86_64.pl"),
63     (&[X86_64], "crypto/fipsmodule/aes/asm/vpaes-x86_64.pl"),
64     (&[X86_64], "crypto/fipsmodule/bn/asm/x86_64-mont.pl"),
65     (&[X86_64], "crypto/fipsmodule/bn/asm/x86_64-mont5.pl"),
66     (&[X86_64], "crypto/chacha/asm/chacha-x86_64.pl"),
67     (&[X86_64], "crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl"),
68     (&[X86_64], "crypto/fipsmodule/modes/asm/aesni-gcm-x86_64.pl"),
69     (&[X86_64], "crypto/fipsmodule/modes/asm/ghash-x86_64.pl"),
70     (&[X86_64], "crypto/poly1305/poly1305_vec.c"),
71     (&[X86_64], SHA512_X86_64),
72     (&[X86_64], "crypto/cipher_extra/asm/chacha20_poly1305_x86_64.pl"),
73 
74     (&[AARCH64, ARM], "crypto/fipsmodule/aes/asm/aesv8-armx.pl"),
75     (&[AARCH64, ARM], "crypto/fipsmodule/modes/asm/ghashv8-armx.pl"),
76 
77     (&[ARM], "crypto/fipsmodule/aes/asm/bsaes-armv7.pl"),
78     (&[ARM], "crypto/fipsmodule/aes/asm/vpaes-armv7.pl"),
79     (&[ARM], "crypto/fipsmodule/bn/asm/armv4-mont.pl"),
80     (&[ARM], "crypto/chacha/asm/chacha-armv4.pl"),
81     (&[ARM], "crypto/curve25519/asm/x25519-asm-arm.S"),
82     (&[ARM], "crypto/fipsmodule/ec/asm/ecp_nistz256-armv4.pl"),
83     (&[ARM], "crypto/fipsmodule/modes/asm/ghash-armv4.pl"),
84     (&[ARM], "crypto/poly1305/poly1305_arm.c"),
85     (&[ARM], "crypto/poly1305/poly1305_arm_asm.S"),
86     (&[ARM], "crypto/fipsmodule/sha/asm/sha256-armv4.pl"),
87     (&[ARM], "crypto/fipsmodule/sha/asm/sha512-armv4.pl"),
88 
89     (&[AARCH64], "crypto/fipsmodule/aes/asm/vpaes-armv8.pl"),
90     (&[AARCH64], "crypto/fipsmodule/bn/asm/armv8-mont.pl"),
91     (&[AARCH64], "crypto/chacha/asm/chacha-armv8.pl"),
92     (&[AARCH64], "crypto/fipsmodule/ec/asm/ecp_nistz256-armv8.pl"),
93     (&[AARCH64], "crypto/fipsmodule/modes/asm/ghash-neon-armv8.pl"),
94     (&[AARCH64], SHA512_ARMV8),
95 ];
96 
97 const SHA256_X86_64: &str = "crypto/fipsmodule/sha/asm/sha256-x86_64.pl";
98 const SHA512_X86_64: &str = "crypto/fipsmodule/sha/asm/sha512-x86_64.pl";
99 
100 const SHA256_ARMV8: &str = "crypto/fipsmodule/sha/asm/sha256-armv8.pl";
101 const SHA512_ARMV8: &str = "crypto/fipsmodule/sha/asm/sha512-armv8.pl";
102 
103 const RING_TEST_SRCS: &[&str] = &[("crypto/constant_time_test.c")];
104 
105 #[rustfmt::skip]
106 const RING_INCLUDES: &[&str] =
107     &[
108       "crypto/curve25519/curve25519_tables.h",
109       "crypto/curve25519/internal.h",
110       "crypto/fipsmodule/bn/internal.h",
111       "crypto/fipsmodule/ec/ecp_nistz256_table.inl",
112       "crypto/fipsmodule/ec/ecp_nistz384.inl",
113       "crypto/fipsmodule/ec/ecp_nistz.h",
114       "crypto/fipsmodule/ec/ecp_nistz384.h",
115       "crypto/fipsmodule/ec/ecp_nistz256.h",
116       "crypto/internal.h",
117       "crypto/limbs/limbs.h",
118       "crypto/limbs/limbs.inl",
119       "crypto/poly1305/internal.h",
120       "include/GFp/aes.h",
121       "include/GFp/arm_arch.h",
122       "include/GFp/base.h",
123       "include/GFp/check.h",
124       "include/GFp/cpu.h",
125       "include/GFp/mem.h",
126       "include/GFp/poly1305.h",
127       "include/GFp/type_check.h",
128       "third_party/fiat/curve25519_32.h",
129       "third_party/fiat/curve25519_64.h",
130     ];
131 
132 #[rustfmt::skip]
133 const RING_PERL_INCLUDES: &[&str] =
134     &["crypto/perlasm/arm-xlate.pl",
135       "crypto/perlasm/x86gas.pl",
136       "crypto/perlasm/x86nasm.pl",
137       "crypto/perlasm/x86asm.pl",
138       "crypto/perlasm/x86_64-xlate.pl"];
139 
140 const RING_BUILD_FILE: &[&str] = &["build.rs"];
141 
142 const PREGENERATED: &str = "pregenerated";
143 
c_flags(target: &Target) -> &'static [&'static str]144 fn c_flags(target: &Target) -> &'static [&'static str] {
145     if target.env != MSVC {
146         static NON_MSVC_FLAGS: &[&str] = &[
147             "-std=c1x", // GCC 4.6 requires "c1x" instead of "c11"
148             "-Wbad-function-cast",
149             "-Wnested-externs",
150             "-Wstrict-prototypes",
151         ];
152         NON_MSVC_FLAGS
153     } else {
154         &[]
155     }
156 }
157 
cpp_flags(target: &Target) -> &'static [&'static str]158 fn cpp_flags(target: &Target) -> &'static [&'static str] {
159     if target.env != MSVC {
160         static NON_MSVC_FLAGS: &[&str] = &[
161             "-pedantic",
162             "-pedantic-errors",
163             "-Wall",
164             "-Wextra",
165             "-Wcast-align",
166             "-Wcast-qual",
167             "-Wconversion",
168             "-Wenum-compare",
169             "-Wfloat-equal",
170             "-Wformat=2",
171             "-Winline",
172             "-Winvalid-pch",
173             "-Wmissing-field-initializers",
174             "-Wmissing-include-dirs",
175             "-Wredundant-decls",
176             "-Wshadow",
177             "-Wsign-compare",
178             "-Wsign-conversion",
179             "-Wundef",
180             "-Wuninitialized",
181             "-Wwrite-strings",
182             "-fno-strict-aliasing",
183             "-fvisibility=hidden",
184         ];
185         NON_MSVC_FLAGS
186     } else {
187         static MSVC_FLAGS: &[&str] = &[
188             "/GS",   // Buffer security checks.
189             "/Gy",   // Enable function-level linking.
190             "/EHsc", // C++ exceptions only, only in C++.
191             "/GR-",  // Disable RTTI.
192             "/Zc:wchar_t",
193             "/Zc:forScope",
194             "/Zc:inline",
195             "/Zc:rvalueCast",
196             // Warnings.
197             "/sdl",
198             "/Wall",
199             "/wd4127", // C4127: conditional expression is constant
200             "/wd4464", // C4464: relative include path contains '..'
201             "/wd4514", // C4514: <name>: unreferenced inline function has be
202             "/wd4710", // C4710: function not inlined
203             "/wd4711", // C4711: function 'function' selected for inline expansion
204             "/wd4820", // C4820: <struct>: <n> bytes padding added after <name>
205             "/wd5045", /* C5045: Compiler will insert Spectre mitigation for memory load if
206                         * /Qspectre switch specified */
207         ];
208         MSVC_FLAGS
209     }
210 }
211 
212 const LD_FLAGS: &[&str] = &[];
213 
214 // None means "any OS" or "any target". The first match in sequence order is
215 // taken.
216 const ASM_TARGETS: &[(&str, Option<&str>, Option<&str>)] = &[
217     ("x86_64", Some("ios"), Some("macosx")),
218     ("x86_64", Some("macos"), Some("macosx")),
219     ("x86_64", Some(WINDOWS), Some("nasm")),
220     ("x86_64", None, Some("elf")),
221     ("aarch64", Some("ios"), Some("ios64")),
222     ("aarch64", Some("macos"), Some("ios64")),
223     ("aarch64", None, Some("linux64")),
224     ("x86", Some(WINDOWS), Some("win32n")),
225     ("x86", Some("ios"), Some("macosx")),
226     ("x86", None, Some("elf")),
227     ("arm", Some("ios"), Some("ios32")),
228     ("arm", None, Some("linux32")),
229     ("wasm32", None, None),
230 ];
231 
232 const WINDOWS: &str = "windows";
233 const MSVC: &str = "msvc";
234 const MSVC_OBJ_OPT: &str = "/Fo";
235 const MSVC_OBJ_EXT: &str = "obj";
236 
main()237 fn main() {
238     if let Ok(package_name) = std::env::var("CARGO_PKG_NAME") {
239         if package_name == "ring" {
240             ring_build_rs_main();
241             return;
242         }
243     }
244 
245     pregenerate_asm_main();
246 }
247 
ring_build_rs_main()248 fn ring_build_rs_main() {
249     use std::env;
250 
251     let out_dir = env::var("OUT_DIR").unwrap();
252     let out_dir = PathBuf::from(out_dir);
253 
254     let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
255     let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
256     let env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
257     let (obj_ext, obj_opt) = if env == MSVC {
258         (MSVC_OBJ_EXT, MSVC_OBJ_OPT)
259     } else {
260         ("o", "-o")
261     };
262 
263     let is_git = std::fs::metadata(".git").is_ok();
264 
265     // Published builds are always release builds.
266     let is_debug = is_git && env::var("DEBUG").unwrap() != "false";
267 
268     let target = Target {
269         arch,
270         os,
271         env,
272         obj_ext,
273         obj_opt,
274         is_git,
275         is_debug,
276     };
277     let pregenerated = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join(PREGENERATED);
278 
279     build_c_code(&target, pregenerated, &out_dir);
280     check_all_files_tracked()
281 }
282 
pregenerate_asm_main()283 fn pregenerate_asm_main() {
284     let pregenerated = PathBuf::from(PREGENERATED);
285     std::fs::create_dir(&pregenerated).unwrap();
286     let pregenerated_tmp = pregenerated.join("tmp");
287     std::fs::create_dir(&pregenerated_tmp).unwrap();
288 
289     for &(target_arch, target_os, perlasm_format) in ASM_TARGETS {
290         // For Windows, package pregenerated object files instead of
291         // pregenerated assembly language source files, so that the user
292         // doesn't need to install the assembler.
293         let asm_dir = if target_os == Some(WINDOWS) {
294             &pregenerated_tmp
295         } else {
296             &pregenerated
297         };
298 
299         if let Some(perlasm_format) = perlasm_format {
300             let perlasm_src_dsts =
301                 perlasm_src_dsts(&asm_dir, target_arch, target_os, perlasm_format);
302             perlasm(&perlasm_src_dsts, target_arch, perlasm_format, None);
303 
304             if target_os == Some(WINDOWS) {
305                 let srcs = asm_srcs(perlasm_src_dsts);
306                 for src in srcs {
307                     let obj_path = obj_path(&pregenerated, &src, MSVC_OBJ_EXT);
308                     run_command(nasm(&src, target_arch, &obj_path));
309                 }
310             }
311         }
312     }
313 }
314 
315 struct Target {
316     arch: String,
317     os: String,
318     env: String,
319     obj_ext: &'static str,
320     obj_opt: &'static str,
321     is_git: bool,
322     is_debug: bool,
323 }
324 
build_c_code(target: &Target, pregenerated: PathBuf, out_dir: &Path)325 fn build_c_code(target: &Target, pregenerated: PathBuf, out_dir: &Path) {
326     #[cfg(not(feature = "wasm32_c"))]
327     {
328         if &target.arch == "wasm32" {
329             return;
330         }
331     }
332 
333     let includes_modified = RING_INCLUDES
334         .iter()
335         .chain(RING_BUILD_FILE.iter())
336         .chain(RING_PERL_INCLUDES.iter())
337         .map(|f| file_modified(Path::new(*f)))
338         .max()
339         .unwrap();
340 
341     fn is_none_or_equals<T>(opt: Option<T>, other: T) -> bool
342     where
343         T: PartialEq,
344     {
345         if let Some(value) = opt {
346             value == other
347         } else {
348             true
349         }
350     }
351 
352     let (_, _, perlasm_format) = ASM_TARGETS
353         .iter()
354         .find(|entry| {
355             let &(entry_arch, entry_os, _) = *entry;
356             entry_arch == target.arch && is_none_or_equals(entry_os, &target.os)
357         })
358         .unwrap();
359 
360     let use_pregenerated = !target.is_git;
361     let warnings_are_errors = target.is_git;
362 
363     let asm_dir = if use_pregenerated {
364         &pregenerated
365     } else {
366         out_dir
367     };
368 
369     let asm_srcs = if let Some(perlasm_format) = perlasm_format {
370         let perlasm_src_dsts =
371             perlasm_src_dsts(asm_dir, &target.arch, Some(&target.os), perlasm_format);
372 
373         if !use_pregenerated {
374             perlasm(
375                 &perlasm_src_dsts[..],
376                 &target.arch,
377                 perlasm_format,
378                 Some(includes_modified),
379             );
380         }
381 
382         let mut asm_srcs = asm_srcs(perlasm_src_dsts);
383 
384         // For Windows we also pregenerate the object files for non-Git builds so
385         // the user doesn't need to install the assembler. On other platforms we
386         // assume the C compiler also assembles.
387         if use_pregenerated && target.os == WINDOWS {
388             // The pregenerated object files always use ".obj" as the extension,
389             // even when the C/C++ compiler outputs files with the ".o" extension.
390             asm_srcs = asm_srcs
391                 .iter()
392                 .map(|src| obj_path(&pregenerated, src.as_path(), "obj"))
393                 .collect::<Vec<_>>();
394         }
395 
396         asm_srcs
397     } else {
398         Vec::new()
399     };
400 
401     let core_srcs = sources_for_arch(&target.arch)
402         .into_iter()
403         .filter(|p| !is_perlasm(&p))
404         .collect::<Vec<_>>();
405 
406     let test_srcs = RING_TEST_SRCS.iter().map(PathBuf::from).collect::<Vec<_>>();
407 
408     let libs = [
409         ("ring-core", &core_srcs[..], &asm_srcs[..]),
410         ("ring-test", &test_srcs[..], &[]),
411     ];
412 
413     // XXX: Ideally, ring-test would only be built for `cargo test`, but Cargo
414     // can't do that yet.
415     libs.iter().for_each(|&(lib_name, srcs, additional_srcs)| {
416         build_library(
417             &target,
418             &out_dir,
419             lib_name,
420             srcs,
421             additional_srcs,
422             warnings_are_errors,
423             includes_modified,
424         )
425     });
426 
427     println!(
428         "cargo:rustc-link-search=native={}",
429         out_dir.to_str().expect("Invalid path")
430     );
431 }
432 
build_library( target: &Target, out_dir: &Path, lib_name: &str, srcs: &[PathBuf], additional_srcs: &[PathBuf], warnings_are_errors: bool, includes_modified: SystemTime, )433 fn build_library(
434     target: &Target,
435     out_dir: &Path,
436     lib_name: &str,
437     srcs: &[PathBuf],
438     additional_srcs: &[PathBuf],
439     warnings_are_errors: bool,
440     includes_modified: SystemTime,
441 ) {
442     // Compile all the (dirty) source files into object files.
443     let objs = additional_srcs
444         .iter()
445         .chain(srcs.iter())
446         .filter(|f| &target.env != "msvc" || f.extension().unwrap().to_str().unwrap() != "S")
447         .map(|f| compile(f, target, warnings_are_errors, out_dir, includes_modified))
448         .collect::<Vec<_>>();
449 
450     // Rebuild the library if necessary.
451     let lib_path = PathBuf::from(out_dir).join(format!("lib{}.a", lib_name));
452 
453     if objs
454         .iter()
455         .map(Path::new)
456         .any(|p| need_run(&p, &lib_path, includes_modified))
457     {
458         let mut c = cc::Build::new();
459 
460         for f in LD_FLAGS {
461             let _ = c.flag(&f);
462         }
463         match target.os.as_str() {
464             "macos" => {
465                 let _ = c.flag("-fPIC");
466                 let _ = c.flag("-Wl,-dead_strip");
467             }
468             _ => {
469                 let _ = c.flag("-Wl,--gc-sections");
470             }
471         }
472         for o in objs {
473             let _ = c.object(o);
474         }
475 
476         // Handled below.
477         let _ = c.cargo_metadata(false);
478 
479         c.compile(
480             lib_path
481                 .file_name()
482                 .and_then(|f| f.to_str())
483                 .expect("No filename"),
484         );
485     }
486 
487     // Link the library. This works even when the library doesn't need to be
488     // rebuilt.
489     println!("cargo:rustc-link-lib=static={}", lib_name);
490 }
491 
compile( p: &Path, target: &Target, warnings_are_errors: bool, out_dir: &Path, includes_modified: SystemTime, ) -> String492 fn compile(
493     p: &Path,
494     target: &Target,
495     warnings_are_errors: bool,
496     out_dir: &Path,
497     includes_modified: SystemTime,
498 ) -> String {
499     let ext = p.extension().unwrap().to_str().unwrap();
500     if ext == "obj" {
501         p.to_str().expect("Invalid path").into()
502     } else {
503         let mut out_path = out_dir.join(p.file_name().unwrap());
504         assert!(out_path.set_extension(target.obj_ext));
505         if need_run(&p, &out_path, includes_modified) {
506             let cmd = if target.os != WINDOWS || ext != "asm" {
507                 cc(p, ext, target, warnings_are_errors, &out_path)
508             } else {
509                 nasm(p, &target.arch, &out_path)
510             };
511 
512             run_command(cmd);
513         }
514         out_path.to_str().expect("Invalid path").into()
515     }
516 }
517 
obj_path(out_dir: &Path, src: &Path, obj_ext: &str) -> PathBuf518 fn obj_path(out_dir: &Path, src: &Path, obj_ext: &str) -> PathBuf {
519     let mut out_path = out_dir.join(src.file_name().unwrap());
520     assert!(out_path.set_extension(obj_ext));
521     out_path
522 }
523 
cc( file: &Path, ext: &str, target: &Target, warnings_are_errors: bool, out_dir: &Path, ) -> Command524 fn cc(
525     file: &Path,
526     ext: &str,
527     target: &Target,
528     warnings_are_errors: bool,
529     out_dir: &Path,
530 ) -> Command {
531     let is_musl = target.env.starts_with("musl");
532 
533     let mut c = cc::Build::new();
534     let _ = c.include("include");
535     match ext {
536         "c" => {
537             for f in c_flags(target) {
538                 let _ = c.flag(f);
539             }
540         }
541         "S" => (),
542         e => panic!("Unsupported file extension: {:?}", e),
543     };
544     for f in cpp_flags(target) {
545         let _ = c.flag(&f);
546     }
547     if target.os != "none"
548         && target.os != "redox"
549         && target.os != "windows"
550         && target.arch != "wasm32"
551     {
552         let _ = c.flag("-fstack-protector");
553     }
554 
555     match (target.os.as_str(), target.env.as_str()) {
556         // ``-gfull`` is required for Darwin's |-dead_strip|.
557         ("macos", _) => {
558             let _ = c.flag("-gfull");
559         }
560         (_, "msvc") => (),
561         _ => {
562             let _ = c.flag("-g3");
563         }
564     };
565     if !target.is_debug {
566         let _ = c.define("NDEBUG", None);
567     }
568 
569     if &target.env == "msvc" {
570         if std::env::var("OPT_LEVEL").unwrap() == "0" {
571             let _ = c.flag("/Od"); // Disable optimization for debug builds.
572                                    // run-time checking: (s)tack frame, (u)ninitialized variables
573             let _ = c.flag("/RTCsu");
574         } else {
575             let _ = c.flag("/Ox"); // Enable full optimization.
576         }
577     }
578 
579     // Allow cross-compiling without a target sysroot for these targets.
580     //
581     // poly1305_vec.c requires <emmintrin.h> which requires <stdlib.h>.
582     if (target.arch == "wasm32" && target.os == "unknown")
583         || (target.os == "linux" && is_musl && target.arch != "x86_64")
584     {
585         if let Ok(compiler) = c.try_get_compiler() {
586             // TODO: Expand this to non-clang compilers in 0.17.0 if practical.
587             if compiler.is_like_clang() {
588                 let _ = c.flag("-nostdlibinc");
589                 let _ = c.define("GFp_NOSTDLIBINC", "1");
590             }
591         }
592     }
593 
594     if warnings_are_errors {
595         let flag = if &target.env != "msvc" {
596             "-Werror"
597         } else {
598             "/WX"
599         };
600         let _ = c.flag(flag);
601     }
602     if is_musl {
603         // Some platforms enable _FORTIFY_SOURCE by default, but musl
604         // libc doesn't support it yet. See
605         // http://wiki.musl-libc.org/wiki/Future_Ideas#Fortify
606         // http://www.openwall.com/lists/musl/2015/02/04/3
607         // http://www.openwall.com/lists/musl/2015/06/17/1
608         let _ = c.flag("-U_FORTIFY_SOURCE");
609     }
610 
611     let mut c = c.get_compiler().to_command();
612     let _ = c
613         .arg("-c")
614         .arg(format!(
615             "{}{}",
616             target.obj_opt,
617             out_dir.to_str().expect("Invalid path")
618         ))
619         .arg(file);
620     c
621 }
622 
nasm(file: &Path, arch: &str, out_file: &Path) -> Command623 fn nasm(file: &Path, arch: &str, out_file: &Path) -> Command {
624     let oformat = match arch {
625         "x86_64" => ("win64"),
626         "x86" => ("win32"),
627         _ => panic!("unsupported arch: {}", arch),
628     };
629     let mut c = Command::new("./target/tools/nasm");
630     let _ = c
631         .arg("-o")
632         .arg(out_file.to_str().expect("Invalid path"))
633         .arg("-f")
634         .arg(oformat)
635         .arg("-Xgnu")
636         .arg("-gcv8")
637         .arg(file);
638     c
639 }
640 
run_command_with_args<S>(command_name: S, args: &[String]) where S: AsRef<std::ffi::OsStr> + Copy,641 fn run_command_with_args<S>(command_name: S, args: &[String])
642 where
643     S: AsRef<std::ffi::OsStr> + Copy,
644 {
645     let mut cmd = Command::new(command_name);
646     let _ = cmd.args(args);
647     run_command(cmd)
648 }
649 
run_command(mut cmd: Command)650 fn run_command(mut cmd: Command) {
651     eprintln!("running {:?}", cmd);
652     let status = cmd.status().unwrap_or_else(|e| {
653         panic!("failed to execute [{:?}]: {}", cmd, e);
654     });
655     if !status.success() {
656         panic!("execution failed");
657     }
658 }
659 
sources_for_arch(arch: &str) -> Vec<PathBuf>660 fn sources_for_arch(arch: &str) -> Vec<PathBuf> {
661     RING_SRCS
662         .iter()
663         .filter(|&&(archs, _)| archs.is_empty() || archs.contains(&arch))
664         .map(|&(_, p)| PathBuf::from(p))
665         .collect::<Vec<_>>()
666 }
667 
perlasm_src_dsts( out_dir: &Path, arch: &str, os: Option<&str>, perlasm_format: &str, ) -> Vec<(PathBuf, PathBuf)>668 fn perlasm_src_dsts(
669     out_dir: &Path,
670     arch: &str,
671     os: Option<&str>,
672     perlasm_format: &str,
673 ) -> Vec<(PathBuf, PathBuf)> {
674     let srcs = sources_for_arch(arch);
675     let mut src_dsts = srcs
676         .iter()
677         .filter(|p| is_perlasm(p))
678         .map(|src| (src.clone(), asm_path(out_dir, src, os, perlasm_format)))
679         .collect::<Vec<_>>();
680 
681     // Some PerlAsm source files need to be run multiple times with different
682     // output paths.
683     {
684         // Appease the borrow checker.
685         let mut maybe_synthesize = |concrete, synthesized| {
686             let concrete_path = PathBuf::from(concrete);
687             if srcs.contains(&concrete_path) {
688                 let synthesized_path = PathBuf::from(synthesized);
689                 src_dsts.push((
690                     concrete_path,
691                     asm_path(out_dir, &synthesized_path, os, perlasm_format),
692                 ))
693             }
694         };
695         maybe_synthesize(SHA512_X86_64, SHA256_X86_64);
696         maybe_synthesize(SHA512_ARMV8, SHA256_ARMV8);
697     }
698 
699     src_dsts
700 }
701 
asm_srcs(perlasm_src_dsts: Vec<(PathBuf, PathBuf)>) -> Vec<PathBuf>702 fn asm_srcs(perlasm_src_dsts: Vec<(PathBuf, PathBuf)>) -> Vec<PathBuf> {
703     perlasm_src_dsts
704         .into_iter()
705         .map(|(_src, dst)| dst)
706         .collect::<Vec<_>>()
707 }
708 
is_perlasm(path: &PathBuf) -> bool709 fn is_perlasm(path: &PathBuf) -> bool {
710     path.extension().unwrap().to_str().unwrap() == "pl"
711 }
712 
asm_path(out_dir: &Path, src: &Path, os: Option<&str>, perlasm_format: &str) -> PathBuf713 fn asm_path(out_dir: &Path, src: &Path, os: Option<&str>, perlasm_format: &str) -> PathBuf {
714     let src_stem = src.file_stem().expect("source file without basename");
715 
716     let dst_stem = src_stem.to_str().unwrap();
717     let dst_extension = if os == Some("windows") { "asm" } else { "S" };
718     let dst_filename = format!("{}-{}.{}", dst_stem, perlasm_format, dst_extension);
719     out_dir.join(dst_filename)
720 }
721 
perlasm( src_dst: &[(PathBuf, PathBuf)], arch: &str, perlasm_format: &str, includes_modified: Option<SystemTime>, )722 fn perlasm(
723     src_dst: &[(PathBuf, PathBuf)],
724     arch: &str,
725     perlasm_format: &str,
726     includes_modified: Option<SystemTime>,
727 ) {
728     for (src, dst) in src_dst {
729         if let Some(includes_modified) = includes_modified {
730             if !need_run(src, dst, includes_modified) {
731                 continue;
732             }
733         }
734 
735         let mut args = Vec::<String>::new();
736         args.push(src.to_string_lossy().into_owned());
737         args.push(perlasm_format.to_owned());
738         if arch == "x86" {
739             args.push("-fPIC".into());
740             args.push("-DOPENSSL_IA32_SSE2".into());
741         }
742         // Work around PerlAsm issue for ARM and AAarch64 targets by replacing
743         // back slashes with forward slashes.
744         let dst = dst
745             .to_str()
746             .expect("Could not convert path")
747             .replace("\\", "/");
748         args.push(dst);
749         run_command_with_args(&get_command("PERL_EXECUTABLE", "perl"), &args);
750     }
751 }
752 
753 fn need_run(source: &Path, target: &Path, includes_modified: SystemTime) -> bool {
754     let s_modified = file_modified(source);
755     if let Ok(target_metadata) = std::fs::metadata(target) {
756         let target_modified = target_metadata.modified().unwrap();
757         s_modified >= target_modified || includes_modified >= target_modified
758     } else {
759         // On error fetching metadata for the target file, assume the target
760         // doesn't exist.
761         true
762     }
763 }
764 
765 fn file_modified(path: &Path) -> SystemTime {
766     let path = Path::new(path);
767     let path_as_str = format!("{:?}", path);
768     std::fs::metadata(path)
769         .expect(&path_as_str)
770         .modified()
771         .expect("nah")
772 }
773 
774 fn get_command(var: &str, default: &str) -> String {
775     std::env::var(var).unwrap_or_else(|_| default.into())
776 }
777 
778 fn check_all_files_tracked() {
779     for path in &["crypto", "include", "third_party/fiat"] {
780         walk_dir(&PathBuf::from(path), &is_tracked);
781     }
782 }
783 
784 fn is_tracked(file: &DirEntry) {
785     let p = file.path();
786     let cmp = |f| p == PathBuf::from(f);
787     let tracked = match p.extension().and_then(|p| p.to_str()) {
788         Some("h") | Some("inl") => RING_INCLUDES.iter().any(cmp),
789         Some("c") | Some("S") | Some("asm") => {
790             RING_SRCS.iter().any(|(_, f)| cmp(f)) || RING_TEST_SRCS.iter().any(cmp)
791         }
792         Some("pl") => RING_SRCS.iter().any(|(_, f)| cmp(f)) || RING_PERL_INCLUDES.iter().any(cmp),
793         _ => true,
794     };
795     if !tracked {
796         panic!("{:?} is not tracked in build.rs", p);
797     }
798 }
799 
800 fn walk_dir<F>(dir: &Path, cb: &F)
801 where
802     F: Fn(&DirEntry),
803 {
804     if dir.is_dir() {
805         for entry in fs::read_dir(dir).unwrap() {
806             let entry = entry.unwrap();
807             let path = entry.path();
808             if path.is_dir() {
809                 walk_dir(&path, cb);
810             } else {
811                 cb(&entry);
812             }
813         }
814     }
815 }
816