summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Andres Klode <jak@debian.org>2025-10-06 21:47:32 +0200
committerJulian Andres Klode <jak@debian.org>2025-10-25 17:27:48 +0200
commit4b22a1e36787923fcf303c481f64d580607e670b (patch)
treeffa99de22e65c0a85a04cc06cf37d0c1354e99fb
parent2e1243e3f582b1a68ac3fb313364a93128a3f918 (diff)
Record varying defaults based on cli-version, rather than binary
The --cli-version argument can be used to request a specific CLI version. The default CLI version is the APT version for apt(8) and 0 for the other apt-get tools.
-rw-r--r--apt-private/private-cmndline.cc133
-rw-r--r--apt-private/private-cmndline.h2
-rw-r--r--apt-private/private-main.cc3
-rw-r--r--debian/NEWS17
-rw-r--r--doc/apt.ent29
-rw-r--r--doc/examples/configure-index4
-rw-r--r--test/libapt/cliversion_test.cc68
7 files changed, 230 insertions, 26 deletions
diff --git a/apt-private/private-cmndline.cc b/apt-private/private-cmndline.cc
index e16aa7280..2e6b47b11 100644
--- a/apt-private/private-cmndline.cc
+++ b/apt-private/private-cmndline.cc
@@ -3,6 +3,7 @@
#include <apt-pkg/cmndline.h>
#include <apt-pkg/configuration.h>
+#include <apt-pkg/debversion.h>
#include <apt-pkg/error.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/init.h>
@@ -12,6 +13,7 @@
#include <apt-private/private-cmndline.h>
#include <apt-private/private-main.h>
+#include <cassert>
#include <cstdarg>
#include <cstdlib>
#include <cstring>
@@ -420,6 +422,7 @@ std::vector<CommandLine::Args> getCommandArgs(APT_CMD const Program, char const
addArg('q', "silent", "quiet", CommandLine::IntLevel);
addArg('c', "config-file", 0, CommandLine::ConfigFile);
addArg('o', "option", 0, CommandLine::ArbItem);
+ addArg(0, "cli-version", "APT::Version", CommandLine::HasArg);
addArg(0, NULL, NULL, 0);
return Args;
@@ -492,32 +495,30 @@ static void BinarySpecificConfiguration(char const * const Binary) /*{{{*/
{
_config->CndSet("Binary::apt-cdrom::APT::Internal::OpProgress::EraseLines", false);
}
- if (binary == "apt" || binary == "apt-config")
- {
- if (getenv("NO_COLOR") == nullptr && getenv("APT_NO_COLOR") == nullptr)
- _config->CndSet("Binary::apt::APT::Color", true);
- _config->CndSet("Binary::apt::APT::Output-Version", 30);
- _config->CndSet("Binary::apt::APT::Cache::Show::Version", 2);
- _config->CndSet("Binary::apt::APT::Cache::AllVersions", false);
- _config->CndSet("Binary::apt::APT::Cache::ShowVirtuals", true);
- _config->CndSet("Binary::apt::APT::Cache::Search::Version", 2);
- _config->CndSet("Binary::apt::APT::Cache::ShowDependencyType", true);
- _config->CndSet("Binary::apt::APT::Cache::ShowVersion", true);
- _config->CndSet("Binary::apt::APT::Get::Upgrade-Allow-New", true);
- _config->CndSet("Binary::apt::APT::Cmd::Show-Update-Stats", true);
- _config->CndSet("Binary::apt::DPkg::Progress-Fancy", true);
- _config->CndSet("Binary::apt::APT::Keep-Downloaded-Packages", false);
- _config->CndSet("Binary::apt::APT::Get::Update::InteractiveReleaseInfoChanges", true);
- _config->CndSet("Binary::apt::APT::Cmd::Pattern-Only", true);
- _config->CndSet("Binary::apt::Pager", true);
-
- if (isatty(STDIN_FILENO))
- _config->CndSet("Binary::apt::Dpkg::Lock::Timeout", -1);
- else
- _config->CndSet("Binary::apt::Dpkg::Lock::Timeout", 120);
- }
-
_config->Set("Binary", binary);
+
+ // Version specific configuration
+ _config->CndSet("Version::1.2::APT::Color",
+ getenv("NO_COLOR") == nullptr && getenv("APT_NO_COLOR") == nullptr);
+ _config->CndSet("Version::3.0::APT::Output-Version", 30);
+ _config->CndSet("Version::1.1::APT::Cache::Show::Version", 2);
+ _config->CndSet("Version::1.1::APT::Cache::AllVersions", false);
+ _config->CndSet("Version::1.1::APT::Cache::ShowVirtuals", true);
+ _config->CndSet("Version::1.1::APT::Cache::Search::Version", 2);
+ _config->CndSet("Version::1.1::APT::Cache::ShowDependencyType", true);
+ _config->CndSet("Version::1.1::APT::Cache::ShowVersion", true);
+ _config->CndSet("Version::1.1::APT::Get::Upgrade-Allow-New", true);
+ _config->CndSet("Version::1.1::APT::Cmd::Show-Update-Stats", true);
+ _config->CndSet("Version::1.2::DPkg::Progress-Fancy", true);
+ _config->CndSet("Version::1.2::APT::Keep-Downloaded-Packages", false);
+ _config->CndSet("Version::1.5::APT::Get::Update::InteractiveReleaseInfoChanges", true);
+ _config->CndSet("Version::2.0::APT::Cmd::Pattern-Only", true);
+ _config->CndSet("Version::3.0::Pager", true);
+
+ if (isatty(STDIN_FILENO))
+ _config->CndSet("Version::2.0::Dpkg::Lock::Timeout", -1);
+ else
+ _config->CndSet("Version::2.0::Dpkg::Lock::Timeout", 120);
}
/*}}}*/
static void BinaryCommandSpecificConfiguration(char const * const Binary, char const * const Cmd)/*{{{*/
@@ -535,6 +536,48 @@ static void BinaryCommandSpecificConfiguration(char const * const Binary, char c
}
#undef CmdMatches
/*}}}*/
+
+static std::pair<std::optional<long unsigned int>, std::optional<long unsigned int>> parseCliVersion(std::string_view version)
+{
+ auto level = VectorizeString(version, '.');
+ long unsigned int first = 0, second = 0;
+ if (not StrToNum(level[0].data(), first, level[0].size(), 10))
+ return {};
+ if (level.size() <= 1)
+ return {first, {}};
+ if (not StrToNum(level[1].data(), second, level[1].size(), 10))
+ return {};
+
+ return {first, second};
+}
+
+bool cliVersionIsCompatible(std::string_view rawLevel, std::string_view rawPattern)
+{
+ auto level = parseCliVersion(rawLevel);
+ auto pattern = parseCliVersion(rawPattern);
+
+ assert(level.first);
+ assert(level.second);
+
+ // Requested an earlier major version
+ if (pattern.first && *level.first > *pattern.first)
+ return false;
+ // Requested a latter major version
+ if (pattern.first && *level.first < *pattern.first)
+ return (*level.second < 10); // Keep in mind 2.10 follows 2.8, not 2.9, but 3.0 follows 2.9
+ // Same major version, requested a higher pattern version (compatible usually)
+ if (pattern.second && *level.second < *pattern.second)
+ return (*level.second != 9); // X.9 is not compatible with X.10 onward
+ // Special case for short pattern, X.9 does not migrate to X+1
+ if (not pattern.second && level.second == 9)
+ return false;
+ // Same major version, requested a lower major version.
+ if (pattern.second && *level.second > *pattern.second)
+ return false;
+
+ return true;
+}
+
std::vector<CommandLine::Dispatch> ParseCommandLine(CommandLine &CmdL, APT_CMD const Binary,/*{{{*/
Configuration * const * const Cnf, pkgSystem ** const Sys, int const argc, const char *argv[],
bool (*ShowHelp)(CommandLine &), std::vector<aptDispatchWithHelp> (*GetCommands)(void))
@@ -564,6 +607,7 @@ std::vector<CommandLine::Dispatch> ParseCommandLine(CommandLine &CmdL, APT_CMD c
CmdCalled = CommandLine::GetCommand(Cmds.data(), argc, argv);
if (CmdCalled != nullptr)
BinaryCommandSpecificConfiguration(argv[0], CmdCalled);
+
std::string const conf = "Binary::" + _config->Find("Binary");
_config->MoveSubTree(conf.c_str(), nullptr);
@@ -573,7 +617,46 @@ std::vector<CommandLine::Dispatch> ParseCommandLine(CommandLine &CmdL, APT_CMD c
auto Args = getCommandArgs(Binary, CmdCalled);
CmdL = CommandLine(Args.data(), _config);
+ auto initVersion = []() -> bool
+ {
+ auto defaultVersion = parseCliVersion(PACKAGE_VERSION);
+ std::string requestedVersion = _config->Find("APT::Version");
+ if (requestedVersion.empty())
+ {
+ if (_config->Find("Binary", "") == "apt")
+ requestedVersion = PACKAGE_VERSION;
+ else
+ strprintf(requestedVersion, "0.%lu", *defaultVersion.first * 10 + *defaultVersion.second);
+ }
+ else
+ {
+ // Check that the requested version is compatible with our version
+ auto parsedRequestedVersion = parseCliVersion(requestedVersion);
+ // This appends a 0 to the requested version if not specified.
+ std::string cleanRequestedVersion;
+ strprintf(cleanRequestedVersion, "%lu.%lu", *parsedRequestedVersion.first, parsedRequestedVersion.second ? *parsedRequestedVersion.second : 0);
+ // Supported version "lowers" say 3.1 to 0.31, 1.21, 2.11 depending on requested major
+ std::string supportedVersion;
+ strprintf(supportedVersion, "%lu.%lu", *parsedRequestedVersion.first, (*defaultVersion.first - *parsedRequestedVersion.first) * 10 + *defaultVersion.second);
+ if (not cliVersionIsCompatible(cleanRequestedVersion, supportedVersion))
+ return _error->Error("Requested version %s is not supported in version %s (%s is)", requestedVersion.c_str(), PACKAGE_VERSION, supportedVersion.c_str());
+ }
+ if (auto Versions = _config->Tree("Version"))
+ {
+ for (auto I = Versions->Child; I != NULL; I = I->Next)
+ {
+ auto sub = "Version::" + I->Tag;
+ if (not cliVersionIsCompatible(I->Tag, requestedVersion))
+ continue;
+ // FIXME: 2.10 should not flow into 3.0
+ _config->MoveSubTree(sub.c_str(), nullptr, false);
+ }
+ }
+ return true;
+ };
+
if (CmdL.Parse(argc,argv) == false ||
+ initVersion() == false ||
(Sys != NULL && pkgInitSystem(*_config, *Sys) == false))
{
if (_config->FindB("version") == true)
diff --git a/apt-private/private-cmndline.h b/apt-private/private-cmndline.h
index 22e25d280..6e14aa836 100644
--- a/apt-private/private-cmndline.h
+++ b/apt-private/private-cmndline.h
@@ -4,6 +4,7 @@
#include <apt-pkg/cmndline.h>
#include <apt-pkg/macros.h>
+#include <string_view>
#include <vector>
class Configuration;
@@ -39,4 +40,5 @@ APT_PUBLIC unsigned short DispatchCommandLine(CommandLine &CmdL, std::vector<Com
APT_PUBLIC std::vector<CommandLine::Args> getCommandArgs(APT_CMD const Program, char const * const Cmd);
+APT_PUBLIC bool cliVersionIsCompatible(std::string_view level, std::string_view pattern);
#endif
diff --git a/apt-private/private-main.cc b/apt-private/private-main.cc
index 7b4f05a50..1c34b8cd6 100644
--- a/apt-private/private-main.cc
+++ b/apt-private/private-main.cc
@@ -80,7 +80,8 @@ void CheckIfCalledByScript(int argc, const char *argv[]) /*{{{*/
if (unlikely(argc < 1)) return;
if (not IsStdoutAtty() &&
- _config->FindB("Apt::Cmd::Disable-Script-Warning", false) == false)
+ not _config->Exists("APT::Version") &&
+ not _config->FindB("Apt::Cmd::Disable-Script-Warning", false))
{
// NOTE: CLI interface is redundant on the I/interface, this is
// intentional to make it easier to read.
diff --git a/debian/NEWS b/debian/NEWS
index 812e95e25..7ac58d84a 100644
--- a/debian/NEWS
+++ b/debian/NEWS
@@ -1,3 +1,20 @@
+apt (3.1.10) unstable; urgency=medium
+
+ The new --cli-version optionr equests a specific CLI version.
+
+ The default --cli-version is the current APT version for the apt(8) command
+ or 0.(10 * major + minor) for the other command.
+
+ If --cli-version is specified, the output of the apt(8) command may be
+ recorded. This allows for replacing legacy commands like apt-get(8) in
+ scripts.
+
+ The legacy commands may roll-over to the 2.X series in the future.
+ For further information on this mechanism, consult the apt(8) manual
+ page.
+
+ -- Julian Andres Klode <jak@debian.org> Sat, 25 Oct 2025 17:27:38 +0200
+
apt (2.9.24) unstable; urgency=medium
/etc/apt/trusted.gpg is no longer trusted. Setting the Dir::Etc::trusted
diff --git a/doc/apt.ent b/doc/apt.ent
index 9034d2e66..9ac40e52a 100644
--- a/doc/apt.ent
+++ b/doc/apt.ent
@@ -79,6 +79,35 @@
</varlistentry>
<varlistentry>
+ <term><option>--cli-version</option></term>
+ <listitem><para>Requests a specific CLI version. This is a pattern that takes
+ a MAJOR.MINOR version. You may omit the MINOR version, in which case the latest
+ supported minor level will be used.
+ </para>
+ <para>
+ Version tracks are presented as follows: The version 3.2 has equivalent versions
+ 0.32, 1.22, and 2.12, implementing new features for their specified major versions.
+ </para>
+ <para>
+ A special case is the X.9 version pattern: A version like 2.9 is followed by 3.0,
+ but 2.10 follows directly on 2.8; given that we use odd version numbers as development
+ series (2.9 is the 3.0 development series).
+ </para>
+ <para>
+ This feature has been introduced in APT 3.1.10, and replaces the binary-specific
+configuration. You may use the <option>APT::Version</option> to similar effect on older
+binaries, for example, use <option>-o APT::Version=2</option> to make &apt; behave like
+the 2.x series.
+ </para>
+ <para>
+ The default value is the current APT version for &apt; and <literal>0.(10 * major + minor)</literal>
+ for the classic commands. The 0.X series is soft-deprecated and the other commands will
+ roll over to the 2.x series in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>--no-color</option></term>
<term><option>--color</option></term>
<listitem><para>Turn colors on or off. Colors are on by default on supported terminals for &apt; and
diff --git a/doc/examples/configure-index b/doc/examples/configure-index
index 65b5032bc..1b050d0b9 100644
--- a/doc/examples/configure-index
+++ b/doc/examples/configure-index
@@ -237,6 +237,8 @@ APT
Periodic {};
Machine-ID "<STRING>"; // Value of /etc/machine-id
+
+ Version "<STRING>"; // Version of behavior requested
};
// Options for the downloading routines
@@ -913,3 +915,5 @@ update-manager::always-include-phased-updates "<BOOL>";
update-manager::never-include-phased-updates "<BOOL>";
apt::history::comment "<STRING>";
+
+interactive "<BOOL>";
diff --git a/test/libapt/cliversion_test.cc b/test/libapt/cliversion_test.cc
new file mode 100644
index 000000000..a51872aaa
--- /dev/null
+++ b/test/libapt/cliversion_test.cc
@@ -0,0 +1,68 @@
+#include <config.h>
+
+#include <apt-private/private-cmndline.h>
+
+#include "common.h"
+
+TEST(CliVersionTest, TestShort)
+{
+
+ EXPECT_TRUE(cliVersionIsCompatible("0.0", "0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.1", "0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.0", "1"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.1", "1"));
+ EXPECT_FALSE(cliVersionIsCompatible("0.9", "0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.9", "1"));
+ EXPECT_FALSE(cliVersionIsCompatible("0.10", "1"));
+}
+
+TEST(CliVersionTest, Test_0_10)
+{
+ EXPECT_TRUE(cliVersionIsCompatible("0.0", "0.10"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.1", "0.10"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.2", "0.10"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.3", "0.10"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.4", "0.10"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.5", "0.10"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.6", "0.10"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.7", "0.10"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.8", "0.10"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.10", "0.10"));
+ // 0.9 does not migrate to 0.10
+ EXPECT_FALSE(cliVersionIsCompatible("0.9", "0.10"));
+ // 0.11 is newer than 0.10
+ EXPECT_FALSE(cliVersionIsCompatible("0.11", "0.10"));
+}
+
+TEST(CliVersionTest, TestSame)
+{
+ EXPECT_TRUE(cliVersionIsCompatible("0.0", "0.0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.1", "0.1"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.2", "0.2"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.3", "0.3"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.4", "0.4"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.5", "0.5"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.6", "0.6"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.7", "0.7"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.8", "0.8"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.9", "0.9"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.10", "0.10"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.11", "0.11"));
+}
+TEST(CliVersionTest, Test_1_0)
+{
+ EXPECT_TRUE(cliVersionIsCompatible("0.0", "1.0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.1", "1.0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.2", "1.0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.3", "1.0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.4", "1.0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.5", "1.0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.6", "1.0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.7", "1.0"));
+ EXPECT_TRUE(cliVersionIsCompatible("0.8", "1.0"));
+ // 0.9 *does* migrate to 0.10
+ EXPECT_TRUE(cliVersionIsCompatible("0.9", "1.0"));
+ // 0.11 is a continuation of the 0 branch in parallel to 1.1
+ EXPECT_FALSE(cliVersionIsCompatible("0.10", "1.0"));
+ EXPECT_FALSE(cliVersionIsCompatible("0.11", "1.0"));
+}