1use std::io::Read;
2use std::{env, fs, path, process};
3
4struct BuildDir {
6 pub path: path::PathBuf,
7}
8
9impl BuildDir {
10 fn new() -> Self {
11 let mut random_data = [0u8; 16];
12 let mut file = fs::File::open("/dev/urandom").expect("failed to open /dev/urandom");
13 file.read_exact(&mut random_data)
14 .expect("failed to read /dev/urandom");
15
16 let mut hex_str = String::new();
17 for digit in random_data.iter() {
18 hex_str = hex_str + &format!("{:x}", digit)
19 }
20
21 let mut dir = env::temp_dir();
22 dir.push(format!("build-script-{}", hex_str));
23
24 BuildDir { path: dir }
25 }
26}
27
28impl Drop for BuildDir {
29 fn drop(&mut self) {
30 assert!(self.path.starts_with(env::temp_dir()));
32
33 println!("Removing build crate staging dir: {}", self.path.display());
34 fs::remove_dir_all(&self.path).expect(&format!(
35 "Couldn't clean up build dir: {}",
36 self.path.display()
37 ));
38 }
39}
40
41fn qualify_cargo_toml_paths_in_text(cargo_toml_content: &str, base_dir: &path::Path) -> String {
46 let mut cargo_toml = cargo_toml_content.to_owned();
49 cargo_toml = cargo_toml.replace("path = \"", &format!("path = \"{}/", base_dir.display()));
50 cargo_toml = cargo_toml.replace("path=\"", &format!("path=\"{}/", base_dir.display()));
51 cargo_toml = cargo_toml.replace("path = '", &format!("path = '{}/", base_dir.display()));
52 cargo_toml = cargo_toml.replace("path='", &format!("path='{}/", base_dir.display()));
53 cargo_toml
54}
55
56fn read_toml_section(toml_path: &path::Path, section_name: &str) -> String {
57 let content = fs::read_to_string(toml_path)
58 .expect(&format!("Can't read toml from {}", toml_path.display()));
59
60 let mut section = String::new();
61 let mut in_section = false;
62 for line in content.lines() {
63 if in_section {
64 if line.starts_with("[") {
65 break;
66 } else {
67 section.push_str(line);
68 section.push_str("\n");
69 }
70 } else if line == format!("[{}]", section_name) {
71 in_section = true
72 }
73 }
74
75 section
76}
77
78fn compile_build_crate(
79 build_dir: &BuildDir,
80 cargo: &str,
81 path: &str,
82 ssh_auth_sock: &str,
83 rustup_home: &str,
84 rustup_toolchain: &str,
85 build_crate_src: &path::Path,
86) {
87 let res = process::Command::new(cargo)
88 .args(&["build", "-vv"])
89 .env_clear()
90 .env("PATH", path)
91 .env("SSH_AUTH_SOCK", ssh_auth_sock)
92 .env("RUSTUP_HOME", rustup_home)
93 .env("RUSTUP_TOOLCHAIN", rustup_toolchain)
94 .env("CARGO_TARGET_DIR", build_crate_src.join("build-script-target"))
95 .env("RUSTFLAGS", "--cfg workaround_build")
96 .current_dir(&build_dir.path)
97 .stdout(process::Stdio::inherit())
98 .stderr(process::Stdio::inherit())
99 .output()
100 .expect("failed to compile build-script crate");
101
102 assert!(
103 res.status.success(),
104 "Failed to run compile build crate at {} with {:#?}",
105 build_dir.path.display(),
106 res
107 );
108}
109
110fn run_compiled_build_script(executable_name: &str, working_dir: &path::Path) {
111 let build_script_path = working_dir
113 .join("build-script-target")
114 .join("debug")
115 .join(executable_name);
116
117 let res = process::Command::new(&build_script_path)
118 .current_dir(&working_dir)
119 .stdout(process::Stdio::inherit())
120 .stderr(process::Stdio::inherit())
121 .output()
122 .expect(&format!(
123 "failed to run build script at {}",
124 build_script_path.display()
125 ));
126
127 assert!(
128 res.status.success(),
129 "Failed to run build script at {} with {:#?}",
130 build_script_path.display(),
131 res
132 );
133}
134
135pub fn run_build_script() {
140 let base_dir = env::var("CARGO_MANIFEST_DIR").expect("Can't get CARGO_MANIFEST_DIR from env");
141 let base_dir = path::Path::new(&base_dir);
142
143 let build_rs = base_dir.join("build.rs");
144 let cargo_toml = base_dir.join("Cargo.toml");
145 let cargo_build_lock = base_dir.join("Cargo.build.lock");
146
147 let build_dir = BuildDir::new();
148
149 let cargo = env::var("CARGO").expect("Can't get CARGO from env");
150 let path = env::var("PATH").expect("Can't get PATH from env");
151 let ssh_auth_sock = env::var("SSH_AUTH_SOCK").unwrap_or_default();
152
153 let rustup_home = env::var("RUSTUP_HOME").unwrap_or_default();
154 let rustup_toolchain = env::var("RUSTUP_TOOLCHAIN").unwrap_or_default();
155
156 fs::create_dir(&build_dir.path).expect(&format!(
159 "Couldn't create temp build dir at {}",
160 build_dir.path.display()
161 ));
162
163 fs::create_dir(build_dir.path.join("src")).expect(&format!(
165 "Couldn't create directory {}/src",
166 build_dir.path.display()
167 ));
168
169 fs::copy(&build_rs, build_dir.path.join("src").join("main.rs")).expect(&format!(
170 "Error copying build script {} to {}/src/main.rs",
171 build_rs.display(),
172 build_dir.path.display()
173 ));
174
175 let deps_section = read_toml_section(&cargo_toml, "workaround-build-dependencies");
177
178 let deps_section = qualify_cargo_toml_paths_in_text(&deps_section, &base_dir);
180 let cargo_toml_content = format!(
181 r#"
182[package]
183name = "workaround-build-script"
184version = "0.1.0"
185authors = ["The cargo-5730 crate"]
186edition = "2018"
187
188[dependencies]
189{}
190"#,
191 deps_section
192 );
193
194 fs::write(build_dir.path.join("Cargo.toml"), cargo_toml_content).expect(&format!(
195 "Error writing synthesized Cargo manifest to {}/Cargo.toml",
196 build_dir.path.display()
197 ));
198
199 if cargo_build_lock.exists() {
201 fs::copy(&cargo_build_lock, build_dir.path.join("Cargo.lock")).expect(&format!(
202 "Error copying build manifest lockfile from {} to {}/Cargo.lock",
203 cargo_build_lock.display(),
204 build_dir.path.display()
205 ));
206 }
207
208 compile_build_crate(
209 &build_dir,
210 &cargo,
211 &path,
212 &ssh_auth_sock,
213 &rustup_home,
214 &rustup_toolchain,
215 &base_dir,
216 );
217
218 fs::copy(build_dir.path.join("Cargo.lock"), &cargo_build_lock).expect(&format!(
220 "Error copying out Cargo.lock from {}/Cargo.lock to {}",
221 build_dir.path.display(),
222 cargo_build_lock.display()
223 ));
224
225 run_compiled_build_script("workaround-build-script", &base_dir);
228}
229
230#[cfg(test)]
231mod test {
232 use super::*;
233
234 #[test]
235 fn test_path_fixup_1() {
236 let input = r#"
237[dependencies]
238lib-crate = { path = "../../lib-crate" }
239"#;
240 let expected = r#"
241[dependencies]
242lib-crate = { path = "/basedir/../../lib-crate" }
243"#;
244
245 assert_eq!(
246 qualify_cargo_toml_paths_in_text(input, path::Path::new("/basedir")),
247 expected.to_string()
248 );
249 }
250
251 #[test]
252 fn test_path_fixup_2() {
253 let input = r#"
254[dependencies]
255lib-crate = { path="../../lib-crate" }
256"#;
257 let expected = r#"
258[dependencies]
259lib-crate = { path="/basedir/../../lib-crate" }
260"#;
261
262 assert_eq!(
263 qualify_cargo_toml_paths_in_text(input, path::Path::new("/basedir")),
264 expected.to_string()
265 );
266 }
267
268 #[test]
269 fn test_path_fixup_3() {
270 let input = r#"
271[dependencies]
272lib-crate = { path = '../../lib-crate' }
273"#;
274 let expected = r#"
275[dependencies]
276lib-crate = { path = '/basedir/../../lib-crate' }
277"#;
278
279 assert_eq!(
280 qualify_cargo_toml_paths_in_text(input, path::Path::new("/basedir")),
281 expected.to_string()
282 );
283 }
284
285 #[test]
286 fn test_path_fixup_4() {
287 let input = r#"
288[dependencies]
289lib-crate = { path='../../lib-crate' }
290"#;
291 let expected = r#"
292[dependencies]
293lib-crate = { path='/basedir/../../lib-crate' }
294"#;
295
296 assert_eq!(
297 qualify_cargo_toml_paths_in_text(input, path::Path::new("/basedir")),
298 expected.to_string()
299 );
300 }
301}