| // Copyright 2016 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "tools/gn/analyzer.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <iterator> | 
 | #include <memory> | 
 | #include <set> | 
 | #include <vector> | 
 |  | 
 | #include "base/json/json_reader.h" | 
 | #include "base/json/json_writer.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/values.h" | 
 | #include "tools/gn/builder.h" | 
 | #include "tools/gn/config.h" | 
 | #include "tools/gn/config_values_extractors.h" | 
 | #include "tools/gn/deps_iterator.h" | 
 | #include "tools/gn/err.h" | 
 | #include "tools/gn/filesystem_utils.h" | 
 | #include "tools/gn/loader.h" | 
 | #include "tools/gn/location.h" | 
 | #include "tools/gn/pool.h" | 
 | #include "tools/gn/source_file.h" | 
 | #include "tools/gn/target.h" | 
 |  | 
 | namespace { | 
 |  | 
 | struct Inputs { | 
 |   std::vector<SourceFile> source_vec; | 
 |   std::vector<Label> compile_vec; | 
 |   std::vector<Label> test_vec; | 
 |   bool compile_included_all = false; | 
 |   std::set<const SourceFile*> source_files; | 
 |   std::set<Label> compile_labels; | 
 |   std::set<Label> test_labels; | 
 | }; | 
 |  | 
 | struct Outputs { | 
 |   std::string status; | 
 |   std::string error; | 
 |   bool compile_includes_all = false; | 
 |   std::set<Label> compile_labels; | 
 |   std::set<Label> test_labels; | 
 |   std::set<Label> invalid_labels; | 
 | }; | 
 |  | 
 | std::set<Label> LabelsFor(const std::set<const Target*>& targets) { | 
 |   std::set<Label> labels; | 
 |   for (auto* target : targets) | 
 |     labels.insert(target->label()); | 
 |   return labels; | 
 | } | 
 |  | 
 | std::set<const Target*> Intersect(const std::set<const Target*>& l, | 
 |                                   const std::set<const Target*>& r) { | 
 |   std::set<const Target*> result; | 
 |   std::set_intersection(l.begin(), l.end(), r.begin(), r.end(), | 
 |                         std::inserter(result, result.begin())); | 
 |   return result; | 
 | } | 
 |  | 
 | std::vector<std::string> GetStringVector(const base::DictionaryValue& dict, | 
 |                                          const std::string& key, | 
 |                                          Err* err) { | 
 |   std::vector<std::string> strings; | 
 |   const base::ListValue* lst; | 
 |   bool ret = dict.GetList(key, &lst); | 
 |   if (!ret) { | 
 |     *err = Err(Location(), "Input does not have a key named \"" + key + | 
 |                                "\" with a list value."); | 
 |     return strings; | 
 |   } | 
 |  | 
 |   for (size_t i = 0; i < lst->GetSize(); i++) { | 
 |     std::string s; | 
 |     ret = lst->GetString(i, &s); | 
 |     if (!ret) { | 
 |       *err = Err(Location(), "Item " + std::to_string(i) + " of \"" + key + | 
 |                                  "\" is not a string."); | 
 |       strings.clear(); | 
 |       return strings; | 
 |     } | 
 |     strings.push_back(std::move(s)); | 
 |   } | 
 |   *err = Err(); | 
 |   return strings; | 
 | } | 
 |  | 
 | void WriteString(base::DictionaryValue& dict, | 
 |                  const std::string& key, | 
 |                  const std::string& value) { | 
 |   dict.SetKey(key, base::Value(value)); | 
 | }; | 
 |  | 
 | void WriteLabels(const Label& default_toolchain, | 
 |                  base::DictionaryValue& dict, | 
 |                  const std::string& key, | 
 |                  const std::set<Label>& labels) { | 
 |   std::vector<std::string> strings; | 
 |   auto value = std::make_unique<base::ListValue>(); | 
 |   for (const auto l : labels) | 
 |     strings.push_back(l.GetUserVisibleName(default_toolchain)); | 
 |   std::sort(strings.begin(), strings.end()); | 
 |   value->AppendStrings(strings); | 
 |   dict.SetWithoutPathExpansion(key, std::move(value)); | 
 | } | 
 |  | 
 | Label AbsoluteOrSourceAbsoluteStringToLabel(const Label& default_toolchain, | 
 |                                             const std::string& s, | 
 |                                             Err* err) { | 
 |   if (!IsPathSourceAbsolute(s) && !IsPathAbsolute(s)) { | 
 |     *err = Err(Location(), | 
 |                "\"" + s + "\" is not a source-absolute or absolute path."); | 
 |     return Label(); | 
 |   } | 
 |   return Label::Resolve(SourceDir("//"), default_toolchain, Value(nullptr, s), | 
 |                         err); | 
 | } | 
 |  | 
 | Err JSONToInputs(const Label& default_toolchain, | 
 |                  const std::string input, | 
 |                  Inputs* inputs) { | 
 |   int error_code_out; | 
 |   std::string error_msg_out; | 
 |   int error_line_out; | 
 |   int error_column_out; | 
 |   std::unique_ptr<base::Value> value = base::JSONReader().ReadAndReturnError( | 
 |       input, base::JSONParserOptions::JSON_PARSE_RFC, &error_code_out, | 
 |       &error_msg_out, &error_line_out, &error_column_out); | 
 |   if (!value) | 
 |     return Err(Location(), "Input is not valid JSON:" + error_msg_out); | 
 |  | 
 |   const base::DictionaryValue* dict; | 
 |   if (!value->GetAsDictionary(&dict)) | 
 |     return Err(Location(), "Input is not a dictionary."); | 
 |  | 
 |   Err err; | 
 |   std::vector<std::string> strings; | 
 |   strings = GetStringVector(*dict, "files", &err); | 
 |   if (err.has_error()) | 
 |     return err; | 
 |   for (auto s : strings) { | 
 |     if (!IsPathSourceAbsolute(s) && !IsPathAbsolute(s)) | 
 |       return Err(Location(), | 
 |                  "\"" + s + "\" is not a source-absolute or absolute path."); | 
 |     inputs->source_vec.push_back(SourceFile(s)); | 
 |   } | 
 |  | 
 |   strings = GetStringVector(*dict, "additional_compile_targets", &err); | 
 |   if (err.has_error()) | 
 |     return err; | 
 |  | 
 |   inputs->compile_included_all = false; | 
 |   for (auto& s : strings) { | 
 |     if (s == "all") { | 
 |       inputs->compile_included_all = true; | 
 |     } else { | 
 |       inputs->compile_vec.push_back( | 
 |           AbsoluteOrSourceAbsoluteStringToLabel(default_toolchain, s, &err)); | 
 |       if (err.has_error()) | 
 |         return err; | 
 |     } | 
 |   } | 
 |  | 
 |   strings = GetStringVector(*dict, "test_targets", &err); | 
 |   if (err.has_error()) | 
 |     return err; | 
 |   for (auto& s : strings) { | 
 |     inputs->test_vec.push_back( | 
 |         AbsoluteOrSourceAbsoluteStringToLabel(default_toolchain, s, &err)); | 
 |     if (err.has_error()) | 
 |       return err; | 
 |   } | 
 |  | 
 |   for (auto& s : inputs->source_vec) | 
 |     inputs->source_files.insert(&s); | 
 |   for (auto& l : inputs->compile_vec) | 
 |     inputs->compile_labels.insert(l); | 
 |   for (auto& l : inputs->test_vec) | 
 |     inputs->test_labels.insert(l); | 
 |   return Err(); | 
 | } | 
 |  | 
 | std::string OutputsToJSON(const Outputs& outputs, | 
 |                           const Label& default_toolchain, | 
 |                           Err* err) { | 
 |   std::string output; | 
 |   auto value = std::make_unique<base::DictionaryValue>(); | 
 |  | 
 |   if (outputs.error.size()) { | 
 |     WriteString(*value, "error", outputs.error); | 
 |     WriteLabels(default_toolchain, *value, "invalid_targets", | 
 |                 outputs.invalid_labels); | 
 |   } else { | 
 |     WriteString(*value, "status", outputs.status); | 
 |     if (outputs.compile_includes_all) { | 
 |       auto compile_targets = std::make_unique<base::ListValue>(); | 
 |       compile_targets->AppendString("all"); | 
 |       value->SetWithoutPathExpansion("compile_targets", | 
 |                                      std::move(compile_targets)); | 
 |     } else { | 
 |       WriteLabels(default_toolchain, *value, "compile_targets", | 
 |                   outputs.compile_labels); | 
 |     } | 
 |     WriteLabels(default_toolchain, *value, "test_targets", outputs.test_labels); | 
 |   } | 
 |  | 
 |   if (!base::JSONWriter::Write(*value.get(), &output)) | 
 |     *err = Err(Location(), "Failed to marshal JSON value for output"); | 
 |   return output; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | Analyzer::Analyzer(const Builder& builder, | 
 |                    const SourceFile& build_config_file, | 
 |                    const SourceFile& dot_file, | 
 |                    const std::set<SourceFile>& build_args_dependency_files) | 
 |     : all_items_(builder.GetAllResolvedItems()), | 
 |       default_toolchain_(builder.loader()->GetDefaultToolchain()), | 
 |       build_config_file_(build_config_file), | 
 |       dot_file_(dot_file), | 
 |       build_args_dependency_files_(build_args_dependency_files) { | 
 |   for (const auto* item : all_items_) { | 
 |     labels_to_items_[item->label()] = item; | 
 |  | 
 |     // Fill dep_map_. | 
 |     if (item->AsTarget()) { | 
 |       for (const auto& dep_target_pair : | 
 |            item->AsTarget()->GetDeps(Target::DEPS_ALL)) | 
 |         dep_map_.insert(std::make_pair(dep_target_pair.ptr, item)); | 
 |  | 
 |       for (const auto& dep_config_pair : item->AsTarget()->configs()) | 
 |         dep_map_.insert(std::make_pair(dep_config_pair.ptr, item)); | 
 |  | 
 |       dep_map_.insert(std::make_pair(item->AsTarget()->toolchain(), item)); | 
 |  | 
 |       if (item->AsTarget()->output_type() == Target::ACTION || | 
 |           item->AsTarget()->output_type() == Target::ACTION_FOREACH) { | 
 |         const LabelPtrPair<Pool>& pool = | 
 |             item->AsTarget()->action_values().pool(); | 
 |         if (pool.ptr) | 
 |           dep_map_.insert(std::make_pair(pool.ptr, item)); | 
 |       } | 
 |     } else if (item->AsConfig()) { | 
 |       for (const auto& dep_config_pair : item->AsConfig()->configs()) | 
 |         dep_map_.insert(std::make_pair(dep_config_pair.ptr, item)); | 
 |     } else if (item->AsToolchain()) { | 
 |       for (const auto& dep_pair : item->AsToolchain()->deps()) | 
 |         dep_map_.insert(std::make_pair(dep_pair.ptr, item)); | 
 |     } else { | 
 |       DCHECK(item->AsPool()); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | Analyzer::~Analyzer() = default; | 
 |  | 
 | std::string Analyzer::Analyze(const std::string& input, Err* err) const { | 
 |   Inputs inputs; | 
 |   Outputs outputs; | 
 |  | 
 |   Err local_err = JSONToInputs(default_toolchain_, input, &inputs); | 
 |   if (local_err.has_error()) { | 
 |     outputs.error = local_err.message(); | 
 |     return OutputsToJSON(outputs, default_toolchain_, err); | 
 |   } | 
 |  | 
 |   std::set<Label> invalid_labels; | 
 |   for (const auto& label : InvalidLabels(inputs.compile_labels)) | 
 |     invalid_labels.insert(label); | 
 |   for (const auto& label : InvalidLabels(inputs.test_labels)) | 
 |     invalid_labels.insert(label); | 
 |   if (!invalid_labels.empty()) { | 
 |     outputs.error = "Invalid targets"; | 
 |     outputs.invalid_labels = invalid_labels; | 
 |     return OutputsToJSON(outputs, default_toolchain_, err); | 
 |   } | 
 |  | 
 |   if (WereMainGNFilesModified(inputs.source_files)) { | 
 |     outputs.status = "Found dependency (all)"; | 
 |     if (inputs.compile_included_all) { | 
 |       outputs.compile_includes_all = true; | 
 |     } else { | 
 |       outputs.compile_labels.insert(inputs.compile_labels.begin(), | 
 |                                     inputs.compile_labels.end()); | 
 |       outputs.compile_labels.insert(inputs.test_labels.begin(), | 
 |                                     inputs.test_labels.end()); | 
 |     } | 
 |     outputs.test_labels = inputs.test_labels; | 
 |     return OutputsToJSON(outputs, default_toolchain_, err); | 
 |   } | 
 |  | 
 |   std::set<const Item*> affected_items = | 
 |       GetAllAffectedItems(inputs.source_files); | 
 |   std::set<const Target*> affected_targets; | 
 |   for (const Item* affected_item : affected_items) { | 
 |     // Only handles targets in the default toolchain. | 
 |     // TODO(crbug.com/667989): Expand analyzer to non-default toolchains when | 
 |     // the bug is fixed. | 
 |     if (affected_item->AsTarget() && | 
 |         affected_item->label().GetToolchainLabel() == default_toolchain_) | 
 |       affected_targets.insert(affected_item->AsTarget()); | 
 |   } | 
 |  | 
 |   if (affected_targets.empty()) { | 
 |     outputs.status = "No dependency"; | 
 |     return OutputsToJSON(outputs, default_toolchain_, err); | 
 |   } | 
 |  | 
 |   std::set<const Target*> root_targets; | 
 |   for (const auto* item : all_items_) { | 
 |     if (item->AsTarget() && dep_map_.find(item) == dep_map_.end()) | 
 |       root_targets.insert(item->AsTarget()); | 
 |   } | 
 |  | 
 |   std::set<const Target*> compile_targets = TargetsFor(inputs.compile_labels); | 
 |   if (inputs.compile_included_all) { | 
 |     for (auto* root_target : root_targets) | 
 |       compile_targets.insert(root_target); | 
 |   } | 
 |   std::set<const Target*> filtered_targets = Filter(compile_targets); | 
 |   outputs.compile_labels = | 
 |       LabelsFor(Intersect(filtered_targets, affected_targets)); | 
 |  | 
 |   // If every target is affected, simply compile All instead of listing all | 
 |   // the targets to make the output easier to read. | 
 |   if (inputs.compile_included_all && | 
 |       outputs.compile_labels.size() == filtered_targets.size()) | 
 |     outputs.compile_includes_all = true; | 
 |  | 
 |   std::set<const Target*> test_targets = TargetsFor(inputs.test_labels); | 
 |   outputs.test_labels = LabelsFor(Intersect(test_targets, affected_targets)); | 
 |  | 
 |   if (outputs.compile_labels.empty() && outputs.test_labels.empty()) | 
 |     outputs.status = "No dependency"; | 
 |   else | 
 |     outputs.status = "Found dependency"; | 
 |   return OutputsToJSON(outputs, default_toolchain_, err); | 
 | } | 
 |  | 
 | std::set<const Item*> Analyzer::GetAllAffectedItems( | 
 |     const std::set<const SourceFile*>& source_files) const { | 
 |   std::set<const Item*> directly_affected_items; | 
 |   for (auto* source_file : source_files) | 
 |     AddItemsDirectlyReferringToFile(source_file, &directly_affected_items); | 
 |  | 
 |   std::set<const Item*> all_affected_items; | 
 |   for (auto* affected_item : directly_affected_items) | 
 |     AddAllItemsReferringToItem(affected_item, &all_affected_items); | 
 |  | 
 |   return all_affected_items; | 
 | } | 
 |  | 
 | std::set<Label> Analyzer::InvalidLabels(const std::set<Label>& labels) const { | 
 |   std::set<Label> invalid_labels; | 
 |   for (const Label& label : labels) { | 
 |     if (labels_to_items_.find(label) == labels_to_items_.end()) | 
 |       invalid_labels.insert(label); | 
 |   } | 
 |   return invalid_labels; | 
 | } | 
 |  | 
 | std::set<const Target*> Analyzer::TargetsFor( | 
 |     const std::set<Label>& labels) const { | 
 |   std::set<const Target*> targets; | 
 |   for (const auto& label : labels) { | 
 |     auto it = labels_to_items_.find(label); | 
 |     if (it != labels_to_items_.end()) { | 
 |       DCHECK(it->second->AsTarget()); | 
 |       targets.insert(it->second->AsTarget()); | 
 |     } | 
 |   } | 
 |   return targets; | 
 | } | 
 |  | 
 | std::set<const Target*> Analyzer::Filter( | 
 |     const std::set<const Target*>& targets) const { | 
 |   std::set<const Target*> seen; | 
 |   std::set<const Target*> filtered; | 
 |   for (const auto* target : targets) | 
 |     FilterTarget(target, &seen, &filtered); | 
 |   return filtered; | 
 | } | 
 |  | 
 | void Analyzer::FilterTarget(const Target* target, | 
 |                             std::set<const Target*>* seen, | 
 |                             std::set<const Target*>* filtered) const { | 
 |   if (seen->find(target) == seen->end()) { | 
 |     seen->insert(target); | 
 |     if (target->output_type() != Target::GROUP) { | 
 |       filtered->insert(target); | 
 |     } else { | 
 |       for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) | 
 |         FilterTarget(pair.ptr, seen, filtered); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | bool Analyzer::ItemRefersToFile(const Item* item, | 
 |                                 const SourceFile* file) const { | 
 |   for (const auto& cur_file : item->build_dependency_files()) { | 
 |     if (cur_file == *file) | 
 |       return true; | 
 |   } | 
 |  | 
 |   if (!item->AsTarget()) | 
 |     return false; | 
 |  | 
 |   const Target* target = item->AsTarget(); | 
 |   for (const auto& cur_file : target->sources()) { | 
 |     if (cur_file == *file) | 
 |       return true; | 
 |   } | 
 |   for (const auto& cur_file : target->public_headers()) { | 
 |     if (cur_file == *file) | 
 |       return true; | 
 |   } | 
 |   for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) { | 
 |     for (const auto& cur_file : iter.cur().inputs()) { | 
 |       if (cur_file == *file) | 
 |         return true; | 
 |     } | 
 |   } | 
 |   for (const auto& cur_file : target->data()) { | 
 |     if (cur_file == file->value()) | 
 |       return true; | 
 |     if (cur_file.back() == '/' && | 
 |         base::StartsWith(file->value(), cur_file, base::CompareCase::SENSITIVE)) | 
 |       return true; | 
 |   } | 
 |  | 
 |   if (target->action_values().script().value() == file->value()) | 
 |     return true; | 
 |  | 
 |   std::vector<SourceFile> outputs; | 
 |   target->action_values().GetOutputsAsSourceFiles(target, &outputs); | 
 |   for (const auto& cur_file : outputs) { | 
 |     if (cur_file == *file) | 
 |       return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | void Analyzer::AddItemsDirectlyReferringToFile( | 
 |     const SourceFile* file, | 
 |     std::set<const Item*>* directly_affected_items) const { | 
 |   for (const auto* item : all_items_) { | 
 |     if (ItemRefersToFile(item, file)) | 
 |       directly_affected_items->insert(item); | 
 |   } | 
 | } | 
 |  | 
 | void Analyzer::AddAllItemsReferringToItem( | 
 |     const Item* item, | 
 |     std::set<const Item*>* all_affected_items) const { | 
 |   if (all_affected_items->find(item) != all_affected_items->end()) | 
 |     return;  // Already found this item. | 
 |  | 
 |   all_affected_items->insert(item); | 
 |  | 
 |   auto dep_begin = dep_map_.lower_bound(item); | 
 |   auto dep_end = dep_map_.upper_bound(item); | 
 |   for (auto cur_dep = dep_begin; cur_dep != dep_end; ++cur_dep) | 
 |     AddAllItemsReferringToItem(cur_dep->second, all_affected_items); | 
 | } | 
 |  | 
 | bool Analyzer::WereMainGNFilesModified( | 
 |     const std::set<const SourceFile*>& modified_files) const { | 
 |   for (const auto* file : modified_files) { | 
 |     if (*file == dot_file_) | 
 |       return true; | 
 |  | 
 |     if (*file == build_config_file_) | 
 |       return true; | 
 |  | 
 |     for (const auto& build_args_dependency_file : | 
 |          build_args_dependency_files_) { | 
 |       if (*file == build_args_dependency_file) | 
 |         return true; | 
 |     } | 
 |   } | 
 |  | 
 |   return false; | 
 | } |