|  | // Copyright 2018 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "tools/gn/compile_commands_writer.h" | 
|  |  | 
|  | #include <sstream> | 
|  |  | 
|  | #include "base/json/string_escape.h" | 
|  | #include "base/strings/string_split.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "tools/gn/builder.h" | 
|  | #include "tools/gn/c_substitution_type.h" | 
|  | #include "tools/gn/c_tool.h" | 
|  | #include "tools/gn/config_values_extractors.h" | 
|  | #include "tools/gn/deps_iterator.h" | 
|  | #include "tools/gn/escape.h" | 
|  | #include "tools/gn/filesystem_utils.h" | 
|  | #include "tools/gn/ninja_target_command_util.h" | 
|  | #include "tools/gn/path_output.h" | 
|  | #include "tools/gn/substitution_writer.h" | 
|  |  | 
|  | // Structure of JSON output file | 
|  | // [ | 
|  | //   { | 
|  | //      "directory": "The build directory." | 
|  | //      "file": "The main source file processed by this compilation step. | 
|  | //               Must be absolute or relative to the above build directory." | 
|  | //      "command": "The compile command executed." | 
|  | //   } | 
|  | //   ... | 
|  | // ] | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | const char kPrettyPrintLineEnding[] = "\r\n"; | 
|  | #else | 
|  | const char kPrettyPrintLineEnding[] = "\n"; | 
|  | #endif | 
|  |  | 
|  | struct CompileFlags { | 
|  | std::string includes; | 
|  | std::string defines; | 
|  | std::string cflags; | 
|  | std::string cflags_c; | 
|  | std::string cflags_cc; | 
|  | std::string cflags_objc; | 
|  | std::string cflags_objcc; | 
|  | }; | 
|  |  | 
|  | void SetupCompileFlags(const Target* target, | 
|  | PathOutput& path_output, | 
|  | EscapeOptions opts, | 
|  | CompileFlags& flags) { | 
|  | bool has_precompiled_headers = | 
|  | target->config_values().has_precompiled_headers(); | 
|  |  | 
|  | std::ostringstream defines_out; | 
|  | RecursiveTargetConfigToStream<std::string>( | 
|  | target, &ConfigValues::defines, | 
|  | DefineWriter(ESCAPE_NINJA_PREFORMATTED_COMMAND, true), defines_out); | 
|  | base::EscapeJSONString(defines_out.str(), false, &flags.defines); | 
|  |  | 
|  | std::ostringstream includes_out; | 
|  | RecursiveTargetConfigToStream<SourceDir>(target, &ConfigValues::include_dirs, | 
|  | IncludeWriter(path_output), | 
|  | includes_out); | 
|  | base::EscapeJSONString(includes_out.str(), false, &flags.includes); | 
|  |  | 
|  | std::ostringstream cflags_out; | 
|  | WriteOneFlag(target, &CSubstitutionCFlags, false, Tool::kToolNone, | 
|  | &ConfigValues::cflags, opts, path_output, cflags_out, | 
|  | /*write_substitution=*/false); | 
|  | base::EscapeJSONString(cflags_out.str(), false, &flags.cflags); | 
|  |  | 
|  | std::ostringstream cflags_c_out; | 
|  | WriteOneFlag(target, &CSubstitutionCFlagsC, has_precompiled_headers, | 
|  | CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output, | 
|  | cflags_c_out, /*write_substitution=*/false); | 
|  | base::EscapeJSONString(cflags_c_out.str(), false, &flags.cflags_c); | 
|  |  | 
|  | std::ostringstream cflags_cc_out; | 
|  | WriteOneFlag(target, &CSubstitutionCFlagsCc, has_precompiled_headers, | 
|  | CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output, | 
|  | cflags_cc_out, /*write_substitution=*/false); | 
|  | base::EscapeJSONString(cflags_cc_out.str(), false, &flags.cflags_cc); | 
|  |  | 
|  | std::ostringstream cflags_objc_out; | 
|  | WriteOneFlag(target, &CSubstitutionCFlagsObjC, has_precompiled_headers, | 
|  | CTool::kCToolObjC, &ConfigValues::cflags_objc, opts, path_output, | 
|  | cflags_objc_out, | 
|  | /*write_substitution=*/false); | 
|  | base::EscapeJSONString(cflags_objc_out.str(), false, &flags.cflags_objc); | 
|  |  | 
|  | std::ostringstream cflags_objcc_out; | 
|  | WriteOneFlag(target, &CSubstitutionCFlagsObjCc, has_precompiled_headers, | 
|  | CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts, | 
|  | path_output, cflags_objcc_out, /*write_substitution=*/false); | 
|  | base::EscapeJSONString(cflags_objcc_out.str(), false, &flags.cflags_objcc); | 
|  | } | 
|  |  | 
|  | void WriteFile(const SourceFile& source, | 
|  | PathOutput& path_output, | 
|  | std::string* compile_commands) { | 
|  | std::ostringstream rel_source_path; | 
|  | path_output.WriteFile(rel_source_path, source); | 
|  | compile_commands->append("    \"file\": \""); | 
|  | compile_commands->append(rel_source_path.str()); | 
|  | } | 
|  |  | 
|  | void WriteDirectory(std::string build_dir, std::string* compile_commands) { | 
|  | compile_commands->append("\","); | 
|  | compile_commands->append(kPrettyPrintLineEnding); | 
|  | compile_commands->append("    \"directory\": \""); | 
|  | compile_commands->append(build_dir); | 
|  | compile_commands->append("\","); | 
|  | } | 
|  |  | 
|  | void WriteCommand(const Target* target, | 
|  | const SourceFile& source, | 
|  | const CompileFlags& flags, | 
|  | std::vector<OutputFile>& tool_outputs, | 
|  | PathOutput& path_output, | 
|  | SourceFile::Type source_type, | 
|  | const char* tool_name, | 
|  | EscapeOptions opts, | 
|  | std::string* compile_commands) { | 
|  | EscapeOptions no_quoting(opts); | 
|  | no_quoting.inhibit_quoting = true; | 
|  | const Tool* tool = target->toolchain()->GetTool(tool_name); | 
|  | std::ostringstream command_out; | 
|  |  | 
|  | for (const auto& range : tool->command().ranges()) { | 
|  | // TODO: this is emitting a bonus space prior to each substitution. | 
|  | if (range.type == &SubstitutionLiteral) { | 
|  | EscapeStringToStream(command_out, range.literal, no_quoting); | 
|  | } else if (range.type == &SubstitutionOutput) { | 
|  | path_output.WriteFiles(command_out, tool_outputs); | 
|  | } else if (range.type == &CSubstitutionDefines) { | 
|  | command_out << flags.defines; | 
|  | } else if (range.type == &CSubstitutionIncludeDirs) { | 
|  | command_out << flags.includes; | 
|  | } else if (range.type == &CSubstitutionCFlags) { | 
|  | command_out << flags.cflags; | 
|  | } else if (range.type == &CSubstitutionCFlagsC) { | 
|  | if (source_type == SourceFile::SOURCE_C) | 
|  | command_out << flags.cflags_c; | 
|  | } else if (range.type == &CSubstitutionCFlagsCc) { | 
|  | if (source_type == SourceFile::SOURCE_CPP) | 
|  | command_out << flags.cflags_cc; | 
|  | } else if (range.type == &CSubstitutionCFlagsObjC) { | 
|  | if (source_type == SourceFile::SOURCE_M) | 
|  | command_out << flags.cflags_objc; | 
|  | } else if (range.type == &CSubstitutionCFlagsObjCc) { | 
|  | if (source_type == SourceFile::SOURCE_MM) | 
|  | command_out << flags.cflags_objcc; | 
|  | } else if (range.type == &SubstitutionLabel || | 
|  | range.type == &SubstitutionLabelName || | 
|  | range.type == &SubstitutionRootGenDir || | 
|  | range.type == &SubstitutionRootOutDir || | 
|  | range.type == &SubstitutionTargetGenDir || | 
|  | range.type == &SubstitutionTargetOutDir || | 
|  | range.type == &SubstitutionTargetOutputName || | 
|  | range.type == &SubstitutionSource || | 
|  | range.type == &SubstitutionSourceNamePart || | 
|  | range.type == &SubstitutionSourceFilePart || | 
|  | range.type == &SubstitutionSourceDir || | 
|  | range.type == &SubstitutionSourceRootRelativeDir || | 
|  | range.type == &SubstitutionSourceGenDir || | 
|  | range.type == &SubstitutionSourceOutDir || | 
|  | range.type == &SubstitutionSourceTargetRelative) { | 
|  | EscapeStringToStream(command_out, | 
|  | SubstitutionWriter::GetCompilerSubstitution( | 
|  | target, source, range.type), | 
|  | opts); | 
|  | } else { | 
|  | // Other flags shouldn't be relevant to compiling C/C++/ObjC/ObjC++ | 
|  | // source files. | 
|  | NOTREACHED() << "Unsupported substitution for this type of target : " | 
|  | << range.type->name; | 
|  | continue; | 
|  | } | 
|  | } | 
|  | compile_commands->append(kPrettyPrintLineEnding); | 
|  | compile_commands->append("    \"command\": \""); | 
|  | compile_commands->append(command_out.str()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | void CompileCommandsWriter::RenderJSON(const BuildSettings* build_settings, | 
|  | std::vector<const Target*>& all_targets, | 
|  | std::string* compile_commands) { | 
|  | // TODO: Determine out an appropriate size to reserve. | 
|  | compile_commands->reserve(all_targets.size() * 100); | 
|  | compile_commands->append("["); | 
|  | compile_commands->append(kPrettyPrintLineEnding); | 
|  | bool first = true; | 
|  | auto build_dir = build_settings->GetFullPath(build_settings->build_dir()) | 
|  | .StripTrailingSeparators(); | 
|  | std::vector<OutputFile> tool_outputs;  // Prevent reallocation in loop. | 
|  |  | 
|  | EscapeOptions opts; | 
|  | opts.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND; | 
|  |  | 
|  | for (const auto* target : all_targets) { | 
|  | if (!target->IsBinary()) | 
|  | continue; | 
|  |  | 
|  | // Precompute values that are the same for all sources in a target to avoid | 
|  | // computing for every source. | 
|  |  | 
|  | PathOutput path_output( | 
|  | target->settings()->build_settings()->build_dir(), | 
|  | target->settings()->build_settings()->root_path_utf8(), | 
|  | ESCAPE_NINJA_COMMAND); | 
|  |  | 
|  | CompileFlags flags; | 
|  | SetupCompileFlags(target, path_output, opts, flags); | 
|  |  | 
|  | for (const auto& source : target->sources()) { | 
|  | // If this source is not a C/C++/ObjC/ObjC++ source (not header) file, | 
|  | // continue as it does not belong in the compilation database. | 
|  | SourceFile::Type source_type = source.type(); | 
|  | if (source_type != SourceFile::SOURCE_CPP && | 
|  | source_type != SourceFile::SOURCE_C && | 
|  | source_type != SourceFile::SOURCE_M && | 
|  | source_type != SourceFile::SOURCE_MM) | 
|  | continue; | 
|  |  | 
|  | const char* tool_name = Tool::kToolNone; | 
|  | if (!target->GetOutputFilesForSource(source, &tool_name, &tool_outputs)) | 
|  | continue; | 
|  |  | 
|  | if (!first) { | 
|  | compile_commands->append(","); | 
|  | compile_commands->append(kPrettyPrintLineEnding); | 
|  | } | 
|  | first = false; | 
|  | compile_commands->append("  {"); | 
|  | compile_commands->append(kPrettyPrintLineEnding); | 
|  |  | 
|  | WriteFile(source, path_output, compile_commands); | 
|  | WriteDirectory(base::StringPrintf("%" PRIsFP, build_dir.value().c_str()), | 
|  | compile_commands); | 
|  | WriteCommand(target, source, flags, tool_outputs, path_output, | 
|  | source_type, tool_name, opts, compile_commands); | 
|  | compile_commands->append("\""); | 
|  | compile_commands->append(kPrettyPrintLineEnding); | 
|  | compile_commands->append("  }"); | 
|  | } | 
|  | } | 
|  |  | 
|  | compile_commands->append(kPrettyPrintLineEnding); | 
|  | compile_commands->append("]"); | 
|  | compile_commands->append(kPrettyPrintLineEnding); | 
|  | } | 
|  |  | 
|  | bool CompileCommandsWriter::RunAndWriteFiles( | 
|  | const BuildSettings* build_settings, | 
|  | const Builder& builder, | 
|  | const std::string& file_name, | 
|  | const std::string& target_filters, | 
|  | bool quiet, | 
|  | Err* err) { | 
|  | SourceFile output_file = build_settings->build_dir().ResolveRelativeFile( | 
|  | Value(nullptr, file_name), err); | 
|  | if (output_file.is_null()) | 
|  | return false; | 
|  |  | 
|  | base::FilePath output_path = build_settings->GetFullPath(output_file); | 
|  |  | 
|  | std::vector<const Target*> all_targets = builder.GetAllResolvedTargets(); | 
|  |  | 
|  | std::set<std::string> target_filters_set; | 
|  | for (auto& target : | 
|  | base::SplitString(target_filters, ",", base::TRIM_WHITESPACE, | 
|  | base::SPLIT_WANT_NONEMPTY)) { | 
|  | target_filters_set.insert(target); | 
|  | } | 
|  | std::string json; | 
|  | if (target_filters_set.empty()) { | 
|  | RenderJSON(build_settings, all_targets, &json); | 
|  | } else { | 
|  | std::vector<const Target*> preserved_targets = | 
|  | FilterTargets(all_targets, target_filters_set); | 
|  | RenderJSON(build_settings, preserved_targets, &json); | 
|  | } | 
|  |  | 
|  | if (!WriteFileIfChanged(output_path, json, err)) | 
|  | return false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::vector<const Target*> CompileCommandsWriter::FilterTargets( | 
|  | const std::vector<const Target*>& all_targets, | 
|  | const std::set<std::string>& target_filters_set) { | 
|  | std::vector<const Target*> preserved_targets; | 
|  |  | 
|  | std::set<const Target*> visited; | 
|  | for (auto& target : all_targets) { | 
|  | if (target_filters_set.count(target->label().name())) { | 
|  | VisitDeps(target, &visited); | 
|  | } | 
|  | } | 
|  |  | 
|  | preserved_targets.reserve(visited.size()); | 
|  | // Preserve the original ordering of all_targets | 
|  | // to allow easier debugging and testing. | 
|  | for (auto& target : all_targets) { | 
|  | if (visited.count(target)) { | 
|  | preserved_targets.push_back(target); | 
|  | } | 
|  | } | 
|  | return preserved_targets; | 
|  | } | 
|  |  | 
|  | void CompileCommandsWriter::VisitDeps(const Target* target, | 
|  | std::set<const Target*>* visited) { | 
|  | if (!visited->count(target)) { | 
|  | visited->insert(target); | 
|  | for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) { | 
|  | VisitDeps(pair.ptr, visited); | 
|  | } | 
|  | } | 
|  | } |