LCOV - code coverage report
Current view: top level - src/esys/repo - manifest.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 258 321 80.4 %
Date: 2026-11-07 22:18:26 Functions: 53 62 85.5 %

          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

Generated by: LCOV version 1.14