diff --git a/misc/codespell-allowlist.txt b/misc/codespell-allowlist.txt index 952724dfaf..4a337e9b56 100644 --- a/misc/codespell-allowlist.txt +++ b/misc/codespell-allowlist.txt @@ -1,3 +1,4 @@ +fo copyable creat files' diff --git a/src/Args.cpp b/src/Args.cpp index e918c6a481..3731e26b66 100644 --- a/src/Args.cpp +++ b/src/Args.cpp @@ -50,7 +50,7 @@ Args::from_string(const std::string& command) } optional -Args::from_gcc_atfile(const std::string& filename) +Args::from_atfile(const std::string& filename, bool ignore_backslash) { std::string argtext; try { @@ -72,6 +72,9 @@ Args::from_gcc_atfile(const std::string& filename) while (true) { switch (*pos) { case '\\': + if (ignore_backslash) { + break; + } pos++; if (*pos == '\0') { continue; diff --git a/src/Args.hpp b/src/Args.hpp index a93d1881bb..1a07ddc716 100644 --- a/src/Args.hpp +++ b/src/Args.hpp @@ -36,7 +36,8 @@ class Args static Args from_argv(int argc, const char* const* argv); static Args from_string(const std::string& command); - static nonstd::optional from_gcc_atfile(const std::string& filename); + static nonstd::optional from_atfile(const std::string& filename, + bool ignore_backslash = false); Args& operator=(const Args& other) = default; Args& operator=(Args&& other) noexcept; diff --git a/src/Config.cpp b/src/Config.cpp index 9b619b211e..2dd1e44be2 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -455,6 +455,7 @@ compiler_type_to_string(CompilerType compiler_type) CASE(nvcc); CASE(other); CASE(pump); + CASE(cl); } #undef CASE diff --git a/src/Config.hpp b/src/Config.hpp index 4e386870d1..982511222c 100644 --- a/src/Config.hpp +++ b/src/Config.hpp @@ -31,7 +31,7 @@ #include #include -enum class CompilerType { auto_guess, clang, gcc, nvcc, other, pump }; +enum class CompilerType { auto_guess, clang, gcc, nvcc, other, pump, cl }; std::string compiler_type_to_string(CompilerType compiler_type); diff --git a/src/argprocessing.cpp b/src/argprocessing.cpp index 5b5b60540e..adab3e7c06 100644 --- a/src/argprocessing.cpp +++ b/src/argprocessing.cpp @@ -252,7 +252,7 @@ process_arg(const Context& ctx, } // Special case for -E. - if (args[i] == "-E") { + if (args[i] == "-E" || args[i] == "/E") { return Statistic::called_for_preprocessing; } @@ -263,7 +263,8 @@ process_arg(const Context& ctx, if (argpath[-1] == '-') { ++argpath; } - auto file_args = Args::from_gcc_atfile(argpath); + auto file_args = + Args::from_atfile(argpath, config.compiler_type() == CompilerType::cl); if (!file_args) { LOG("Couldn't read arg file {}", argpath); return Statistic::bad_compiler_arguments; @@ -286,7 +287,7 @@ process_arg(const Context& ctx, // Argument is a comma-separated list of files. auto paths = Util::split_into_strings(args[i], ","); for (auto it = paths.rbegin(); it != paths.rend(); ++it) { - auto file_args = Args::from_gcc_atfile(*it); + auto file_args = Args::from_atfile(*it); if (!file_args) { LOG("Couldn't read CUDA options file {}", *it); return Statistic::bad_compiler_arguments; @@ -300,7 +301,8 @@ process_arg(const Context& ctx, // These are always too hard. if (compopt_too_hard(args[i]) || util::starts_with(args[i], "-fdump-") - || util::starts_with(args[i], "-MJ")) { + || util::starts_with(args[i], "-MJ") || util::starts_with(args[i], "-Yc") + || util::starts_with(args[i], "/Yc")) { LOG("Compiler option {} is unsupported", args[i]); return Statistic::unsupported_compiler_option; } @@ -398,11 +400,19 @@ process_arg(const Context& ctx, } // We must have -c. - if (args[i] == "-c") { + if (args[i] == "-c" || args[i] == "/c") { state.found_c_opt = true; return nullopt; } + // MSVC /Fo with no space. + if (util::starts_with(args[i], "/Fo") + && config.compiler_type() == CompilerType::cl) { + args_info.output_obj = + Util::make_relative_path(ctx, string_view(args[i]).substr(3)); + return nullopt; + } + // when using nvcc with separable compilation, -dc implies -c if ((args[i] == "-dc" || args[i] == "--device-c") && config.compiler_type() == CompilerType::nvcc) { @@ -511,7 +521,8 @@ process_arg(const Context& ctx, // These options require special handling, because they behave differently // with gcc -E, when the output file is not specified. - if (args[i] == "-MD" || args[i] == "-MMD") { + if ((args[i] == "-MD" || args[i] == "-MMD") + && config.compiler_type() != CompilerType::cl) { args_info.generating_dependencies = true; args_info.seen_MD_MMD = true; state.dep_args.push_back(args[i]); @@ -845,7 +856,8 @@ process_arg(const Context& ctx, // Same as above but options with concatenated argument beginning with a // slash. - if (args[i][0] == '-') { + if (args[i][0] == '-' + || (config.compiler_type() == CompilerType::cl && args[i][0] == '/')) { size_t slash_pos = args[i].find('/'); if (slash_pos != std::string::npos) { std::string option = args[i].substr(0, slash_pos); @@ -883,7 +895,8 @@ process_arg(const Context& ctx, } // Other options. - if (args[i][0] == '-') { + if (args[i][0] == '-' + || (config.compiler_type() == CompilerType::cl && args[i][0] == '/')) { if (compopt_affects_cpp_output(args[i]) || compopt_prefix_affects_cpp_output(args[i])) { state.cpp_args.push_back(args[i]); diff --git a/src/ccache.cpp b/src/ccache.cpp index 85cc59a884..df8e620684 100644 --- a/src/ccache.cpp +++ b/src/ccache.cpp @@ -232,6 +232,8 @@ guess_compiler(string_view path) return CompilerType::nvcc; } else if (name == "pump" || name == "distcc-pump") { return CompilerType::pump; + } else if (name.find("cl") != nonstd::string_view::npos) { + return CompilerType::cl; } else { return CompilerType::other; } @@ -850,8 +852,12 @@ to_cache(Context& ctx, const Args& depend_extra_args, Hash* depend_mode_hash) { - args.push_back("-o"); - args.push_back(ctx.args_info.output_obj); + if (ctx.config.compiler_type() == CompilerType::cl) { + args.push_back(fmt::format("-Fo{}", ctx.args_info.output_obj)); + } else { + args.push_back("-o"); + args.push_back(ctx.args_info.output_obj); + } if (ctx.config.hard_link() && ctx.args_info.output_obj != "/dev/null") { // Workaround for Clang bug where it overwrites an existing object file @@ -930,9 +936,28 @@ to_cache(Context& ctx, return nonstd::make_unexpected(Statistic::missing_cache_file); } + // MSVC compiler always print the input file name to stdout, + // plus parts of the warnings/error messages. + // So we have to fusion that into stderr... + // Transform \r\n into \n. This way ninja won't produce empty newlines + // for the /showIncludes argument. + if (ctx.config.compiler_type() == CompilerType::cl) { + const std::string merged_output = + Util::read_file(tmp_stdout_path) + Util::read_file(tmp_stderr_path); + const std::string merged_output_with_unix_line_endings = + util::replace_all(merged_output, "\r\n", "\n"); + try { + Util::write_file(tmp_stderr_path, merged_output_with_unix_line_endings); + } catch (const core::Error& e) { + LOG("Failed writing to {}: {}", tmp_stderr_path, e.what()); + return nonstd::make_unexpected(Statistic::internal_error); + } + } + // distcc-pump outputs lines like this: // __________Using # distcc servers in pump mode - if (st.size() != 0 && ctx.config.compiler_type() != CompilerType::pump) { + if (st.size() != 0 && ctx.config.compiler_type() != CompilerType::pump + && ctx.config.compiler_type() != CompilerType::cl) { LOG_RAW("Compiler produced stdout"); return nonstd::make_unexpected(Statistic::compiler_produced_stdout); } diff --git a/src/compopt.cpp b/src/compopt.cpp index e2f0fe1e44..84fe3ab11b 100644 --- a/src/compopt.cpp +++ b/src/compopt.cpp @@ -142,6 +142,20 @@ const CompOpt compopts[] = { {"-stdlib=", AFFECTS_CPP | TAKES_CONCAT_ARG}, {"-trigraphs", AFFECTS_CPP}, {"-u", TAKES_ARG | TAKES_CONCAT_ARG}, + {"/AI", TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc + {"/D", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG}, // msvc + {"/E", TOO_HARD}, // msvc + {"/EP", TOO_HARD}, // msvc + {"/FI", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc + {"/FU", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc + {"/I", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc + {"/L", TAKES_ARG}, // msvc + {"/P", TOO_HARD}, // msvc + {"/U", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG}, // msvc + {"/Yc", TAKES_ARG | TOO_HARD}, // msvc + {"/ZI", TOO_HARD}, // msvc + {"/Zi", TOO_HARD}, // msvc + {"/u", AFFECTS_CPP}, // msvc }; static int diff --git a/unittest/test_Args.cpp b/unittest/test_Args.cpp index 1aea024aec..391b71bd86 100644 --- a/unittest/test_Args.cpp +++ b/unittest/test_Args.cpp @@ -76,7 +76,7 @@ TEST_CASE("Args::from_string") CHECK(args[3] == "f"); } -TEST_CASE("Args::from_gcc_atfile") +TEST_CASE("Args::from_atfile") { TestContext test_context; @@ -84,20 +84,20 @@ TEST_CASE("Args::from_gcc_atfile") SUBCASE("Nonexistent file") { - CHECK(Args::from_gcc_atfile("at_file") == nonstd::nullopt); + CHECK(Args::from_atfile("at_file") == nonstd::nullopt); } SUBCASE("Empty") { Util::write_file("at_file", ""); - args = *Args::from_gcc_atfile("at_file"); + args = *Args::from_atfile("at_file"); CHECK(args.size() == 0); } SUBCASE("One argument without newline") { Util::write_file("at_file", "foo"); - args = *Args::from_gcc_atfile("at_file"); + args = *Args::from_atfile("at_file"); CHECK(args.size() == 1); CHECK(args[0] == "foo"); } @@ -105,7 +105,7 @@ TEST_CASE("Args::from_gcc_atfile") SUBCASE("One argument with newline") { Util::write_file("at_file", "foo\n"); - args = *Args::from_gcc_atfile("at_file"); + args = *Args::from_atfile("at_file"); CHECK(args.size() == 1); CHECK(args[0] == "foo"); } @@ -113,7 +113,7 @@ TEST_CASE("Args::from_gcc_atfile") SUBCASE("Multiple simple arguments") { Util::write_file("at_file", "x y z\n"); - args = *Args::from_gcc_atfile("at_file"); + args = *Args::from_atfile("at_file"); CHECK(args.size() == 3); CHECK(args[0] == "x"); CHECK(args[1] == "y"); @@ -126,7 +126,7 @@ TEST_CASE("Args::from_gcc_atfile") "at_file", "first\rsec\\\tond\tthi\\\\rd\nfourth \tfif\\ th \"si'x\\\" th\"" " 'seve\nth'\\"); - args = *Args::from_gcc_atfile("at_file"); + args = *Args::from_atfile("at_file"); CHECK(args.size() == 7); CHECK(args[0] == "first"); CHECK(args[1] == "sec\tond");