1use std::ffi::{OsStr, OsString};
2use std::{env, io};
3use std::io::ErrorKind;
4use std::path::{Path, PathBuf};
5use std::process::{Command, Output};
6use std::str::from_utf8;
7
8#[derive(Default)]
9pub struct SmithyBuild {
10 path: PathBuf,
12 out_dir: PathBuf,
14 projection: Option<OsString>,
16 plugin: Option<OsString>,
18 models: Vec<PathBuf>,
20 configs: Vec<PathBuf>,
22 no_config: bool,
24 force_color: bool,
26 debug: bool,
29 format: bool,
31 quiet: bool,
33 allow_unknown_traits: bool,
35 env: Vec<(OsString, OsString)>,
37}
38
39impl SmithyBuild {
40 pub fn new() -> SmithyBuild {
41 let path = env::current_dir().unwrap();
42 let out_dir = path
43 .join(env::var("OUT_DIR").unwrap_or("target".into()))
44 .join(String::from("smithy"));
45 SmithyBuild {
46 path,
47 out_dir,
48 projection: None,
49 plugin: None,
50 models: vec![],
51 configs: vec![],
52 no_config: false,
53 debug: match env::var("CARGO_LOG") {
54 Ok(s) => s == "debug",
55 Err(_) => false,
56 },
57 force_color: true,
58 format: true,
59 quiet: false,
60 allow_unknown_traits: false,
61 env: vec![],
62 }
63 }
64
65 pub fn path(mut self, path: impl AsRef<Path>) -> SmithyBuild {
69 self.path = env::current_dir().unwrap().join(path);
70 self
71 }
72
73 pub fn out_dir<P: AsRef<Path>>(&mut self, out: P) -> &mut SmithyBuild {
78 self.out_dir = out.as_ref().to_path_buf();
79 self
80 }
81
82 pub fn projection<T: AsRef<OsStr>>(&mut self, projection: T) -> &mut SmithyBuild {
86 self.projection = Some(projection.as_ref().to_owned());
87 self
88 }
89
90 pub fn plugin<T: AsRef<OsStr>>(&mut self, plugin: T) -> &mut SmithyBuild {
94 self.projection = Some(plugin.as_ref().to_owned());
95 self
96 }
97
98 pub fn model<T: AsRef<PathBuf>>(&mut self, model: T) -> &mut SmithyBuild {
102 self.models.push(model.as_ref().to_owned());
103 self
104 }
105
106 pub fn config<T: AsRef<PathBuf>>(&mut self, config: T) -> &mut SmithyBuild {
111 self.configs.push(config.as_ref().to_owned());
112 self
113 }
114
115 pub fn no_config(&mut self) -> &mut Self {
117 self.no_config = true;
118 self
119 }
120
121 pub fn debug(&mut self) -> &mut SmithyBuild {
126 self.debug = true;
127 self
128 }
129
130 pub fn format(&mut self) -> &mut SmithyBuild {
132 self.format = true;
133 self
134 }
135
136 pub fn quiet(&mut self) -> &mut SmithyBuild {
138 self.quiet = true;
139 self
140 }
141
142 pub fn allow_unknown_traits(&mut self) -> &mut SmithyBuild {
144 self.allow_unknown_traits = true;
145 self
146 }
147
148 pub fn env<K, V>(&mut self, key: K, value: V) -> &mut SmithyBuild
150 where
151 K: AsRef<OsStr>,
152 V: AsRef<OsStr>,
153 {
154 self.env
155 .push((key.as_ref().to_owned(), value.as_ref().to_owned()));
156 self
157 }
158
159 fn build_args(&self) -> Vec<OsString> {
160 let mut args = vec![OsString::from("build")];
161
162 args.push("--output".into());
165 args.push(self.out_dir.as_os_str().into());
166
167 if let Some(p) = &self.projection {
168 args.push("--projection".into());
169 args.push(p.into());
170 }
171
172 if let Some(p) = &self.plugin {
173 args.push("--plugin".into());
174 args.push(p.into());
175 }
176
177 for config in &self.configs {
179 args.push("--config".into());
180 args.push(config.into());
181 }
182
183 if self.no_config {
185 args.push("--no-config".into())
186 };
187 if self.allow_unknown_traits {
188 args.push("--aut".into())
189 };
190 if self.force_color {
191 args.push("--force-color".into());
192 }
193
194 args.append(&mut self.common_args());
195
196 args
197 }
198
199 fn common_args(&self) -> Vec<OsString> {
200 let mut args = vec![];
201 if self.debug {
202 args.push("--debug".into());
203 }
204 if self.quiet {
205 args.push("--quiet".into())
206 }
207
208 if self.path.join("model").exists() {
210 println!("cargo:rerun-if-changed=model/");
211 args.push("model/".into());
212 }
213 for model in &self.models {
214 println!("cargo:rerun-if-changed={}", model.display());
215 args.push(model.into());
216 }
217 args
218 }
219
220 fn format_args(&self) -> Vec<OsString> {
221 let mut args = vec![OsString::from("build")];
222 args.append(&mut self.common_args());
224 args
225 }
226
227 pub fn execute(&self) -> io::Result<Output> {
228 if self.format {
229 if !self.quiet {
230 println!("cargo:warning=\r \x1b[32;1mFormatting\x1b[0m smithy models");
231 }
232 Command::new("smithy")
233 .current_dir(&self.path)
234 .args(self.format_args())
235 .envs(self.env.clone())
236 .output()
237 .expect("Failed to execute Smithy format");
238 }
239 let output = Command::new("smithy")
240 .current_dir(&self.path)
241 .args(self.build_args())
242 .envs(self.env.clone())
243 .output()
244 .expect("Failed to execute Smithy build");
245 if !self.quiet {
246 if let Ok(output_str) = from_utf8(output.stderr.as_slice()) {
247 let wrapped = output_str
248 .split("\n")
249 .flat_map(wrap)
250 .collect::<Vec<&str>>()
251 .join("\x0C\r\t");
252 println!("cargo:warning=\r \x1b[32;1mBuilding\x1b[0m smithy models \r\x0C\r\t{}", wrapped);
253 }
254 }
255
256 if !output.status.success() {
257 return Err(io::Error::new(ErrorKind::Other, "Smithy build failed"));
258 }
259
260 println!(
262 "cargo:rustc-env=SMITHY_OUTPUT_DIR={}",
263 self.out_dir.display()
264 );
265
266 Ok(output)
267 }
268}
269
270fn wrap(line: &str) -> Vec<&str> {
272 line.split_at_checked(80)
273 .map_or_else(|| vec![line], |(a, b)| { let mut data = vec![a]; data.append(&mut wrap(b)); return data })
274}