Convert a number of destructors to default synthesized functions. Try to inline a few instances into the header. It should be possible to inline all of them, however, gcc seems to emit a number of warnings. Furthermore, some of the destructors are pure-virtualed, but provide an implementation. Placing the definition into the header causes ODR violations.
1148 lines
46 KiB
C++
1148 lines
46 KiB
C++
/* vim: set sw=4 sts=4 et foldmethod=syntax : */
|
|
|
|
/*
|
|
* Copyright (c) 2010, 2011, 2014 Ciaran McCreesh
|
|
*
|
|
* This file is part of the Paludis package manager. Paludis is free software;
|
|
* you can redistribute it and/or modify it under the terms of the GNU General
|
|
* Public License version 2, as published by the Free Software Foundation.
|
|
*
|
|
* Paludis is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
|
* Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <paludis/resolver/orderer.hh>
|
|
#include <paludis/resolver/decision.hh>
|
|
#include <paludis/resolver/decisions.hh>
|
|
#include <paludis/resolver/resolution.hh>
|
|
#include <paludis/resolver/nag.hh>
|
|
#include <paludis/resolver/reason.hh>
|
|
#include <paludis/resolver/constraint.hh>
|
|
#include <paludis/resolver/strongly_connected_component.hh>
|
|
#include <paludis/resolver/resolutions_by_resolvent.hh>
|
|
#include <paludis/resolver/resolver_functions.hh>
|
|
#include <paludis/resolver/job_lists.hh>
|
|
#include <paludis/resolver/job_list.hh>
|
|
#include <paludis/resolver/job.hh>
|
|
#include <paludis/resolver/job_requirements.hh>
|
|
#include <paludis/resolver/destination.hh>
|
|
#include <paludis/resolver/orderer_notes.hh>
|
|
#include <paludis/resolver/change_by_resolvent.hh>
|
|
#include <paludis/resolver/labels_classifier.hh>
|
|
#include <paludis/resolver/collect_depped_upon.hh>
|
|
#include <paludis/resolver/reason_utils.hh>
|
|
|
|
#include <paludis/util/pimp-impl.hh>
|
|
#include <paludis/util/exception.hh>
|
|
#include <paludis/util/stringify.hh>
|
|
#include <paludis/util/hashes.hh>
|
|
#include <paludis/util/join.hh>
|
|
#include <paludis/util/make_named_values.hh>
|
|
#include <paludis/util/make_shared_copy.hh>
|
|
#include <paludis/util/visitor_cast.hh>
|
|
#include <paludis/util/tribool.hh>
|
|
#include <paludis/util/enum_iterator.hh>
|
|
|
|
#include <paludis/partially_made_package_dep_spec.hh>
|
|
#include <paludis/environment.hh>
|
|
#include <paludis/notifier_callback.hh>
|
|
#include <paludis/package_id.hh>
|
|
#include <paludis/metadata_key.hh>
|
|
#include <paludis/slot.hh>
|
|
#include <paludis/slot_requirement.hh>
|
|
#include <paludis/selection.hh>
|
|
#include <paludis/generator.hh>
|
|
#include <paludis/filter.hh>
|
|
#include <paludis/filtered_generator.hh>
|
|
|
|
#include <unordered_set>
|
|
#include <unordered_map>
|
|
#include <algorithm>
|
|
#include <list>
|
|
|
|
using namespace paludis;
|
|
using namespace paludis::resolver;
|
|
|
|
typedef std::unordered_map<NAGIndex, std::shared_ptr<const ChangeOrRemoveDecision>, Hash<NAGIndex> > ChangeOrRemoveIndices;
|
|
typedef std::unordered_map<NAGIndex, JobNumber, Hash<NAGIndex> > ChangeOrRemoveJobNumbers;
|
|
typedef std::unordered_map<Resolvent, JobNumber, Hash<Resolvent> > FetchJobNumbers;
|
|
|
|
namespace paludis
|
|
{
|
|
template <>
|
|
struct Imp<Orderer>
|
|
{
|
|
const Environment * const env;
|
|
const ResolverFunctions fns;
|
|
const std::shared_ptr<Resolved> resolved;
|
|
ChangeOrRemoveIndices change_or_remove_indices;
|
|
FetchJobNumbers fetch_job_numbers;
|
|
ChangeOrRemoveJobNumbers change_or_remove_job_numbers;
|
|
|
|
Imp(
|
|
const Environment * const e,
|
|
const ResolverFunctions & f,
|
|
const std::shared_ptr<Resolved> & r) :
|
|
env(e),
|
|
fns(f),
|
|
resolved(r)
|
|
{
|
|
}
|
|
};
|
|
}
|
|
|
|
Orderer::Orderer(
|
|
const Environment * const e,
|
|
const ResolverFunctions & f,
|
|
const std::shared_ptr<Resolved> & r) :
|
|
_imp(e, f, r)
|
|
{
|
|
}
|
|
|
|
Orderer::~Orderer() = default;
|
|
|
|
namespace
|
|
{
|
|
typedef std::unordered_set<Resolvent, Hash<Resolvent> > ResolventsSet;
|
|
|
|
struct DecisionDispatcher
|
|
{
|
|
const std::shared_ptr<Resolved> resolved;
|
|
ResolventsSet & ignore_dependencies_from_resolvents;
|
|
ChangeOrRemoveIndices & change_or_remove_indices;
|
|
const Resolvent resolvent;
|
|
const std::shared_ptr<const Decision> decision;
|
|
|
|
DecisionDispatcher(
|
|
const std::shared_ptr<Resolved> & r,
|
|
ResolventsSet & i,
|
|
ChangeOrRemoveIndices & c,
|
|
const Resolvent & v,
|
|
const std::shared_ptr<const Decision> & d) :
|
|
resolved(r),
|
|
ignore_dependencies_from_resolvents(i),
|
|
change_or_remove_indices(c),
|
|
resolvent(v),
|
|
decision(d)
|
|
{
|
|
}
|
|
|
|
bool visit(const UnableToMakeDecision &)
|
|
{
|
|
if (decision->taken())
|
|
resolved->taken_unable_to_make_decisions()->push_back(
|
|
std::static_pointer_cast<const UnableToMakeDecision>(decision));
|
|
else
|
|
resolved->untaken_unable_to_make_decisions()->push_back(
|
|
std::static_pointer_cast<const UnableToMakeDecision>(decision));
|
|
|
|
ignore_dependencies_from_resolvents.insert(resolvent);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool visit(const NothingNoChangeDecision &)
|
|
{
|
|
resolved->nag()->add_node(make_named_values<NAGIndex>(
|
|
n::resolvent() = resolvent,
|
|
n::role() = nir_done
|
|
));
|
|
return true;
|
|
}
|
|
|
|
bool visit(const ExistingNoChangeDecision &)
|
|
{
|
|
resolved->nag()->add_node(make_named_values<NAGIndex>(
|
|
n::resolvent() = resolvent,
|
|
n::role() = nir_done
|
|
));
|
|
return true;
|
|
}
|
|
|
|
bool visit(const ChangesToMakeDecision &)
|
|
{
|
|
if (decision->taken())
|
|
{
|
|
NAGIndex fetched_index(make_named_values<NAGIndex>(
|
|
n::resolvent() = resolvent,
|
|
n::role() = nir_fetched
|
|
));
|
|
resolved->nag()->add_node(fetched_index);
|
|
change_or_remove_indices.insert(std::make_pair(fetched_index,
|
|
std::static_pointer_cast<const ChangeOrRemoveDecision>(decision)));
|
|
|
|
NAGIndex done_index(make_named_values<NAGIndex>(
|
|
n::resolvent() = resolvent,
|
|
n::role() = nir_done
|
|
));
|
|
resolved->nag()->add_node(done_index);
|
|
change_or_remove_indices.insert(std::make_pair(done_index,
|
|
std::static_pointer_cast<const ChangeOrRemoveDecision>(decision)));
|
|
|
|
resolved->nag()->add_edge(done_index, fetched_index,
|
|
make_named_values<NAGEdgeProperties>(
|
|
n::always() = false,
|
|
n::build() = true,
|
|
n::build_all_met() = false,
|
|
n::run() = false,
|
|
n::run_all_met() = true
|
|
));
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
resolved->untaken_change_or_remove_decisions()->push_back(
|
|
std::static_pointer_cast<const ChangesToMakeDecision>(decision));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool visit(const RemoveDecision &)
|
|
{
|
|
if (decision->taken())
|
|
{
|
|
NAGIndex index(make_named_values<NAGIndex>(
|
|
n::resolvent() = resolvent,
|
|
n::role() = nir_done
|
|
));
|
|
resolved->nag()->add_node(index);
|
|
change_or_remove_indices.insert(std::make_pair(index,
|
|
std::static_pointer_cast<const ChangeOrRemoveDecision>(decision)));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
resolved->untaken_change_or_remove_decisions()->push_back(
|
|
std::static_pointer_cast<const ChangeOrRemoveDecision>(decision));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool visit(const BreakDecision & d)
|
|
{
|
|
if (d.required_confirmations_if_any())
|
|
resolved->taken_unconfirmed_decisions()->push_back(
|
|
std::static_pointer_cast<const BreakDecision>(decision));
|
|
return false;
|
|
}
|
|
};
|
|
|
|
struct SlotIsLocked
|
|
{
|
|
bool visit(const SlotExactPartialRequirement &) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool visit(const SlotExactFullRequirement &) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool visit(const SlotAnyAtAllLockedRequirement &) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool visit(const SlotAnyPartialLockedRequirement &) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool visit(const SlotAnyUnlockedRequirement &) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool visit(const SlotUnknownRewrittenRequirement &) const
|
|
{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
struct EdgesFromReasonVisitor
|
|
{
|
|
const Environment * const env;
|
|
const std::shared_ptr<NAG> nag;
|
|
const ResolventsSet & ignore_dependencies_from_resolvents;
|
|
const Resolvent resolvent;
|
|
const std::shared_ptr<const Decision> decision;
|
|
const std::function<NAGIndexRole (const Resolvent &)> role_for_fetching;
|
|
|
|
EdgesFromReasonVisitor(
|
|
const Environment * const e,
|
|
const std::shared_ptr<NAG> & n,
|
|
const ResolventsSet & i,
|
|
const Resolvent & v,
|
|
const std::shared_ptr<const Decision> & d,
|
|
const std::function<NAGIndexRole (const Resolvent &)> & f) :
|
|
env(e),
|
|
nag(n),
|
|
ignore_dependencies_from_resolvents(i),
|
|
resolvent(v),
|
|
decision(d),
|
|
role_for_fetching(f)
|
|
{
|
|
}
|
|
|
|
void visit(const DependencyReason & r)
|
|
{
|
|
/* we may be constrained by a dep from a package that was changed
|
|
* from a non error decision to an unable to make decision */
|
|
if (ignore_dependencies_from_resolvents.end() != ignore_dependencies_from_resolvents.find(r.from_resolvent()))
|
|
return;
|
|
|
|
/* what sort of dep are we? */
|
|
LabelsClassifierBuilder classifier_builder(env, r.from_id());
|
|
std::string active_labels;
|
|
for (DependenciesLabelSequence::ConstIterator l(r.sanitised_dependency().active_dependency_labels()->begin()),
|
|
l_end(r.sanitised_dependency().active_dependency_labels()->end()) ;
|
|
l != l_end ; ++l)
|
|
{
|
|
(*l)->accept(classifier_builder);
|
|
if (! active_labels.empty())
|
|
active_labels.append(", ");
|
|
active_labels.append(stringify(**l));
|
|
}
|
|
|
|
Context labels_context("With active labels '" + active_labels + "':");
|
|
|
|
auto classifier(classifier_builder.create());
|
|
|
|
if (classifier->includes_buildish || classifier->includes_non_post_runish)
|
|
{
|
|
bool normal(true);
|
|
if (r.sanitised_dependency().spec().if_block())
|
|
switch (find_blocker_role_in_annotations(r.sanitised_dependency().spec().if_block()->maybe_annotations()))
|
|
{
|
|
case dsar_blocker_weak:
|
|
case dsar_blocker_uninstall_blocked_after:
|
|
normal = false;
|
|
break;
|
|
|
|
case dsar_blocker_strong:
|
|
case dsar_blocker_manual:
|
|
case dsar_blocker_upgrade_blocked_before:
|
|
case dsar_blocker_uninstall_blocked_before:
|
|
break;
|
|
|
|
default:
|
|
throw InternalError(PALUDIS_HERE, "unexpected role");
|
|
}
|
|
|
|
NAGIndex from(make_named_values<NAGIndex>(
|
|
n::resolvent() = r.from_resolvent(),
|
|
n::role() = classifier->includes_fetch ? role_for_fetching(r.from_resolvent()) : nir_done
|
|
));
|
|
|
|
NAGIndex to(make_named_values<NAGIndex>(
|
|
n::resolvent() = resolvent,
|
|
n::role() = nir_done
|
|
));
|
|
|
|
if (normal)
|
|
{
|
|
/* we might have added in binary creation later, so make from be
|
|
* our binary creation node rather than ourself, if applicable */
|
|
if (from.resolvent().destination_type() != dt_create_binary)
|
|
{
|
|
NAGIndex from_bin(from);
|
|
from_bin.resolvent().destination_type() = dt_create_binary;
|
|
if (nag->end_nodes() != nag->find_node(from_bin))
|
|
from = from_bin;
|
|
}
|
|
|
|
/* ticket:1174: making binaries with self run dependencies */
|
|
bool override_run_all_met(false);
|
|
if (to.resolvent().destination_type() != dt_create_binary &&
|
|
from.resolvent().destination_type() == dt_create_binary &&
|
|
from.resolvent().package() == to.resolvent().package() &&
|
|
from.resolvent().slot() == to.resolvent().slot())
|
|
{
|
|
override_run_all_met = true;
|
|
}
|
|
|
|
bool build_all_met(! classifier->includes_buildish);
|
|
if (! build_all_met && r.already_met().is_true())
|
|
{
|
|
build_all_met = true;
|
|
std::shared_ptr<const PackageDepSpec> if_package(r.sanitised_dependency().spec().if_package());
|
|
if (if_package && if_package->slot_requirement_ptr() &&
|
|
if_package->slot_requirement_ptr()->accept_returning<bool>(SlotIsLocked{}))
|
|
{
|
|
const ChangesToMakeDecision *d(visitor_cast<const ChangesToMakeDecision>(*decision));
|
|
if (d && d->origin_id()->slot_key())
|
|
{
|
|
std::shared_ptr<const PackageIDSequence> matches((*env)[selection::BestVersionOnly(
|
|
generator::Matches(*if_package, r.from_id(), { }) |
|
|
filter::InstalledAtRoot(env->system_root_key()->parse_value()))]);
|
|
if (! matches->empty() && (*matches->last())->slot_key() &&
|
|
(*matches->last())->slot_key()->parse_value().match_values() !=
|
|
d->origin_id()->slot_key()->parse_value().match_values())
|
|
build_all_met = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
nag->add_edge(from, to,
|
|
make_named_values<NAGEdgeProperties>(
|
|
n::always() = false,
|
|
n::build() = classifier->includes_buildish,
|
|
n::build_all_met() = build_all_met,
|
|
n::run() = classifier->includes_non_post_runish,
|
|
n::run_all_met() = override_run_all_met || r.already_met().is_true() || ! classifier->includes_non_post_runish
|
|
));
|
|
}
|
|
else
|
|
{
|
|
nag->add_edge(to, from,
|
|
make_named_values<NAGEdgeProperties>(
|
|
n::always() = false,
|
|
n::build() = false,
|
|
n::build_all_met() = true,
|
|
n::run() = false,
|
|
n::run_all_met() = true
|
|
));
|
|
}
|
|
}
|
|
else if (classifier->includes_postish)
|
|
{
|
|
/* we won't add a backwards edge, since most post deps dep upon
|
|
* the thing requiring them anyway */
|
|
// nag->add_edge(resolvent, r.from_resolvent());
|
|
}
|
|
else
|
|
throw InternalError(PALUDIS_HERE, "No classification");
|
|
}
|
|
|
|
void visit(const SetReason & r)
|
|
{
|
|
if (r.reason_for_set())
|
|
r.reason_for_set()->accept(*this);
|
|
}
|
|
|
|
void visit(const LikeOtherDestinationTypeReason & r)
|
|
{
|
|
if (r.reason_for_other())
|
|
r.reason_for_other()->accept(*this);
|
|
}
|
|
|
|
void visit(const PresetReason &)
|
|
{
|
|
}
|
|
|
|
void visit(const ViaBinaryReason &)
|
|
{
|
|
/* we do clever binary arrows later, to get binaries that just
|
|
* happen to work anyway right too */
|
|
}
|
|
|
|
void visit(const DependentReason & r)
|
|
{
|
|
/* we may be constrained by a dep from a package that was changed
|
|
* from a non error decision to an unable to make decision */
|
|
if (ignore_dependencies_from_resolvents.end() != ignore_dependencies_from_resolvents.find(
|
|
r.dependent_upon().resolvent()))
|
|
return;
|
|
|
|
NAGIndex from(make_named_values<NAGIndex>(
|
|
n::resolvent() = r.dependent_upon().resolvent(),
|
|
n::role() = nir_done
|
|
));
|
|
|
|
NAGIndex to(make_named_values<NAGIndex>(
|
|
n::resolvent() = resolvent,
|
|
n::role() = nir_done
|
|
));
|
|
|
|
nag->add_edge(from, to,
|
|
make_named_values<NAGEdgeProperties>(
|
|
n::always() = false,
|
|
n::build() = false,
|
|
n::build_all_met() = true,
|
|
n::run() = false,
|
|
n::run_all_met() = true
|
|
));
|
|
}
|
|
|
|
void visit(const TargetReason &)
|
|
{
|
|
}
|
|
|
|
void visit(const WasUsedByReason & r)
|
|
{
|
|
for (ChangeByResolventSequence::ConstIterator i(r.ids_and_resolvents_being_removed()->begin()),
|
|
i_end(r.ids_and_resolvents_being_removed()->end()) ;
|
|
i != i_end ; ++i)
|
|
{
|
|
NAGIndex to(make_named_values<NAGIndex>(
|
|
n::resolvent() = i->resolvent(),
|
|
n::role() = nir_done
|
|
));
|
|
|
|
NAGIndex from(make_named_values<NAGIndex>(
|
|
n::resolvent() = resolvent,
|
|
n::role() = nir_done
|
|
));
|
|
|
|
nag->add_edge(from, to,
|
|
make_named_values<NAGEdgeProperties>(
|
|
n::always() = false,
|
|
n::build() = false,
|
|
n::build_all_met() = true,
|
|
n::run() = false,
|
|
n::run_all_met() = true
|
|
));
|
|
}
|
|
}
|
|
};
|
|
|
|
bool no_build_dependencies(
|
|
const Set<NAGIndex> & indices,
|
|
const NAG & nag)
|
|
{
|
|
for (Set<NAGIndex>::ConstIterator r(indices.begin()), r_end(indices.end()) ;
|
|
r != r_end ; ++r)
|
|
for (NAG::EdgesFromConstIterator e(nag.begin_edges_from(*r)), e_end(nag.end_edges_from(*r)) ;
|
|
e != e_end ; ++e)
|
|
if (e->second.build())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
Tribool
|
|
Orderer::_order_early(const NAGIndex & i) const
|
|
{
|
|
return _imp->fns.order_early_fn()(*_imp->resolved->resolutions_by_resolvent()->find(i.resolvent()));
|
|
}
|
|
|
|
void
|
|
Orderer::resolve()
|
|
{
|
|
Context context("When resolving ordering:");
|
|
|
|
_imp->env->trigger_notifier_callback(NotifierCallbackResolverStageEvent("Nodifying Decisions"));
|
|
|
|
ResolventsSet ignore_dependencies_from_resolvents, ignore_edges_from_resolvents;
|
|
for (ResolutionsByResolvent::ConstIterator r(_imp->resolved->resolutions_by_resolvent()->begin()),
|
|
r_end(_imp->resolved->resolutions_by_resolvent()->end()) ;
|
|
r != r_end ; ++r)
|
|
{
|
|
_imp->env->trigger_notifier_callback(NotifierCallbackResolverStepEvent());
|
|
|
|
DecisionDispatcher decision_dispatcher(
|
|
_imp->resolved,
|
|
ignore_dependencies_from_resolvents,
|
|
_imp->change_or_remove_indices,
|
|
(*r)->resolvent(),
|
|
(*r)->decision());
|
|
if (! (*r)->decision()->accept_returning<bool>(decision_dispatcher))
|
|
ignore_edges_from_resolvents.insert((*r)->resolvent());
|
|
}
|
|
|
|
_imp->env->trigger_notifier_callback(NotifierCallbackResolverStageEvent("Building NAG Edges"));
|
|
|
|
for (ResolutionsByResolvent::ConstIterator r(_imp->resolved->resolutions_by_resolvent()->begin()),
|
|
r_end(_imp->resolved->resolutions_by_resolvent()->end()) ;
|
|
r != r_end ; ++r)
|
|
{
|
|
Context subcontext("When ordering '" + stringify((*r)->resolvent()) + "':");
|
|
|
|
_imp->env->trigger_notifier_callback(NotifierCallbackResolverStepEvent());
|
|
|
|
if (ignore_edges_from_resolvents.end() != ignore_edges_from_resolvents.find((*r)->resolvent()))
|
|
continue;
|
|
|
|
_add_binary_cleverness(*r);
|
|
|
|
EdgesFromReasonVisitor edges_from_reason_visitor(_imp->env, _imp->resolved->nag(), ignore_dependencies_from_resolvents, (*r)->resolvent(),
|
|
(*r)->decision(), std::bind(&Orderer::_role_for_fetching, this, std::placeholders::_1));
|
|
for (Constraints::ConstIterator c((*r)->constraints()->begin()),
|
|
c_end((*r)->constraints()->end()) ;
|
|
c != c_end ; ++c)
|
|
{
|
|
Context subsubcontext("When handling constraint '" + stringify((*c)->spec()) + "' with reason '" + stringify(*(*c)->reason()) + "':");
|
|
(*c)->reason()->accept(edges_from_reason_visitor);
|
|
}
|
|
}
|
|
|
|
_imp->resolved->nag()->verify_edges();
|
|
|
|
const std::function<Tribool (const NAGIndex &)> order_early_fn(std::bind(&Orderer::_order_early, this, std::placeholders::_1));
|
|
|
|
_imp->env->trigger_notifier_callback(NotifierCallbackResolverStageEvent("Finding NAG SCCs"));
|
|
const std::shared_ptr<const SortedStronglyConnectedComponents> ssccs(
|
|
_imp->resolved->nag()->sorted_strongly_connected_components(order_early_fn));
|
|
|
|
_imp->env->trigger_notifier_callback(NotifierCallbackResolverStageEvent("Ordering SCCs"));
|
|
for (SortedStronglyConnectedComponents::ConstIterator scc(ssccs->begin()), scc_end(ssccs->end()) ;
|
|
scc != scc_end ; ++scc)
|
|
{
|
|
_imp->env->trigger_notifier_callback(NotifierCallbackResolverStepEvent());
|
|
|
|
/* some (or none, or all) of the nodes in our SCC are change or remove
|
|
* nodes. this matters for cycle resolution. we identify them now, even
|
|
* though our scc might just contain a single install, rather than
|
|
* adding in extra useless code for the special easy case. */
|
|
typedef std::unordered_set<NAGIndex, Hash<NAGIndex> > ChangesInSCC;
|
|
ChangesInSCC changes_in_scc;
|
|
|
|
for (Set<NAGIndex>::ConstIterator r(scc->nodes()->begin()), r_end(scc->nodes()->end()) ;
|
|
r != r_end ; ++r)
|
|
if (_imp->change_or_remove_indices.end() != _imp->change_or_remove_indices.find(*r))
|
|
changes_in_scc.insert(*r);
|
|
|
|
if (changes_in_scc.empty())
|
|
{
|
|
/* two or more installed packages are codependent, but we don't have
|
|
* to care */
|
|
}
|
|
else if (changes_in_scc.size() == 1)
|
|
{
|
|
/* there's only one real package in the component, so there's no
|
|
* need to try anything clever */
|
|
_check_self_deps_and_schedule(*changes_in_scc.begin(),
|
|
_imp->change_or_remove_indices.find(*changes_in_scc.begin())->second,
|
|
make_shared_copy(make_named_values<OrdererNotes>(
|
|
n::cycle_breaking() = ""
|
|
)));
|
|
}
|
|
else
|
|
{
|
|
Context sub_context("When considering only changes:");
|
|
|
|
/* whoop de doo. what do our SCCs look like if we only count change
|
|
* or remove nodes? */
|
|
NAG scc_nag;
|
|
for (ChangesInSCC::const_iterator r(changes_in_scc.begin()), r_end(changes_in_scc.end()) ;
|
|
r != r_end ; ++r)
|
|
{
|
|
scc_nag.add_node(*r);
|
|
/* we only need edges inside our SCC, and only those to other
|
|
* change or remove nodes */
|
|
for (NAG::EdgesFromConstIterator e(_imp->resolved->nag()->begin_edges_from(*r)), e_end(_imp->resolved->nag()->end_edges_from(*r)) ;
|
|
e != e_end ; ++e)
|
|
if (changes_in_scc.end() != changes_in_scc.find(e->first))
|
|
scc_nag.add_edge(*r, e->first, e->second);
|
|
}
|
|
|
|
scc_nag.verify_edges();
|
|
|
|
/* now we try again, hopefully with lots of small SCCs now */
|
|
const std::shared_ptr<const SortedStronglyConnectedComponents> sub_ssccs(scc_nag.sorted_strongly_connected_components(order_early_fn));
|
|
_order_sub_ssccs(scc_nag, *scc, sub_ssccs, true, order_early_fn);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Orderer::_add_binary_cleverness(const std::shared_ptr<const Resolution> & resolution)
|
|
{
|
|
if (resolution->resolvent().destination_type() != dt_create_binary)
|
|
return;
|
|
|
|
const ChangesToMakeDecision * changes_decision(visitor_cast<const ChangesToMakeDecision>(*resolution->decision()));
|
|
if (! changes_decision)
|
|
return;
|
|
|
|
for (EnumIterator<DestinationType> t, t_end(last_dt) ; t != t_end ; ++t)
|
|
{
|
|
if (*t == dt_create_binary)
|
|
continue;
|
|
|
|
Resolvent non_binary_resolvent(resolution->resolvent());
|
|
non_binary_resolvent.destination_type() = *t;
|
|
|
|
ResolutionsByResolvent::ConstIterator non_binary_resolution(_imp->resolved->resolutions_by_resolvent()->find(non_binary_resolvent));
|
|
if (_imp->resolved->resolutions_by_resolvent()->end() == non_binary_resolution)
|
|
continue;
|
|
|
|
ChangesToMakeDecision * non_binary_changes_decision(visitor_cast<ChangesToMakeDecision>(*(*non_binary_resolution)->decision()));
|
|
if (! non_binary_changes_decision)
|
|
continue;
|
|
|
|
if (changes_decision->origin_id() != non_binary_changes_decision->origin_id())
|
|
throw InternalError(PALUDIS_HERE, "chose different origin ids: " + stringify(*changes_decision->origin_id()) + " vs "
|
|
+ stringify(*non_binary_changes_decision->origin_id()));
|
|
non_binary_changes_decision->set_via_new_binary_in(changes_decision->destination()->repository());
|
|
|
|
NAGIndex from(make_named_values<NAGIndex>(
|
|
n::resolvent() = non_binary_resolvent,
|
|
n::role() = nir_fetched /* can't fetch until our origin exists */
|
|
));
|
|
|
|
NAGIndex to(make_named_values<NAGIndex>(
|
|
n::resolvent() = resolution->resolvent(),
|
|
n::role() = nir_done
|
|
));
|
|
|
|
_imp->resolved->nag()->add_edge(from, to,
|
|
make_named_values<NAGEdgeProperties>(
|
|
n::always() = true,
|
|
n::build() = true,
|
|
n::build_all_met() = false,
|
|
n::run() = false,
|
|
n::run_all_met() = true
|
|
));
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
std::string nice_index(const NAGIndex & x)
|
|
{
|
|
std::string result(stringify(x.resolvent().package()) + stringify(x.resolvent().slot()));
|
|
if (x.role() == nir_fetched)
|
|
result = result + " (fetch)";
|
|
return result;
|
|
}
|
|
}
|
|
|
|
void
|
|
Orderer::_order_sub_ssccs(
|
|
const NAG & scc_nag,
|
|
const StronglyConnectedComponent & top_scc,
|
|
const std::shared_ptr<const SortedStronglyConnectedComponents> & sub_ssccs,
|
|
const bool can_recurse,
|
|
const std::function<Tribool (const NAGIndex &)> & order_early_fn)
|
|
{
|
|
Context context("When ordering SSCCs" + std::string(can_recurse ? " for the first time" : " for the second time") + ":");
|
|
|
|
for (SortedStronglyConnectedComponents::ConstIterator sub_scc(sub_ssccs->begin()), sub_scc_end(sub_ssccs->end()) ;
|
|
sub_scc != sub_scc_end ; ++sub_scc)
|
|
{
|
|
_imp->env->trigger_notifier_callback(NotifierCallbackResolverStepEvent());
|
|
|
|
if (sub_scc->nodes()->size() == 1)
|
|
{
|
|
/* yay. it's all on its own. */
|
|
_check_self_deps_and_schedule(*sub_scc->nodes()->begin(),
|
|
_imp->change_or_remove_indices.find(*sub_scc->nodes()->begin())->second,
|
|
make_shared_copy(make_named_values<OrdererNotes>(
|
|
n::cycle_breaking() = (can_recurse ?
|
|
"In dependency cycle with existing packages: " + join(scc_nag.begin_nodes(), scc_nag.end_nodes(), ", ", nice_index) :
|
|
"In dependency cycle with: " + join(top_scc.nodes()->begin(), top_scc.nodes()->end(), ", ", nice_index))
|
|
)));
|
|
}
|
|
else if (no_build_dependencies(*sub_scc->nodes(), scc_nag))
|
|
{
|
|
/* what's that, timmy? we have directly codependent nodes?
|
|
* well i'm jolly glad that's because they're run
|
|
* dependency cycles which we can order however we like! */
|
|
for (Set<NAGIndex>::ConstIterator r(sub_scc->nodes()->begin()), r_end(sub_scc->nodes()->end()) ;
|
|
r != r_end ; ++r)
|
|
_check_self_deps_and_schedule(*r,
|
|
_imp->change_or_remove_indices.find(*r)->second,
|
|
make_shared_copy(make_named_values<OrdererNotes>(
|
|
n::cycle_breaking() = "In run dependency cycle with: " + join(
|
|
sub_scc->nodes()->begin(), sub_scc->nodes()->end(), ", ", nice_index) + (can_recurse ?
|
|
" in dependency cycle with " + join(top_scc.nodes()->begin(), top_scc.nodes()->end(), ", ", nice_index) : "")
|
|
)));
|
|
}
|
|
else if (can_recurse)
|
|
{
|
|
/* no, at least one of the deps is a build dep. let's try
|
|
* this whole mess again, except without any edges for
|
|
* dependencies that're already met */
|
|
NAG scc_nag_without_met_deps;
|
|
for (Set<NAGIndex>::ConstIterator r(sub_scc->nodes()->begin()), r_end(sub_scc->nodes()->end()) ;
|
|
r != r_end ; ++r)
|
|
{
|
|
scc_nag_without_met_deps.add_node(*r);
|
|
for (NAG::EdgesFromConstIterator e(scc_nag.begin_edges_from(*r)), e_end(scc_nag.end_edges_from(*r)) ;
|
|
e != e_end ; ++e)
|
|
if (sub_scc->nodes()->end() != sub_scc->nodes()->find(e->first))
|
|
if ((! e->second.build_all_met()) || (! e->second.run_all_met()))
|
|
scc_nag_without_met_deps.add_edge(*r, e->first, make_named_values<NAGEdgeProperties>(
|
|
n::always() = e->second.always(),
|
|
n::build() = e->second.build() && ! e->second.build_all_met(),
|
|
n::build_all_met() = e->second.build_all_met(),
|
|
n::run() = e->second.run() && ! e->second.run_all_met(),
|
|
n::run_all_met() = e->second.run_all_met()
|
|
));
|
|
}
|
|
|
|
scc_nag_without_met_deps.verify_edges();
|
|
|
|
const std::shared_ptr<const SortedStronglyConnectedComponents> sub_ssccs_without_met_deps(
|
|
scc_nag_without_met_deps.sorted_strongly_connected_components(order_early_fn));
|
|
_order_sub_ssccs(scc_nag_without_met_deps, top_scc, sub_ssccs_without_met_deps, false, order_early_fn);
|
|
}
|
|
else
|
|
{
|
|
for (Set<NAGIndex>::ConstIterator r(sub_scc->nodes()->begin()), r_end(sub_scc->nodes()->end()) ;
|
|
r != r_end ; ++r)
|
|
{
|
|
if (r->role() == nir_fetched && sub_scc->nodes()->end() != std::find(sub_scc->nodes()->begin(),
|
|
sub_scc->nodes()->end(), make_named_values<NAGIndex>(
|
|
n::resolvent() = r->resolvent(),
|
|
n::role() = nir_done
|
|
)))
|
|
continue;
|
|
|
|
_imp->resolved->taken_unorderable_decisions()->push_back(
|
|
_imp->change_or_remove_indices.find(*r)->second,
|
|
make_shared_copy(make_named_values<OrdererNotes>(
|
|
n::cycle_breaking() = "In unsolvable cycle with " + join(
|
|
top_scc.nodes()->begin(), top_scc.nodes()->end(), ", ", nice_index))));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
typedef std::unordered_set<NAGIndex, Hash<NAGIndex> > RecursedRequirements;
|
|
|
|
void populate_requirements(
|
|
const std::shared_ptr<const NAG> & nag,
|
|
const ChangeOrRemoveJobNumbers & change_or_remove_job_numbers,
|
|
const NAGIndex & index,
|
|
const std::shared_ptr<JobRequirements> & requirements,
|
|
const bool is_uninstall,
|
|
const bool is_fetch,
|
|
const bool recursing,
|
|
RecursedRequirements & recursed)
|
|
{
|
|
JobRequirementIfs basic_required_ifs;
|
|
if (is_fetch)
|
|
basic_required_ifs += jri_fetching;
|
|
|
|
if (! recursing)
|
|
for (NAG::EdgesFromConstIterator e(nag->begin_edges_from(index)),
|
|
e_end(nag->end_edges_from(index)) ;
|
|
e != e_end ; ++e)
|
|
{
|
|
if ((! e->second.build_all_met()) || (! e->second.run_all_met()) || is_uninstall)
|
|
{
|
|
ChangeOrRemoveJobNumbers::const_iterator n(change_or_remove_job_numbers.find(e->first));
|
|
if (n != change_or_remove_job_numbers.end())
|
|
requirements->push_back(make_named_values<JobRequirement>(
|
|
n::job_number() = n->second,
|
|
n::required_if() = basic_required_ifs + jri_require_for_satisfied + jri_require_for_independent
|
|
));
|
|
}
|
|
|
|
if (e->second.always())
|
|
{
|
|
ChangeOrRemoveJobNumbers::const_iterator n(change_or_remove_job_numbers.find(e->first));
|
|
if (n != change_or_remove_job_numbers.end())
|
|
requirements->push_back(make_named_values<JobRequirement>(
|
|
n::job_number() = n->second,
|
|
n::required_if() = basic_required_ifs + jri_require_always
|
|
));
|
|
}
|
|
}
|
|
|
|
if ((! is_uninstall) && recursed.insert(index).second)
|
|
for (NAG::EdgesFromConstIterator e(nag->begin_edges_from(index)),
|
|
e_end(nag->end_edges_from(index)) ;
|
|
e != e_end ; ++e)
|
|
{
|
|
ChangeOrRemoveJobNumbers::const_iterator n(change_or_remove_job_numbers.find(e->first));
|
|
if (n != change_or_remove_job_numbers.end())
|
|
requirements->push_back(make_named_values<JobRequirement>(
|
|
n::job_number() = n->second,
|
|
n::required_if() = basic_required_ifs + jri_require_for_independent
|
|
));
|
|
populate_requirements(nag, change_or_remove_job_numbers, e->first, requirements,
|
|
is_uninstall, is_fetch, true, recursed);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Orderer::_check_self_deps_and_schedule(
|
|
const NAGIndex & index,
|
|
const std::shared_ptr<const ChangeOrRemoveDecision> & d,
|
|
const std::shared_ptr<OrdererNotes> & n)
|
|
{
|
|
/* do we dep directly upon ourself? */
|
|
bool direct_self_dep(false), self_dep_is_met(true), self_dep_is_not_build(true);
|
|
for (NAG::EdgesFromConstIterator e(_imp->resolved->nag()->begin_edges_from(index)),
|
|
e_end(_imp->resolved->nag()->end_edges_from(index)) ;
|
|
e != e_end ; ++e)
|
|
{
|
|
if (e->first == index)
|
|
{
|
|
direct_self_dep = true;
|
|
self_dep_is_met = self_dep_is_met && e->second.build_all_met() && e->second.run_all_met();
|
|
self_dep_is_not_build = self_dep_is_not_build && ! e->second.build();
|
|
}
|
|
}
|
|
|
|
if (direct_self_dep)
|
|
{
|
|
if (! n->cycle_breaking().empty())
|
|
n->cycle_breaking().append("; ");
|
|
|
|
if (self_dep_is_met)
|
|
n->cycle_breaking().append("Self dependent (already met)");
|
|
else if (self_dep_is_not_build)
|
|
n->cycle_breaking().append("Self dependent (runtime only)");
|
|
else
|
|
n->cycle_breaking().append("Self dependent (unsolvable)");
|
|
}
|
|
|
|
if (direct_self_dep && ! self_dep_is_met && ! self_dep_is_not_build)
|
|
{
|
|
_imp->resolved->taken_unorderable_decisions()->push_back(
|
|
_imp->change_or_remove_indices.find(index)->second,
|
|
n);
|
|
}
|
|
else
|
|
_schedule(index, d, n);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
PackageDepSpec make_origin_spec(const ChangesToMakeDecision & changes_to_make_decision)
|
|
{
|
|
PartiallyMadePackageDepSpec result(changes_to_make_decision.origin_id()->uniquely_identifying_spec());
|
|
|
|
if (changes_to_make_decision.if_via_new_binary_in())
|
|
result.in_repository(*changes_to_make_decision.if_via_new_binary_in());
|
|
|
|
return result;
|
|
}
|
|
|
|
bool was_target(
|
|
const std::shared_ptr<const Resolved> & r,
|
|
const Decision & d)
|
|
{
|
|
auto resolution(r->resolutions_by_resolvent()->find(d.resolvent()));
|
|
if (r->resolutions_by_resolvent()->end() == resolution)
|
|
throw InternalError(PALUDIS_HERE, "couldn't find " + stringify(d.resolvent()));
|
|
|
|
for (auto c((*resolution)->constraints()->begin()), c_end((*resolution)->constraints()->end()) ;
|
|
c != c_end ; ++c)
|
|
if (is_target((*c)->reason()))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
struct ExtraScheduler
|
|
{
|
|
const std::shared_ptr<const Resolved> resolved;
|
|
FetchJobNumbers & fetch_job_numbers;
|
|
ChangeOrRemoveJobNumbers & change_or_remove_job_numbers;
|
|
const NAGIndex index;
|
|
|
|
ExtraScheduler(
|
|
const std::shared_ptr<const Resolved> & r,
|
|
FetchJobNumbers & f,
|
|
ChangeOrRemoveJobNumbers & i,
|
|
const NAGIndex & v) :
|
|
resolved(r),
|
|
fetch_job_numbers(f),
|
|
change_or_remove_job_numbers(i),
|
|
index(v)
|
|
{
|
|
}
|
|
|
|
void visit(const ChangesToMakeDecision & changes_to_make_decision) const
|
|
{
|
|
switch (index.role())
|
|
{
|
|
case nir_done:
|
|
{
|
|
FetchJobNumbers::const_iterator fetch_job_n(fetch_job_numbers.find(index.resolvent()));
|
|
if (fetch_job_n == fetch_job_numbers.end())
|
|
throw InternalError(PALUDIS_HERE, "haven't scheduled the fetch for " + stringify(index.resolvent()) + " yet");
|
|
|
|
resolved->job_lists()->pretend_job_list()->append(std::make_shared<PretendJob>(
|
|
changes_to_make_decision.origin_id()->uniquely_identifying_spec(),
|
|
changes_to_make_decision.destination()->repository(),
|
|
changes_to_make_decision.resolvent().destination_type()));
|
|
|
|
std::shared_ptr<JobRequirements> requirements(std::make_shared<JobRequirements>());
|
|
requirements->push_back(make_named_values<JobRequirement>(
|
|
n::job_number() = fetch_job_n->second,
|
|
n::required_if() = JobRequirementIfs() + jri_require_for_satisfied + jri_require_for_independent
|
|
+ jri_require_always + jri_fetching
|
|
));
|
|
|
|
RecursedRequirements recursed;
|
|
populate_requirements(
|
|
resolved->nag(),
|
|
change_or_remove_job_numbers,
|
|
index,
|
|
requirements,
|
|
false,
|
|
false,
|
|
false,
|
|
recursed
|
|
);
|
|
|
|
requirements = minimise_requirements(requirements);
|
|
|
|
const std::shared_ptr<Sequence<PackageDepSpec> > replacing(std::make_shared<Sequence<PackageDepSpec>>());
|
|
for (PackageIDSequence::ConstIterator i(changes_to_make_decision.destination()->replacing()->begin()),
|
|
i_end(changes_to_make_decision.destination()->replacing()->end()) ;
|
|
i != i_end ; ++i)
|
|
replacing->push_back((*i)->uniquely_identifying_spec());
|
|
|
|
JobNumber install_job_n(resolved->job_lists()->execute_job_list()->append(std::make_shared<InstallJob>(
|
|
requirements,
|
|
make_origin_spec(changes_to_make_decision),
|
|
changes_to_make_decision.destination()->repository(),
|
|
changes_to_make_decision.resolvent().destination_type(),
|
|
replacing,
|
|
was_target(resolved, changes_to_make_decision)
|
|
)));
|
|
|
|
change_or_remove_job_numbers.insert(std::make_pair(index, install_job_n));
|
|
}
|
|
return;
|
|
|
|
case nir_fetched:
|
|
{
|
|
std::shared_ptr<JobRequirements> requirements(std::make_shared<JobRequirements>());
|
|
|
|
RecursedRequirements recursed;
|
|
populate_requirements(
|
|
resolved->nag(),
|
|
change_or_remove_job_numbers,
|
|
index,
|
|
requirements,
|
|
false,
|
|
true,
|
|
false,
|
|
recursed
|
|
);
|
|
|
|
requirements = minimise_requirements(requirements);
|
|
|
|
JobNumber fetch_job_n(resolved->job_lists()->execute_job_list()->append(std::make_shared<FetchJob>(
|
|
requirements,
|
|
make_origin_spec(changes_to_make_decision),
|
|
was_target(resolved, changes_to_make_decision))));
|
|
fetch_job_numbers.insert(std::make_pair(index.resolvent(), fetch_job_n));
|
|
}
|
|
return;
|
|
|
|
case last_nir:
|
|
break;
|
|
}
|
|
|
|
throw InternalError(PALUDIS_HERE, "bad index.role");
|
|
}
|
|
|
|
void visit(const RemoveDecision & remove_decision) const
|
|
{
|
|
const std::shared_ptr<Sequence<PackageDepSpec> > removing(std::make_shared<Sequence<PackageDepSpec>>());
|
|
for (PackageIDSequence::ConstIterator i(remove_decision.ids()->begin()),
|
|
i_end(remove_decision.ids()->end()) ;
|
|
i != i_end ; ++i)
|
|
removing->push_back((*i)->uniquely_identifying_spec());
|
|
|
|
std::shared_ptr<JobRequirements> requirements(std::make_shared<JobRequirements>());
|
|
RecursedRequirements recursed;
|
|
populate_requirements(
|
|
resolved->nag(),
|
|
change_or_remove_job_numbers,
|
|
index,
|
|
requirements,
|
|
true,
|
|
false,
|
|
false,
|
|
recursed
|
|
);
|
|
|
|
requirements = minimise_requirements(requirements);
|
|
|
|
JobNumber uninstall_job_n(resolved->job_lists()->execute_job_list()->append(std::make_shared<UninstallJob>(
|
|
requirements,
|
|
removing,
|
|
was_target(resolved, remove_decision)
|
|
)));
|
|
|
|
change_or_remove_job_numbers.insert(std::make_pair(index, uninstall_job_n));
|
|
}
|
|
};
|
|
}
|
|
|
|
void
|
|
Orderer::_schedule(
|
|
const NAGIndex & index,
|
|
const std::shared_ptr<const ChangeOrRemoveDecision> & d,
|
|
const std::shared_ptr<const OrdererNotes> & n)
|
|
{
|
|
do
|
|
{
|
|
switch (index.role())
|
|
{
|
|
case nir_done:
|
|
_imp->resolved->taken_change_or_remove_decisions()->push_back(d, n);
|
|
if (d->required_confirmations_if_any())
|
|
_imp->resolved->taken_unconfirmed_decisions()->push_back(d);
|
|
continue;
|
|
|
|
case nir_fetched:
|
|
continue;
|
|
|
|
case last_nir:
|
|
break;
|
|
}
|
|
|
|
throw InternalError(PALUDIS_HERE, "bad index.role");
|
|
} while (false);
|
|
|
|
d->accept(ExtraScheduler(_imp->resolved, _imp->fetch_job_numbers, _imp->change_or_remove_job_numbers, index));
|
|
}
|
|
|
|
namespace
|
|
{
|
|
struct RoleForFetchingVisitor
|
|
{
|
|
NAGIndexRole visit(const ChangesToMakeDecision &) const
|
|
{
|
|
return nir_fetched;
|
|
}
|
|
|
|
NAGIndexRole visit(const ExistingNoChangeDecision &) const
|
|
{
|
|
return nir_done;
|
|
}
|
|
|
|
NAGIndexRole visit(const UnableToMakeDecision &) const
|
|
{
|
|
return nir_done;
|
|
}
|
|
|
|
NAGIndexRole visit(const BreakDecision &) const
|
|
{
|
|
return nir_done;
|
|
}
|
|
|
|
NAGIndexRole visit(const RemoveDecision &) const
|
|
{
|
|
return nir_done;
|
|
}
|
|
|
|
NAGIndexRole visit(const NothingNoChangeDecision &) const
|
|
{
|
|
return nir_done;
|
|
}
|
|
};
|
|
}
|
|
|
|
NAGIndexRole
|
|
Orderer::_role_for_fetching(
|
|
const Resolvent & resolvent) const
|
|
{
|
|
return (*_imp->resolved->resolutions_by_resolvent()->find(
|
|
resolvent))->decision()->accept_returning<NAGIndexRole>(RoleForFetchingVisitor());
|
|
}
|
|
|