• 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/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