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::fs::File;
8use std::path::PathBuf;
9use std::process::{Command, Output, Stdio};
10use std::{env, io, iter};
11use target_triple::TARGET;
12
13#[derive(Deserialize)]
14pub(crate) struct Metadata {
15 pub target_directory: Directory,
16 pub workspace_root: Directory,
17 pub packages: Vec<PackageMetadata>,
18}
19
20#[derive(Deserialize)]
21pub(crate) struct PackageMetadata {
22 pub name: String,
23 pub targets: Vec<BuildTarget>,
24 pub manifest_path: PathBuf,
25}
26
27#[derive(Deserialize)]
28pub(crate) struct BuildTarget {
29 pub crate_types: Vec<String>,
30}
31
32fn raw_cargo() -> Command {
33 match env::var_os("CARGO") {
34 Some(cargo) => Command::new(cargo),
35 None => Command::new("cargo"),
36 }
37}
38
39fn cargo(project: &Project) -> Command {
40 cargo_with_rustflags(project, &[])
41}
42
43fn cargo_with_rustflags(project: &Project, extra_rustflags: &[&'static str]) -> Command {
44 let mut cmd = raw_cargo();
45 cmd.current_dir(&project.dir);
46 cmd.envs(cargo_target_dir(project));
47 cmd.env_remove("RUSTFLAGS");
48 cmd.env("CARGO_INCREMENTAL", "0");
49 cmd.arg("--offline");
50
51 let rustflags = rustflags::toml(extra_rustflags);
52 cmd.arg(format!("--config=build.rustflags={rustflags}"));
53 cmd.arg(format!("--config=target.{TARGET}.rustflags={rustflags}"));
54
55 cmd
56}
57
58fn cargo_target_dir(project: &Project) -> impl Iterator<Item = (&'static str, PathBuf)> {
59 iter::once((
60 "CARGO_TARGET_DIR",
61 path!(project.target_dir / "tests" / "trybuild"),
62 ))
63}
64
65pub(crate) fn manifest_dir() -> Result<Directory> {
66 if let Some(manifest_dir) = env::var_os("CARGO_MANIFEST_DIR") {
67 return Ok(Directory::from(manifest_dir));
68 }
69 let mut dir = Directory::current()?;
70 loop {
71 if dir.join("Cargo.toml").exists() {
72 return Ok(dir);
73 }
74 dir = dir.parent().ok_or(Error::ProjectDir)?;
75 }
76}
77
78pub(crate) fn build_dependencies(project: &mut Project) -> Result<()> {
79 match File::open(path!(project.workspace / "Cargo.lock")) {
81 Ok(mut workspace_cargo_lock) => {
82 if let Ok(mut new_cargo_lock) = File::create(path!(project.dir / "Cargo.lock")) {
83 let _ = io::copy(&mut workspace_cargo_lock, &mut new_cargo_lock);
86 }
87 }
88 Err(err) => {
89 if err.kind() == io::ErrorKind::NotFound {
90 let _ = cargo(project).arg("generate-lockfile").status();
91 }
92 }
93 }
94
95 let mut command = cargo(project);
96 command
97 .arg(if project.has_pass { "build" } else { "check" })
98 .args(target())
99 .arg("--bin")
100 .arg(&project.name)
101 .args(features(project));
102
103 let status = command.status().map_err(Error::Cargo)?;
104 if !status.success() {
105 return Err(Error::CargoFail);
106 }
107
108 project.keep_going = command
110 .arg("--keep-going")
111 .stdout(Stdio::null())
112 .stderr(Stdio::null())
113 .status()
114 .is_ok_and(|status| status.success());
115
116 Ok(())
117}
118
119pub(crate) fn build_test(project: &Project, name: &Name) -> Result<Output> {
120 let _ = cargo(project)
121 .arg("clean")
122 .arg("--package")
123 .arg(&project.name)
124 .arg("--color=never")
125 .stdout(Stdio::null())
126 .stderr(Stdio::null())
127 .status();
128
129 cargo_with_rustflags(project, &["--diagnostic-width=140"])
130 .arg(if project.has_pass { "build" } else { "check" })
131 .args(target())
132 .arg("--bin")
133 .arg(name)
134 .args(features(project))
135 .arg("--quiet")
136 .arg("--color=never")
137 .arg("--message-format=json")
138 .output()
139 .map_err(Error::Cargo)
140}
141
142pub(crate) fn build_all_tests(project: &Project) -> Result<Output> {
143 let _ = cargo(project)
144 .arg("clean")
145 .arg("--package")
146 .arg(&project.name)
147 .arg("--color=never")
148 .stdout(Stdio::null())
149 .stderr(Stdio::null())
150 .status();
151
152 cargo_with_rustflags(project, &["--diagnostic-width=140"])
153 .arg(if project.has_pass { "build" } else { "check" })
154 .args(target())
155 .arg("--bins")
156 .args(features(project))
157 .arg("--quiet")
158 .arg("--color=never")
159 .arg("--message-format=json")
160 .arg("--keep-going")
161 .output()
162 .map_err(Error::Cargo)
163}
164
165pub(crate) fn run_test(project: &Project, name: &Name) -> Result<Output> {
166 cargo(project)
167 .arg("run")
168 .args(target())
169 .arg("--bin")
170 .arg(name)
171 .args(features(project))
172 .arg("--quiet")
173 .arg("--color=never")
174 .output()
175 .map_err(Error::Cargo)
176}
177
178pub(crate) fn metadata() -> Result<Metadata> {
179 let output = raw_cargo()
180 .arg("metadata")
181 .arg("--no-deps")
182 .arg("--format-version=1")
183 .output()
184 .map_err(Error::Cargo)?;
185
186 serde_json::from_slice(&output.stdout).map_err(|err| {
187 print!("{}", String::from_utf8_lossy(&output.stderr));
188 Error::Metadata(err)
189 })
190}
191
192fn features(project: &Project) -> Vec<String> {
193 match &project.features {
194 Some(features) => vec![
195 "--no-default-features".to_owned(),
196 "--features".to_owned(),
197 features.join(","),
198 ],
199 None => vec![],
200 }
201}
202
203fn target() -> Vec<&'static str> {
204 if cfg!(trybuild_no_target) {
218 vec![]
219 } else {
220 vec!["--target", TARGET]
221 }
222}