1 use std::env;
2 use std::fs;
3 use std::io::{self, Write};
4 use std::path::{Path, PathBuf};
5 use std::process::{self, Command, Stdio};
6
7 use super::path::{Dirs, RelPath};
8
9 #[derive(Clone, Debug)]
10 pub(crate) struct Compiler {
11 pub(crate) cargo: PathBuf,
12 pub(crate) rustc: PathBuf,
13 pub(crate) rustdoc: PathBuf,
14 pub(crate) rustflags: String,
15 pub(crate) rustdocflags: String,
16 pub(crate) triple: String,
17 pub(crate) runner: Vec<String>,
18 }
19
20 impl Compiler {
set_cross_linker_and_runner(&mut self)21 pub(crate) fn set_cross_linker_and_runner(&mut self) {
22 match self.triple.as_str() {
23 "aarch64-unknown-linux-gnu" => {
24 // We are cross-compiling for aarch64. Use the correct linker and run tests in qemu.
25 self.rustflags += " -Clinker=aarch64-linux-gnu-gcc";
26 self.rustdocflags += " -Clinker=aarch64-linux-gnu-gcc";
27 self.runner = vec![
28 "qemu-aarch64".to_owned(),
29 "-L".to_owned(),
30 "/usr/aarch64-linux-gnu".to_owned(),
31 ];
32 }
33 "s390x-unknown-linux-gnu" => {
34 // We are cross-compiling for s390x. Use the correct linker and run tests in qemu.
35 self.rustflags += " -Clinker=s390x-linux-gnu-gcc";
36 self.rustdocflags += " -Clinker=s390x-linux-gnu-gcc";
37 self.runner = vec![
38 "qemu-s390x".to_owned(),
39 "-L".to_owned(),
40 "/usr/s390x-linux-gnu".to_owned(),
41 ];
42 }
43 "x86_64-pc-windows-gnu" => {
44 // We are cross-compiling for Windows. Run tests in wine.
45 self.runner = vec!["wine".to_owned()];
46 }
47 _ => {
48 println!("Unknown non-native platform");
49 }
50 }
51 }
52 }
53
54 pub(crate) struct CargoProject {
55 source: &'static RelPath,
56 target: &'static str,
57 }
58
59 impl CargoProject {
new(path: &'static RelPath, target: &'static str) -> CargoProject60 pub(crate) const fn new(path: &'static RelPath, target: &'static str) -> CargoProject {
61 CargoProject { source: path, target }
62 }
63
source_dir(&self, dirs: &Dirs) -> PathBuf64 pub(crate) fn source_dir(&self, dirs: &Dirs) -> PathBuf {
65 self.source.to_path(dirs)
66 }
67
manifest_path(&self, dirs: &Dirs) -> PathBuf68 pub(crate) fn manifest_path(&self, dirs: &Dirs) -> PathBuf {
69 self.source_dir(dirs).join("Cargo.toml")
70 }
71
target_dir(&self, dirs: &Dirs) -> PathBuf72 pub(crate) fn target_dir(&self, dirs: &Dirs) -> PathBuf {
73 RelPath::BUILD.join(self.target).to_path(dirs)
74 }
75
76 #[must_use]
base_cmd(&self, command: &str, cargo: &Path, dirs: &Dirs) -> Command77 fn base_cmd(&self, command: &str, cargo: &Path, dirs: &Dirs) -> Command {
78 let mut cmd = Command::new(cargo);
79
80 cmd.arg(command)
81 .arg("--manifest-path")
82 .arg(self.manifest_path(dirs))
83 .arg("--target-dir")
84 .arg(self.target_dir(dirs))
85 .arg("--locked");
86
87 if dirs.frozen {
88 cmd.arg("--frozen");
89 }
90
91 cmd
92 }
93
94 #[must_use]
build_cmd(&self, command: &str, compiler: &Compiler, dirs: &Dirs) -> Command95 fn build_cmd(&self, command: &str, compiler: &Compiler, dirs: &Dirs) -> Command {
96 let mut cmd = self.base_cmd(command, &compiler.cargo, dirs);
97
98 cmd.arg("--target").arg(&compiler.triple);
99
100 cmd.env("RUSTC", &compiler.rustc);
101 cmd.env("RUSTDOC", &compiler.rustdoc);
102 cmd.env("RUSTFLAGS", &compiler.rustflags);
103 cmd.env("RUSTDOCFLAGS", &compiler.rustdocflags);
104 if !compiler.runner.is_empty() {
105 cmd.env(
106 format!("CARGO_TARGET_{}_RUNNER", compiler.triple.to_uppercase().replace('-', "_")),
107 compiler.runner.join(" "),
108 );
109 }
110
111 cmd
112 }
113
clean(&self, dirs: &Dirs)114 pub(crate) fn clean(&self, dirs: &Dirs) {
115 let _ = fs::remove_dir_all(self.target_dir(dirs));
116 }
117
118 #[must_use]
build(&self, compiler: &Compiler, dirs: &Dirs) -> Command119 pub(crate) fn build(&self, compiler: &Compiler, dirs: &Dirs) -> Command {
120 self.build_cmd("build", compiler, dirs)
121 }
122
123 #[must_use]
test(&self, compiler: &Compiler, dirs: &Dirs) -> Command124 pub(crate) fn test(&self, compiler: &Compiler, dirs: &Dirs) -> Command {
125 self.build_cmd("test", compiler, dirs)
126 }
127
128 #[must_use]
run(&self, compiler: &Compiler, dirs: &Dirs) -> Command129 pub(crate) fn run(&self, compiler: &Compiler, dirs: &Dirs) -> Command {
130 self.build_cmd("run", compiler, dirs)
131 }
132 }
133
134 #[must_use]
hyperfine_command( warmup: u64, runs: u64, prepare: Option<&str>, cmds: &[&str], ) -> Command135 pub(crate) fn hyperfine_command(
136 warmup: u64,
137 runs: u64,
138 prepare: Option<&str>,
139 cmds: &[&str],
140 ) -> Command {
141 let mut bench = Command::new("hyperfine");
142
143 if warmup != 0 {
144 bench.arg("--warmup").arg(warmup.to_string());
145 }
146
147 if runs != 0 {
148 bench.arg("--runs").arg(runs.to_string());
149 }
150
151 if let Some(prepare) = prepare {
152 bench.arg("--prepare").arg(prepare);
153 }
154
155 bench.args(cmds);
156
157 bench
158 }
159
160 #[must_use]
git_command<'a>(repo_dir: impl Into<Option<&'a Path>>, cmd: &str) -> Command161 pub(crate) fn git_command<'a>(repo_dir: impl Into<Option<&'a Path>>, cmd: &str) -> Command {
162 let mut git_cmd = Command::new("git");
163 git_cmd
164 .arg("-c")
165 .arg("user.name=Dummy")
166 .arg("-c")
167 .arg("user.email=dummy@example.com")
168 .arg("-c")
169 .arg("core.autocrlf=false")
170 .arg(cmd);
171 if let Some(repo_dir) = repo_dir.into() {
172 git_cmd.current_dir(repo_dir);
173 }
174 git_cmd
175 }
176
177 #[track_caller]
try_hard_link(src: impl AsRef<Path>, dst: impl AsRef<Path>)178 pub(crate) fn try_hard_link(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
179 let src = src.as_ref();
180 let dst = dst.as_ref();
181 if let Err(_) = fs::hard_link(src, dst) {
182 fs::copy(src, dst).unwrap(); // Fallback to copying if hardlinking failed
183 }
184 }
185
186 #[track_caller]
spawn_and_wait(mut cmd: Command)187 pub(crate) fn spawn_and_wait(mut cmd: Command) {
188 if !cmd.spawn().unwrap().wait().unwrap().success() {
189 process::exit(1);
190 }
191 }
192
193 // Based on the retry function in rust's src/ci/shared.sh
194 #[track_caller]
retry_spawn_and_wait(tries: u64, mut cmd: Command)195 pub(crate) fn retry_spawn_and_wait(tries: u64, mut cmd: Command) {
196 for i in 1..tries + 1 {
197 if i != 1 {
198 println!("Command failed. Attempt {i}/{tries}:");
199 }
200 if cmd.spawn().unwrap().wait().unwrap().success() {
201 return;
202 }
203 std::thread::sleep(std::time::Duration::from_secs(i * 5));
204 }
205 println!("The command has failed after {tries} attempts.");
206 process::exit(1);
207 }
208
209 #[track_caller]
spawn_and_wait_with_input(mut cmd: Command, input: String) -> String210 pub(crate) fn spawn_and_wait_with_input(mut cmd: Command, input: String) -> String {
211 let mut child = cmd
212 .stdin(Stdio::piped())
213 .stdout(Stdio::piped())
214 .spawn()
215 .expect("Failed to spawn child process");
216
217 let mut stdin = child.stdin.take().expect("Failed to open stdin");
218 std::thread::spawn(move || {
219 stdin.write_all(input.as_bytes()).expect("Failed to write to stdin");
220 });
221
222 let output = child.wait_with_output().expect("Failed to read stdout");
223 if !output.status.success() {
224 process::exit(1);
225 }
226
227 String::from_utf8(output.stdout).unwrap()
228 }
229
remove_dir_if_exists(path: &Path)230 pub(crate) fn remove_dir_if_exists(path: &Path) {
231 match fs::remove_dir_all(&path) {
232 Ok(()) => {}
233 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
234 Err(err) => panic!("Failed to remove {path}: {err}", path = path.display()),
235 }
236 }
237
copy_dir_recursively(from: &Path, to: &Path)238 pub(crate) fn copy_dir_recursively(from: &Path, to: &Path) {
239 for entry in fs::read_dir(from).unwrap() {
240 let entry = entry.unwrap();
241 let filename = entry.file_name();
242 if filename == "." || filename == ".." {
243 continue;
244 }
245 if entry.metadata().unwrap().is_dir() {
246 fs::create_dir(to.join(&filename)).unwrap();
247 copy_dir_recursively(&from.join(&filename), &to.join(&filename));
248 } else {
249 fs::copy(from.join(&filename), to.join(&filename)).unwrap();
250 }
251 }
252 }
253
is_ci() -> bool254 pub(crate) fn is_ci() -> bool {
255 env::var("CI").is_ok()
256 }
257
is_ci_opt() -> bool258 pub(crate) fn is_ci_opt() -> bool {
259 env::var("CI_OPT").is_ok()
260 }
261
maybe_incremental(cmd: &mut Command)262 pub(crate) fn maybe_incremental(cmd: &mut Command) {
263 if is_ci() || std::env::var("CARGO_BUILD_INCREMENTAL").map_or(false, |val| val == "false") {
264 // Disabling incr comp reduces cache size and incr comp doesn't save as much on CI anyway
265 cmd.env("CARGO_BUILD_INCREMENTAL", "false");
266 } else {
267 // Force incr comp even in release mode unless in CI or incremental builds are explicitly disabled
268 cmd.env("CARGO_BUILD_INCREMENTAL", "true");
269 }
270 }
271