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);
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