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