• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 std::vector<const Target * > & all_targets,const std::vector<LabelPattern> & patterns,const std::optional<std::string> & legacy_target_filters,const base::FilePath & output_path,Err * err)300 bool CompileCommandsWriter::RunAndWriteFiles(
301     const BuildSettings* build_settings,
302     const std::vector<const Target*>& all_targets,
303     const std::vector<LabelPattern>& patterns,
304     const std::optional<std::string>& legacy_target_filters,
305     const base::FilePath& output_path,
306     Err* err) {
307   std::vector<const Target*> to_write = CollectTargets(
308       build_settings, all_targets, patterns, legacy_target_filters, err);
309   if (err->has_error())
310     return false;
311 
312   StringOutputBuffer json;
313   std::ostream output_to_json(&json);
314   OutputJSON(build_settings, to_write, output_to_json);
315 
316   return json.WriteToFileIfChanged(output_path, err);
317 }
318 
CollectTargets(const BuildSettings * build_setting,const std::vector<const Target * > & all_targets,const std::vector<LabelPattern> & patterns,const std::optional<std::string> & legacy_target_filters,Err * err)319 std::vector<const Target*> CompileCommandsWriter::CollectTargets(
320     const BuildSettings* build_setting,
321     const std::vector<const Target*>& all_targets,
322     const std::vector<LabelPattern>& patterns,
323     const std::optional<std::string>& legacy_target_filters,
324     Err* err) {
325   if (legacy_target_filters && legacy_target_filters->empty()) {
326     // The legacy filter was specified but has no parameter. This matches
327     // everything and we can skip any other kinds of matching.
328     return all_targets;
329   }
330 
331   // Collect the first level of target matches. These are the ones that the
332   // patterns match directly.
333   std::vector<const Target*> input_targets;
334   for (const Target* target : all_targets) {
335     if (LabelPattern::VectorMatches(patterns, target->label()))
336       input_targets.push_back(target);
337   }
338 
339   // Add in any legacy filter matches.
340   if (legacy_target_filters) {
341     std::vector<const Target*> legacy_matches =
342         FilterLegacyTargets(all_targets, *legacy_target_filters);
343 
344     // This can produce some duplicates with the patterns but the "collect
345     // deps" phase will eliminate them.
346     input_targets.insert(input_targets.end(), legacy_matches.begin(),
347                          legacy_matches.end());
348   }
349 
350   return CollectDepsOfMatches(input_targets);
351 }
352 
CollectDepsOfMatches(const std::vector<const Target * > & input_targets)353 std::vector<const Target*> CompileCommandsWriter::CollectDepsOfMatches(
354     const std::vector<const Target*>& input_targets) {
355   // The current set of matched targets.
356   TargetSet collected;
357 
358   // Represents the next layer of the breadth-first seach. These are all targets
359   // that we haven't checked so far.
360   std::vector<const Target*> frontier;
361 
362   // Collect the first level of target matches specified in the input. There may
363   // be duplicates so we still need to do the set checking.
364   // patterns match directly.
365   for (const Target* target : input_targets) {
366     if (!collected.contains(target)) {
367       collected.add(target);
368       frontier.push_back(target);
369     }
370   }
371 
372   // Collects the dependencies for the next level of iteration. This could be
373   // inside the loop but is kept outside to avoid reallocating in every
374   // iteration.
375   std::vector<const Target*> next_frontier;
376 
377   // Loop for each level of the search.
378   while (!frontier.empty()) {
379     for (const Target* target : frontier) {
380       // Check the target's dependencies.
381       for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) {
382         if (!collected.contains(pair.ptr)) {
383           // New dependency found.
384           collected.add(pair.ptr);
385           next_frontier.push_back(pair.ptr);
386         }
387       }
388     }
389 
390     // Swap to the new level and clear out the next one without deallocating the
391     // buffer (in most STL implementations, clear() doesn't free the existing
392     // buffer).
393     std::swap(frontier, next_frontier);
394     next_frontier.clear();
395   }
396 
397   // Convert to vector for output.
398   std::vector<const Target*> output;
399   output.reserve(collected.size());
400   for (const Target* target : collected) {
401     output.push_back(target);
402   }
403   return output;
404 }
405 
FilterLegacyTargets(const std::vector<const Target * > & all_targets,const std::string & target_filter_string)406 std::vector<const Target*> CompileCommandsWriter::FilterLegacyTargets(
407     const std::vector<const Target*>& all_targets,
408     const std::string& target_filter_string) {
409   std::set<std::string> target_filters_set;
410   for (auto& target :
411        base::SplitString(target_filter_string, ",", base::TRIM_WHITESPACE,
412                          base::SPLIT_WANT_NONEMPTY)) {
413     target_filters_set.insert(target);
414   }
415 
416   std::vector<const Target*> result;
417   for (auto& target : all_targets) {
418     if (target_filters_set.count(target->label().name()))
419       result.push_back(target);
420   }
421 
422   return result;
423 }
424