From 4b22a1e36787923fcf303c481f64d580607e670b Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Mon, 6 Oct 2025 21:47:32 +0200 Subject: 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. --- apt-private/private-cmndline.cc | 133 ++++++++++++++++++++++++++++++++-------- apt-private/private-cmndline.h | 2 + apt-private/private-main.cc | 3 +- debian/NEWS | 17 +++++ doc/apt.ent | 29 +++++++++ doc/examples/configure-index | 4 ++ test/libapt/cliversion_test.cc | 68 ++++++++++++++++++++ 7 files changed, 230 insertions(+), 26 deletions(-) create mode 100644 test/libapt/cliversion_test.cc 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 #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include +#include #include #include #include @@ -420,6 +422,7 @@ std::vector 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> 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 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 (*GetCommands)(void)) @@ -564,6 +607,7 @@ std::vector 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 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 #include +#include #include class Configuration; @@ -39,4 +40,5 @@ APT_PUBLIC unsigned short DispatchCommandLine(CommandLine &CmdL, std::vector 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 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 @@ -78,6 +78,35 @@ + + + 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. + + + 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. + + + 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). + + + This feature has been introduced in APT 3.1.10, and replaces the binary-specific +configuration. You may use the to similar effect on older +binaries, for example, use to make &apt; behave like +the 2.x series. + + + The default value is the current APT version for &apt; and 0.(10 * major + minor) + 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. + + + + 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 ""; // Value of /etc/machine-id + + Version ""; // Version of behavior requested }; // Options for the downloading routines @@ -913,3 +915,5 @@ update-manager::always-include-phased-updates ""; update-manager::never-include-phased-updates ""; apt::history::comment ""; + +interactive ""; 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 + +#include + +#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")); +} -- cgit v1.2.3-70-g09d2