Line data Source code
1 : /*! 2 : * \file esys/repo/manifest.cpp 3 : * \brief 4 : * 5 : * \cond 6 : * __legal_b__ 7 : * 8 : * Copyright (c) 2020-2021 Michel Gillet 9 : * Distributed under the MIT License. 10 : * (See accompanying file LICENSE.txt or 11 : * copy at https://opensource.org/licenses/MIT) 12 : * 13 : * __legal_e__ 14 : * \endcond 15 : * 16 : */ 17 : 18 : #include "esys/repo/esysrepo_prec.h" 19 : #include "esys/repo/manifest.h" 20 : #include "esys/repo/manifest/depends.h" 21 : #include "esys/repo/manifest/repository.h" 22 : 23 : #include <cassert> 24 : #include <functional> 25 : 26 : namespace esys::repo 27 : { 28 : 29 80 : Manifest::Manifest() = default; 30 : 31 105 : Manifest::~Manifest() = default; 32 : 33 78 : void Manifest::set_type(manifest::Type type) 34 : { 35 78 : m_type = type; 36 78 : } 37 : 38 5 : manifest::Type Manifest::get_type() const 39 : { 40 5 : return m_type; 41 : } 42 : 43 80 : void Manifest::set_kind(manifest::Kind kind) 44 : { 45 80 : m_kind = kind; 46 80 : } 47 : 48 40 : manifest::Kind Manifest::get_kind() const 49 : { 50 40 : return m_kind; 51 : } 52 : 53 30 : void Manifest::set_format(manifest::Format format) 54 : { 55 30 : m_format = format; 56 30 : } 57 : 58 12 : manifest::Format Manifest::get_format() const 59 : { 60 12 : return m_format; 61 : } 62 : 63 110 : int Manifest::add_location(std::shared_ptr<manifest::Location> location, bool is_default) 64 : { 65 110 : assert(location != nullptr); 66 110 : assert(!location->get_name().empty()); 67 : 68 110 : auto it = m_map_locations.find(location->get_name()); 69 110 : if (it != m_map_locations.end()) return -1; 70 : 71 110 : m_locations.push_back(location); 72 110 : m_map_locations[location->get_name()] = location; 73 116 : if (is_default) set_default_location(location); 74 : 75 : return 0; 76 : } 77 : 78 556 : std::vector<std::shared_ptr<manifest::Location>> &Manifest::get_locations() 79 : { 80 556 : return m_locations; 81 : } 82 : 83 8 : const std::vector<std::shared_ptr<manifest::Location>> &Manifest::get_locations() const 84 : { 85 8 : return m_locations; 86 : } 87 : 88 0 : std::map<std::string, std::shared_ptr<manifest::Location>> &Manifest::get_map_locations() 89 : { 90 0 : return m_map_locations; 91 : } 92 : 93 0 : const std::map<std::string, std::shared_ptr<manifest::Location>> &Manifest::get_map_locations() const 94 : { 95 0 : return m_map_locations; 96 : } 97 : 98 562 : std::shared_ptr<manifest::Location> Manifest::find_location(const std::string &name) 99 : { 100 562 : auto it = m_map_locations.find(name); 101 : 102 562 : if (it == m_map_locations.end()) return nullptr; 103 : 104 562 : return it->second; 105 : } 106 : 107 367 : std::shared_ptr<manifest::Repository> Manifest::find_repo_by_path(const std::string &path) 108 : { 109 367 : std::shared_ptr<manifest::Repository> repo; 110 : 111 476 : for (auto &location : get_locations()) 112 : { 113 950 : repo = location->find_repo_by_path(path); 114 475 : if (repo != nullptr) return repo; 115 : } 116 367 : return nullptr; 117 367 : } 118 : 119 0 : std::string Manifest::find_repo_path_by_url(const std::string &git_repo_name) 120 : { 121 0 : std::string path; 122 : 123 0 : for (auto &location : get_locations()) 124 : { 125 0 : path = location->find_repo_path_by_url(git_repo_name); 126 0 : if (!path.empty()) return path; 127 : } 128 0 : return ""; 129 0 : } 130 : 131 0 : std::shared_ptr<manifest::Repository> Manifest::find_repo_by_url(const std::string &url) 132 : { 133 0 : std::shared_ptr<manifest::Repository> repo; 134 : 135 0 : for (auto &location : get_locations()) 136 : { 137 0 : repo = location->find_repo_by_url(url); 138 0 : if (repo != nullptr) return repo; 139 : } 140 0 : return nullptr; 141 : 142 0 : } 143 : 144 1 : std::vector<std::shared_ptr<manifest::Repository>> Manifest::find_repos_by_name(const std::string &name) 145 : { 146 1 : std::vector<std::shared_ptr<manifest::Repository>> matches; 147 2 : for (auto &location : get_locations()) 148 : { 149 1 : if (location == nullptr) continue; 150 1 : auto repo = location->find_repo_by_name(name); 151 1 : if (repo != nullptr) matches.push_back(repo); 152 1 : } 153 1 : return matches; 154 0 : } 155 : 156 1 : std::shared_ptr<manifest::Repository> Manifest::find_repo_by_name(const std::string &name) 157 : { 158 1 : auto matches = find_repos_by_name(name); 159 1 : if (matches.size() != 1) return nullptr; 160 1 : return matches[0]; 161 1 : } 162 : 163 2 : std::vector<std::shared_ptr<manifest::Repository>> Manifest::find_repos_by_project(const std::string &project) 164 : { 165 2 : std::vector<std::shared_ptr<manifest::Repository>> matches; 166 2 : if (project.empty()) return matches; 167 : 168 4 : for (auto &location : get_locations()) 169 : { 170 2 : if (location == nullptr) continue; 171 6 : for (auto &repo : location->get_repos()) 172 : { 173 4 : if (repo == nullptr) continue; 174 4 : const std::string &name = repo->get_name(); 175 4 : if (name == project) 176 : { 177 0 : matches.push_back(repo); 178 0 : continue; 179 : } 180 4 : const std::string suffix = "/" + name; 181 8 : if (project.size() > suffix.size() && 182 4 : project.compare(project.size() - suffix.size(), suffix.size(), suffix) == 0) 183 2 : matches.push_back(repo); 184 4 : } 185 : } 186 : return matches; 187 0 : } 188 : 189 1 : std::shared_ptr<manifest::Repository> Manifest::find_repo_by_project(const std::string &project) 190 : { 191 1 : auto matches = find_repos_by_project(project); 192 1 : if (matches.size() != 1) return nullptr; 193 1 : return matches[0]; 194 1 : } 195 : 196 3 : void Manifest::set_default_location(std::shared_ptr<manifest::Location> default_location) 197 : { 198 3 : m_default_location = default_location; 199 3 : } 200 : 201 515 : std::shared_ptr<manifest::Location> Manifest::get_default_location() 202 : { 203 515 : if (m_default_location != nullptr) return m_default_location; 204 : 205 503 : if (m_default_location_str.empty()) return nullptr; 206 : 207 503 : return find_location(m_default_location_str); 208 : } 209 : 210 37 : void Manifest::set_default_location(const std::string &default_location_str) 211 : { 212 37 : m_default_location_str = default_location_str; 213 37 : } 214 : 215 0 : const std::string &Manifest::get_default_location_str() const 216 : { 217 0 : return m_default_location_str; 218 : } 219 : 220 77 : void Manifest::set_default_revision(const std::string &default_revision) 221 : { 222 77 : m_default_revision = default_revision; 223 77 : } 224 : 225 162 : const std::string &Manifest::get_default_revision() const 226 : { 227 162 : return m_default_revision; 228 : } 229 : 230 71 : void Manifest::set_default_job_count(int default_job_count) 231 : { 232 71 : m_default_job_count = default_job_count; 233 71 : } 234 : 235 22 : int Manifest::get_default_job_count() const 236 : { 237 22 : return m_default_job_count; 238 : } 239 : 240 4 : void Manifest::set_default_sync_s(std::optional<bool> sync_s) 241 : { 242 4 : m_default_sync_s = sync_s; 243 4 : } 244 : 245 169 : const std::optional<bool> &Manifest::get_default_sync_s() const 246 : { 247 169 : return m_default_sync_s; 248 : } 249 : 250 225 : std::string Manifest::get_repo_revision(std::shared_ptr<manifest::Repository> repo) 251 : { 252 225 : if (repo == nullptr) return get_default_revision(); 253 : 254 225 : const std::string pinned = get_pinned_revision(repo->get_path()); 255 225 : if (!pinned.empty()) return pinned; 256 : 257 221 : const std::string set_name = get_effective_revision_set_name(); 258 221 : if (!set_name.empty()) 259 : { 260 8 : auto revision_set = get_revision_sets().find_by_name(set_name); 261 8 : if (revision_set != nullptr) 262 : { 263 8 : const std::string path_rev = revision_set->get_repo_revision(repo->get_path()); 264 8 : if (!path_rev.empty()) return path_rev; 265 5 : if (!revision_set->get_default_revision().empty()) return revision_set->get_default_revision(); 266 8 : } 267 8 : } 268 : 269 213 : if (!repo->get_revision().empty()) return repo->get_revision(); 270 110 : return get_default_revision(); 271 450 : } 272 : 273 28 : void Manifest::set_revision_pins(std::shared_ptr<RevisionPins> pins) 274 : { 275 28 : m_revision_pins = pins; 276 28 : } 277 : 278 0 : std::shared_ptr<RevisionPins> Manifest::get_revision_pins() const 279 : { 280 0 : return m_revision_pins; 281 : } 282 : 283 0 : void Manifest::clear_revision_pins() 284 : { 285 0 : m_revision_pins.reset(); 286 0 : } 287 : 288 69 : bool Manifest::has_revision_pin(const std::string &path) const 289 : { 290 69 : return !get_pinned_revision(path).empty(); 291 : } 292 : 293 294 : std::string Manifest::get_pinned_revision(const std::string &path) const 294 : { 295 294 : if (m_revision_pins == nullptr) return ""; 296 209 : return m_revision_pins->get_rev_for_path(path); 297 : } 298 : 299 3 : void Manifest::set_default_revision_set(const std::string &name) 300 : { 301 3 : m_default_revision_set = name; 302 3 : } 303 : 304 35 : const std::string &Manifest::get_default_revision_set() const 305 : { 306 35 : return m_default_revision_set; 307 : } 308 : 309 29 : void Manifest::set_active_revision_set(const std::string &name) 310 : { 311 29 : m_active_revision_set = name; 312 29 : } 313 : 314 1 : const std::string &Manifest::get_active_revision_set() const 315 : { 316 1 : return m_active_revision_set; 317 : } 318 : 319 30 : Result Manifest::select_revision_set(const std::string &preferred) 320 : { 321 30 : if (!preferred.empty()) 322 : { 323 7 : if (get_revision_sets().find_by_name(preferred) == nullptr) 324 1 : return ESYSREPO_RESULT(ResultCode::MANIFEST_REVISION_SET_UNKNOWN, preferred); 325 3 : set_active_revision_set(preferred); 326 3 : return ESYSREPO_RESULT(ResultCode::OK); 327 : } 328 : 329 26 : if (!get_default_revision_set().empty()) 330 : { 331 0 : if (get_revision_sets().find_by_name(get_default_revision_set()) == nullptr) 332 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_REVISION_SET_UNKNOWN, get_default_revision_set()); 333 0 : set_active_revision_set(get_default_revision_set()); 334 0 : return ESYSREPO_RESULT(ResultCode::OK); 335 : } 336 : 337 26 : set_active_revision_set(""); 338 26 : return ESYSREPO_RESULT(ResultCode::OK); 339 : } 340 : 341 221 : std::string Manifest::get_effective_revision_set_name() const 342 : { 343 221 : if (!m_active_revision_set.empty()) return m_active_revision_set; 344 216 : return m_default_revision_set; 345 : } 346 : 347 105 : manifest::RevisionSets &Manifest::get_revision_sets() 348 : { 349 105 : return m_revision_sets; 350 : } 351 : 352 0 : const manifest::RevisionSets &Manifest::get_revision_sets() const 353 : { 354 0 : return m_revision_sets; 355 : } 356 : 357 78 : void Manifest::clear() 358 : { 359 78 : m_type = manifest::Type::NOT_SET; 360 78 : m_locations.clear(); 361 78 : m_map_locations.clear(); 362 78 : m_map_locations_by_path.clear(); 363 78 : m_default_location.reset(); 364 78 : get_groups().clear(); 365 78 : get_revision_sets().clear(); 366 78 : m_revision_pins.reset(); 367 78 : m_map_locations_by_path.clear(); 368 78 : m_default_location_str.clear(); 369 78 : m_default_revision = "master"; 370 78 : m_default_revision_set.clear(); 371 78 : m_active_revision_set.clear(); 372 78 : m_default_job_count = 1; 373 78 : m_default_sync_s.reset(); 374 78 : } 375 : 376 42 : Result Manifest::validate_depends() 377 : { 378 108 : for (auto &location : get_locations()) 379 : { 380 397 : for (auto &repo : location->get_repos()) 381 : { 382 337 : for (auto &edge : repo->get_depends()) 383 : { 384 7 : if (edge == nullptr) 385 0 : return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR, "null depends"); 386 7 : if (edge->get_selector() == manifest::DependsSelector::NOT_SET || edge->get_value().empty()) 387 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_WITHOUT_IDENTITY, 388 : "repo path=" + repo->get_path()); 389 : 390 7 : std::shared_ptr<manifest::Repository> target; 391 7 : switch (edge->get_selector()) 392 : { 393 7 : case manifest::DependsSelector::PATH: 394 14 : target = find_repo_by_path(edge->get_value()); 395 7 : if (target == nullptr) 396 1 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_UNRESOLVED, 397 : "path=" + edge->get_value()); 398 : break; 399 0 : case manifest::DependsSelector::NAME: { 400 0 : auto matches = find_repos_by_name(edge->get_value()); 401 0 : if (matches.empty()) 402 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_UNRESOLVED, 403 : "name=" + edge->get_value()); 404 0 : if (matches.size() > 1) 405 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_AMBIGUOUS, 406 : "name=" + edge->get_value()); 407 0 : target = matches.front(); 408 0 : break; 409 0 : } 410 0 : case manifest::DependsSelector::PROJECT: { 411 0 : auto matches = find_repos_by_project(edge->get_value()); 412 0 : if (matches.empty()) 413 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_UNRESOLVED, 414 : "project=" + edge->get_value()); 415 0 : if (matches.size() > 1) 416 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_AMBIGUOUS, 417 : "project=" + edge->get_value()); 418 0 : target = matches.front(); 419 0 : break; 420 0 : } 421 0 : case manifest::DependsSelector::URL: 422 0 : target = find_repo_by_url(edge->get_value()); 423 0 : if (target == nullptr) 424 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_UNRESOLVED, 425 : "url=" + edge->get_value()); 426 : break; 427 0 : default: 428 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_WITHOUT_IDENTITY, 429 : "repo path=" + repo->get_path()); 430 : } 431 6 : edge->set_resolved_path(target->get_path()); 432 7 : } 433 : } 434 : } 435 : 436 : // Cycle detection: white/gray/black DFS on directed depends graph. 437 41 : enum class Color 438 : { 439 : White, 440 : Gray, 441 : Black 442 : }; 443 83 : std::map<std::string, Color> color; 444 107 : for (auto &location : get_locations()) 445 : { 446 396 : for (auto &repo : location->get_repos()) 447 330 : color[repo->get_path()] = Color::White; 448 : } 449 : 450 371 : std::function<Result(const std::string &)> dfs = [&](const std::string &path) -> Result { 451 330 : color[path] = Color::Gray; 452 334 : for (const auto &dep_path : get_depends_paths(path)) 453 : { 454 6 : auto it = color.find(dep_path); 455 6 : if (it == color.end()) 456 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_UNRESOLVED, "path=" + dep_path); 457 6 : if (it->second == Color::Gray) 458 1 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_CYCLE, path + " -> " + dep_path); 459 5 : if (it->second == Color::White) 460 : { 461 1 : Result nested = dfs(dep_path); 462 1 : if (nested.error()) return nested; 463 1 : } 464 330 : } 465 328 : color[path] = Color::Black; 466 328 : return ESYSREPO_RESULT(ResultCode::OK); 467 82 : }; 468 : 469 369 : for (auto &kv : color) 470 : { 471 329 : if (kv.second != Color::White) continue; 472 329 : Result result = dfs(kv.first); 473 329 : if (result.error()) return result; 474 329 : } 475 40 : return ESYSREPO_RESULT(ResultCode::OK); 476 : } 477 : 478 333 : std::vector<std::string> Manifest::get_depends_paths(const std::string &repo_path) const 479 : { 480 333 : std::vector<std::string> out; 481 333 : auto repo = const_cast<Manifest *>(this)->find_repo_by_path(repo_path); 482 333 : if (repo == nullptr) return out; 483 341 : for (const auto &edge : repo->get_depends()) 484 : { 485 8 : if (edge == nullptr) continue; 486 8 : if (!edge->get_resolved_path().empty()) out.push_back(edge->get_resolved_path()); 487 : } 488 : return out; 489 333 : } 490 : 491 1 : std::set<std::string> Manifest::get_depends_closure(const std::string &repo_path) const 492 : { 493 1 : std::set<std::string> closure; 494 1 : std::vector<std::string> stack = get_depends_paths(repo_path); 495 3 : while (!stack.empty()) 496 : { 497 2 : std::string cur = stack.back(); 498 2 : stack.pop_back(); 499 2 : if (!closure.insert(cur).second) continue; 500 3 : for (const auto &next : get_depends_paths(cur)) 501 3 : stack.push_back(next); 502 2 : } 503 1 : return closure; 504 1 : } 505 : 506 2 : std::vector<std::pair<std::string, std::string>> Manifest::get_depends_edges() const 507 : { 508 2 : std::vector<std::pair<std::string, std::string>> edges; 509 4 : for (const auto &location : get_locations()) 510 : { 511 11 : for (const auto &repo : location->get_repos()) 512 : { 513 11 : for (const auto &edge : repo->get_depends()) 514 : { 515 2 : if (edge == nullptr || edge->get_resolved_path().empty()) continue; 516 2 : edges.emplace_back(repo->get_path(), edge->get_resolved_path()); 517 : } 518 : } 519 : } 520 2 : return edges; 521 0 : } 522 : 523 259 : manifest::Groups &Manifest::get_groups() 524 : { 525 259 : return m_groups; 526 : } 527 : 528 0 : const manifest::Groups &Manifest::get_groups() const 529 : { 530 0 : return m_groups; 531 : } 532 : 533 2 : void Manifest::add_include(std::shared_ptr<manifest::Include> include) 534 : { 535 2 : m_includes.push_back(include); 536 2 : } 537 : 538 : 539 4 : std::vector<std::shared_ptr<manifest::Include>> &Manifest::get_includes() 540 : { 541 4 : return m_includes; 542 : } 543 : 544 2 : const std::vector<std::shared_ptr<manifest::Include>> &Manifest::get_includes() const 545 : { 546 2 : return m_includes; 547 : } 548 : 549 1 : bool Manifest::operator==(const Manifest &other) const 550 : { 551 1 : if (get_type() != other.get_type()) return false; 552 : 553 1 : if (get_locations().size() != other.get_locations().size()) return false; 554 : 555 2 : for (std::size_t idx = 0; idx < get_locations().size(); ++idx) 556 : { 557 1 : if (*get_locations()[idx] != *other.get_locations()[idx]) return false; 558 : } 559 : 560 1 : if (get_includes().size() != other.get_includes().size()) return false; 561 : 562 : 563 : 564 : return true; 565 : } 566 : 567 0 : bool Manifest::operator!=(const Manifest &other) const 568 : { 569 0 : return !operator==(other); 570 : } 571 : 572 0 : ESYSREPO_API std::ostream &operator<<(std::ostream &os, const Manifest &manifest) 573 : { 574 0 : return os; 575 : } 576 : 577 : } // namespace esys::repo