1use std::env;
2use std::fmt;
3use std::fs::{read_to_string, write, File};
4use std::io::{BufReader, Read};
5use std::path::{Path, PathBuf};
6
7use failure::ResultExt;
8use lazy_static::*;
9use regex::Regex;
10
11use crate::error::*;
12use crate::executable::{Cargo, ExecutableRunner, Linker};
13use crate::source::Crate;
14
15const LAST_BUILD_CMD: &str = ".last-build-command";
16const TARGET_NAME: &str = "nvptx64-nvidia-cuda";
17
18#[derive(Debug)]
20pub struct Builder {
21 source_crate: Crate,
22
23 profile: Profile,
24 colors: bool,
25 crate_type: Option<CrateType>,
26}
27
28#[derive(Debug)]
30pub struct BuildOutput<'a> {
31 builder: &'a Builder,
32 output_path: PathBuf,
33 file_suffix: String,
34}
35
36#[derive(Debug)]
38pub enum BuildStatus<'a> {
39 Success(BuildOutput<'a>),
41
42 NotNeeded,
46}
47
48#[derive(PartialEq, Clone, Debug)]
63pub enum Profile {
64 Debug,
66
67 Release,
69}
70
71#[derive(Clone, Copy, Debug)]
93pub enum CrateType {
94 Library,
95 Binary,
96}
97
98impl Builder {
99 pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
120 Ok(Builder {
121 source_crate: Crate::analyse(path).context("Unable to analyse source crate")?,
122
123 profile: Profile::Release, colors: true,
125 crate_type: None,
126 })
127 }
128
129 pub fn is_build_needed() -> bool {
134 let cargo_env = env::var("CARGO");
135 let recursive_env = env::var("PTX_CRATE_BUILDING");
136
137 let is_rls_build = cargo_env.is_ok() && cargo_env.unwrap().ends_with("rls");
138 let is_recursive_build = recursive_env.is_ok() && recursive_env.unwrap() == "1";
139
140 !is_rls_build && !is_recursive_build
141 }
142
143 pub fn disable_colors(mut self) -> Self {
145 self.colors = false;
146 self
147 }
148
149 pub fn set_profile(mut self, profile: Profile) -> Self {
151 self.profile = profile;
152 self
153 }
154
155 pub fn set_crate_type(mut self, crate_type: CrateType) -> Self {
164 self.crate_type = Some(crate_type);
165 self
166 }
167
168 pub fn build(&self) -> Result<BuildStatus> {
170 if !Self::is_build_needed() {
171 return Ok(BuildStatus::NotNeeded);
172 }
173
174 ExecutableRunner::new(Linker).with_args(vec!["-V"]).run()?;
176
177 let mut cargo = ExecutableRunner::new(Cargo);
178 let mut args = Vec::new();
179
180 args.push("rustc");
181
182 if self.profile == Profile::Release {
183 args.push("--release");
184 }
185
186 args.push("--color");
187 args.push(if self.colors { "always" } else { "never" });
188
189 args.push("--target");
190 args.push(TARGET_NAME);
191
192 match self.crate_type {
193 Some(CrateType::Binary) => {
194 args.push("--bin");
195 args.push(self.source_crate.get_name());
196 }
197
198 Some(CrateType::Library) => {
199 args.push("--lib");
200 }
201
202 _ => {}
203 }
204
205 args.push("-v");
206 args.push("--");
207 args.push("--crate-type");
208 args.push("cdylib");
209 args.push("-Zcrate-attr=no_main");
210
211 let output_path = {
212 self.source_crate
213 .get_output_path()
214 .context("Unable to create output path")?
215 };
216
217 cargo
218 .with_args(&args)
219 .with_cwd(self.source_crate.get_path())
220 .with_env("PTX_CRATE_BUILDING", "1")
221 .with_env("CARGO_TARGET_DIR", output_path.clone());
222
223 let cargo_output = cargo.run().map_err(|error| match error.kind() {
224 BuildErrorKind::CommandFailed { stderr, .. } => {
225 let lines = stderr
226 .trim_matches('\n')
227 .split('\n')
228 .filter(Self::output_is_not_verbose)
229 .map(String::from)
230 .collect();
231
232 Error::from(BuildErrorKind::BuildFailed(lines))
233 }
234
235 _ => error,
236 })?;
237
238 Ok(BuildStatus::Success(
239 self.prepare_output(output_path, &cargo_output.stderr)?,
240 ))
241 }
242
243 fn prepare_output(&self, output_path: PathBuf, cargo_stderr: &str) -> Result<BuildOutput> {
244 lazy_static! {
245 static ref SUFFIX_REGEX: Regex =
246 Regex::new(r"-C extra-filename=([\S]+)").expect("Unable to parse regex...");
247 }
248
249 let crate_name = self.source_crate.get_output_file_prefix();
250
251 let build_command = {
253 cargo_stderr
254 .trim_matches('\n')
255 .split('\n')
256 .find(|line| {
257 line.contains(&format!("--crate-name {}", crate_name))
258 && line.contains("--crate-type cdylib")
259 })
260 .map(|line| BuildCommand::Realtime(line.to_string()))
261 .or_else(|| Self::load_cached_build_command(&output_path))
262 .ok_or_else(|| {
263 Error::from(BuildErrorKind::InternalError(String::from(
264 "Unable to find build command of the device crate",
265 )))
266 })?
267 };
268
269 if let BuildCommand::Realtime(ref command) = build_command {
270 Self::store_cached_build_command(&output_path, &command)?;
271 }
272
273 let file_suffix = match SUFFIX_REGEX.captures(&build_command) {
274 Some(caps) => caps[1].to_string(),
275
276 None => {
277 bail!(BuildErrorKind::InternalError(String::from(
278 "Unable to find `extra-filename` rustc flag",
279 )));
280 }
281 };
282
283 Ok(BuildOutput::new(self, output_path, file_suffix))
284 }
285
286 fn output_is_not_verbose(line: &&str) -> bool {
287 !line.starts_with("+ ")
288 && !line.contains("Running")
289 && !line.contains("Fresh")
290 && !line.starts_with("Caused by:")
291 && !line.starts_with(" process didn\'t exit successfully: ")
292 }
293
294 fn load_cached_build_command(output_path: &Path) -> Option<BuildCommand> {
295 match read_to_string(output_path.join(LAST_BUILD_CMD)) {
296 Ok(contents) => Some(BuildCommand::Cached(contents)),
297 Err(_) => None,
298 }
299 }
300
301 fn store_cached_build_command(output_path: &Path, command: &str) -> Result<()> {
302 write(output_path.join(LAST_BUILD_CMD), command.as_bytes())
303 .context(BuildErrorKind::OtherError)?;
304
305 Ok(())
306 }
307}
308
309impl<'a> BuildOutput<'a> {
310 fn new(builder: &'a Builder, output_path: PathBuf, file_suffix: String) -> Self {
311 BuildOutput {
312 builder,
313 output_path,
314 file_suffix,
315 }
316 }
317
318 pub fn get_assembly_path(&self) -> PathBuf {
338 self.output_path
339 .join(TARGET_NAME)
340 .join(self.builder.profile.to_string())
341 .join("deps")
342 .join(format!(
343 "{}{}.ptx",
344 self.builder.source_crate.get_output_file_prefix(),
345 self.file_suffix,
346 ))
347 }
348
349 pub fn dependencies(&self) -> Result<Vec<PathBuf>> {
368 let mut deps_contents = {
369 self.get_deps_file_contents()
370 .context("Unable to get crate deps")?
371 };
372
373 if deps_contents.is_empty() {
374 bail!(BuildErrorKind::InternalError(String::from(
375 "Empty deps file",
376 )));
377 }
378
379 deps_contents = deps_contents
380 .chars()
381 .skip(3) .skip_while(|c| *c != ':')
383 .skip(1)
384 .collect::<String>();
385
386 let cargo_deps = vec![
387 self.builder.source_crate.get_path().join("Cargo.toml"),
388 self.builder.source_crate.get_path().join("Cargo.lock"),
389 ];
390
391 Ok(deps_contents
392 .trim()
393 .split(' ')
394 .map(|item| PathBuf::from(item.trim()))
395 .chain(cargo_deps.into_iter())
396 .collect())
397 }
398
399 fn get_deps_file_contents(&self) -> Result<String> {
400 let crate_deps_path = self
401 .output_path
402 .join(TARGET_NAME)
403 .join(self.builder.profile.to_string())
404 .join(format!(
405 "{}.d",
406 self.builder
407 .source_crate
408 .get_deps_file_prefix(self.builder.crate_type)?
409 ));
410
411 let mut crate_deps_reader =
412 BufReader::new(File::open(crate_deps_path).context(BuildErrorKind::OtherError)?);
413
414 let mut crate_deps_contents = String::new();
415
416 crate_deps_reader
417 .read_to_string(&mut crate_deps_contents)
418 .context(BuildErrorKind::OtherError)?;
419
420 Ok(crate_deps_contents)
421 }
422}
423
424impl fmt::Display for Profile {
425 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
426 match self {
427 Profile::Debug => write!(f, "debug"),
428 Profile::Release => write!(f, "release"),
429 }
430 }
431}
432
433enum BuildCommand {
434 Realtime(String),
435 Cached(String),
436}
437
438impl std::ops::Deref for BuildCommand {
439 type Target = str;
440
441 fn deref(&self) -> &str {
442 match self {
443 BuildCommand::Realtime(line) => &line,
444 BuildCommand::Cached(line) => &line,
445 }
446 }
447}