1use {
2 crate::{
3 formatter::{format_interface_file, format_mod_file, format_protocol_file},
4 parser::{ParserError, parse},
5 },
6 std::{
7 env::VarError,
8 fs::File,
9 io::{self, BufWriter, Write},
10 path::{Path, PathBuf},
11 },
12 thiserror::Error,
13};
14
15#[derive(Debug, Error)]
16enum BuilderError {
17 #[error("Could not read {}", .0.display())]
18 ReadFile(PathBuf, #[source] io::Error),
19 #[error("Could not open {} for reading", .0.display())]
20 OpenDir(PathBuf, #[source] io::Error),
21 #[error("Could not read from {}", .0.display())]
22 ReadDir(PathBuf, #[source] io::Error),
23 #[error("Could not parse {}", .0.display())]
24 ParseFile(PathBuf, #[source] ParserError),
25 #[error("Could not format {}", .0.display())]
26 FormatFile(PathBuf, #[source] io::Error),
27 #[error("Could not determine OUT_DIR")]
28 OutDir(#[source] VarError),
29 #[error("Could not create {}", .0.display())]
30 CreateDir(PathBuf, #[source] io::Error),
31 #[error("Could not open {} for writing", .0.display())]
32 OpenFile(PathBuf, #[source] io::Error),
33}
34
35pub struct Builder {
37 build_script: bool,
38 add_default_dir: bool,
39 mutable_data: bool,
40 target_dir: Option<PathBuf>,
41 files: Vec<PathBuf>,
42 dirs: Vec<PathBuf>,
43 wl_client_path: Option<String>,
44}
45
46impl Default for Builder {
47 fn default() -> Self {
48 Self {
49 build_script: true,
50 add_default_dir: true,
51 mutable_data: false,
52 target_dir: Default::default(),
53 files: Default::default(),
54 dirs: Default::default(),
55 wl_client_path: None,
56 }
57 }
58}
59
60impl Builder {
61 pub fn wl_client_path(mut self, path: &str) -> Self {
66 self.wl_client_path = Some(path.into());
67 self
68 }
69
70 pub fn xml_file(mut self, path: impl AsRef<Path>) -> Self {
72 self.files.push(path.as_ref().to_path_buf());
73 self
74 }
75
76 pub fn xml_dir(mut self, path: impl AsRef<Path>) -> Self {
81 self.dirs.push(path.as_ref().to_path_buf());
82 self
83 }
84
85 pub fn with_default_dir(mut self, default_dir: bool) -> Self {
90 self.add_default_dir = default_dir;
91 self
92 }
93
94 pub fn for_build_rs(mut self, build_rs: bool) -> Self {
100 self.build_script = build_rs;
101 self
102 }
103
104 pub fn target_dir(mut self, target_dir: impl AsRef<Path>) -> Self {
111 self.target_dir = Some(target_dir.as_ref().to_path_buf());
112 self
113 }
114
115 pub fn with_mutable_data(mut self, mutable_data: bool) -> Self {
121 self.mutable_data = mutable_data;
122 self
123 }
124
125 pub fn build(self) -> Result<(), crate::Error> {
127 self.build_().map_err(|e| crate::Error(Box::new(e)))
128 }
129
130 fn build_(mut self) -> Result<(), BuilderError> {
131 let mut target_dir = PathBuf::new();
132 if self.build_script {
133 let out_dir = std::env::var("OUT_DIR").map_err(BuilderError::OutDir)?;
134 target_dir.push(out_dir);
135 }
136 if let Some(d) = &self.target_dir {
137 target_dir.push(d);
138 } else {
139 target_dir.push("wayland-protocols");
140 }
141 create_dir(&target_dir)?;
142
143 let mut protocol_objects = vec![];
144
145 if self.add_default_dir {
146 self.dirs.push(PathBuf::from("wayland-protocols"));
147 }
148 for dir in self.dirs {
149 if self.build_script {
150 println!("cargo::rerun-if-changed={}", dir.display());
151 }
152 let iter = match std::fs::read_dir(&dir) {
153 Ok(c) => c,
154 Err(e) => return Err(BuilderError::OpenDir(dir, e)),
155 };
156 for file in iter {
157 let file = match file {
158 Ok(f) => f,
159 Err(e) => return Err(BuilderError::ReadDir(dir, e)),
160 };
161 if !file.file_name().as_encoded_bytes().ends_with(b".xml") {
162 continue;
163 }
164 self.files.push(file.path());
165 }
166 }
167 for file in self.files {
168 if self.build_script {
169 println!("cargo::rerun-if-changed={}", file.display());
170 }
171 let contents = match std::fs::read(&file) {
172 Ok(c) => c,
173 Err(e) => return Err(BuilderError::ReadFile(file, e)),
174 };
175 let protocols = match parse(&contents) {
176 Ok(c) => c,
177 Err(e) => return Err(BuilderError::ParseFile(file, e)),
178 };
179 for protocol in protocols {
180 let protocol_file = format!("{}.rs", protocol.name);
181 format_file(&target_dir.join(&protocol_file), |f| {
182 format_protocol_file(f, &protocol)
183 })?;
184 let dir = target_dir.join(&protocol.name);
185 create_dir(&dir)?;
186 let mut interfaces = vec![];
187 for interface in protocol.interfaces {
188 let file_name = format!("{}.rs", interface.name);
189 format_file(&dir.join(&file_name), |f| {
190 format_interface_file(
191 f,
192 self.wl_client_path.as_deref().unwrap_or("::wl_client"),
193 self.mutable_data,
194 &interface,
195 )
196 })?;
197 let mut enums = vec![];
198 for enum_ in interface.enums {
199 enums.push(enum_.name);
200 }
201 interfaces.push((interface.name, enums));
202 }
203 protocol_objects.push((protocol.name, interfaces));
204 }
205 }
206
207 format_file(&target_dir.join("mod.rs"), |f| {
208 format_mod_file(f, &protocol_objects)
209 })?;
210 Ok(())
211 }
212}
213
214fn create_dir(path: &Path) -> Result<(), BuilderError> {
215 if let Err(e) = std::fs::create_dir_all(path) {
216 return Err(BuilderError::CreateDir(path.to_owned(), e));
217 }
218 Ok(())
219}
220
221fn open_file(path: &Path) -> Result<BufWriter<File>, BuilderError> {
222 let file = File::options()
223 .write(true)
224 .create(true)
225 .truncate(true)
226 .open(path);
227 match file {
228 Ok(f) => Ok(BufWriter::new(f)),
229 Err(e) => Err(BuilderError::OpenFile(path.to_owned(), e)),
230 }
231}
232
233fn format_file(
234 path: &Path,
235 f: impl FnOnce(&mut BufWriter<File>) -> io::Result<()>,
236) -> Result<(), BuilderError> {
237 let mut file = open_file(path)?;
238 let mut res = f(&mut file);
239 if res.is_ok() {
240 res = file.flush();
241 }
242 if let Err(e) = res {
243 return Err(BuilderError::FormatFile(path.to_owned(), e));
244 }
245 Ok(())
246}