LCOV - code coverage report
Current view: top level - src/esys/repo - manifest.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 245 314 78.0 %
Date: 2026-09-26 15:38:18 Functions: 48 60 80.0 %

          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

Generated by: LCOV version 1.14