blob: 208952d253c3901aa0cb2544ef83e333a9f6525f [file] [log] [blame]
// 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);
}
}
}