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