From 8e99b22c31eb47d0422e9a69e83dc99bb315ded8 Mon Sep 17 00:00:00 2001 From: David Kalnischkies Date: Wed, 29 Jun 2016 09:16:53 +0200 Subject: eipp: let apt make a plan, not make stuff plane Julian noticed on IRC that I fall victim to a lovely false friend by calling referring to a 'planer' all the time even through these are machines to e.g. remove splinters from woodwork ("make stuff plane"). The term I meant is written in german in this way (= with a single n) but in english there are two, aka: 'planner'. As that is unreleased code switching all instances without any transitional provisions. Also the reason why its skipped in changelog. Thanks: Julian Andres Klode Gbp-Dch: Ignore --- apt-pkg/edsp.cc | 28 +- apt-pkg/edsp.h | 2 +- apt-pkg/edsp/edspsystem.cc | 2 +- apt-pkg/init.cc | 2 +- apt-pkg/packagemanager.cc | 6 +- apt-pkg/packagemanager.h | 2 +- apt-private/private-cmndline.cc | 10 +- apt-private/private-cmndline.h | 2 +- apt-private/private-main.cc | 2 +- cmdline/apt-internal-planer.cc | 196 -------------- cmdline/apt-internal-planner.cc | 196 ++++++++++++++ cmdline/makefile | 6 +- debian/apt-doc.docs | 2 +- debian/apt-utils.dirs | 2 +- debian/apt.dirs | 2 +- debian/rules | 6 +- debian/tests/run-tests | 2 +- doc/external-installation-planer-protocol.txt | 298 --------------------- doc/external-installation-planner-protocol.txt | 298 +++++++++++++++++++++ test/integration/framework | 13 +- test/integration/test-00-commands-have-help | 2 +- .../test-external-installation-planer-protocol | 75 ------ .../test-external-installation-planner-protocol | 75 ++++++ 23 files changed, 615 insertions(+), 614 deletions(-) delete mode 100644 cmdline/apt-internal-planer.cc create mode 100644 cmdline/apt-internal-planner.cc delete mode 100644 doc/external-installation-planer-protocol.txt create mode 100644 doc/external-installation-planner-protocol.txt delete mode 100755 test/integration/test-external-installation-planer-protocol create mode 100755 test/integration/test-external-installation-planner-protocol diff --git a/apt-pkg/edsp.cc b/apt-pkg/edsp.cc index 49873f243..55bc0fcbd 100644 --- a/apt-pkg/edsp.cc +++ b/apt-pkg/edsp.cc @@ -1076,7 +1076,7 @@ bool EIPP::OrderInstall(char const * const solver, pkgPackageManager * const PM, { if (strcmp(solver, "internal") == 0) { - auto const dumpfile = _config->FindFile("Dir::Log::Planer"); + auto const dumpfile = _config->FindFile("Dir::Log::Planner"); if (dumpfile.empty()) return false; auto const dumpdir = flNotFile(dumpfile); @@ -1089,27 +1089,27 @@ bool EIPP::OrderInstall(char const * const solver, pkgPackageManager * const PM, } int solver_in, solver_out; - pid_t const solver_pid = ExecuteExternal("planer", solver, "Dir::Bin::Planers", &solver_in, &solver_out); + pid_t const solver_pid = ExecuteExternal("planner", solver, "Dir::Bin::Planners", &solver_in, &solver_out); if (solver_pid == 0) return false; FileFd output; if (output.OpenDescriptor(solver_in, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) - return _error->Errno("EIPP::OrderInstall", "Opening planer %s stdin on fd %d for writing failed", solver, solver_in); + return _error->Errno("EIPP::OrderInstall", "Opening planner %s stdin on fd %d for writing failed", solver, solver_in); bool Okay = output.Failed() == false; if (Progress != NULL) - Progress->OverallProgress(0, 100, 5, _("Execute external planer")); + Progress->OverallProgress(0, 100, 5, _("Execute external planner")); Okay &= EIPP::WriteRequest(PM->Cache, output, flags, Progress); if (Progress != NULL) - Progress->OverallProgress(5, 100, 20, _("Execute external planer")); + Progress->OverallProgress(5, 100, 20, _("Execute external planner")); Okay &= EIPP::WriteScenario(PM->Cache, output, Progress); output.Close(); if (Progress != NULL) - Progress->OverallProgress(25, 100, 75, _("Execute external planer")); + Progress->OverallProgress(25, 100, 75, _("Execute external planner")); - // we don't tell the external planers about boring things + // we don't tell the external planners about boring things for (auto Pkg = PM->Cache.PkgBegin(); Pkg.end() == false; ++Pkg) { if (Pkg->CurrentState == pkgCache::State::ConfigFiles && PM->Cache[Pkg].Purge() == true) @@ -1127,7 +1127,7 @@ bool EIPP::WriteRequest(pkgDepCache &Cache, FileFd &output, /*{{{*/ OpProgress * const Progress) { if (Progress != NULL) - Progress->SubProgress(Cache.Head().PackageCount, _("Send request to planer")); + Progress->SubProgress(Cache.Head().PackageCount, _("Send request to planner")); unsigned long p = 0; string del, inst, reinst; for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg, ++p) @@ -1164,7 +1164,7 @@ bool EIPP::WriteRequest(pkgDepCache &Cache, FileFd &output, /*{{{*/ WriteOkay(Okay, output, "Install:", inst, "\n"); if (reinst.empty() == false) WriteOkay(Okay, output, "ReInstall:", reinst, "\n"); - WriteOkay(Okay, output, "Planer: ", _config->Find("APT::Planer", "internal"), "\n"); + WriteOkay(Okay, output, "Planner: ", _config->Find("APT::Planner", "internal"), "\n"); if ((flags & Request::IMMEDIATE_CONFIGURATION_ALL) != 0) WriteOkay(Okay, output, "Immediate-Configuration: yes\n"); else if ((flags & Request::NO_IMMEDIATE_CONFIGURATION) != 0) @@ -1217,7 +1217,7 @@ template void forAllInterestingVersions(pkgDepCache &Cache, bool EIPP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress * const Progress) { if (Progress != NULL) - Progress->SubProgress(Cache.Head().PackageCount, _("Send scenario to planer")); + Progress->SubProgress(Cache.Head().PackageCount, _("Send scenario to planner")); unsigned long p = 0; bool Okay = output.Failed() == false; std::vector archs = APT::Configuration::getArchitectures(); @@ -1318,13 +1318,13 @@ bool EIPP::ReadResponse(int const input, pkgPackageManager * const PM, OpProgres } else if (section.Exists("Error") == true) { std::string msg = SubstVar(SubstVar(section.FindS("Message"), "\n .\n", "\n\n"), "\n ", "\n"); if (msg.empty() == true) { - msg = _("External planer failed without a proper error message"); + msg = _("External planner failed without a proper error message"); _error->Error("%s", msg.c_str()); } else - _error->Error("External planer failed with: %s", msg.substr(0,msg.find('\n')).c_str()); + _error->Error("External planner failed with: %s", msg.substr(0,msg.find('\n')).c_str()); if (Progress != NULL) Progress->Done(); - std::cerr << "The planer encountered an error of type: " << section.FindS("Error") << std::endl; + std::cerr << "The planner encountered an error of type: " << section.FindS("Error") << std::endl; std::cerr << "The following information might help you to understand what is wrong:" << std::endl; std::cerr << msg << std::endl << std::endl; return false; @@ -1396,7 +1396,7 @@ bool EIPP::ReadRequest(int const input, std::listSet("APT::Architecture", line); else if (LineStartsWithAndStrip(line, "Architectures:")) _config->Set("APT::Architectures", SubstVar(line, " ", ",")); - else if (LineStartsWithAndStrip(line, "Planer:")) + else if (LineStartsWithAndStrip(line, "Planner:")) ; // purely informational line else if (LineStartsWithAndStrip(line, "Immediate-Configuration:")) { diff --git a/apt-pkg/edsp.h b/apt-pkg/edsp.h index 4adfc95e9..60f8c2883 100644 --- a/apt-pkg/edsp.h +++ b/apt-pkg/edsp.h @@ -254,7 +254,7 @@ namespace EIPP /*{{{*/ APT_HIDDEN bool WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress * const Progress); - APT_HIDDEN bool OrderInstall(char const * const planer, pkgPackageManager * const PM, + APT_HIDDEN bool OrderInstall(char const * const planner, pkgPackageManager * const PM, unsigned int const version, OpProgress * const Progress); APT_HIDDEN bool ReadResponse(int const input, pkgPackageManager * const PM, OpProgress * const Progress); diff --git a/apt-pkg/edsp/edspsystem.cc b/apt-pkg/edsp/edspsystem.cc index b0e7b8a21..1ceb21a17 100644 --- a/apt-pkg/edsp/edspsystem.cc +++ b/apt-pkg/edsp/edspsystem.cc @@ -35,7 +35,7 @@ edspLikeSystem::edspLikeSystem(char const * const Label) : pkgSystem(Label, &deb edspSystem::edspSystem() : edspLikeSystem("Debian APT solver interface") { } -eippSystem::eippSystem() : edspLikeSystem("Debian APT planer interface") +eippSystem::eippSystem() : edspLikeSystem("Debian APT planner interface") { } /*}}}*/ diff --git a/apt-pkg/init.cc b/apt-pkg/init.cc index 0e8d9be8a..9543ca7e8 100644 --- a/apt-pkg/init.cc +++ b/apt-pkg/init.cc @@ -76,7 +76,7 @@ bool pkgInitConfig(Configuration &Cnf) Cnf.CndSet("Dir::Log","var/log/apt"); Cnf.CndSet("Dir::Log::Terminal","term.log"); Cnf.CndSet("Dir::Log::History","history.log"); - Cnf.CndSet("Dir::Log::Planer","eipp.log.xz"); + Cnf.CndSet("Dir::Log::Planner","eipp.log.xz"); Cnf.Set("Dir::Ignore-Files-Silently::", "~$"); Cnf.Set("Dir::Ignore-Files-Silently::", "\\.disabled$"); diff --git a/apt-pkg/packagemanager.cc b/apt-pkg/packagemanager.cc index 898e5d156..7fdd0393f 100644 --- a/apt-pkg/packagemanager.cc +++ b/apt-pkg/packagemanager.cc @@ -1037,7 +1037,7 @@ pkgPackageManager::OrderResult pkgPackageManager::OrderInstall() if (Debug == true) clog << "Beginning to order" << endl; - std::string const planer = _config->Find("APT::Planer", "internal"); + std::string const planner = _config->Find("APT::Planner", "internal"); unsigned int flags = 0; if (_config->FindB("APT::Immediate-Configure", true) == false) flags |= EIPP::Request::NO_IMMEDIATE_CONFIGURATION; @@ -1045,8 +1045,8 @@ pkgPackageManager::OrderResult pkgPackageManager::OrderInstall() flags |= EIPP::Request::IMMEDIATE_CONFIGURATION_ALL; else if (_config->FindB("APT::Force-LoopBreak", false)) flags |= EIPP::Request::ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS; - auto const ret = EIPP::OrderInstall(planer.c_str(), this, flags, nullptr); - if (planer != "internal") + auto const ret = EIPP::OrderInstall(planner.c_str(), this, flags, nullptr); + if (planner != "internal") return ret ? Completed : Failed; bool const ordering = diff --git a/apt-pkg/packagemanager.h b/apt-pkg/packagemanager.h index 145fe40a8..25b6ee7c9 100644 --- a/apt-pkg/packagemanager.h +++ b/apt-pkg/packagemanager.h @@ -117,7 +117,7 @@ class pkgPackageManager : protected pkgCache::Namespace // compat APT_DEPRECATED_MSG("Use APT::Progress::PackageManager subclass instead of fd") OrderResult DoInstall(int statusFd=-1); - friend bool EIPP::OrderInstall(char const * const planer, pkgPackageManager * const PM, + friend bool EIPP::OrderInstall(char const * const planner, pkgPackageManager * const PM, unsigned int const version, OpProgress * const Progress); friend bool EIPP::ReadResponse(int const input, pkgPackageManager * const PM, OpProgress * const Progress); diff --git a/apt-private/private-cmndline.cc b/apt-private/private-cmndline.cc index 8fb4a48dd..feb8d67a9 100644 --- a/apt-private/private-cmndline.cc +++ b/apt-private/private-cmndline.cc @@ -160,7 +160,7 @@ static bool addArgumentsAPTFTPArchive(std::vector &Args, char return true; } /*}}}*/ -static bool addArgumentsAPTInternalPlaner(std::vector &, char const * const)/*{{{*/ +static bool addArgumentsAPTInternalPlanner(std::vector &, char const * const)/*{{{*/ { return true; } @@ -191,7 +191,7 @@ static bool addArgumentsAPTGet(std::vector &Args, char const addArg(0, "auto-remove", "APT::Get::AutomaticRemove", 0); addArg(0, "reinstall", "APT::Get::ReInstall", 0); addArg(0, "solver", "APT::Solver", CommandLine::HasArg); - addArg(0, "planer", "APT::Planer", CommandLine::HasArg); + addArg(0, "planner", "APT::Planner", CommandLine::HasArg); if (CmdMatches("upgrade")) { addArg(0, "new-pkgs", "APT::Get::Upgrade-Allow-New", @@ -361,7 +361,7 @@ std::vector getCommandArgs(APT_CMD const Program, char const case APT_CMD::APT_EXTRACTTEMPLATES: addArgumentsAPTExtractTemplates(Args, Cmd); break; case APT_CMD::APT_FTPARCHIVE: addArgumentsAPTFTPArchive(Args, Cmd); break; case APT_CMD::APT_HELPER: addArgumentsAPTHelper(Args, Cmd); break; - case APT_CMD::APT_INTERNAL_PLANER: addArgumentsAPTInternalPlaner(Args, Cmd); break; + case APT_CMD::APT_INTERNAL_PLANNER: addArgumentsAPTInternalPlanner(Args, Cmd); break; case APT_CMD::APT_INTERNAL_SOLVER: addArgumentsAPTInternalSolver(Args, Cmd); break; case APT_CMD::APT_MARK: addArgumentsAPTMark(Args, Cmd); break; case APT_CMD::APT_SORTPKG: addArgumentsAPTSortPkgs(Args, Cmd); break; @@ -418,7 +418,7 @@ static bool ShowCommonHelp(APT_CMD const Binary, CommandLine &CmdL, std::vector< case APT_CMD::APT_FTPARCHIVE: cmd = "apt-ftparchive(1)"; break; case APT_CMD::APT_GET: cmd = "apt-get(8)"; break; case APT_CMD::APT_HELPER: cmd = nullptr; break; - case APT_CMD::APT_INTERNAL_PLANER: cmd = nullptr; break; + case APT_CMD::APT_INTERNAL_PLANNER: cmd = nullptr; break; case APT_CMD::APT_INTERNAL_SOLVER: cmd = nullptr; break; case APT_CMD::APT_MARK: cmd = "apt-mark(8)"; break; case APT_CMD::APT_SORTPKG: cmd = "apt-sortpkgs(1)"; break; @@ -426,7 +426,7 @@ static bool ShowCommonHelp(APT_CMD const Binary, CommandLine &CmdL, std::vector< if (cmd != nullptr) ioprintf(std::cout, _("See %s for more information about the available commands."), cmd); if (Binary != APT_CMD::APT_DUMP_SOLVER && Binary != APT_CMD::APT_INTERNAL_SOLVER && - Binary != APT_CMD::APT_INTERNAL_PLANER) + Binary != APT_CMD::APT_INTERNAL_PLANNER) std::cout << std::endl << _("Configuration options and syntax is detailed in apt.conf(5).\n" "Information about how to configure sources can be found in sources.list(5).\n" diff --git a/apt-private/private-cmndline.h b/apt-private/private-cmndline.h index 6882add0c..37fe2c91a 100644 --- a/apt-private/private-cmndline.h +++ b/apt-private/private-cmndline.h @@ -22,7 +22,7 @@ enum class APT_CMD { APT_MARK, APT_SORTPKG, APT_DUMP_SOLVER, - APT_INTERNAL_PLANER, + APT_INTERNAL_PLANNER, }; struct aptDispatchWithHelp { diff --git a/apt-private/private-main.cc b/apt-private/private-main.cc index 6405b71b8..52f35cfdc 100644 --- a/apt-private/private-main.cc +++ b/apt-private/private-main.cc @@ -38,7 +38,7 @@ void InitLocale(APT_CMD const binary) /*{{{*/ break; case APT_CMD::APT_EXTRACTTEMPLATES: case APT_CMD::APT_FTPARCHIVE: - case APT_CMD::APT_INTERNAL_PLANER: + case APT_CMD::APT_INTERNAL_PLANNER: case APT_CMD::APT_INTERNAL_SOLVER: case APT_CMD::APT_SORTPKG: textdomain("apt-utils"); diff --git a/cmdline/apt-internal-planer.cc b/cmdline/apt-internal-planer.cc deleted file mode 100644 index 2d5f44c6a..000000000 --- a/cmdline/apt-internal-planer.cc +++ /dev/null @@ -1,196 +0,0 @@ -// -*- mode: cpp; mode: fold -*- -// Description /*{{{*/ -/* ##################################################################### - - cover around the internal solver to be able to run it like an external - - ##################################################################### */ - /*}}}*/ -// Include Files /*{{{*/ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - /*}}}*/ - -static bool ShowHelp(CommandLine &) /*{{{*/ -{ - std::cout << - _("Usage: apt-internal-planer\n" - "\n" - "apt-internal-planer is an interface to use the current internal\n" - "installation planer for the APT family like an external one,\n" - "for debugging or the like.\n"); - return true; -} - /*}}}*/ -APT_NORETURN static void DIE(std::string const &message) { /*{{{*/ - std::cerr << "ERROR: " << message << std::endl; - _error->DumpErrors(std::cerr); - exit(EXIT_FAILURE); -} - /*}}}*/ -static std::vector GetCommands() /*{{{*/ -{ - return {}; -} - /*}}}*/ -class PMOutput: public pkgPackageManager /*{{{*/ -{ - FileFd &output; - bool const Debug; - -protected: - virtual bool Install(PkgIterator Pkg,std::string) APT_OVERRIDE - { - //std::cerr << "INSTALL: " << APT::PrettyPkg(&Cache, Pkg) << std::endl; - return EDSP::WriteSolutionStanza(output, "Unpack", Cache[Pkg].InstVerIter(Cache)); - } - virtual bool Configure(PkgIterator Pkg) APT_OVERRIDE - { - //std::cerr << "CONFIGURE: " << APT::PrettyPkg(&Cache, Pkg) << " " << std::endl; - return EDSP::WriteSolutionStanza(output, "Configure", Cache[Pkg].InstVerIter(Cache)); - } - virtual bool Remove(PkgIterator Pkg,bool) APT_OVERRIDE - { - //std::cerr << "REMOVE: " << APT::PrettyPkg(&Cache, Pkg) << " " << std::endl; - return EDSP::WriteSolutionStanza(output, "Remove", Pkg.CurrentVer()); - } -public: - PMOutput(pkgDepCache *Cache, FileFd &file) : pkgPackageManager(Cache), output(file), - Debug(_config->FindB("Debug::EDSP::WriteSolution", false)) - {} - - bool ApplyRequest(std::list> const &actions) - { - for (auto && a: actions) - { - auto const Pkg = Cache.FindPkg(a.first); - if (unlikely(Pkg.end() == true)) - continue; - switch (a.second) - { - case EIPP::PKG_ACTION::NOOP: - break; - case EIPP::PKG_ACTION::INSTALL: - case EIPP::PKG_ACTION::REINSTALL: - FileNames[Pkg->ID] = "EIPP"; - break; - case EIPP::PKG_ACTION::REMOVE: - break; - } - } - return true; - } -}; - /*}}}*/ -int main(int argc,const char *argv[]) /*{{{*/ -{ - // we really don't need anything - DropPrivileges(); - - CommandLine CmdL; - ParseCommandLine(CmdL, APT_CMD::APT_INTERNAL_PLANER, &_config, NULL, argc, argv, &ShowHelp, &GetCommands); - - // Deal with stdout not being a tty - if (!isatty(STDOUT_FILENO) && _config->FindI("quiet", -1) == -1) - _config->Set("quiet","1"); - - if (_config->FindI("quiet", 0) < 1) - _config->Set("Debug::EIPP::WriteSolution", true); - - _config->Set("APT::System", "Debian APT planer interface"); - _config->Set("APT::Planer", "internal"); - _config->Set("eipp::scenario", "/nonexistent/stdin"); - FileFd output; - if (output.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) - DIE("stdout couldn't be opened"); - int const input = STDIN_FILENO; - SetNonBlock(input, false); - - EDSP::WriteProgress(0, "Start up planer…", output); - - if (pkgInitSystem(*_config,_system) == false) - DIE("System could not be initialized!"); - - EDSP::WriteProgress(1, "Read request…", output); - - if (WaitFd(input, false, 5) == false) - DIE("WAIT timed out in the planer"); - - std::list> actions; - unsigned int flags; - if (EIPP::ReadRequest(input, actions, flags) == false) - DIE("Parsing the request failed!"); - _config->Set("APT::Immediate-Configure", (flags & EIPP::Request::NO_IMMEDIATE_CONFIGURATION) == 0); - _config->Set("APT::Immediate-Configure-All", (flags & EIPP::Request::IMMEDIATE_CONFIGURATION_ALL) != 0); - _config->Set("APT::Force-LoopBreak", (flags & EIPP::Request::ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS) != 0); - - EDSP::WriteProgress(5, "Read scenario…", output); - - pkgCacheFile CacheFile; - if (CacheFile.Open(NULL, false) == false) - DIE("Failed to open CacheFile!"); - - EDSP::WriteProgress(50, "Apply request on scenario…", output); - - if (EIPP::ApplyRequest(actions, CacheFile) == false) - DIE("Failed to apply request to depcache!"); - - EDSP::WriteProgress(60, "Call orderinstall on current scenario…", output); - - //_config->Set("Debug::pkgOrderList", true); - //_config->Set("Debug::pkgPackageManager", true); - PMOutput PM(CacheFile, output); - if (PM.ApplyRequest(actions) == false) - DIE("Failed to apply request to packagemanager!"); - pkgPackageManager::OrderResult const Res = PM.DoInstallPreFork(); - std::ostringstream broken; - switch (Res) - { - case pkgPackageManager::Completed: - EDSP::WriteProgress(100, "Done", output); - break; - case pkgPackageManager::Incomplete: - broken << "Planer could only incompletely plan an installation order!" << std::endl; - _error->DumpErrors(broken, GlobalError::DEBUG); - EDSP::WriteError("pm-incomplete", broken.str(), output); - break; - case pkgPackageManager::Failed: - broken << "Planer failed to find an installation order!" << std::endl; - _error->DumpErrors(broken, GlobalError::DEBUG); - EDSP::WriteError("pm-failed", broken.str(), output); - break; - } - - return DispatchCommandLine(CmdL, {}); -} - /*}}}*/ diff --git a/cmdline/apt-internal-planner.cc b/cmdline/apt-internal-planner.cc new file mode 100644 index 000000000..1f74aa78f --- /dev/null +++ b/cmdline/apt-internal-planner.cc @@ -0,0 +1,196 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ##################################################################### + + cover around the internal solver to be able to run it like an external + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + /*}}}*/ + +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-internal-planner\n" + "\n" + "apt-internal-planner is an interface to use the current internal\n" + "installation planner for the APT family like an external one,\n" + "for debugging or the like.\n"); + return true; +} + /*}}}*/ +APT_NORETURN static void DIE(std::string const &message) { /*{{{*/ + std::cerr << "ERROR: " << message << std::endl; + _error->DumpErrors(std::cerr); + exit(EXIT_FAILURE); +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return {}; +} + /*}}}*/ +class PMOutput: public pkgPackageManager /*{{{*/ +{ + FileFd &output; + bool const Debug; + +protected: + virtual bool Install(PkgIterator Pkg,std::string) APT_OVERRIDE + { + //std::cerr << "INSTALL: " << APT::PrettyPkg(&Cache, Pkg) << std::endl; + return EDSP::WriteSolutionStanza(output, "Unpack", Cache[Pkg].InstVerIter(Cache)); + } + virtual bool Configure(PkgIterator Pkg) APT_OVERRIDE + { + //std::cerr << "CONFIGURE: " << APT::PrettyPkg(&Cache, Pkg) << " " << std::endl; + return EDSP::WriteSolutionStanza(output, "Configure", Cache[Pkg].InstVerIter(Cache)); + } + virtual bool Remove(PkgIterator Pkg,bool) APT_OVERRIDE + { + //std::cerr << "REMOVE: " << APT::PrettyPkg(&Cache, Pkg) << " " << std::endl; + return EDSP::WriteSolutionStanza(output, "Remove", Pkg.CurrentVer()); + } +public: + PMOutput(pkgDepCache *Cache, FileFd &file) : pkgPackageManager(Cache), output(file), + Debug(_config->FindB("Debug::EDSP::WriteSolution", false)) + {} + + bool ApplyRequest(std::list> const &actions) + { + for (auto && a: actions) + { + auto const Pkg = Cache.FindPkg(a.first); + if (unlikely(Pkg.end() == true)) + continue; + switch (a.second) + { + case EIPP::PKG_ACTION::NOOP: + break; + case EIPP::PKG_ACTION::INSTALL: + case EIPP::PKG_ACTION::REINSTALL: + FileNames[Pkg->ID] = "EIPP"; + break; + case EIPP::PKG_ACTION::REMOVE: + break; + } + } + return true; + } +}; + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + // we really don't need anything + DropPrivileges(); + + CommandLine CmdL; + ParseCommandLine(CmdL, APT_CMD::APT_INTERNAL_PLANNER, &_config, NULL, argc, argv, &ShowHelp, &GetCommands); + + // Deal with stdout not being a tty + if (!isatty(STDOUT_FILENO) && _config->FindI("quiet", -1) == -1) + _config->Set("quiet","1"); + + if (_config->FindI("quiet", 0) < 1) + _config->Set("Debug::EIPP::WriteSolution", true); + + _config->Set("APT::System", "Debian APT planner interface"); + _config->Set("APT::Planner", "internal"); + _config->Set("eipp::scenario", "/nonexistent/stdin"); + FileFd output; + if (output.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) + DIE("stdout couldn't be opened"); + int const input = STDIN_FILENO; + SetNonBlock(input, false); + + EDSP::WriteProgress(0, "Start up planner…", output); + + if (pkgInitSystem(*_config,_system) == false) + DIE("System could not be initialized!"); + + EDSP::WriteProgress(1, "Read request…", output); + + if (WaitFd(input, false, 5) == false) + DIE("WAIT timed out in the planner"); + + std::list> actions; + unsigned int flags; + if (EIPP::ReadRequest(input, actions, flags) == false) + DIE("Parsing the request failed!"); + _config->Set("APT::Immediate-Configure", (flags & EIPP::Request::NO_IMMEDIATE_CONFIGURATION) == 0); + _config->Set("APT::Immediate-Configure-All", (flags & EIPP::Request::IMMEDIATE_CONFIGURATION_ALL) != 0); + _config->Set("APT::Force-LoopBreak", (flags & EIPP::Request::ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS) != 0); + + EDSP::WriteProgress(5, "Read scenario…", output); + + pkgCacheFile CacheFile; + if (CacheFile.Open(NULL, false) == false) + DIE("Failed to open CacheFile!"); + + EDSP::WriteProgress(50, "Apply request on scenario…", output); + + if (EIPP::ApplyRequest(actions, CacheFile) == false) + DIE("Failed to apply request to depcache!"); + + EDSP::WriteProgress(60, "Call orderinstall on current scenario…", output); + + //_config->Set("Debug::pkgOrderList", true); + //_config->Set("Debug::pkgPackageManager", true); + PMOutput PM(CacheFile, output); + if (PM.ApplyRequest(actions) == false) + DIE("Failed to apply request to packagemanager!"); + pkgPackageManager::OrderResult const Res = PM.DoInstallPreFork(); + std::ostringstream broken; + switch (Res) + { + case pkgPackageManager::Completed: + EDSP::WriteProgress(100, "Done", output); + break; + case pkgPackageManager::Incomplete: + broken << "Planner could only incompletely plan an installation order!" << std::endl; + _error->DumpErrors(broken, GlobalError::DEBUG); + EDSP::WriteError("pm-incomplete", broken.str(), output); + break; + case pkgPackageManager::Failed: + broken << "Planner failed to find an installation order!" << std::endl; + _error->DumpErrors(broken, GlobalError::DEBUG); + EDSP::WriteError("pm-failed", broken.str(), output); + break; + } + + return DispatchCommandLine(CmdL, {}); +} + /*}}}*/ diff --git a/cmdline/makefile b/cmdline/makefile index ee50224e1..8b79ce05f 100644 --- a/cmdline/makefile +++ b/cmdline/makefile @@ -79,17 +79,17 @@ LIB_MAKES = apt-pkg/makefile apt-inst/makefile apt-private/makefile SOURCE = apt-extracttemplates.cc include $(PROGRAM_H) -# The internal solver/planer acting as an external +# The internal solver/planner acting as an external PROGRAM=apt-internal-solver SLIBS = -lapt-pkg -lapt-private $(INTLLIBS) LIB_MAKES = apt-pkg/makefile apt-private/makefile SOURCE = apt-internal-solver.cc include $(PROGRAM_H) -PROGRAM=apt-internal-planer +PROGRAM=apt-internal-planner SLIBS = -lapt-pkg -lapt-private $(INTLLIBS) LIB_MAKES = apt-pkg/makefile apt-private/makefile -SOURCE = apt-internal-planer.cc +SOURCE = apt-internal-planner.cc include $(PROGRAM_H) # This just dumps out the state diff --git a/debian/apt-doc.docs b/debian/apt-doc.docs index dd2a1eee8..2058000c1 100644 --- a/debian/apt-doc.docs +++ b/debian/apt-doc.docs @@ -2,5 +2,5 @@ build/docs/guide* build/docs/offline* README.progress-reporting doc/external-dependency-solver-protocol.txt -doc/external-installation-planer-protocol.txt +doc/external-installation-planner-protocol.txt doc/acquire-additional-files.txt diff --git a/debian/apt-utils.dirs b/debian/apt-utils.dirs index cb3a9ebef..de7fa756e 100644 --- a/debian/apt-utils.dirs +++ b/debian/apt-utils.dirs @@ -1,3 +1,3 @@ usr/lib/apt/solvers -usr/lib/apt/planers +usr/lib/apt/planners usr/bin diff --git a/debian/apt.dirs b/debian/apt.dirs index 7486a7b69..7d46801df 100644 --- a/debian/apt.dirs +++ b/debian/apt.dirs @@ -1,7 +1,7 @@ usr/bin usr/lib/apt/methods usr/lib/apt/solvers -usr/lib/apt/planers +usr/lib/apt/planners usr/lib/dpkg/methods/apt etc/apt etc/apt/apt.conf.d diff --git a/debian/rules b/debian/rules index 43e979dc8..18b3f34f3 100755 --- a/debian/rules +++ b/debian/rules @@ -189,9 +189,9 @@ apt: build-binary build-manpages debian/apt.install dh_install -p$@ --sourcedir=$(BLD) # Remove the bits that are in apt-utils - rm $(addprefix debian/$@/usr/bin/apt-,$(APT_UTILS) dump-solver internal-solver internal-planer) + rm $(addprefix debian/$@/usr/bin/apt-,$(APT_UTILS) dump-solver internal-solver internal-planner) cp $(BLD)/bin/apt-dump-solver debian/$@/usr/lib/apt/solvers/dump - ln -s ../solvers/dump debian/$@/usr/lib/apt/planers/dump + ln -s ../solvers/dump debian/$@/usr/lib/apt/planners/dump # https has its own package rm debian/$@/usr/lib/apt/methods/https @@ -253,7 +253,7 @@ apt-utils: build-binary build-manpages cp $(addprefix $(BLD)/bin/apt-,$(APT_UTILS)) debian/$@/usr/bin/ cp $(BLD)/bin/apt-internal-solver debian/$@/usr/lib/apt/solvers/apt - cp $(BLD)/bin/apt-internal-planer debian/$@/usr/lib/apt/planers/apt + cp $(BLD)/bin/apt-internal-planner debian/$@/usr/lib/apt/planners/apt dh_install -p$@ --sourcedir=$(BLD) dh_link -p$@ diff --git a/debian/tests/run-tests b/debian/tests/run-tests index 11139e3cd..e33992b28 100644 --- a/debian/tests/run-tests +++ b/debian/tests/run-tests @@ -17,7 +17,7 @@ APT_INTEGRATION_TESTS_METHODS_DIR=/usr/lib/apt/methods \ APT_INTEGRATION_TESTS_LIBEXEC_DIR=/usr/lib/apt/ \ APT_INTEGRATION_TESTS_INTERNAL_SOLVER=/usr/lib/apt/solvers/apt \ APT_INTEGRATION_TESTS_DUMP_SOLVER=/usr/lib/apt/solvers/dump \ -APT_INTEGRATION_TESTS_INTERNAL_PLANER=/usr/lib/apt/planers/apt \ +APT_INTEGRATION_TESTS_INTERNAL_PLANNER=/usr/lib/apt/planners/apt \ APT_INTEGRATION_TESTS_BUILD_DIR=/usr/bin \ APT_INTEGRATION_TESTS_LIBRARY_PATH=/dev/null/does/not/exist \ ./test/integration/run-tests -q diff --git a/doc/external-installation-planer-protocol.txt b/doc/external-installation-planer-protocol.txt deleted file mode 100644 index 916982ca1..000000000 --- a/doc/external-installation-planer-protocol.txt +++ /dev/null @@ -1,298 +0,0 @@ -# APT External Installation Planer Protocol (EIPP) - version 0.1 - -This document describes the communication protocol between APT and -external installation planer. The protocol is called APT EIPP, for "APT -External Installation Planer Protocol". - - -## Terminology - -In the following we use the term **architecture qualified package name** -(or *arch-qualified package names* for short) to refer to package -identifiers of the form "package:arch" where "package" is a package name -and "arch" a dpkg architecture. - - -## Components - -- **APT**: we know this one. -- APT is equipped with its own **internal planer** for the order of - package installation (and removal) which is identified by the string - `internal`. -- **External planer**: an *external* software component able to plan an - installation on behalf of APT. - -At each interaction with APT, a single planer is in use. When there is -a total of 2 or more planers, internals or externals, the user can -choose which one to use. - -Each planer is identified by an unique string, the **planer name**. -Planer names must be formed using only alphanumeric ASCII characters, -dashes, and underscores; planer names must start with a lowercase ASCII -letter. The special name `internal` denotes APT's internal planer, is -reserved, and cannot be used by external planers. - - -## Installation - -Each external planer is installed as a file under Dir::Bin::Planers (see -below), which defaults to `/usr/lib/apt/planers`. We will assume in the -remainder of this section that such a default value is in effect. - -The naming scheme is `/usr/lib/apt/planers/NAME`, where `NAME` is the -name of the external planer. - -Each file under `/usr/lib/apt/planers` corresponding to an external -planer must be executable. - -No non-planer files must be installed under `/usr/lib/apt/planers`, so -that an index of available external planers can be obtained by listing -the content of that directory. - - -## Configuration - -Several APT options can be used to affect installation planing in APT. -An overview of them is given below. Please refer to proper APT -configuration documentation for more, and more up to date, information. - -- **APT::Planer**: the name of the planer to be used for dependency - solving. Defaults to `internal` - -- **Dir::Bin::Planers**: absolute path of the directory where to look - for external solvers. Defaults to `/usr/lib/apt/planers`. - - -## Protocol - -When configured to use an external planer, APT will resort to it to -decide in which order packages should be installed, configured and -removed. - -The interaction happens **in batch**: APT will invoke the external -planer passing the current status of (half-)installed packages and of -packages which should be installed, as well as a request denoting the -packages to install, reinstall, remove and purge. The external planer -will compute a valid plan of when and how to call the low-level package -manager (like dpkg) with each package to satisfy the request. - -External planers are invoked by executing them. Communications happens -via the file descriptors: **stdin** (standard input) and **stdout** -(standard output). stderr is not used by the EIPP protocol. Planers can -therefore use stderr to dump debugging information that could be -inspected separately. - -After invocation, the protocol passes through a sequence of phases: - -1. APT invokes the external planer -2. APT send to the planer an installation planer **scenario** -3. The planer calculates the order. During this phase the planer may - send, repeatedly, **progress** information to APT. -4. The planer sends back to APT an **answer**, i.e. either a *solution* - or an *error* report. -5. The external planer exits - - -### Scenario - -A scenario is a text file encoded in a format very similar to the "Deb -822" format (AKA "the format used by Debian `Packages` files"). A -scenario consists of two distinct parts: a **request** and a **package -universe**, occurring in that order. The request consists of a single -Deb 822 stanza, while the package universe consists of several such -stanzas. All stanzas occurring in a scenario are separated by an empty -line. - - -#### Request - -Within an installation planer scenario, a request represents the action -on packages requested by the user explicitly as well as potentially -additions calculated by a dependency resolver which the user has -accepted. - -An installation planer is not allowed to suggest the modification of -package states (e.g. removing additional packages) even if it can't -calculate a solution otherwise – the planer must error out in such -a case. An exception is made for scenarios which contain packages which -aren't completely installed (like half-installed or trigger-awaiting): -Solvers are free to move these packages to a fully installed state (but -are still forbidden to remove them). - -A request is a single Deb 822 stanza opened by a mandatory Request field -and followed by a mixture of action, preference, and global -configuration fields. - -The value of the **Request:** field is a string describing the EIPP -protocol which will be used to communicate and especially which answers -APT will understand. At present, the string must be `EIPP 0.1`. Request -fields are mainly used to identify the beginning of a request stanza; -their actual values are otherwise not used by the EIPP protocol. - -The following **configuration fields** are supported in request stanzas: - -- **Architecture:** (mandatory) The name of the *native* architecture on - the user machine (see also: `dpkg --print-architecture`) - -- **Architectures:** (optional, defaults to the native architecture) A - space separated list of *all* architectures known to APT (this is - roughly equivalent to the union of `dpkg --print-architecture` and - `dpkg --print-foreign-architectures`) - -The following **action fields** are supported in request stanzas: - -- **Install:** (optional, defaults to the empty string) A space - separated list of arch-qualified package names, with *no version - attached*, to install. This field denotes a list of packages that the - user wants to install, usually via an APT `install` request. - -- **Remove:** (optional, defaults to the empty string) Same syntax of - Install. This field denotes a list of packages that the user wants to - remove, usually via APT `remove` or `purge` requests. - -- **ReInstall:** (optional, defaults to the empty string) Same syntax of - Install. This field denotes a list of packages which are installed, - but should be reinstalled again e.g. because files shipped by that - package were removed or corrupted accidentally, usually requested via - an APT `install` request with the `--reinstall` flag. - -The following **preference fields** are supported in request stanzas: - -- **Planer:** (optional, defaults to the empty string) a purely - informational string specifying to which planer this request was send - initially. - -- **Immediate-Configuration:** (option, unset by default) A boolean - value defining if the planer should try to configure all packages as - quickly as possible (true) or shouldn't perform any kind of immediate - configuration at all (false). If not explicitly set with this field - the planer is free to pick either mode or implementing e.g. a mode - which configures only packages immediately if they are flagged as - `Essential` (or are dependencies of packages marked as `Essential`). - -- **Allow-Temporary-Remove-of-Essentials** (optional, defaults to `no`). - A boolean value allowing the planer (if set to yes) to temporarily - remove an essential package. Associated with the APT::Force-LoopBreak - configuration option its main use is highlighting that planers who do - temporary removes must take special care in terms of essentials. Legit - uses of this option by users is very uncommon, traditionally - a situation in which it is needed indicates a packaging error. - - -#### Package universe - -A package universe is a list of Deb 822 stanzas, one per package, called -**package stanzas**. Each package stanzas starts with a Package -field. The following fields are supported in package stanzas: - -- The fields Package, Version, Architecture (all mandatory) and - Multi-Arch, Pre-Depends, Depends, Conflicts, Breaks, Essential - (optional) as they are contained in the dpkg database (see the manpage - `dpkg-query (1)`). - -- **Status:** (optional, defaults to `uninstalled`). Allowed values are - the "package status" names as listed in `dpkg-query (1)` and visible - e.g. in the dpkg database as the second value in the space separated - list of values in the Status field there. In other words: Neither - desired action nor error flags are present in this field in EIPP! - -- **APT-ID:** (mandatory). Unique package identifier, according to APT. - - -### Answer - -An answer from the external planer to APT is either a *solution* or an -*error*. - -The following invariant on **exit codes** must hold true. When the -external planer is *able to find a solution*, it will write the solution -to standard output and then exit with an exit code of 0. When the -external planer is *unable to find a solution* (and is aware of that), -it will write an error to standard output and then exit with an exit -code of 0. An exit code other than 0 will be interpreted as a planer -crash with no meaningful error about dependency resolution to convey to -the user. - - -#### Solution - -A solution is a list of Deb 822 stanzas. Each of them could be an: - -- unpack stanza to cause the extraction of a package to the disk - -- configure stanza to cause an unpacked package to be configured and - therefore the installation to be completed - -- remove stanza to cause the removal of a package from the system - -An **unpack stanza** starts with an Unpack field and supports the -following fields: - -- **Unpack:** (mandatory). The value is a package identifier, - referencing one of the package stanzas of the package universe via its - APT-ID field. - -- All fields supported by package stanzas. - -**Configure** and **Remove stanzas** require and support the same -fields with the exception of the Unpack field which is replaced in -these instances with the Configure or Remove field respectively. - -The order of the stanzas is significant (unlike in the EDSP protocol), -with the first stanza being the first performed action. If multiple -stanzas of the same type appear in direct succession the order in such -a set isn't significant through. - -The solution needs to be valid (it is not allowed to configure a package -before it was unpacked, dependency relations must be satisfied, …), but -they don't need to be complete: A planer can and should expect that any -package which wasn't explicitly configured will be configured at the end -automatically. That also means through that a planer is not allowed to -produce a solution in which a package remains unconfigured. - -In terms of expressivity, all stanzas can carry one single field each, as -APT-IDs are enough to pinpoint packages to be installed/removed. Nonetheless, -for protocol readability, it is recommended that planers either add -unconditionally the fields Package, Version, and Architecture to all -install/remove stanzas or, alternatively, that they support a `--verbose` -command line flag that explicitly enables the output of those fields in -solutions. - -#### Error - -An error is a single Deb 822 stanza, starting the field Error. The -following fields are supported in error stanzas: - -- **Error:** (mandatory). The value of this field is ignored, although - it should be a unique error identifier, such as a UUID. - -- **Message:** (mandatory). The value of this field is a text string, - meant to be read by humans, that explains the cause of the planer - error. Message fields might be multi-line, like the Description field - in the dpkg database. The first line conveys a short message, which - can be explained in more details using subsequent lines. - - -### Progress - -During dependency solving, an external planer may send progress -information to APT using **progress stanzas**. A progress stanza starts -with the Progress field and might contain the following fields: - -- **Progress:** (mandatory). The value of this field is a date and time - timestamp, in RFC 2822 format. The timestamp provides a time - annotation for the progress report. - -- **Percentage:** (optional). An integer from 0 to 100, representing the - completion of the installation planning process, as declared by the - planer. - -- **Message:** (optional). A textual message, meant to be read by the - APT user, telling what is going on within the installation planer - (e.g. the current phase of planning, as declared by the planer). - - -# Future extensions - -Potential future extensions to this protocol are to be discussed on -deity@lists.debian.org. diff --git a/doc/external-installation-planner-protocol.txt b/doc/external-installation-planner-protocol.txt new file mode 100644 index 000000000..319d139c6 --- /dev/null +++ b/doc/external-installation-planner-protocol.txt @@ -0,0 +1,298 @@ +# APT External Installation Planner Protocol (EIPP) - version 0.1 + +This document describes the communication protocol between APT and +external installation planner. The protocol is called APT EIPP, for "APT +External Installation Planner Protocol". + + +## Terminology + +In the following we use the term **architecture qualified package name** +(or *arch-qualified package names* for short) to refer to package +identifiers of the form "package:arch" where "package" is a package name +and "arch" a dpkg architecture. + + +## Components + +- **APT**: we know this one. +- APT is equipped with its own **internal planner** for the order of + package installation (and removal) which is identified by the string + `internal`. +- **External planner**: an *external* software component able to plan an + installation on behalf of APT. + +At each interaction with APT, a single planner is in use. When there is +a total of 2 or more planners, internals or externals, the user can +choose which one to use. + +Each planner is identified by an unique string, the **planner name**. +Planner names must be formed using only alphanumeric ASCII characters, +dashes, and underscores; planner names must start with a lowercase ASCII +letter. The special name `internal` denotes APT's internal planner, is +reserved, and cannot be used by external planners. + + +## Installation + +Each external planner is installed as a file under Dir::Bin::Planners +(see below), which defaults to `/usr/lib/apt/planners`. We will assume +in the remainder of this section that such a default value is in effect. + +The naming scheme is `/usr/lib/apt/planners/NAME`, where `NAME` is the +name of the external planner. + +Each file under `/usr/lib/apt/planners` corresponding to an external +planner must be executable. + +No non-planner files must be installed under `/usr/lib/apt/planners`, so +that an index of available external planners can be obtained by listing +the content of that directory. + + +## Configuration + +Several APT options can be used to affect installation planing in APT. +An overview of them is given below. Please refer to proper APT +configuration documentation for more, and more up to date, information. + +- **APT::Planner**: the name of the planner to be used for dependency + solving. Defaults to `internal` + +- **Dir::Bin::Planners**: absolute path of the directory where to look + for external solvers. Defaults to `/usr/lib/apt/planners`. + + +## Protocol + +When configured to use an external planner, APT will resort to it to +decide in which order packages should be installed, configured and +removed. + +The interaction happens **in batch**: APT will invoke the external +planner passing the current status of (half-)installed packages and of +packages which should be installed, as well as a request denoting the +packages to install, reinstall, remove and purge. The external planner +will compute a valid plan of when and how to call the low-level package +manager (like dpkg) with each package to satisfy the request. + +External planners are invoked by executing them. Communications happens +via the file descriptors: **stdin** (standard input) and **stdout** +(standard output). stderr is not used by the EIPP protocol. Planners can +therefore use stderr to dump debugging information that could be +inspected separately. + +After invocation, the protocol passes through a sequence of phases: + +1. APT invokes the external planner +2. APT send to the planner an installation planner **scenario** +3. The planner calculates the order. During this phase the planner may + send, repeatedly, **progress** information to APT. +4. The planner sends back to APT an **answer**, i.e. either a *solution* + or an *error* report. +5. The external planner exits + + +### Scenario + +A scenario is a text file encoded in a format very similar to the "Deb +822" format (AKA "the format used by Debian `Packages` files"). A +scenario consists of two distinct parts: a **request** and a **package +universe**, occurring in that order. The request consists of a single +Deb 822 stanza, while the package universe consists of several such +stanzas. All stanzas occurring in a scenario are separated by an empty +line. + + +#### Request + +Within an installation planner scenario, a request represents the action +on packages requested by the user explicitly as well as potentially +additions calculated by a dependency resolver which the user has +accepted. + +An installation planner is not allowed to suggest the modification of +package states (e.g. removing additional packages) even if it can't +calculate a solution otherwise – the planner must error out in such +a case. An exception is made for scenarios which contain packages which +aren't completely installed (like half-installed or trigger-awaiting): +Solvers are free to move these packages to a fully installed state (but +are still forbidden to remove them). + +A request is a single Deb 822 stanza opened by a mandatory Request field +and followed by a mixture of action, preference, and global +configuration fields. + +The value of the **Request:** field is a string describing the EIPP +protocol which will be used to communicate and especially which answers +APT will understand. At present, the string must be `EIPP 0.1`. Request +fields are mainly used to identify the beginning of a request stanza; +their actual values are otherwise not used by the EIPP protocol. + +The following **configuration fields** are supported in request stanzas: + +- **Architecture:** (mandatory) The name of the *native* architecture on + the user machine (see also: `dpkg --print-architecture`) + +- **Architectures:** (optional, defaults to the native architecture) A + space separated list of *all* architectures known to APT (this is + roughly equivalent to the union of `dpkg --print-architecture` and + `dpkg --print-foreign-architectures`) + +The following **action fields** are supported in request stanzas: + +- **Install:** (optional, defaults to the empty string) A space + separated list of arch-qualified package names, with *no version + attached*, to install. This field denotes a list of packages that the + user wants to install, usually via an APT `install` request. + +- **Remove:** (optional, defaults to the empty string) Same syntax of + Install. This field denotes a list of packages that the user wants to + remove, usually via APT `remove` or `purge` requests. + +- **ReInstall:** (optional, defaults to the empty string) Same syntax of + Install. This field denotes a list of packages which are installed, + but should be reinstalled again e.g. because files shipped by that + package were removed or corrupted accidentally, usually requested via + an APT `install` request with the `--reinstall` flag. + +The following **preference fields** are supported in request stanzas: + +- **Planner:** (optional, defaults to the empty string) a purely + informational string specifying to which planner this request was send + initially. + +- **Immediate-Configuration:** (option, unset by default) A boolean + value defining if the planner should try to configure all packages as + quickly as possible (true) or shouldn't perform any kind of immediate + configuration at all (false). If not explicitly set with this field + the planner is free to pick either mode or implementing e.g. a mode + which configures only packages immediately if they are flagged as + `Essential` (or are dependencies of packages marked as `Essential`). + +- **Allow-Temporary-Remove-of-Essentials** (optional, defaults to `no`). + A boolean value allowing the planner (if set to yes) to temporarily + remove an essential package. Associated with the APT::Force-LoopBreak + configuration option its main use is highlighting that planners who do + temporary removes must take special care in terms of essentials. Legit + uses of this option by users is very uncommon, traditionally + a situation in which it is needed indicates a packaging error. + + +#### Package universe + +A package universe is a list of Deb 822 stanzas, one per package, called +**package stanzas**. Each package stanzas starts with a Package +field. The following fields are supported in package stanzas: + +- The fields Package, Version, Architecture (all mandatory) and + Multi-Arch, Pre-Depends, Depends, Conflicts, Breaks, Essential + (optional) as they are contained in the dpkg database (see the manpage + `dpkg-query (1)`). + +- **Status:** (optional, defaults to `uninstalled`). Allowed values are + the "package status" names as listed in `dpkg-query (1)` and visible + e.g. in the dpkg database as the second value in the space separated + list of values in the Status field there. In other words: Neither + desired action nor error flags are present in this field in EIPP! + +- **APT-ID:** (mandatory). Unique package identifier, according to APT. + + +### Answer + +An answer from the external planner to APT is either a *solution* or an +*error*. + +The following invariant on **exit codes** must hold true. When the +external planner is *able to find a solution*, it will write the +solution to standard output and then exit with an exit code of 0. When +the external planner is *unable to find a solution* (and is aware of +that), it will write an error to standard output and then exit with an +exit code of 0. An exit code other than 0 will be interpreted as +a planner crash with no meaningful error about dependency resolution to +convey to the user. + + +#### Solution + +A solution is a list of Deb 822 stanzas. Each of them could be an: + +- unpack stanza to cause the extraction of a package to the disk + +- configure stanza to cause an unpacked package to be configured and + therefore the installation to be completed + +- remove stanza to cause the removal of a package from the system + +An **unpack stanza** starts with an Unpack field and supports the +following fields: + +- **Unpack:** (mandatory). The value is a package identifier, + referencing one of the package stanzas of the package universe via its + APT-ID field. + +- All fields supported by package stanzas. + +**Configure** and **Remove stanzas** require and support the same +fields with the exception of the Unpack field which is replaced in +these instances with the Configure or Remove field respectively. + +The order of the stanzas is significant (unlike in the EDSP protocol), +with the first stanza being the first performed action. If multiple +stanzas of the same type appear in direct succession the order in such +a set isn't significant through. + +The solution needs to be valid (it is not allowed to configure a package +before it was unpacked, dependency relations must be satisfied, …), but +they don't need to be complete: A planner can and should expect that any +package which wasn't explicitly configured will be configured at the end +automatically. That also means through that a planner is not allowed to +produce a solution in which a package remains unconfigured. + +In terms of expressivity, all stanzas can carry one single field each, as +APT-IDs are enough to pinpoint packages to be installed/removed. +Nonetheless, for protocol readability, it is recommended that planners +either add unconditionally the fields Package, Version, and Architecture +to all install/remove stanzas or, alternatively, that they support +a `--verbose` command line flag that explicitly enables the output of +those fields in solutions. + +#### Error + +An error is a single Deb 822 stanza, starting the field Error. The +following fields are supported in error stanzas: + +- **Error:** (mandatory). The value of this field is ignored, although + it should be a unique error identifier, such as a UUID. + +- **Message:** (mandatory). The value of this field is a text string, + meant to be read by humans, that explains the cause of the planner + error. Message fields might be multi-line, like the Description field + in the dpkg database. The first line conveys a short message, which + can be explained in more details using subsequent lines. + + +### Progress + +During dependency solving, an external planner may send progress +information to APT using **progress stanzas**. A progress stanza starts +with the Progress field and might contain the following fields: + +- **Progress:** (mandatory). The value of this field is a date and time + timestamp, in RFC 2822 format. The timestamp provides a time + annotation for the progress report. + +- **Percentage:** (optional). An integer from 0 to 100, representing the + completion of the installation planning process, as declared by the + planner. + +- **Message:** (optional). A textual message, meant to be read by the + APT user, telling what is going on within the installation planner + (e.g. the current phase of planning, as declared by the planner). + + +# Future extensions + +Potential future extensions to this protocol are to be discussed on +deity@lists.debian.org. diff --git a/test/integration/framework b/test/integration/framework index cd8597f80..795731deb 100644 --- a/test/integration/framework +++ b/test/integration/framework @@ -191,7 +191,7 @@ aptitude() { runapt aptitude "$@"; } aptextracttemplates() { runapt apt-extracttemplates "$@"; } aptinternalsolver() { runapt "${APTINTERNALSOLVER}" "$@"; } aptdumpsolver() { runapt "${APTDUMPSOLVER}" "$@"; } -aptinternalplaner() { runapt "${APTINTERNALPLANER}" "$@"; } +aptinternalplanner() { runapt "${APTINTERNALPLANNER}" "$@"; } dpkg() { "${TMPWORKINGDIRECTORY}/rootdir/usr/bin/dpkg" "$@" @@ -290,7 +290,7 @@ setupenvironment() { APTWEBSERVERBINDIR="${APT_INTEGRATION_TESTS_WEBSERVER_BIN_DIR:-"${BUILDDIRECTORY}"}" APTINTERNALSOLVER="${APT_INTEGRATION_TESTS_INTERNAL_SOLVER:-"${BUILDDIRECTORY}/apt-internal-solver"}" APTDUMPSOLVER="${APT_INTEGRATION_TESTS_DUMP_SOLVER:-"${BUILDDIRECTORY}/apt-dump-solver"}" - APTINTERNALPLANER="${APT_INTEGRATION_TESTS_INTERNAL_PLANER:-"${BUILDDIRECTORY}/apt-internal-planer"}" + APTINTERNALPLANNER="${APT_INTEGRATION_TESTS_INTERNAL_PLANNER:-"${BUILDDIRECTORY}/apt-internal-planner"}" test -x "${BUILDDIRECTORY}/apt-get" || msgdie "You need to build tree first" # ----- @@ -304,13 +304,13 @@ setupenvironment() { mkdir -p usr/lib/apt ln -s "${METHODSDIR}" usr/lib/apt/methods if [ "$BUILDDIRECTORY" = "$LIBRARYPATH" ]; then - mkdir -p usr/lib/apt/solvers usr/lib/apt/planers + mkdir -p usr/lib/apt/solvers usr/lib/apt/planners ln -s "${BUILDDIRECTORY}/apt-dump-solver" usr/lib/apt/solvers/dump - ln -s "${BUILDDIRECTORY}/apt-dump-solver" usr/lib/apt/planers/dump + ln -s "${BUILDDIRECTORY}/apt-dump-solver" usr/lib/apt/planners/dump ln -s "${BUILDDIRECTORY}/apt-internal-solver" usr/lib/apt/solvers/apt - ln -s "${BUILDDIRECTORY}/apt-internal-planer" usr/lib/apt/planers/apt + ln -s "${BUILDDIRECTORY}/apt-internal-planner" usr/lib/apt/planners/apt echo "Dir::Bin::Solvers \"${TMPWORKINGDIRECTORY}/rootdir/usr/lib/apt/solvers\";" >> ../aptconfig.conf - echo "Dir::Bin::Planers \"${TMPWORKINGDIRECTORY}/rootdir/usr/lib/apt/planers\";" >> ../aptconfig.conf + echo "Dir::Bin::Planners \"${TMPWORKINGDIRECTORY}/rootdir/usr/lib/apt/planners\";" >> ../aptconfig.conf fi # use the autoremove from the BUILDDIRECTORY if its there, otherwise # system @@ -347,6 +347,7 @@ setupenvironment() { echo 'Binary::gpgv::APT::Sandbox::User "root";' >> aptconfig.conf # same for the solver executables echo 'APT::Solver::RunAsUser "root";' >> aptconfig.conf + echo 'APT::Planner::RunAsUser "root";' >> aptconfig.conf fi cat > "${TMPWORKINGDIRECTORY}/rootdir/usr/bin/dpkg" < ./rootdir/etc/apt/apt.conf.d/eipp-logging - -testsuccess apt update -export APT_EDSP_DUMP_FILENAME="${TMPWORKINGDIRECTORY}/eipp.dump" -testfailure test -r "$EIPPLOG" -testfailure aptget install foo --planer dump -y -testfailure test -r "$EIPPLOG" -testfailure grep 'unrelated-2' "$APT_EDSP_DUMP_FILENAME" -testsuccessequal '2' grep -c '^Package: foo$' "$APT_EDSP_DUMP_FILENAME" -testsuccessequal '1' grep -c '^Package: libfoo$' "$APT_EDSP_DUMP_FILENAME" -testsuccessequal 'Planer: dump' grep '^Planer: ' "$APT_EDSP_DUMP_FILENAME" - -testsuccess aptget install foo -s -testsuccess aptget install foo -y -testsuccess test -r "$EIPPLOG" -testsuccessequal 'Request: EIPP 0.1 -Architecture: amd64 -Architectures: amd64 -Remove: bar:amd64 -Install: libfoo:amd64 foo:amd64 -Planer: internal' head -n 6 "$EIPPLOG" -aptinternalplaner < "$EIPPLOG" > planer.log || true -testsuccessequal 'Remove: 6 -Unpack: 2 -Unpack: 4 -Configure: 2 -Configure: 4' grep -e '^Unpack:' -e '^Install:' -e '^Configure:' -e '^Remove:' planer.log - -rm -f "$EIPPLOG" -testsuccess aptget install foo -s --reinstall -testsuccess aptget install foo -y --reinstall -testsuccess test -r "$EIPPLOG" -testsuccessequal 'Request: EIPP 0.1 -Architecture: amd64 -Architectures: amd64 -ReInstall: foo:amd64 -Planer: internal' head -n 5 "$EIPPLOG" -aptinternalplaner < "$EIPPLOG" > planer.log || true -testsuccessequal 'Unpack: 4 -Configure: 4' grep -e '^Unpack:' -e '^Install:' -e '^Configure:' -e '^Remove:' planer.log - -rm -f "$EIPPLOG" -testsuccess aptget purge foo -s -testsuccess aptget purge foo -y -testsuccess test -r "$EIPPLOG" -testsuccessequal 'Request: EIPP 0.1 -Architecture: amd64 -Architectures: amd64 -Remove: foo:amd64 -Planer: internal' head -n 5 "$EIPPLOG" -aptinternalplaner < "$EIPPLOG" > planer.log || true -testsuccessequal 'Remove: 4' grep -e '^Unpack:' -e '^Install:' -e '^Configure:' -e '^Remove:' planer.log diff --git a/test/integration/test-external-installation-planner-protocol b/test/integration/test-external-installation-planner-protocol new file mode 100755 index 000000000..bd3c99c32 --- /dev/null +++ b/test/integration/test-external-installation-planner-protocol @@ -0,0 +1,75 @@ +#!/bin/sh +set -e + +TESTDIR="$(readlink -f "$(dirname "$0")")" +. "$TESTDIR/framework" +setupenvironment +configarchitecture 'amd64' + +buildsimplenativepackage 'libfoo' 'amd64' '3' 'experimental' 'Multi-Arch: same' +buildsimplenativepackage 'foo' 'all' '3' 'experimental' 'Depends: newstuff' +buildsimplenativepackage 'foo' 'all' '2' 'unstable' 'Depends: libfoo:amd64, stuff +Conflicts: bar, libfoo:i386 +Recommends: unrelated' +buildsimplenativepackage 'libfoo' 'amd64' '2' 'unstable' 'Multi-Arch: same' +buildsimplenativepackage 'unrelated-2' 'amd64' '2' 'unstable' +insertinstalledpackage 'foo' 'all' '1' +insertinstalledpackage 'bar' 'all' '1' +insertinstalledpackage 'stuff' 'all' '1' +insertinstalledpackage 'unrelated-1' 'all' '1' + +setupaptarchive --no-update + +EIPPLOG="${TMPWORKINGDIRECTORY}/rootdir/var/log/apt/eipp.log" +echo "Dir::Log::Planner \"$EIPPLOG\";" > ./rootdir/etc/apt/apt.conf.d/eipp-logging + +testsuccess apt update +export APT_EDSP_DUMP_FILENAME="${TMPWORKINGDIRECTORY}/downloaded/dump.eipp" +testfailure test -r "$EIPPLOG" +testfailure aptget install foo --planner dump -y +testfailure test -r "$EIPPLOG" +testfailure grep 'unrelated-2' "$APT_EDSP_DUMP_FILENAME" +testsuccessequal '2' grep -c '^Package: foo$' "$APT_EDSP_DUMP_FILENAME" +testsuccessequal '1' grep -c '^Package: libfoo$' "$APT_EDSP_DUMP_FILENAME" +testsuccessequal 'Planner: dump' grep '^Planner: ' "$APT_EDSP_DUMP_FILENAME" + +testsuccess aptget install foo -s +testsuccess aptget install foo -y +testsuccess test -r "$EIPPLOG" +testsuccessequal 'Request: EIPP 0.1 +Architecture: amd64 +Architectures: amd64 +Remove: bar:amd64 +Install: libfoo:amd64 foo:amd64 +Planner: internal' head -n 6 "$EIPPLOG" +aptinternalplanner < "$EIPPLOG" > planner.log || true +testsuccessequal 'Remove: 6 +Unpack: 2 +Unpack: 4 +Configure: 2 +Configure: 4' grep -e '^Unpack:' -e '^Install:' -e '^Configure:' -e '^Remove:' planner.log + +rm -f "$EIPPLOG" +testsuccess aptget install foo -s --reinstall +testsuccess aptget install foo -y --reinstall +testsuccess test -r "$EIPPLOG" +testsuccessequal 'Request: EIPP 0.1 +Architecture: amd64 +Architectures: amd64 +ReInstall: foo:amd64 +Planner: internal' head -n 5 "$EIPPLOG" +aptinternalplanner < "$EIPPLOG" > planner.log || true +testsuccessequal 'Unpack: 4 +Configure: 4' grep -e '^Unpack:' -e '^Install:' -e '^Configure:' -e '^Remove:' planner.log + +rm -f "$EIPPLOG" +testsuccess aptget purge foo -s +testsuccess aptget purge foo -y +testsuccess test -r "$EIPPLOG" +testsuccessequal 'Request: EIPP 0.1 +Architecture: amd64 +Architectures: amd64 +Remove: foo:amd64 +Planner: internal' head -n 5 "$EIPPLOG" +aptinternalplanner < "$EIPPLOG" > planner.log || true +testsuccessequal 'Remove: 4' grep -e '^Unpack:' -e '^Install:' -e '^Configure:' -e '^Remove:' planner.log -- cgit v1.2.3