macrotest/
cargo.rs

1use std::ffi::OsStr;
2use std::io::BufRead;
3use std::path::PathBuf;
4use std::process::Command;
5
6use crate::error::{Error, Result};
7use crate::expand::Project;
8use crate::manifest::Name;
9use crate::rustflags;
10use serde_derive::Deserialize;
11
12#[derive(Deserialize)]
13pub struct Metadata {
14    pub target_directory: PathBuf,
15    pub workspace_root: PathBuf,
16}
17
18fn raw_cargo() -> Command {
19    Command::new(option_env!("CARGO").unwrap_or("cargo"))
20}
21
22fn cargo(project: &Project) -> Command {
23    let mut cmd = raw_cargo();
24    cmd.current_dir(&project.dir);
25    cmd.env("CARGO_TARGET_DIR", &project.inner_target_dir);
26    rustflags::set_env(&mut cmd);
27    cmd
28}
29
30pub(crate) fn metadata() -> Result<Metadata> {
31    let output = raw_cargo()
32        .arg("metadata")
33        .arg("--format-version=1")
34        .output()
35        .map_err(Error::Cargo)?;
36
37    serde_json::from_slice(&output.stdout).map_err(Error::CargoMetadata)
38}
39
40pub(crate) fn expand<I, S>(
41    project: &Project,
42    name: &Name,
43    args: &Option<I>,
44) -> Result<(bool, Vec<u8>)>
45where
46    I: IntoIterator<Item = S> + Clone,
47    S: AsRef<OsStr>,
48{
49    let mut cargo = cargo(project);
50    let cargo = cargo
51        .arg("expand")
52        .arg("--bin")
53        .arg(name.as_ref())
54        .arg("--theme")
55        .arg("none");
56
57    if let Some(args) = args {
58        cargo.args(args.clone());
59    }
60
61    let cargo_expand = cargo
62        .output()
63        .map_err(|e| Error::CargoExpandExecution(e.to_string()))?;
64
65    if !cargo_expand.status.success() {
66        return Ok((false, cargo_expand.stderr));
67    }
68
69    Ok((true, cargo_expand.stdout))
70}
71
72/// Builds dependencies for macro expansion and pipes `cargo` output to `STDOUT`.
73/// Tries to expand macros in `main.rs` and intentionally filters the result.
74/// This function is called before macro expansions to speed them up and
75/// for dependencies build process to be visible for user.
76pub(crate) fn build_dependencies(project: &Project) -> Result<()> {
77    use std::io::Write;
78
79    let stdout = cargo(project)
80        .arg("expand")
81        .arg("--bin")
82        .arg(project.name.clone())
83        .arg("--theme")
84        .arg("none")
85        .stdout(std::process::Stdio::piped())
86        .spawn()?
87        .stdout
88        .ok_or(Error::CargoFail)?;
89
90    let reader = std::io::BufReader::new(stdout);
91
92    // Filter ignored lines and main.rs content
93    reader
94        .lines()
95        .filter_map(|line| line.ok())
96        .filter(|line| !line.starts_with("fn main() {}"))
97        .filter(|line| !line_should_be_ignored(line))
98        .for_each(|line| {
99            let _ = writeln!(std::io::stdout(), "{}", line);
100        });
101
102    Ok(())
103}
104
105const IGNORED_LINES: [&str; 5] = [
106    "#![feature(prelude_import)]",
107    "#[prelude_import]",
108    "use std::prelude::",
109    "#[macro_use]",
110    "extern crate std;",
111];
112
113fn line_should_be_ignored(line: &str) -> bool {
114    for check in IGNORED_LINES.iter() {
115        if line.starts_with(check) {
116            return true;
117        }
118    }
119
120    false
121}