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}