1
2extern crate serde_json as json;
3
4use std::fs;
5use std::path::{Path, PathBuf};
6use std::fs::DirEntry;
7use std::process;
8use std::error::Error;
9use std::io::{self, stderr, Write};
10use std::convert::From;
11use std::result;
12use std::fmt::{self, Display};
13
14macro_rules! otry(
16 ($e:expr) => (match $e { Some(e) => e, None => return None })
17);
18
19#[cfg(target_os="macos")]
22static DYLIB_LINKER_ARGS: &'static[&'static str] = &["--", "--codegen", "link-args=-flat_namespace -undefined suppress"];
23
24#[cfg(not(target_os="macos"))]
25static DYLIB_LINKER_ARGS: &'static[&'static str] = &[];
26
27
28static BIN_LINKER_ARGS: &'static[&'static str] = &[];
29
30
31
32#[derive(Debug)]
33enum MsgError {
34 Msg(&'static str),
35 MsgIo(&'static str, io::Error),
36}
37
38use MsgError::*;
39
40impl Error for MsgError {
41 fn description(&self) -> &str {
42 match self {
43 &Msg(s) => s,
44 &MsgIo(s, ref _err) => s,
45 }
46 }
47}
48
49impl Display for MsgError {
50 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
51 match self {
52 &Msg(s) =>
53 write!(f, "{}", s),
54 &MsgIo(s, ref err) =>
55 write!(f, "{} ({})", s, err),
56 }
57 }
58}
59
60pub fn invoke_with_args_str(args: &[&str], appdir: &Path) {
62 let args_string: Vec<String> = args.into_iter().cloned().map(From::from).collect();
63 invoke_with_args(&args_string, appdir)
64}
65
66pub fn invoke_with_args(args: &[String], appdir: &Path)
67{
68 match ArgsInfo::from_args(&args) {
69 Some(ref ai) => invoke(ai, appdir),
70 None => usage(),
71 }
72}
73
74
75fn usage() {
76 writeln!(stderr(), "Usage:").unwrap();
77 writeln!(stderr(), "\tcargo-erlangapp build [cargo rustc args]").unwrap();
78 writeln!(stderr(), "\tcargo-erlangapp clean [cargo clean args]").unwrap();
79 writeln!(stderr(), "\tcargo-erlangapp test [cargo test args]").unwrap();
80 process::exit(1);
81}
82
83
84
85fn invoke(argsinfo: &ArgsInfo, appdir: &Path) {
86 match do_command(argsinfo, appdir) {
87 Ok(_) => (),
88 Err(err) => {
89 writeln!(stderr(), "Error: {}", err).unwrap();
90 process::exit(1);
91 }
92 }
93}
94
95fn do_command(argsinfo: &ArgsInfo, appdir: &Path) -> Result<(), MsgError> {
96 match argsinfo.command {
97 CargoCommand::Build =>
98 build_crates(argsinfo, appdir),
99 CargoCommand::Test =>
100 test_crates(argsinfo, appdir),
101 CargoCommand::Clean =>
102 clean_crates(argsinfo, appdir),
103 }
104}
105
106fn build_crates(argsinfo: &ArgsInfo, appdir: &Path) -> Result<(), MsgError> {
107 for crate_dir in try!(enumerate_crate_dirs(appdir)).iter() {
109 for target in try!(enumerate_targets(crate_dir)).into_iter() {
110 println!("Building {}", crate_dir.to_string_lossy());
111
112 let mut rustc_args: Vec<String> = match target {
114 Target::Bin(ref s) => vec!("--bin".to_string(), s.to_string()),
115 Target::Dylib(_) => vec!("--lib".to_string()), };
117
118 rustc_args.extend(argsinfo.cargo_args.iter().cloned());
120
121 rustc_args.extend(linker_args(&target).iter().map(|x|x.to_string()));
123
124 try!(cargo_command("rustc", rustc_args.as_slice(), crate_dir));
126
127 let (dst_name, src_name) = target_filenames(&target);
129
130 let mut src_path = crate_dir.join("target");
132 if let Some(ref target_arch) = argsinfo.target {
133 src_path.push(target_arch);
134 }
135 src_path.push( match argsinfo.build_type {
136 BuildType::Release => "release",
137 _ => "debug",
138 });
139 src_path.push(src_name);
140
141 let mut dst_path = appdir.join("priv");
143 dst_path.push("crates");
144 dst_path.push(crate_dir.file_name().unwrap()); try!(fs::create_dir_all(&dst_path)
146 .map_err(|err| MsgIo("cannot create dest directories in priv/", err)));
147 dst_path.push(dst_name);
148
149 try!(fs::copy(src_path, dst_path)
151 .map_err(|err| MsgIo("cannot copy artifact", err)));
152 }
153 };
154
155 Ok(())
156}
157
158fn linker_args(target: &Target) -> &'static [&'static str] {
159 match *target {
160 Target::Dylib(_) => DYLIB_LINKER_ARGS,
161 Target::Bin(_) => BIN_LINKER_ARGS,
162 }
163}
164
165
166#[cfg(target_os="macos")]
170pub fn target_filenames(target: &Target) -> (String, String) {
171 match *target {
172 Target::Bin(ref s) => (s.to_string(), s.to_string()),
173 Target::Dylib(ref s) => ("lib".to_string() + s + ".so", "lib".to_string() + s + ".dylib"),
174 }
175}
176#[cfg(windows)]
180pub fn target_filenames(target: &Target) -> (String, String) {
181 match *target {
182 Target::Bin(ref s) => (s.to_string() + ".exe", s.to_string() + ".exe"),
183 Target::Dylib(ref s) => (s.to_string() + ".dll", s.to_string() + ".dll"),
184 }
185}
186
187#[cfg(all(unix, not(target_os="macos")))]
191pub fn target_filenames(target: &Target) -> (String, String) {
192 match *target {
193 Target::Bin(ref s) => (s.to_string(), s.to_string()),
194 Target::Dylib(ref s) => ("lib".to_string() + s + ".so", "lib".to_string() + s + ".so"),
195 }
196}
197
198
199#[derive(Debug)]
201pub enum Target {
202 Bin(String),
203 Dylib(String),
204}
205
206impl AsRef<String> for Target {
207 fn as_ref(&self) -> &String {
208 match *self {
209 Target::Bin(ref s) => s,
210 Target::Dylib(ref s) => s,
211 }
212 }
213}
214
215impl Target {
216 fn from_json(obj: &json::Value) -> Option<Target> {
218 let name = otry!(obj.find("name")
219 .and_then(|s| s.as_string())
220 .map(|s| s.to_string()));
221 let kinds: Vec<&str> = otry!(obj.find("kind")
222 .and_then(|s| s.as_array())
223 .map(|arr| arr.iter()
224 .filter_map( |s| s.as_string())
225 .collect()));
226
227 if kinds.contains(&"bin") {
228 Some(Target::Bin(name))
229 } else if kinds.contains(&"dylib") || kinds.contains(&"cdylib"){
230 Some(Target::Dylib(name))
231 } else {
232 None
233 }
234 }
235}
236
237fn enumerate_targets(crate_dir: &Path) -> Result<Vec<Target>, MsgError> {
239 let output = try!(process::Command::new("cargo").arg("read-manifest")
240 .current_dir(crate_dir)
241 .output()
242 .map_err(|err| MsgIo("Cannot read crate manifest",err)));
243
244 enumerate_targets_opt(output.stdout.as_slice())
245 .ok_or(Msg("Cannot parse crate manifest"))
246}
247fn enumerate_targets_opt(json_slice: &[u8]) -> Option<Vec<Target>> {
249 let value: json::Value = otry!(json::from_slice(json_slice).ok());
250 value.find("targets")
251 .and_then(|v| v.as_array()) .map(|targets|
253 targets
254 .iter()
255 .filter_map(Target::from_json) .collect())
257}
258
259fn test_crates(argsinfo: &ArgsInfo, appdir: &Path) -> Result<(), MsgError> {
261 for crate_dir in try!(enumerate_crate_dirs(appdir)).iter() {
263 println!("Testing {}", crate_dir.to_string_lossy());
264 try!(cargo_command("test", &argsinfo.cargo_args, crate_dir));
265 };
266 Ok(())
267}
268
269fn clean_crates(argsinfo: &ArgsInfo, appdir: &Path) -> Result<(), MsgError> {
271 for crate_dir in try!(enumerate_crate_dirs(appdir)).iter() {
273 println!("Cleaning {}", crate_dir.to_string_lossy());
274 try!(cargo_command("clean", &argsinfo.cargo_args, crate_dir));
275 };
276
277 let output_dir = appdir.join("priv").join("crates");
279 remove_dir_all_force(output_dir).map_err(|err| MsgIo("can't delete output dir", err))
280}
281
282fn remove_dir_all_force<P: AsRef<Path>>(path: P) -> io::Result<()> {
284
285 match fs::metadata(path.as_ref()) {
286 Err(err) => {
287 match err.kind() {
288 io::ErrorKind::NotFound => Ok(()), _ => Err(err), }
291 },
292 Ok(m) => {
293 match m.is_dir() {
294 true => fs::remove_dir_all(path),
295 false => Ok(()),
296 }
297 },
298 }
299}
300
301fn cargo_command(cmd: &str, args: &[String], dir: &Path) -> Result<(), MsgError> {
302 process::Command::new("cargo")
303 .arg(cmd)
304 .args(args)
305 .current_dir(dir)
306 .status()
307 .map_err(|err| MsgIo("cannot start cargo", err))
308 .and_then(|status| {
309 match status.success() {
310 true => Ok(()),
311 false => Err(Msg("cargo command failed")),
312 }
313 })
314}
315
316
317fn enumerate_crate_dirs(appdir: &Path) -> Result<Vec<PathBuf>, MsgError> {
318
319 appdir
320 .join("crates") .read_dir() .map_err(|err|
323 MsgIo("Cannot read 'crates' directory", err)
324 )
325 .map(|dirs|
326 dirs.filter_map(result::Result::ok) .filter(is_crate) .map(|x| x.path()) .collect()
330 )
331}
332
333fn is_crate(dirent: &DirEntry) -> bool {
334 let mut toml_path = dirent.path();
335 toml_path.push("Cargo.toml");
336 toml_path
337 .metadata() .map(|x| x.is_file()) .unwrap_or(false)
340}
341
342#[derive(Debug)]
343enum CargoCommand { Build, Test, Clean }
344#[derive(Debug)]
345enum BuildType { Release, Debug, DefaultDebug }
346#[derive(Debug)]
347pub struct ArgsInfo {
348 command: CargoCommand,
349 target: Option<String>,
350 build_type: BuildType,
351 cargo_args: Vec<String>,
352}
353
354impl ArgsInfo {
355 pub fn from_args(args: &[String]) -> Option<ArgsInfo> {
356 if args.len() < 2 {
357 return None;
358 }
359
360 let build_type =
361 if find_option(args, "--release") { BuildType::Release }
362 else if find_option(args, "--debug") { BuildType::Debug }
363 else { BuildType::DefaultDebug };
364
365 Some(ArgsInfo {
366 command: otry!(parse_cmd_name(args[1].as_str())),
367 target: find_option_value(&args[2..], "--target").map(Into::into),
368 build_type: build_type,
369 cargo_args: args[2..].into_iter().cloned().collect(),
370 })
371 }
372}
373
374fn parse_cmd_name(arg: &str) -> Option<CargoCommand> {
375 match arg {
376 "build" => Some(CargoCommand::Build),
377 "test" => Some(CargoCommand::Test),
378 "clean" => Some(CargoCommand::Clean),
379 _ => None,
380 }
381}
382
383fn find_option(args: &[String], key: &str) -> bool {
384 args.iter().any(|x| **x == *key)
385}
386
387pub fn find_option_value(args: &[String], key: &str) -> Option<String> {
389 let mut i = args.iter();
390 loop {
391 let arg0 = otry!(i.next());
392 if arg0.starts_with(key) {
393 match arg0.split('=').nth(1) { Some("") => return i.next().map(Clone::clone), Some(x) => return Some(x.to_string()), None => {
398 if **arg0 == *key { let arg1 = otry!(i.next());
400 if **arg1 == *"=" { return i.next().map(Clone::clone) } if arg1.starts_with('=') {
402 return arg1.split('=').nth(1).map(From::from) }
404 }
406 }
407 }
408 }
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 fn find_option_value_wrapper(args: &[&str], key: &str) -> Option<String> {
417 let argsv: Vec<String> = args.into_iter().cloned().map(From::from).collect();
418 find_option_value(&argsv, key)
419 }
420
421 #[test]
422 fn test_find_option_value() {
423 assert_eq!(None, find_option_value_wrapper(&[], "key"));
424 assert_eq!(None, find_option_value_wrapper(&["asdfasdfasdfsdf"], "key"));
425 assert_eq!(None, find_option_value_wrapper(&["asdfasdfasdfsdf", "sdfsf"], "key"));
426 assert_eq!(None, find_option_value_wrapper(&["asdfasdfasdfsdf", "sdfsf", "sdfsdf"], "key"));
427 assert_eq!(Some("value".to_string()), find_option_value_wrapper(&["key=value"], "key"));
428 assert_eq!(Some("value".to_string()), find_option_value_wrapper(&["key", "=value"], "key"));
429 assert_eq!(Some("value".to_string()), find_option_value_wrapper(&["key=", "value"], "key"));
430 assert_eq!(Some("value".to_string()), find_option_value_wrapper(&["key", "=", "value"], "key"));
431 assert_eq!(None, find_option_value_wrapper(&["key", "value"], "key"));
432 assert_eq!(None, find_option_value_wrapper(&["key", "="], "key"));
433 assert_eq!(None, find_option_value_wrapper(&["key"], "key"));
434 assert_eq!(None, find_option_value_wrapper(&["key="], "key"));
435 }
436}