Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move problem graph creation to MSolver #2515

Merged
merged 6 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions libmamba/include/mamba/core/satisfiability_error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ namespace mamba
using node_id = graph_t::node_id;
using conflicts_t = conflict_map<node_id>;

static ProblemsGraph from_solver(const MSolver& solver, const MPool& pool);

ProblemsGraph(graph_t graph, conflicts_t conflicts, node_id root_node);

const graph_t& graph() const noexcept;
Expand Down
6 changes: 4 additions & 2 deletions libmamba/include/mamba/core/solver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "mamba/core/package_info.hpp"
#include "mamba/core/pool.hpp"
#include "mamba/core/satisfiability_error.hpp"

#include "match_spec.hpp"

Expand Down Expand Up @@ -65,8 +66,8 @@ namespace mamba

MSolver(const MSolver&) = delete;
MSolver& operator=(const MSolver&) = delete;
MSolver(MSolver&&) = delete;
MSolver& operator=(MSolver&&) = delete;
MSolver(MSolver&&);
MSolver& operator=(MSolver&&);

void add_global_job(int job_flag);
void add_jobs(const std::vector<std::string>& jobs, int job_flag);
Expand All @@ -82,6 +83,7 @@ namespace mamba
[[nodiscard]] std::string problems_to_str() const;
[[nodiscard]] std::vector<std::string> all_problems() const;
[[nodiscard]] std::vector<MSolverProblem> all_problems_structured() const;
[[nodiscard]] ProblemsGraph problems_graph() const;
[[nodiscard]] std::string all_problems_to_str() const;
std::ostream& explain_problems(std::ostream& out) const;
[[nodiscard]] std::string explain_problems() const;
Expand Down
277 changes: 0 additions & 277 deletions libmamba/src/core/satisfiability_error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,291 +18,14 @@
#include <fmt/color.h>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <solv/pool.h>

#include "mamba/core/output.hpp"
#include "mamba/core/package_info.hpp"
#include "mamba/core/pool.hpp"
#include "mamba/core/satisfiability_error.hpp"
#include "mamba/core/solver.hpp"
#include "mamba/core/util_string.hpp"

namespace mamba
{
namespace
{

void warn_unexpected_problem(const MSolverProblem& problem)
{
// TODO: Once the new error message are not experimental, we should consider
// reducing this level since it is not somethig the user has control over.
LOG_WARNING << "Unexpected empty optionals for problem type "
<< solver_ruleinfo_name(problem.type);
}

class ProblemsGraphCreator
{
public:

using SolvId = Id; // Unscoped from libsolv

using graph_t = ProblemsGraph::graph_t;
using RootNode = ProblemsGraph::RootNode;
using PackageNode = ProblemsGraph::PackageNode;
using UnresolvedDependencyNode = ProblemsGraph::UnresolvedDependencyNode;
using ConstraintNode = ProblemsGraph::ConstraintNode;
using node_t = ProblemsGraph::node_t;
using node_id = ProblemsGraph::node_id;
using edge_t = ProblemsGraph::edge_t;
using conflicts_t = ProblemsGraph::conflicts_t;

ProblemsGraphCreator(const MSolver& solver, const MPool& pool);

ProblemsGraph problem_graph() &&;

private:

const MSolver& m_solver;
const MPool& m_pool;
graph_t m_graph;
conflicts_t m_conflicts;
std::map<SolvId, node_id> m_solv2node;
node_id m_root_node;

/**
* Add a node and return the node id.
*
* If the node is already present and ``update`` is false then the current
* node is left as it is, otherwise the new value is inserted.
*/
node_id add_solvable(SolvId solv_id, node_t&& pkg_info, bool update = true);

void add_conflict(node_id n1, node_id n2);
[[nodiscard]] bool
add_expanded_deps_edges(node_id from_id, SolvId dep_id, const edge_t& edge);

void parse_problems();
};

ProblemsGraphCreator::ProblemsGraphCreator(const MSolver& solver, const MPool& pool)
: m_solver{ solver }
, m_pool{ pool }
{
m_root_node = m_graph.add_node(RootNode());
parse_problems();
}

ProblemsGraph ProblemsGraphCreator::problem_graph() &&
{
return { std::move(m_graph), std::move(m_conflicts), m_root_node };
}

auto ProblemsGraphCreator::add_solvable(SolvId solv_id, node_t&& node, bool update) -> node_id
{
if (const auto iter = m_solv2node.find(solv_id); iter != m_solv2node.end())
{
const node_id id = iter->second;
if (update)
{
m_graph.node(id) = std::move(node);
}
return id;
}
const node_id id = m_graph.add_node(std::move(node));
m_solv2node[solv_id] = id;
return id;
};

void ProblemsGraphCreator::add_conflict(node_id n1, node_id n2)
{
m_conflicts.add(n1, n2);
}

bool
ProblemsGraphCreator::add_expanded_deps_edges(node_id from_id, SolvId dep_id, const edge_t& edge)
{
bool added = false;
for (const auto& solv_id : m_pool.select_solvables(dep_id))
{
added = true;
auto pkg_info = m_pool.id2pkginfo(solv_id);
assert(pkg_info.has_value());
node_id to_id = add_solvable(solv_id, PackageNode{ std::move(pkg_info).value() }, false);
m_graph.add_edge(from_id, to_id, edge);
}
return added;
}

void ProblemsGraphCreator::parse_problems()
{
for (auto& problem : m_solver.all_problems_structured())
{
std::optional<PackageInfo>& source = problem.source;
std::optional<PackageInfo>& target = problem.target;
std::optional<std::string>& dep = problem.dep;
SolverRuleinfo type = problem.type;

switch (type)
{
case SOLVER_RULE_PKG_CONSTRAINS:
{
// A constraint (run_constrained) on source is conflicting with target.
// SOLVER_RULE_PKG_CONSTRAINS has a dep, but it can resolve to nothing.
// The constraint conflict is actually expressed between the target and
// a constrains node child of the source.
if (!source || !target || !dep)
{
warn_unexpected_problem(problem);
break;
}
auto src_id = add_solvable(
problem.source_id,
PackageNode{ std::move(source).value() }
);
node_id tgt_id = add_solvable(
problem.target_id,
PackageNode{ std::move(target).value() }
);
node_id cons_id = add_solvable(
problem.dep_id,
ConstraintNode{ { dep.value(), m_pool.channel_context() } }
);
MatchSpec edge(dep.value(), m_pool.channel_context());
m_graph.add_edge(src_id, cons_id, std::move(edge));
add_conflict(cons_id, tgt_id);
break;
}
case SOLVER_RULE_PKG_REQUIRES:
{
// Express a dependency on source that is involved in explaining the
// problem.
// Not all dependency of package will appear, only enough to explain the
// problem. It is not a problem in itself, only a part of the graph.
if (!dep || !source)
{
warn_unexpected_problem(problem);
break;
}
auto src_id = add_solvable(
problem.source_id,
PackageNode{ std::move(source).value() }
);
MatchSpec edge{ dep.value(), m_pool.channel_context() };
bool added = add_expanded_deps_edges(src_id, problem.dep_id, edge);
if (!added)
{
LOG_WARNING << "Added empty dependency for problem type "
<< solver_ruleinfo_name(type);
}
break;
}
case SOLVER_RULE_JOB:
case SOLVER_RULE_PKG:
{
// A top level requirement.
// The difference between JOB and PKG is unknown (possibly unused).
if (!dep)
{
warn_unexpected_problem(problem);
break;
}
MatchSpec edge(dep.value(), m_pool.channel_context());
bool added = add_expanded_deps_edges(m_root_node, problem.dep_id, edge);
if (!added)
{
LOG_WARNING << "Added empty dependency for problem type "
<< solver_ruleinfo_name(type);
}
break;
}
case SOLVER_RULE_JOB_NOTHING_PROVIDES_DEP:
case SOLVER_RULE_JOB_UNKNOWN_PACKAGE:
{
// A top level dependency does not exist.
// Could be a wrong name or missing channel.
if (!dep)
{
warn_unexpected_problem(problem);
break;
}
MatchSpec edge(dep.value(), m_pool.channel_context());
node_id dep_id = add_solvable(
problem.dep_id,
UnresolvedDependencyNode{
{ std::move(dep).value(), m_pool.channel_context() } }
);
m_graph.add_edge(m_root_node, dep_id, std::move(edge));
break;
}
case SOLVER_RULE_PKG_NOTHING_PROVIDES_DEP:
{
// A package dependency does not exist.
// Could be a wrong name or missing channel.
// This is a partial exaplanation of why a specific solvable (could be any
// of the parent) cannot be installed.
if (!source || !dep)
{
warn_unexpected_problem(problem);
break;
}
MatchSpec edge(dep.value(), m_pool.channel_context());
node_id src_id = add_solvable(
problem.source_id,
PackageNode{ std::move(source).value() }
);
node_id dep_id = add_solvable(
problem.dep_id,
UnresolvedDependencyNode{
{ std::move(dep).value(), m_pool.channel_context() } }
);
m_graph.add_edge(src_id, dep_id, std::move(edge));
break;
}
case SOLVER_RULE_PKG_CONFLICTS:
case SOLVER_RULE_PKG_SAME_NAME:
{
// Looking for a valid solution to the installation satisfiability expand to
// two solvables of same package that cannot be installed together. This is
// a partial exaplanation of why one of the solvables (could be any of the
// parent) cannot be installed.
if (!source || !target)
{
warn_unexpected_problem(problem);
break;
}
node_id src_id = add_solvable(
problem.source_id,
PackageNode{ std::move(source).value() }
);
node_id tgt_id = add_solvable(
problem.target_id,
PackageNode{ std::move(target).value() }
);
add_conflict(src_id, tgt_id);
break;
}
case SOLVER_RULE_UPDATE:
{
// Encounterd in the problems list from libsolv but unknown.
// Explicitly ignored until we do something with it.
break;
}
default:
{
// Many more SolverRuleinfo that heve not been encountered.
LOG_WARNING << "Problem type not implemented " << solver_ruleinfo_name(type);
break;
}
}
}
}
}

auto ProblemsGraph::from_solver(const MSolver& solver, const MPool& pool) -> ProblemsGraph
{
return ProblemsGraphCreator(solver, pool).problem_graph();
}

ProblemsGraph::ProblemsGraph(graph_t graph, conflicts_t conflicts, node_id root_node)
: m_graph(std::move(graph))
, m_conflicts(std::move(conflicts))
Expand Down
Loading