return tl::unexpected(Statistic::internal_error);
}
+ // If includes were already extracted from /sourceDependencies or
+ // /showIncludes (this avoids source encoding issues on Windows), don't
+ // extract them from the preprocessor output.
+ const bool includes_already_extracted = !ctx.included_files.empty();
+
std::unordered_map<std::string, std::string> relative_inc_path_cache;
// Bytes between p and q are pending to be hashed.
r++;
}
- // p and q span the include file path.
- std::string inc_path(p, q - p);
- while (!inc_path.empty() && inc_path.back() == '/') {
- inc_path.pop_back();
- }
- fs::path inc_fs_path;
- try {
- inc_fs_path = inc_path;
- } catch (const std::filesystem::filesystem_error&) {
- return tl::unexpected(Failure(Statistic::unsupported_source_encoding));
- }
- if (!ctx.config.base_dirs().empty()) {
- auto it = relative_inc_path_cache.find(inc_path);
- if (it == relative_inc_path_cache.end()) {
- std::string rel_inc_path =
- util::pstr(core::make_relative_path(ctx, inc_fs_path));
- relative_inc_path_cache.emplace(inc_path, rel_inc_path);
- inc_path = util::pstr(rel_inc_path);
- } else {
- inc_path = it->second;
+ if (includes_already_extracted) {
+ hash.hash(p, q - p);
+ } else {
+ // p and q span the include file path.
+ std::string inc_path(p, q - p);
+ while (!inc_path.empty() && inc_path.back() == '/') {
+ inc_path.pop_back();
+ }
+ fs::path inc_fs_path;
+ try {
+ inc_fs_path = inc_path;
+ } catch (const std::filesystem::filesystem_error&) {
+ return tl::unexpected(
+ Failure(Statistic::unsupported_source_encoding));
+ }
+ if (!ctx.config.base_dirs().empty()) {
+ auto it = relative_inc_path_cache.find(inc_path);
+ if (it == relative_inc_path_cache.end()) {
+ std::string rel_inc_path =
+ util::pstr(core::make_relative_path(ctx, inc_fs_path));
+ relative_inc_path_cache.emplace(inc_path, rel_inc_path);
+ inc_path = util::pstr(rel_inc_path);
+ } else {
+ inc_path = it->second;
+ }
}
- }
- if (inc_fs_path != ctx.apparent_cwd || ctx.config.hash_dir()) {
- hash.hash(inc_fs_path);
+ if (inc_fs_path != ctx.apparent_cwd || ctx.config.hash_dir()) {
+ hash.hash(inc_fs_path);
+ }
+
+ TRY(remember_include_file(ctx, inc_fs_path, hash, system, nullptr));
}
- TRY(remember_include_file(ctx, inc_fs_path, hash, system, nullptr));
p = q; // Everything of interest between p and q has been hashed now.
} else if (strncmp(q, incbin_directive, sizeof(incbin_directive)) == 0
&& ((q[7] == ' '
// mention of it in the preprocessed output.
TRY(check_included_pch_file(ctx, hash));
- bool debug_included = getenv("CCACHE_DEBUG_INCLUDED");
- if (debug_included) {
- print_included_files(ctx, stdout);
- }
-
return {};
}
// dependencies output.
TRY(check_included_pch_file(ctx, hash));
- bool debug_included = getenv("CCACHE_DEBUG_INCLUDED");
- if (debug_included) {
- print_included_files(ctx, stdout);
- }
-
return hash.digest();
}
util::Bytes stderr_data;
};
-// Extract the used includes from /showIncludes (or clang-cl's
-// /showIncludes:user) output in stdout. Note that we cannot distinguish system
-// headers from other includes when /showIncludes is used.
-static tl::expected<Hash::Digest, Failure>
-result_key_from_includes(Context& ctx, Hash& hash, std::string_view stdout_data)
+static tl::expected<void, Failure>
+extract_includes_from_msvc_stdout(Context& ctx,
+ Hash& hash,
+ std::string_view stdout_data)
{
+ ASSERT(ctx.config.is_compiler_group_msvc());
+ ASSERT(ctx.config.compiler_type() != CompilerType::msvc);
+
for (std::string_view include :
compiler::get_includes_from_msvc_show_includes(
stdout_data, ctx.config.msvc_dep_prefix())) {
- const fs::path path = core::make_relative_path(ctx, include);
- TRY(remember_include_file(ctx, path, hash, false, &hash));
+ try {
+ const fs::path path = core::make_relative_path(ctx, include);
+ TRY(remember_include_file(ctx, path, hash, false, &hash));
+ } catch (const std::filesystem::filesystem_error&) {
+ return tl::unexpected(Failure(Statistic::unsupported_source_encoding));
+ }
}
- // Explicitly check the .pch file as it is not mentioned in the
- // includes output.
+ // Explicitly check the .pch file as it is not mentioned in the output.
TRY(check_included_pch_file(ctx, hash));
- const bool debug_included = getenv("CCACHE_DEBUG_INCLUDED");
- if (debug_included) {
- print_included_files(ctx, stdout);
+ return {};
+}
+
+static tl::expected<void, Failure>
+extract_includes_from_msvc_source_deps_file(
+ Context& ctx, Hash& hash, const fs::path& source_dependencies_file)
+{
+ ASSERT(ctx.config.compiler_type() == CompilerType::msvc);
+
+ auto json_content = util::read_file<std::string>(source_dependencies_file);
+ if (!json_content) {
+ LOG("Failed to read /sourceDependencies file {}: {}",
+ source_dependencies_file,
+ json_content.error());
+ return tl::unexpected(Statistic::internal_error);
}
+ auto includes = compiler::get_includes_from_msvc_source_deps(*json_content);
+ if (!includes) {
+ LOG("Failed to parse /sourceDependencies file: {}", includes.error());
+ return tl::unexpected(Failure(Statistic::internal_error));
+ }
+ for (const auto& include : *includes) {
+ try {
+ const fs::path path = core::make_relative_path(ctx, include);
+ TRY(remember_include_file(ctx, path, hash, false, &hash));
+ } catch (const std::filesystem::filesystem_error&) {
+ return tl::unexpected(Failure(Statistic::unsupported_source_encoding));
+ }
+ }
+
+ // Explicitly check the .pch file as it is not mentioned in the output.
+ TRY(check_included_pch_file(ctx, hash));
+
+ return {};
+}
+
+// Extract used includes from /showIncludes (or clang-cl's /showIncludes:user)
+// output in stdout. Note that we cannot distinguish system headers from other
+// includes when /showIncludes is used.
+static tl::expected<Hash::Digest, Failure>
+result_key_from_show_includes(Context& ctx,
+ Hash& hash,
+ std::string_view stdout_data)
+{
+ TRY(extract_includes_from_msvc_stdout(ctx, hash, stdout_data));
+ return hash.digest();
+}
+
+// Extract used includes from /sourceDependencies file for MSVC in depend mode.
+// Note that we cannot distinguish system headers from other includes when
+// /sourceDependencies is used.
+static tl::expected<Hash::Digest, Failure>
+result_key_from_source_deps(Context& ctx, Hash& hash, const fs::path& path)
+{
+ TRY(extract_includes_from_msvc_source_deps_file(ctx, hash, path));
return hash.digest();
}
}
static util::Bytes
-rewrite_stdout_from_compiler(const Context& ctx, util::Bytes&& stdout_data)
+rewrite_stdout_from_compiler(const Context& ctx,
+ util::Bytes&& stdout_data,
+ bool strip_includes_from_stdout)
{
using util::Tokenizer;
using Mode = Tokenizer::Mode;
using IncludeDelimiter = Tokenizer::IncludeDelimiter;
- if (!stdout_data.empty()) {
- util::Bytes new_stdout_data;
- for (const auto line : Tokenizer(util::to_string_view(stdout_data),
- "\n",
- Mode::include_empty,
- IncludeDelimiter::yes)) {
- if (util::starts_with(line, "__________")) {
- // distcc-pump outputs lines like this:
- //
- // __________Using # distcc servers in pump mode
- //
- // We don't want to cache those.
- core::send_to_console(ctx, line, STDOUT_FILENO);
- } else if (ctx.config.compiler_type() == CompilerType::msvc
- && !ctx.config.base_dirs().empty()
- && util::starts_with(line, ctx.config.msvc_dep_prefix())) {
- // Ninja uses the lines with 'Note: including file: ' to determine the
- // used headers. Headers within basedir need to be changed into relative
- // paths because otherwise Ninja will use the abs path to original
- // header to check if a file needs to be recompiled.
- std::string orig_line(line.data(), line.length());
- std::string abs_inc_path =
- util::replace_first(orig_line, ctx.config.msvc_dep_prefix(), "");
- abs_inc_path = util::strip_whitespace(abs_inc_path);
- fs::path rel_inc_path = core::make_relative_path(ctx, abs_inc_path);
- std::string line_with_rel_inc = util::replace_first(
- orig_line, abs_inc_path, util::pstr(rel_inc_path).str());
- new_stdout_data.insert(new_stdout_data.end(),
- line_with_rel_inc.data(),
- line_with_rel_inc.size());
- } else if (ctx.config.is_compiler_group_msvc()
- && !ctx.config.base_dirs().empty()) {
- // The MSVC /FC option causes paths in diagnostics messages to become
- // absolute. Those within basedir need to be changed into relative
- // paths.
- size_t path_end = core::get_diagnostics_path_length(line);
- if (path_end != 0) {
- std::string_view abs_path = line.substr(0, path_end);
- fs::path rel_path = core::make_relative_path(ctx, abs_path);
- std::string line_with_rel =
- util::replace_all(line, abs_path, util::pstr(rel_path).str());
- new_stdout_data.insert(
- new_stdout_data.end(), line_with_rel.data(), line_with_rel.size());
- }
- } else {
- new_stdout_data.insert(new_stdout_data.end(), line.data(), line.size());
+
+ if (stdout_data.empty()) {
+ return std::move(stdout_data);
+ }
+
+ util::Bytes new_stdout_data;
+ for (const auto line : Tokenizer(util::to_string_view(stdout_data),
+ "\n",
+ Mode::include_empty,
+ IncludeDelimiter::yes)) {
+ if (util::starts_with(line, "__________")) {
+ // distcc-pump outputs lines like this:
+ //
+ // __________Using # distcc servers in pump mode
+ //
+ // We don't want to cache those.
+ core::send_to_console(ctx, line, STDOUT_FILENO);
+ } else if (strip_includes_from_stdout
+ && util::starts_with(line, ctx.config.msvc_dep_prefix())) {
+ // Strip line.
+ } else if (ctx.config.is_compiler_group_msvc()
+ && !ctx.config.base_dirs().empty()) {
+ // The MSVC /FC option causes paths in diagnostics messages to become
+ // absolute. Those within basedir need to be changed into relative
+ // paths.
+ size_t path_end = core::get_diagnostics_path_length(line);
+ if (path_end != 0) {
+ std::string_view abs_path = line.substr(0, path_end);
+ fs::path rel_path = core::make_relative_path(ctx, abs_path);
+ std::string line_with_rel =
+ util::replace_all(line, abs_path, util::pstr(rel_path).str());
+ new_stdout_data.insert(
+ new_stdout_data.end(), line_with_rel.data(), line_with_rel.size());
}
+ } else {
+ new_stdout_data.insert(new_stdout_data.end(), line.data(), line.size());
}
- return new_stdout_data;
- } else {
- return std::move(stdout_data);
}
+ return new_stdout_data;
}
static std::string
}
}
+ bool capture_stdout = true;
+
+ // For MSVC/clang-cl in depend mode, set up includes extraction.
+ fs::path msvc_deps_file;
+ bool strip_includes_from_stdout = false;
+
+ if (ctx.config.depend_mode() && ctx.config.is_compiler_group_msvc()) {
+ if (ctx.config.compiler_type() == CompilerType::msvc) {
+ // MSVC: set up /sourceDependencies if needed. If user specified
+ // /sourceDependencies, use their file.
+ if (!ctx.args_info.output_sd.empty()) {
+ msvc_deps_file = ctx.args_info.output_sd;
+ } else {
+ auto tmp_deps =
+ util::value_or_throw<core::Fatal>(util::TemporaryFile::create(
+ FMT("{}/source_deps", ctx.config.temporary_dir()), ".json"));
+ msvc_deps_file = tmp_deps.path;
+ tmp_deps.fd.close();
+ ctx.register_pending_tmp_file(msvc_deps_file);
+
+ // Add flag since we're managing the file (not user-specified).
+ args.push_back("/sourceDependencies");
+ args.push_back(msvc_deps_file);
+ }
+ // Also add /showIncludes as a fallback to support older MSVC versions
+ // without /sourceDependencies. Only strip from stdout if we injected
+ // the flag ourselves (not when the user already specified it).
+ if (!ctx.args_info.generating_includes) {
+ args.push_back("/showIncludes");
+ strip_includes_from_stdout = true;
+ }
+ } else {
+ if (!ctx.args_info.generating_includes) {
+ args.push_back("/showIncludes");
+ strip_includes_from_stdout = true;
+ }
+ capture_stdout = true; // To be able to parse /showIncludes output
+ }
+ }
+
LOG_RAW("Running real compiler");
+ auto result = do_execute(ctx, args, capture_stdout);
- tl::expected<DoExecuteResult, Failure> result;
- result = do_execute(ctx, args);
+ // No early abort: if /sourceDependencies is unsupported, fall back to
+ // parsing /showIncludes output below.
if (!result) {
return tl::unexpected(result.error());
ctx.cpp_stderr_data.end());
}
- result->stdout_data =
- rewrite_stdout_from_compiler(ctx, std::move(result->stdout_data));
-
if (result->exit_status != 0) {
LOG("Compiler gave exit status {}", result->exit_status);
core::send_to_console(
ctx, util::to_string_view(result->stderr_data), STDERR_FILENO);
core::send_to_console(
- ctx,
- util::to_string_view(compiler::strip_includes_from_msvc_show_includes(
- ctx, std::move(result->stdout_data))),
- STDOUT_FILENO);
+ ctx, util::to_string_view(result->stdout_data), STDOUT_FILENO);
auto failure = Failure(Statistic::compile_failed);
failure.set_exit_code(result->exit_status);
if (ctx.config.depend_mode()) {
ASSERT(depend_mode_hash);
- if (ctx.args_info.generating_dependencies) {
- TRY_ASSIGN(result_key, result_key_from_depfile(ctx, *depend_mode_hash));
- } else if (ctx.args_info.generating_includes) {
+ if (ctx.config.compiler_type() == CompilerType::msvc) {
+ if (fs::exists(msvc_deps_file)) {
+ TRY_ASSIGN(
+ result_key,
+ result_key_from_source_deps(ctx, *depend_mode_hash, msvc_deps_file));
+ } else {
+ TRY_ASSIGN(
+ result_key,
+ result_key_from_show_includes(
+ ctx, *depend_mode_hash, util::to_string_view(result->stdout_data)));
+ }
+ } else if (ctx.config.is_compiler_group_msvc()) {
+ // Compiler printed /showIncludes output to stdout.
TRY_ASSIGN(
result_key,
- result_key_from_includes(
+ result_key_from_show_includes(
ctx, *depend_mode_hash, util::to_string_view(result->stdout_data)));
} else {
- ASSERT(false);
+ ASSERT(ctx.args_info.generating_dependencies);
+ TRY_ASSIGN(result_key, result_key_from_depfile(ctx, *depend_mode_hash));
+ }
+
+ if (getenv("CCACHE_DEBUG_INCLUDED")) {
+ print_included_files(ctx, stdout);
}
+
LOG_RAW("Got result key from dependency file");
LOG("Result key: {}", util::format_digest(*result_key));
}
}
}
- if (!write_result(
- ctx, *result_key, result->stdout_data, result->stderr_data)) {
+ // Strip output from /showIncludes if we added it ourselves for depend mode
+ // and rewrite diagnostics paths as needed, then store and display.
+ auto rewritten_stdout = rewrite_stdout_from_compiler(
+ ctx, std::move(result->stdout_data), strip_includes_from_stdout);
+
+ if (!write_result(ctx, *result_key, rewritten_stdout, result->stderr_data)) {
return tl::unexpected(Statistic::compiler_produced_no_output);
}
ctx, util::to_string_view(result->stderr_data), STDERR_FILENO);
// Send stdout after stderr, it makes the output clearer with MSVC.
core::send_to_console(
- ctx,
- util::to_string_view(compiler::strip_includes_from_msvc_show_includes(
- ctx, std::move(result->stdout_data))),
- STDOUT_FILENO);
+ ctx, util::to_string_view(rewritten_stdout), STDOUT_FILENO);
return *result_key;
}
args.push_back("-C");
}
+ fs::path msvc_deps_file;
+ if (ctx.config.is_compiler_group_msvc()) {
+ // For MSVC: use /sourceDependencies (MSVC 2017 15.7+). For others: use
+ // /showIncludes since only MSVC supports /sourceDependencies.
+ if (ctx.config.compiler_type() == CompilerType::msvc) {
+ // Use existing file from command line if present, otherwise create one
+ // ourselves.
+ if (!ctx.args_info.output_sd.empty()) {
+ msvc_deps_file = ctx.args_info.output_sd;
+ } else {
+ auto tmp_deps =
+ util::value_or_throw<core::Fatal>(util::TemporaryFile::create(
+ FMT("{}/source_deps", ctx.config.temporary_dir()), ".json"));
+ msvc_deps_file = tmp_deps.path;
+ tmp_deps.fd.close();
+ ctx.register_pending_tmp_file(msvc_deps_file);
+
+ args.push_back("/sourceDependencies");
+ args.push_back(msvc_deps_file);
+ }
+ } else {
+ args.push_back("/showIncludes");
+ }
+ }
+
// Send preprocessor output to a file instead of stdout to work around
// compilers that don't exit with a proper status on write error to stdout.
// See also <https://github.com/llvm/llvm-project/issues/56499>.
if (ctx.config.is_compiler_group_msvc()) {
- if (ctx.config.msvc_utf8()) {
- args.push_back("-utf-8"); // Avoid garbling filenames in output
- }
args.push_back("-P");
args.push_back(FMT("-Fi{}", preprocessed_path));
} else {
add_prefix(ctx, args, ctx.config.prefix_command_cpp());
LOG_RAW("Running preprocessor");
- const auto result = do_execute(ctx, args, capture_stdout);
+ auto result = do_execute(ctx, args, capture_stdout);
args.pop_back(args.size() - orig_args_size);
if (!result) {
return tl::unexpected(result.error());
- } else if (result->exit_status != 0) {
+ }
+
+ if (result->exit_status != 0) {
LOG("Preprocessor gave exit status {}", result->exit_status);
return tl::unexpected(Statistic::preprocessor_error);
}
- cpp_stderr_data = result->stderr_data;
- cpp_stdout_data = result->stdout_data;
+ if (!msvc_deps_file.empty() && !fs::exists(msvc_deps_file)) {
+ LOG_RAW(
+ "Compiler doesn't support /sourceDependencies, extracting includes"
+ " from preprocessed output instead");
+ msvc_deps_file.clear();
+ // The compiler likely printed "cl : Command line warning D9002 : ignoring
+ // unknown option '/sourceDependencies'" to stdout, but no need to scrub
+ // that since we don't hash stdout and we don't send it to the console
+ // either.
+ }
+
+ cpp_stderr_data = std::move(result->stderr_data);
+ cpp_stdout_data = std::move(result->stdout_data);
- if (ctx.config.is_compiler_group_msvc() && ctx.config.msvc_utf8()) {
- // Check that usage of -utf-8 didn't garble the preprocessor output.
- static constexpr char warning_c4828[] =
- "warning C4828: The file contains a character starting at offset";
- if (util::to_string_view(cpp_stderr_data).find(warning_c4828)
- != std::string_view::npos) {
- LOG_RAW("Non-UTF-8 source code unsupported in preprocessor mode");
- return tl::unexpected(Statistic::unsupported_source_encoding);
- }
+ if (ctx.config.is_compiler_group_msvc()) {
+ if (ctx.config.compiler_type() != CompilerType::msvc) {
+ TRY(extract_includes_from_msvc_stdout(
+ ctx, hash, util::to_string_view(cpp_stdout_data)));
+ } else if (!msvc_deps_file.empty()) {
+ TRY(extract_includes_from_msvc_source_deps_file(
+ ctx, hash, msvc_deps_file));
+ } // else: extract includes from preprocessed output
}
}
for (size_t i = 0; i < chunks.size(); ++i) {
TRY(process_cuda_chunk(ctx, hash, chunks[i], i));
}
-
} else {
hash.hash_delimiter("cpp");
-
TRY(process_preprocessed_file(ctx, hash, preprocessed_path));
}
+ if (getenv("CCACHE_DEBUG_INCLUDED")) {
+ print_included_files(ctx, stdout);
+ }
+
hash.hash_delimiter("cppstderr");
hash.hash(util::to_string_view(cpp_stderr_data));
}
}
- if (ctx.config.depend_mode()
- && !(ctx.args_info.generating_dependencies
- || ctx.args_info.generating_includes)) {
- LOG_RAW("Disabling depend mode");
+ if (ctx.config.depend_mode() && !ctx.config.is_compiler_group_msvc()
+ && !ctx.args_info.generating_dependencies) {
+ LOG_RAW("Disabling depend mode since dependency file isn't generated");
ctx.config.set_depend_mode(false);
}
#include <doctest/doctest.h>
-static const std::string defaultPrefix = "Note: including file:";
+const std::string_view k_default_prefix = "Note: including file:";
TEST_SUITE_BEGIN("msvc");
SUBCASE("Parse empty output")
{
std::string contents;
- const auto result =
- compiler::get_includes_from_msvc_show_includes(contents, defaultPrefix);
+ const auto result = compiler::get_includes_from_msvc_show_includes(
+ contents, k_default_prefix);
CHECK(result.size() == 0);
}
Note: including file: F:\Projects\ccache\src\NonCopyable.hpp
Note: including file: C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.33.31629\include\deque
)";
- const auto result =
- compiler::get_includes_from_msvc_show_includes(contents, defaultPrefix);
+ const auto result = compiler::get_includes_from_msvc_show_includes(
+ contents, k_default_prefix);
REQUIRE(result.size() == 5);
CHECK(result[0] == "F:/Projects/ccache/build-msvc/config.h");
CHECK(result[1] == R"(F:\Projects\ccache\unittest\../src/Context.hpp)");
std::string contents =
"Note: including file: foo\r\n"
"Note: including file: bar\r\n";
- const auto result =
- compiler::get_includes_from_msvc_show_includes(contents, defaultPrefix);
+ const auto result = compiler::get_includes_from_msvc_show_includes(
+ contents, k_default_prefix);
REQUIRE(result.size() == 2);
CHECK(result[0] == "foo");
CHECK(result[1] == "bar");
"Note: including file: foo\n"
"Note: including file: \n"
"Note: including file: bar\n";
- const auto result =
- compiler::get_includes_from_msvc_show_includes(contents, defaultPrefix);
+ const auto result = compiler::get_includes_from_msvc_show_includes(
+ contents, k_default_prefix);
REQUIRE(result.size() == 2);
CHECK(result[0] == "foo");
CHECK(result[1] == "bar");
}
}
-TEST_CASE("strip_includes_from_msvc_show_includes")
+TEST_CASE("get_includes_from_msvc_source_deps")
{
- Context ctx;
- const util::Bytes input = util::to_span(
- "First\n"
- "Note: including file: foo\n"
- "Second\n");
-
- SUBCASE("Empty output")
+ SUBCASE("Simple case")
{
- const util::Bytes result =
- compiler::strip_includes_from_msvc_show_includes(ctx, {});
- CHECK(result.size() == 0);
+ std::string json = R"({
+ "Version": "1.1",
+ "Data": {
+ "Source": "C:\\path\\to\\source.cpp",
+ "Includes": [
+ "C:\\path\\to\\header1.h",
+ "C:\\path\\to\\header2.h"
+ ]
+ }
+})";
+
+ auto includes_res = compiler::get_includes_from_msvc_source_deps(json);
+ REQUIRE(includes_res);
+ const auto& includes = *includes_res;
+ REQUIRE(includes.size() == 2);
+ CHECK(includes[0] == "C:\\path\\to\\header1.h");
+ CHECK(includes[1] == "C:\\path\\to\\header2.h");
}
- SUBCASE("Feature disabled")
+ SUBCASE("Empty includes array")
{
- const util::Bytes result =
- compiler::strip_includes_from_msvc_show_includes(ctx, util::Bytes(input));
- CHECK(result == input);
+ std::string json = R"({
+ "Version": "1.1",
+ "Data": {
+ "Source": "C:\\path\\to\\source.cpp",
+ "Includes": []
}
+})";
- ctx.auto_depend_mode = true;
+ auto includes_res = compiler::get_includes_from_msvc_source_deps(json);
+ REQUIRE(includes_res);
+ CHECK(includes_res->empty());
+ }
- SUBCASE("Wrong compiler")
+ SUBCASE("Escaped paths")
{
- const util::Bytes result =
- compiler::strip_includes_from_msvc_show_includes(ctx, util::Bytes(input));
- CHECK(result == input);
+ std::string json = R"({
+ "Version": "1.1",
+ "Data": {
+ "Source": "C:\\path\\to\\source.cpp",
+ "Includes": [
+ "C:\\path\\to\\header\"with\"quotes.h",
+ "C:\\path\\to\\header\\with\\backslashes.h"
+ ]
+ }
+})";
+
+ auto includes_res = compiler::get_includes_from_msvc_source_deps(json);
+ REQUIRE(includes_res);
+ const auto& includes = *includes_res;
+ REQUIRE(includes.size() == 2);
+ CHECK(includes[0] == "C:\\path\\to\\header\"with\"quotes.h");
+ CHECK(includes[1] == "C:\\path\\to\\header\\with\\backslashes.h");
}
- ctx.config.set_compiler_type(CompilerType::msvc);
-
- SUBCASE("Simple output")
+ SUBCASE("Minified JSON")
{
- const util::Bytes result =
- compiler::strip_includes_from_msvc_show_includes(ctx, util::Bytes(input));
- CHECK(result == util::to_span("First\nSecond\n"));
+ std::string json =
+ R"({"Version":"1.1","Data":{"Source":"C:\\source.cpp","Includes":["C:\\header1.h","C:\\header2.h"]}})";
+
+ auto includes_res = compiler::get_includes_from_msvc_source_deps(json);
+ REQUIRE(includes_res);
+ const auto& includes = *includes_res;
+ REQUIRE(includes.size() == 2);
+ CHECK(includes[0] == "C:\\header1.h");
+ CHECK(includes[1] == "C:\\header2.h");
}
- SUBCASE("Empty lines")
+ SUBCASE("UTF-8 paths")
{
- const util::Bytes result = compiler::strip_includes_from_msvc_show_includes(
- ctx,
- util::to_span("First\n"
- "\n"
- "Note: including file: foo\n"
- "\n"
- "Second\n"
- "\n"));
- CHECK(result == util::to_span("First\n\n\nSecond\n\n"));
- }
-
- SUBCASE("CRLF")
+ std::string json = R"({
+ "Version": "1.1",
+ "Data": {
+ "Source": "C:\\日本語\\source.cpp",
+ "Includes": [
+ "C:\\日本語\\header1.h",
+ "C:\\Ελληνικά\\header2.h"
+ ]
+ }
+})";
+
+ auto includes_res = compiler::get_includes_from_msvc_source_deps(json);
+ REQUIRE(includes_res);
+ const auto& includes = *includes_res;
+ REQUIRE(includes.size() == 2);
+ CHECK(includes[0] == "C:\\日本語\\header1.h");
+ CHECK(includes[1] == "C:\\Ελληνικά\\header2.h");
+ }
+
+ SUBCASE("Invalid JSON")
{
- const util::Bytes result = compiler::strip_includes_from_msvc_show_includes(
- ctx,
- util::to_span("First\r\n"
- "Note: including file: foo\r\n"
- "Second\r\n"));
- CHECK(result == util::to_span("First\r\nSecond\r\n"));
+ auto includes_res =
+ compiler::get_includes_from_msvc_source_deps("not json");
+ REQUIRE(!includes_res);
+ CHECK(includes_res.error().find("Expected object") != std::string::npos);
}
- SUBCASE("Custom prefix")
+ SUBCASE("Unicode escape sequences are rejected")
{
- ctx.config.set_msvc_dep_prefix("custom");
- const util::Bytes result = compiler::strip_includes_from_msvc_show_includes(
- ctx,
- util::to_span("First\n"
- "custom: including file: foo\n"
- "Second\n"
- "Third custom line\n"));
- CHECK(result == util::to_span("First\nSecond\nThird custom line\n"));
+ std::string json = R"({
+ "Version": "1.1",
+ "Data": {
+ "Includes": ["C:\\path\\to\\\u65E5\u672C\u8A9E.h"]
+ }
+})";
+ auto includes_res = compiler::get_includes_from_msvc_source_deps(json);
+ CHECK_FALSE(includes_res);
}
}