1 // Copyright 2016 Mozilla Foundation
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use crate::compiler::args::*;
16 use crate::compiler::c::{CCompilerImpl, CCompilerKind, Language, ParsedArguments};
17 use crate::compiler::{
18 clang, gcc, write_temp_file, Cacheable, ColorMode, CompileCommand, CompilerArguments,
19 };
20 use crate::dist;
21 use crate::mock_command::{CommandCreatorSync, RunCommand};
22 use crate::util::{run_input_output, SpawnExt};
23 use futures::future::Future;
24 use futures_03::executor::ThreadPool;
25 use local_encoding::{Encoder, Encoding};
26 use log::Level::Debug;
27 use std::collections::{HashMap, HashSet};
28 use std::ffi::{OsStr, OsString};
29 use std::fs::File;
30 use std::io::{self, BufWriter, Write};
31 use std::path::{Path, PathBuf};
32 use std::process::{self, Stdio};
33
34 use crate::errors::*;
35
36 /// A struct on which to implement `CCompilerImpl`.
37 ///
38 /// Needs a little bit of state just to persist `includes_prefix`.
39 #[derive(Debug, PartialEq, Clone)]
40 pub struct MSVC {
41 /// The prefix used in the output of `-showIncludes`.
42 pub includes_prefix: String,
43 pub is_clang: bool,
44 }
45
46 impl CCompilerImpl for MSVC {
kind(&self) -> CCompilerKind47 fn kind(&self) -> CCompilerKind {
48 CCompilerKind::MSVC
49 }
plusplus(&self) -> bool50 fn plusplus(&self) -> bool {
51 false
52 }
parse_arguments( &self, arguments: &[OsString], cwd: &Path, ) -> CompilerArguments<ParsedArguments>53 fn parse_arguments(
54 &self,
55 arguments: &[OsString],
56 cwd: &Path,
57 ) -> CompilerArguments<ParsedArguments> {
58 parse_arguments(arguments, cwd, self.is_clang)
59 }
60
preprocess<T>( &self, creator: &T, executable: &Path, parsed_args: &ParsedArguments, cwd: &Path, env_vars: &[(OsString, OsString)], may_dist: bool, rewrite_includes_only: bool, ) -> SFuture<process::Output> where T: CommandCreatorSync,61 fn preprocess<T>(
62 &self,
63 creator: &T,
64 executable: &Path,
65 parsed_args: &ParsedArguments,
66 cwd: &Path,
67 env_vars: &[(OsString, OsString)],
68 may_dist: bool,
69 rewrite_includes_only: bool,
70 ) -> SFuture<process::Output>
71 where
72 T: CommandCreatorSync,
73 {
74 preprocess(
75 creator,
76 executable,
77 parsed_args,
78 cwd,
79 env_vars,
80 may_dist,
81 &self.includes_prefix,
82 rewrite_includes_only,
83 self.is_clang,
84 )
85 }
86
generate_compile_commands( &self, path_transformer: &mut dist::PathTransformer, executable: &Path, parsed_args: &ParsedArguments, cwd: &Path, env_vars: &[(OsString, OsString)], _rewrite_includes_only: bool, ) -> Result<(CompileCommand, Option<dist::CompileCommand>, Cacheable)>87 fn generate_compile_commands(
88 &self,
89 path_transformer: &mut dist::PathTransformer,
90 executable: &Path,
91 parsed_args: &ParsedArguments,
92 cwd: &Path,
93 env_vars: &[(OsString, OsString)],
94 _rewrite_includes_only: bool,
95 ) -> Result<(CompileCommand, Option<dist::CompileCommand>, Cacheable)> {
96 generate_compile_commands(path_transformer, executable, parsed_args, cwd, env_vars)
97 }
98 }
99
from_local_codepage(bytes: &[u8]) -> io::Result<String>100 fn from_local_codepage(bytes: &[u8]) -> io::Result<String> {
101 Encoding::OEM.to_string(bytes)
102 }
103
104 /// Detect the prefix included in the output of MSVC's -showIncludes output.
detect_showincludes_prefix<T>( creator: &T, exe: &OsStr, is_clang: bool, env: Vec<(OsString, OsString)>, pool: &ThreadPool, ) -> SFuture<String> where T: CommandCreatorSync,105 pub fn detect_showincludes_prefix<T>(
106 creator: &T,
107 exe: &OsStr,
108 is_clang: bool,
109 env: Vec<(OsString, OsString)>,
110 pool: &ThreadPool,
111 ) -> SFuture<String>
112 where
113 T: CommandCreatorSync,
114 {
115 let write = write_temp_file(pool, "test.c".as_ref(), b"#include \"test.h\"\n".to_vec());
116
117 let exe = exe.to_os_string();
118 let mut creator = creator.clone();
119 let pool = pool.clone();
120 let write2 = write.and_then(move |(tempdir, input)| {
121 let header = tempdir.path().join("test.h");
122 pool.spawn_fn(move || -> Result<_> {
123 let mut file = File::create(&header)?;
124 file.write_all(b"/* empty */\n")?;
125 Ok((tempdir, input))
126 })
127 .fcontext("failed to write temporary file")
128 });
129 let output = write2.and_then(move |(tempdir, input)| {
130 let mut cmd = creator.new_command_sync(&exe);
131 // clang.exe on Windows reports the same set of built-in preprocessor defines as clang-cl,
132 // but it doesn't accept MSVC commandline arguments unless you pass --driver-mode=cl.
133 // clang-cl.exe will accept this argument as well, so always add it in this case.
134 if is_clang {
135 cmd.arg("--driver-mode=cl");
136 }
137 cmd.args(&["-nologo", "-showIncludes", "-c", "-Fonul", "-I."])
138 .arg(&input)
139 .current_dir(&tempdir.path())
140 // The MSDN docs say the -showIncludes output goes to stderr,
141 // but that's not true unless running with -E.
142 .stdout(Stdio::piped())
143 .stderr(Stdio::null());
144 for (k, v) in env {
145 cmd.env(k, v);
146 }
147 trace!("detect_showincludes_prefix: {:?}", cmd);
148
149 run_input_output(cmd, None).map(|e| {
150 // Keep the tempdir around so test.h still exists for the
151 // checks below.
152 (e, tempdir)
153 })
154 });
155
156 Box::new(output.and_then(|(output, tempdir)| {
157 if !output.status.success() {
158 bail!("Failed to detect showIncludes prefix")
159 }
160
161 let process::Output {
162 stdout: stdout_bytes,
163 ..
164 } = output;
165 let stdout = from_local_codepage(&stdout_bytes)
166 .context("Failed to convert compiler stdout while detecting showIncludes prefix")?;
167 for line in stdout.lines() {
168 if !line.ends_with("test.h") {
169 continue;
170 }
171 for (i, c) in line.char_indices().rev() {
172 if c != ' ' {
173 continue;
174 }
175 let path = tempdir.path().join(&line[i + 1..]);
176 // See if the rest of this line is a full pathname.
177 if path.exists() {
178 // Everything from the beginning of the line
179 // to this index is the prefix.
180 return Ok(line[..=i].to_owned());
181 }
182 }
183 }
184 drop(tempdir);
185
186 debug!(
187 "failed to detect showIncludes prefix with output: {}",
188 stdout
189 );
190
191 bail!("Failed to detect showIncludes prefix")
192 }))
193 }
194
195 #[cfg(unix)]
encode_path(dst: &mut dyn Write, path: &Path) -> io::Result<()>196 fn encode_path(dst: &mut dyn Write, path: &Path) -> io::Result<()> {
197 use std::os::unix::prelude::*;
198
199 let bytes = path.as_os_str().as_bytes();
200 dst.write_all(bytes)
201 }
202
203 #[cfg(windows)]
encode_path(dst: &mut dyn Write, path: &Path) -> io::Result<()>204 fn encode_path(dst: &mut dyn Write, path: &Path) -> io::Result<()> {
205 use local_encoding::windows::wide_char_to_multi_byte;
206 use std::os::windows::prelude::*;
207 use winapi::um::winnls::CP_OEMCP;
208
209 let points = path.as_os_str().encode_wide().collect::<Vec<_>>();
210 let (bytes, _) = wide_char_to_multi_byte(
211 CP_OEMCP, 0, &points, None, // default_char
212 false,
213 )?; // use_default_char_flag
214 dst.write_all(&bytes)
215 }
216
217 ArgData! {
218 TooHardFlag,
219 TooHard(OsString),
220 TooHardPath(PathBuf),
221 PreprocessorArgument(OsString),
222 PreprocessorArgumentPath(PathBuf),
223 SuppressCompilation,
224 DoCompilation,
225 ShowIncludes,
226 Output(PathBuf),
227 DepFile(PathBuf),
228 ProgramDatabase(PathBuf),
229 DebugInfo,
230 PassThrough, // Miscellaneous flags that don't prevent caching.
231 PassThroughWithPath(PathBuf), // As above, recognised by prefix.
232 PassThroughWithSuffix(OsString), // As above, recognised by prefix.
233 Ignore, // The flag is not passed to the compiler.
234 IgnoreWithSuffix(OsString), // As above, recognized by prefix.
235 ExtraHashFile(PathBuf),
236 XClang(OsString), // -Xclang ...
237 Clang(OsString), // -clang:...
238 ExternalIncludePath(PathBuf),
239 }
240
241 use self::ArgData::*;
242
243 macro_rules! msvc_args {
244 (static ARGS: [$t:ty; _] = [$($macro:ident ! ($($v:tt)*),)*]) => {
245 counted_array!(static ARGS: [$t; _] = [$(msvc_args!(@one "-", $macro!($($v)*)),)*]);
246 counted_array!(static SLASH_ARGS: [$t; _] = [$(msvc_args!(@one "/", $macro!($($v)*)),)*]);
247 };
248 (@one $prefix:expr, msvc_take_arg!($s:expr, $($t:tt)*)) => {
249 take_arg!(concat!($prefix, $s), $($t)+)
250 };
251 (@one $prefix:expr, msvc_flag!($s:expr, $($t:tt)+)) => {
252 flag!(concat!($prefix, $s), $($t)+)
253 };
254 (@one $prefix:expr, $other:expr) => { $other };
255 }
256
257 // Reference:
258 // https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=vs-2019
259 msvc_args!(static ARGS: [ArgInfo<ArgData>; _] = [
260 msvc_flag!("?", SuppressCompilation),
261 msvc_flag!("C", PassThrough), // Ignored unless a preprocess-only flag is specified.
262 msvc_take_arg!("D", OsString, CanBeSeparated, PreprocessorArgument),
263 msvc_flag!("E", SuppressCompilation),
264 msvc_take_arg!("EH", OsString, Concatenated, PassThroughWithSuffix), // /EH[acsr\-]+ - TODO: use a regex?
265 msvc_flag!("EP", SuppressCompilation),
266 msvc_take_arg!("F", OsString, Concatenated, PassThroughWithSuffix),
267 msvc_take_arg!("FA", OsString, Concatenated, TooHard),
268 msvc_flag!("FC", TooHardFlag), // Use absolute paths in error messages.
269 msvc_take_arg!("FI", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
270 msvc_take_arg!("FR", PathBuf, Concatenated, TooHardPath),
271 msvc_flag!("FS", Ignore),
272 msvc_take_arg!("FU", PathBuf, CanBeSeparated, TooHardPath),
273 msvc_take_arg!("Fa", PathBuf, Concatenated, TooHardPath),
274 msvc_take_arg!("Fd", PathBuf, Concatenated, ProgramDatabase),
275 msvc_take_arg!("Fe", PathBuf, Concatenated, TooHardPath),
276 msvc_take_arg!("Fi", PathBuf, Concatenated, TooHardPath),
277 msvc_take_arg!("Fm", PathBuf, Concatenated, PassThroughWithPath), // No effect if /c is specified.
278 msvc_take_arg!("Fo", PathBuf, Concatenated, Output),
279 msvc_take_arg!("Fp", PathBuf, Concatenated, TooHardPath),
280 msvc_take_arg!("Fr", PathBuf, Concatenated, TooHardPath),
281 msvc_flag!("Fx", TooHardFlag),
282 msvc_flag!("GA", PassThrough),
283 msvc_flag!("GF", PassThrough),
284 msvc_flag!("GH", PassThrough),
285 msvc_flag!("GL", PassThrough),
286 msvc_flag!("GL-", PassThrough),
287 msvc_flag!("GR", PassThrough),
288 msvc_flag!("GR-", PassThrough),
289 msvc_flag!("GS", PassThrough),
290 msvc_flag!("GS-", PassThrough),
291 msvc_flag!("GT", PassThrough),
292 msvc_flag!("GX", PassThrough),
293 msvc_flag!("GZ", PassThrough),
294 msvc_flag!("Gd", PassThrough),
295 msvc_flag!("Ge", PassThrough),
296 msvc_flag!("Gh", PassThrough),
297 msvc_flag!("Gm", TooHardFlag),
298 msvc_flag!("Gr", PassThrough),
299 msvc_take_arg!("Gs", OsString, Concatenated, PassThroughWithSuffix),
300 msvc_flag!("Gv", PassThrough),
301 msvc_flag!("Gw", PassThrough),
302 msvc_flag!("Gw-", PassThrough),
303 msvc_flag!("Gy", PassThrough),
304 msvc_flag!("Gy-", PassThrough),
305 msvc_flag!("Gz", PassThrough),
306 msvc_take_arg!("H", OsString, Concatenated, PassThroughWithSuffix),
307 msvc_flag!("HELP", SuppressCompilation),
308 msvc_take_arg!("I", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
309 msvc_flag!("J", PassThrough),
310 msvc_flag!("JMC", PassThrough),
311 msvc_flag!("JMC-", PassThrough),
312 msvc_flag!("LD", PassThrough),
313 msvc_flag!("LDd", PassThrough),
314 msvc_flag!("MD", PassThrough),
315 msvc_flag!("MDd", PassThrough),
316 msvc_take_arg!("MP", OsString, Concatenated, IgnoreWithSuffix),
317 msvc_flag!("MT", PassThrough),
318 msvc_flag!("MTd", PassThrough),
319 msvc_flag!("O1", PassThrough),
320 msvc_flag!("O2", PassThrough),
321 msvc_flag!("Ob0", PassThrough),
322 msvc_flag!("Ob1", PassThrough),
323 msvc_flag!("Ob2", PassThrough),
324 msvc_flag!("Ob3", PassThrough),
325 msvc_flag!("Od", PassThrough),
326 msvc_flag!("Og", PassThrough),
327 msvc_flag!("Oi", PassThrough),
328 msvc_flag!("Oi-", PassThrough),
329 msvc_flag!("Os", PassThrough),
330 msvc_flag!("Ot", PassThrough),
331 msvc_flag!("Ox", PassThrough),
332 msvc_flag!("Oy", PassThrough),
333 msvc_flag!("Oy-", PassThrough),
334 msvc_flag!("P", SuppressCompilation),
335 msvc_flag!("QIfist", PassThrough),
336 msvc_flag!("QIntel-jcc-erratum", PassThrough),
337 msvc_flag!("Qfast_transcendentals", PassThrough),
338 msvc_flag!("Qimprecise_fwaits", PassThrough),
339 msvc_flag!("Qpar", PassThrough),
340 msvc_flag!("Qsafe_fp_loads", PassThrough),
341 msvc_flag!("Qspectre", PassThrough),
342 msvc_flag!("Qspectre-load", PassThrough),
343 msvc_flag!("Qspectre-load-cf", PassThrough),
344 msvc_flag!("Qvec-report:1", PassThrough),
345 msvc_flag!("Qvec-report:2", PassThrough),
346 msvc_take_arg!("RTC", OsString, Concatenated, PassThroughWithSuffix),
347 msvc_flag!("TC", PassThrough), // TODO: disable explicit language check, hope for the best for now? Also, handle /Tc & /Tp.
348 msvc_flag!("TP", PassThrough), // As above.
349 msvc_take_arg!("U", OsString, Concatenated, PreprocessorArgument),
350 msvc_take_arg!("V", OsString, Concatenated, PassThroughWithSuffix),
351 msvc_flag!("W0", PassThrough),
352 msvc_flag!("W1", PassThrough),
353 msvc_flag!("W2", PassThrough),
354 msvc_flag!("W3", PassThrough),
355 msvc_flag!("W4", PassThrough),
356 msvc_flag!("WL", PassThrough),
357 msvc_flag!("WX", PassThrough),
358 msvc_flag!("Wall", PassThrough),
359 msvc_take_arg!("Wv:", OsString, Concatenated, PassThroughWithSuffix),
360 msvc_flag!("X", PassThrough),
361 msvc_take_arg!("Xclang", OsString, Separated, XClang),
362 msvc_flag!("Yd", PassThrough),
363 msvc_flag!("Z7", PassThrough), // Add debug info to .obj files.
364 msvc_flag!("ZI", DebugInfo), // Implies /FC, which puts absolute paths in error messages -> TooHardFlag?
365 msvc_flag!("ZW", PassThrough),
366 msvc_flag!("Za", PassThrough),
367 msvc_take_arg!("Zc:", OsString, Concatenated, PassThroughWithSuffix),
368 msvc_flag!("Ze", PassThrough),
369 msvc_flag!("Zi", DebugInfo),
370 msvc_flag!("Zo", PassThrough),
371 msvc_flag!("Zo-", PassThrough),
372 msvc_flag!("Zp1", PassThrough),
373 msvc_flag!("Zp16", PassThrough),
374 msvc_flag!("Zp2", PassThrough),
375 msvc_flag!("Zp4", PassThrough),
376 msvc_flag!("Zp8", PassThrough),
377 msvc_flag!("Zs", SuppressCompilation),
378 msvc_flag!("analyze-", PassThrough),
379 msvc_take_arg!("analyze:", OsString, Concatenated, PassThroughWithSuffix),
380 msvc_take_arg!("arch:", OsString, Concatenated, PassThroughWithSuffix),
381 msvc_flag!("await", PassThrough),
382 msvc_flag!("bigobj", PassThrough),
383 msvc_flag!("c", DoCompilation),
384 msvc_take_arg!("cgthreads", OsString, Concatenated, PassThroughWithSuffix),
385 msvc_take_arg!("clang:", OsString, Concatenated, Clang),
386 msvc_flag!("clr", PassThrough),
387 msvc_take_arg!("clr:", OsString, Concatenated, PassThroughWithSuffix),
388 msvc_take_arg!("constexpr:", OsString, Concatenated, PassThroughWithSuffix),
389 msvc_take_arg!("deps", PathBuf, Concatenated, DepFile),
390 msvc_take_arg!("diagnostics:", OsString, Concatenated, PassThroughWithSuffix),
391 msvc_take_arg!("doc", PathBuf, Concatenated, TooHardPath), // Creates an .xdc file.
392 msvc_take_arg!("errorReport:", OsString, Concatenated, PassThroughWithSuffix), // Deprecated.
393 msvc_take_arg!("execution-charset:", OsString, Concatenated, PassThroughWithSuffix),
394 msvc_flag!("experimental:module", TooHardFlag),
395 msvc_flag!("experimental:module-", PassThrough), // Explicitly disabled modules.
396 msvc_take_arg!("experimental:preprocessor", OsString, Concatenated, PassThroughWithSuffix),
397 msvc_take_arg!("external:I", PathBuf, CanBeSeparated, ExternalIncludePath),
398 msvc_take_arg!("favor:", OsString, Separated, PassThroughWithSuffix),
399 msvc_take_arg!("fp:", OsString, Separated, PassThroughWithSuffix),
400 msvc_take_arg!("fsanitize-blacklist", PathBuf, Concatenated('='), ExtraHashFile),
401 msvc_flag!("fsyntax-only", SuppressCompilation),
402 msvc_take_arg!("guard:cf", OsString, Concatenated, PassThroughWithSuffix),
403 msvc_flag!("homeparams", PassThrough),
404 msvc_flag!("hotpatch", PassThrough),
405 msvc_flag!("kernel", PassThrough),
406 msvc_flag!("kernel-", PassThrough),
407 msvc_flag!("nologo", PassThrough),
408 msvc_take_arg!("o", PathBuf, Separated, Output), // Deprecated but valid
409 msvc_flag!("openmp", PassThrough),
410 msvc_flag!("openmp:experimental", PassThrough),
411 msvc_flag!("permissive-", PassThrough),
412 msvc_flag!("sdl", PassThrough),
413 msvc_flag!("sdl-", PassThrough),
414 msvc_flag!("showIncludes", ShowIncludes),
415 msvc_take_arg!("source-charset:", OsString, Concatenated, PassThroughWithSuffix),
416 msvc_take_arg!("std:", OsString, Concatenated, PassThroughWithSuffix),
417 msvc_flag!("u", PassThrough),
418 msvc_flag!("utf-8", PassThrough),
419 msvc_flag!("validate-charset", PassThrough),
420 msvc_flag!("validate-charset-", PassThrough),
421 msvc_flag!("vd0", PassThrough),
422 msvc_flag!("vd1", PassThrough),
423 msvc_flag!("vd2", PassThrough),
424 msvc_flag!("vmb", PassThrough),
425 msvc_flag!("vmg", PassThrough),
426 msvc_flag!("vmm", PassThrough),
427 msvc_flag!("vms", PassThrough),
428 msvc_flag!("vmv", PassThrough),
429 msvc_flag!("volatile:iso", PassThrough),
430 msvc_flag!("volatile:ms", PassThrough),
431 msvc_flag!("w", PassThrough),
432 msvc_take_arg!("w1", OsString, Concatenated, PassThroughWithSuffix),
433 msvc_take_arg!("w2", OsString, Concatenated, PassThroughWithSuffix),
434 msvc_take_arg!("w3", OsString, Concatenated, PassThroughWithSuffix),
435 msvc_take_arg!("w4", OsString, Concatenated, PassThroughWithSuffix),
436 msvc_take_arg!("wd", OsString, Concatenated, PassThroughWithSuffix),
437 msvc_take_arg!("we", OsString, Concatenated, PassThroughWithSuffix),
438 msvc_take_arg!("wo", OsString, Concatenated, PassThroughWithSuffix),
439 take_arg!("@", PathBuf, Concatenated, TooHardPath),
440 ]);
441
442 // TODO: what do do with precompiled header flags? eg: /Y-, /Yc, /YI, /Yu, /Zf, /ZH, /Zm
443
parse_arguments( arguments: &[OsString], cwd: &Path, is_clang: bool, ) -> CompilerArguments<ParsedArguments>444 pub fn parse_arguments(
445 arguments: &[OsString],
446 cwd: &Path,
447 is_clang: bool,
448 ) -> CompilerArguments<ParsedArguments> {
449 let mut output_arg = None;
450 let mut input_arg = None;
451 let mut common_args = vec![];
452 let mut preprocessor_args = vec![];
453 let mut dependency_args = vec![];
454 let mut extra_hash_files = vec![];
455 let mut compilation = false;
456 let mut compilation_flag = OsString::new();
457 let mut debug_info = false;
458 let mut pdb = None;
459 let mut depfile = None;
460 let mut show_includes = false;
461 let mut xclangs: Vec<OsString> = vec![];
462 let mut clangs: Vec<OsString> = vec![];
463 let mut profile_generate = false;
464
465 for arg in ArgsIter::new(arguments.iter().cloned(), (&ARGS[..], &SLASH_ARGS[..])) {
466 let arg = try_or_cannot_cache!(arg, "argument parse");
467 match arg.get_data() {
468 Some(PassThrough) | Some(PassThroughWithPath(_)) | Some(PassThroughWithSuffix(_)) => {}
469 Some(TooHardFlag) | Some(TooHard(_)) | Some(TooHardPath(_)) => {
470 cannot_cache!(arg.flag_str().expect("Can't be Argument::Raw/UnknownFlag",))
471 }
472 Some(DoCompilation) => {
473 compilation = true;
474 compilation_flag =
475 OsString::from(arg.flag_str().expect("Compilation flag expected"));
476 }
477 Some(ShowIncludes) => {
478 show_includes = true;
479 dependency_args.push(arg.to_os_string());
480 }
481 Some(Output(out)) => {
482 output_arg = Some(out.clone());
483 // Can't usefully cache output that goes to nul anyway,
484 // and it breaks reading entries from cache.
485 if out.as_os_str() == "nul" {
486 cannot_cache!("output to nul")
487 }
488 }
489 Some(DepFile(p)) => depfile = Some(p.clone()),
490 Some(ProgramDatabase(p)) => pdb = Some(p.clone()),
491 Some(DebugInfo) => debug_info = true,
492 Some(PreprocessorArgument(_))
493 | Some(PreprocessorArgumentPath(_))
494 | Some(ExtraHashFile(_))
495 | Some(Ignore)
496 | Some(IgnoreWithSuffix(_))
497 | Some(ExternalIncludePath(_)) => {}
498 Some(SuppressCompilation) => {
499 return CompilerArguments::NotCompilation;
500 }
501 Some(XClang(s)) => xclangs.push(s.clone()),
502 Some(Clang(s)) => clangs.push(s.clone()),
503 None => {
504 match arg {
505 Argument::Raw(ref val) => {
506 if input_arg.is_some() {
507 // Can't cache compilations with multiple inputs.
508 cannot_cache!("multiple input files");
509 }
510 input_arg = Some(val.clone());
511 }
512 Argument::UnknownFlag(ref flag) => common_args.push(flag.clone()),
513 _ => unreachable!(),
514 }
515 }
516 }
517 match arg.get_data() {
518 Some(PreprocessorArgument(_)) | Some(PreprocessorArgumentPath(_)) => preprocessor_args
519 .extend(
520 arg.normalize(NormalizedDisposition::Concatenated)
521 .iter_os_strings(),
522 ),
523 Some(ProgramDatabase(_))
524 | Some(DebugInfo)
525 | Some(PassThrough)
526 | Some(PassThroughWithPath(_))
527 | Some(PassThroughWithSuffix(_)) => common_args.extend(
528 arg.normalize(NormalizedDisposition::Concatenated)
529 .iter_os_strings(),
530 ),
531 Some(ExtraHashFile(path)) => {
532 extra_hash_files.push(cwd.join(path));
533 common_args.extend(
534 arg.normalize(NormalizedDisposition::Concatenated)
535 .iter_os_strings(),
536 )
537 }
538 Some(ExternalIncludePath(_)) => common_args.extend(
539 arg.normalize(NormalizedDisposition::Separated)
540 .iter_os_strings(),
541 ),
542 // We ignore -MP and -FS and never pass them down to the compiler.
543 //
544 // -MP tells the compiler to build with multiple processes and is used
545 // to spread multiple compilations when there are multiple inputs.
546 // Either we have multiple inputs on the command line, and we're going
547 // to bail out and not cache, or -MP is not going to be useful.
548 // -MP also implies -FS.
549 //
550 // -FS forces synchronous access to PDB files via a MSPDBSRV process.
551 // This option is only useful when multiple compiler invocations are going
552 // to share the same PDB file, which is not supported by sccache. So either
553 // -Fd was passed with a pdb that is not shared and sccache is going to
554 // handle the compile, in which case -FS is not needed, or -Fd was not passed
555 // and we're going to bail out and not cache.
556 //
557 // In both cases, the flag is not going to be useful if we are going to cache,
558 // so we just skip them entirely. -FS may also have a side effect of creating
559 // race conditions in which we may try to read the PDB before MSPDBSRC is done
560 // writing it, so we're better off ignoring the flags.
561 Some(Ignore) | Some(IgnoreWithSuffix(_)) => {}
562 _ => {}
563 }
564 }
565
566 // TODO: doing this here reorders the arguments, hopefully that doesn't affect the meaning
567 fn xclang_append(arg: OsString, args: &mut Vec<OsString>) {
568 args.push("-Xclang".into());
569 args.push(arg);
570 }
571
572 fn dash_clang_append(arg: OsString, args: &mut Vec<OsString>) {
573 let mut a = OsString::from("-clang:");
574 a.push(arg);
575 args.push(a);
576 }
577
578 for (args, append_fn) in Iterator::zip(
579 [xclangs, clangs].iter(),
580 &[xclang_append, dash_clang_append],
581 ) {
582 let it = gcc::ExpandIncludeFile::new(cwd, args);
583 for arg in ArgsIter::new(it, (&gcc::ARGS[..], &clang::ARGS[..])) {
584 let arg = try_or_cannot_cache!(arg, "argument parse");
585 // Eagerly bail if it looks like we need to do more complicated work
586 use crate::compiler::gcc::ArgData::*;
587 let mut args = match arg.get_data() {
588 Some(SplitDwarf) | Some(TestCoverage) | Some(Coverage) | Some(DoCompilation)
589 | Some(Language(_)) | Some(Output(_)) | Some(TooHardFlag) | Some(XClang(_))
590 | Some(TooHard(_)) => cannot_cache!(arg
591 .flag_str()
592 .unwrap_or("Can't handle complex arguments through clang",)),
593 None => match arg {
594 Argument::Raw(_) | Argument::UnknownFlag(_) => &mut common_args,
595 _ => unreachable!(),
596 },
597 Some(DiagnosticsColor(_))
598 | Some(DiagnosticsColorFlag)
599 | Some(NoDiagnosticsColorFlag)
600 | Some(Arch(_))
601 | Some(PassThrough(_))
602 | Some(PassThroughPath(_)) => &mut common_args,
603
604 Some(ProfileGenerate) => {
605 profile_generate = true;
606 &mut common_args
607 }
608 Some(ExtraHashFile(path)) => {
609 extra_hash_files.push(cwd.join(path));
610 &mut common_args
611 }
612 Some(PreprocessorArgumentFlag)
613 | Some(PreprocessorArgument(_))
614 | Some(PreprocessorArgumentPath(_)) => &mut preprocessor_args,
615 Some(DepArgumentPath(_)) | Some(DepTarget(_)) | Some(NeedDepTarget) => {
616 &mut dependency_args
617 }
618 };
619 // Normalize attributes such as "-I foo", "-D FOO=bar", as
620 // "-Ifoo", "-DFOO=bar", etc. and "-includefoo", "idirafterbar" as
621 // "-include foo", "-idirafter bar", etc.
622 let norm = match arg.flag_str() {
623 Some(s) if s.len() == 2 => NormalizedDisposition::Concatenated,
624 _ => NormalizedDisposition::Separated,
625 };
626 for arg in arg.normalize(norm).iter_os_strings() {
627 append_fn(arg, &mut args);
628 }
629 }
630 }
631
632 // We only support compilation.
633 if !compilation {
634 return CompilerArguments::NotCompilation;
635 }
636 let (input, language) = match input_arg {
637 Some(i) => match Language::from_file_name(Path::new(&i)) {
638 Some(l) => (i.to_owned(), l),
639 None => cannot_cache!("unknown source language"),
640 },
641 // We can't cache compilation without an input.
642 None => cannot_cache!("no input file"),
643 };
644 let mut outputs = HashMap::new();
645 match output_arg {
646 // If output file name is not given, use default naming rule
647 None => {
648 outputs.insert("obj", Path::new(&input).with_extension("obj"));
649 }
650 Some(o) => {
651 outputs.insert("obj", o);
652 }
653 }
654 // -Fd is not taken into account unless -Zi or -ZI are given
655 // Clang is currently unable to generate PDB files
656 if debug_info && !is_clang {
657 match pdb {
658 Some(p) => outputs.insert("pdb", p),
659 None => {
660 // -Zi and -ZI without -Fd defaults to vcxxx.pdb (where xxx depends on the
661 // MSVC version), and that's used for all compilations with the same
662 // working directory. We can't cache such a pdb.
663 cannot_cache!("shared pdb");
664 }
665 };
666 }
667
668 CompilerArguments::Ok(ParsedArguments {
669 input: input.into(),
670 language,
671 compilation_flag,
672 depfile,
673 outputs,
674 dependency_args,
675 preprocessor_args,
676 common_args,
677 extra_hash_files,
678 msvc_show_includes: show_includes,
679 profile_generate,
680 // FIXME: implement color_mode for msvc.
681 color_mode: ColorMode::Auto,
682 })
683 }
684
685 #[cfg(windows)]
normpath(path: &str) -> String686 fn normpath(path: &str) -> String {
687 use std::os::windows::ffi::OsStringExt;
688 use std::os::windows::io::AsRawHandle;
689 use std::ptr;
690 use winapi::um::fileapi::GetFinalPathNameByHandleW;
691 File::open(path)
692 .and_then(|f| {
693 let handle = f.as_raw_handle();
694 let size = unsafe { GetFinalPathNameByHandleW(handle, ptr::null_mut(), 0, 0) };
695 if size == 0 {
696 return Err(io::Error::last_os_error());
697 }
698 let mut wchars = vec![0; size as usize];
699 if unsafe {
700 GetFinalPathNameByHandleW(handle, wchars.as_mut_ptr(), wchars.len() as u32, 0)
701 } == 0
702 {
703 return Err(io::Error::last_os_error());
704 }
705 // The return value of GetFinalPathNameByHandleW uses the
706 // '\\?\' prefix.
707 let o = OsString::from_wide(&wchars[4..wchars.len() - 1]);
708 o.into_string()
709 .map(|s| s.replace('\\', "/"))
710 .map_err(|_| io::Error::new(io::ErrorKind::Other, "Error converting string"))
711 })
712 .unwrap_or_else(|_| path.replace('\\', "/"))
713 }
714
715 #[cfg(not(windows))]
normpath(path: &str) -> String716 fn normpath(path: &str) -> String {
717 path.to_owned()
718 }
719
720 #[allow(clippy::too_many_arguments)]
preprocess<T>( creator: &T, executable: &Path, parsed_args: &ParsedArguments, cwd: &Path, env_vars: &[(OsString, OsString)], may_dist: bool, includes_prefix: &str, rewrite_includes_only: bool, is_clang: bool, ) -> SFuture<process::Output> where T: CommandCreatorSync,721 pub fn preprocess<T>(
722 creator: &T,
723 executable: &Path,
724 parsed_args: &ParsedArguments,
725 cwd: &Path,
726 env_vars: &[(OsString, OsString)],
727 may_dist: bool,
728 includes_prefix: &str,
729 rewrite_includes_only: bool,
730 is_clang: bool,
731 ) -> SFuture<process::Output>
732 where
733 T: CommandCreatorSync,
734 {
735 let mut cmd = creator.clone().new_command_sync(executable);
736
737 // When performing distributed compilation, line number info is important for error
738 // reporting and to not cause spurious compilation failure (e.g. no exceptions build
739 // fails due to exceptions transitively included in the stdlib).
740 // With -fprofile-generate line number information is important, so use -E.
741 // Otherwise, use -EP to maximize cache hits (because no absolute file paths are
742 // emitted) and improve performance.
743 if may_dist || parsed_args.profile_generate {
744 cmd.arg("-E");
745 } else {
746 cmd.arg("-EP");
747 }
748
749 cmd.arg(&parsed_args.input)
750 .arg("-nologo")
751 .args(&parsed_args.preprocessor_args)
752 .args(&parsed_args.dependency_args)
753 .args(&parsed_args.common_args)
754 .env_clear()
755 .envs(env_vars.iter().map(|&(ref k, ref v)| (k, v)))
756 .current_dir(&cwd);
757 if parsed_args.depfile.is_some() && !parsed_args.msvc_show_includes {
758 cmd.arg("-showIncludes");
759 }
760 if rewrite_includes_only && is_clang {
761 cmd.arg("-clang:-frewrite-includes");
762 }
763
764 if log_enabled!(Debug) {
765 debug!("preprocess: {:?}", cmd);
766 }
767
768 let parsed_args = parsed_args.clone();
769 let includes_prefix = includes_prefix.to_string();
770 let cwd = cwd.to_owned();
771
772 Box::new(run_input_output(cmd, None).and_then(move |output| {
773 let parsed_args = &parsed_args;
774 if let (Some(ref objfile), &Some(ref depfile)) =
775 (parsed_args.outputs.get("obj"), &parsed_args.depfile)
776 {
777 let f = File::create(cwd.join(depfile))?;
778 let mut f = BufWriter::new(f);
779
780 encode_path(&mut f, &objfile)
781 .with_context(|| format!("Couldn't encode objfile filename: '{:?}'", objfile))?;
782 write!(f, ": ")?;
783 encode_path(&mut f, &parsed_args.input)
784 .with_context(|| format!("Couldn't encode input filename: '{:?}'", objfile))?;
785 write!(f, " ")?;
786 let process::Output {
787 status,
788 stdout,
789 stderr: stderr_bytes,
790 } = output;
791 let stderr = from_local_codepage(&stderr_bytes)
792 .context("Failed to convert preprocessor stderr")?;
793 let mut deps = HashSet::new();
794 let mut stderr_bytes = vec![];
795 for line in stderr.lines() {
796 if line.starts_with(&includes_prefix) {
797 let dep = normpath(line[includes_prefix.len()..].trim());
798 trace!("included: {}", dep);
799 if deps.insert(dep.clone()) && !dep.contains(' ') {
800 write!(f, "{} ", dep)?;
801 }
802 if !parsed_args.msvc_show_includes {
803 continue;
804 }
805 }
806 stderr_bytes.extend_from_slice(line.as_bytes());
807 stderr_bytes.push(b'\n');
808 }
809 writeln!(f)?;
810 // Write extra rules for each dependency to handle
811 // removed files.
812 encode_path(&mut f, &parsed_args.input)
813 .with_context(|| format!("Couldn't encode filename: '{:?}'", parsed_args.input))?;
814 writeln!(f, ":")?;
815 let mut sorted = deps.into_iter().collect::<Vec<_>>();
816 sorted.sort();
817 for dep in sorted {
818 if !dep.contains(' ') {
819 writeln!(f, "{}:", dep)?;
820 }
821 }
822 Ok(process::Output {
823 status,
824 stdout,
825 stderr: stderr_bytes,
826 })
827 } else {
828 Ok(output)
829 }
830 }))
831 }
832
generate_compile_commands( path_transformer: &mut dist::PathTransformer, executable: &Path, parsed_args: &ParsedArguments, cwd: &Path, env_vars: &[(OsString, OsString)], ) -> Result<(CompileCommand, Option<dist::CompileCommand>, Cacheable)>833 fn generate_compile_commands(
834 path_transformer: &mut dist::PathTransformer,
835 executable: &Path,
836 parsed_args: &ParsedArguments,
837 cwd: &Path,
838 env_vars: &[(OsString, OsString)],
839 ) -> Result<(CompileCommand, Option<dist::CompileCommand>, Cacheable)> {
840 #[cfg(not(feature = "dist-client"))]
841 let _ = path_transformer;
842
843 trace!("compile");
844 let out_file = match parsed_args.outputs.get("obj") {
845 Some(obj) => obj,
846 None => bail!("Missing object file output"),
847 };
848
849 // See if this compilation will produce a PDB.
850 let cacheable = parsed_args
851 .outputs
852 .get("pdb")
853 .map_or(Cacheable::Yes, |pdb| {
854 // If the PDB exists, we don't know if it's shared with another
855 // compilation. If it is, we can't cache.
856 if Path::new(&cwd).join(pdb).exists() {
857 Cacheable::No
858 } else {
859 Cacheable::Yes
860 }
861 });
862
863 let mut fo = OsString::from("-Fo");
864 fo.push(&out_file);
865
866 let mut arguments: Vec<OsString> = vec![
867 parsed_args.compilation_flag.clone(),
868 parsed_args.input.clone().into(),
869 fo,
870 ];
871 arguments.extend(parsed_args.preprocessor_args.clone());
872 arguments.extend(parsed_args.dependency_args.clone());
873 arguments.extend(parsed_args.common_args.clone());
874
875 let command = CompileCommand {
876 executable: executable.to_owned(),
877 arguments,
878 env_vars: env_vars.to_owned(),
879 cwd: cwd.to_owned(),
880 };
881
882 #[cfg(not(feature = "dist-client"))]
883 let dist_command = None;
884 #[cfg(feature = "dist-client")]
885 let dist_command = (|| {
886 // http://releases.llvm.org/6.0.0/tools/clang/docs/UsersManual.html#clang-cl
887 // TODO: Use /T... for language?
888 let mut fo = String::from("-Fo");
889 fo.push_str(&path_transformer.as_dist(out_file)?);
890
891 let mut arguments: Vec<String> = vec![
892 parsed_args.compilation_flag.clone().into_string().ok()?,
893 path_transformer.as_dist(&parsed_args.input)?,
894 fo,
895 ];
896 // It's important to avoid preprocessor_args because of things like /FI which
897 // forcibly includes another file. This does mean we're potentially vulnerable
898 // to misidentification of flags like -DYNAMICBASE (though in that specific
899 // case we're safe as it only applies to link time, which sccache avoids).
900 arguments.extend(dist::osstrings_to_strings(&parsed_args.common_args)?);
901
902 Some(dist::CompileCommand {
903 executable: path_transformer.as_dist(&executable)?,
904 arguments,
905 env_vars: dist::osstring_tuples_to_strings(env_vars)?,
906 cwd: path_transformer.as_dist(cwd)?,
907 })
908 })();
909
910 Ok((command, dist_command, cacheable))
911 }
912
913 #[cfg(test)]
914 mod test {
915 use super::*;
916 use crate::compiler::*;
917 use crate::mock_command::*;
918 use crate::test::utils::*;
919 use futures::Future;
920 use futures_03::executor::ThreadPool;
921
parse_arguments(arguments: Vec<OsString>) -> CompilerArguments<ParsedArguments>922 fn parse_arguments(arguments: Vec<OsString>) -> CompilerArguments<ParsedArguments> {
923 super::parse_arguments(&arguments, &std::env::current_dir().unwrap(), false)
924 }
925
926 #[test]
test_detect_showincludes_prefix()927 fn test_detect_showincludes_prefix() {
928 drop(env_logger::try_init());
929 let creator = new_creator();
930 let pool = ThreadPool::sized(1);
931 let f = TestFixture::new();
932 let srcfile = f.touch("test.h").unwrap();
933 let mut s = srcfile.to_str().unwrap();
934 if s.starts_with("\\\\?\\") {
935 s = &s[4..];
936 }
937 let stdout = format!("blah: {}\r\n", s);
938 let stderr = String::from("some\r\nstderr\r\n");
939 next_command(
940 &creator,
941 Ok(MockChild::new(exit_status(0), &stdout, &stderr)),
942 );
943 assert_eq!(
944 "blah: ",
945 detect_showincludes_prefix(&creator, "cl.exe".as_ref(), false, Vec::new(), &pool)
946 .wait()
947 .unwrap()
948 );
949 }
950
951 #[test]
952 fn test_parse_arguments_simple() {
953 let args = ovec!["-c", "foo.c", "-Fofoo.obj"];
954 let ParsedArguments {
955 input,
956 language,
957 compilation_flag,
958 outputs,
959 preprocessor_args,
960 msvc_show_includes,
961 common_args,
962 ..
963 } = match parse_arguments(args) {
964 CompilerArguments::Ok(args) => args,
965 o => panic!("Got unexpected parse result: {:?}", o),
966 };
967 assert_eq!(Some("foo.c"), input.to_str());
968 assert_eq!(Language::C, language);
969 assert_eq!(Some("-c"), compilation_flag.to_str());
970 assert_map_contains!(outputs, ("obj", PathBuf::from("foo.obj")));
971 assert!(preprocessor_args.is_empty());
972 assert!(common_args.is_empty());
973 assert!(!msvc_show_includes);
974 }
975
976 #[test]
977 fn test_parse_compile_flag() {
978 let args = ovec!["/c", "foo.c", "-Fofoo.obj"];
979 let ParsedArguments {
980 input,
981 language,
982 compilation_flag,
983 outputs,
984 preprocessor_args,
985 msvc_show_includes,
986 common_args,
987 ..
988 } = match parse_arguments(args) {
989 CompilerArguments::Ok(args) => args,
990 o => panic!("Got unexpected parse result: {:?}", o),
991 };
992 assert_eq!(Some("foo.c"), input.to_str());
993 assert_eq!(Language::C, language);
994 assert_eq!(Some("/c"), compilation_flag.to_str());
995 assert_map_contains!(outputs, ("obj", PathBuf::from("foo.obj")));
996 assert!(preprocessor_args.is_empty());
997 assert!(common_args.is_empty());
998 assert!(!msvc_show_includes);
999 }
1000
1001 #[test]
1002 fn test_parse_arguments_default_name() {
1003 let args = ovec!["-c", "foo.c"];
1004 let ParsedArguments {
1005 input,
1006 language,
1007 outputs,
1008 preprocessor_args,
1009 msvc_show_includes,
1010 common_args,
1011 ..
1012 } = match parse_arguments(args) {
1013 CompilerArguments::Ok(args) => args,
1014 o => panic!("Got unexpected parse result: {:?}", o),
1015 };
1016 assert_eq!(Some("foo.c"), input.to_str());
1017 assert_eq!(Language::C, language);
1018 assert_map_contains!(outputs, ("obj", PathBuf::from("foo.obj")));
1019 assert!(preprocessor_args.is_empty());
1020 assert!(common_args.is_empty());
1021 assert!(!msvc_show_includes);
1022 }
1023
1024 #[test]
1025 fn parse_argument_slashes() {
1026 let args = ovec!["-c", "foo.c", "/Fofoo.obj"];
1027 let ParsedArguments {
1028 input,
1029 language,
1030 outputs,
1031 preprocessor_args,
1032 msvc_show_includes,
1033 common_args,
1034 ..
1035 } = match parse_arguments(args) {
1036 CompilerArguments::Ok(args) => args,
1037 o => panic!("Got unexpected parse result: {:?}", o),
1038 };
1039 assert_eq!(Some("foo.c"), input.to_str());
1040 assert_eq!(Language::C, language);
1041 assert_map_contains!(outputs, ("obj", PathBuf::from("foo.obj")));
1042 assert!(preprocessor_args.is_empty());
1043 assert!(common_args.is_empty());
1044 assert!(!msvc_show_includes);
1045 }
1046
1047 #[test]
1048 fn test_parse_arguments_clang_passthrough() {
1049 let args = ovec![
1050 "-Fohost_dictionary.obj",
1051 "-c",
1052 "-Xclang",
1053 "-MP",
1054 "-Xclang",
1055 "-dependency-file",
1056 "-Xclang",
1057 ".deps/host_dictionary.obj.pp",
1058 "-Xclang",
1059 "-MT",
1060 "-Xclang",
1061 "host_dictionary.obj",
1062 "-clang:-fprofile-generate",
1063 "dictionary.c"
1064 ];
1065 let ParsedArguments {
1066 dependency_args,
1067 preprocessor_args,
1068 common_args,
1069 profile_generate,
1070 ..
1071 } = match parse_arguments(args) {
1072 CompilerArguments::Ok(args) => args,
1073 o => panic!("Got unexpected parse result: {:?}", o),
1074 };
1075 assert!(profile_generate);
1076 assert!(preprocessor_args.is_empty());
1077 assert_eq!(
1078 dependency_args,
1079 ovec!(
1080 "-Xclang",
1081 "-MP",
1082 "-Xclang",
1083 "-dependency-file",
1084 "-Xclang",
1085 ".deps/host_dictionary.obj.pp",
1086 "-Xclang",
1087 "-MT",
1088 "-Xclang",
1089 "host_dictionary.obj"
1090 )
1091 );
1092 assert_eq!(common_args, ovec!("-clang:-fprofile-generate"));
1093 }
1094
1095 #[test]
1096 fn test_parse_arguments_extra() {
1097 let args = ovec!["-c", "foo.c", "-foo", "-Fofoo.obj", "-bar"];
1098 let ParsedArguments {
1099 input,
1100 language,
1101 outputs,
1102 preprocessor_args,
1103 msvc_show_includes,
1104 common_args,
1105 ..
1106 } = match parse_arguments(args) {
1107 CompilerArguments::Ok(args) => args,
1108 o => panic!("Got unexpected parse result: {:?}", o),
1109 };
1110 assert_eq!(Some("foo.c"), input.to_str());
1111 assert_eq!(Language::C, language);
1112 assert_map_contains!(outputs, ("obj", PathBuf::from("foo.obj")));
1113 assert!(preprocessor_args.is_empty());
1114 assert_eq!(common_args, ovec!["-foo", "-bar"]);
1115 assert!(!msvc_show_includes);
1116 }
1117
1118 #[test]
1119 fn test_parse_arguments_values() {
1120 let args = ovec!["-c", "foo.c", "-FI", "file", "-Fofoo.obj", "/showIncludes"];
1121 let ParsedArguments {
1122 input,
1123 language,
1124 outputs,
1125 preprocessor_args,
1126 dependency_args,
1127 msvc_show_includes,
1128 common_args,
1129 ..
1130 } = match parse_arguments(args) {
1131 CompilerArguments::Ok(args) => args,
1132 o => panic!("Got unexpected parse result: {:?}", o),
1133 };
1134 assert_eq!(Some("foo.c"), input.to_str());
1135 assert_eq!(Language::C, language);
1136 assert_map_contains!(outputs, ("obj", PathBuf::from("foo.obj")));
1137 assert_eq!(preprocessor_args, ovec!["-FIfile"]);
1138 assert_eq!(dependency_args, ovec!["/showIncludes"]);
1139 assert!(common_args.is_empty());
1140 assert!(msvc_show_includes);
1141 }
1142
1143 #[test]
1144 fn test_parse_arguments_pdb() {
1145 let args = ovec!["-c", "foo.c", "-Zi", "-Fdfoo.pdb", "-Fofoo.obj"];
1146 let ParsedArguments {
1147 input,
1148 language,
1149 outputs,
1150 preprocessor_args,
1151 msvc_show_includes,
1152 common_args,
1153 ..
1154 } = match parse_arguments(args) {
1155 CompilerArguments::Ok(args) => args,
1156 o => panic!("Got unexpected parse result: {:?}", o),
1157 };
1158 assert_eq!(Some("foo.c"), input.to_str());
1159 assert_eq!(Language::C, language);
1160 assert_map_contains!(
1161 outputs,
1162 ("obj", PathBuf::from("foo.obj")),
1163 ("pdb", PathBuf::from("foo.pdb"))
1164 );
1165 assert!(preprocessor_args.is_empty());
1166 assert_eq!(common_args, ovec!["-Zi", "-Fdfoo.pdb"]);
1167 assert!(!msvc_show_includes);
1168 }
1169
1170 #[test]
1171 fn test_parse_arguments_external_include() {
1172 // Parsing -external:I relies on -experimental:external being parsed
1173 // and placed into common_args.
1174 let args = ovec![
1175 "-c",
1176 "foo.c",
1177 "-Fofoo.obj",
1178 "-experimental:external",
1179 "-external:templates-",
1180 "-external:I",
1181 "path/to/system/includes"
1182 ];
1183 let ParsedArguments {
1184 input,
1185 language,
1186 outputs,
1187 preprocessor_args,
1188 msvc_show_includes,
1189 common_args,
1190 ..
1191 } = match parse_arguments(args) {
1192 CompilerArguments::Ok(args) => args,
1193 o => panic!("Got unexpected parse result: {:?}", o),
1194 };
1195 assert_eq!(Some("foo.c"), input.to_str());
1196 assert_eq!(Language::C, language);
1197 assert_map_contains!(outputs, ("obj", PathBuf::from("foo.obj")));
1198 assert_eq!(1, outputs.len());
1199 assert!(preprocessor_args.is_empty());
1200 assert_eq!(
1201 common_args,
1202 ovec![
1203 "-experimental:external",
1204 "-external:templates-",
1205 "-external:I",
1206 "path/to/system/includes"
1207 ]
1208 );
1209 assert!(!msvc_show_includes);
1210 }
1211
1212 #[test]
1213 fn test_parse_arguments_empty_args() {
1214 assert_eq!(CompilerArguments::NotCompilation, parse_arguments(vec!()));
1215 }
1216
1217 #[test]
1218 fn test_parse_arguments_not_compile() {
1219 assert_eq!(
1220 CompilerArguments::NotCompilation,
1221 parse_arguments(ovec!["-Fofoo", "foo.c"])
1222 );
1223 }
1224
1225 #[test]
1226 fn test_parse_arguments_passthrough() {
1227 let args = ovec![
1228 "-Oy",
1229 "-Gw",
1230 "-EHa",
1231 "-Fmdictionary-map",
1232 "-c",
1233 "-Fohost_dictionary.obj",
1234 "dictionary.c"
1235 ];
1236 let ParsedArguments {
1237 input,
1238 common_args,
1239 dependency_args,
1240 preprocessor_args,
1241 ..
1242 } = match parse_arguments(args) {
1243 CompilerArguments::Ok(args) => args,
1244 o => panic!("Got unexpected parse result: {:?}", o),
1245 };
1246 assert_eq!(Some("dictionary.c"), input.to_str());
1247 assert!(preprocessor_args.is_empty());
1248 assert!(dependency_args.is_empty());
1249 assert!(!common_args.is_empty());
1250 assert_eq!(
1251 common_args,
1252 ovec!("-Oy", "-Gw", "-EHa", "-Fmdictionary-map")
1253 );
1254 }
1255
1256 #[test]
1257 fn test_parse_arguments_too_many_inputs() {
1258 assert_eq!(
1259 CompilerArguments::CannotCache("multiple input files", None),
1260 parse_arguments(ovec!["-c", "foo.c", "-Fofoo.obj", "bar.c"])
1261 );
1262 }
1263
1264 #[test]
1265 fn test_parse_arguments_unsupported() {
1266 assert_eq!(
1267 CompilerArguments::CannotCache("-FA", None),
1268 parse_arguments(ovec!["-c", "foo.c", "-Fofoo.obj", "-FA"])
1269 );
1270
1271 assert_eq!(
1272 CompilerArguments::CannotCache("-Fa", None),
1273 parse_arguments(ovec!["-Fa", "-c", "foo.c", "-Fofoo.obj"])
1274 );
1275
1276 assert_eq!(
1277 CompilerArguments::CannotCache("-FR", None),
1278 parse_arguments(ovec!["-c", "foo.c", "-FR", "-Fofoo.obj"])
1279 );
1280 }
1281
1282 #[test]
1283 fn test_parse_arguments_response_file() {
1284 assert_eq!(
1285 CompilerArguments::CannotCache("@", None),
1286 parse_arguments(ovec!["-c", "foo.c", "@foo", "-Fofoo.obj"])
1287 );
1288 }
1289
1290 #[test]
1291 fn test_parse_arguments_missing_pdb() {
1292 assert_eq!(
1293 CompilerArguments::CannotCache("shared pdb", None),
1294 parse_arguments(ovec!["-c", "foo.c", "-Zi", "-Fofoo.obj"])
1295 );
1296 }
1297
1298 #[test]
1299 fn test_parse_arguments_missing_edit_and_continue_pdb() {
1300 assert_eq!(
1301 CompilerArguments::CannotCache("shared pdb", None),
1302 parse_arguments(ovec!["-c", "foo.c", "-ZI", "-Fofoo.obj"])
1303 );
1304 }
1305
1306 #[test]
1307 fn test_compile_simple() {
1308 let creator = new_creator();
1309 let f = TestFixture::new();
1310 let parsed_args = ParsedArguments {
1311 input: "foo.c".into(),
1312 language: Language::C,
1313 compilation_flag: "-c".into(),
1314 depfile: None,
1315 outputs: vec![("obj", "foo.obj".into())].into_iter().collect(),
1316 dependency_args: vec![],
1317 preprocessor_args: vec![],
1318 common_args: vec![],
1319 extra_hash_files: vec![],
1320 msvc_show_includes: false,
1321 profile_generate: false,
1322 color_mode: ColorMode::Auto,
1323 };
1324 let compiler = &f.bins[0];
1325 // Compiler invocation.
1326 next_command(&creator, Ok(MockChild::new(exit_status(0), "", "")));
1327 let mut path_transformer = dist::PathTransformer::default();
1328 let (command, dist_command, cacheable) = generate_compile_commands(
1329 &mut path_transformer,
1330 &compiler,
1331 &parsed_args,
1332 f.tempdir.path(),
1333 &[],
1334 )
1335 .unwrap();
1336 #[cfg(feature = "dist-client")]
1337 assert!(dist_command.is_some());
1338 #[cfg(not(feature = "dist-client"))]
1339 assert!(dist_command.is_none());
1340 let _ = command.execute(&creator).wait();
1341 assert_eq!(Cacheable::Yes, cacheable);
1342 // Ensure that we ran all processes.
1343 assert_eq!(0, creator.lock().unwrap().children.len());
1344 }
1345
1346 #[test]
1347 fn test_compile_not_cacheable_pdb() {
1348 let creator = new_creator();
1349 let f = TestFixture::new();
1350 let pdb = f.touch("foo.pdb").unwrap();
1351 let parsed_args = ParsedArguments {
1352 input: "foo.c".into(),
1353 language: Language::C,
1354 compilation_flag: "/c".into(),
1355 depfile: None,
1356 outputs: vec![("obj", "foo.obj".into()), ("pdb", pdb)]
1357 .into_iter()
1358 .collect(),
1359 dependency_args: vec![],
1360 preprocessor_args: vec![],
1361 common_args: vec![],
1362 extra_hash_files: vec![],
1363 msvc_show_includes: false,
1364 profile_generate: false,
1365 color_mode: ColorMode::Auto,
1366 };
1367 let compiler = &f.bins[0];
1368 // Compiler invocation.
1369 next_command(&creator, Ok(MockChild::new(exit_status(0), "", "")));
1370 let mut path_transformer = dist::PathTransformer::default();
1371 let (command, dist_command, cacheable) = generate_compile_commands(
1372 &mut path_transformer,
1373 &compiler,
1374 &parsed_args,
1375 f.tempdir.path(),
1376 &[],
1377 )
1378 .unwrap();
1379 #[cfg(feature = "dist-client")]
1380 assert!(dist_command.is_some());
1381 #[cfg(not(feature = "dist-client"))]
1382 assert!(dist_command.is_none());
1383 let _ = command.execute(&creator).wait();
1384 assert_eq!(Cacheable::No, cacheable);
1385 // Ensure that we ran all processes.
1386 assert_eq!(0, creator.lock().unwrap().children.len());
1387 }
1388
1389 #[test]
1390 fn test_parse_fsanitize_blacklist() {
1391 let args = ovec![
1392 "-c",
1393 "foo.c",
1394 "-o",
1395 "foo.o",
1396 "-fsanitize-blacklist=list.txt"
1397 ];
1398 let ParsedArguments {
1399 common_args,
1400 extra_hash_files,
1401 ..
1402 } = match parse_arguments(args) {
1403 CompilerArguments::Ok(args) => args,
1404 o => panic!("Got unexpected parse result: {:?}", o),
1405 };
1406 assert_eq!(ovec!["-fsanitize-blacklist=list.txt"], common_args);
1407 assert_eq!(
1408 ovec![std::env::current_dir().unwrap().join("list.txt")],
1409 extra_hash_files
1410 );
1411 }
1412 }
1413