1use crate::directory::Directory;
2use crate::error::{Error, Result};
3use crate::manifest::Name;
4use crate::run::Project;
5use crate::rustflags;
6use serde_derive::Deserialize;
7use std::path::PathBuf;
8use std::process::{Command, Output, Stdio};
9use std::{env, fs, iter};
10
11#[derive(Deserialize)]
12pub struct Metadata {
13 pub target_directory: Directory,
14 pub workspace_root: Directory,
15 pub packages: Vec<PackageMetadata>,
16}
17
18#[derive(Deserialize)]
19pub struct PackageMetadata {
20 pub name: String,
21 pub targets: Vec<BuildTarget>,
22 pub manifest_path: PathBuf,
23}
24
25#[derive(Deserialize)]
26pub struct BuildTarget {
27 pub crate_types: Vec<String>,
28}
29
30fn raw_cargo() -> Command {
31 match env::var_os("CARGO") {
32 Some(cargo) => Command::new(cargo),
33 None => Command::new("cargo"),
34 }
35}
36
37fn cargo(project: &Project) -> Command {
38 let mut cmd = raw_cargo();
39 cmd.current_dir(&project.dir);
40 cmd.envs(cargo_target_dir(project));
41 cmd.envs(rustflags::envs());
42 cmd.env("CARGO_INCREMENTAL", "0");
43 cmd.arg("--offline");
44 cmd
45}
46
47fn cargo_target_dir(project: &Project) -> impl Iterator<Item = (&'static str, PathBuf)> {
48 iter::once((
49 "CARGO_TARGET_DIR",
50 path!(project.target_dir / "tests" / "trybuild"),
51 ))
52}
53
54pub fn manifest_dir() -> Result<Directory> {
55 if let Some(manifest_dir) = env::var_os("CARGO_MANIFEST_DIR") {
56 return Ok(Directory::from(manifest_dir));
57 }
58 let mut dir = Directory::current()?;
59 loop {
60 if dir.join("Cargo.toml").exists() {
61 return Ok(dir);
62 }
63 dir = dir.parent().ok_or(Error::ProjectDir)?;
64 }
65}
66
67pub fn build_dependencies(project: &mut Project) -> Result<()> {
68 let workspace_cargo_lock = path!(project.workspace / "Cargo.lock");
69 if workspace_cargo_lock.exists() {
70 let _ = fs::copy(workspace_cargo_lock, path!(project.dir / "Cargo.lock"));
71 } else {
72 let _ = cargo(project).arg("generate-lockfile").status();
73 }
74
75 let mut command = cargo(project);
76 command
77 .arg(if project.has_pass { "build" } else { "check" })
78 .args(target())
79 .arg("--bin")
80 .arg(&project.name)
81 .args(features(project));
82
83 let status = command.status().map_err(Error::Cargo)?;
84 if !status.success() {
85 return Err(Error::CargoFail);
86 }
87
88 // Check if this Cargo contains https://github.com/rust-lang/cargo/pull/10383
89 project.keep_going = command
90 .arg("--keep-going")
91 .stdout(Stdio::null())
92 .stderr(Stdio::null())
93 .status()
94 .map(|status| status.success())
95 .unwrap_or(false);
96
97 Ok(())
98}
99
100pub fn build_test(project: &Project, name: &Name) -> Result<Output> {
101 let _ = cargo(project)
102 .arg("clean")
103 .arg("--package")
104 .arg(&project.name)
105 .arg("--color=never")
106 .stdout(Stdio::null())
107 .stderr(Stdio::null())
108 .status();
109
110 cargo(project)
111 .arg(if project.has_pass { "build" } else { "check" })
112 .args(target())
113 .arg("--bin")
114 .arg(name)
115 .args(features(project))
116 .arg("--quiet")
117 .arg("--color=never")
118 .arg("--message-format=json")
119 .output()
120 .map_err(Error::Cargo)
121}
122
123pub fn build_all_tests(project: &Project) -> Result<Output> {
124 let _ = cargo(project)
125 .arg("clean")
126 .arg("--package")
127 .arg(&project.name)
128 .arg("--color=never")
129 .stdout(Stdio::null())
130 .stderr(Stdio::null())
131 .status();
132
133 cargo(project)
134 .arg(if project.has_pass { "build" } else { "check" })
135 .args(target())
136 .arg("--bins")
137 .args(features(project))
138 .arg("--quiet")
139 .arg("--color=never")
140 .arg("--message-format=json")
141 .arg("--keep-going")
142 .output()
143 .map_err(Error::Cargo)
144}
145
146pub fn run_test(project: &Project, name: &Name) -> Result<Output> {
147 cargo(project)
148 .arg("run")
149 .args(target())
150 .arg("--bin")
151 .arg(name)
152 .args(features(project))
153 .arg("--quiet")
154 .arg("--color=never")
155 .output()
156 .map_err(Error::Cargo)
157}
158
159pub fn metadata() -> Result<Metadata> {
160 let output = raw_cargo()
161 .arg("metadata")
162 .arg("--no-deps")
163 .arg("--format-version=1")
164 .output()
165 .map_err(Error::Cargo)?;
166
167 serde_json::from_slice(&output.stdout).map_err(|err| {
168 print!("{}", String::from_utf8_lossy(&output.stderr));
169 Error::Metadata(err)
170 })
171}
172
173fn features(project: &Project) -> Vec<String> {
174 match &project.features {
175 Some(features) => vec![
176 "--no-default-features".to_owned(),
177 "--features".to_owned(),
178 features.join(","),
179 ],
180 None => vec![],
181 }
182}
183
184fn target() -> Vec<&'static str> {
185 const TARGET: Option<&str> = include!(concat!(env!("OUT_DIR"), "/target"));
186
187 // When --target flag is passed, cargo does not pass RUSTFLAGS to rustc when
188 // building proc-macro and build script even if the host and target triples
189 // are the same. Therefore, if we always pass --target to cargo, tools such
190 // as coverage that require RUSTFLAGS do not work for tests run by trybuild.
191 //
192 // To avoid that problem, do not pass --target to cargo if we know that it
193 // has not been passed.
194 //
195 // Currently, cargo does not have a way to tell the build script whether
196 // --target has been passed or not, and there is no heuristic that can
197 // handle this well.
198 //
199 // Therefore, expose a cfg to always treat the target as host.
200 if cfg!(trybuild_no_target) {
201 vec![]
202 } else if let Some(target) = TARGET {
203 vec!["--target", target]
204 } else {
205 vec![]
206 }
207}
208