eclair_builder/
lib.rs

1extern crate cc;
2
3use std::collections::HashMap;
4use std::env;
5use std::fs::File;
6use std::io::Write;
7use std::path::Path;
8use std::process::Command;
9use which::which;
10
11pub struct NoFileYet;
12pub struct WithEclairFile<'a>(&'a str);
13
14pub struct Build<'a, T = NoFileYet> {
15    clang_compiler: &'a str,
16    eclair_compiler: &'a str,
17    datalog_dir: Option<&'a str>, // TODO remove once Eclair is fully self-hosted
18    eclair_file: T,
19}
20
21impl<'a> Build<'a, NoFileYet> {
22    pub fn new() -> Self {
23        Build {
24            clang_compiler: "clang",
25            eclair_compiler: "eclair",
26            eclair_file: NoFileYet,
27            datalog_dir: None,
28        }
29    }
30
31    // NOTE: Only one file allowed for now.
32    pub fn file(self, path: &str) -> Build<'a, WithEclairFile> {
33        Build {
34            clang_compiler: self.clang_compiler,
35            eclair_compiler: self.eclair_compiler,
36            datalog_dir: self.datalog_dir,
37            eclair_file: WithEclairFile(path),
38        }
39    }
40}
41
42impl<'a, T> Build<'a, T> {
43    pub fn eclair(mut self, path: &'a str) -> Self {
44        self.eclair_compiler = path;
45        self
46    }
47
48    // NOTE: for now we only allow clang since that is supported by cc crate.
49    // Technically just llc could also work.
50    pub fn clang(mut self, path: &'a str) -> Self {
51        self.clang_compiler = path;
52        self
53    }
54
55    pub fn datalog_dir(mut self, path: &'a str) -> Self {
56        self.datalog_dir = path.into();
57        self
58    }
59}
60
61impl<'a> Build<'a, WithEclairFile<'a>> {
62    pub fn compile(self) {
63        self.check_valid_setup();
64
65        println!("cargo:rerun-if-changed={}", self.eclair_file.0);
66
67        let llvm_output_file = self.eclair_compile();
68        self.llvm_compile(llvm_output_file);
69    }
70
71    fn check_valid_setup(&self) {
72        let _ = which(&self.eclair_compiler).expect("Could not locate eclair compiler!");
73        let _ = which(&self.clang_compiler).expect("Could not locate clang compiler!");
74    }
75
76    fn eclair_compile(&self) -> std::path::PathBuf {
77        let output = self.llvm_output_file();
78
79        let mut env = HashMap::new();
80        if let Some(dl_dir) = &self.datalog_dir {
81            env.insert("DATALOG_DIR", dl_dir);
82        }
83
84        let llvm_ir = Command::new(&self.eclair_compiler)
85            .args(["compile", &self.eclair_file.0, "--emit", "llvm"])
86            .envs(env)
87            .output()
88            .unwrap();
89
90        let mut f = File::create(&output).unwrap();
91        f.write_all(llvm_ir.stdout.as_slice())
92            .expect("Failed to write LLVM IR generated by Eclair to file");
93
94        output
95    }
96
97    fn llvm_compile(self, llvm_file: std::path::PathBuf) {
98        // TODO allow specifying another library name, right now libeclair.a
99        // is forced because of how eclair_bindings crate works..
100        let lib_name = "eclair";
101        cc::Build::new()
102            .compiler(self.clang_compiler)
103            .file(llvm_file)
104            .compile(lib_name);
105    }
106
107    fn llvm_output_file(&self) -> std::path::PathBuf {
108        let out_dir = env::var("OUT_DIR").unwrap();
109        Path::new(&out_dir).join("program.ll")
110    }
111}