1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "gn/compile_commands_writer.h"
6
7 #include <sstream>
8
9 #include "base/json/string_escape.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/stringprintf.h"
12 #include "gn/builder.h"
13 #include "gn/c_substitution_type.h"
14 #include "gn/c_tool.h"
15 #include "gn/config_values_extractors.h"
16 #include "gn/deps_iterator.h"
17 #include "gn/escape.h"
18 #include "gn/filesystem_utils.h"
19 #include "gn/ninja_target_command_util.h"
20 #include "gn/path_output.h"
21 #include "gn/substitution_writer.h"
22
23 // Structure of JSON output file
24 // [
25 // {
26 // "directory": "The build directory."
27 // "file": "The main source file processed by this compilation step.
28 // Must be absolute or relative to the above build directory."
29 // "command": "The compile command executed."
30 // }
31 // ...
32 // ]
33
34 namespace {
35
36 #if defined(OS_WIN)
37 const char kPrettyPrintLineEnding[] = "\r\n";
38 #else
39 const char kPrettyPrintLineEnding[] = "\n";
40 #endif
41
42 struct CompileFlags {
43 std::string includes;
44 std::string defines;
45 std::string cflags;
46 std::string cflags_c;
47 std::string cflags_cc;
48 std::string cflags_objc;
49 std::string cflags_objcc;
50 std::string framework_dirs;
51 std::string frameworks;
52 };
53
SetupCompileFlags(const Target * target,PathOutput & path_output,EscapeOptions opts,CompileFlags & flags)54 void SetupCompileFlags(const Target* target,
55 PathOutput& path_output,
56 EscapeOptions opts,
57 CompileFlags& flags) {
58 bool has_precompiled_headers =
59 target->config_values().has_precompiled_headers();
60
61 std::ostringstream defines_out;
62 RecursiveTargetConfigToStream<std::string>(target, &ConfigValues::defines,
63 DefineWriter(ESCAPE_SPACE, true),
64 defines_out);
65 base::EscapeJSONString(defines_out.str(), false, &flags.defines);
66
67 std::ostringstream framework_dirs_out;
68 RecursiveTargetConfigToStream<SourceDir>(
69 target, &ConfigValues::framework_dirs,
70 FrameworkDirsWriter(path_output, "-F"), framework_dirs_out);
71 base::EscapeJSONString(framework_dirs_out.str(), false,
72 &flags.framework_dirs);
73
74 std::ostringstream frameworks_out;
75 RecursiveTargetConfigToStream<std::string>(
76 target, &ConfigValues::frameworks,
77 FrameworksWriter(ESCAPE_SPACE, true, "-framework "), frameworks_out);
78 base::EscapeJSONString(frameworks_out.str(), false, &flags.frameworks);
79
80 std::ostringstream includes_out;
81 RecursiveTargetConfigToStream<SourceDir>(target, &ConfigValues::include_dirs,
82 IncludeWriter(path_output),
83 includes_out);
84 base::EscapeJSONString(includes_out.str(), false, &flags.includes);
85
86 std::ostringstream cflags_out;
87 WriteOneFlag(target, &CSubstitutionCFlags, false, Tool::kToolNone,
88 &ConfigValues::cflags, opts, path_output, cflags_out,
89 /*write_substitution=*/false);
90 base::EscapeJSONString(cflags_out.str(), false, &flags.cflags);
91
92 std::ostringstream cflags_c_out;
93 WriteOneFlag(target, &CSubstitutionCFlagsC, has_precompiled_headers,
94 CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output,
95 cflags_c_out, /*write_substitution=*/false);
96 base::EscapeJSONString(cflags_c_out.str(), false, &flags.cflags_c);
97
98 std::ostringstream cflags_cc_out;
99 WriteOneFlag(target, &CSubstitutionCFlagsCc, has_precompiled_headers,
100 CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output,
101 cflags_cc_out, /*write_substitution=*/false);
102 base::EscapeJSONString(cflags_cc_out.str(), false, &flags.cflags_cc);
103
104 std::ostringstream cflags_objc_out;
105 WriteOneFlag(target, &CSubstitutionCFlagsObjC, has_precompiled_headers,
106 CTool::kCToolObjC, &ConfigValues::cflags_objc, opts, path_output,
107 cflags_objc_out,
108 /*write_substitution=*/false);
109 base::EscapeJSONString(cflags_objc_out.str(), false, &flags.cflags_objc);
110
111 std::ostringstream cflags_objcc_out;
112 WriteOneFlag(target, &CSubstitutionCFlagsObjCc, has_precompiled_headers,
113 CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
114 path_output, cflags_objcc_out, /*write_substitution=*/false);
115 base::EscapeJSONString(cflags_objcc_out.str(), false, &flags.cflags_objcc);
116 }
117
WriteFile(const SourceFile & source,PathOutput & path_output,std::string * compile_commands)118 void WriteFile(const SourceFile& source,
119 PathOutput& path_output,
120 std::string* compile_commands) {
121 std::ostringstream rel_source_path;
122 path_output.WriteFile(rel_source_path, source);
123 compile_commands->append(" \"file\": \"");
124 compile_commands->append(rel_source_path.str());
125 }
126
WriteDirectory(std::string build_dir,std::string * compile_commands)127 void WriteDirectory(std::string build_dir, std::string* compile_commands) {
128 compile_commands->append("\",");
129 compile_commands->append(kPrettyPrintLineEnding);
130 compile_commands->append(" \"directory\": \"");
131 compile_commands->append(build_dir);
132 compile_commands->append("\",");
133 }
134
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)135 void WriteCommand(const Target* target,
136 const SourceFile& source,
137 const CompileFlags& flags,
138 std::vector<OutputFile>& tool_outputs,
139 PathOutput& path_output,
140 SourceFile::Type source_type,
141 const char* tool_name,
142 EscapeOptions opts,
143 std::string* compile_commands) {
144 EscapeOptions no_quoting(opts);
145 no_quoting.inhibit_quoting = true;
146 const Tool* tool = target->toolchain()->GetTool(tool_name);
147 std::ostringstream command_out;
148
149 for (const auto& range : tool->command().ranges()) {
150 // TODO: this is emitting a bonus space prior to each substitution.
151 if (range.type == &SubstitutionLiteral) {
152 EscapeStringToStream(command_out, range.literal, no_quoting);
153 } else if (range.type == &SubstitutionOutput) {
154 path_output.WriteFiles(command_out, tool_outputs);
155 } else if (range.type == &CSubstitutionDefines) {
156 command_out << flags.defines;
157 } else if (range.type == &CSubstitutionFrameworkDirs) {
158 command_out << flags.framework_dirs;
159 } else if (range.type == &CSubstitutionFrameworks) {
160 command_out << flags.frameworks;
161 } else if (range.type == &CSubstitutionIncludeDirs) {
162 command_out << flags.includes;
163 } else if (range.type == &CSubstitutionCFlags) {
164 command_out << flags.cflags;
165 } else if (range.type == &CSubstitutionCFlagsC) {
166 if (source_type == SourceFile::SOURCE_C)
167 command_out << flags.cflags_c;
168 } else if (range.type == &CSubstitutionCFlagsCc) {
169 if (source_type == SourceFile::SOURCE_CPP)
170 command_out << flags.cflags_cc;
171 } else if (range.type == &CSubstitutionCFlagsObjC) {
172 if (source_type == SourceFile::SOURCE_M)
173 command_out << flags.cflags_objc;
174 } else if (range.type == &CSubstitutionCFlagsObjCc) {
175 if (source_type == SourceFile::SOURCE_MM)
176 command_out << flags.cflags_objcc;
177 } else if (range.type == &SubstitutionLabel ||
178 range.type == &SubstitutionLabelName ||
179 range.type == &SubstitutionRootGenDir ||
180 range.type == &SubstitutionRootOutDir ||
181 range.type == &SubstitutionTargetGenDir ||
182 range.type == &SubstitutionTargetOutDir ||
183 range.type == &SubstitutionTargetOutputName ||
184 range.type == &SubstitutionSource ||
185 range.type == &SubstitutionSourceNamePart ||
186 range.type == &SubstitutionSourceFilePart ||
187 range.type == &SubstitutionSourceDir ||
188 range.type == &SubstitutionSourceRootRelativeDir ||
189 range.type == &SubstitutionSourceGenDir ||
190 range.type == &SubstitutionSourceOutDir ||
191 range.type == &SubstitutionSourceTargetRelative) {
192 EscapeStringToStream(command_out,
193 SubstitutionWriter::GetCompilerSubstitution(
194 target, source, range.type),
195 opts);
196 } else {
197 // Other flags shouldn't be relevant to compiling C/C++/ObjC/ObjC++
198 // source files.
199 NOTREACHED() << "Unsupported substitution for this type of target : "
200 << range.type->name;
201 continue;
202 }
203 }
204 compile_commands->append(kPrettyPrintLineEnding);
205 compile_commands->append(" \"command\": \"");
206 compile_commands->append(command_out.str());
207 }
208
209 } // namespace
210
RenderJSON(const BuildSettings * build_settings,std::vector<const Target * > & all_targets,std::string * compile_commands)211 void CompileCommandsWriter::RenderJSON(const BuildSettings* build_settings,
212 std::vector<const Target*>& all_targets,
213 std::string* compile_commands) {
214 // TODO: Determine out an appropriate size to reserve.
215 compile_commands->reserve(all_targets.size() * 100);
216 compile_commands->append("[");
217 compile_commands->append(kPrettyPrintLineEnding);
218 bool first = true;
219 auto build_dir = build_settings->GetFullPath(build_settings->build_dir())
220 .StripTrailingSeparators();
221 std::vector<OutputFile> tool_outputs; // Prevent reallocation in loop.
222
223 EscapeOptions opts;
224 opts.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND;
225
226 for (const auto* target : all_targets) {
227 if (!target->IsBinary())
228 continue;
229
230 // Precompute values that are the same for all sources in a target to avoid
231 // computing for every source.
232
233 PathOutput path_output(
234 target->settings()->build_settings()->build_dir(),
235 target->settings()->build_settings()->root_path_utf8(),
236 ESCAPE_NINJA_COMMAND);
237
238 CompileFlags flags;
239 SetupCompileFlags(target, path_output, opts, flags);
240
241 for (const auto& source : target->sources()) {
242 // If this source is not a C/C++/ObjC/ObjC++ source (not header) file,
243 // continue as it does not belong in the compilation database.
244 SourceFile::Type source_type = source.type();
245 if (source_type != SourceFile::SOURCE_CPP &&
246 source_type != SourceFile::SOURCE_C &&
247 source_type != SourceFile::SOURCE_M &&
248 source_type != SourceFile::SOURCE_MM)
249 continue;
250
251 const char* tool_name = Tool::kToolNone;
252 if (!target->GetOutputFilesForSource(source, &tool_name, &tool_outputs))
253 continue;
254
255 if (!first) {
256 compile_commands->append(",");
257 compile_commands->append(kPrettyPrintLineEnding);
258 }
259 first = false;
260 compile_commands->append(" {");
261 compile_commands->append(kPrettyPrintLineEnding);
262
263 WriteFile(source, path_output, compile_commands);
264 WriteDirectory(base::StringPrintf("%" PRIsFP, PATH_CSTR(build_dir)),
265 compile_commands);
266 WriteCommand(target, source, flags, tool_outputs, path_output,
267 source_type, tool_name, opts, compile_commands);
268 compile_commands->append("\"");
269 compile_commands->append(kPrettyPrintLineEnding);
270 compile_commands->append(" }");
271 }
272 }
273
274 compile_commands->append(kPrettyPrintLineEnding);
275 compile_commands->append("]");
276 compile_commands->append(kPrettyPrintLineEnding);
277 }
278
RunAndWriteFiles(const BuildSettings * build_settings,const Builder & builder,const std::string & file_name,const std::string & target_filters,bool quiet,Err * err)279 bool CompileCommandsWriter::RunAndWriteFiles(
280 const BuildSettings* build_settings,
281 const Builder& builder,
282 const std::string& file_name,
283 const std::string& target_filters,
284 bool quiet,
285 Err* err) {
286 SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
287 Value(nullptr, file_name), err);
288 if (output_file.is_null())
289 return false;
290
291 base::FilePath output_path = build_settings->GetFullPath(output_file);
292
293 std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
294
295 std::set<std::string> target_filters_set;
296 for (auto& target :
297 base::SplitString(target_filters, ",", base::TRIM_WHITESPACE,
298 base::SPLIT_WANT_NONEMPTY)) {
299 target_filters_set.insert(target);
300 }
301 std::string json;
302 if (target_filters_set.empty()) {
303 RenderJSON(build_settings, all_targets, &json);
304 } else {
305 std::vector<const Target*> preserved_targets =
306 FilterTargets(all_targets, target_filters_set);
307 RenderJSON(build_settings, preserved_targets, &json);
308 }
309
310 if (!WriteFileIfChanged(output_path, json, err))
311 return false;
312 return true;
313 }
314
FilterTargets(const std::vector<const Target * > & all_targets,const std::set<std::string> & target_filters_set)315 std::vector<const Target*> CompileCommandsWriter::FilterTargets(
316 const std::vector<const Target*>& all_targets,
317 const std::set<std::string>& target_filters_set) {
318 std::vector<const Target*> preserved_targets;
319
320 std::set<const Target*> visited;
321 for (auto& target : all_targets) {
322 if (target_filters_set.count(target->label().name())) {
323 VisitDeps(target, &visited);
324 }
325 }
326
327 preserved_targets.reserve(visited.size());
328 // Preserve the original ordering of all_targets
329 // to allow easier debugging and testing.
330 for (auto& target : all_targets) {
331 if (visited.count(target)) {
332 preserved_targets.push_back(target);
333 }
334 }
335 return preserved_targets;
336 }
337
VisitDeps(const Target * target,std::set<const Target * > * visited)338 void CompileCommandsWriter::VisitDeps(const Target* target,
339 std::set<const Target*>* visited) {
340 if (!visited->count(target)) {
341 visited->insert(target);
342 for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) {
343 VisitDeps(pair.ptr, visited);
344 }
345 }
346 }
347