Skip to main content

dealer/
compiler.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5use crate::error::{DealerError, DealerResult, process_output_message};
6
7pub(crate) trait CompilerBackend {
8    fn check(&self, entry_file: &Path, deps: &HashMap<String, PathBuf>) -> DealerResult<()>;
9
10    fn build(
11        &self,
12        entry_file: &Path,
13        deps: &HashMap<String, PathBuf>,
14        output_dir: &Path,
15        package_name: &str,
16        rusttime_path: &Path,
17    ) -> DealerResult<()>;
18
19    fn metadata(&self, entry_file: &Path) -> DealerResult<ProjectMetadata>;
20}
21
22#[derive(Debug, Clone)]
23pub(crate) struct PikoExecutableBackend {
24    pub(crate) piko_path: PathBuf,
25}
26
27impl CompilerBackend for PikoExecutableBackend {
28    fn check(&self, entry_file: &Path, deps: &HashMap<String, PathBuf>) -> DealerResult<()> {
29        let mut command = self.command();
30        command.arg("check").arg(entry_file);
31        add_deps(&mut command, deps);
32        run_empty(command, "piko check")
33    }
34
35    fn build(
36        &self,
37        entry_file: &Path,
38        deps: &HashMap<String, PathBuf>,
39        output_dir: &Path,
40        package_name: &str,
41        rusttime_path: &Path,
42    ) -> DealerResult<()> {
43        let mut command = self.command();
44        command
45            .arg("build")
46            .arg(entry_file)
47            .arg("--output")
48            .arg(output_dir)
49            .arg("--package-name")
50            .arg(package_name)
51            .arg("--rusttime-path")
52            .arg(rusttime_path);
53        add_deps(&mut command, deps);
54        run_empty(command, "piko build")
55    }
56
57    fn metadata(&self, entry_file: &Path) -> DealerResult<ProjectMetadata> {
58        let mut command = self.command();
59        command.arg("metadata").arg(entry_file);
60        let output = run_output(command, "piko metadata")?;
61        serde_json::from_slice(&output.stdout).map_err(|error| {
62            DealerError::Compiler(format!("failed to parse piko metadata JSON: {error}"))
63        })
64    }
65}
66
67impl PikoExecutableBackend {
68    fn command(&self) -> Command {
69        Command::new(&self.piko_path)
70    }
71}
72
73fn add_deps(command: &mut Command, deps: &HashMap<String, PathBuf>) {
74    for (name, path) in deps {
75        command
76            .arg("--dep")
77            .arg(format!("{}={}", name, path.display()));
78    }
79}
80
81fn run_empty(command: Command, label: &str) -> DealerResult<()> {
82    let output = run_output(command, label)?;
83    if output.status.success() {
84        Ok(())
85    } else {
86        Err(DealerError::Compiler(process_output_message(output, label)))
87    }
88}
89
90fn run_output(mut command: Command, label: &str) -> DealerResult<std::process::Output> {
91    command
92        .output()
93        .map_err(|error| DealerError::Compiler(format!("failed to execute {label}: {error}")))
94}
95
96#[derive(Debug, Clone, serde::Deserialize)]
97pub(crate) struct ProjectMetadata {
98    pub(crate) project_type: String,
99    pub(crate) name: String,
100    pub(crate) dependencies: Vec<MetadataDependency>,
101}
102
103#[derive(Debug, Clone, serde::Deserialize)]
104pub(crate) struct MetadataDependency {
105    pub(crate) name: String,
106    pub(crate) source_type: String,
107    pub(crate) arg1: String,
108    pub(crate) arg2: Option<String>,
109}
110
111#[cfg(test)]
112pub(crate) struct StaticCompilerBackend {
113    pub(crate) metadata: HashMap<PathBuf, ProjectMetadata>,
114}
115
116#[cfg(test)]
117impl CompilerBackend for StaticCompilerBackend {
118    fn check(&self, _entry_file: &Path, _deps: &HashMap<String, PathBuf>) -> DealerResult<()> {
119        Ok(())
120    }
121
122    fn build(
123        &self,
124        _entry_file: &Path,
125        _deps: &HashMap<String, PathBuf>,
126        output_dir: &Path,
127        package_name: &str,
128        _rusttime_path: &Path,
129    ) -> DealerResult<()> {
130        std::fs::create_dir_all(output_dir.join("src"))
131            .map_err(|error| DealerError::io(output_dir.join("src"), error))?;
132        std::fs::write(
133            output_dir.join("Cargo.toml"),
134            format!(
135                "[workspace]\n\n[package]\nname = \"{}\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
136                crate::names::sanitize_package_name(package_name)
137            ),
138        )
139        .map_err(|error| DealerError::io(output_dir.join("Cargo.toml"), error))?;
140        std::fs::write(output_dir.join("src/main.rs"), "fn main() {}\n")
141            .map_err(|error| DealerError::io(output_dir.join("src/main.rs"), error))?;
142        Ok(())
143    }
144
145    fn metadata(&self, entry_file: &Path) -> DealerResult<ProjectMetadata> {
146        self.metadata.get(entry_file).cloned().ok_or_else(|| {
147            DealerError::Compiler(format!(
148                "missing test metadata for '{}'",
149                entry_file.display()
150            ))
151        })
152    }
153}