diff options
author | Daniel Burrows <dburrows@debian.org> | 2005-10-01 23:40:49 +0000 |
---|---|---|
committer | Daniel Burrows <dburrows@debian.org> | 2005-10-01 23:40:49 +0000 |
commit | db949f313eb10b747a875067623b89c47ee2b81d (patch) | |
tree | 95891553696a84cc382aa9a92bacdc88950361e1 /src/reason_fragment.cc | |
parent | e5434a5aaf63b1602c81606824b94f0368e4aaa0 (diff) | |
download | aptitude-db949f313eb10b747a875067623b89c47ee2b81d.tar.gz |
[aptitude @ Import the Subversion repository into darcs.]
Diffstat (limited to 'src/reason_fragment.cc')
-rw-r--r-- | src/reason_fragment.cc | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/src/reason_fragment.cc b/src/reason_fragment.cc new file mode 100644 index 00000000..81dcf40f --- /dev/null +++ b/src/reason_fragment.cc @@ -0,0 +1,572 @@ +// reason_fragment.cc +// +// Copyright 2004 Daniel Burrows + +#include "reason_fragment.h" + +#include "aptitude.h" +#include "pkg_item.h" +#include "pkg_ver_item.h" +#include "ui.h" + +#include <generic/apt/apt.h> +#include <generic/apt/infer_reason.h> + +#include <vscreen/config/colors.h> +#include <vscreen/fragment.h> + +#include <functional> +#include <set> + +using namespace std; + +/** Returns a fragment describing a dependency (as in "depends + * on" or "suggests" rather than "Depends" or "Suggests") + */ +fragment *depname_frag(pkgCache::DepIterator dep) +{ + switch(dep->Type) + { + case pkgCache::Dep::Depends: return text_fragment(_("depends on"), + style_attrs_on(A_BOLD)); + case pkgCache::Dep::PreDepends: return text_fragment(_("pre-depends on"), + style_attrs_on(A_BOLD)); + case pkgCache::Dep::Suggests: return text_fragment(_("suggests")); + case pkgCache::Dep::Recommends: return text_fragment(_("recommends"), + style_attrs_on(A_BOLD)); + case pkgCache::Dep::Conflicts: return text_fragment(_("conflicts with"), + style_attrs_on(A_BOLD)); + case pkgCache::Dep::Replaces: return text_fragment(_("replaces")); + case pkgCache::Dep::Obsoletes: return text_fragment(_("obsoletes")); + } + + // Untranslated (internal error that will only happen if things go + // entirely wonky, and I want to be able to understand it if it + // appears) + return text_fragment("has an invalid dependency type!", get_style("Error")); +} + +/** Compare two packages by name */ +struct pkg_name_cmp +{ + bool operator()(pkgCache::PkgIterator P1, + pkgCache::PkgIterator P2) + { + return strcmp(P1.Name(), P2.Name())<0; + } +}; + +/** Compare two versions by memory location (useful for inserting into + * maps when the particular order is uninteresting) + */ +struct ver_ptr_cmp +{ + bool operator()(pkgCache::VerIterator V1, + pkgCache::VerIterator V2) + { + return less<void*>()(&*V1, &*V2); + } +}; + +/** Generate a fragment describing the packages providing a given package. + * + * \param pkg the package whose providers will be described. + * \param ignpkg a package which should not be shown as a provider. + * + * \param installed a filter: if \b true, only providees which are + * installed, or which will be installed, are shown; otherwise, all + * providees are shown. + */ +fragment *prvfrag(pkgCache::PkgIterator pkg, + pkgCache::PkgIterator ignpkg, + bool installed) +{ + // All packages providing the given package name are listed. + // + // For each package, we check whether both its current and + // candidate versions (if any) provide the name; if so, the + // package name is listed, colorized for the package. If only + // one provides the name, its version is listed, colorized for + // the version. + + vector<fragment*> fragments; + + set<pkgCache::VerIterator, ver_ptr_cmp> providing_versions; + set<pkgCache::PkgIterator, pkg_name_cmp> providing_packages; + + pkgCache::VerIterator candver=(*apt_cache_file)[pkg].CandidateVerIter(*apt_cache_file); + + for(pkgCache::PrvIterator P=pkg.ProvidesList(); + !P.end(); ++P) + { + if(P.OwnerPkg()!=ignpkg) + { + providing_versions.insert(P.OwnerVer()); + providing_packages.insert(P.OwnerPkg()); + } + } + + for(set<pkgCache::PkgIterator>::const_iterator P=providing_packages.begin(); + P!=providing_packages.end(); ++P) + { + bool provided_curr=false, provided_cand=false; + pkgCache::VerIterator currver=P->CurrentVer(); + pkgCache::VerIterator instver=(*apt_cache_file)[*P].InstVerIter(*apt_cache_file); + + if(installed) + { + if(instver.end() && currver.end()) + continue; + } +#if 0 + else + { + if(!(!currver.end() && instver.end())) + continue; + } +#endif + + if(!currver.end() && + providing_versions.find(currver)!=providing_versions.end()) + provided_curr=true; + if(!candver.end() && + providing_versions.find(candver)!=providing_versions.end()) + provided_cand=true; + + if((currver.end() || provided_cand) && + (currver.end() || provided_curr) && + (provided_cand || provided_curr)) + fragments.push_back(text_fragment(P->Name(), + pkg_item::pkg_style(*P, false))); + else if(provided_cand || provided_curr) + { + pkgCache::VerIterator &pv=provided_cand?candver:currver; + + fragments.push_back(style_fragment(fragf("%s %s", + P->Name(), + pv.VerStr()), + pkg_ver_item::ver_style(pv))); + } + else + // Bail and print EVERYTHING IN SIGHT...not very efficiently, either. + { + for(set<pkgCache::VerIterator>::const_iterator i=providing_versions.begin(); + i!=providing_versions.end(); ++i) + if(i->ParentPkg()==*P) + fragments.push_back(style_fragment(fragf("%s %s", + P->Name(), + i->VerStr()), + pkg_ver_item::ver_style(*i))); + } + } + + if(fragments.size()==0) + return fragf(""); + else + return fragf(_(" (provided by %F)"), + join_fragments(fragments, L", ")); +} + +/** Generate a fragment describing the given dependency iterator. */ +fragment *dep_singlefrag(pkgCache::PkgIterator pkg, + pkgCache::DepIterator dep) +{ + fragment *verfrag; + + pkgCache::VerIterator instver=(*apt_cache_file)[pkg].InstVerIter(*apt_cache_file); + + if(!dep.TargetVer()) + verfrag=text_fragment(""); + else + { + // Figure out the state of the versioned dep. + // + // Display it as "uninstalled" if it is not satisfied and won't + // be; "installed" if it is satisfied and will be; "removed" if + // it is satisfied but won't be, and "installing" if it is not + // satisfied and will be. + + style verstyle; + + bool matches_now=!pkg.CurrentVer().end() && + _system->VS->CheckDep(pkg.CurrentVer().VerStr(), + dep->CompareOp, + dep.TargetVer()); + bool matches_inst=!instver.end() && + _system->VS->CheckDep(instver.VerStr(), + dep->CompareOp, + dep.TargetVer()); + + if(matches_now) + { + if(matches_inst) + verstyle=style_attrs_on(A_BOLD); + else + verstyle=get_style("PkgToRemove"); + } + else + { + if(matches_inst) + verstyle=get_style("PkgToInstall"); + } + + verfrag=fragf(" (%s %F)", + dep.CompType(), + text_fragment(dep.TargetVer(), verstyle)); + } + + // Display a note if the package that is depended upon is not in + // main and is not the package being displayed. + string sec=dep.TargetPkg().Section()?dep.TargetPkg().Section():""; + if(sec.find('/')==sec.npos || dep.TargetPkg()==pkg) + sec=""; + else + sec=string(sec, 0, sec.find('/')); + + bool available=false; + + for(pkgCache::VerIterator i=dep.TargetPkg().VersionList(); !i.end(); i++) + if(_system->VS->CheckDep(i.VerStr(), dep->CompareOp, dep.TargetVer())) + available=true; + + for(pkgCache::PrvIterator i=dep.TargetPkg().ProvidesList(); !i.end(); i++) + if(_system->VS->CheckDep(i.ProvideVersion(), dep->CompareOp, dep.TargetVer())) + available=true; + + return fragf("%F%s%F%F%s", + text_fragment(dep.TargetPkg().Name(), + pkg_item::pkg_style(dep.TargetPkg(), false)), + sec.empty() || sec=="main"?"":(" ["+sec+']').c_str(), + verfrag, + prvfrag(dep.TargetPkg(), + dep.ParentPkg(), + dep->Type==pkgCache::Dep::Conflicts), + available?"":(string(" [")+_("UNAVAILABLE")+"]").c_str()); +} + +/** Generate a fragment describing the OR group that contains the + * given dependency, assuming that we are examining pkg. Assumes + * that duplicate OR dependencies are already dealt with in some way. + */ +fragment *dep_or_frag(pkgCache::PkgIterator pkg, + pkgCache::DepIterator dep) +{ + vector<fragment*> fragments; + + pkgCache::DepIterator or_begin, or_end; + + surrounding_or(dep, or_begin, or_end); + + for(pkgCache::DepIterator D=or_begin; D!=or_end; ++D) + if(D->CompareOp&pkgCache::Dep::Or) + fragments.push_back(fragf("%F | ", + dep_singlefrag(pkg, D))); + else + fragments.push_back(dep_singlefrag(pkg, D)); + + // Display a note if the source of the dependency is not in main and + // is not the package being displayed. + string sec=dep.ParentVer().Section()?dep.ParentVer().Section():""; + if(sec.find('/')==sec.npos || dep.ParentPkg()==pkg) + sec=""; + else + sec=string(sec, 0, sec.find('/')); + + return fragf(_("%F%s %F %F"), + text_fragment(dep.ParentPkg().Name(), + pkg_item::pkg_style(dep.ParentPkg(), false)), + sec.empty() || sec=="main"?"":(" ["+sec+']').c_str(), + depname_frag(dep), + sequence_fragment(fragments)); +} + +typedef pair<pkgCache::DepIterator, pkgCache::DepIterator> deppair; + +/** Return a fragment describing the reasons in the given vector. */ +fragment *reasonsfrag(pkgCache::PkgIterator pkg, set<reason> &reasons) +{ + vector<fragment*> fragments; + + // Used to exclude dependencies from the same OR that show up twice. + // If this is too expensive, switch to a set. + vector<deppair> seen_ors; + + for(set<reason>::const_iterator i=reasons.begin(); i!=reasons.end(); ++i) + { + pkgCache::DepIterator depbegin, depend; + surrounding_or(i->dep, depbegin, depend); + + bool seen=false; + for(vector<deppair>::const_iterator j=seen_ors.begin(); + j!=seen_ors.end(); ++j) + if(j->first==depbegin && j->second==depend) + seen=true; + + if(!seen) + { + seen_ors.push_back(deppair(depbegin, depend)); + + fragment *itemtext=dep_or_frag(pkg, i->dep); + + fragments.push_back(sequence_fragment(text_fragment(" * ", + get_style("Bullet")), + indentbox(0, 4, flowbox(itemtext)), + NULL)); + } + } + + return sequence_fragment(fragments); +} + +/** Return a fragment describing the lack of a package. */ +fragment *nopackage() +{ + return wrapbox(text_fragment(_("If you select a package, an explanation of its current state will appear in this space."))); +} + +fragment *reason_fragment(const pkgCache::PkgIterator &pkg) +{ + bool dummy; + + return reason_fragment(pkg, dummy); +} + +fragment *reason_fragment(const pkgCache::PkgIterator &pkg, bool &breakage) +{ + breakage=false; + + if(pkg.end()) + return nopackage(); + + set<reason> reasons; + + infer_reason(pkg, reasons); + + vector<fragment *> fragments; + pkg_action_state actionstate=find_pkg_state(pkg); + + aptitudeDepCache::StateCache &state=(*apt_cache_file)[pkg]; + aptitudeDepCache::aptitude_state &estate=(*apt_cache_file)->get_ext_state(pkg); + pkgCache::VerIterator candver=state.CandidateVerIter(*apt_cache_file); + pkgCache::VerIterator instver=state.InstVerIter(*apt_cache_file); + + // TODO: get non-lame text...some of these are just placeholders and + // should be reworded before release. + switch(actionstate) + { + case pkg_unused_remove: + fragments.push_back(wrapbox(fragf(_("%B%s%b was installed automatically; it is being removed because all of the packages which depend upon it are being removed:"), + pkg.Name()))); + break; + case pkg_auto_remove: + fragments.push_back(wrapbox(fragf(_("%B%s%b will be automatically removed because of dependency errors:"), + pkg.Name()))); + break; + case pkg_auto_install: + fragments.push_back(wrapbox(fragf(_("%B%s%b will be automatically installed to satisfy the following dependencies:"), + pkg.Name()))); + break; + case pkg_auto_hold: + { + if(candver.end() || candver==pkg.CurrentVer()) + fragments.push_back(wrapbox(fragf(_("%B%s%b cannot be upgraded now, but if it could be, it would still be held at version %B%s%b."), + pkg.Name(), pkg.CurrentVer().VerStr()))); + else + fragments.push_back(wrapbox(fragf(_("%B%s%b will not be upgraded to version %B%s%b, to avoid breaking the following dependencies:"), + pkg.Name(), + candver.VerStr()))); + break; + } + case pkg_unchanged: + if(!pkg.CurrentVer().end()) + { + if((*apt_cache_file)->is_held(pkg)) + fragments.push_back(wrapbox(fragf(_("%B%s%b cannot be upgraded now, but if it could be, it would still be held at version %B%s%b."), + pkg.Name(), pkg.CurrentVer().VerStr()))); + else + fragments.push_back(wrapbox(fragf(_("%B%s%b is currently installed."), + pkg.Name()))); + break; + } + else + { + fragments.push_back(wrapbox(fragf(_("%B%s%b is not currently installed."), + pkg.Name()))); + + break; + } + case pkg_broken: + breakage=true; + + fragments.push_back(wrapbox(fragf(_("Some dependencies of %B%s%b are not satisfied:"), + pkg.Name()))); + break; + case pkg_downgrade: + fragments.push_back(wrapbox(fragf(_("%B%s%b will be downgraded."), + pkg.Name()))); + break; + case pkg_hold: + { + if(estate.selection_state != pkgCache::State::Hold && + !candver.end() && candver.VerStr() == estate.forbidver) + fragments.push_back(wrapbox(fragf(_("%B%s%b will not be upgraded to the forbidden version %B%s%b."), + pkg.Name(), + candver.VerStr()))); + else + fragments.push_back(wrapbox(fragf(_("%B%s%b could be upgraded to version %B%s%b, but it is being held at version %B%s%b."), + pkg.Name(), + candver.VerStr(), + pkg.CurrentVer().VerStr()))); + } + break; + case pkg_reinstall: + fragments.push_back(wrapbox(fragf(_("%B%s%b will be re-installed."), + pkg.Name()))); + break; + case pkg_install: + fragments.push_back(wrapbox(fragf(_("%B%s%b will be installed."), + pkg.Name()))); + break; + case pkg_remove: + fragments.push_back(wrapbox(fragf(_("%B%s%b will be removed."), + pkg.Name()))); + break; + case pkg_upgrade: + { + fragments.push_back(wrapbox(fragf(_("%B%s%b will be upgraded from version %B%s%b to version %B%s%b."), + pkg.Name(), + pkg.CurrentVer().VerStr(), + candver.VerStr(), A_BOLD))); + } + break; + default: + // Another non-translatable internal error. + fragments.push_back(wrapbox(fragf("Internal error: Unknown package state for %s!", + pkg.Name()))); + } + + + if(!reasons.empty()) + fragments.push_back(sequence_fragment(newline_fragment(), + newline_fragment(), + NULL)); + + fragments.push_back(reasonsfrag(pkg, reasons)); + + reasons.clear(); + + infer_reverse_breakage(pkg, reasons); + + if(!reasons.empty()) + { + breakage=true; + + fragments.push_back(sequence_fragment(newline_fragment(), + newline_fragment(), + NULL)); + + // It will end up un-installed. + if(instver.end()) + { + if(state.Delete()) + fragments.push_back(wrapbox(fragf(_("The following packages depend on %B%s%b and will be broken by its removal:"), + pkg.Name()))); + else + fragments.push_back(wrapbox(fragf(_("The following packages depend on %B%s%b and are broken:"), + pkg.Name()))); + } + // It will end up installed. + else + { + if(pkg.CurrentVer().end()) + fragments.push_back(wrapbox(fragf(_("The following packages conflict with %B%s%b and will be broken by its installation:"), + pkg.Name()))); + else + // up/downgrade; could be either Depends or Conflicts + { + bool has_depends=false; + bool has_conflicts=false; + bool now_broken=false; + bool inst_broken=false; + + for(set<reason>::const_iterator i=reasons.begin(); + i!=reasons.end(); ++i) + { + if(i->dep->Type == pkgCache::Dep::Conflicts) + has_conflicts=true; + else + has_depends=true; + + // um, these are not entirely accurate :-( + // + // They show whether the whole dependency is ok, not + // whether this particular part is ok. (think ORed deps) + if(!((*apt_cache_file)[i->dep]&pkgDepCache::DepGNow)) + now_broken=true; + if(!((*apt_cache_file)[i->dep]&pkgDepCache::DepGInstall)) + inst_broken=true; + } + + if(state.Keep() || (now_broken && inst_broken)) + { + if(has_conflicts && has_depends) + { + if(state.Keep()) + fragments.push_back(wrapbox(fragf(_("The following packages depend on a version of %B%s%b other than the currently installed version of %B%s%b, or conflict with the currently installed version:"), + pkg.Name(), + pkg.CurrentVer().VerStr()))); + else + fragments.push_back(wrapbox(fragf(_("The following packages conflict with %B%s%b, or depend on a version of it which is not going to be installed."), + pkg.Name()))); + } + else if(has_conflicts) + fragments.push_back(wrapbox(fragf(_("The following packages conflict with %B%s%b:"), + pkg.Name()))); + else if(has_depends) + { + if(state.Keep()) + fragments.push_back(wrapbox(fragf(_("The following packages depend on a version of %B%s%b other than the currently installed version of %B%s%b:"), + pkg.Name(), + pkg.CurrentVer().VerStr()))); + else + fragments.push_back(wrapbox(fragf(_("The following packages depend on a version of %B%s%b which is not going to be installed."), + pkg.Name()))); + } + } + else + { + const char *actionname=(actionstate==pkg_upgrade)?_("upgraded"):_("downgraded"); + + if(has_conflicts && has_depends) + // I hope this is ok for the translators :-/ -- + // factoring out upgraded/downgraded in its two senses + // would be a royal pain even if gettext supported it. + fragments.push_back(wrapbox(fragf(_("The following packages depend on the currently installed version of %B%s%b (%B%s%b), or conflict with the version it will be %s to (%B%s%b), and will be broken if it is %s."), + pkg.Name(), + pkg.CurrentVer().VerStr(), + actionname, + instver.VerStr(), + actionname))); + else if(has_conflicts) + fragments.push_back(wrapbox(fragf(_("The following packages conflict with version %B%s%b of %B%s%b, and will be broken if it is %s."), + instver.VerStr(), + pkg.Name(), + actionname))); + else if(has_depends) + fragments.push_back(wrapbox(fragf(_("The following packages depend on version %B%s%b of %B%s%b, and will be broken if it is %s."), + pkg.CurrentVer().VerStr(), + pkg.Name(), + actionname))); + } + } + } + + fragments.push_back(sequence_fragment(newline_fragment(), + newline_fragment(), + NULL)); + + fragments.push_back(reasonsfrag(pkg, reasons)); + } + + return sequence_fragment(fragments); +} |