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/ninja_target_command_util.h"
19 #include "gn/path_output.h"
20 #include "gn/string_output_buffer.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
54 // Helper template function to call RecursiveTargetConfigToStream<std::string>
55 // and return the JSON-escaped resulting string.
56 //
57 // NOTE: The Windows compiler cannot properly deduce the first parameter type
58 // so pass it at each call site to ensure proper builds for this platform.
59 template <typename T, typename Writer>
FlagsGetter(RecursiveWriterConfig config,const Target * target,const std::vector<T> & (ConfigValues::* getter)()const,const Writer & writer)60 std::string FlagsGetter(RecursiveWriterConfig config,
61 const Target* target,
62 const std::vector<T>& (ConfigValues::*getter)() const,
63 const Writer& writer) {
64 std::string result;
65 std::ostringstream out;
66 RecursiveTargetConfigToStream<T>(config, target, getter, writer, out);
67 base::EscapeJSONString(out.str(), false, &result);
68 return result;
69 };
70
SetupCompileFlags(const Target * target,PathOutput & path_output,EscapeOptions opts,CompileFlags & flags)71 void SetupCompileFlags(const Target* target,
72 PathOutput& path_output,
73 EscapeOptions opts,
74 CompileFlags& flags) {
75 bool has_precompiled_headers =
76 target->config_values().has_precompiled_headers();
77
78 flags.defines = FlagsGetter<std::string>(
79 kRecursiveWriterSkipDuplicates, target, &ConfigValues::defines,
80 DefineWriter(ESCAPE_COMPILATION_DATABASE));
81
82 flags.framework_dirs = FlagsGetter<SourceDir>(
83 kRecursiveWriterSkipDuplicates, target, &ConfigValues::framework_dirs,
84 FrameworkDirsWriter(path_output, "-F"));
85
86 flags.frameworks = FlagsGetter<std::string>(
87 kRecursiveWriterSkipDuplicates, target, &ConfigValues::frameworks,
88 FrameworksWriter(ESCAPE_COMPILATION_DATABASE, "-framework"));
89 flags.frameworks += FlagsGetter<std::string>(
90 kRecursiveWriterSkipDuplicates, target, &ConfigValues::weak_frameworks,
91 FrameworksWriter(ESCAPE_COMPILATION_DATABASE, "-weak_framework"));
92
93 flags.includes = FlagsGetter<SourceDir>(kRecursiveWriterSkipDuplicates,
94 target, &ConfigValues::include_dirs,
95 IncludeWriter(path_output));
96
97 // Helper lambda to call WriteOneFlag() and return the resulting
98 // escaped JSON string.
99 auto one_flag = [&](RecursiveWriterConfig config,
100 const Substitution* substitution,
101 bool has_precompiled_headers, const char* tool_name,
102 const std::vector<std::string>& (ConfigValues::*getter)()
103 const) -> std::string {
104 std::string result;
105 std::ostringstream out;
106 WriteOneFlag(config, target, substitution, has_precompiled_headers,
107 tool_name, getter, opts, path_output, out,
108 /*write_substitution=*/false, /*indent=*/false);
109 base::EscapeJSONString(out.str(), false, &result);
110 return result;
111 };
112
113 flags.cflags = one_flag(kRecursiveWriterKeepDuplicates, &CSubstitutionCFlags,
114 false, Tool::kToolNone, &ConfigValues::cflags);
115
116 flags.cflags_c = one_flag(kRecursiveWriterKeepDuplicates,
117 &CSubstitutionCFlagsC, has_precompiled_headers,
118 CTool::kCToolCc, &ConfigValues::cflags_c);
119
120 flags.cflags_cc = one_flag(kRecursiveWriterKeepDuplicates,
121 &CSubstitutionCFlagsCc, has_precompiled_headers,
122 CTool::kCToolCxx, &ConfigValues::cflags_cc);
123
124 flags.cflags_objc = one_flag(
125 kRecursiveWriterKeepDuplicates, &CSubstitutionCFlagsObjC,
126 has_precompiled_headers, CTool::kCToolObjC, &ConfigValues::cflags_objc);
127
128 flags.cflags_objcc =
129 one_flag(kRecursiveWriterKeepDuplicates, &CSubstitutionCFlagsObjCc,
130 has_precompiled_headers, CTool::kCToolObjCxx,
131 &ConfigValues::cflags_objcc);
132 }
133
WriteFile(const SourceFile & source,PathOutput & path_output,std::ostream & out)134 void WriteFile(const SourceFile& source,
135 PathOutput& path_output,
136 std::ostream& out) {
137 std::ostringstream rel_source_path;
138 out << " \"file\": \"";
139 path_output.WriteFile(out, source);
140 }
141
WriteDirectory(std::string build_dir,std::ostream & out)142 void WriteDirectory(std::string build_dir, std::ostream& out) {
143 out << "\",";
144 out << kPrettyPrintLineEnding;
145 out << " \"directory\": \"";
146 out << build_dir;
147 out << "\",";
148 }
149
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::ostream & out)150 void WriteCommand(const Target* target,
151 const SourceFile& source,
152 const CompileFlags& flags,
153 std::vector<OutputFile>& tool_outputs,
154 PathOutput& path_output,
155 SourceFile::Type source_type,
156 const char* tool_name,
157 EscapeOptions opts,
158 std::ostream& out) {
159 EscapeOptions no_quoting(opts);
160 no_quoting.inhibit_quoting = true;
161 const Tool* tool = target->toolchain()->GetTool(tool_name);
162
163 out << kPrettyPrintLineEnding;
164 out << " \"command\": \"";
165
166 for (const auto& range : tool->command().ranges()) {
167 // TODO: this is emitting a bonus space prior to each substitution.
168 if (range.type == &SubstitutionLiteral) {
169 EscapeJSONStringToStream(out, range.literal, no_quoting);
170 } else if (range.type == &SubstitutionOutput) {
171 path_output.WriteFiles(out, tool_outputs);
172 } else if (range.type == &CSubstitutionDefines) {
173 out << flags.defines;
174 } else if (range.type == &CSubstitutionFrameworkDirs) {
175 out << flags.framework_dirs;
176 } else if (range.type == &CSubstitutionFrameworks) {
177 out << flags.frameworks;
178 } else if (range.type == &CSubstitutionIncludeDirs) {
179 out << flags.includes;
180 } else if (range.type == &CSubstitutionCFlags) {
181 out << flags.cflags;
182 } else if (range.type == &CSubstitutionCFlagsC) {
183 if (source_type == SourceFile::SOURCE_C)
184 out << flags.cflags_c;
185 } else if (range.type == &CSubstitutionCFlagsCc) {
186 if (source_type == SourceFile::SOURCE_CPP)
187 out << flags.cflags_cc;
188 } else if (range.type == &CSubstitutionCFlagsObjC) {
189 if (source_type == SourceFile::SOURCE_M)
190 out << flags.cflags_objc;
191 } else if (range.type == &CSubstitutionCFlagsObjCc) {
192 if (source_type == SourceFile::SOURCE_MM)
193 out << flags.cflags_objcc;
194 } else if (range.type == &SubstitutionLabel ||
195 range.type == &SubstitutionLabelName ||
196 range.type == &SubstitutionLabelNoToolchain ||
197 range.type == &SubstitutionRootGenDir ||
198 range.type == &SubstitutionRootOutDir ||
199 range.type == &SubstitutionTargetGenDir ||
200 range.type == &SubstitutionTargetOutDir ||
201 range.type == &SubstitutionTargetOutputName ||
202 range.type == &SubstitutionSource ||
203 range.type == &SubstitutionSourceNamePart ||
204 range.type == &SubstitutionSourceFilePart ||
205 range.type == &SubstitutionSourceDir ||
206 range.type == &SubstitutionSourceRootRelativeDir ||
207 range.type == &SubstitutionSourceGenDir ||
208 range.type == &SubstitutionSourceOutDir ||
209 range.type == &SubstitutionSourceTargetRelative) {
210 EscapeStringToStream(out,
211 SubstitutionWriter::GetCompilerSubstitution(
212 target, source, range.type),
213 opts);
214 } else {
215 // Other flags shouldn't be relevant to compiling C/C++/ObjC/ObjC++
216 // source files.
217 NOTREACHED() << "Unsupported substitution for this type of target : "
218 << range.type->name;
219 continue;
220 }
221 }
222 }
223
OutputJSON(const BuildSettings * build_settings,std::vector<const Target * > & all_targets,std::ostream & out)224 void OutputJSON(const BuildSettings* build_settings,
225 std::vector<const Target*>& all_targets,
226 std::ostream& out) {
227 out << '[';
228 out << kPrettyPrintLineEnding;
229 bool first = true;
230 auto build_dir = build_settings->GetFullPath(build_settings->build_dir())
231 .StripTrailingSeparators();
232 std::vector<OutputFile> tool_outputs; // Prevent reallocation in loop.
233
234 EscapeOptions opts;
235 opts.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND;
236
237 for (const auto* target : all_targets) {
238 if (!target->IsBinary())
239 continue;
240
241 // Precompute values that are the same for all sources in a target to avoid
242 // computing for every source.
243
244 PathOutput path_output(
245 target->settings()->build_settings()->build_dir(),
246 target->settings()->build_settings()->root_path_utf8(),
247 ESCAPE_NINJA_COMMAND);
248
249 CompileFlags flags;
250 SetupCompileFlags(target, path_output, opts, flags);
251
252 for (const auto& source : target->sources()) {
253 // If this source is not a C/C++/ObjC/ObjC++ source (not header) file,
254 // continue as it does not belong in the compilation database.
255 const SourceFile::Type source_type = source.GetType();
256 if (source_type != SourceFile::SOURCE_CPP &&
257 source_type != SourceFile::SOURCE_C &&
258 source_type != SourceFile::SOURCE_M &&
259 source_type != SourceFile::SOURCE_MM)
260 continue;
261
262 const char* tool_name = Tool::kToolNone;
263 if (!target->GetOutputFilesForSource(source, &tool_name, &tool_outputs))
264 continue;
265
266 if (!first) {
267 out << ',';
268 out << kPrettyPrintLineEnding;
269 }
270 first = false;
271 out << " {";
272 out << kPrettyPrintLineEnding;
273
274 WriteFile(source, path_output, out);
275 WriteDirectory(base::StringPrintf("%" PRIsFP, PATH_CSTR(build_dir)), out);
276 WriteCommand(target, source, flags, tool_outputs, path_output,
277 source_type, tool_name, opts, out);
278 out << "\"";
279 out << kPrettyPrintLineEnding;
280 out << " }";
281 }
282 }
283
284 out << kPrettyPrintLineEnding;
285 out << "]";
286 out << kPrettyPrintLineEnding;
287 }
288
289 } // namespace
290
RenderJSON(const BuildSettings * build_settings,std::vector<const Target * > & all_targets)291 std::string CompileCommandsWriter::RenderJSON(
292 const BuildSettings* build_settings,
293 std::vector<const Target*>& all_targets) {
294 StringOutputBuffer json;
295 std::ostream out(&json);
296 OutputJSON(build_settings, all_targets, out);
297 return json.str();
298 }
299
RunAndWriteFiles(const BuildSettings * build_settings,const Builder & builder,const std::string & file_name,const std::string & target_filters,bool quiet,Err * err)300 bool CompileCommandsWriter::RunAndWriteFiles(
301 const BuildSettings* build_settings,
302 const Builder& builder,
303 const std::string& file_name,
304 const std::string& target_filters,
305 bool quiet,
306 Err* err) {
307 SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
308 Value(nullptr, file_name), err);
309 if (output_file.is_null())
310 return false;
311
312 base::FilePath output_path = build_settings->GetFullPath(output_file);
313
314 std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
315
316 std::set<std::string> target_filters_set;
317 for (auto& target :
318 base::SplitString(target_filters, ",", base::TRIM_WHITESPACE,
319 base::SPLIT_WANT_NONEMPTY)) {
320 target_filters_set.insert(target);
321 }
322
323 StringOutputBuffer json;
324 std::ostream output_to_json(&json);
325 if (target_filters_set.empty()) {
326 OutputJSON(build_settings, all_targets, output_to_json);
327 } else {
328 std::vector<const Target*> preserved_targets =
329 FilterTargets(all_targets, target_filters_set);
330 OutputJSON(build_settings, preserved_targets, output_to_json);
331 }
332
333 return json.WriteToFileIfChanged(output_path, err);
334 }
335
FilterTargets(const std::vector<const Target * > & all_targets,const std::set<std::string> & target_filters_set)336 std::vector<const Target*> CompileCommandsWriter::FilterTargets(
337 const std::vector<const Target*>& all_targets,
338 const std::set<std::string>& target_filters_set) {
339 std::vector<const Target*> preserved_targets;
340
341 TargetSet visited;
342 for (auto& target : all_targets) {
343 if (target_filters_set.count(target->label().name())) {
344 VisitDeps(target, &visited);
345 }
346 }
347
348 preserved_targets.reserve(visited.size());
349 // Preserve the original ordering of all_targets
350 // to allow easier debugging and testing.
351 for (auto& target : all_targets) {
352 if (visited.contains(target)) {
353 preserved_targets.push_back(target);
354 }
355 }
356 return preserved_targets;
357 }
358
VisitDeps(const Target * target,TargetSet * visited)359 void CompileCommandsWriter::VisitDeps(const Target* target,
360 TargetSet* visited) {
361 if (visited->add(target)) {
362 for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) {
363 VisitDeps(pair.ptr, visited);
364 }
365 }
366 }
367