From 9244f712396c10b674740cc79fdab61c47173d04 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 9 May 2019 22:23:17 +0200 Subject: Introduce apt satisfy and apt-get satisfy Allow to satisfy dependency strings supplied on the command line, optionally prefixed with "Conflicts:" to satisfy them like Conflicts. Build profiles and architecture restriction lists, as used in build dependencies, are supported as well. Compared to build-dep, build-essential is not installed automatically, and installing of recommended packages follows the global default, which defaults to yes. Closes: #275379 See merge request apt-team/apt!63 --- apt-private/private-cmndline.cc | 11 +++-- apt-private/private-source.cc | 77 +++++++++++++++++++++++++++++++++-- cmdline/apt-get.cc | 1 + cmdline/apt.cc | 1 + doc/apt-get.8.xml | 13 ++++++ doc/apt.8.xml | 9 ++++ doc/examples/configure-index | 1 + test/integration/test-apt-get-satisfy | 72 ++++++++++++++++++++++++++++++++ 8 files changed, 178 insertions(+), 7 deletions(-) create mode 100755 test/integration/test-apt-get-satisfy diff --git a/apt-private/private-cmndline.cc b/apt-private/private-cmndline.cc index 3a25c7131..3f43d6eb1 100644 --- a/apt-private/private-cmndline.cc +++ b/apt-private/private-cmndline.cc @@ -216,14 +216,17 @@ static bool addArgumentsAPTGet(std::vector &Args, char const addArg(0, "tar-only", "APT::Get::Tar-Only", 0); addArg(0, "dsc-only", "APT::Get::Dsc-Only", 0); } - else if (CmdMatches("build-dep")) + else if (CmdMatches("build-dep") || CmdMatches("satisfy")) { addArg('a', "host-architecture", "APT::Get::Host-Architecture", CommandLine::HasArg); addArg('P', "build-profiles", "APT::Build-Profiles", CommandLine::HasArg); addArg(0, "purge", "APT::Get::Purge", 0); addArg(0, "solver", "APT::Solver", CommandLine::HasArg); - addArg(0,"arch-only","APT::Get::Arch-Only",0); - addArg(0,"indep-only","APT::Get::Indep-Only",0); + if (CmdMatches("build-dep")) + { + addArg(0,"arch-only","APT::Get::Arch-Only",0); + addArg(0,"indep-only","APT::Get::Indep-Only",0); + } // this has no effect *but* sbuild is using it (see LP: #1255806) // once sbuild is fixed, this option can be removed addArg('f', "fix-broken", "APT::Get::Fix-Broken", 0); @@ -241,7 +244,7 @@ static bool addArgumentsAPTGet(std::vector &Args, char const if (CmdMatches("install", "reinstall", "remove", "purge", "upgrade", "dist-upgrade", "dselect-upgrade", "autoremove", "auto-remove", "autopurge", "clean", "autoclean", "auto-clean", "check", - "build-dep", "full-upgrade", "source")) + "build-dep", "satisfy", "full-upgrade", "source")) { addArg('s', "simulate", "APT::Get::Simulate", 0); addArg('s', "just-print", "APT::Get::Simulate", 0); diff --git a/apt-private/private-source.cc b/apt-private/private-source.cc index 48c9d8094..bbb14b1d8 100644 --- a/apt-private/private-source.cc +++ b/apt-private/private-source.cc @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -652,8 +653,10 @@ bool DoBuildDep(CommandLine &CmdL) CacheFile Cache; auto VolatileCmdL = GetPseudoPackages(Cache.GetSourceList(), CmdL, AddVolatileSourceFile, pseudoArch); + auto AreDoingSatisfy = strcasecmp(CmdL.FileList[0], "satisfy") == 0; - _config->Set("APT::Install-Recommends", false); + if (not AreDoingSatisfy) + _config->Set("APT::Install-Recommends", false); if (CmdL.FileSize() <= 1 && VolatileCmdL.empty()) return _error->Error(_("Must specify at least one package to check builddeps for")); @@ -661,6 +664,7 @@ bool DoBuildDep(CommandLine &CmdL) std::ostringstream buildDepsPkgFile; std::vector pseudoPkgs; // deal with the build essentials first + if (not AreDoingSatisfy) { std::vector BuildDeps; for (auto && opt: _config->FindVector("APT::Build-Essential")) @@ -678,11 +682,78 @@ bool DoBuildDep(CommandLine &CmdL) pseudoPkgs.emplace_back(pseudo, nativeArch, ""); } + if (AreDoingSatisfy) + { + std::vector BuildDeps; + for (unsigned i = 1; i < CmdL.FileSize(); i++) + { + const char *Start = CmdL.FileList[i]; + const char *Stop = Start + strlen(Start); + auto Type = pkgSrcRecords::Parser::BuildDependIndep; + + // Reject '>' and '<' as operators, as they have strange meanings. + bool insideVersionRestriction = false; + for (auto C = Start; C + 1 != Stop; C++) + { + if (*C == '(') + insideVersionRestriction = true; + else if (*C == ')') + insideVersionRestriction = false; + else if (insideVersionRestriction && (*C == '<' || *C == '>')) + { + if (C[1] != *C && C[1] != '=') + return _error->Error(_("Invalid operator '%c' at offset %d, did you mean '%c%c' or '%c='? - in: %s"), *C, (int)(C - Start), *C, *C, *C, Start); + C++; + } + } + + if (APT::String::Startswith(Start, "Conflicts:")) + { + Type = pkgSrcRecords::Parser::BuildConflictIndep; + Start += strlen("Conflicts:"); + } + while (1) + { + pkgSrcRecords::Parser::BuildDepRec rec; + Start = debListParser::ParseDepends(Start, Stop, + rec.Package, rec.Version, rec.Op, true, false, true, pseudoArch); + + if (Start == 0) + return _error->Error("Problem parsing dependency: %s", CmdL.FileList[i]); + rec.Type = Type; + + // We parsed a package that was ignored (wrong architecture restriction + // or something). + if (rec.Package.empty()) + { + // If we are in an OR group, we need to set the "Or" flag of the + // previous entry to our value. + if (BuildDeps.empty() == false && (BuildDeps[BuildDeps.size() - 1].Op & pkgCache::Dep::Or) == pkgCache::Dep::Or) + { + BuildDeps[BuildDeps.size() - 1].Op &= ~pkgCache::Dep::Or; + BuildDeps[BuildDeps.size() - 1].Op |= (rec.Op & pkgCache::Dep::Or); + } + } + else + { + BuildDeps.emplace_back(std::move(rec)); + } + + if (Start == Stop) + break; + } + } + std::string const pseudo = "command line argument"; + WriteBuildDependencyPackage(buildDepsPkgFile, pseudo, pseudoArch, BuildDeps); + pseudoPkgs.emplace_back(pseudo, pseudoArch, ""); + } + // Read the source list if (Cache.BuildSourceList() == false) return false; pkgSourceList *List = Cache.GetSourceList(); + if (not AreDoingSatisfy) { auto const VolatileSources = List->GetVolatileFiles(); for (auto &&pkg : VolatileCmdL) @@ -713,7 +784,7 @@ bool DoBuildDep(CommandLine &CmdL) } bool const WantLock = _config->FindB("APT::Get::Print-URIs", false) == false; - if (CmdL.FileList[1] != 0) + if (CmdL.FileList[1] != 0 && not AreDoingSatisfy) { if (Cache.BuildCaches(WantLock) == false) return false; @@ -786,7 +857,7 @@ bool DoBuildDep(CommandLine &CmdL) { pkgDepCache::ActionGroup group(Cache); - if (_config->FindB("APT::Get::Build-Dep-Automatic", false) == false) + if (_config->FindB(AreDoingSatisfy ? "APT::Get::Satisfy-Automatic" : "APT::Get::Build-Dep-Automatic", false) == false) { for (auto const &pkg: removeAgain) { diff --git a/cmdline/apt-get.cc b/cmdline/apt-get.cc index fe6e22d81..7ef07fbf0 100644 --- a/cmdline/apt-get.cc +++ b/cmdline/apt-get.cc @@ -416,6 +416,7 @@ static std::vector GetCommands() /*{{{*/ {"full-upgrade", &DoDistUpgrade, nullptr}, {"dselect-upgrade", &DoDSelectUpgrade, _("Follow dselect selections")}, {"build-dep", &DoBuildDep, _("Configure build-dependencies for source packages")}, + {"satisfy", &DoBuildDep, _("Satisfy dependency strings")}, {"clean", &DoClean, _("Erase downloaded archive files")}, {"autoclean", &DoAutoClean, _("Erase old downloaded archive files")}, {"auto-clean", &DoAutoClean, nullptr}, diff --git a/cmdline/apt.cc b/cmdline/apt.cc index d388e4af4..cc73181c0 100644 --- a/cmdline/apt.cc +++ b/cmdline/apt.cc @@ -78,6 +78,7 @@ static std::vector GetCommands() /*{{{*/ // misc {"edit-sources", &EditSources, _("edit the source information file")}, {"moo", &DoMoo, nullptr}, + {"satisfy", &DoBuildDep, _("satisfy dependency strings")}, // for compat with muscle memory {"dist-upgrade", &DoDistUpgrade, nullptr}, diff --git a/doc/apt-get.8.xml b/doc/apt-get.8.xml index b757c3d30..fca682015 100644 --- a/doc/apt-get.8.xml +++ b/doc/apt-get.8.xml @@ -205,6 +205,19 @@ option if you want to change that. + + satisfy causes apt-get to satisfy the given dependency string.s The + dependency strings may have build profiles and architecture restriction list as in build dependencies. They + may optionally be prefixed with "Conflicts: " to unsatisfy the dependency string. Multiple strings of the same type can be specified. + + Example: apt-get satisfy "foo" "Conflicts: bar" "baz (>> 1.0) | bar (= 2.0), moo" + + The legacy operator '</>' is not supported, use '<=/>=' instead. + + + + + check is a diagnostic tool; it updates the package cache and checks for broken dependencies. diff --git a/doc/apt.8.xml b/doc/apt.8.xml index e43ad9e9e..0c822b4ef 100644 --- a/doc/apt.8.xml +++ b/doc/apt.8.xml @@ -109,6 +109,15 @@ + (&apt-get;) + satisfies dependency strings, as + used in Build-Depends. It also handles conflicts, by prefixing an argument + with "Conflicts: ". + Example: apt satisfy "foo, bar (>= 1.0)" "Conflicts: baz, fuzz" + + + + (&apt-cache;) can be used to search for the given ®ex; term(s) in the list of available packages and display diff --git a/doc/examples/configure-index b/doc/examples/configure-index index 5e317d7e0..25378a809 100644 --- a/doc/examples/configure-index +++ b/doc/examples/configure-index @@ -63,6 +63,7 @@ APT Arch-Only ""; Indep-Only ""; Build-Dep-Automatic ""; + Satisfy-Automatic ""; // (non-)confirming options Force-Yes ""; // allows downgrades, essential removal and eats children diff --git a/test/integration/test-apt-get-satisfy b/test/integration/test-apt-get-satisfy new file mode 100755 index 000000000..f2e04d789 --- /dev/null +++ b/test/integration/test-apt-get-satisfy @@ -0,0 +1,72 @@ +#!/bin/sh +set -e + +TESTDIR="$(readlink -f "$(dirname "$0")")" +. "$TESTDIR/framework" + +setupenvironment +configarchitecture 'i386' 'amd64' + +insertpackage 'stable' 'depends' 'i386' '1' +insertpackage 'stable' 'depends' 'amd64' '1' +insertinstalledpackage 'conflicts' 'i386' '1' 'Multi-Arch: same' +insertinstalledpackage 'conflicts' 'amd64' '1' 'Multi-Arch: same' +setupaptarchive + + +testsuccessequal "Reading package lists... +Building dependency tree... +The following packages will be REMOVED: + conflicts +The following NEW packages will be installed: + depends +0 upgraded, 1 newly installed, 1 to remove and 0 not upgraded. +Remv conflicts [1] +Inst depends (1 stable [i386]) +Conf depends (1 stable [i386])" aptget satisfy --simulate "depends (>= 1)" "Conflicts: conflicts:i386 (>= 1) [i386], conflicts:amd64 (>= 1) [amd64]" + +testsuccessequal "Reading package lists... +Building dependency tree... +The following packages will be REMOVED: + conflicts:amd64 +The following NEW packages will be installed: + depends:amd64 +0 upgraded, 1 newly installed, 1 to remove and 0 not upgraded. +Remv conflicts:amd64 [1] +Inst depends:amd64 (1 stable [amd64]) +Conf depends:amd64 (1 stable [amd64])" aptget satisfy --simulate "depends (>= 1)" "Conflicts: conflicts:i386 (>= 1) [i386], conflicts:amd64 (>= 1) [amd64]" --host-architecture amd64 + +msgmsg "Build profile" + +testsuccessequal "Reading package lists... +Building dependency tree... +0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded." aptget satisfy --simulate "depends:amd64 , depends:i386 " + +testsuccessequal "Reading package lists... +Building dependency tree... +The following NEW packages will be installed: + depends:amd64 +0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. +Inst depends:amd64 (1 stable [amd64]) +Conf depends:amd64 (1 stable [amd64])" aptget satisfy --simulate "depends:amd64 , depends:i386 " -P a + +testsuccessequal "Reading package lists... +Building dependency tree... +The following NEW packages will be installed: + depends +0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. +Inst depends (1 stable [i386]) +Conf depends (1 stable [i386])" aptget satisfy --simulate "depends:amd64 , depends:i386 " -P i + + +msgmsg "Broken syntax" +testfailureequal "E: Problem parsing dependency: apt (>= 2" aptget satisfy 'foo' 'apt (>= 2' -s +testfailureequal "E: Problem parsing dependency: Conflicts: apt (>= 2" aptget satisfy 'foo' 'Conflicts: apt (>= 2' -s + + +msgmsg "Legacy operators" +testfailureequal "E: Invalid operator '<' at offset 5, did you mean '<<' or '<='? - in: foo (< 1)" aptget satisfy 'foo (< 1)' -s +testfailureequal "E: Invalid operator '>' at offset 5, did you mean '>>' or '>='? - in: foo (> 1)" aptget satisfy 'foo (> 1)' -s + +msgmsg "Unsupported dependency type" +testfailureequal "E: Problem parsing dependency: Recommends: foo" aptget satisfy 'Recommends: foo' -s -- cgit v1.2.3