Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(322)

Side by Side Diff: tools/gn/analyzer.cc

Issue 2265833002: Implement `gn analyze`. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: update with review feedback, rename graph -> analyzer, add proper unit tests Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2016 The Chromium Authors. All rights reserved.
brettw 2016/08/25 21:22:30 No (c)
Dirk Pranke 2016/08/26 00:34:13 Done.
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 "tools/gn/analyzer.h"
6
7 #include <algorithm>
8 #include <iterator>
9 #include <set>
10 #include <vector>
11
12 #include "base/json/json_reader.h"
13 #include "base/json/json_writer.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/string_util.h"
17 #include "base/values.h"
18 #include "tools/gn/builder.h"
19 #include "tools/gn/deps_iterator.h"
20 #include "tools/gn/err.h"
21 #include "tools/gn/loader.h"
22 #include "tools/gn/location.h"
23 #include "tools/gn/source_file.h"
24 #include "tools/gn/target.h"
25
26 using LabelSet = Analyzer::LabelSet;
27 using SourceFileSet = Analyzer::SourceFileSet;
28 using TargetSet = Analyzer::TargetSet;
29
30 namespace {
31
32 struct Inputs {
33 std::vector<SourceFile> source_vec;
34 std::vector<Label> compile_vec;
35 std::vector<Label> test_vec;
36 bool compile_included_all;
37 SourceFileSet source_files;
38 LabelSet compile_labels;
39 LabelSet test_labels;
40 };
41
42 struct Outputs {
43 std::string status;
44 std::string error;
45 LabelSet compile_labels;
46 LabelSet test_labels;
47 LabelSet invalid_labels;
48 };
49
50 LabelSet LabelsFor(const TargetSet& targets) {
51 LabelSet labels;
52 for (const auto& target : targets)
53 labels.insert(target->label());
54 return labels;
55 }
56
57 bool AnyBuildFilesWereModified(const SourceFileSet& source_files) {
58 for (const auto& file : source_files) {
59 if (base::EndsWith(file->value(), ".gn", base::CompareCase::SENSITIVE) ||
60 base::EndsWith(file->value(), ".gni", base::CompareCase::SENSITIVE))
61 return true;
62 }
63 return false;
64 }
65
66 TargetSet Intersect(const TargetSet& l, const TargetSet& r) {
67 TargetSet result;
68 std::set_intersection(l.begin(), l.end(), r.begin(), r.end(),
69 std::inserter(result, result.begin()));
70 return result;
71 }
72
73 std::vector<std::string> GetStringVector(const base::DictionaryValue& dict,
74 const std::string& key,
75 Err* err) {
76 std::vector<std::string> strings;
77 const base::ListValue* lst;
78 bool ret = dict.GetList(key, &lst);
79 if (!ret) {
80 *err = Err(Location(), "Input does not have a key named \"" + key +
81 "\" with a list value.");
82 return strings;
83 }
84
85 for (size_t i = 0; i < lst->GetSize(); i++) {
86 std::string s;
87 ret = lst->GetString(i, &s);
88 if (!ret) {
89 *err = Err(Location(), "Item " + std::to_string(i) + " of \"" + key +
90 "\" is not a string.");
91 strings.clear();
92 return strings;
93 }
94 strings.push_back(std::move(s));
95 }
96 *err = Err();
97 return strings;
98 }
99
100 void WriteString(base::DictionaryValue& dict,
101 const std::string& key,
102 const std::string& value) {
103 dict.SetString(key, value);
104 };
105
106 void WriteLabels(const Label& default_toolchain,
107 base::DictionaryValue& dict,
108 const std::string& key,
109 const LabelSet& labels) {
110 std::vector<std::string> strings;
111 auto value = base::WrapUnique(new base::ListValue());
112 for (const auto l : labels)
113 strings.push_back(l.GetUserVisibleName(default_toolchain));
114 std::sort(strings.begin(), strings.end());
115 value->AppendStrings(strings);
116 dict.Set(key, std::move(value));
117 }
118
119 bool IsSourceAbsolute(const std::string& path) {
brettw 2016/08/25 21:22:29 I think this should allow filesystem absolute path
Dirk Pranke 2016/08/26 00:34:13 I don't know where we use absolute paths, and doin
brettw 2016/08/26 17:09:19 Most of our support for stuff outside of the sourc
Dirk Pranke 2016/08/26 20:40:05 It's true that we support absolute paths in some s
120 return path.size() >= 2 && path[0] == '/' && path[1] == '/';
121 }
122
123 Label SourceAbsolutePathToLabel(const Label& default_toolchain,
brettw 2016/08/25 21:22:29 You're really taking label strings as inputs rathe
Dirk Pranke 2016/08/26 00:34:13 Fair enough. Will change.
124 const std::string& path,
125 Err* err) {
126 if (!IsSourceAbsolute(path)) {
127 *err = Err(Location(), "\"" + path + "\" is not a source-absolute path");
128 return Label();
129 }
130
131 SourceDir source_root("//");
132 Value v(nullptr, Value::STRING);
brettw 2016/08/25 21:22:30 These two lines can be: Value v(nullptr, path);
Dirk Pranke 2016/08/26 00:34:13 Done.
133 v.string_value() = path;
134 return Label::Resolve(source_root, default_toolchain, v, err);
135 }
136
137 Err JSONToInputs(const Label& default_toolchain,
138 const std::string input,
139 Inputs* inputs) {
140 int error_code_out;
141 std::string error_msg_out;
142 int error_line_out;
143 int error_column_out;
144 std::unique_ptr<base::Value> value = base::JSONReader().ReadAndReturnError(
145 input, base::JSONParserOptions::JSON_PARSE_RFC, &error_code_out,
146 &error_msg_out, &error_line_out, &error_column_out);
147 if (!value)
148 return Err(Location(), "Input is not valid JSON:" + error_msg_out);
149
150 const base::DictionaryValue* dict;
151 if (!value->GetAsDictionary(&dict))
152 return Err(Location(), "Input is not a dictionary.");
153
154 Err err;
155 std::vector<std::string> strings;
156 strings = GetStringVector(*dict, "files", &err);
157 if (err.has_error())
158 return err;
159 for (auto s : strings) {
160 if (!IsSourceAbsolute(s))
161 return Err(Location(), "\"" + s + "\" is not a source-absolute path.");
162 inputs->source_vec.push_back(SourceFile(s));
163 }
164
165 strings = GetStringVector(*dict, "compile_targets", &err);
166 if (err.has_error())
167 return err;
168
169 inputs->compile_included_all = false;
170 for (auto& s : strings) {
171 if (s == "all") {
172 inputs->compile_included_all = true;
173 } else {
174 inputs->compile_vec.push_back(
175 SourceAbsolutePathToLabel(default_toolchain, s, &err));
176 if (err.has_error())
177 return err;
178 }
179 }
180
181 strings = GetStringVector(*dict, "test_targets", &err);
182 if (err.has_error())
183 return err;
184 for (auto& s : strings) {
185 inputs->test_vec.push_back(
186 SourceAbsolutePathToLabel(default_toolchain, s, &err));
187 if (err.has_error())
188 return err;
189 }
190
191 for (auto& s : inputs->source_vec)
192 inputs->source_files.insert(&s);
193 for (auto& l : inputs->compile_vec)
194 inputs->compile_labels.insert(l);
195 for (auto& l : inputs->test_vec)
196 inputs->test_labels.insert(l);
197 return Err();
198 }
199
200 std::string OutputsToJSON(const Outputs& outputs,
201 const Label& default_toolchain) {
202 std::string output;
203 auto value = base::MakeUnique<base::DictionaryValue>();
204
205 if (outputs.error.size()) {
206 WriteString(*value, "error", outputs.error);
207 WriteLabels(default_toolchain, *value, "invalid_targets",
208 outputs.invalid_labels);
209 } else {
210 WriteString(*value, "status", outputs.status);
211 WriteLabels(default_toolchain, *value, "compile_targets",
212 outputs.compile_labels);
213 WriteLabels(default_toolchain, *value, "test_targets", outputs.test_labels);
214 }
215
216 base::JSONWriter::WriteWithOptions(
217 *value.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &output);
218 return output;
219 }
220
221 } // namespace
222
223 Analyzer::Analyzer(const Builder& builder)
224 : all_targets_(builder.GetAllResolvedTargets()),
225 default_toolchain_(builder.loader()->GetDefaultToolchain()) {
226 for (const auto* target : all_targets_) {
227 labels_to_targets_[target->label()] = target;
228 for (const auto& dep_pair : target->GetDeps(Target::DEPS_ALL))
229 dep_map_.insert(std::make_pair(dep_pair.ptr, target));
230 }
231 for (const auto* target : all_targets_) {
232 if (dep_map_.find(target) == dep_map_.end())
233 roots_.insert(target);
234 }
235 }
236
237 Analyzer::~Analyzer() {}
238
239 std::string Analyzer::Analyze(const std::string& input, Err* err) {
brettw 2016/08/25 21:22:29 I can live with what you wrote but I would have pe
Dirk Pranke 2016/08/26 00:34:13 Yeah, I struggled with this tradeoff and the code
240 Inputs inputs;
241 Outputs outputs;
242
243 Err local_err = JSONToInputs(default_toolchain_, input, &inputs);
244 if (local_err.has_error()) {
245 outputs.error = local_err.message();
246 if (err)
247 *err = Err();
248 return "";
249 }
250
251 LabelSet invalid_labels;
252 for (const auto& label : InvalidLabels(inputs.compile_labels))
253 invalid_labels.insert(label);
254 for (const auto& label : InvalidLabels(inputs.test_labels))
255 invalid_labels.insert(label);
256 if (!invalid_labels.empty()) {
257 outputs.error = "Invalid targets";
258 outputs.invalid_labels = invalid_labels;
259 if (err)
260 *err = Err();
261 return OutputsToJSON(outputs, default_toolchain_);
262 }
263
264 TargetSet affected_targets = AllAffectedTargets(inputs.source_files);
265 if (affected_targets.empty()) {
266 outputs.status = "No dependency";
267 if (err)
268 *err = Err();
269 return OutputsToJSON(outputs, default_toolchain_);
270 }
271
272 // TODO: We can do smarter things when we detect changes to build files.
273 // For example, if all of the ninja files are unchanged, we know that we
274 // can ignore changes to these files. Also, for most .gn files, we can
275 // treat a change as simply affecting every target, config, or toolchain
276 // defined in that file.
277 if (AnyBuildFilesWereModified(inputs.source_files)) {
278 outputs.status = "Found dependency (all)";
279 outputs.compile_labels = inputs.compile_labels;
280 outputs.test_labels = inputs.test_labels;
281 if (err)
282 *err = Err();
283 return OutputsToJSON(outputs, default_toolchain_);
284 }
285
286 TargetSet compile_targets = TargetsFor(inputs.compile_labels);
287 if (inputs.compile_included_all) {
288 for (auto& root : roots_)
289 compile_targets.insert(root);
290 }
291 TargetSet filtered_targets = Filter(compile_targets);
292 outputs.compile_labels =
293 LabelsFor(Intersect(filtered_targets, affected_targets));
294
295 TargetSet test_targets = TargetsFor(inputs.test_labels);
296 outputs.test_labels = LabelsFor(Intersect(test_targets, affected_targets));
297
298 if (outputs.compile_labels.empty() && outputs.test_labels.empty())
299 outputs.status = "No dependency";
300 else
301 outputs.status = "Found dependency";
302 *err = Err();
303 return OutputsToJSON(outputs, default_toolchain_);
304 }
305
306 TargetSet Analyzer::AllAffectedTargets(const SourceFileSet& source_files) {
307 TargetSet direct_matches;
308 for (const auto& source_file : source_files)
309 AddTargetsDirectlyReferringToFileTo(source_file, &direct_matches);
310 TargetSet all_matches;
311 for (const auto& match : direct_matches)
312 AddAllRefsTo(match, &all_matches);
313 return all_matches;
314 }
315
316 LabelSet Analyzer::InvalidLabels(const LabelSet& labels) {
317 LabelSet invalid_labels;
318 for (const Label& label : labels) {
319 if (labels_to_targets_.find(label) == labels_to_targets_.end())
320 invalid_labels.insert(label);
321 }
322 return invalid_labels;
323 }
324
325 TargetSet Analyzer::TargetsFor(const LabelSet& labels) {
326 TargetSet targets;
327 for (const auto& label : labels) {
328 auto it = labels_to_targets_.find(label);
329 if (it != labels_to_targets_.end())
330 targets.insert(it->second);
331 }
332 return targets;
333 }
334
335 TargetSet Analyzer::Filter(const TargetSet& targets) {
336 TargetSet seen;
337 TargetSet filtered;
338 for (const auto* target : targets)
339 FilterTarget(target, &seen, &filtered);
340 return filtered;
341 }
342
343 void Analyzer::FilterTarget(const Target* target,
344 TargetSet* seen,
345 TargetSet* filtered) {
346 if (seen->find(target) == seen->end()) {
347 seen->insert(target);
348 if (target->output_type() != Target::GROUP) {
349 filtered->insert(target);
350 } else {
351 for (const auto& pair : target->GetDeps(Target::DEPS_ALL))
352 FilterTarget(pair.ptr, seen, filtered);
353 }
354 }
355 }
356
357 bool Analyzer::TargetRefersToFile(const Target* target,
358 const SourceFile* file) {
359 for (const auto& cur_file : target->sources()) {
360 if (cur_file == *file)
361 return true;
362 }
363 for (const auto& cur_file : target->public_headers()) {
364 if (cur_file == *file)
365 return true;
366 }
367 for (const auto& cur_file : target->inputs()) {
368 if (cur_file == *file)
369 return true;
370 }
371 for (const auto& cur_file : target->data()) {
372 if (cur_file == file->value())
373 return true;
374 if (cur_file.back() == '/' &&
375 base::StartsWith(file->value(), cur_file, base::CompareCase::SENSITIVE))
376 return true;
377 }
378
379 if (target->action_values().script().value() == file->value())
380 return true;
381
382 std::vector<SourceFile> outputs;
383 target->action_values().GetOutputsAsSourceFiles(target, &outputs);
384 for (const auto& cur_file : outputs) {
385 if (cur_file == *file)
386 return true;
387 }
388 return false;
389 }
390
391 void Analyzer::AddTargetsDirectlyReferringToFileTo(const SourceFile* file,
392 TargetSet* matches) {
393 for (const auto& target : all_targets_) {
394 // Only handles targets in the default toolchain.
395 if ((target->label().GetToolchainLabel() == default_toolchain_) &&
396 TargetRefersToFile(target, file))
397 matches->insert(target);
398 }
399 }
400
401 void Analyzer::AddAllRefsTo(const Target* target, TargetSet* results) {
402 if (results->find(target) != results->end())
403 return; // Already found this target.
404 results->insert(target);
405
406 DepMap::const_iterator dep_begin = dep_map_.lower_bound(target);
407 DepMap::const_iterator dep_end = dep_map_.upper_bound(target);
brettw 2016/08/25 21:22:29 I would have used "auto dep_begin" and "auto dep_e
Dirk Pranke 2016/08/26 00:34:13 Done.
408 for (auto cur_dep = dep_begin; cur_dep != dep_end; cur_dep++)
409 AddAllRefsTo(cur_dep->second, results);
410 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698