1 /// Build the OCaml code and statically link it into the Rust library; see the
2 /// [ocaml-interop
3 /// example](https://github.com/tezedge/ocaml-interop/blob/master/testing/rust-caller/build.rs)
4 /// for more details. After playing with this a bit, I discovered that the best
5 /// approach to avoid missing symbols was to imitate `dune`: I observed `rm -rf
6 /// _build && dune build ./ocaml/interpret.exe.o --display=verbose` and used
7 /// that as a pattern, now encoded in `ocaml/Makefile` for easier debugging.
8 use std::{env, path::PathBuf, process::Command};
9
10 const LIB_NAME: &'static str = "interpret";
11 const OCAML_DIR: &'static str = "ocaml";
12 const SPEC_DIR: &'static str = "ocaml/spec";
13 const SPEC_REPOSITORY: &'static str = "https://github.com/conrad-watt/spec";
14 const SPEC_REPOSITORY_BRANCH: &'static str = "wasmtime_fuzzing";
15 const SPEC_REPOSITORY_REV: &'static str = "c6bab4461e10229e557aae2e1027cadfce0161ce";
16
main()17 fn main() {
18 println!("cargo:rustc-check-cfg=cfg(feature, values(\"has-libinterpret\"))");
19 println!("cargo:rustc-check-cfg=cfg(fuzzing)");
20 if cfg!(feature = "build-libinterpret") {
21 build();
22 }
23 }
24
build()25 fn build() {
26 let out_dir = &env::var("OUT_DIR").unwrap();
27
28 // Re-run if changed.
29 println!("cargo:rerun-if-changed={OCAML_DIR}/{LIB_NAME}.ml");
30 println!("cargo:rerun-if-changed={OCAML_DIR}/Makefile");
31
32 if let Some(other_dir) = env::var_os("FFI_LIB_DIR") {
33 // Link with a library provided in the `FFI_LIB_DIR`.
34 println!("cargo:rustc-link-search={}", other_dir.to_str().unwrap());
35 println!("cargo:rustc-link-lib=static={LIB_NAME}");
36 } else {
37 // Ensure the spec repository is present.
38 if is_spec_repository_empty(SPEC_DIR) {
39 retrieve_spec_repository(SPEC_DIR)
40 }
41
42 // Build the library to link to.
43 build_lib(out_dir, OCAML_DIR);
44 println!("cargo:rustc-link-search={out_dir}");
45 println!("cargo:rustc-link-lib=static={LIB_NAME}");
46 }
47
48 // Enabling this feature alerts the compiler to use the `with_library`
49 // module.
50 println!("cargo:rustc-cfg=feature=\"has-libinterpret\"");
51 }
52
53 // Build the OCaml library into Cargo's `out` directory.
build_lib(out_dir: &str, ocaml_dir: &str)54 fn build_lib(out_dir: &str, ocaml_dir: &str) {
55 let status = Command::new("make")
56 .arg(format!("BUILD_DIR={out_dir}"))
57 .current_dir(ocaml_dir)
58 .status()
59 .expect("Failed to execute 'make' command to build OCaml library");
60
61 assert!(
62 status.success(),
63 "Failed to build the OCaml library using 'make'."
64 )
65 }
66
67 // Check if the spec repository directory contains any files.
is_spec_repository_empty(destination: &str) -> bool68 fn is_spec_repository_empty(destination: &str) -> bool {
69 PathBuf::from(destination)
70 .read_dir()
71 .map(|mut i| i.next().is_none())
72 .unwrap_or(true)
73 }
74
75 // Clone the spec repository into `destination`. This exists due to the large
76 // size of the dependencies (e.g. KaTeX) that are pulled if this were cloned
77 // recursively as a submodule.
retrieve_spec_repository(destination: &str)78 fn retrieve_spec_repository(destination: &str) {
79 let status = Command::new("git")
80 .arg("clone")
81 .arg(SPEC_REPOSITORY)
82 .arg("-b")
83 .arg(SPEC_REPOSITORY_BRANCH)
84 .arg(destination)
85 .status()
86 .expect("Failed to execute 'git' command to clone spec repository.");
87 assert!(status.success(), "Failed to retrieve the spec repository.");
88
89 let status = Command::new("git")
90 .arg("reset")
91 .arg("--hard")
92 .arg(SPEC_REPOSITORY_REV)
93 .current_dir(destination)
94 .status()
95 .expect("Failed to execute 'git' command to clone spec repository.");
96 assert!(status.success(), "Failed to reset to revision.");
97 }
98