1 // Copyright 2016 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/analyzer.h"
6
7 #include <algorithm>
8 #include <iterator>
9 #include <memory>
10 #include <set>
11 #include <vector>
12
13 #include "base/json/json_reader.h"
14 #include "base/json/json_writer.h"
15 #include "base/strings/string_util.h"
16 #include "base/values.h"
17 #include "gn/builder.h"
18 #include "gn/config.h"
19 #include "gn/config_values_extractors.h"
20 #include "gn/deps_iterator.h"
21 #include "gn/err.h"
22 #include "gn/filesystem_utils.h"
23 #include "gn/loader.h"
24 #include "gn/location.h"
25 #include "gn/pool.h"
26 #include "gn/source_file.h"
27 #include "gn/target.h"
28
29 namespace {
30
31 struct Inputs {
32 std::vector<SourceFile> source_vec;
33 std::vector<Label> compile_vec;
34 std::vector<Label> test_vec;
35 bool compile_included_all = false;
36 std::set<const SourceFile*> source_files;
37 std::set<Label> compile_labels;
38 std::set<Label> test_labels;
39 };
40
41 struct Outputs {
42 std::string status;
43 std::string error;
44 bool compile_includes_all = false;
45 std::set<Label> compile_labels;
46 std::set<Label> test_labels;
47 std::set<Label> invalid_labels;
48 };
49
LabelsFor(const std::set<const Target * > & targets)50 std::set<Label> LabelsFor(const std::set<const Target*>& targets) {
51 std::set<Label> labels;
52 for (auto* target : targets)
53 labels.insert(target->label());
54 return labels;
55 }
56
Intersect(const std::set<const Target * > & l,const std::set<const Target * > & r)57 std::set<const Target*> Intersect(const std::set<const Target*>& l,
58 const std::set<const Target*>& r) {
59 std::set<const Target*> result;
60 std::set_intersection(l.begin(), l.end(), r.begin(), r.end(),
61 std::inserter(result, result.begin()));
62 return result;
63 }
64
GetStringVector(const base::DictionaryValue & dict,const std::string & key,Err * err)65 std::vector<std::string> GetStringVector(const base::DictionaryValue& dict,
66 const std::string& key,
67 Err* err) {
68 std::vector<std::string> strings;
69 const base::ListValue* lst;
70 bool ret = dict.GetList(key, &lst);
71 if (!ret) {
72 *err = Err(Location(), "Input does not have a key named \"" + key +
73 "\" with a list value.");
74 return strings;
75 }
76
77 for (size_t i = 0; i < lst->GetSize(); i++) {
78 std::string s;
79 ret = lst->GetString(i, &s);
80 if (!ret) {
81 *err = Err(Location(), "Item " + std::to_string(i) + " of \"" + key +
82 "\" is not a string.");
83 strings.clear();
84 return strings;
85 }
86 strings.push_back(std::move(s));
87 }
88 *err = Err();
89 return strings;
90 }
91
WriteString(base::DictionaryValue & dict,const std::string & key,const std::string & value)92 void WriteString(base::DictionaryValue& dict,
93 const std::string& key,
94 const std::string& value) {
95 dict.SetKey(key, base::Value(value));
96 };
97
WriteLabels(const Label & default_toolchain,base::DictionaryValue & dict,const std::string & key,const std::set<Label> & labels)98 void WriteLabels(const Label& default_toolchain,
99 base::DictionaryValue& dict,
100 const std::string& key,
101 const std::set<Label>& labels) {
102 std::vector<std::string> strings;
103 auto value = std::make_unique<base::ListValue>();
104 for (const auto l : labels)
105 strings.push_back(l.GetUserVisibleName(default_toolchain));
106 std::sort(strings.begin(), strings.end());
107 value->AppendStrings(strings);
108 dict.SetWithoutPathExpansion(key, std::move(value));
109 }
110
AbsoluteOrSourceAbsoluteStringToLabel(const Label & default_toolchain,const std::string & s,Err * err)111 Label AbsoluteOrSourceAbsoluteStringToLabel(const Label& default_toolchain,
112 const std::string& s,
113 Err* err) {
114 if (!IsPathSourceAbsolute(s) && !IsPathAbsolute(s)) {
115 *err = Err(Location(),
116 "\"" + s + "\" is not a source-absolute or absolute path.");
117 return Label();
118 }
119 return Label::Resolve(SourceDir("//"), std::string_view(), default_toolchain,
120 Value(nullptr, s), err);
121 }
122
JSONToInputs(const Label & default_toolchain,const std::string input,Inputs * inputs)123 Err JSONToInputs(const Label& default_toolchain,
124 const std::string input,
125 Inputs* inputs) {
126 int error_code_out;
127 std::string error_msg_out;
128 int error_line_out;
129 int error_column_out;
130 std::unique_ptr<base::Value> value = base::JSONReader().ReadAndReturnError(
131 input, base::JSONParserOptions::JSON_PARSE_RFC, &error_code_out,
132 &error_msg_out, &error_line_out, &error_column_out);
133 if (!value)
134 return Err(Location(), "Input is not valid JSON:" + error_msg_out);
135
136 const base::DictionaryValue* dict;
137 if (!value->GetAsDictionary(&dict))
138 return Err(Location(), "Input is not a dictionary.");
139
140 Err err;
141 std::vector<std::string> strings;
142 strings = GetStringVector(*dict, "files", &err);
143 if (err.has_error())
144 return err;
145 for (auto& s : strings) {
146 if (!IsPathSourceAbsolute(s) && !IsPathAbsolute(s)) {
147 return Err(Location(),
148 "\"" + s + "\" is not a source-absolute or absolute path.");
149 }
150 inputs->source_vec.emplace_back(std::move(s));
151 }
152
153 strings = GetStringVector(*dict, "additional_compile_targets", &err);
154 if (err.has_error())
155 return err;
156
157 inputs->compile_included_all = false;
158 for (auto& s : strings) {
159 if (s == "all") {
160 inputs->compile_included_all = true;
161 } else {
162 inputs->compile_vec.push_back(
163 AbsoluteOrSourceAbsoluteStringToLabel(default_toolchain, s, &err));
164 if (err.has_error())
165 return err;
166 }
167 }
168
169 strings = GetStringVector(*dict, "test_targets", &err);
170 if (err.has_error())
171 return err;
172 for (auto& s : strings) {
173 inputs->test_vec.push_back(
174 AbsoluteOrSourceAbsoluteStringToLabel(default_toolchain, s, &err));
175 if (err.has_error())
176 return err;
177 }
178
179 for (auto& s : inputs->source_vec)
180 inputs->source_files.insert(&s);
181 for (auto& l : inputs->compile_vec)
182 inputs->compile_labels.insert(l);
183 for (auto& l : inputs->test_vec)
184 inputs->test_labels.insert(l);
185 return Err();
186 }
187
OutputsToJSON(const Outputs & outputs,const Label & default_toolchain,Err * err)188 std::string OutputsToJSON(const Outputs& outputs,
189 const Label& default_toolchain,
190 Err* err) {
191 std::string output;
192 auto value = std::make_unique<base::DictionaryValue>();
193
194 if (outputs.error.size()) {
195 WriteString(*value, "error", outputs.error);
196 WriteLabels(default_toolchain, *value, "invalid_targets",
197 outputs.invalid_labels);
198 } else {
199 WriteString(*value, "status", outputs.status);
200 if (outputs.compile_includes_all) {
201 auto compile_targets = std::make_unique<base::ListValue>();
202 compile_targets->AppendString("all");
203 value->SetWithoutPathExpansion("compile_targets",
204 std::move(compile_targets));
205 } else {
206 WriteLabels(default_toolchain, *value, "compile_targets",
207 outputs.compile_labels);
208 }
209 WriteLabels(default_toolchain, *value, "test_targets", outputs.test_labels);
210 }
211
212 if (!base::JSONWriter::Write(*value.get(), &output))
213 *err = Err(Location(), "Failed to marshal JSON value for output");
214 return output;
215 }
216
217 } // namespace
218
Analyzer(const Builder & builder,const SourceFile & build_config_file,const SourceFile & dot_file,const SourceFileSet & build_args_dependency_files)219 Analyzer::Analyzer(const Builder& builder,
220 const SourceFile& build_config_file,
221 const SourceFile& dot_file,
222 const SourceFileSet& build_args_dependency_files)
223 : all_items_(builder.GetAllResolvedItems()),
224 default_toolchain_(builder.loader()->GetDefaultToolchain()),
225 build_config_file_(build_config_file),
226 dot_file_(dot_file),
227 build_args_dependency_files_(build_args_dependency_files) {
228 for (const auto* item : all_items_) {
229 labels_to_items_[item->label()] = item;
230
231 // Fill dep_map_.
232 if (item->AsTarget()) {
233 for (const auto& dep_target_pair :
234 item->AsTarget()->GetDeps(Target::DEPS_ALL))
235 dep_map_.insert(std::make_pair(dep_target_pair.ptr, item));
236
237 for (const auto& dep_config_pair : item->AsTarget()->configs())
238 dep_map_.insert(std::make_pair(dep_config_pair.ptr, item));
239
240 dep_map_.insert(std::make_pair(item->AsTarget()->toolchain(), item));
241
242 if (item->AsTarget()->output_type() == Target::ACTION ||
243 item->AsTarget()->output_type() == Target::ACTION_FOREACH) {
244 const LabelPtrPair<Pool>& pool =
245 item->AsTarget()->action_values().pool();
246 if (pool.ptr)
247 dep_map_.insert(std::make_pair(pool.ptr, item));
248 }
249 } else if (item->AsConfig()) {
250 for (const auto& dep_config_pair : item->AsConfig()->configs())
251 dep_map_.insert(std::make_pair(dep_config_pair.ptr, item));
252 } else if (item->AsToolchain()) {
253 for (const auto& dep_pair : item->AsToolchain()->deps())
254 dep_map_.insert(std::make_pair(dep_pair.ptr, item));
255 } else {
256 DCHECK(item->AsPool());
257 }
258 }
259 }
260
261 Analyzer::~Analyzer() = default;
262
Analyze(const std::string & input,Err * err) const263 std::string Analyzer::Analyze(const std::string& input, Err* err) const {
264 Inputs inputs;
265 Outputs outputs;
266
267 Err local_err = JSONToInputs(default_toolchain_, input, &inputs);
268 if (local_err.has_error()) {
269 outputs.error = local_err.message();
270 return OutputsToJSON(outputs, default_toolchain_, err);
271 }
272
273 std::set<Label> invalid_labels;
274 for (const auto& label : InvalidLabels(inputs.compile_labels))
275 invalid_labels.insert(label);
276 for (const auto& label : InvalidLabels(inputs.test_labels))
277 invalid_labels.insert(label);
278 if (!invalid_labels.empty()) {
279 outputs.error = "Invalid targets";
280 outputs.invalid_labels = invalid_labels;
281 return OutputsToJSON(outputs, default_toolchain_, err);
282 }
283
284 if (WereMainGNFilesModified(inputs.source_files)) {
285 outputs.status = "Found dependency (all)";
286 if (inputs.compile_included_all) {
287 outputs.compile_includes_all = true;
288 } else {
289 outputs.compile_labels.insert(inputs.compile_labels.begin(),
290 inputs.compile_labels.end());
291 outputs.compile_labels.insert(inputs.test_labels.begin(),
292 inputs.test_labels.end());
293 }
294 outputs.test_labels = inputs.test_labels;
295 return OutputsToJSON(outputs, default_toolchain_, err);
296 }
297
298 std::set<const Item*> affected_items =
299 GetAllAffectedItems(inputs.source_files);
300 std::set<const Target*> affected_targets;
301 for (const Item* affected_item : affected_items) {
302 // Only handles targets in the default toolchain.
303 // TODO(crbug.com/667989): Expand analyzer to non-default toolchains when
304 // the bug is fixed.
305 if (affected_item->AsTarget() &&
306 affected_item->label().GetToolchainLabel() == default_toolchain_)
307 affected_targets.insert(affected_item->AsTarget());
308 }
309
310 if (affected_targets.empty()) {
311 outputs.status = "No dependency";
312 return OutputsToJSON(outputs, default_toolchain_, err);
313 }
314
315 std::set<const Target*> root_targets;
316 for (const auto* item : all_items_) {
317 if (item->AsTarget() && dep_map_.find(item) == dep_map_.end())
318 root_targets.insert(item->AsTarget());
319 }
320
321 std::set<const Target*> compile_targets = TargetsFor(inputs.compile_labels);
322 if (inputs.compile_included_all) {
323 for (auto* root_target : root_targets)
324 compile_targets.insert(root_target);
325 }
326 std::set<const Target*> filtered_targets = Filter(compile_targets);
327 outputs.compile_labels =
328 LabelsFor(Intersect(filtered_targets, affected_targets));
329
330 // If every target is affected, simply compile All instead of listing all
331 // the targets to make the output easier to read.
332 if (inputs.compile_included_all &&
333 outputs.compile_labels.size() == filtered_targets.size())
334 outputs.compile_includes_all = true;
335
336 std::set<const Target*> test_targets = TargetsFor(inputs.test_labels);
337 outputs.test_labels = LabelsFor(Intersect(test_targets, affected_targets));
338
339 if (outputs.compile_labels.empty() && outputs.test_labels.empty())
340 outputs.status = "No dependency";
341 else
342 outputs.status = "Found dependency";
343 return OutputsToJSON(outputs, default_toolchain_, err);
344 }
345
GetAllAffectedItems(const std::set<const SourceFile * > & source_files) const346 std::set<const Item*> Analyzer::GetAllAffectedItems(
347 const std::set<const SourceFile*>& source_files) const {
348 std::set<const Item*> directly_affected_items;
349 for (auto* source_file : source_files)
350 AddItemsDirectlyReferringToFile(source_file, &directly_affected_items);
351
352 std::set<const Item*> all_affected_items;
353 for (auto* affected_item : directly_affected_items)
354 AddAllItemsReferringToItem(affected_item, &all_affected_items);
355
356 return all_affected_items;
357 }
358
InvalidLabels(const std::set<Label> & labels) const359 std::set<Label> Analyzer::InvalidLabels(const std::set<Label>& labels) const {
360 std::set<Label> invalid_labels;
361 for (const Label& label : labels) {
362 if (labels_to_items_.find(label) == labels_to_items_.end())
363 invalid_labels.insert(label);
364 }
365 return invalid_labels;
366 }
367
TargetsFor(const std::set<Label> & labels) const368 std::set<const Target*> Analyzer::TargetsFor(
369 const std::set<Label>& labels) const {
370 std::set<const Target*> targets;
371 for (const auto& label : labels) {
372 auto it = labels_to_items_.find(label);
373 if (it != labels_to_items_.end()) {
374 DCHECK(it->second->AsTarget());
375 targets.insert(it->second->AsTarget());
376 }
377 }
378 return targets;
379 }
380
Filter(const std::set<const Target * > & targets) const381 std::set<const Target*> Analyzer::Filter(
382 const std::set<const Target*>& targets) const {
383 std::set<const Target*> seen;
384 std::set<const Target*> filtered;
385 for (const auto* target : targets)
386 FilterTarget(target, &seen, &filtered);
387 return filtered;
388 }
389
FilterTarget(const Target * target,std::set<const Target * > * seen,std::set<const Target * > * filtered) const390 void Analyzer::FilterTarget(const Target* target,
391 std::set<const Target*>* seen,
392 std::set<const Target*>* filtered) const {
393 if (seen->find(target) == seen->end()) {
394 seen->insert(target);
395 if (target->output_type() != Target::GROUP) {
396 filtered->insert(target);
397 } else {
398 for (const auto& pair : target->GetDeps(Target::DEPS_ALL))
399 FilterTarget(pair.ptr, seen, filtered);
400 }
401 }
402 }
403
ItemRefersToFile(const Item * item,const SourceFile * file) const404 bool Analyzer::ItemRefersToFile(const Item* item,
405 const SourceFile* file) const {
406 for (const auto& cur_file : item->build_dependency_files()) {
407 if (cur_file == *file)
408 return true;
409 }
410
411 if (!item->AsTarget())
412 return false;
413
414 const Target* target = item->AsTarget();
415 for (const auto& cur_file : target->sources()) {
416 if (cur_file == *file)
417 return true;
418 }
419 for (const auto& cur_file : target->public_headers()) {
420 if (cur_file == *file)
421 return true;
422 }
423 for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
424 for (const auto& cur_file : iter.cur().inputs()) {
425 if (cur_file == *file)
426 return true;
427 }
428 }
429 for (const auto& cur_file : target->data()) {
430 if (cur_file == file->value())
431 return true;
432 if (cur_file.back() == '/' &&
433 base::StartsWith(file->value(), cur_file, base::CompareCase::SENSITIVE))
434 return true;
435 }
436
437 if (target->action_values().script().value() == file->value())
438 return true;
439
440 std::vector<SourceFile> outputs;
441 target->action_values().GetOutputsAsSourceFiles(target, &outputs);
442 for (const auto& cur_file : outputs) {
443 if (cur_file == *file)
444 return true;
445 }
446 return false;
447 }
448
AddItemsDirectlyReferringToFile(const SourceFile * file,std::set<const Item * > * directly_affected_items) const449 void Analyzer::AddItemsDirectlyReferringToFile(
450 const SourceFile* file,
451 std::set<const Item*>* directly_affected_items) const {
452 for (const auto* item : all_items_) {
453 if (ItemRefersToFile(item, file))
454 directly_affected_items->insert(item);
455 }
456 }
457
AddAllItemsReferringToItem(const Item * item,std::set<const Item * > * all_affected_items) const458 void Analyzer::AddAllItemsReferringToItem(
459 const Item* item,
460 std::set<const Item*>* all_affected_items) const {
461 if (all_affected_items->find(item) != all_affected_items->end())
462 return; // Already found this item.
463
464 all_affected_items->insert(item);
465
466 auto dep_begin = dep_map_.lower_bound(item);
467 auto dep_end = dep_map_.upper_bound(item);
468 for (auto cur_dep = dep_begin; cur_dep != dep_end; ++cur_dep)
469 AddAllItemsReferringToItem(cur_dep->second, all_affected_items);
470 }
471
WereMainGNFilesModified(const std::set<const SourceFile * > & modified_files) const472 bool Analyzer::WereMainGNFilesModified(
473 const std::set<const SourceFile*>& modified_files) const {
474 for (const auto* file : modified_files) {
475 if (*file == dot_file_)
476 return true;
477
478 if (*file == build_config_file_)
479 return true;
480
481 for (const auto& build_args_dependency_file :
482 build_args_dependency_files_) {
483 if (*file == build_args_dependency_file)
484 return true;
485 }
486 }
487
488 return false;
489 }
490