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