Skip to content

Commit

Permalink
Introduce apt satisfy and apt-get satisfy
Browse files Browse the repository at this point in the history
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
  • Loading branch information
julian-klode committed Jun 11, 2019
1 parent 35cb34d commit 9244f71
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 7 deletions.
11 changes: 7 additions & 4 deletions apt-private/private-cmndline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,17 @@ static bool addArgumentsAPTGet(std::vector<CommandLine::Args> &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);
Expand All @@ -241,7 +244,7 @@ static bool addArgumentsAPTGet(std::vector<CommandLine::Args> &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);
Expand Down
77 changes: 74 additions & 3 deletions apt-private/private-source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <apt-private/private-source.h>

#include <apt-pkg/debindexfile.h>
#include <apt-pkg/deblistparser.h>

#include <stddef.h>
#include <stdio.h>
Expand Down Expand Up @@ -652,15 +653,18 @@ 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"));

std::ostringstream buildDepsPkgFile;
std::vector<PseudoPkg> pseudoPkgs;
// deal with the build essentials first
if (not AreDoingSatisfy)
{
std::vector<pkgSrcRecords::Parser::BuildDepRec> BuildDeps;
for (auto && opt: _config->FindVector("APT::Build-Essential"))
Expand All @@ -678,11 +682,78 @@ bool DoBuildDep(CommandLine &CmdL)
pseudoPkgs.emplace_back(pseudo, nativeArch, "");
}

if (AreDoingSatisfy)
{
std::vector<pkgSrcRecords::Parser::BuildDepRec> 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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down
1 change: 1 addition & 0 deletions cmdline/apt-get.cc
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ static std::vector<aptDispatchWithHelp> 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},
Expand Down
1 change: 1 addition & 0 deletions cmdline/apt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ static std::vector<aptDispatchWithHelp> 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},
Expand Down
13 changes: 13 additions & 0 deletions doc/apt-get.8.xml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,19 @@
option if you want to change that.</para></listitem>
</varlistentry>

<varlistentry><term><option>satisfy</option></term>
<listitem><para><literal>satisfy</literal> 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 <literal>"Conflicts: "</literal> to unsatisfy the dependency string. Multiple strings of the same type can be specified.</para>

<para>Example: <literal>apt-get satisfy "foo" "Conflicts: bar" "baz (&gt;&gt; 1.0) | bar (= 2.0), moo"</literal></para>

<para>The legacy operator '&lt;/&gt;' is not supported, use '&lt;=/&gt;=' instead.</para>


</listitem>
</varlistentry>

<varlistentry><term><option>check</option></term>
<listitem><para><literal>check</literal> is a diagnostic tool; it updates the package cache and checks
for broken dependencies.</para></listitem>
Expand Down
9 changes: 9 additions & 0 deletions doc/apt.8.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@
</para></listitem>
</varlistentry>

<varlistentry><term><option>satisfy</option> (&apt-get;)</term>
<listitem><para><option>satisfy</option> satisfies dependency strings, as
used in Build-Depends. It also handles conflicts, by prefixing an argument
with <literal>"Conflicts: "</literal>.
</para><para>Example: <literal>apt satisfy "foo, bar (>= 1.0)" "Conflicts: baz, fuzz"</literal>
</para></listitem>
</varlistentry>


<varlistentry><term><option>search</option> (&apt-cache;)</term>
<listitem><para><option>search</option> can be used to search for the given
&regex; term(s) in the list of available packages and display
Expand Down
1 change: 1 addition & 0 deletions doc/examples/configure-index
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ APT
Arch-Only "<BOOL>";
Indep-Only "<BOOL>";
Build-Dep-Automatic "<BOOL>";
Satisfy-Automatic "<BOOL>";

// (non-)confirming options
Force-Yes "<BOOL>"; // allows downgrades, essential removal and eats children
Expand Down
72 changes: 72 additions & 0 deletions test/integration/test-apt-get-satisfy
Original file line number Diff line number Diff line change
@@ -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 <a>, depends:i386 <i>"

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 <a>, depends:i386 <i>" -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 <a>, depends:i386 <i>" -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

0 comments on commit 9244f71

Please sign in to comment.