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 target_dir: Option<PathBuf>,
40 files: Vec<PathBuf>,
41 dirs: Vec<PathBuf>,
42 wl_client_path: Option<String>,
43}
44
45impl Default for Builder {
46 fn default() -> Self {
47 Self {
48 build_script: true,
49 add_default_dir: true,
50 target_dir: Default::default(),
51 files: Default::default(),
52 dirs: Default::default(),
53 wl_client_path: None,
54 }
55 }
56}
57
58impl Builder {
59 pub fn wl_client_path(mut self, path: &str) -> Self {
64 self.wl_client_path = Some(path.into());
65 self
66 }
67
68 pub fn xml_file(mut self, path: impl AsRef<Path>) -> Self {
70 self.files.push(path.as_ref().to_path_buf());
71 self
72 }
73
74 pub fn xml_dir(mut self, path: impl AsRef<Path>) -> Self {
79 self.dirs.push(path.as_ref().to_path_buf());
80 self
81 }
82
83 pub fn with_default_dir(mut self, default_dir: bool) -> Self {
88 self.add_default_dir = default_dir;
89 self
90 }
91
92 pub fn for_build_rs(mut self, build_rs: bool) -> Self {
98 self.build_script = build_rs;
99 self
100 }
101
102 pub fn target_dir(mut self, target_dir: impl AsRef<Path>) -> Self {
109 self.target_dir = Some(target_dir.as_ref().to_path_buf());
110 self
111 }
112
113 pub fn build(self) -> Result<(), crate::Error> {
115 self.build_().map_err(|e| crate::Error(Box::new(e)))
116 }
117
118 fn build_(mut self) -> Result<(), BuilderError> {
119 let mut target_dir = PathBuf::new();
120 if self.build_script {
121 let out_dir = std::env::var("OUT_DIR").map_err(BuilderError::OutDir)?;
122 target_dir.push(out_dir);
123 }
124 if let Some(d) = &self.target_dir {
125 target_dir.push(d);
126 } else {
127 target_dir.push("wayland-protocols");
128 }
129 create_dir(&target_dir)?;
130
131 let mut protocol_objects = vec![];
132
133 if self.add_default_dir {
134 self.dirs.push(PathBuf::from("wayland-protocols"));
135 }
136 for dir in self.dirs {
137 if self.build_script {
138 println!("cargo::rerun-if-changed={}", dir.display());
139 }
140 let iter = match std::fs::read_dir(&dir) {
141 Ok(c) => c,
142 Err(e) => return Err(BuilderError::OpenDir(dir, e)),
143 };
144 for file in iter {
145 let file = match file {
146 Ok(f) => f,
147 Err(e) => return Err(BuilderError::ReadDir(dir, e)),
148 };
149 if !file.file_name().as_encoded_bytes().ends_with(b".xml") {
150 continue;
151 }
152 self.files.push(file.path());
153 }
154 }
155 for file in self.files {
156 if self.build_script {
157 println!("cargo::rerun-if-changed={}", file.display());
158 }
159 let contents = match std::fs::read(&file) {
160 Ok(c) => c,
161 Err(e) => return Err(BuilderError::ReadFile(file, e)),
162 };
163 let protocols = match parse(&contents) {
164 Ok(c) => c,
165 Err(e) => return Err(BuilderError::ParseFile(file, e)),
166 };
167 for protocol in protocols {
168 let protocol_file = format!("{}.rs", protocol.name);
169 format_file(&target_dir.join(&protocol_file), |f| {
170 format_protocol_file(f, &protocol)
171 })?;
172 let dir = target_dir.join(&protocol.name);
173 create_dir(&dir)?;
174 let mut interfaces = vec![];
175 for interface in protocol.interfaces {
176 let file_name = format!("{}.rs", interface.name);
177 format_file(&dir.join(&file_name), |f| {
178 format_interface_file(
179 f,
180 self.wl_client_path.as_deref().unwrap_or("::wl_client"),
181 &interface,
182 )
183 })?;
184 let mut enums = vec![];
185 for enum_ in interface.enums {
186 enums.push(enum_.name);
187 }
188 interfaces.push((interface.name, enums));
189 }
190 protocol_objects.push((protocol.name, interfaces));
191 }
192 }
193
194 format_file(&target_dir.join("mod.rs"), |f| {
195 format_mod_file(f, &protocol_objects)
196 })?;
197 Ok(())
198 }
199}
200
201fn create_dir(path: &Path) -> Result<(), BuilderError> {
202 if let Err(e) = std::fs::create_dir_all(path) {
203 return Err(BuilderError::CreateDir(path.to_owned(), e));
204 }
205 Ok(())
206}
207
208fn open_file(path: &Path) -> Result<BufWriter<File>, BuilderError> {
209 let file = File::options()
210 .write(true)
211 .create(true)
212 .truncate(true)
213 .open(path);
214 match file {
215 Ok(f) => Ok(BufWriter::new(f)),
216 Err(e) => Err(BuilderError::OpenFile(path.to_owned(), e)),
217 }
218}
219
220fn format_file(
221 path: &Path,
222 f: impl FnOnce(&mut BufWriter<File>) -> io::Result<()>,
223) -> Result<(), BuilderError> {
224 let mut file = open_file(path)?;
225 let mut res = f(&mut file);
226 if res.is_ok() {
227 res = file.flush();
228 }
229 if let Err(e) = res {
230 return Err(BuilderError::FormatFile(path.to_owned(), e));
231 }
232 Ok(())
233}