1use std::collections::LinkedList;
2use std::fs;
3use std::path::{Path, PathBuf};
4use std::process::Command;
5
6#[cfg(feature = "downloader")]
7pub use downloader::*;
8#[cfg(feature = "format")]
9use format::*;
10
11pub static mut COMPILER_PATH: Option<PathBuf> = None;
13pub static mut GENERATED_PREFIX: Option<String> = None;
14
15#[cfg(feature = "downloader")]
16mod downloader;
17#[cfg(feature = "format")]
18mod format;
19
20#[derive(Debug)]
21pub struct BuildConfig {
22 pub skip_generated_notice: bool,
24 pub generate_module_file: bool,
26 pub format_files: bool,
29}
30
31impl Default for BuildConfig {
32 fn default() -> Self {
33 Self {
34 skip_generated_notice: false,
35 generate_module_file: true,
36 format_files: true,
37 }
38 }
39}
40
41pub fn build_schema_dir(
46 source: impl AsRef<Path>,
47 destination: impl AsRef<Path>,
48 config: &BuildConfig,
49) {
50 if !destination.as_ref().exists() {
51 fs::create_dir_all(destination.as_ref()).unwrap();
52 }
53
54 fs::read_dir(destination.as_ref())
56 .unwrap()
57 .filter_map(|entry| {
58 let entry = entry.unwrap();
59 let name = entry.file_name().to_str().unwrap().to_string();
60 if entry.file_type().unwrap().is_file() && name != "mod.rs" {
61 Some(name)
62 } else {
63 None
64 }
65 })
66 .for_each(|file| fs::remove_file(PathBuf::from(destination.as_ref()).join(file)).unwrap());
67
68 let files = recurse_schema_dir(source, destination.as_ref(), config);
70
71 if config.generate_module_file {
73 let mod_file_path = PathBuf::from(destination.as_ref()).join("mod.rs");
74 fs::write(
75 &mod_file_path,
76 &files
77 .into_iter()
78 .map(|mut schema_name| {
79 schema_name.insert_str(0, "pub mod ");
80 schema_name.push(';');
81 schema_name.push('\n');
82 schema_name
83 })
84 .collect::<String>(),
85 )
86 .unwrap();
87
88 #[cfg(feature = "format")]
89 if config.format_files {
90 fmt_file(mod_file_path);
91 }
92 }
93}
94
95pub fn build_schema(schema: impl AsRef<Path>, destination: impl AsRef<Path>, config: &BuildConfig) {
99 let (schema, destination) = (schema.as_ref(), destination.as_ref());
100 let compiler_path = compiler_path();
101 println!("cargo:rerun-if-changed={}", compiler_path.to_str().unwrap());
102 println!("cargo:rerun-if-changed={}", schema.to_str().unwrap());
103
104 let mut cmd = Command::new(compiler_path);
105 let output = cmd
106 .arg("-i")
107 .arg(schema)
108 .arg("build")
109 .arg("--generator")
110 .arg(format!(
111 "rust:{},noEmitNotice={}",
112 destination.to_str().unwrap(),
113 config.skip_generated_notice
114 ))
115 .output()
116 .expect("Could not run bebopc");
117
118 if !(output.status.success()) {
119 println!(
120 "cargo:warning=Failed to build schema {}",
121 schema.to_str().unwrap()
122 );
123 for line in String::from_utf8(output.stdout).unwrap().lines() {
124 println!("cargo:warning=STDOUT: {}", line);
125 }
126 for line in String::from_utf8(output.stderr).unwrap().lines() {
127 println!("cargo:warning=STDERR: {}", line);
128 }
129 panic!("Failed to build schema!");
130 }
131
132 #[cfg(feature = "format")]
133 if config.format_files {
134 fmt_file(destination);
135 }
136}
137
138fn recurse_schema_dir(
139 dir: impl AsRef<Path>,
140 dest: impl AsRef<Path>,
141 config: &BuildConfig,
142) -> LinkedList<String> {
143 let mut list = LinkedList::new();
144 for dir_entry in fs::read_dir(&dir).unwrap() {
145 let dir_entry = dir_entry.unwrap();
146 let file_type = dir_entry.file_type().unwrap();
147 let file_path = PathBuf::from(dir.as_ref()).join(dir_entry.file_name());
148 if file_type.is_dir() {
149 if dir_entry.file_name() == "ShouldFail" {
150 } else {
152 list.append(&mut recurse_schema_dir(&file_path, dest.as_ref(), config));
153 }
154 } else if file_type.is_file()
155 && file_path
156 .extension()
157 .map(|s| s.to_str().unwrap())
158 .unwrap_or("")
159 == "bop"
160 {
161 let fname = format!(
162 "{}{}",
163 unsafe { GENERATED_PREFIX.as_deref().unwrap_or_else(|| "".into()) },
164 file_stem(file_path.as_path())
165 );
166 build_schema(
167 canonicalize(file_path.to_str().unwrap()),
168 canonicalize(&dest).join(fname.clone() + ".rs"),
169 config,
170 );
171 list.push_back(fname);
172 } else {
173 }
175 }
176 list
177}
178
179fn file_stem(path: impl AsRef<Path>) -> String {
180 path.as_ref()
181 .file_stem()
182 .unwrap()
183 .to_str()
184 .unwrap()
185 .to_string()
186}
187
188fn canonicalize(path: impl AsRef<Path>) -> PathBuf {
189 let p = path
190 .as_ref()
191 .canonicalize()
192 .unwrap()
193 .to_str()
194 .unwrap()
195 .to_string();
196 if p.starts_with(r"\\?\") {
197 p.strip_prefix(r"\\?\").unwrap()
198 } else {
199 &p
200 }
201 .into()
202}
203
204fn compiler_path() -> PathBuf {
205 (unsafe { COMPILER_PATH.clone() }).unwrap_or_else(|| canonicalize("bebopc"))
206}