From 92b78f693d73716c8f9cbc4e15cbba6a3c76c3a3 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 12 Dec 2022 14:59:28 -0800 Subject: [PATCH 01/55] Add pin index --- .../Commands/PinCommand.cpp | 99 ++++++++--- .../AppInstallerCommonCore.vcxproj | 2 + .../AppInstallerCommonCore.vcxproj.filters | 6 + src/AppInstallerCommonCore/Pin.cpp | 66 ++++++++ .../Public/AppInstallerVersions.h | 10 ++ .../Public/winget/Pin.h | 57 +++++++ .../AppInstallerRepositoryCore.vcxproj | 7 + ...AppInstallerRepositoryCore.vcxproj.filters | 24 +++ .../Microsoft/PinningIndex.cpp | 96 +++++++++++ .../Microsoft/PinningIndex.h | 54 ++++++ .../Microsoft/Schema/IPinningIndex.h | 33 ++++ .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 157 ++++++++++++++++++ .../Microsoft/Schema/Pinning_1_0/PinTable.h | 26 +++ .../Pinning_1_0/PinningIndexInterface.h | 26 +++ .../Pinning_1_0/PinningIndexInterface_1_0.cpp | 81 +++++++++ .../Schema/Portable_1_0/PortableTable.cpp | 2 +- 16 files changed, 721 insertions(+), 25 deletions(-) create mode 100644 src/AppInstallerCommonCore/Pin.cpp create mode 100644 src/AppInstallerCommonCore/Public/winget/Pin.h create mode 100644 src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp create mode 100644 src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index 5cdedb26b0..bb13d7deec 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -76,15 +76,26 @@ namespace AppInstaller::CLI void PinAddCommand::Complete(Execution::Context& context, Args::Type valueType) const { + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + switch (valueType) { - case Args::Type::Query: - case Args::Type::Id: - case Args::Type::Name: - case Args::Type::Moniker: - case Args::Type::Source: + case Execution::Args::Type::Query: + context << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Source: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: context << - Workflow::CompleteWithSingleSemanticsForValue(valueType); + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); break; } } @@ -106,7 +117,14 @@ namespace AppInstaller::CLI void PinAddCommand::ExecuteInternal(Execution::Context& context) const { // TODO - Command::ExecuteInternal(context); + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << + Workflow::SearchSourceForMany << + Workflow::HandleSearchResultFailures << + Workflow::EnsureMatchesFromSearchResult(true) << + Workflow::SearchPin << + Workflow::AddPin; } std::vector PinRemoveCommand::GetArguments() const @@ -137,15 +155,26 @@ namespace AppInstaller::CLI void PinRemoveCommand::Complete(Execution::Context& context, Args::Type valueType) const { + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + switch (valueType) { - case Args::Type::Query: - case Args::Type::Id: - case Args::Type::Name: - case Args::Type::Moniker: - case Args::Type::Source: + case Execution::Args::Type::Query: + context << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Source: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: context << - Workflow::CompleteWithSingleSemanticsForValue(valueType); + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); break; } } @@ -157,8 +186,14 @@ namespace AppInstaller::CLI void PinRemoveCommand::ExecuteInternal(Execution::Context& context) const { - // TODO - Command::ExecuteInternal(context); + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << + Workflow::SearchSourceForMany << + Workflow::HandleSearchResultFailures << + Workflow::EnsureMatchesFromSearchResult(true) << + Workflow::SearchPin << + Workflow::RemovePin; } std::vector PinListCommand::GetArguments() const @@ -189,15 +224,26 @@ namespace AppInstaller::CLI void PinListCommand::Complete(Execution::Context& context, Args::Type valueType) const { + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + switch (valueType) { - case Args::Type::Query: - case Args::Type::Id: - case Args::Type::Name: - case Args::Type::Moniker: - case Args::Type::Source: + case Execution::Args::Type::Query: context << - Workflow::CompleteWithSingleSemanticsForValue(valueType); + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Source: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: + context << + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); break; } } @@ -210,7 +256,13 @@ namespace AppInstaller::CLI void PinListCommand::ExecuteInternal(Execution::Context& context) const { // TODO - Command::ExecuteInternal(context); + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << + Workflow::SearchSourceForMany << + Workflow::HandleSearchResultFailures << + Workflow::SearchPins << + Workflow::ReportPins; } std::vector PinResetCommand::GetArguments() const @@ -237,7 +289,6 @@ namespace AppInstaller::CLI void PinResetCommand::ExecuteInternal(Execution::Context& context) const { - // TODO - Command::ExecuteInternal(context); + context << Workflow::ResetAllPins; } } diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index 7c51c73099..961eddb16b 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -326,6 +326,7 @@ + @@ -394,6 +395,7 @@ + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 574833856a..836105d370 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -222,6 +222,9 @@ Public\winget + + Public\winget + @@ -392,6 +395,9 @@ Source Files + + Source Files + diff --git a/src/AppInstallerCommonCore/Pin.cpp b/src/AppInstallerCommonCore/Pin.cpp new file mode 100644 index 0000000000..9ca422785a --- /dev/null +++ b/src/AppInstallerCommonCore/Pin.cpp @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Pin.h" + +namespace AppInstaller::Pinning +{ + using namespace std::string_view_literals; + + std::string_view ToString(PinType type) + { + switch (type) + { + case PinType::Blocking: + return "Blocking"sv; + case PinType::Pinning: + return "Pinning"sv; + case PinType::Gating: + return "Gating"sv; + case PinType::Unknown: + default: + return "Unknown"; + } + } + + Pin Pin::CreateBlockingPin(PinKey&& pinKey) + { + return { PinType::Blocking, std::move(pinKey) }; + } + + Pin Pin::CreatePinningPin(PinKey&& pinKey) + { + return { PinType::Pinning, std::move(pinKey) }; + } + + Pin Pin::CreateGatingPin(PinKey&& pinKey, AppInstaller::Utility::GatedVersion gatedVersion) + { + return { PinType::Gating, std::move(pinKey), gatedVersion }; + } + + PinType Pin::GetType() const + { + return m_type; + } + + const PinKey& Pin::GetKey() const + { + return m_id; + } + + const AppInstaller::Manifest::Manifest::string_t& Pin::GetPackageId() const + { + return m_id.PackageId; + } + + std::string_view Pin::GetSourceId() const + { + return m_id.SourceId; + } + + AppInstaller::Utility::GatedVersion Pin::GetGatedVersion() const + { + THROW_HR_IF(E_NOT_VALID_STATE, m_type != PinType::Gating); + return m_gatedVersion.value(); + } +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h index 954c76a12b..aa46ad7187 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h @@ -169,6 +169,16 @@ namespace AppInstaller::Utility bool m_isEmpty = false; }; + // A range of versions indicated by a version and optionally a wildcard at the end. + struct GatedVersion + { + GatedVersion(); + GatedVersion(std::string_view s); + std::string ToString() const; + private: + + }; + // A channel string; existing solely to give a type. // // Compared lexicographically. diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h new file mode 100644 index 0000000000..39daab92e8 --- /dev/null +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace AppInstaller::Pinning +{ + enum class PinType + { + Unknown, + // The package is blocked from 'upgrade --all' and 'upgrade '. + // User has to unblock to allow update. + Blocking, + // The package is excluded from 'upgrade --all', unless '--include-pinned' is added. + // 'upgrade ' is not blocked. + Pinning, + // The package is pinned to a specific version. + Gating, + }; + + std::string_view ToString(PinType type); + + // The set of values needed to uniquely identify a Pin + struct PinKey + { + PinKey() {} + PinKey(const Manifest::Manifest::string_t& packageId, std::string_view sourceId) + : PackageId(packageId), SourceId(sourceId) {} + + Manifest::Manifest::string_t PackageId; + std::string SourceId; + }; + + struct Pin + { + static Pin CreateBlockingPin(PinKey&& pinKey); + static Pin CreatePinningPin(PinKey&& pinKey); + static Pin CreateGatingPin(PinKey&& pinKey, Utility::GatedVersion gatedVersion); + + PinType GetType() const; + const PinKey& GetKey() const; + const Manifest::Manifest::string_t& GetPackageId() const; + std::string_view GetSourceId() const; + + // Only available for PinType Gating + Utility::GatedVersion GetGatedVersion() const; + + private: + Pin(PinType type, PinKey&& pinKey, std::optional gatedVersion = {}) + : m_type(type), m_id(std::move(pinKey)), m_gatedVersion(gatedVersion) {} + + PinType m_type = PinType::Unknown; + PinKey m_id; + std::optional m_gatedVersion; + }; +} diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 2f97d0b62e..fad805b911 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -237,6 +237,7 @@ + @@ -273,8 +274,11 @@ + + + @@ -339,6 +343,7 @@ + @@ -361,6 +366,8 @@ + + diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index 07bfc49bc3..8ffbdc73ca 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -76,6 +76,9 @@ {d9d70cf5-ce04-4db2-a0ec-970dd0ad22b6} + + {f05e19bb-2161-4ab0-9d04-2dfa2d3eb3c6} + @@ -339,6 +342,18 @@ Rest\Schema + + Microsoft\Schema + + + Microsoft + + + Microsoft\Schema\Pinning_1_0 + + + Microsoft\Schema\Pinning_1_0 + @@ -539,6 +554,15 @@ Source Files + + Source Files + + + Source Files + + + Microsoft\Schema\Pinning_1_0 + diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp new file mode 100644 index 0000000000..ca6ffd5e22 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinningIndex.h" +#include "SQLiteStorageBase.h" +#include "Schema/Pinning_1_0/PinningIndexInterface.h" + +namespace AppInstaller::Repository::Microsoft +{ + PinningIndex PinningIndex::CreateNew(const std::string& filePath, Schema::Version version) + { + AICLI_LOG(Repo, Info, << "Creating new Pinning Index [" << version << "] at '" << filePath << "'"); + PinningIndex result{ filePath, version }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "pinningIndex_createNew"); + + // Use calculated version, as incoming version could be 'latest' + result.m_version.SetSchemaVersion(result.m_dbconn); + + result.m_interface->CreateTables(result.m_dbconn); + + result.SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + PinningIndex::IdType PinningIndex::AddPin(const Pinning::Pin& pin) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Adding Pin for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "] with pin type " << Pinning::ToString(pin.GetType())); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningIndex_addPin"); + + IdType result = m_interface->AddPin(m_dbconn, pin); + + SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + void PinningIndex::RemovePin(const Pinning::PinKey& pinKey) + { + AICLI_LOG(Repo, Verbose, << "Removing Pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); + std::lock_guard lockInterface{ *m_interfaceLock }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningIndex_removePin"); + + m_interface->RemovePin(m_dbconn, pinKey); + + SetLastWriteTime(); + + savepoint.Commit(); + } + + std::optional PinningIndex::GetPin(const Pinning::PinKey& pinKey) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetPin(m_dbconn, pinKey); + } + + std::vector PinningIndex::GetAllPins(SQLite::Connection& connection) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetAllPins(connection); + } + + std::unique_ptr PinningIndex::CreateIPinningIndex() const + { + if (m_version == Schema::Version{ 1, 0 } || + m_version.MajorVersion == 1 || + m_version.IsLatest()) + { + return std::make_unique(); + } + + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + + PinningIndex::PinningIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : + SQLiteStorageBase(target, disposition, std::move(indexFile)) + { + AICLI_LOG(Repo, Info, << "Opened Pinning Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); + m_interface = CreateIPinningIndex(); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLiteStorageBase::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); + } + + PinningIndex::PinningIndex(const std::string& target, Schema::Version version) : SQLiteStorageBase(target, version) + { + m_interface = CreateIPinningIndex(); + m_version = m_interface->GetVersion(); + } +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h new file mode 100644 index 0000000000..73ce98ac9b --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -0,0 +1,54 @@ +#pragma once +#include "SQLiteWrapper.h" +#include "Microsoft/Schema/IPinningIndex.h" +#include "Microsoft/SQLiteStorageBase.h" +#include "winget/Pin.h" +#include + +namespace AppInstaller::Repository::Microsoft +{ + struct PinningIndex : SQLiteStorageBase + { + // An id that refers to a specific Pinning file. + using IdType = SQLite::rowid_t; + + PinningIndex(const PinningIndex&) = delete; + PinningIndex& operator=(const PinningIndex&) = delete; + + PinningIndex(PinningIndex&&) = default; + PinningIndex& operator=(PinningIndex&&) = default; + + // Creates a new PinningIndex database of the given version. + static PinningIndex CreateNew(const std::string& filePath, Schema::Version version = Schema::Version::Latest()); + + // Opens an existing PinningIndex database. + static PinningIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}) + { + return { filePath, disposition, std::move(indexFile) }; + } + + // Adds a pin to the index. + IdType AddPin(const Pinning::Pin& pin); + + // Removes a pin from the index. + void RemovePin(const Pinning::PinKey& pinKey); + + // Returns the current pin for a given package if it exists. + std::optional GetPin(const Pinning::PinKey& pinKey); + + // Returns a vector containing all the existing pins. + std::vector GetAllPins(SQLite::Connection& connection); + + private: + // Constructor used to open an existing index. + PinningIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); + + // Constructor used to create a new index. + PinningIndex(const std::string& target, Schema::Version version); + + // Creates the IPinningIndex interface object for this version. + std::unique_ptr CreateIPinningIndex() const; + + std::unique_ptr m_interface; + }; +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h new file mode 100644 index 0000000000..12414a132a --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "SQLiteWrapper.h" +#include "Microsoft/Schema/Version.h" +#include "winget/Pin.h" + +namespace AppInstaller::Repository::Microsoft::Schema +{ + struct IPinningIndex + { + virtual ~IPinningIndex() = default; + + // Gets the schema version that this index interface is built for. + virtual Schema::Version GetVersion() const = 0; + + // Creates all of the version dependent tables within the database. + virtual void CreateTables(SQLite::Connection& connection) = 0; + + // Version 1.0 + // Adds a pin to the index. + virtual SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; + + // Removes a pin from the index. + virtual SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; + + // Returns the current pin for a given package if it exists. + virtual std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; + + // Returns a vector containing all the existing pins. + virtual std::vector GetAllPins(SQLite::Connection& connection) = 0; + }; +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp new file mode 100644 index 0000000000..d87abef076 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinTable.h" +#include "SQLiteStatementBuilder.h" +#include "Microsoft/Schema/IPinningIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + namespace + { + std::optional GetPinFromRow(std::string_view packageId, std::string_view sourceId, Pinning::PinType type, std::string_view gatedVersion) + { + switch (type) + { + case Pinning::PinType::Blocking: + return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); + case Pinning::PinType::Pinning: + return Pinning::Pin::CreatePinningPin({ packageId, sourceId }); + case Pinning::PinType::Gating: + return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ gatedVersion }); + default: + return {} + } + } + } + + using namespace std::string_view_literals; + static constexpr std::string_view s_PinTable_Table_Name = "pin"sv; + static constexpr std::string_view s_PinTable_PackageId_Column = "packageId"sv; + static constexpr std::string_view s_PinTable_SourceId_Column = "sourceId"sv; + static constexpr std::string_view s_PinTable_Type_Column = "type"sv; + static constexpr std::string_view s_PinTable_GatedVersion_Column = "gatedVersion"sv; + static constexpr std::string_view s_PinTable_Index = "pinIndex"sv; + + std::string_view PinTable::TableName() + { + return s_PinTable_Table_Name; + } + + void PinTable::Create(SQLite::Connection& connection) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPinTable_v1_0"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_PinTable_Table_Name).BeginColumns(); + + createTableBuilder.Column(ColumnBuilder(s_PinTable_PackageId_Column, Type::Text).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_SourceId_Column, Type::Text).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_Type_Column, Type::Int64).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_GatedVersion_Column, Type::Text)); + + createTableBuilder.EndColumns(); + createTableBuilder.Execute(connection); + + // Create an index over the pairs package,source + StatementBuilder createIndexBuilder; + createIndexBuilder.CreateUniqueIndex(s_PinTable_Index).On(s_PinTable_Table_Name).Columns({ s_PinTable_PackageId_Column, s_PinTable_SourceId_Column }); + createIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + std::optional PinTable::SelectByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) + .Where(s_PinTable_PackageId_Column).Equals(pinKey.PackageId) + .And(s_PinTable_SourceId_Column).Equals(pinKey.SourceId); + + SQLite::Statement select = builder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + SQLite::Builder::StatementBuilder builder; + builder.InsertInto(s_PinTable_Table_Name) + .Columns({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_GatedVersion_Column }) + .Values( + pin.GetPackageId(), + pin.GetSourceId(), + pin.GetType(), + pin.GetType() == Pinning::PinType::Gating ? pin.GetGatedVersion().ToString() : ""); + + builder.Execute(connection); + return connection.GetLastInsertRowID(); + + } + + SQLite::rowid_t PinTable::RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); + builder.Execute(connection); + } + + std::optional PinTable::GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_GatedVersion_Column }) + .From(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); + + SQLite::Statement select = builder.Prepare(connection); + + if (!select.Step()) + { + return {}; + } + + auto [packageId, sourceId, type, gatedVersion] = select.GetRow(); + return GetPinFromRow(packageId, sourceId, type, gatedVersion); + } + + std::vector PinTable::GetAllPins(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_GatedVersion_Column }) + .From(s_PinTable_Table_Name); + + SQLite::Statement select = builder.Prepare(connection); + + std::vector pins; + while (select.Step()) + { + auto [packageId, sourceId, type, gatedVersion] = select.GetRow(); + auto pin = GetPinFromRow(packageId, sourceId, type, gatedVersion); + if (pin) { + pins.push_back(std::move(pin.value())); + } + } + + return pins; + } +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h new file mode 100644 index 0000000000..bd4d3ad6d3 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "SQLiteWrapper.h" +#include "SQLiteStatementBuilder.h" +#include "Microsoft/Schema/IPinningIndex.h" +#include + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + // A table + struct PinTable + { + // Get the table name. + static std::string_view TableName(); + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection); + + static std::optional SelectByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); + static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); + static SQLite::rowid_t RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); + static std::optional GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId); + static std::vector GetAllPins(SQLite::Connection& connection); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h new file mode 100644 index 0000000000..862e8ce1ac --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/IPinningIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + struct PinningIndexInterface : public IPinningIndex + { + // Version 1.0 + Schema::Version GetVersion() const override; + void CreateTables(SQLite::Connection& connection) override; + + private: + SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; + + // Removes a pin from the index. + SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; + + // Returns the current pin for a given package if it exists. + std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; + + // Returns a vector containing all the existing pins. + std::vector GetAllPins(SQLite::Connection& connection) override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp new file mode 100644 index 0000000000..44beb525d9 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h" +#include "Microsoft/Schema/Pinning_1_0/PinTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + namespace + { + std::optional GetExistingPinId(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto result = PinTable::SelectByPinKey(connection, pinKey); + + if (!result) + { + AICLI_LOG(Repo, Verbose, << "Did not find a pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); + } + + return result; + } + + } + + // Version 1.0 + Schema::Version PinningIndexInterface::GetVersion() const + { + return { 1, 0 }; + } + + void CreateTables(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpinningtables_v1_0"); + Pinning_V1_0::PinTable::Create(connection); + savepoint.Commit(); + } + + SQLite::rowid_t PinningIndexInterface::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + auto existingPin = GetExistingPinId(connection, pin.GetKey()); + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPin.has_value()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_0"); + SQLite::rowid_t pinId = PinTable::AddPin(connection, pin); + + savepoint.Commit(); + return pinId; + } + + SQLite::rowid_t PinningIndexInterface::RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto existingPinId = GetExistingPinId(connection, pinKey); + + // If the pin doesn't exist, fail the remove + THROW_HR_IF(E_NOT_SET, !existingPinId); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removepin_v1_0"); + PinTable::RemovePinById(connection, existingPinId.value()); + + savepoint.Commit(); + return existingPinId.value(); + } + + std::optional PinningIndexInterface::GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto existingPinId = GetExistingPinId(connection, pinKey); + + if (!existingPinId) + { + return {}; + } + + return PinTable::GetPinById(connection, existingPinId.value()); + } + + std::vector PinningIndexInterface::GetAllPins(SQLite::Connection& connection) + { + return PinTable::GetAllPins(connection); + } +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp index 2efca323fd..247ec33689 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp @@ -172,7 +172,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 portableFile.SymlinkTarget = std::move(symlinkTarget); result.emplace_back(std::move(portableFile)); } - + return result; } } \ No newline at end of file From a24c0de65a72bb26adfb11c1bf584fd91774e0a1 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Wed, 14 Dec 2022 16:34:56 -0800 Subject: [PATCH 02/55] Complete pinning index; implement workflow tasks --- .../AppInstallerCLICore.vcxproj | 1 + .../AppInstallerCLICore.vcxproj.filters | 5 +- .../Commands/PinCommand.cpp | 22 +- .../ExecutionContextData.h | 19 ++ src/AppInstallerCLICore/Resources.h | 8 + src/AppInstallerCLICore/Workflows/PinFlow.cpp | 223 ++++++++++++++++++ src/AppInstallerCLICore/Workflows/PinFlow.h | 46 ++++ .../Shared/Strings/en-us/winget.resw | 26 ++ src/AppInstallerCommonCore/Errors.cpp | 2 + src/AppInstallerCommonCore/Pin.cpp | 21 +- .../Public/AppInstallerErrors.h | 1 + .../Public/AppInstallerRuntime.h | 2 + .../Public/AppInstallerVersions.h | 13 +- .../Public/winget/Pin.h | 15 +- .../Microsoft/PinningIndex.cpp | 28 ++- .../Microsoft/PinningIndex.h | 7 +- .../Microsoft/Schema/IPinningIndex.h | 7 + .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 38 ++- .../Microsoft/Schema/Pinning_1_0/PinTable.h | 4 +- .../Pinning_1_0/PinningIndexInterface.h | 8 +- .../Pinning_1_0/PinningIndexInterface_1_0.cpp | 29 ++- 21 files changed, 487 insertions(+), 38 deletions(-) create mode 100644 src/AppInstallerCLICore/Workflows/PinFlow.cpp diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index effaa5d8b7..e43c58fb68 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -341,6 +341,7 @@ + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index 53e912fc82..f2983e3f44 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -349,8 +349,11 @@ Source Files + + Workflows + - Source Files + Commands diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index bb13d7deec..3b93c96d80 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -120,9 +120,11 @@ namespace AppInstaller::CLI context << Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << - Workflow::SearchSourceForMany << + Workflow::SearchSourceForSingle << Workflow::HandleSearchResultFailures << - Workflow::EnsureMatchesFromSearchResult(true) << + Workflow::EnsureOneMatchFromSearchResult(false) << + Workflow::ReportPackageIdentity << + Workflow::OpenPinningIndex << Workflow::SearchPin << Workflow::AddPin; } @@ -189,9 +191,11 @@ namespace AppInstaller::CLI context << Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << - Workflow::SearchSourceForMany << + Workflow::SearchSourceForSingle << Workflow::HandleSearchResultFailures << - Workflow::EnsureMatchesFromSearchResult(true) << + Workflow::EnsureOneMatchFromSearchResult(false) << + Workflow::ReportPackageIdentity << + Workflow::OpenPinningIndex << Workflow::SearchPin << Workflow::RemovePin; } @@ -257,11 +261,11 @@ namespace AppInstaller::CLI { // TODO context << + Workflow::OpenPinningIndex << + Workflow::GetAllPins << Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << - Workflow::SearchSourceForMany << - Workflow::HandleSearchResultFailures << - Workflow::SearchPins << + Workflow::CrossReferencePinsWithSource << Workflow::ReportPins; } @@ -289,6 +293,8 @@ namespace AppInstaller::CLI void PinResetCommand::ExecuteInternal(Execution::Context& context) const { - context << Workflow::ResetAllPins; + context << + Workflow::OpenPinningIndex << + Workflow::ResetAllPins; } } diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h index 9ab78e1873..6011e4b397 100644 --- a/src/AppInstallerCLICore/ExecutionContextData.h +++ b/src/AppInstallerCLICore/ExecutionContextData.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "CompletionData.h" #include "PackageCollection.h" #include "PortableInstaller.h" @@ -16,6 +17,10 @@ #include #include +namespace AppInstaller::Repository::Microsoft +{ + struct PinningIndex; +} namespace AppInstaller::CLI::Execution { @@ -55,6 +60,8 @@ namespace AppInstaller::CLI::Execution AllowedArchitectures, AllowUnknownScope, PortableInstaller, + PinningIndex, + Pins, Max }; @@ -229,5 +236,17 @@ namespace AppInstaller::CLI::Execution { using value_t = CLI::Portable::PortableInstaller; }; + + template <> + struct DataMapping + { + using value_t = std::shared_ptr; + }; + + template <> + struct DataMapping + { + using value_t = std::vector; + }; } } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 1122c02259..ffd536b62f 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -98,6 +98,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(GatedVersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(GatedVersion); WINGET_DEFINE_RESOURCE_STRINGID(GetManifestResultVersionNotFound); WINGET_DEFINE_RESOURCE_STRINGID(HashCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(HashCommandShortDescription); @@ -242,14 +243,21 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PinAddBlockingArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinAdded); + WINGET_DEFINE_RESOURCE_STRINGID(PinAlreadyExists); WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinExistsOverwriting); + WINGET_DEFINE_RESOURCE_STRINGID(PinExistsUseForceArg); WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetUseForceArg); + WINGET_DEFINE_RESOURCE_STRINGID(PinType); WINGET_DEFINE_RESOURCE_STRINGID(PoliciesDisabled); WINGET_DEFINE_RESOURCE_STRINGID(PoliciesEnabled); WINGET_DEFINE_RESOURCE_STRINGID(PoliciesPolicy); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp new file mode 100644 index 0000000000..52cccf8138 --- /dev/null +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Resources.h" +#include "PinFlow.h" +#include "TableOutput.h" +#include "Microsoft/PinningIndex.h" +#include "Microsoft/SQLiteStorageBase.h" +#include "winget/RepositorySearch.h" + +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Microsoft; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + // Creates a Pin appropriate for the context based on the arguments provided + Pinning::Pin CreatePin(Execution::Context& context, std::string_view packageId, std::string_view sourceId) + { + if (context.Args.Contains(Execution::Args::Type::BlockingPin)) + { + return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); + } + else if (context.Args.Contains(Execution::Args::Type::GatedVersion)) + { + return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, context.Args.GetArg(Execution::Args::Type::GatedVersion)); + } + else + { + return Pinning::Pin::CreatePinningPin({ packageId, sourceId }); + } + } + } + + void OpenPinningIndex(Execution::Context& context) + { + auto indexPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; + + AICLI_LOG(CLI, Info, << "Openning pinning index"); + auto pinningIndex = std::filesystem::exists(indexPath) ? + PinningIndex::Open(indexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite) : + PinningIndex::CreateNew(indexPath.u8string()); + + context.Add(std::make_shared(std::move(pinningIndex))); + } + + void GetAllPins(Execution::Context& context) + { + AICLI_LOG(CLI, Info, << "Getting all existing pins"); + context.Add(context.Get()->GetAllPins()); + } + + void SearchPin(Execution::Context& context) + { + AICLI_LOG(CLI, Info, << "SEARCHING PIN"); + auto package = context.Get(); + std::vector pins; + + // TODO: We should support querying the multiple sources for a package, instead of just one + auto availableVersion = package->GetLatestAvailableVersion(); + + auto pinningIndex = context.Get(); + auto pin = pinningIndex->GetPin({ + availableVersion->GetProperty(PackageVersionProperty::Id).get(), + availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }); + if (pin) + { + pins.emplace_back(std::move(pin.value())); + } + + context.Add(std::move(pins)); + } + + // Adds a pin for the current package. + // A separate pin will be added for each source. + // Required Args: None + // Inputs: PinningIndex, Package + // Outputs: None + void AddPin(Execution::Context& context) + { + auto package = context.Get(); + std::vector pins; + + // TODO: We should support querying the multiple sources for a package, instead of just one + auto availableVersion = package->GetLatestAvailableVersion(); + + Pinning::PinKey pinKey{ + availableVersion->GetProperty(PackageVersionProperty::Id).get(), + availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; + auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId); + AICLI_LOG(CLI, Info, << "Adding pin with type " << ToString(pin.GetType()) << " for package[" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]"); + + auto pinningIndex = context.Get(); + auto existingPin = pinningIndex->GetPin(pinKey); + + if (existingPin) + { + // Pin already exists. + // If it is the same, we do nothing. If it is different, check for the --force arg + if (pin == existingPin) + { + AICLI_LOG(CLI, Info, << "Pin already exists"); + context.Reporter.Info() << Resource::String::PinAlreadyExists << std::endl; + return; + } + + AICLI_LOG(CLI, Info, << "Another pin already exists for the package"); + if (context.Args.Contains(Execution::Args::Type::Force)) + { + AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); + context.Reporter.Warn() << Resource::String::PinExistsOverwriting << std::endl; + pinningIndex->UpdatePin(pin); + } + else + { + context.Reporter.Error() << Resource::String::PinExistsUseForceArg << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); + } + } + else + { + pinningIndex->AddPin(pin); + AICLI_LOG(CLI, Info, << "Finished adding pin"); + context.Reporter.Info() << Resource::String::PinAdded << std::endl; + } + } + + // Removes all the pins associated with a package. + void RemovePin(Execution::Context& context) + { + auto package = context.Get(); + std::vector pins; + + // TODO: We should support querying the multiple sources for a package, instead of just one + auto availableVersion = package->GetLatestAvailableVersion(); + + auto pinningIndex = context.Get(); + Pinning::PinKey pinKey{ + availableVersion->GetProperty(PackageVersionProperty::Id).get(), + availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; + if (!pinningIndex->GetPin(pinKey)) + { + // TODO: Report pin not found + } + + pinningIndex->RemovePin(pinKey); + } + + // Report the pins in a table. + // Required Args: None + // Inputs: Pins + // Outputs: None + void ReportPins(Execution::Context& context) + { + // TODO: Use package and source names + Execution::TableOutput<4> table(context.Reporter, + { + Resource::String::SearchId, + Resource::String::SearchSource, + Resource::String::PinType, + Resource::String::GatedVersion + }); + + for (const auto& pin : context.Get()) + { + // TODO: Avoid these conversions to string + table.OutputLine({ + pin.GetPackageId(), + std::string{ pin.GetSourceId() }, + std::string{ ToString(pin.GetType()) }, + pin.GetType() == Pinning::PinType::Gating ? pin.GetGatedVersion().ToString() : "", + }); + } + + table.Complete(); + } + + // Resets all the existing pins. + // Required Args: None + // Inputs: PinningIndex + // Outputs: None + void ResetAllPins(Execution::Context& context) + { + AICLI_LOG(CLI, Info, << "Resetting all pins"); + if (context.Args.Contains(Execution::Args::Type::Force)) + { + context.Reporter.Info() << Resource::String::PinResettingAll << std::endl; + auto pinningIndex = context.Get(); + pinningIndex->ResetAllPins(); + } + else + { + AICLI_LOG(CLI, Info, << "--force argument is not present"); + context.Reporter.Info() << Resource::String::PinResetUseForceArg << std::endl; + + // TODO: Report pins here + } + } + + // Updates the list of pins to include only those matching the current open source + // Required Args: None + // Inputs: Pins, Source + // Outputs: None + void CrossReferencePinsWithSource(Execution::Context& context) + { + const auto& pins = context.Get(); + const auto& source = context.Get(); + + std::vector matchingPins; + std::copy_if(pins.begin(), pins.end(), std::back_inserter(matchingPins), [&](Pinning::Pin pin) { + // TODO: Filter to source + SearchRequest searchRequest; + searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pin.GetPackageId()); + auto searchResult = source.Search(searchRequest); + + return !searchResult.Matches.empty(); + }); + + context.Add(std::move(matchingPins)); + } + +} \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h index 504ed5782e..bc03696ad8 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.h +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -5,4 +5,50 @@ namespace AppInstaller::CLI::Workflow { + // Opens the pinning index for use in future operations. + // Required Args: None + // Inputs: None + // Outputs: PinningIndex + void OpenPinningIndex(Execution::Context& context); + + // Gets all the pins from the index. + // Required Args: None + // Inputs: PinningIndex + // Outputs: Pins + void GetAllPins(Execution::Context& context); + + // Searches for all the pins associated with a package. + // There may be several if a package is available from multiple sources. + // Required Args: None + // Inputs: PinningIndex, Package + // Outputs Pins + void SearchPin(Execution::Context& context); + + // Adds a pin for the current package. + // A separate pin will be added for each source. + // Required Args: None + // Inputs: PinningIndex, Package + // Outputs: None + void AddPin(Execution::Context& context); + + // Removes all the pins associated with a package. + void RemovePin(Execution::Context& context); + + // Report the pins in a table. + // Required Args: None + // Inputs: Pins + // Outputs: None + void ReportPins(Execution::Context& context); + + // Resets all the existing pins. + // Required Args: None + // Inputs: PinningIndex + // Outputs: None + void ResetAllPins(Execution::Context& context); + + // Updates the list of pins to include only those matching the current open source + // Required Args: None + // Inputs: Pins, Source + // Outputs: None + void CrossReferencePinsWithSource(Execution::Context& context); } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 73044e944f..cafb90b948 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1526,4 +1526,30 @@ Please specify one of them using the --source option to proceed. Block from updating until the pin is removed, preventing override arguments + + Gated version + + + Pin added successfully + + + The pin already exists + + + A pin already exists for this package. Overwriting due to the --force argument. + {Locked="--force"} + + + A pin already exists for this package. Use the --force argument to overwrite it. + {Locked="--force"} + + + Resetting all current pins. + + + Use the --force argument to reset all pins. + + + Pin type + \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp index 0dce355f0e..dbc4738e36 100644 --- a/src/AppInstallerCommonCore/Errors.cpp +++ b/src/AppInstallerCommonCore/Errors.cpp @@ -208,6 +208,8 @@ namespace AppInstaller return "Archive malware scan failed."; case APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED: return "Found at least one version of the package installed."; + case APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS: + return "A pin already exists for the package."; // Install errors case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: diff --git a/src/AppInstallerCommonCore/Pin.cpp b/src/AppInstallerCommonCore/Pin.cpp index 9ca422785a..5a551d003b 100644 --- a/src/AppInstallerCommonCore/Pin.cpp +++ b/src/AppInstallerCommonCore/Pin.cpp @@ -45,17 +45,17 @@ namespace AppInstaller::Pinning const PinKey& Pin::GetKey() const { - return m_id; + return m_key; } const AppInstaller::Manifest::Manifest::string_t& Pin::GetPackageId() const { - return m_id.PackageId; + return m_key.PackageId; } std::string_view Pin::GetSourceId() const { - return m_id.SourceId; + return m_key.SourceId; } AppInstaller::Utility::GatedVersion Pin::GetGatedVersion() const @@ -63,4 +63,19 @@ namespace AppInstaller::Pinning THROW_HR_IF(E_NOT_VALID_STATE, m_type != PinType::Gating); return m_gatedVersion.value(); } + + bool Pin::operator==(const Pin& other) const + { + if (m_type != other.m_type || m_key != other.m_key) + { + return false; + } + + if (m_type != PinType::Gating) + { + return false; + } + + return m_gatedVersion == other.m_gatedVersion; + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h index 130a556b0b..7aee237d29 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h @@ -110,6 +110,7 @@ #define APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED ((HRESULT)0x8A15005F) #define APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED ((HRESULT)0x8A150060) #define APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED ((HRESULT)0x8A150061) +#define APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS ((HRESULT)0x8A150062) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h index 451a2fc33b..56f3cdd19e 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -62,6 +62,8 @@ namespace AppInstaller::Runtime PortableLinksUserLocation, // The location where symlinks to portable packages are stored under machine scope. PortableLinksMachineLocation, + + PinningIndex, }; // The principal that an ACE applies to. diff --git a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h index aa46ad7187..6b4a01cd0c 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h @@ -172,11 +172,16 @@ namespace AppInstaller::Utility // A range of versions indicated by a version and optionally a wildcard at the end. struct GatedVersion { - GatedVersion(); - GatedVersion(std::string_view s); - std::string ToString() const; - private: + // TODO + // For now, using dummy implementation that just holds a string + GatedVersion() {} + GatedVersion(std::string_view s) : m_tmp(s) {} + std::string ToString() const { return m_tmp; } + bool operator==(const GatedVersion& other) const { return m_tmp == other.m_tmp; } + + private: + std::string m_tmp; }; // A channel string; existing solely to give a type. diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h index 39daab92e8..cee3b5e302 100644 --- a/src/AppInstallerCommonCore/Public/winget/Pin.h +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -28,6 +28,15 @@ namespace AppInstaller::Pinning PinKey(const Manifest::Manifest::string_t& packageId, std::string_view sourceId) : PackageId(packageId), SourceId(sourceId) {} + bool operator==(const PinKey& other) const + { + return PackageId == other.PackageId && SourceId == other.SourceId; + } + bool operator!=(const PinKey& other) const + { + return !(*this == other); + } + Manifest::Manifest::string_t PackageId; std::string SourceId; }; @@ -46,12 +55,14 @@ namespace AppInstaller::Pinning // Only available for PinType Gating Utility::GatedVersion GetGatedVersion() const; + bool operator==(const Pin& other) const; + private: Pin(PinType type, PinKey&& pinKey, std::optional gatedVersion = {}) - : m_type(type), m_id(std::move(pinKey)), m_gatedVersion(gatedVersion) {} + : m_type(type), m_key(std::move(pinKey)), m_gatedVersion(gatedVersion) {} PinType m_type = PinType::Unknown; - PinKey m_id; + PinKey m_key; std::optional m_gatedVersion; }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index ca6ffd5e22..b27104dd6d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -42,6 +42,24 @@ namespace AppInstaller::Repository::Microsoft return result; } + bool PinningIndex::UpdatePin(const Pinning::Pin& pin) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Updating Pin for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "] with pin type " << Pinning::ToString(pin.GetType())); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningIndex_updatePin"); + + bool result = m_interface->UpdatePin(m_dbconn, pin).first; + + if (result) + { + SetLastWriteTime(); + savepoint.Commit(); + } + + return result; + } + void PinningIndex::RemovePin(const Pinning::PinKey& pinKey) { AICLI_LOG(Repo, Verbose, << "Removing Pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); @@ -62,10 +80,16 @@ namespace AppInstaller::Repository::Microsoft return m_interface->GetPin(m_dbconn, pinKey); } - std::vector PinningIndex::GetAllPins(SQLite::Connection& connection) + std::vector PinningIndex::GetAllPins() + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetAllPins(m_dbconn); + } + + void PinningIndex::ResetAllPins() { std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->GetAllPins(connection); + m_interface->ResetAllPins(m_dbconn); } std::unique_ptr PinningIndex::CreateIPinningIndex() const diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h index 73ce98ac9b..e35ff91366 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -30,6 +30,9 @@ namespace AppInstaller::Repository::Microsoft // Adds a pin to the index. IdType AddPin(const Pinning::Pin& pin); + // Updates a pin type, and gated version if needed + bool UpdatePin(const Pinning::Pin& pin); + // Removes a pin from the index. void RemovePin(const Pinning::PinKey& pinKey); @@ -37,7 +40,9 @@ namespace AppInstaller::Repository::Microsoft std::optional GetPin(const Pinning::PinKey& pinKey); // Returns a vector containing all the existing pins. - std::vector GetAllPins(SQLite::Connection& connection); + std::vector GetAllPins(); + + void ResetAllPins(); private: // Constructor used to open an existing index. diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h index 12414a132a..732db3ba7e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h @@ -21,6 +21,10 @@ namespace AppInstaller::Repository::Microsoft::Schema // Adds a pin to the index. virtual SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; + // Updates an existing pin in the index. + // The return value indicates whether the index was modified by the function. + virtual std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; + // Removes a pin from the index. virtual SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; @@ -29,5 +33,8 @@ namespace AppInstaller::Repository::Microsoft::Schema // Returns a vector containing all the existing pins. virtual std::vector GetAllPins(SQLite::Connection& connection) = 0; + + // Removes all the pins from the index + virtual void ResetAllPins(SQLite::Connection& connection) = 0; }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index d87abef076..c12c108a9d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -20,7 +20,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 case Pinning::PinType::Gating: return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ gatedVersion }); default: - return {} + return {}; } } } @@ -65,10 +65,12 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 std::optional PinTable::SelectByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) { + // TODO: The statement builder requires that the bound parameters can be converted to T&&, + // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) - .Where(s_PinTable_PackageId_Column).Equals(pinKey.PackageId) - .And(s_PinTable_SourceId_Column).Equals(pinKey.SourceId); + .Where(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) + .And(s_PinTable_SourceId_Column).Equals((std::string_view)pinKey.SourceId); SQLite::Statement select = builder.Prepare(connection); @@ -84,6 +86,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) { + // TODO: The statement builder requires that the bound parameters can be converted to T&&, + // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.InsertInto(s_PinTable_Table_Name) .Columns({ @@ -92,17 +96,32 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 s_PinTable_Type_Column, s_PinTable_GatedVersion_Column }) .Values( - pin.GetPackageId(), - pin.GetSourceId(), + (std::string_view)pin.GetPackageId(), + (std::string_view)pin.GetSourceId(), pin.GetType(), pin.GetType() == Pinning::PinType::Gating ? pin.GetGatedVersion().ToString() : ""); builder.Execute(connection); return connection.GetLastInsertRowID(); + } + bool PinTable::UpdatePin(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) + { + // TODO: The statement builder requires that the bound parameters can be converted to T&&, + // but the package/source IDs go as const T&. Find a way to avoid the weird casting. + SQLite::Builder::StatementBuilder builder; + builder.Update(s_PinTable_Table_Name).Set() + .Column(s_PinTable_PackageId_Column).Equals((std::string_view)pin.GetPackageId()) + .Column(s_PinTable_SourceId_Column).Equals((std::string_view)pin.GetSourceId()) + .Column(s_PinTable_Type_Column).Equals(pin.GetType()) + .Column(s_PinTable_GatedVersion_Column).Equals(pin.GetType() == Pinning::PinType::Gating ? pin.GetGatedVersion().ToString() : "") + .Where(SQLite::RowIDName).Equals(pinId); + + builder.Execute(connection); + return connection.GetChanges() != 0; } - SQLite::rowid_t PinTable::RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId) + void PinTable::RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId) { SQLite::Builder::StatementBuilder builder; builder.DeleteFrom(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); @@ -154,4 +173,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 return pins; } + + void PinTable::ResetAllPins(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PinTable_Table_Name); + builder.Execute(connection); + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h index bd4d3ad6d3..9ba338c175 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h @@ -19,8 +19,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 static std::optional SelectByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); - static SQLite::rowid_t RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); + static bool UpdatePin(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); + static void RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); static std::optional GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId); static std::vector GetAllPins(SQLite::Connection& connection); + static void ResetAllPins(SQLite::Connection& connection); }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h index 862e8ce1ac..c6560d5534 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h @@ -13,14 +13,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 private: SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; - - // Removes a pin from the index. + std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) override; SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; - - // Returns the current pin for a given package if it exists. std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; - - // Returns a vector containing all the existing pins. std::vector GetAllPins(SQLite::Connection& connection) override; + void ResetAllPins(SQLite::Connection& connection) override; }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp index 44beb525d9..446aee5588 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -28,9 +28,9 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 return { 1, 0 }; } - void CreateTables(SQLite::Connection& connection) + void PinningIndexInterface::CreateTables(SQLite::Connection& connection) { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpinningtables_v1_0"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPinningTables"); Pinning_V1_0::PinTable::Create(connection); savepoint.Commit(); } @@ -41,13 +41,27 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPin.has_value()); - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_0"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addPin_v1_0"); SQLite::rowid_t pinId = PinTable::AddPin(connection, pin); savepoint.Commit(); return pinId; } + std::pair PinningIndexInterface::UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + auto existingPinId = GetExistingPinId(connection, pin.GetKey()); + + // If the pin doesn't exist, fail the update + THROW_HR_IF(E_NOT_SET, !existingPinId); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addPin_v1_0"); + bool status = PinTable::UpdatePin(connection, existingPinId.value(), pin); + + savepoint.Commit(); + return { status, existingPinId.value() }; + } + SQLite::rowid_t PinningIndexInterface::RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) { auto existingPinId = GetExistingPinId(connection, pinKey); @@ -55,7 +69,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // If the pin doesn't exist, fail the remove THROW_HR_IF(E_NOT_SET, !existingPinId); - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removepin_v1_0"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removePin_v1_0"); PinTable::RemovePinById(connection, existingPinId.value()); savepoint.Commit(); @@ -78,4 +92,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 { return PinTable::GetAllPins(connection); } + + void PinningIndexInterface::ResetAllPins(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "resetPins_v1_0"); + PinTable::ResetAllPins(connection); + savepoint.Commit(); + } } \ No newline at end of file From 964a2014ec3d4541926d96ef889ea6c247aa40a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Thu, 15 Dec 2022 09:48:45 -0800 Subject: [PATCH 03/55] Apply suggestions from code review Co-authored-by: Kaleb Luedtke --- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 52cccf8138..8c6cf7b129 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -37,7 +37,8 @@ namespace AppInstaller::CLI::Workflow { auto indexPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; - AICLI_LOG(CLI, Info, << "Openning pinning index"); + AICLI_LOG(CLI, Info, << "Opening pinning index"); + auto pinningIndex = std::filesystem::exists(indexPath) ? PinningIndex::Open(indexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite) : PinningIndex::CreateNew(indexPath.u8string()); @@ -89,7 +90,8 @@ namespace AppInstaller::CLI::Workflow availableVersion->GetProperty(PackageVersionProperty::Id).get(), availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId); - AICLI_LOG(CLI, Info, << "Adding pin with type " << ToString(pin.GetType()) << " for package[" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]"); + AICLI_LOG(CLI, Info, << "Adding pin with type " << ToString(pin.GetType()) << " for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]"); + auto pinningIndex = context.Get(); auto existingPin = pinningIndex->GetPin(pinKey); From 27167a801ad11dff7ecb25f3de346133c9b4680e Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 15 Dec 2022 10:13:48 -0800 Subject: [PATCH 04/55] Spelling --- .github/actions/spelling/allow.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 2b75ac9a91..27e25d1bcc 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -253,6 +253,7 @@ INVALIDARG INVALIDSID iomanip iostream +IPinning IPortable ipmo ISAPPROVEDFOROUTPUT From ad6d007ba47d78401e5098f2c7a04e5eb3f3d804 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 15 Dec 2022 14:48:06 -0800 Subject: [PATCH 05/55] Add tests for pinning index --- .../AppInstallerCLITests.vcxproj | 1 + .../AppInstallerCLITests.vcxproj.filters | 3 + src/AppInstallerCLITests/PinningIndex.cpp | 167 ++++++++++++++++++ src/AppInstallerCommonCore/Pin.cpp | 2 +- .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 2 +- .../Microsoft/Schema/Pinning_1_0/PinTable.h | 2 +- .../Pinning_1_0/PinningIndexInterface_1_0.cpp | 2 +- 7 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 src/AppInstallerCLITests/PinningIndex.cpp diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 06eb723967..41e73bfaaa 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -214,6 +214,7 @@ + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 2ced205cba..161af5859b 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -239,6 +239,9 @@ Source Files\Common + + Source Files\Repository + diff --git a/src/AppInstallerCLITests/PinningIndex.cpp b/src/AppInstallerCLITests/PinningIndex.cpp new file mode 100644 index 0000000000..b22e97c762 --- /dev/null +++ b/src/AppInstallerCLITests/PinningIndex.cpp @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace TestCommon; +using namespace AppInstaller::Pinning; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Repository::SQLite; +using namespace AppInstaller::Repository::Microsoft::Schema; + +TEST_CASE("PinningIndexCreateLatestAndReopen", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Schema::Version versionCreated; + + // Create the index + { + PinningIndex index = PinningIndex::CreateNew(tempFile, Schema::Version::Latest()); + versionCreated = index.GetVersion(); + } + + // Reopen the index for read only + { + INFO("Trying with Read"); + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); + Schema::Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for read/write + { + INFO("Trying with ReadWrite"); + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + Schema::Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for immutable read + { + INFO("Trying with Immutable"); + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); + Schema::Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } +} + +TEST_CASE("PinningIndexAddEntryToTable", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "pkgId", "sourceId" }); + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + auto pins = Pinning_V1_0::PinTable::GetAllPins(connection); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0] == pin); + + auto pinFromIndex = Pinning_V1_0::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex.value() == pin); + + REQUIRE(pinFromIndex->GetType() == pin.GetType()); + REQUIRE(pinFromIndex->GetPackageId() == pin.GetPackageId()); + REQUIRE(pinFromIndex->GetSourceId() == pin.GetSourceId()); + } + + { + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.RemovePin(pin.GetKey()); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Pinning_V1_0::PinTable::GetAllPins(connection).empty()); + REQUIRE(!Pinning_V1_0::PinTable::GetPinById(connection, 1)); + } +} + +TEST_CASE("PinningIndex_AddUpdateRemove", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*" }); + Pin updatedPin = Pin::CreatePinningPin({ "pkgId", "srcId" }); + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + REQUIRE(index.UpdatePin(updatedPin)); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + auto pinFromIndex = Pinning_V1_0::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex.value() == updatedPin); + } + + { + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.RemovePin(updatedPin.GetKey()); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Pinning_V1_0::PinTable::GetAllPins(connection).empty()); + REQUIRE(!Pinning_V1_0::PinTable::GetPinById(connection, 1)); + } +} + +TEST_CASE("PinningIndex_ResetAll", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin1 = Pin::CreateBlockingPin({ "pkg1", "src1" }); + Pin pin2 = Pin::CreatePinningPin({ "pkg2", "src2" }); + + // Add two pins to the index, then check that they show up when queried + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin1); + index.AddPin(pin2); + + REQUIRE(index.GetAllPins().size() == 2); + REQUIRE(index.GetPin(pin1.GetKey()).has_value()); + REQUIRE(index.GetPin(pin2.GetKey()).has_value()); + REQUIRE(!index.GetPin({ "pkg", "src" }).has_value()); + + // Reset the index, then check that there are no pins + index.ResetAllPins(); + REQUIRE(index.GetAllPins().empty()); + REQUIRE(!index.GetPin(pin1.GetKey()).has_value()); + REQUIRE(!index.GetPin(pin2.GetKey()).has_value()); +} + +TEST_CASE("PinningIndex_AddDuplicatePin", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateGatingPin({ "pkg", "src" }, { "1.*" }); + + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + + REQUIRE_THROWS(index.AddPin(pin), ERROR_ALREADY_EXISTS); +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Pin.cpp b/src/AppInstallerCommonCore/Pin.cpp index 5a551d003b..8a5a521742 100644 --- a/src/AppInstallerCommonCore/Pin.cpp +++ b/src/AppInstallerCommonCore/Pin.cpp @@ -73,7 +73,7 @@ namespace AppInstaller::Pinning if (m_type != PinType::Gating) { - return false; + return true; } return m_gatedVersion == other.m_gatedVersion; diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index c12c108a9d..8700b1bb29 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -63,7 +63,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 savepoint.Commit(); } - std::optional PinTable::SelectByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + std::optional PinTable::GetByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) { // TODO: The statement builder requires that the bound parameters can be converted to T&&, // but the package/source IDs go as const T&. Find a way to avoid the weird casting. diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h index 9ba338c175..341335bb54 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h @@ -17,7 +17,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // Creates the table with named indices. static void Create(SQLite::Connection& connection); - static std::optional SelectByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); + static std::optional GetByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); static bool UpdatePin(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); static void RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp index 446aee5588..d9459529d6 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -10,7 +10,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 { std::optional GetExistingPinId(SQLite::Connection& connection, const Pinning::PinKey& pinKey) { - auto result = PinTable::SelectByPinKey(connection, pinKey); + auto result = PinTable::GetByPinKey(connection, pinKey); if (!result) { From f5ea9ce55d623dfcb1c136537644900da05864cf Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Fri, 16 Dec 2022 15:42:42 -0800 Subject: [PATCH 06/55] Store version in all types of pins --- src/AppInstallerCLICore/Resources.h | 1 - src/AppInstallerCLICore/Workflows/PinFlow.cpp | 19 ++++++----- .../Shared/Strings/en-us/winget.resw | 5 +-- src/AppInstallerCommonCore/Pin.cpp | 33 ++++++++----------- .../Public/winget/Pin.h | 15 +++++---- .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 26 +++++++-------- 6 files changed, 45 insertions(+), 54 deletions(-) diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index bd51b7427d..b782d358fd 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -98,7 +98,6 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(GatedVersionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(GatedVersion); WINGET_DEFINE_RESOURCE_STRINGID(GetManifestResultVersionNotFound); WINGET_DEFINE_RESOURCE_STRINGID(HashCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(HashCommandShortDescription); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 8c6cf7b129..db7c0e95ba 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -16,19 +16,19 @@ namespace AppInstaller::CLI::Workflow namespace { // Creates a Pin appropriate for the context based on the arguments provided - Pinning::Pin CreatePin(Execution::Context& context, std::string_view packageId, std::string_view sourceId) + Pinning::Pin CreatePin(Execution::Context& context, std::string_view packageId, std::string_view sourceId, const std::string& installedVersion) { - if (context.Args.Contains(Execution::Args::Type::BlockingPin)) + if (context.Args.Contains(Execution::Args::Type::GatedVersion)) { - return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); + return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, context.Args.GetArg(Execution::Args::Type::GatedVersion)); } - else if (context.Args.Contains(Execution::Args::Type::GatedVersion)) + else if (context.Args.Contains(Execution::Args::Type::BlockingPin)) { - return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, context.Args.GetArg(Execution::Args::Type::GatedVersion)); + return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }, { installedVersion }); } else { - return Pinning::Pin::CreatePinningPin({ packageId, sourceId }); + return Pinning::Pin::CreatePinningPin({ packageId, sourceId }, { installedVersion }); } } } @@ -89,7 +89,8 @@ namespace AppInstaller::CLI::Workflow Pinning::PinKey pinKey{ availableVersion->GetProperty(PackageVersionProperty::Id).get(), availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; - auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId); + auto installedVersion = package->GetInstalledVersion()->GetProperty(PackageVersionProperty::Version); + auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId, installedVersion); AICLI_LOG(CLI, Info, << "Adding pin with type " << ToString(pin.GetType()) << " for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]"); @@ -160,8 +161,8 @@ namespace AppInstaller::CLI::Workflow { Resource::String::SearchId, Resource::String::SearchSource, + Resource::String::SearchVersion, Resource::String::PinType, - Resource::String::GatedVersion }); for (const auto& pin : context.Get()) @@ -170,8 +171,8 @@ namespace AppInstaller::CLI::Workflow table.OutputLine({ pin.GetPackageId(), std::string{ pin.GetSourceId() }, + std::string{ pin.GetVersionString() }, std::string{ ToString(pin.GetType()) }, - pin.GetType() == Pinning::PinType::Gating ? pin.GetGatedVersion().ToString() : "", }); } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index c1d9175d04..4f46c9d2f4 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1541,9 +1541,6 @@ Please specify one of them using the --source option to proceed. User Settings - - Gated version - Pin added successfully @@ -1567,4 +1564,4 @@ Please specify one of them using the --source option to proceed. Pin type - + \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Pin.cpp b/src/AppInstallerCommonCore/Pin.cpp index 8a5a521742..acc3b2b2ba 100644 --- a/src/AppInstallerCommonCore/Pin.cpp +++ b/src/AppInstallerCommonCore/Pin.cpp @@ -3,6 +3,8 @@ #include "pch.h" #include "winget/Pin.h" +using namespace AppInstaller::Utility; + namespace AppInstaller::Pinning { using namespace std::string_view_literals; @@ -23,19 +25,19 @@ namespace AppInstaller::Pinning } } - Pin Pin::CreateBlockingPin(PinKey&& pinKey) + Pin Pin::CreateBlockingPin(PinKey&& pinKey, Version version) { - return { PinType::Blocking, std::move(pinKey) }; + return { PinType::Blocking, std::move(pinKey), version.ToString() }; } - Pin Pin::CreatePinningPin(PinKey&& pinKey) + Pin Pin::CreatePinningPin(PinKey&& pinKey, Version version) { - return { PinType::Pinning, std::move(pinKey) }; + return { PinType::Pinning, std::move(pinKey), version.ToString() }; } - Pin Pin::CreateGatingPin(PinKey&& pinKey, AppInstaller::Utility::GatedVersion gatedVersion) + Pin Pin::CreateGatingPin(PinKey&& pinKey, GatedVersion gatedVersion) { - return { PinType::Gating, std::move(pinKey), gatedVersion }; + return { PinType::Gating, std::move(pinKey), gatedVersion.ToString() }; } PinType Pin::GetType() const @@ -58,24 +60,15 @@ namespace AppInstaller::Pinning return m_key.SourceId; } - AppInstaller::Utility::GatedVersion Pin::GetGatedVersion() const + std::string_view Pin::GetVersionString() const { - THROW_HR_IF(E_NOT_VALID_STATE, m_type != PinType::Gating); - return m_gatedVersion.value(); + return m_versionString; } bool Pin::operator==(const Pin& other) const { - if (m_type != other.m_type || m_key != other.m_key) - { - return false; - } - - if (m_type != PinType::Gating) - { - return true; - } - - return m_gatedVersion == other.m_gatedVersion; + return m_type == other.m_type && + m_key == other.m_key && + m_versionString == other.m_versionString; } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h index cee3b5e302..cbca0adc1b 100644 --- a/src/AppInstallerCommonCore/Public/winget/Pin.h +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -43,8 +43,8 @@ namespace AppInstaller::Pinning struct Pin { - static Pin CreateBlockingPin(PinKey&& pinKey); - static Pin CreatePinningPin(PinKey&& pinKey); + static Pin CreateBlockingPin(PinKey&& pinKey, Utility::Version version = {}); + static Pin CreatePinningPin(PinKey&& pinKey, Utility::Version version = {}); static Pin CreateGatingPin(PinKey&& pinKey, Utility::GatedVersion gatedVersion); PinType GetType() const; @@ -52,17 +52,18 @@ namespace AppInstaller::Pinning const Manifest::Manifest::string_t& GetPackageId() const; std::string_view GetSourceId() const; - // Only available for PinType Gating - Utility::GatedVersion GetGatedVersion() const; + // TODO: For now we'll keep the versions as simply a string in all cases. + // This will change once we actually start using them. + std::string_view GetVersionString() const; bool operator==(const Pin& other) const; private: - Pin(PinType type, PinKey&& pinKey, std::optional gatedVersion = {}) - : m_type(type), m_key(std::move(pinKey)), m_gatedVersion(gatedVersion) {} + Pin(PinType type, PinKey&& pinKey, std::string_view versionString) + : m_type(type), m_key(std::move(pinKey)), m_versionString(versionString) {} PinType m_type = PinType::Unknown; PinKey m_key; - std::optional m_gatedVersion; + std::string m_versionString; }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index 8700b1bb29..0b45fc4416 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -9,16 +9,16 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 { namespace { - std::optional GetPinFromRow(std::string_view packageId, std::string_view sourceId, Pinning::PinType type, std::string_view gatedVersion) + std::optional GetPinFromRow(std::string_view packageId, std::string_view sourceId, Pinning::PinType type, const std::string& version) { switch (type) { case Pinning::PinType::Blocking: - return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); + return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }, Utility::Version{ version }); case Pinning::PinType::Pinning: - return Pinning::Pin::CreatePinningPin({ packageId, sourceId }); + return Pinning::Pin::CreatePinningPin({ packageId, sourceId }, Utility::Version{ version }); case Pinning::PinType::Gating: - return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ gatedVersion }); + return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ version }); default: return {}; } @@ -30,7 +30,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 static constexpr std::string_view s_PinTable_PackageId_Column = "packageId"sv; static constexpr std::string_view s_PinTable_SourceId_Column = "sourceId"sv; static constexpr std::string_view s_PinTable_Type_Column = "type"sv; - static constexpr std::string_view s_PinTable_GatedVersion_Column = "gatedVersion"sv; + static constexpr std::string_view s_PinTable_Version_Column = "version"sv; static constexpr std::string_view s_PinTable_Index = "pinIndex"sv; std::string_view PinTable::TableName() @@ -50,7 +50,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 createTableBuilder.Column(ColumnBuilder(s_PinTable_PackageId_Column, Type::Text).NotNull()); createTableBuilder.Column(ColumnBuilder(s_PinTable_SourceId_Column, Type::Text).NotNull()); createTableBuilder.Column(ColumnBuilder(s_PinTable_Type_Column, Type::Int64).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_GatedVersion_Column, Type::Text)); + createTableBuilder.Column(ColumnBuilder(s_PinTable_Version_Column, Type::Text)); createTableBuilder.EndColumns(); createTableBuilder.Execute(connection); @@ -94,12 +94,12 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 s_PinTable_PackageId_Column, s_PinTable_SourceId_Column, s_PinTable_Type_Column, - s_PinTable_GatedVersion_Column }) + s_PinTable_Version_Column }) .Values( (std::string_view)pin.GetPackageId(), - (std::string_view)pin.GetSourceId(), + pin.GetSourceId(), pin.GetType(), - pin.GetType() == Pinning::PinType::Gating ? pin.GetGatedVersion().ToString() : ""); + pin.GetVersionString()); builder.Execute(connection); return connection.GetLastInsertRowID(); @@ -112,9 +112,9 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 SQLite::Builder::StatementBuilder builder; builder.Update(s_PinTable_Table_Name).Set() .Column(s_PinTable_PackageId_Column).Equals((std::string_view)pin.GetPackageId()) - .Column(s_PinTable_SourceId_Column).Equals((std::string_view)pin.GetSourceId()) + .Column(s_PinTable_SourceId_Column).Equals(pin.GetSourceId()) .Column(s_PinTable_Type_Column).Equals(pin.GetType()) - .Column(s_PinTable_GatedVersion_Column).Equals(pin.GetType() == Pinning::PinType::Gating ? pin.GetGatedVersion().ToString() : "") + .Column(s_PinTable_Version_Column).Equals(pin.GetVersionString()) .Where(SQLite::RowIDName).Equals(pinId); builder.Execute(connection); @@ -135,7 +135,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 s_PinTable_PackageId_Column, s_PinTable_SourceId_Column, s_PinTable_Type_Column, - s_PinTable_GatedVersion_Column }) + s_PinTable_Version_Column }) .From(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); SQLite::Statement select = builder.Prepare(connection); @@ -156,7 +156,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 s_PinTable_PackageId_Column, s_PinTable_SourceId_Column, s_PinTable_Type_Column, - s_PinTable_GatedVersion_Column }) + s_PinTable_Version_Column }) .From(s_PinTable_Table_Name); SQLite::Statement select = builder.Prepare(connection); From 0a489764d4030b6fe9b66c7c01c443843a9df020 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 20 Dec 2022 12:31:44 -0800 Subject: [PATCH 07/55] Tests for pin add --- .../Commands/PinCommand.cpp | 2 + src/AppInstallerCLICore/Workflows/PinFlow.cpp | 10 +- .../AppInstallerCLITests.vcxproj | 1 + .../AppInstallerCLITests.vcxproj.filters | 3 + src/AppInstallerCLITests/PinFlow.cpp | 192 ++++++++++++++++++ 5 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 src/AppInstallerCLITests/PinFlow.cpp diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index 3b93c96d80..d212e910e1 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -123,6 +123,7 @@ namespace AppInstaller::CLI Workflow::SearchSourceForSingle << Workflow::HandleSearchResultFailures << Workflow::EnsureOneMatchFromSearchResult(false) << + Workflow::GetInstalledPackageVersion << Workflow::ReportPackageIdentity << Workflow::OpenPinningIndex << Workflow::SearchPin << @@ -194,6 +195,7 @@ namespace AppInstaller::CLI Workflow::SearchSourceForSingle << Workflow::HandleSearchResultFailures << Workflow::EnsureOneMatchFromSearchResult(false) << + Workflow::GetInstalledPackageVersion << Workflow::ReportPackageIdentity << Workflow::OpenPinningIndex << Workflow::SearchPin << diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index db7c0e95ba..0e2d6cbf8b 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -54,7 +54,6 @@ namespace AppInstaller::CLI::Workflow void SearchPin(Execution::Context& context) { - AICLI_LOG(CLI, Info, << "SEARCHING PIN"); auto package = context.Get(); std::vector pins; @@ -81,6 +80,12 @@ namespace AppInstaller::CLI::Workflow void AddPin(Execution::Context& context) { auto package = context.Get(); + auto installedVersion = context.Get(); + if (!installedVersion) + { + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); + } + std::vector pins; // TODO: We should support querying the multiple sources for a package, instead of just one @@ -89,8 +94,7 @@ namespace AppInstaller::CLI::Workflow Pinning::PinKey pinKey{ availableVersion->GetProperty(PackageVersionProperty::Id).get(), availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; - auto installedVersion = package->GetInstalledVersion()->GetProperty(PackageVersionProperty::Version); - auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId, installedVersion); + auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId, installedVersion->GetProperty(PackageVersionProperty::Version)); AICLI_LOG(CLI, Info, << "Adding pin with type " << ToString(pin.GetType()) << " for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]"); diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 39396da682..f6c35bb3a9 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -219,6 +219,7 @@ + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 7cb377815d..21072d3f32 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -278,6 +278,9 @@ Source Files\Repository + + Source Files\CLI + diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp new file mode 100644 index 0000000000..5a132e6611 --- /dev/null +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Pinning; + +void OverrideForOpenPinningIndex(TestContext& context, const std::filesystem::path& indexPath) +{ + context.Override({ OpenPinningIndex, [=](TestContext& context) + { + auto pinningIndex = std::filesystem::exists(indexPath) ? + PinningIndex::Open(indexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite) : + PinningIndex::CreateNew(indexPath.u8string()); + context.Add(std::make_shared(std::move(pinningIndex))); + } }); +} + +TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + // auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenPinningIndex(addContext, indexFile.GetPath()); + OverrideForCompositeInstalledSource(addContext); + addContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + SECTION("Pin is saved") + { + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Pinning); + REQUIRE(pins[0].GetPackageId() == "AppInstallerCliTest.TestExeInstaller"); + REQUIRE(pins[0].GetSourceId() == "*TestSource"); + REQUIRE(pins[0].GetVersionString() == "1.0.0.0"); + } + SECTION("Remove pin") + { + std::ostringstream pinRemoveOutput; + TestContext removeContext{ pinRemoveOutput, std::cin }; + // auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenPinningIndex(removeContext, indexFile.GetPath()); + OverrideForCompositeInstalledSource(removeContext); + removeContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + + PinRemoveCommand pinRemove({}); + pinRemove.Execute(removeContext); + INFO(pinRemoveOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.empty()); + } + SECTION("Reset pins") + { + std::ostringstream pinResetOutput; + TestContext resetContext{ pinResetOutput, std::cin }; + // auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenPinningIndex(resetContext, indexFile.GetPath()); + + SECTION("Without --force") + { + PinResetCommand pinReset({}); + pinReset.Execute(resetContext); + INFO(pinResetOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + } + SECTION("With --force") + { + resetContext.Args.AddArg(Execution::Args::Type::Force); + + PinResetCommand pinReset({}); + pinReset.Execute(resetContext); + INFO(pinResetOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.empty()); + } + } + SECTION("Update pin") + { + std::ostringstream pinUpdateOutput; + TestContext updateContext{ pinUpdateOutput, std::cin }; + // auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenPinningIndex(updateContext, indexFile.GetPath()); + OverrideForCompositeInstalledSource(updateContext); + updateContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + updateContext.Args.AddArg(Execution::Args::Type::BlockingPin); + + SECTION("Without --force") + { + PinAddCommand pinUpdate({}); + pinUpdate.Execute(updateContext); + INFO(pinUpdateOutput.str()); + REQUIRE_TERMINATED_WITH(updateContext, APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Pinning); + } + SECTION("With --force") + { + updateContext.Args.AddArg(Execution::Args::Type::Force); + + PinAddCommand pinUpdate({}); + pinUpdate.Execute(updateContext); + INFO(pinUpdateOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Blocking); + } + } +} + +TEST_CASE("PinFlow_Add_NotFound", "[PinFlow][workflow]") +{ + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + // auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(addContext); + addContext.Args.AddArg(Execution::Args::Type::Query, "This package doesn't exist"sv); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + REQUIRE_TERMINATED_WITH(addContext, APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); +} + +TEST_CASE("PinFlow_Add_NotInstalled", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + // auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenPinningIndex(addContext, indexFile.GetPath()); + OverrideForCompositeInstalledSource(addContext); + addContext.Args.AddArg(Execution::Args::Type::Query, "TestExeInstallerWithNothingInstalled"sv); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + REQUIRE_TERMINATED_WITH(addContext, APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.empty()); +} + +TEST_CASE("PinFlow_ListEmpty", "[PinFlow][workflow]") +{ +} + +TEST_CASE("PinFlow_ListMultiple", "[PinFlow][workflow]") +{ +} + +TEST_CASE("PinFlow_ListUninstalled", "[PinFlow][workflow]") +{ +} + +TEST_CASE("PinFlow_RemoveNonExisting", "[PinFlow][workflow]") +{ +} + +TEST_CASE("PinFlow_ResetEmpty", "[PinFlow][workflow]") +{ +} \ No newline at end of file From 2e8a05744be4fbd15dcae5d4a3f611ca6dd23ef2 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 20 Dec 2022 14:14:04 -0800 Subject: [PATCH 08/55] Add more tests --- .../Commands/PinCommand.cpp | 4 ++ src/AppInstallerCLICore/Resources.h | 2 + src/AppInstallerCLICore/Workflows/PinFlow.cpp | 29 ++++++-- src/AppInstallerCLICore/Workflows/PinFlow.h | 5 +- .../Shared/Strings/en-us/winget.resw | 10 ++- src/AppInstallerCLITests/PinFlow.cpp | 70 ++++++++++++++----- src/AppInstallerCommonCore/Errors.cpp | 2 + .../Public/AppInstallerErrors.h | 1 + 8 files changed, 99 insertions(+), 24 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index d212e910e1..a7b07eb3e9 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -297,6 +297,10 @@ namespace AppInstaller::CLI { context << Workflow::OpenPinningIndex << + Workflow::GetAllPins << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << + Workflow::CrossReferencePinsWithSource << Workflow::ResetAllPins; } } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index d2d10c2f20..e3f22e1b89 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -248,10 +248,12 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PinAlreadyExists); WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinDoesNotExist); WINGET_DEFINE_RESOURCE_STRINGID(PinExistsOverwriting); WINGET_DEFINE_RESOURCE_STRINGID(PinExistsUseForceArg); WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinNoPinsExist); WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 0e2d6cbf8b..a71b12d9a1 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -146,9 +146,12 @@ namespace AppInstaller::CLI::Workflow Pinning::PinKey pinKey{ availableVersion->GetProperty(PackageVersionProperty::Id).get(), availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; + AICLI_LOG(CLI, Info, << "Removing pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); if (!pinningIndex->GetPin(pinKey)) { - // TODO: Report pin not found + AICLI_LOG(CLI, Warning, << "Pin does not exist"); + context.Reporter.Warn() << Resource::String::PinDoesNotExist(pinKey.PackageId) << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); } pinningIndex->RemovePin(pinKey); @@ -160,6 +163,13 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void ReportPins(Execution::Context& context) { + const auto& pins = context.Get(); + if (pins.empty()) + { + context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; + return; + } + // TODO: Use package and source names Execution::TableOutput<4> table(context.Reporter, { @@ -169,7 +179,7 @@ namespace AppInstaller::CLI::Workflow Resource::String::PinType, }); - for (const auto& pin : context.Get()) + for (const auto& pin : pins) { // TODO: Avoid these conversions to string table.OutputLine({ @@ -193,15 +203,22 @@ namespace AppInstaller::CLI::Workflow if (context.Args.Contains(Execution::Args::Type::Force)) { context.Reporter.Info() << Resource::String::PinResettingAll << std::endl; - auto pinningIndex = context.Get(); - pinningIndex->ResetAllPins(); + + if (context.Get().empty()) + { + context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; + } + else + { + auto pinningIndex = context.Get(); + pinningIndex->ResetAllPins(); + } } else { AICLI_LOG(CLI, Info, << "--force argument is not present"); context.Reporter.Info() << Resource::String::PinResetUseForceArg << std::endl; - - // TODO: Report pins here + context << ReportPins; } } diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h index bc03696ad8..35862c4940 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.h +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -32,6 +32,9 @@ namespace AppInstaller::CLI::Workflow void AddPin(Execution::Context& context); // Removes all the pins associated with a package. + // Required Args: None + // Inputs: PinningIndex, Package, InstalledPackageVersion + // Outputs: None void RemovePin(Execution::Context& context); // Report the pins in a table. @@ -42,7 +45,7 @@ namespace AppInstaller::CLI::Workflow // Resets all the existing pins. // Required Args: None - // Inputs: PinningIndex + // Inputs: PinningIndex, Pins // Outputs: None void ResetAllPins(Execution::Context& context); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 8ebed6b48e..489ee62e83 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1623,9 +1623,17 @@ Please specify one of them using the --source option to proceed. Resetting all current pins. - Use the --force argument to reset all pins. + Use the --force argument to reset all pins. The following pins would be removed: Pin type + + There is no pin for package {0} + {Locked="{0}"} {0} is a placeholder that will be replaced by a package name. The message is shown when attempting to delete a pin for a package that is not pinned. + + + There are no pins configured. + Shown when listing or modifying existing pins if there are none. + \ No newline at end of file diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index 5a132e6611..a2b527eaa7 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -30,10 +30,10 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") std::ostringstream pinAddOutput; TestContext addContext{ pinAddOutput, std::cin }; - // auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForOpenPinningIndex(addContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(addContext); addContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + addContext.Args.AddArg(Execution::Args::Type::BlockingPin); PinAddCommand pinAdd({}); pinAdd.Execute(addContext); @@ -44,16 +44,28 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); auto pins = index.GetAllPins(); REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetType() == PinType::Pinning); + REQUIRE(pins[0].GetType() == PinType::Blocking); REQUIRE(pins[0].GetPackageId() == "AppInstallerCliTest.TestExeInstaller"); REQUIRE(pins[0].GetSourceId() == "*TestSource"); REQUIRE(pins[0].GetVersionString() == "1.0.0.0"); + + std::ostringstream pinListOutput; + TestContext listContext{ pinListOutput, std::cin }; + OverrideForOpenPinningIndex(listContext, indexFile.GetPath()); + OverrideForCompositeInstalledSource(listContext); + listContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + + PinListCommand pinList({}); + pinList.Execute(listContext); + + INFO(pinListOutput.str()); + REQUIRE(pinListOutput.str().find("AppInstallerCliTest.TestExeInstaller")); + REQUIRE(pinListOutput.str().find("Blocking")); } SECTION("Remove pin") { std::ostringstream pinRemoveOutput; TestContext removeContext{ pinRemoveOutput, std::cin }; - // auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForOpenPinningIndex(removeContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(removeContext); removeContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); @@ -70,8 +82,8 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") { std::ostringstream pinResetOutput; TestContext resetContext{ pinResetOutput, std::cin }; - // auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForOpenPinningIndex(resetContext, indexFile.GetPath()); + OverrideForCompositeInstalledSource(resetContext); SECTION("Without --force") { @@ -100,11 +112,9 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") { std::ostringstream pinUpdateOutput; TestContext updateContext{ pinUpdateOutput, std::cin }; - // auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForOpenPinningIndex(updateContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(updateContext); updateContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); - updateContext.Args.AddArg(Execution::Args::Type::BlockingPin); SECTION("Without --force") { @@ -116,7 +126,7 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); auto pins = index.GetAllPins(); REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetType() == PinType::Pinning); + REQUIRE(pins[0].GetType() == PinType::Blocking); } SECTION("With --force") { @@ -129,7 +139,7 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); auto pins = index.GetAllPins(); REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetType() == PinType::Blocking); + REQUIRE(pins[0].GetType() == PinType::Pinning); } } } @@ -138,7 +148,6 @@ TEST_CASE("PinFlow_Add_NotFound", "[PinFlow][workflow]") { std::ostringstream pinAddOutput; TestContext addContext{ pinAddOutput, std::cin }; - // auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(addContext); addContext.Args.AddArg(Execution::Args::Type::Query, "This package doesn't exist"sv); @@ -155,7 +164,6 @@ TEST_CASE("PinFlow_Add_NotInstalled", "[PinFlow][workflow]") std::ostringstream pinAddOutput; TestContext addContext{ pinAddOutput, std::cin }; - // auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForOpenPinningIndex(addContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(addContext); addContext.Args.AddArg(Execution::Args::Type::Query, "TestExeInstallerWithNothingInstalled"sv); @@ -173,20 +181,50 @@ TEST_CASE("PinFlow_Add_NotInstalled", "[PinFlow][workflow]") TEST_CASE("PinFlow_ListEmpty", "[PinFlow][workflow]") { -} + TempFile indexFile("pinningIndex", ".db"); -TEST_CASE("PinFlow_ListMultiple", "[PinFlow][workflow]") -{ -} + std::ostringstream pinListOutput; + TestContext listContext{ pinListOutput, std::cin }; + OverrideForOpenPinningIndex(listContext, indexFile.GetPath()); + OverrideForCompositeInstalledSource(listContext); -TEST_CASE("PinFlow_ListUninstalled", "[PinFlow][workflow]") -{ + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(pinListOutput.str()); + + REQUIRE(pinListOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); } TEST_CASE("PinFlow_RemoveNonExisting", "[PinFlow][workflow]") { + TempFile indexFile("pinningIndex", ".db"); + + std::ostringstream pinRemoveOutput; + TestContext removeContext{ pinRemoveOutput, std::cin }; + OverrideForOpenPinningIndex(removeContext, indexFile.GetPath()); + OverrideForCompositeInstalledSource(removeContext); + removeContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + + PinRemoveCommand pinRemove({}); + pinRemove.Execute(removeContext); + INFO(pinRemoveOutput.str()); + + REQUIRE_TERMINATED_WITH(removeContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); } TEST_CASE("PinFlow_ResetEmpty", "[PinFlow][workflow]") { + TempFile indexFile("pinningIndex", ".db"); + + std::ostringstream pinResetOutput; + TestContext resetContext{ pinResetOutput, std::cin }; + OverrideForOpenPinningIndex(resetContext, indexFile.GetPath()); + OverrideForCompositeInstalledSource(resetContext); + resetContext.Args.AddArg(Execution::Args::Type::Force); + + PinResetCommand pinReset({}); + pinReset.Execute(resetContext); + INFO(pinResetOutput.str()); + + REQUIRE(pinResetOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp index dbc4738e36..c27694fdd7 100644 --- a/src/AppInstallerCommonCore/Errors.cpp +++ b/src/AppInstallerCommonCore/Errors.cpp @@ -210,6 +210,8 @@ namespace AppInstaller return "Found at least one version of the package installed."; case APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS: return "A pin already exists for the package."; + case APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST: + return "There is no pin for the package."; // Install errors case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: diff --git a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h index 7aee237d29..0ec281229d 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h @@ -111,6 +111,7 @@ #define APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED ((HRESULT)0x8A150060) #define APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED ((HRESULT)0x8A150061) #define APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS ((HRESULT)0x8A150062) +#define APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST ((HRESULT)0x8A150063) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) From 44123df1b066881405d50767bef11c8de1d4e336 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 29 Dec 2022 16:18:27 -0800 Subject: [PATCH 09/55] Handle pins and multiple sources in composite packages --- .../Commands/PinCommand.cpp | 2 - src/AppInstallerCLICore/ExecutionArgs.h | 1 + src/AppInstallerCLICore/Resources.h | 1 + src/AppInstallerCLICore/Workflows/PinFlow.cpp | 119 ++++--- .../Workflows/UpdateFlow.cpp | 30 +- .../Workflows/WorkflowBase.cpp | 22 +- .../Shared/Strings/en-us/winget.resw | 20 +- src/AppInstallerCLITests/TestSource.cpp | 8 +- src/AppInstallerCLITests/TestSource.h | 8 +- src/AppInstallerCommonCore/Errors.cpp | 2 + src/AppInstallerCommonCore/Pin.cpp | 26 ++ .../Public/AppInstallerErrors.h | 1 + .../Public/AppInstallerVersions.h | 6 +- .../Public/winget/Pin.h | 11 +- src/AppInstallerCommonCore/Versions.cpp | 6 + .../CompositeSource.cpp | 304 ++++++++++++++---- .../Microsoft/PinningIndex.cpp | 29 ++ .../Microsoft/PinningIndex.h | 10 +- .../Microsoft/SQLiteIndexSource.cpp | 16 +- .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 6 +- .../PackageTrackingCatalog.cpp | 3 +- .../Public/winget/RepositorySearch.h | 48 ++- .../RepositorySearch.cpp | 24 -- .../Rest/RestSource.cpp | 10 +- 24 files changed, 491 insertions(+), 222 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index a7b07eb3e9..7fd1c89f79 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -116,7 +116,6 @@ namespace AppInstaller::CLI void PinAddCommand::ExecuteInternal(Execution::Context& context) const { - // TODO context << Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << @@ -261,7 +260,6 @@ namespace AppInstaller::CLI void PinListCommand::ExecuteInternal(Execution::Context& context) const { - // TODO context << Workflow::OpenPinningIndex << Workflow::GetAllPins << diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index 22aa215354..96e56ac11f 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -84,6 +84,7 @@ namespace AppInstaller::CLI::Execution // Upgrade command All, // Used in Update command to update all installed packages to latest IncludeUnknown, // Used in Upgrade command to allow upgrades of packages with unknown versions + IncludePinned, // Used in Upgrade command to allow upgrades to pinned packages (only for pinning type of pins) // Show command ListVersions, // Used in Show command to list all available versions of an app diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index e3f22e1b89..be3134808e 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -426,6 +426,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpdateAllArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicable); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeAvailableForPinned); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockingPinCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index a71b12d9a1..9202aa13df 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -35,14 +35,7 @@ namespace AppInstaller::CLI::Workflow void OpenPinningIndex(Execution::Context& context) { - auto indexPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; - - AICLI_LOG(CLI, Info, << "Opening pinning index"); - - auto pinningIndex = std::filesystem::exists(indexPath) ? - PinningIndex::Open(indexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite) : - PinningIndex::CreateNew(indexPath.u8string()); - + auto pinningIndex = PinningIndex::OpenOrCreateDefault(SQLiteStorageBase::OpenDisposition::ReadWrite); context.Add(std::make_shared(std::move(pinningIndex))); } @@ -56,17 +49,26 @@ namespace AppInstaller::CLI::Workflow { auto package = context.Get(); std::vector pins; - - // TODO: We should support querying the multiple sources for a package, instead of just one - auto availableVersion = package->GetLatestAvailableVersion(); + std::set sources; auto pinningIndex = context.Get(); - auto pin = pinningIndex->GetPin({ - availableVersion->GetProperty(PackageVersionProperty::Id).get(), - availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }); - if (pin) + + auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); + for (auto versionKey : packageVersionKeys) { - pins.emplace_back(std::move(pin.value())); + auto availableVersion = package->GetAvailableVersion(versionKey); + Pinning::PinKey pinKey{ + availableVersion->GetProperty(PackageVersionProperty::Id).get(), + availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; + + if (sources.insert(pinKey.SourceId).second) + { + auto pin = pinningIndex->GetPin(pinKey); + if (pin) + { + pins.emplace_back(std::move(pin.value())); + } + } } context.Add(std::move(pins)); @@ -86,49 +88,70 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); } - std::vector pins; - - // TODO: We should support querying the multiple sources for a package, instead of just one - auto availableVersion = package->GetLatestAvailableVersion(); - - Pinning::PinKey pinKey{ - availableVersion->GetProperty(PackageVersionProperty::Id).get(), - availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; - auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId, installedVersion->GetProperty(PackageVersionProperty::Version)); - AICLI_LOG(CLI, Info, << "Adding pin with type " << ToString(pin.GetType()) << " for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]"); + auto installedVersionString = installedVersion->GetProperty(PackageVersionProperty::Version); + std::vector pinsToAdd; + std::vector pinsToUpdate; + std::set sources; auto pinningIndex = context.Get(); - auto existingPin = pinningIndex->GetPin(pinKey); - if (existingPin) + auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); + for (auto versionKey : packageVersionKeys) { - // Pin already exists. - // If it is the same, we do nothing. If it is different, check for the --force arg - if (pin == existingPin) + auto availableVersion = package->GetAvailableVersion(versionKey); + Pinning::PinKey pinKey{ + availableVersion->GetProperty(PackageVersionProperty::Id).get(), + availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; + + if (!sources.insert(pinKey.SourceId).second) { - AICLI_LOG(CLI, Info, << "Pin already exists"); - context.Reporter.Info() << Resource::String::PinAlreadyExists << std::endl; - return; + // We already considered the pin for this source + continue; } - AICLI_LOG(CLI, Info, << "Another pin already exists for the package"); - if (context.Args.Contains(Execution::Args::Type::Force)) + auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId, installedVersionString); + AICLI_LOG(CLI, Info, << "Evaluating pin with type " << ToString(pin.GetType()) << " for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]"); + + auto existingPin = pinningIndex->GetPin(pinKey); + if (existingPin) { - AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); - context.Reporter.Warn() << Resource::String::PinExistsOverwriting << std::endl; - pinningIndex->UpdatePin(pin); + // Pin already exists. + // If it is the same, we do nothing. If it is different, check for the --force arg + // TODO #476: Add source to strings + if (pin == existingPin) + { + AICLI_LOG(CLI, Info, << "Pin already exists"); + context.Reporter.Info() << Resource::String::PinAlreadyExists << std::endl; + continue; + } + + AICLI_LOG(CLI, Info, << "Another pin already exists for the package for source " << pinKey.SourceId); + if (context.Args.Contains(Execution::Args::Type::Force)) + { + AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); + context.Reporter.Warn() << Resource::String::PinExistsOverwriting << std::endl; + pinsToUpdate.push_back(std::move(pin)); + } + else + { + context.Reporter.Error() << Resource::String::PinExistsUseForceArg << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); + } } else { - context.Reporter.Error() << Resource::String::PinExistsUseForceArg << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); + pinsToAdd.push_back(std::move(pin)); } } - else + + if (!pinsToAdd.empty()) { - pinningIndex->AddPin(pin); - AICLI_LOG(CLI, Info, << "Finished adding pin"); + for (auto pin : pinsToAdd) + { + pinningIndex->AddOrUpdatePin(pin); + } + context.Reporter.Info() << Resource::String::PinAdded << std::endl; } } @@ -139,7 +162,7 @@ namespace AppInstaller::CLI::Workflow auto package = context.Get(); std::vector pins; - // TODO: We should support querying the multiple sources for a package, instead of just one + // TODO #476: We should support querying the multiple sources for a package, instead of just one auto availableVersion = package->GetLatestAvailableVersion(); auto pinningIndex = context.Get(); @@ -170,7 +193,7 @@ namespace AppInstaller::CLI::Workflow return; } - // TODO: Use package and source names + // TODO #476: Use package and source names Execution::TableOutput<4> table(context.Reporter, { Resource::String::SearchId, @@ -181,7 +204,7 @@ namespace AppInstaller::CLI::Workflow for (const auto& pin : pins) { - // TODO: Avoid these conversions to string + // TODO #476: Avoid these conversions to string table.OutputLine({ pin.GetPackageId(), std::string{ pin.GetSourceId() }, @@ -233,11 +256,9 @@ namespace AppInstaller::CLI::Workflow std::vector matchingPins; std::copy_if(pins.begin(), pins.end(), std::back_inserter(matchingPins), [&](Pinning::Pin pin) { - // TODO: Filter to source SearchRequest searchRequest; searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pin.GetPackageId()); auto searchResult = source.Search(searchRequest); - return !searchResult.Matches.empty(); }); diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 76a4353963..3628f76cd8 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -7,8 +7,11 @@ #include "InstallFlow.h" #include "UpdateFlow.h" #include "ManifestComparator.h" +#include using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Pinning; namespace AppInstaller::CLI::Workflow { @@ -53,7 +56,7 @@ namespace AppInstaller::CLI::Workflow if (isUpgrade && installedVersion.IsUnknown() && !context.Args.Contains(Execution::Args::Type::IncludeUnknown)) { - // the package has an unknown version and the user did not request to upgrade it anyway. + // the package has an unknown version and the user did not request to upgrade it anyway if (m_reportVersionNotFound) { context.Reporter.Info() << Resource::String::UpgradeUnknownVersionExplanation << std::endl; @@ -190,19 +193,18 @@ namespace AppInstaller::CLI::Workflow continue; } - // Filter out packages that require explicit upgrades. - // We require explicit upgrades only if the installed version is pinned, - // either because it was manually pinned or because the manifest indicated - // RequireExplicitUpgrade. - // Note that this does not consider whether the update to be installed has - // RequireExplicitUpgrade. While this has the downside of not working with - // packages installed from another source, it ensures consistency with the - // list of available updates (there we don't have the selected installer) - // and at most we will update each package like this once. + // Filter out packages that require explicit upgrade. + // User-defined pins are handled when selecting the version to use. auto installedMetadata = updateContext.Get()->GetMetadata(); - auto pinnedState = ConvertToPackagePinnedStateEnum(installedMetadata[PackageVersionMetadata::PinnedState]); - if (pinnedState != PackagePinnedState::NotPinned) + auto pinnedState = ConvertToPinTypeEnum(installedMetadata[PackageVersionMetadata::PinnedState]); + if (pinnedState == PinType::PinnedByManifest) { + // Note that for packages pinned by the manifest + // this does not consider whether the update to be installed has + // RequireExplicitUpgrade. While this has the downside of not working with + // packages installed from another source, it ensures consistency with the + // list of available updates (there we don't have the selected installer) + // and at most we will update each package like this once. AICLI_LOG(CLI, Info, << "Skipping " << match.Package->GetProperty(PackageProperty::Id) << " as it requires explicit upgrade"); ++packagesThatRequireExplicitSkipped; continue; @@ -227,13 +229,13 @@ namespace AppInstaller::CLI::Workflow if (packagesWithUnknownVersionSkipped > 0) { AICLI_LOG(CLI, Info, << packagesWithUnknownVersionSkipped << " package(s) skipped due to unknown installed version"); - context.Reporter.Info() << packagesWithUnknownVersionSkipped << " " << Resource::String::UpgradeUnknownVersionCount << std::endl; + context.Reporter.Info() << Resource::String::UpgradeUnknownVersionCount(packagesWithUnknownVersionSkipped) << std::endl; } if (packagesThatRequireExplicitSkipped > 0) { AICLI_LOG(CLI, Info, << packagesThatRequireExplicitSkipped << " package(s) skipped due to requiring explicit upgrade"); - context.Reporter.Info() << packagesThatRequireExplicitSkipped << " " << Resource::String::UpgradeRequireExplicitCount << std::endl; + context.Reporter.Info() << Resource::String::UpgradeRequireExplicitCount(packagesThatRequireExplicitSkipped) << std::endl; } } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 92f93c108d..22f7ecfacf 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -7,14 +7,15 @@ #include "PromptFlow.h" #include "TableOutput.h" #include +#include +using namespace std::string_literals; +using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::Pinning; +using namespace AppInstaller::Repository; namespace AppInstaller::CLI::Workflow { - using namespace std::string_literals; - using namespace AppInstaller::Utility::literals; - using namespace AppInstaller::Repository; - namespace { std::string GetMatchCriteriaDescriptor(const ResultMatch& match) @@ -752,11 +753,14 @@ namespace AppInstaller::CLI::Workflow shouldShowSource ? sourceName : Utility::LocIndString() ); - auto pinnedState = ConvertToPackagePinnedStateEnum(installedVersion->GetMetadata()[PackageVersionMetadata::PinnedState]); - bool requiresExplicitUpgrade = m_onlyShowUpgrades && pinnedState != PackagePinnedState::NotPinned; - if (requiresExplicitUpgrade) + if (m_onlyShowUpgrades) { - linesForExplicitUpgrade.push_back(std::move(line)); + // Sort into multiple tables according to pins + auto pinnedState = ConvertToPinTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::PinnedState]); + if (pinnedState == PinType::PinnedByManifest) + { + linesForExplicitUpgrade.push_back(std::move(line)); + } } else { @@ -796,7 +800,7 @@ namespace AppInstaller::CLI::Workflow if (packagesWithUnknownVersionSkipped > 0) { AICLI_LOG(CLI, Info, << packagesWithUnknownVersionSkipped << " package(s) skipped due to unknown installed version"); - context.Reporter.Info() << packagesWithUnknownVersionSkipped << " " << Resource::String::UpgradeUnknownVersionCount << std::endl; + context.Reporter.Info() << Resource::String::UpgradeUnknownVersionCount(packagesWithUnknownVersionSkipped) << std::endl; } } } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 489ee62e83..1894565e9e 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1339,21 +1339,13 @@ Please specify one of them using the --source option to proceed. This package's version number cannot be determined. To upgrade it anyway, add the argument --include-unknown to your previous command. {Locked="--include-unknown"} - - packages have version numbers that cannot be determined. Using --include-unknown may show more results. - {Locked="--include-unknown"} This string is preceded by a (integer) number of packages that do not have notated versions. - - - package has a version number that cannot be determined. Using --include-unknown may show more results. - {Locked="--include-unknown"} This string is preceded by a (integer) number of packages that do not have notated versions. - - package(s) have version numbers that cannot be determined. Use --include-unknown to see all results. - {Locked="--include-unknown"} This string is preceded by a (integer) number of packages that do not have notated versions. + {0} package(s) have version numbers that cannot be determined. Use --include-unknown to see all results. + {Locked="{0}","--include-unknown"} {0} is a placeholder that is replaced by an integer number of packages that do not have notated versions. - package(s) is pinned and needs to be explicitly upgraded. - This string is preceded by a (integer) number of packages that require explicit upgrades. + {0} package(s) are pinned and need to be explicitly upgraded. + {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages that require explicit upgrades. The arguments provided can only be used with a query. @@ -1636,4 +1628,8 @@ Please specify one of them using the --source option to proceed. There are no pins configured. Shown when listing or modifying existing pins if there are none. + + {0} package(s) have a blocking pin that needs to be removed before upgrade + {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with blocking pins + \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp index 11b382de0c..d575942dab 100644 --- a/src/AppInstallerCLITests/TestSource.cpp +++ b/src/AppInstallerCLITests/TestSource.cpp @@ -177,7 +177,7 @@ namespace TestCommon return InstalledVersion; } - std::vector TestPackage::GetAvailableVersionKeys() const + std::vector TestPackage::GetAvailableVersionKeys(PinBehavior) const { std::vector result; for (const auto& version : AvailableVersions) @@ -187,7 +187,7 @@ namespace TestCommon return result; } - std::shared_ptr TestPackage::GetLatestAvailableVersion() const + std::shared_ptr TestPackage::GetLatestAvailableVersion(PinBehavior) const { if (AvailableVersions.empty()) { @@ -197,7 +197,7 @@ namespace TestCommon return AvailableVersions[0]; } - std::shared_ptr TestPackage::GetAvailableVersion(const PackageVersionKey& versionKey) const + std::shared_ptr TestPackage::GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior) const { for (const auto& version : AvailableVersions) { @@ -211,7 +211,7 @@ namespace TestCommon return {}; } - bool TestPackage::IsUpdateAvailable() const + bool TestPackage::IsUpdateAvailable(PinBehavior) const { if (InstalledVersion && !AvailableVersions.empty()) { diff --git a/src/AppInstallerCLITests/TestSource.h b/src/AppInstallerCLITests/TestSource.h index bcac291f47..8e1bd57d02 100644 --- a/src/AppInstallerCLITests/TestSource.h +++ b/src/AppInstallerCLITests/TestSource.h @@ -63,10 +63,10 @@ namespace TestCommon AppInstaller::Utility::LocIndString GetProperty(AppInstaller::Repository::PackageProperty property) const override; std::shared_ptr GetInstalledVersion() const override; - std::vector GetAvailableVersionKeys() const override; - std::shared_ptr GetLatestAvailableVersion() const override; - std::shared_ptr GetAvailableVersion(const AppInstaller::Repository::PackageVersionKey& versionKey) const override; - bool IsUpdateAvailable() const override; + std::vector GetAvailableVersionKeys(AppInstaller::Repository::PinBehavior) const override; + std::shared_ptr GetLatestAvailableVersion(AppInstaller::Repository::PinBehavior) const override; + std::shared_ptr GetAvailableVersion(const AppInstaller::Repository::PackageVersionKey& versionKey, AppInstaller::Repository::PinBehavior) const override; + bool IsUpdateAvailable(AppInstaller::Repository::PinBehavior) const override; bool IsSame(const IPackage* other) const override; std::shared_ptr InstalledVersion; diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp index c27694fdd7..0bebb41e5f 100644 --- a/src/AppInstallerCommonCore/Errors.cpp +++ b/src/AppInstallerCommonCore/Errors.cpp @@ -212,6 +212,8 @@ namespace AppInstaller return "A pin already exists for the package."; case APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST: return "There is no pin for the package."; + case APPINSTALLER_CLI_ERROR_PACKAGE_HAS_BLOCKING_PIN: + return "The package has a blocking pin that prevents from updating."; // Install errors case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: diff --git a/src/AppInstallerCommonCore/Pin.cpp b/src/AppInstallerCommonCore/Pin.cpp index acc3b2b2ba..83e174dcb1 100644 --- a/src/AppInstallerCommonCore/Pin.cpp +++ b/src/AppInstallerCommonCore/Pin.cpp @@ -19,12 +19,38 @@ namespace AppInstaller::Pinning return "Pinning"sv; case PinType::Gating: return "Gating"sv; + case PinType::PinnedByManifest: + return "PinnedByManifest"sv; case PinType::Unknown: default: return "Unknown"; } } + PinType ConvertToPinTypeEnum(std::string_view in) + { + if (Utility::CaseInsensitiveEquals(in, "Blocking"sv)) + { + return PinType::Blocking; + } + else if (Utility::CaseInsensitiveEquals(in, "Pinning"sv)) + { + return PinType::Pinning; + } + else if (Utility::CaseInsensitiveEquals(in, "Gating"sv)) + { + return PinType::Gating; + } + else if (Utility::CaseInsensitiveEquals(in, "PinnedByManifest"sv)) + { + return PinType::PinnedByManifest; + } + else + { + return PinType::Unknown; + } + } + Pin Pin::CreateBlockingPin(PinKey&& pinKey, Version version) { return { PinType::Blocking, std::move(pinKey), version.ToString() }; diff --git a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h index 0ec281229d..206adc8158 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h @@ -112,6 +112,7 @@ #define APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED ((HRESULT)0x8A150061) #define APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS ((HRESULT)0x8A150062) #define APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST ((HRESULT)0x8A150063) +#define APPINSTALLER_CLI_ERROR_PACKAGE_HAS_BLOCKING_PIN ((HRESULT)0x8A150064) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) diff --git a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h index 6b4a01cd0c..b453ff5466 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h @@ -172,13 +172,15 @@ namespace AppInstaller::Utility // A range of versions indicated by a version and optionally a wildcard at the end. struct GatedVersion { - // TODO + // TODO #476 // For now, using dummy implementation that just holds a string GatedVersion() {} GatedVersion(std::string_view s) : m_tmp(s) {} - std::string ToString() const { return m_tmp; } + + bool IsValidVersion(Version version) const; bool operator==(const GatedVersion& other) const { return m_tmp == other.m_tmp; } + std::string ToString() const { return m_tmp; } private: std::string m_tmp; diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h index cbca0adc1b..055c01e7bf 100644 --- a/src/AppInstallerCommonCore/Public/winget/Pin.h +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -8,18 +8,23 @@ namespace AppInstaller::Pinning { enum class PinType { + // Unknown pin type or not pinned Unknown, + // Pinned by the manifest using the RequiresExplicitUpgrade field. + // Behaves the same as Pinning pins + PinnedByManifest, // The package is blocked from 'upgrade --all' and 'upgrade '. // User has to unblock to allow update. Blocking, // The package is excluded from 'upgrade --all', unless '--include-pinned' is added. // 'upgrade ' is not blocked. Pinning, - // The package is pinned to a specific version. + // The package is pinned to a specific version range. Gating, }; std::string_view ToString(PinType type); + PinType ConvertToPinTypeEnum(std::string_view in); // The set of values needed to uniquely identify a Pin struct PinKey @@ -36,6 +41,10 @@ namespace AppInstaller::Pinning { return !(*this == other); } + bool operator<(const PinKey& other) const + { + return PackageId < other.PackageId || (PackageId == other.PackageId && SourceId < other.SourceId); + } Manifest::Manifest::string_t PackageId; std::string SourceId; diff --git a/src/AppInstallerCommonCore/Versions.cpp b/src/AppInstallerCommonCore/Versions.cpp index a300e8cdcc..a7e7b43a6a 100644 --- a/src/AppInstallerCommonCore/Versions.cpp +++ b/src/AppInstallerCommonCore/Versions.cpp @@ -498,6 +498,12 @@ namespace AppInstaller::Utility return m_maxVersion; } + bool GatedVersion::IsValidVersion(Version) const + { + // TODO #476: Implement actual gating logic + return false; + } + bool HasOverlapInVersionRanges(const std::vector& ranges) { for (size_t i = 0; i < ranges.size(); i++) diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 20ddecffbc..4d97da563c 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -2,6 +2,11 @@ // Licensed under the MIT License. #include "pch.h" #include "CompositeSource.h" +#include "Microsoft/PinningIndex.h" +#include + +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Settings; namespace AppInstaller::Repository { @@ -17,6 +22,14 @@ namespace AppInstaller::Repository }; } + Pinning::PinKey GetPinKey(IPackage* availablePackage) + { + return { + availablePackage->GetProperty(PackageProperty::Id).get(), + availablePackage->GetLatestAvailableVersion()->GetSource().GetIdentifier() + }; + } + // Returns true for fields that provide a strong match; one that is not based on a heuristic. bool IsStrongMatchField(PackageMatchField field) { @@ -310,6 +323,9 @@ namespace AppInstaller::Repository } } + // TODO #476 pin info + // result[PackageVersionMetadata::] + return result; } @@ -320,11 +336,82 @@ namespace AppInstaller::Repository std::shared_ptr m_trackingPackageVersion; }; + struct CompositeAvailablePackage + { + CompositeAvailablePackage() {} + CompositeAvailablePackage(std::shared_ptr availablePackage, std::optional pin = {}) + : AvailablePackage(availablePackage), Pin(pin) {} + + std::shared_ptr AvailablePackage; + std::optional Pin; + + std::vector GetAvailableVersionKeys(PinBehavior pinBehavior) const + { + if (Pin.has_value() && pinBehavior != PinBehavior::IgnorePins && ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + { + if (Pin->GetType() == Pinning::PinType::Blocking) + { + AICLI_LOG(Repo, Info, << "Ignoring available versions from source [" << Pin->GetSourceId() << "] for package [ " << Pin->GetPackageId() << "] due to Blocking pin"); + return {}; + } + else if (Pin->GetType() == Pinning::PinType::Pinning) + { + if (pinBehavior != PinBehavior::IncludePinned) + { + AICLI_LOG(Repo, Info, << "Ignoring available versions from source [" << Pin->GetSourceId() << "] for package [ " << Pin->GetPackageId() << "] due to Pinning pin"); + return {}; + } + } + else if (Pin->GetType() == Pinning::PinType::Gating) + { + Utility::GatedVersion gatedVersion(Pin->GetVersionString()); + auto versionKeys = AvailablePackage->GetAvailableVersionKeys(PinBehavior::IgnorePins); + std::vector result; + std::copy_if(versionKeys.begin(), versionKeys.end(), std::back_inserter(result), [&](const PackageVersionKey& pvk) { return gatedVersion.IsValidVersion(pvk.Version); }); + return result; + } + } + + return AvailablePackage->GetAvailableVersionKeys(PinBehavior::IgnorePins); + } + + std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior pinBehavior) const + { + if (Pin.has_value() && pinBehavior != PinBehavior::IgnorePins && ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + { + if (Pin->GetType() == Pinning::PinType::Blocking) + { + AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [ " << Pin->GetPackageId() << "] due to Blocking pin"); + return {}; + } + else if (Pin->GetType() == Pinning::PinType::Pinning) + { + if (pinBehavior != PinBehavior::IncludePinned) + { + AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [ " << Pin->GetPackageId() << "] due to Pinning pin"); + return {}; + } + } + else if (Pin->GetType() == Pinning::PinType::Gating) + { + Utility::GatedVersion gatedVersion(Pin->GetVersionString()); + if (!gatedVersion.IsValidVersion(versionKey.Version)) + { + AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [ " << Pin->GetPackageId() << "] as it does not satisfy Gating pin"); + return {}; + } + } + } + + return AvailablePackage->GetAvailableVersion(versionKey); + } + }; + // A composite package for the CompositeSource. struct CompositePackage : public IPackage { CompositePackage(std::shared_ptr installedPackage, std::shared_ptr availablePackage = {}) : - m_installedPackage(std::move(installedPackage)), m_availablePackage(std::move(availablePackage)) + m_installedPackage(std::move(installedPackage)) { // Grab the installed version's channel to allow for filtering in calls to get available info. if (m_installedPackage) @@ -336,12 +423,12 @@ namespace AppInstaller::Repository } } - TrySetOverrideInstalledVersion(); + AddAvailablePackage(std::move(availablePackage)); } Utility::LocIndString GetProperty(PackageProperty property) const override { - std::shared_ptr truth = GetLatestAvailableVersion(); + std::shared_ptr truth = GetLatestAvailableVersion(PinBehavior::IgnorePins); if (!truth) { truth = m_trackingPackageVersion; @@ -376,40 +463,60 @@ namespace AppInstaller::Repository return {}; } - std::vector GetAvailableVersionKeys() const override + std::vector GetAvailableVersionKeys(PinBehavior pinBehavior) const override { - if (m_availablePackage) - { - std::vector result = m_availablePackage->GetAvailableVersionKeys(); - std::string_view channel = m_installedChannel; - - // Remove all elements whose channel does not match the installed package. - result.erase( - std::remove_if(result.begin(), result.end(), [&](const PackageVersionKey& pvk) { return !Utility::ICUCaseInsensitiveEquals(pvk.Channel, channel); }), - result.end()); + std::vector result; - return result; + for (const auto& entry : m_availablePackages) + { + auto versionKeys = entry.second.GetAvailableVersionKeys(pinBehavior); + std::copy(versionKeys.begin(), versionKeys.end(), std::back_inserter(result)); } - return {}; + // Remove all elements whose channel does not match the installed package. + std::string_view channel = m_installedChannel; + result.erase( + std::remove_if(result.begin(), result.end(), [&](const PackageVersionKey& pvk) { return !Utility::ICUCaseInsensitiveEquals(pvk.Channel, channel); }), + result.end()); + + // Put latest versions at the front + std::sort(result.begin(), result.end()); + return result; } - std::shared_ptr GetLatestAvailableVersion() const override + std::shared_ptr GetLatestAvailableVersion(PinBehavior pinBehavior) const override { - return GetAvailableVersion({ "", "", m_installedChannel.get() }); + return GetAvailableVersion({ "", "", m_installedChannel.get() }, pinBehavior); } - std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const override + std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior pinBehavior) const override { - if (m_availablePackage) + if (Utility::IsEmptyOrWhitespace(versionKey.SourceId)) { - return m_availablePackage->GetAvailableVersion(versionKey); + // Look up in all sources + for (const auto& entry : m_availablePackages) + { + auto package = entry.second.GetAvailableVersion(versionKey, pinBehavior); + if (package) + { + return package; + } + } + } + else + { + // Use the provided source + auto itr = m_availablePackages.find(versionKey.SourceId); + if (itr != m_availablePackages.end()) + { + return itr->second.GetAvailableVersion(versionKey, pinBehavior); + } } return {}; } - bool IsUpdateAvailable() const override + bool IsUpdateAvailable(PinBehavior pinBehavior) const override { auto installed = GetInstalledVersion(); @@ -418,7 +525,7 @@ namespace AppInstaller::Repository return false; } - auto latest = GetLatestAvailableVersion(); + auto latest = GetLatestAvailableVersion(pinBehavior); return (latest && (GetVACFromVersion(installed.get()).IsUpdatedBy(GetVACFromVersion(latest.get())))); } @@ -430,23 +537,47 @@ namespace AppInstaller::Repository if (!otherComposite || static_cast(m_installedPackage) != static_cast(otherComposite->m_installedPackage) || (m_installedPackage && !m_installedPackage->IsSame(otherComposite->m_installedPackage.get())) || - static_cast(m_availablePackage) != static_cast(otherComposite->m_availablePackage) || - (m_availablePackage && !m_availablePackage->IsSame(otherComposite->m_availablePackage.get()))) + m_availablePackages.size() != otherComposite->m_availablePackages.size()) { return false; } + auto itr = m_availablePackages.begin(); + auto otherItr = otherComposite->m_availablePackages.begin(); + while (itr != m_availablePackages.end()) + { + if (itr->first != otherItr->first || + !itr->second.AvailablePackage->IsSame(otherItr->second.AvailablePackage.get())) + { + return false; + } + + ++itr; + ++otherItr; + } + return true; } - const std::shared_ptr& GetInstalledPackage() const + bool IsSameAsAnyAvailable(const IPackage* other) const { - return m_installedPackage; + if (other) + { + for (const auto& entry : m_availablePackages) + { + if (other->IsSame(entry.second.AvailablePackage.get())) + { + return true; + } + } + } + + return false; } - const std::shared_ptr& GetAvailablePackage() const + const std::shared_ptr& GetInstalledPackage() const { - return m_availablePackage; + return m_installedPackage; } const std::shared_ptr& GetTrackingPackage() const @@ -454,10 +585,19 @@ namespace AppInstaller::Repository return m_trackingPackage; } - void SetAvailablePackage(std::shared_ptr availablePackage) + void AddAvailablePackage(std::shared_ptr availablePackage) { - m_availablePackage = std::move(availablePackage); - TrySetOverrideInstalledVersion(); + if (availablePackage) + { + if (m_availablePackages.empty()) + { + // Set override only with the first available version found + TrySetOverrideInstalledVersion(availablePackage); + } + + auto sourceId = availablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetSource().GetIdentifier(); + m_availablePackages[sourceId] = CompositeAvailablePackage{ std::move(availablePackage) }; + } } void SetTracking(Source trackingSource, std::shared_ptr trackingPackage, std::shared_ptr trackingPackageVersion) @@ -467,10 +607,35 @@ namespace AppInstaller::Repository m_trackingPackageVersion = std::move(trackingPackageVersion); } + // Gets the information about the pins that exist for this package + void GetExistingPins(PinningIndex& pinningIndex) + { + // If the package is installed, we need to add the pin information to the available packages from any source. + // If the package is not installed, we clean up stale pin information here. + for (auto& entry : m_availablePackages) + { + auto availablePackage = entry.second.AvailablePackage; + + auto pinKey = GetPinKey(availablePackage.get()); + if (m_installedPackage) + { + auto pin = pinningIndex.GetPin(pinKey); + if (pin.has_value()) + { + entry.second.Pin = std::move(pin.value()); + } + } + else if (pinningIndex.GetPin(pinKey)) + { + pinningIndex.RemovePin(pinKey); + } + } + } + private: - void TrySetOverrideInstalledVersion() + void TrySetOverrideInstalledVersion(std::shared_ptr availablePackage) { - if (m_installedPackage && m_availablePackage) + if (m_installedPackage && availablePackage) { auto installedVersion = m_installedPackage->GetInstalledVersion(); if (installedVersion) @@ -478,7 +643,7 @@ namespace AppInstaller::Repository auto installedType = Manifest::ConvertToInstallerTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::InstalledType]); if (Manifest::DoesInstallerTypeSupportArpVersionRange(installedType)) { - m_overrideInstalledVersion = GetMappedInstalledVersion(installedVersion->GetProperty(PackageVersionProperty::Version), m_availablePackage); + m_overrideInstalledVersion = GetMappedInstalledVersion(installedVersion->GetProperty(PackageVersionProperty::Version), availablePackage); } } } @@ -486,11 +651,12 @@ namespace AppInstaller::Repository std::shared_ptr m_installedPackage; Utility::LocIndString m_installedChannel; - std::shared_ptr m_availablePackage; Source m_trackingSource; std::shared_ptr m_trackingPackage; std::shared_ptr m_trackingPackageVersion; std::string m_overrideInstalledVersion; + // Available packages indexed by source ID. + std::map m_availablePackages; }; // The comparator compares the ResultMatch by MatchType first, then Field in a predefined order. @@ -620,8 +786,8 @@ namespace AppInstaller::Repository { for (auto& match : Matches) { - const std::shared_ptr& availablePackage = match.Package->GetAvailablePackage(); - if (availablePackage && availablePackage->IsSame(availableMatch.Package.get())) + const CompositePackage* compositeMatch = dynamic_cast(match.Package.get()); + if (compositeMatch && compositeMatch->IsSameAsAnyAvailable(availableMatch.Package.get())) { if (ResultMatchComparator{}(availableMatch, match)) { @@ -950,7 +1116,6 @@ namespace AppInstaller::Repository std::shared_ptr trackingPackage; std::shared_ptr trackingPackageVersion; std::chrono::system_clock::time_point trackingPackageTime; - std::shared_ptr availablePackage; // Check the tracking catalog first to see if there is a correlation there. // TODO: When the issue with support for multiple available packages is fixed, this should move into @@ -987,43 +1152,38 @@ namespace AppInstaller::Repository // Directly search for the available package from tracking information. if (trackingPackage) { - availablePackage = GetTrackedPackageFromAvailableSource(result, trackedSource, trackingPackage->GetProperty(PackageProperty::Id)); + compositePackage->AddAvailablePackage(GetTrackedPackageFromAvailableSource(result, trackedSource, trackingPackage->GetProperty(PackageProperty::Id))); + compositePackage->SetTracking(std::move(trackedSource), std::move(trackingPackage), std::move(trackingPackageVersion)); } - if (!availablePackage) + // Search sources and add to result + for (const auto& source : m_availableSources) { - // Search sources and add to result - for (const auto& source : m_availableSources) + // Do not attempt to correlate local packages against this source + if (!source.GetDetails().SupportInstalledSearchCorrelation) { - // Do not attempt to correlate local packages against this source - if (!source.GetDetails().SupportInstalledSearchCorrelation) - { - continue; - } - - SearchResult availableResult = result.SearchAndHandleFailures(source, systemReferenceSearch); + continue; + } - if (availableResult.Matches.empty()) - { - continue; - } + SearchResult availableResult = result.SearchAndHandleFailures(source, systemReferenceSearch); - availablePackage = GetMatchingPackage(availableResult.Matches, - [&]() { - AICLI_LOG(Repo, Info, - << "Found multiple matches for installed package [" << installedVersion->GetProperty(PackageVersionProperty::Id) << - "] in source [" << source.GetIdentifier() << "] when searching for [" << systemReferenceSearch.ToString() << "]"); - }, [&] { - AICLI_LOG(Repo, Warning, << " Appropriate available package could not be determined"); - }); - - // We found some matching packages here, don't keep going - break; + if (availableResult.Matches.empty()) + { + continue; } - } - compositePackage->SetAvailablePackage(std::move(availablePackage)); - compositePackage->SetTracking(std::move(trackedSource), std::move(trackingPackage), std::move(trackingPackageVersion)); + // We will keep matching packages found from all sources, but generally we will use only the first one. + auto availablePackage = GetMatchingPackage(availableResult.Matches, + [&]() { + AICLI_LOG(Repo, Info, + << "Found multiple matches for installed package [" << installedVersion->GetProperty(PackageVersionProperty::Id) << + "] in source [" << source.GetIdentifier() << "] when searching for [" << systemReferenceSearch.ToString() << "]"); + }, [&] { + AICLI_LOG(Repo, Warning, << " Appropriate available package could not be determined"); + }); + + compositePackage->AddAvailablePackage(std::move(availablePackage)); + } } // Move the installed result into the composite result @@ -1146,6 +1306,16 @@ namespace AppInstaller::Repository result.Matches.erase(result.Matches.begin() + request.MaximumResults, result.Matches.end()); } + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + { + // Look up any pins for the packages found + auto pinningIndex = PinningIndex::OpenOrCreateDefault(); + for (auto& match : result.Matches) + { + match.Package->GetExistingPins(pinningIndex); + } + } + return std::move(result); } diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index b27104dd6d..adeb1ca4a8 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -26,6 +26,22 @@ namespace AppInstaller::Repository::Microsoft return result; } + PinningIndex PinningIndex::OpenOrCreateDefault(OpenDisposition disposition) + { + const auto indexPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; + + AICLI_LOG(Repo, Info, << "Opening pinning index"); + + if (std::filesystem::exists(indexPath)) + { + return PinningIndex::Open(indexPath.u8string(), disposition); + } + else + { + return PinningIndex::CreateNew(indexPath.u8string()); + } + } + PinningIndex::IdType PinningIndex::AddPin(const Pinning::Pin& pin) { std::lock_guard lockInterface{ *m_interfaceLock }; @@ -60,6 +76,19 @@ namespace AppInstaller::Repository::Microsoft return result; } + void PinningIndex::AddOrUpdatePin(const Pinning::Pin& pin) + { + auto existingPin = GetPin(pin.GetKey()); + if (existingPin.has_value()) + { + UpdatePin(pin); + } + else + { + AddPin(pin); + } + } + void PinningIndex::RemovePin(const Pinning::PinKey& pinKey) { AICLI_LOG(Repo, Verbose, << "Removing Pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h index e35ff91366..0d992d1b6c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -27,12 +27,20 @@ namespace AppInstaller::Repository::Microsoft return { filePath, disposition, std::move(indexFile) }; } + // Opens or creates a PinningIndex database on the default path. + // disposition is only used when opening an existing database. + static PinningIndex OpenOrCreateDefault(OpenDisposition disposition = OpenDisposition::ReadWrite); + // Adds a pin to the index. IdType AddPin(const Pinning::Pin& pin); - // Updates a pin type, and gated version if needed + // Updates a pin type, and version if needed. + // Return value indicates whether there were any changes. bool UpdatePin(const Pinning::Pin& pin); + // Adds a pin or updates it if it already exists. + void AddOrUpdatePin(const Pinning::Pin& pin); + // Removes a pin from the index. void RemovePin(const Pinning::PinKey& pinKey); diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp index bb74dda174..cd49643664 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp @@ -239,7 +239,7 @@ namespace AppInstaller::Repository::Microsoft return {}; } - std::vector GetAvailableVersionKeys() const override + std::vector GetAvailableVersionKeys(PinBehavior) const override { std::shared_ptr source = GetReferenceSource(); std::vector versions = source->GetIndex().GetVersionKeysById(m_idId); @@ -252,12 +252,12 @@ namespace AppInstaller::Repository::Microsoft return result; } - std::shared_ptr GetLatestAvailableVersion() const override + std::shared_ptr GetLatestAvailableVersion(PinBehavior) const override { return GetLatestVersionInternal(); } - std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const override + std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior) const override { std::shared_ptr source = GetReferenceSource(); @@ -277,7 +277,7 @@ namespace AppInstaller::Repository::Microsoft return {}; } - bool IsUpdateAvailable() const override + bool IsUpdateAvailable(PinBehavior) const override { return false; } @@ -311,22 +311,22 @@ namespace AppInstaller::Repository::Microsoft return GetLatestVersionInternal(); } - std::vector GetAvailableVersionKeys() const override + std::vector GetAvailableVersionKeys(PinBehavior) const override { return {}; } - std::shared_ptr GetLatestAvailableVersion() const override + std::shared_ptr GetLatestAvailableVersion(PinBehavior) const override { return {}; } - std::shared_ptr GetAvailableVersion(const PackageVersionKey&) const override + std::shared_ptr GetAvailableVersion(const PackageVersionKey&, PinBehavior) const override { return {}; } - bool IsUpdateAvailable() const override + bool IsUpdateAvailable(PinBehavior) const override { return false; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index 0b45fc4416..5b26df7a35 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -65,7 +65,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 std::optional PinTable::GetByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) { - // TODO: The statement builder requires that the bound parameters can be converted to T&&, + // TODO #476: The statement builder requires that the bound parameters can be converted to T&&, // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) @@ -86,7 +86,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) { - // TODO: The statement builder requires that the bound parameters can be converted to T&&, + // TODO #476: The statement builder requires that the bound parameters can be converted to T&&, // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.InsertInto(s_PinTable_Table_Name) @@ -107,7 +107,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 bool PinTable::UpdatePin(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) { - // TODO: The statement builder requires that the bound parameters can be converted to T&&, + // TODO #476: The statement builder requires that the bound parameters can be converted to T&&, // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.Update(s_PinTable_Table_Name).Set() diff --git a/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp b/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp index 74fefec83d..504834dd79 100644 --- a/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp +++ b/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "winget/PackageTrackingCatalog.h" #include "PackageTrackingCatalogSourceFactory.h" +#include "winget/Pin.h" #include "winget/RepositorySource.h" #include "Microsoft/SQLiteIndexSource.h" #include "AppInstallerDateTime.h" @@ -238,7 +239,7 @@ namespace AppInstaller::Repository if (installer.RequireExplicitUpgrade) { - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::PinnedState, ToString(PackagePinnedState::PinnedByManifest)); + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::PinnedState, ToString(Pinning::PinType::PinnedByManifest)); } // Record installed architecture and locale if applicable diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h index 319d851453..3e047595a0 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h +++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h @@ -181,7 +181,8 @@ namespace AppInstaller::Repository TrackingWriteTime, // The Architecture of an installed package InstalledArchitecture, - // The PackagePinnedState of the installed package + // The pinned state of the installed package + // As a package can have multiple pins for multiple sources, this is the strictest pin PinnedState, // The Architecture of user intent UserIntentArchitecture, @@ -192,17 +193,6 @@ namespace AppInstaller::Repository // Convert a PackageVersionMetadata to a string. std::string_view ToString(PackageVersionMetadata pvm); - // Possible pinned states for a package. - // Pinned packages need to be explicitly updated (i.e., are not included in `upgrade --all`) - enum class PackagePinnedState - { - NotPinned, - PinnedByManifest, - }; - - std::string_view ToString(PackagePinnedState state); - PackagePinnedState ConvertToPackagePinnedStateEnum(std::string_view in); - // A single package version. struct IPackageVersion { @@ -243,8 +233,18 @@ namespace AppInstaller::Repository // The channel. Utility::NormalizedString Channel; + + bool operator<(const PackageVersionKey& other) const + { + Utility::VersionAndChannel vac({ Version }, { Channel }); + Utility::VersionAndChannel otherVac({ other.Version }, { other.Channel }); + if (vac < otherVac) return true; + if (otherVac < vac) return false; + return SourceId < other.SourceId; + } }; + // A property of a package. enum class PackageProperty { @@ -300,6 +300,18 @@ namespace AppInstaller::Repository std::vector Status; }; + // Possible ways to consider pins when getting a package's available versions + enum class PinBehavior + { + // Ignore pins, returns all available versions. + IgnorePins, + // Include available versions for packages with a Pinning pin. + // Blocking pins and Gating pins still respected. + IncludePinned, + // Respect all the types of pins. + ConsiderPins, + }; + // A package, potentially containing information about it's local state and the available versions. struct IPackage { @@ -311,19 +323,23 @@ namespace AppInstaller::Repository // Gets the installed package information. virtual std::shared_ptr GetInstalledVersion() const = 0; + // Note on pins: + // Pins only make sense when there is both an installed and an available version. + // Gets all available versions of this package. // The versions will be returned in sorted, descending order. // Ex. { 4, 3, 2, 1 } - virtual std::vector GetAvailableVersionKeys() const = 0; + // The list may contain versions from multiple sources. + virtual std::vector GetAvailableVersionKeys(PinBehavior pinBehavior = PinBehavior::ConsiderPins) const = 0; // Gets a specific version of this package. - virtual std::shared_ptr GetLatestAvailableVersion() const = 0; + virtual std::shared_ptr GetLatestAvailableVersion(PinBehavior pinBehavior = PinBehavior::ConsiderPins) const = 0; // Gets a specific version of this package. - virtual std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const = 0; + virtual std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior pinBehavior = PinBehavior::ConsiderPins) const = 0; // Gets a value indicating whether an available version is newer than the installed version. - virtual bool IsUpdateAvailable() const = 0; + virtual bool IsUpdateAvailable(PinBehavior pinBehavior = PinBehavior::ConsiderPins) const = 0; // Determines if the given IPackage refers to the same package as this one. virtual bool IsSame(const IPackage*) const = 0; diff --git a/src/AppInstallerRepositoryCore/RepositorySearch.cpp b/src/AppInstallerRepositoryCore/RepositorySearch.cpp index 35eebc73d6..2384b3b893 100644 --- a/src/AppInstallerRepositoryCore/RepositorySearch.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySearch.cpp @@ -92,30 +92,6 @@ namespace AppInstaller::Repository } } - std::string_view ToString(PackagePinnedState state) - { - switch (state) - { - case PackagePinnedState::PinnedByManifest: return "PinnedByManifest"sv; - case PackagePinnedState::NotPinned: - default: - return "Unknown"; - } - } - - PackagePinnedState ConvertToPackagePinnedStateEnum(std::string_view in) - { - if (Utility::CaseInsensitiveEquals(in, "PinnedByManifest"sv)) - { - return PackagePinnedState::PinnedByManifest; - } - else - { - return PackagePinnedState::NotPinned; - } - } - - const char* UnsupportedRequestException::what() const noexcept { if (m_whatMessage.empty()) diff --git a/src/AppInstallerRepositoryCore/Rest/RestSource.cpp b/src/AppInstallerRepositoryCore/Rest/RestSource.cpp index 46d2b04d68..042c36f7c3 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestSource.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestSource.cpp @@ -57,7 +57,7 @@ namespace AppInstaller::Repository::Rest return {}; } - std::vector GetAvailableVersionKeys() const override + std::vector GetAvailableVersionKeys(PinBehavior) const override { std::shared_ptr source = GetReferenceSource(); std::scoped_lock versionsLock{ m_packageVersionsLock }; @@ -72,15 +72,15 @@ namespace AppInstaller::Repository::Rest return result; } - std::shared_ptr GetLatestAvailableVersion() const override + std::shared_ptr GetLatestAvailableVersion(PinBehavior) const override { std::scoped_lock versionsLock{ m_packageVersionsLock }; return GetLatestVersionInternal(); } - std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const override; + std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior) const override; - bool IsUpdateAvailable() const override + bool IsUpdateAvailable(PinBehavior) const override { return false; } @@ -356,7 +356,7 @@ namespace AppInstaller::Repository::Rest IRestClient::VersionInfo m_versionInfo; }; - std::shared_ptr AvailablePackage::GetAvailableVersion(const PackageVersionKey& versionKey) const + std::shared_ptr AvailablePackage::GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior) const { std::shared_ptr source = GetReferenceSource(); std::scoped_lock versionsLock{ m_packageVersionsLock }; From 2c889fc4c3e2600e7764571836c6c93a57f86a7d Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Fri, 30 Dec 2022 15:59:18 -0800 Subject: [PATCH 10/55] Fixes --- .../Workflows/CompletionFlow.cpp | 4 +- .../Workflows/DependencyNodeProcessor.cpp | 2 +- .../Workflows/ImportExportFlow.cpp | 6 +- .../Workflows/InstallFlow.cpp | 1 + src/AppInstallerCLICore/Workflows/PinFlow.cpp | 6 +- .../Workflows/ShowFlow.cpp | 2 +- .../Workflows/UninstallFlow.cpp | 4 +- .../Workflows/UpdateFlow.cpp | 7 ++- .../Workflows/WorkflowBase.cpp | 21 +++---- src/AppInstallerCLITests/CompositeSource.cpp | 60 +++++++++---------- .../PackageTrackingCatalog.cpp | 10 ++-- .../SQLiteIndexSource.cpp | 12 ++-- .../CompositeSource.cpp | 38 ++++++++---- .../PackageInstalledStatus.cpp | 6 +- .../Public/winget/RepositorySearch.h | 8 +-- .../CatalogPackage.cpp | 8 +-- 16 files changed, 106 insertions(+), 89 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp index 361d7a44e5..5cc3f6b18d 100644 --- a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp @@ -70,7 +70,7 @@ namespace AppInstaller::CLI::Workflow const std::string& word = context.Get().Word(); auto stream = context.Reporter.Completion(); - for (const auto& vc : context.Get()->GetAvailableVersionKeys()) + for (const auto& vc : context.Get()->GetAvailableVersionKeys(Repository::PinBehavior::IgnorePins)) { if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Version, word)) { @@ -86,7 +86,7 @@ namespace AppInstaller::CLI::Workflow std::vector channels; - for (const auto& vc : context.Get()->GetAvailableVersionKeys()) + for (const auto& vc : context.Get()->GetAvailableVersionKeys(Repository::PinBehavior::IgnorePins)) { if ((word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Channel, word)) && std::find(channels.begin(), channels.end(), vc.Channel) == channels.end()) diff --git a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp index dfff48779e..1b11269d04 100644 --- a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp +++ b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp @@ -41,7 +41,7 @@ namespace AppInstaller::CLI::Workflow const auto& package = match.Package; auto packageId = package->GetProperty(PackageProperty::Id); m_nodePackageInstalledVersion = package->GetInstalledVersion(); - m_nodePackageLatestVersion = package->GetLatestAvailableVersion(); + m_nodePackageLatestVersion = package->GetLatestAvailableVersion(PinBehavior::ConsiderPins); if (m_nodePackageInstalledVersion && dependencyNode.IsVersionOk(Utility::Version(m_nodePackageInstalledVersion->GetProperty(PackageVersionProperty::Version)))) { diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp index db9e9b4e82..ca927e0377 100644 --- a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -65,13 +65,13 @@ namespace AppInstaller::CLI::Workflow { if (!checkVersion) { - return package->GetLatestAvailableVersion(); + return package->GetLatestAvailableVersion(PinBehavior::IgnorePins); } - auto availablePackageVersion = package->GetAvailableVersion({ "", version, channel }); + auto availablePackageVersion = package->GetAvailableVersion({ "", version, channel }, PinBehavior::IgnorePins); if (!availablePackageVersion) { - availablePackageVersion = package->GetLatestAvailableVersion(); + availablePackageVersion = package->GetLatestAvailableVersion(PinBehavior::IgnorePins); if (availablePackageVersion) { // Warn installed version is not available. diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index c1c4f73882..6ecbd2929e 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -246,6 +246,7 @@ namespace AppInstaller::CLI::Workflow { bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); UpdateBehaviorEnum updateBehavior = context.Get().value().UpdateBehavior; + THROW_HR_IF(E_FAIL, context.Contains(Execution::Data::Package)); switch (m_installerType) { diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 9202aa13df..99bd9e6882 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -56,7 +56,7 @@ namespace AppInstaller::CLI::Workflow auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); for (auto versionKey : packageVersionKeys) { - auto availableVersion = package->GetAvailableVersion(versionKey); + auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); Pinning::PinKey pinKey{ availableVersion->GetProperty(PackageVersionProperty::Id).get(), availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; @@ -99,7 +99,7 @@ namespace AppInstaller::CLI::Workflow auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); for (auto versionKey : packageVersionKeys) { - auto availableVersion = package->GetAvailableVersion(versionKey); + auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); Pinning::PinKey pinKey{ availableVersion->GetProperty(PackageVersionProperty::Id).get(), availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; @@ -163,7 +163,7 @@ namespace AppInstaller::CLI::Workflow std::vector pins; // TODO #476: We should support querying the multiple sources for a package, instead of just one - auto availableVersion = package->GetLatestAvailableVersion(); + auto availableVersion = package->GetLatestAvailableVersion(PinBehavior::IgnorePins); auto pinningIndex = context.Get(); Pinning::PinKey pinKey{ diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index e3e4820f97..8b36db3e8c 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -227,7 +227,7 @@ namespace AppInstaller::CLI::Workflow void ShowAppVersions(Execution::Context& context) { - auto versions = context.Get()->GetAvailableVersionKeys(); + auto versions = context.Get()->GetAvailableVersionKeys(PinBehavior::IgnorePins); Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); for (const auto& version : versions) diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp index f049a20094..04c42e687d 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -233,9 +233,9 @@ namespace AppInstaller::CLI::Workflow correlatedSources.AddIfRemoteAndNotPresent(package->GetInstalledVersion()); // Then look through all available versions - for (const auto& versionKey : package->GetAvailableVersionKeys()) + for (const auto& versionKey : package->GetAvailableVersionKeys(PinBehavior::IgnorePins)) { - correlatedSources.AddIfRemoteAndNotPresent(package->GetAvailableVersion(versionKey)); + correlatedSources.AddIfRemoteAndNotPresent(package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins)); } // Finally record the uninstall for each found value diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 3628f76cd8..b39aaa05a6 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -65,14 +65,17 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); } + // TODO #476 + PinBehavior pinBehavior = (m_reportVersionNotFound || context.Args.Contains(Execution::Args::Type::IncludePinned)) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + // The version keys should have already been sorted by version - const auto& versionKeys = package->GetAvailableVersionKeys(); + const auto& versionKeys = package->GetAvailableVersionKeys(pinBehavior); for (const auto& key : versionKeys) { // Check Applicable Version if (!isUpgrade || IsUpdateVersionApplicable(installedVersion, Utility::Version(key.Version))) { - auto packageVersion = package->GetAvailableVersion(key); + auto packageVersion = package->GetAvailableVersion(key, PinBehavior::IgnorePins); auto manifest = packageVersion->GetManifest(); // Check applicable Installer diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 22f7ecfacf..8205325a17 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -561,7 +561,7 @@ namespace AppInstaller::CLI::Workflow for (size_t i = 0; i < searchResult.Matches.size(); ++i) { - auto latestVersion = searchResult.Matches[i].Package->GetLatestAvailableVersion(); + auto latestVersion = searchResult.Matches[i].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins); table.OutputLine({ latestVersion->GetProperty(PackageVersionProperty::Name), @@ -672,7 +672,7 @@ namespace AppInstaller::CLI::Workflow auto package = searchResult.Matches[i].Package; std::string sourceName; - auto latest = package->GetLatestAvailableVersion(); + auto latest = package->GetLatestAvailableVersion(PinBehavior::IgnorePins); if (latest) { auto source = latest->GetSource(); @@ -709,14 +709,15 @@ namespace AppInstaller::CLI::Workflow auto &source = context.Get(); bool shouldShowSource = source.IsComposite() && source.GetAvailableSources().size() > 1; + PinBehavior pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; for (const auto& match : searchResult.Matches) { auto installedVersion = match.Package->GetInstalledVersion(); if (installedVersion) { - auto latestVersion = match.Package->GetLatestAvailableVersion(); - bool updateAvailable = match.Package->IsUpdateAvailable(); + auto latestVersion = match.Package->GetLatestAvailableVersion(pinBehavior); + bool updateAvailable = match.Package->IsUpdateAvailable(pinBehavior); if (m_onlyShowUpgrades && !context.Args.Contains(Execution::Args::Type::IncludeUnknown) && Utility::Version(installedVersion->GetProperty(PackageVersionProperty::Version)).IsUnknown() && updateAvailable) { @@ -753,14 +754,10 @@ namespace AppInstaller::CLI::Workflow shouldShowSource ? sourceName : Utility::LocIndString() ); - if (m_onlyShowUpgrades) + auto pinnedState = ConvertToPinTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::PinnedState]); + if (m_onlyShowUpgrades && pinnedState == PinType::PinnedByManifest) { - // Sort into multiple tables according to pins - auto pinnedState = ConvertToPinTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::PinnedState]); - if (pinnedState == PinType::PinnedByManifest) - { - linesForExplicitUpgrade.push_back(std::move(line)); - } + linesForExplicitUpgrade.push_back(std::move(line)); } else { @@ -865,7 +862,7 @@ namespace AppInstaller::CLI::Workflow void GetManifestWithVersionFromPackage::operator()(Execution::Context& context) const { PackageVersionKey key("", m_version, m_channel); - auto requestedVersion = context.Get()->GetAvailableVersion(key); + auto requestedVersion = context.Get()->GetAvailableVersion(key, PinBehavior::ConsiderPins); std::optional manifest; if (requestedVersion) diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index 5a42f87705..cfc7a7b404 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -208,7 +208,7 @@ TEST_CASE("CompositeSource_PackageFamilyName_NotAvailable", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().empty()); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).empty()); } TEST_CASE("CompositeSource_PackageFamilyName_Available", "[CompositeSource]") @@ -230,7 +230,7 @@ TEST_CASE("CompositeSource_PackageFamilyName_Available", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); } TEST_CASE("CompositeSource_ProductCode_NotAvailable", "[CompositeSource]") @@ -244,7 +244,7 @@ TEST_CASE("CompositeSource_ProductCode_NotAvailable", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().empty()); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).empty()); } TEST_CASE("CompositeSource_ProductCode_Available", "[CompositeSource]") @@ -266,7 +266,7 @@ TEST_CASE("CompositeSource_ProductCode_Available", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); } TEST_CASE("CompositeSource_NameAndPublisher_Match", "[CompositeSource]") @@ -286,7 +286,7 @@ TEST_CASE("CompositeSource_NameAndPublisher_Match", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); } TEST_CASE("CompositeSource_MultiMatch_FindsStrongMatch", "[CompositeSource]") @@ -307,9 +307,9 @@ TEST_CASE("CompositeSource_MultiMatch_FindsStrongMatch", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); - REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Name).get() == name); - REQUIRE(!Version(result.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Version)).IsUnknown()); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Name).get() == name); + REQUIRE(!Version(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Version)).IsUnknown()); } TEST_CASE("CompositeSource_MultiMatch_DoesNotFindStrongMatch", "[CompositeSource]") @@ -328,7 +328,7 @@ TEST_CASE("CompositeSource_MultiMatch_DoesNotFindStrongMatch", "[CompositeSource REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 0); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 0); } TEST_CASE("CompositeSource_FoundByBothRootSearches", "[CompositeSource]") @@ -363,7 +363,7 @@ TEST_CASE("CompositeSource_FoundByBothRootSearches", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); } TEST_CASE("CompositeSource_OnlyAvailableFoundByRootSearch", "[CompositeSource]") @@ -394,7 +394,7 @@ TEST_CASE("CompositeSource_OnlyAvailableFoundByRootSearch", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); } TEST_CASE("CompositeSource_FoundByAvailableRootSearch_NotInstalled", "[CompositeSource]") @@ -442,7 +442,7 @@ TEST_CASE("CompositeSource_UpdateWithBetterMatchCriteria", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); REQUIRE(result.Matches[0].MatchCriteria.Type == originalType); // Now make the source root search find it with a better criteria @@ -461,7 +461,7 @@ TEST_CASE("CompositeSource_UpdateWithBetterMatchCriteria", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); REQUIRE(result.Matches[0].MatchCriteria.Type == type); } @@ -516,22 +516,22 @@ TEST_CASE("CompositePackage_AvailableVersions_ChannelFilteredOut", "[CompositeSo SearchResult result; result.Matches.emplace_back(TestPackage::Make(std::vector{ noChannel, hasChannel }), Criteria()); - REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys().size() == 2); + REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 2); return result; }; SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - auto versionKeys = result.Matches[0].Package->GetAvailableVersionKeys(); + auto versionKeys = result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins); REQUIRE(versionKeys.size() == 1); REQUIRE(versionKeys[0].Channel.empty()); - auto latestVersion = result.Matches[0].Package->GetLatestAvailableVersion(); + auto latestVersion = result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins); REQUIRE(latestVersion); REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Channel).get().empty()); - REQUIRE(!result.Matches[0].Package->IsUpdateAvailable()); + REQUIRE(!result.Matches[0].Package->IsUpdateAvailable(PinBehavior::IgnorePins)); } TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[CompositeSource]") @@ -552,22 +552,22 @@ TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[Composite SearchResult result; result.Matches.emplace_back(TestPackage::Make(std::vector{ noChannel, hasChannel }), Criteria()); - REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys().size() == 2); + REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 2); return result; }; SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - auto versionKeys = result.Matches[0].Package->GetAvailableVersionKeys(); + auto versionKeys = result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins); REQUIRE(versionKeys.size() == 1); REQUIRE(versionKeys[0].Channel == channel); - auto latestVersion = result.Matches[0].Package->GetLatestAvailableVersion(); + auto latestVersion = result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins); REQUIRE(latestVersion); REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Channel).get() == channel); - REQUIRE(result.Matches[0].Package->IsUpdateAvailable()); + REQUIRE(result.Matches[0].Package->IsUpdateAvailable(PinBehavior::IgnorePins)); } TEST_CASE("CompositeSource_MultipleAvailableSources_MatchFirst", "[CompositeSource]") @@ -604,8 +604,8 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchFirst", "[CompositeSour REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); - REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Name).get() == firstName); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Name).get() == firstName); } TEST_CASE("CompositeSource_MultipleAvailableSources_MatchSecond", "[CompositeSource]") @@ -633,8 +633,8 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchSecond", "[CompositeSou REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); - REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Name).get() == secondName); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Name).get() == secondName); } TEST_CASE("CompositeSource_MultipleAvailableSources_ReverseMatchBoth", "[CompositeSource]") @@ -663,7 +663,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_ReverseMatchBoth", "[Composi REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); } TEST_CASE("CompositeSource_IsSame", "[CompositeSource]") @@ -706,7 +706,7 @@ TEST_CASE("CompositeSource_AvailableSearchFailure", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); - auto pfns = result.Matches[0].Package->GetLatestAvailableVersion()->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); + auto pfns = result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); REQUIRE(pfns.size() == 1); REQUIRE(pfns[0] == pfn); @@ -852,7 +852,7 @@ TEST_CASE("CompositeSource_TrackingPackageFound", "[CompositeSource]") REQUIRE(result.Matches[0].Package); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); REQUIRE(result.Matches[0].Package->GetInstalledVersion()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion()); + REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)); } TEST_CASE("CompositeSource_TrackingPackageFound_MetadataPopulatedFromTracking", "[CompositeSource]") @@ -943,7 +943,7 @@ TEST_CASE("CompositeSource_TrackingFound_AvailableNot", "[CompositeSource]") REQUIRE(result.Matches[0].Package); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); REQUIRE(result.Matches[0].Package->GetInstalledVersion()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(!result.Matches[0].Package->GetLatestAvailableVersion()); + REQUIRE(!result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)); } TEST_CASE("CompositeSource_TrackingFound_AvailablePath", "[CompositeSource]") @@ -983,7 +983,7 @@ TEST_CASE("CompositeSource_TrackingFound_AvailablePath", "[CompositeSource]") REQUIRE(result.Matches[0].Package); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); REQUIRE(result.Matches[0].Package->GetInstalledVersion()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion()); + REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)); } TEST_CASE("CompositeSource_TrackingFound_NotInstalled", "[CompositeSource]") diff --git a/src/AppInstallerCLITests/PackageTrackingCatalog.cpp b/src/AppInstallerCLITests/PackageTrackingCatalog.cpp index 1b9f0dc636..98b3c2767e 100644 --- a/src/AppInstallerCLITests/PackageTrackingCatalog.cpp +++ b/src/AppInstallerCLITests/PackageTrackingCatalog.cpp @@ -87,7 +87,7 @@ TEST_CASE("TrackingCatalog_Install", "[tracking_catalog]") SearchResult resultAfter = catalog.Search(request); REQUIRE(resultAfter.Matches.size() == 1); - auto trackingVersion = resultAfter.Matches[0].Package->GetLatestAvailableVersion(); + auto trackingVersion = resultAfter.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins); REQUIRE(trackingVersion); auto metadata = trackingVersion->GetMetadata(); @@ -113,7 +113,7 @@ TEST_CASE("TrackingCatalog_Reinstall", "[tracking_catalog]") SearchResult resultBefore = catalog.Search(request); REQUIRE(resultBefore.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Name) == + REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Name) == manifest.DefaultLocalization.Get()); // Change name @@ -124,7 +124,7 @@ TEST_CASE("TrackingCatalog_Reinstall", "[tracking_catalog]") SearchResult resultAfter = catalog.Search(request); REQUIRE(resultAfter.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Name) == + REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Name) == newName); } @@ -147,7 +147,7 @@ TEST_CASE("TrackingCatalog_Upgrade", "[tracking_catalog]") SearchResult resultBefore = catalog.Search(request); REQUIRE(resultBefore.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Version) == + REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Version) == manifest.Version); // Change name @@ -157,7 +157,7 @@ TEST_CASE("TrackingCatalog_Upgrade", "[tracking_catalog]") SearchResult resultAfter = catalog.Search(request); REQUIRE(resultAfter.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Version) == + REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Version) == manifest.Version); } diff --git a/src/AppInstallerCLITests/SQLiteIndexSource.cpp b/src/AppInstallerCLITests/SQLiteIndexSource.cpp index 6e8626c64b..79b5b99200 100644 --- a/src/AppInstallerCLITests/SQLiteIndexSource.cpp +++ b/src/AppInstallerCLITests/SQLiteIndexSource.cpp @@ -86,7 +86,7 @@ TEST_CASE("SQLiteIndexSource_Id", "[sqliteindexsource]") auto results = source->Search(request); REQUIRE(results.Matches.size() == 1); REQUIRE(results.Matches[0].Package); - auto latestVersion = results.Matches[0].Package->GetLatestAvailableVersion(); + auto latestVersion = results.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins); REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Id).get() == manifest.Id); } @@ -107,7 +107,7 @@ TEST_CASE("SQLiteIndexSource_Name", "[sqliteindexsource]") auto results = source->Search(request); REQUIRE(results.Matches.size() == 1); REQUIRE(results.Matches[0].Package); - auto latestVersion = results.Matches[0].Package->GetLatestAvailableVersion(); + auto latestVersion = results.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins); REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Name).get() == manifest.DefaultLocalization.Get()); } @@ -129,7 +129,7 @@ TEST_CASE("SQLiteIndexSource_Versions", "[sqliteindexsource]") REQUIRE(results.Matches.size() == 1); REQUIRE(results.Matches[0].Package); - auto result = results.Matches[0].Package->GetAvailableVersionKeys(); + auto result = results.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins); REQUIRE(result.size() == 1); REQUIRE(result[0].Version == manifest.Version); REQUIRE(result[0].Channel == manifest.Channel); @@ -153,7 +153,7 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") REQUIRE(results.Matches[0].Package); auto package = results.Matches[0].Package.get(); - auto specificResultVersion = package->GetAvailableVersion(PackageVersionKey("", manifest.Version, manifest.Channel)); + auto specificResultVersion = package->GetAvailableVersion(PackageVersionKey("", manifest.Version, manifest.Channel), PinBehavior::IgnorePins); REQUIRE(specificResultVersion); auto specificResult = specificResultVersion->GetManifest(); REQUIRE(specificResult.Id == manifest.Id); @@ -161,7 +161,7 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") REQUIRE(specificResult.Version == manifest.Version); REQUIRE(specificResult.Channel == manifest.Channel); - auto latestResultVersion = package->GetAvailableVersion(PackageVersionKey("", "", manifest.Channel)); + auto latestResultVersion = package->GetAvailableVersion(PackageVersionKey("", "", manifest.Channel), PinBehavior::IgnorePins); REQUIRE(latestResultVersion); auto latestResult = latestResultVersion->GetManifest(); REQUIRE(latestResult.Id == manifest.Id); @@ -169,7 +169,7 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") REQUIRE(latestResult.Version == manifest.Version); REQUIRE(latestResult.Channel == manifest.Channel); - auto noResultVersion = package->GetAvailableVersion(PackageVersionKey("", "blargle", "flargle")); + auto noResultVersion = package->GetAvailableVersion(PackageVersionKey("", "blargle", "flargle"), PinBehavior::IgnorePins); REQUIRE(!noResultVersion); } diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 4d97da563c..938455edc9 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -26,7 +26,7 @@ namespace AppInstaller::Repository { return { availablePackage->GetProperty(PackageProperty::Id).get(), - availablePackage->GetLatestAvailableVersion()->GetSource().GetIdentifier() + availablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetSource().GetIdentifier() }; } @@ -106,9 +106,9 @@ namespace AppInstaller::Repository std::chrono::system_clock::time_point resultTime{}; std::shared_ptr resultVersion; - for (const auto& key : trackingPackage->GetAvailableVersionKeys()) + for (const auto& key : trackingPackage->GetAvailableVersionKeys(PinBehavior::IgnorePins)) { - auto version = trackingPackage->GetAvailableVersion(key); + auto version = trackingPackage->GetAvailableVersion(key, PinBehavior::IgnorePins); if (version) { auto metadata = version->GetMetadata(); @@ -142,12 +142,12 @@ namespace AppInstaller::Repository { // Stores raw versions value strings to run a preliminary check whether version mapping is needed. std::vector> rawVersionValues; - auto versionKeys = availablePackage->GetAvailableVersionKeys(); + auto versionKeys = availablePackage->GetAvailableVersionKeys(PinBehavior::IgnorePins); bool shouldTryPerformMapping = false; for (auto const& versionKey : versionKeys) { - auto availableVersion = availablePackage->GetAvailableVersion(versionKey); + auto availableVersion = availablePackage->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); std::string arpMinVersion = availableVersion->GetProperty(PackageVersionProperty::ArpMinVersion); std::string arpMaxVersion = availableVersion->GetProperty(PackageVersionProperty::ArpMaxVersion); @@ -403,7 +403,7 @@ namespace AppInstaller::Repository } } - return AvailablePackage->GetAvailableVersion(versionKey); + return AvailablePackage->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); } }; @@ -486,7 +486,13 @@ namespace AppInstaller::Repository std::shared_ptr GetLatestAvailableVersion(PinBehavior pinBehavior) const override { - return GetAvailableVersion({ "", "", m_installedChannel.get() }, pinBehavior); + auto availableVersionKeys = GetAvailableVersionKeys(pinBehavior); + if (availableVersionKeys.empty()) + { + return {}; + } + + return GetAvailableVersion(availableVersionKeys.front(), PinBehavior::IgnorePins); } std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior pinBehavior) const override @@ -799,9 +805,9 @@ namespace AppInstaller::Repository } PackageData result; - for (auto const& versionKey : availableMatch.Package->GetAvailableVersionKeys()) + for (auto const& versionKey : availableMatch.Package->GetAvailableVersionKeys(PinBehavior::IgnorePins)) { - auto packageVersion = availableMatch.Package->GetAvailableVersion(versionKey); + auto packageVersion = availableMatch.Package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); AddSystemReferenceStrings(packageVersion.get(), result); } return result; @@ -827,9 +833,9 @@ namespace AppInstaller::Repository } PackageData result; - for (auto const& versionKey : trackingMatch.Package->GetAvailableVersionKeys()) + for (auto const& versionKey : trackingMatch.Package->GetAvailableVersionKeys(PinBehavior::IgnorePins)) { - auto packageVersion = trackingMatch.Package->GetAvailableVersion(versionKey); + auto packageVersion = trackingMatch.Package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); AddSystemReferenceStrings(packageVersion.get(), result); } return result; @@ -1193,6 +1199,16 @@ namespace AppInstaller::Repository // Optimization for the "everything installed" case, no need to allow for reverse correlations if (request.IsForEverything() && m_searchBehavior == CompositeSearchBehavior::Installed) { + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + { + // Look up any pins for the packages found + auto pinningIndex = PinningIndex::OpenOrCreateDefault(); + for (auto& match : result.Matches) + { + match.Package->GetExistingPins(pinningIndex); + } + } + return std::move(result); } } diff --git a/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp b/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp index 2270db5415..d7a944392e 100644 --- a/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp +++ b/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp @@ -120,14 +120,14 @@ namespace AppInstaller::Repository { // Use the base version as available version if installed version is mapped to be an approximate. versionKey.Version = installedVersionAsVersion.GetBaseVersion().ToString(); - availableVersion = package->GetAvailableVersion(versionKey); + availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); // It's unexpected if the installed version is already mapped to some version. THROW_HR_IF(E_UNEXPECTED, !availableVersion); } else { versionKey.Version = installedVersionAsVersion.ToString(); - availableVersion = package->GetAvailableVersion(versionKey); + availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); if (availableVersion) { checkFileHash = true; @@ -139,7 +139,7 @@ namespace AppInstaller::Repository { // No installed version, or installed version not found in available versions, // then attempt to check installed status using latest version. - availableVersion = package->GetLatestAvailableVersion(); + availableVersion = package->GetLatestAvailableVersion(PinBehavior::IgnorePins); THROW_HR_IF(E_UNEXPECTED, !availableVersion); } diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h index 3e047595a0..6443f9d253 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h +++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h @@ -330,16 +330,16 @@ namespace AppInstaller::Repository // The versions will be returned in sorted, descending order. // Ex. { 4, 3, 2, 1 } // The list may contain versions from multiple sources. - virtual std::vector GetAvailableVersionKeys(PinBehavior pinBehavior = PinBehavior::ConsiderPins) const = 0; + virtual std::vector GetAvailableVersionKeys(PinBehavior pinBehavior) const = 0; // Gets a specific version of this package. - virtual std::shared_ptr GetLatestAvailableVersion(PinBehavior pinBehavior = PinBehavior::ConsiderPins) const = 0; + virtual std::shared_ptr GetLatestAvailableVersion(PinBehavior pinBehavior) const = 0; // Gets a specific version of this package. - virtual std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior pinBehavior = PinBehavior::ConsiderPins) const = 0; + virtual std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior pinBehavior) const = 0; // Gets a value indicating whether an available version is newer than the installed version. - virtual bool IsUpdateAvailable(PinBehavior pinBehavior = PinBehavior::ConsiderPins) const = 0; + virtual bool IsUpdateAvailable(PinBehavior pinBehavior) const = 0; // Determines if the given IPackage refers to the same package as this one. virtual bool IsSame(const IPackage*) const = 0; diff --git a/src/Microsoft.Management.Deployment/CatalogPackage.cpp b/src/Microsoft.Management.Deployment/CatalogPackage.cpp index 16abdd270b..edc7cc3e15 100644 --- a/src/Microsoft.Management.Deployment/CatalogPackage.cpp +++ b/src/Microsoft.Management.Deployment/CatalogPackage.cpp @@ -51,7 +51,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation [&]() { // Vector hasn't been populated yet. - for (auto const& versionKey : m_package.get()->GetAvailableVersionKeys()) + for (auto const& versionKey : m_package.get()->GetAvailableVersionKeys(AppInstaller::Repository::PinBehavior::IgnorePins)) { auto packageVersionId = winrt::make_self>(); @@ -66,7 +66,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation std::call_once(m_defaultInstallVersionOnceFlag, [&]() { - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> latestVersion = m_package.get()->GetLatestAvailableVersion(); + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> latestVersion = m_package.get()->GetLatestAvailableVersion(AppInstaller::Repository::PinBehavior::IgnorePins); if (latestVersion) { // DefaultInstallVersion hasn't been created yet, create and populate it. @@ -84,7 +84,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; ::AppInstaller::Repository::PackageVersionKey internalVersionKey(winrt::to_string(versionKey.PackageCatalogId()), winrt::to_string(versionKey.Version()), winrt::to_string(versionKey.Channel())); - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> availableVersion = m_package.get()->GetAvailableVersion(internalVersionKey); + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> availableVersion = m_package.get()->GetAvailableVersion(internalVersionKey, AppInstaller::Repository::PinBehavior::IgnorePins); if (availableVersion) { auto packageVersionInfoImpl = winrt::make_selfIsUpdateAvailable(); + return m_package->IsUpdateAvailable(AppInstaller::Repository::PinBehavior::IgnorePins); } Windows::Foundation::IAsyncOperation CatalogPackage::CheckInstalledStatusAsync( Microsoft::Management::Deployment::InstalledStatusType checkTypes) From 4c97b0d107181db165b1757c6830ca731bb2635f Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 2 Jan 2023 17:29:52 -0800 Subject: [PATCH 11/55] Fix failing composite source tests --- .../Workflows/InstallFlow.cpp | 1 - src/AppInstallerCLITests/CompositeSource.cpp | 95 ++++++++++--------- src/AppInstallerCLITests/TestSource.cpp | 2 +- .../CompositeSource.cpp | 70 ++++++-------- .../Public/winget/RepositorySearch.h | 8 +- 5 files changed, 84 insertions(+), 92 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 6ecbd2929e..c1c4f73882 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -246,7 +246,6 @@ namespace AppInstaller::CLI::Workflow { bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); UpdateBehaviorEnum updateBehavior = context.Get().value().UpdateBehavior; - THROW_HR_IF(E_FAIL, context.Contains(Execution::Data::Package)); switch (m_installerType) { diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index cfc7a7b404..00199b41ce 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -110,7 +110,8 @@ Manifest::Manifest MakeDefaultManifest() struct TestPackageHelper { - TestPackageHelper(bool isInstalled) : m_isInstalled(isInstalled), m_manifest(MakeDefaultManifest()) {} + TestPackageHelper(bool isInstalled, std::shared_ptr source = {}) : + m_isInstalled(isInstalled), m_manifest(MakeDefaultManifest()), m_source(source) {} TestPackageHelper& WithId(const std::string& id) { @@ -148,11 +149,11 @@ struct TestPackageHelper { if (m_isInstalled) { - m_package = TestPackage::Make(m_manifest, TestPackage::MetadataMap{}); + m_package = TestPackage::Make(m_manifest, TestPackage::MetadataMap{}, std::vector(), m_source); } else { - m_package = TestPackage::Make(std::vector{ m_manifest }); + m_package = TestPackage::Make(std::vector{ m_manifest }, m_source); } } @@ -167,17 +168,18 @@ struct TestPackageHelper private: bool m_isInstalled; Manifest::Manifest m_manifest; + std::shared_ptr m_source; std::shared_ptr m_package; }; TestPackageHelper MakeInstalled() { - return { true }; + return { /* isInstalled */ true}; } -TestPackageHelper MakeAvailable() +TestPackageHelper MakeAvailable(std::shared_ptr source) { - return { false }; + return { /* isInstalled */ false, source}; } void RequireIncludes(const std::vector& filters, PackageMatchField field, MatchType type, std::optional value = {}) @@ -222,7 +224,7 @@ TEST_CASE("CompositeSource_PackageFamilyName_Available", "[CompositeSource]") RequireIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithPFN(pfn), Criteria()); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN(pfn), Criteria()); return result; }; @@ -258,7 +260,7 @@ TEST_CASE("CompositeSource_ProductCode_Available", "[CompositeSource]") RequireIncludes(request.Inclusions, PackageMatchField::ProductCode, MatchType::Exact, pc); SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithPC(pc), Criteria()); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithPC(pc), Criteria()); return result; }; @@ -278,7 +280,7 @@ TEST_CASE("CompositeSource_NameAndPublisher_Match", "[CompositeSource]") RequireIncludes(request.Inclusions, PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact); SearchResult result; - result.Matches.emplace_back(MakeAvailable(), Criteria()); + result.Matches.emplace_back(MakeAvailable(setup.Available), Criteria()); return result; }; @@ -298,8 +300,8 @@ TEST_CASE("CompositeSource_MultiMatch_FindsStrongMatch", "[CompositeSource]") setup.Available->SearchFunction = [&](const SearchRequest&) { SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithId("A different ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); - result.Matches.emplace_back(MakeAvailable().WithDefaultName(name), Criteria(PackageMatchField::PackageFamilyName)); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithId("A different ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithDefaultName(name), Criteria(PackageMatchField::PackageFamilyName)); return result; }; @@ -319,8 +321,8 @@ TEST_CASE("CompositeSource_MultiMatch_DoesNotFindStrongMatch", "[CompositeSource setup.Available->SearchFunction = [&](const SearchRequest&) { SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithId("A different ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); - result.Matches.emplace_back(MakeAvailable().WithId("Another diff ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithId("A different ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithId("Another diff ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); return result; }; @@ -335,10 +337,10 @@ TEST_CASE("CompositeSource_FoundByBothRootSearches", "[CompositeSource]") { std::string pfn = "sortof_apfn"; + CompositeTestSetup setup; auto installedPackage = MakeInstalled().WithPFN(pfn); - auto availablePackage = MakeAvailable().WithPFN(pfn); + auto availablePackage = MakeAvailable(setup.Available).WithPFN(pfn); - CompositeTestSetup setup; setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); setup.Installed->SearchFunction = [&](const SearchRequest& request) { @@ -380,13 +382,13 @@ TEST_CASE("CompositeSource_OnlyAvailableFoundByRootSearch", "[CompositeSource]") return result; }; - setup.Available->Everything.Matches.emplace_back(MakeAvailable().WithPFN(pfn), Criteria()); + setup.Available->Everything.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN(pfn), Criteria()); setup.Available->SearchFunction = [&](const SearchRequest& request) { RequireIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithPFN(pfn), Criteria()); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN(pfn), Criteria()); return result; }; @@ -402,13 +404,13 @@ TEST_CASE("CompositeSource_FoundByAvailableRootSearch_NotInstalled", "[Composite std::string pfn = "sortof_apfn"; CompositeTestSetup setup; - setup.Available->Everything.Matches.emplace_back(MakeAvailable().WithPFN(pfn), Criteria()); + setup.Available->Everything.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN(pfn), Criteria()); setup.Available->SearchFunction = [&](const SearchRequest& request) { RequireIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithPFN(pfn), Criteria()); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN(pfn), Criteria()); return result; }; @@ -423,10 +425,10 @@ TEST_CASE("CompositeSource_UpdateWithBetterMatchCriteria", "[CompositeSource]") MatchType originalType = MatchType::Wildcard; MatchType type = MatchType::Exact; + CompositeTestSetup setup; auto installedPackage = MakeInstalled().WithPFN(pfn); - auto availablePackage = MakeAvailable().WithPFN(pfn); + auto availablePackage = MakeAvailable(setup.Available).WithPFN(pfn); - CompositeTestSetup setup; setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); setup.Available->SearchFunction = [&](const SearchRequest& request) @@ -488,7 +490,7 @@ TEST_CASE("CompositePackage_PropertyFromAvailable", "[CompositeSource]") setup.Available->SearchFunction = [&](const SearchRequest&) { SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithId(id), Criteria()); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithId(id), Criteria()); return result; }; @@ -515,7 +517,7 @@ TEST_CASE("CompositePackage_AvailableVersions_ChannelFilteredOut", "[CompositeSo hasChannel.Version = "2.0"; SearchResult result; - result.Matches.emplace_back(TestPackage::Make(std::vector{ noChannel, hasChannel }), Criteria()); + result.Matches.emplace_back(TestPackage::Make(std::vector{ noChannel, hasChannel }, setup.Available), Criteria()); REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 2); return result; }; @@ -551,7 +553,7 @@ TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[Composite hasChannel.Version = "2.0"; SearchResult result; - result.Matches.emplace_back(TestPackage::Make(std::vector{ noChannel, hasChannel }), Criteria()); + result.Matches.emplace_back(TestPackage::Make(std::vector{ noChannel, hasChannel }, setup.Available), Criteria()); REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 2); return result; }; @@ -570,7 +572,7 @@ TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[Composite REQUIRE(result.Matches[0].Package->IsUpdateAvailable(PinBehavior::IgnorePins)); } -TEST_CASE("CompositeSource_MultipleAvailableSources_MatchFirst", "[CompositeSource]") +TEST_CASE("CompositeSource_MultipleAvailableSources_MatchAll", "[CompositeSource]") { std::string pfn = "sortof_apfn"; std::string firstName = "Name1"; @@ -587,7 +589,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchFirst", "[CompositeSour RequireIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithDefaultName(firstName), Criteria()); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithDefaultName(firstName), Criteria()); return result; }; @@ -596,7 +598,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchFirst", "[CompositeSour RequireIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithDefaultName(secondName), Criteria()); + result.Matches.emplace_back(MakeAvailable(secondAvailable).WithDefaultName(secondName), Criteria()); return result; }; @@ -604,7 +606,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchFirst", "[CompositeSour REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 2); REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Name).get() == firstName); } @@ -625,7 +627,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchSecond", "[CompositeSou RequireIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithDefaultName(secondName), Criteria()); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithDefaultName(secondName), Criteria()); return result; }; @@ -656,8 +658,8 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_ReverseMatchBoth", "[Composi return result; }; - setup.Available->Everything.Matches.emplace_back(MakeAvailable().WithPFN(pfn), Criteria()); - secondAvailable->Everything.Matches.emplace_back(MakeAvailable().WithPFN(pfn), Criteria()); + setup.Available->Everything.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN(pfn), Criteria()); + secondAvailable->Everything.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN(pfn), Criteria()); SearchResult result = setup.Search(); @@ -670,7 +672,7 @@ TEST_CASE("CompositeSource_IsSame", "[CompositeSource]") { CompositeTestSetup setup; setup.Installed->Everything.Matches.emplace_back(MakeInstalled().WithPFN("sortof_apfn"), Criteria()); - setup.Available->Everything.Matches.emplace_back(MakeAvailable().WithPFN("sortof_apfn"), Criteria()); + setup.Available->Everything.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN("sortof_apfn"), Criteria()); SearchResult result1 = setup.Search(); REQUIRE(result1.Matches.size() == 1); @@ -690,7 +692,7 @@ TEST_CASE("CompositeSource_AvailableSearchFailure", "[CompositeSource]") AvailableSucceeds->SearchFunction = [&](const SearchRequest&) { SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithPFN(pfn), Criteria()); + result.Matches.emplace_back(MakeAvailable({}).WithPFN(pfn), Criteria()); return result; }; @@ -734,7 +736,7 @@ TEST_CASE("CompositeSource_InstalledToAvailableCorrelationSearchFailure", "[Comp CompositeTestSetup setup; setup.Installed->Everything.Matches.emplace_back(MakeInstalled().WithPFN(pfn), Criteria()); - setup.Available->Everything.Matches.emplace_back(MakeAvailable().WithPFN(pfn), Criteria()); + setup.Available->Everything.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN(pfn), Criteria()); std::shared_ptr AvailableFails = std::make_shared(); AvailableFails->SearchFunction = [&](const SearchRequest&) -> SearchResult { THROW_HR(expectedHR); }; @@ -772,7 +774,7 @@ TEST_CASE("CompositeSource_InstalledAvailableSearchFailure", "[CompositeSource]" setup.Available->SearchFunction = [&](const SearchRequest&) { SearchResult result; - result.Matches.emplace_back(MakeAvailable().WithPFN(pfn), Criteria()); + result.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN(pfn), Criteria()); return result; }; @@ -812,10 +814,10 @@ TEST_CASE("CompositeSource_TrackingPackageFound", "[CompositeSource]") std::string availableID = "Available.ID"; std::string pfn = "sortof_apfn"; + CompositeWithTrackingTestSetup setup; auto installedPackage = MakeInstalled().WithPFN(pfn); - auto availablePackage = MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + auto availablePackage = MakeAvailable(setup.Available).WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - CompositeWithTrackingTestSetup setup; setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); setup.Installed->SearchFunction = [&](const SearchRequest& request) { @@ -860,10 +862,10 @@ TEST_CASE("CompositeSource_TrackingPackageFound_MetadataPopulatedFromTracking", std::string availableID = "Available.ID"; std::string pfn = "sortof_apfn"; + CompositeWithTrackingTestSetup setup; auto installedPackage = MakeInstalled().WithPFN(pfn); - auto availablePackage = MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + auto availablePackage = MakeAvailable(setup.Available).WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - CompositeWithTrackingTestSetup setup; setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); setup.Installed->SearchFunction = [&](const SearchRequest& request) { @@ -921,10 +923,10 @@ TEST_CASE("CompositeSource_TrackingFound_AvailableNot", "[CompositeSource]") std::string availableID = "Available.ID"; std::string pfn = "sortof_apfn"; + CompositeWithTrackingTestSetup setup; auto installedPackage = MakeInstalled().WithPFN(pfn); - auto availablePackage = MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + auto availablePackage = MakeAvailable(setup.Available).WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - CompositeWithTrackingTestSetup setup; setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); setup.Installed->SearchFunction = [&](const SearchRequest& request) { @@ -948,13 +950,14 @@ TEST_CASE("CompositeSource_TrackingFound_AvailableNot", "[CompositeSource]") TEST_CASE("CompositeSource_TrackingFound_AvailablePath", "[CompositeSource]") { + CompositeWithTrackingTestSetup setup; + std::string availableID = "Available.ID"; std::string pfn = "sortof_apfn"; auto installedPackage = MakeInstalled().WithPFN(pfn); - auto availablePackage = MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + auto availablePackage = MakeAvailable(setup.Available).WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - CompositeWithTrackingTestSetup setup; setup.Installed->SearchFunction = [&](const SearchRequest& request) { RequireIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); @@ -991,10 +994,10 @@ TEST_CASE("CompositeSource_TrackingFound_NotInstalled", "[CompositeSource]") std::string availableID = "Available.ID"; std::string pfn = "sortof_apfn"; + CompositeWithTrackingTestSetup setup; auto installedPackage = MakeInstalled().WithPFN(pfn); - auto availablePackage = MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + auto availablePackage = MakeAvailable(setup.Available).WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - CompositeWithTrackingTestSetup setup; setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); setup.Tracking->GetIndex().AddManifest(availablePackage); @@ -1007,7 +1010,7 @@ TEST_CASE("CompositeSource_TrackingFound_NotInstalled", "[CompositeSource]") TEST_CASE("CompositeSource_NullInstalledVersion", "[CompositeSource]") { CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(MakeAvailable(), Criteria()); + setup.Installed->Everything.Matches.emplace_back(MakeAvailable(setup.Available), Criteria()); // We are mostly testing to see if a null installed version causes an AV or not SearchResult result = setup.Search(); diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp index d575942dab..6413431894 100644 --- a/src/AppInstallerCLITests/TestSource.cpp +++ b/src/AppInstallerCLITests/TestSource.cpp @@ -182,7 +182,7 @@ namespace TestCommon std::vector result; for (const auto& version : AvailableVersions) { - result.emplace_back(PackageVersionKey("", version->GetProperty(PackageVersionProperty::Version).get(), version->GetProperty(PackageVersionProperty::Channel).get())); + result.emplace_back(PackageVersionKey(version->GetSource().GetIdentifier(), version->GetProperty(PackageVersionProperty::Version).get(), version->GetProperty(PackageVersionProperty::Channel).get())); } return result; } diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 938455edc9..8fe7b06e7e 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -340,8 +340,16 @@ namespace AppInstaller::Repository { CompositeAvailablePackage() {} CompositeAvailablePackage(std::shared_ptr availablePackage, std::optional pin = {}) - : AvailablePackage(availablePackage), Pin(pin) {} + : AvailablePackage(availablePackage), Pin(pin) + { + auto lastAvailable = AvailablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins); + if (lastAvailable) + { + SourceId = lastAvailable->GetSource().GetIdentifier(); + } + } + std::string SourceId; std::shared_ptr AvailablePackage; std::optional Pin; @@ -467,9 +475,9 @@ namespace AppInstaller::Repository { std::vector result; - for (const auto& entry : m_availablePackages) + for (const auto& availablePackage : m_availablePackages) { - auto versionKeys = entry.second.GetAvailableVersionKeys(pinBehavior); + auto versionKeys = availablePackage.GetAvailableVersionKeys(pinBehavior); std::copy(versionKeys.begin(), versionKeys.end(), std::back_inserter(result)); } @@ -479,8 +487,9 @@ namespace AppInstaller::Repository std::remove_if(result.begin(), result.end(), [&](const PackageVersionKey& pvk) { return !Utility::ICUCaseInsensitiveEquals(pvk.Channel, channel); }), result.end()); - // Put latest versions at the front - std::sort(result.begin(), result.end()); + // Put latest versions at the front; for versions available from multiple sources maintain the order they were added in + std::stable_sort(result.begin(), result.end()); + return result; } @@ -497,25 +506,17 @@ namespace AppInstaller::Repository std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior pinBehavior) const override { - if (Utility::IsEmptyOrWhitespace(versionKey.SourceId)) + for (const auto& availablePackage : m_availablePackages) { - // Look up in all sources - for (const auto& entry : m_availablePackages) + if (!Utility::IsEmptyOrWhitespace(versionKey.SourceId) && versionKey.SourceId != availablePackage.SourceId) { - auto package = entry.second.GetAvailableVersion(versionKey, pinBehavior); - if (package) - { - return package; - } + continue; } - } - else - { - // Use the provided source - auto itr = m_availablePackages.find(versionKey.SourceId); - if (itr != m_availablePackages.end()) + + auto package = availablePackage.GetAvailableVersion(versionKey, pinBehavior); + if (package) { - return itr->second.GetAvailableVersion(versionKey, pinBehavior); + return package; } } @@ -548,18 +549,13 @@ namespace AppInstaller::Repository return false; } - auto itr = m_availablePackages.begin(); - auto otherItr = otherComposite->m_availablePackages.begin(); - while (itr != m_availablePackages.end()) + for (size_t i = 0; i < m_availablePackages.size(); ++i) { - if (itr->first != otherItr->first || - !itr->second.AvailablePackage->IsSame(otherItr->second.AvailablePackage.get())) + if (m_availablePackages[i].SourceId != otherComposite->m_availablePackages[i].SourceId || + !m_availablePackages[i].AvailablePackage->IsSame(otherComposite->m_availablePackages[i].AvailablePackage.get())) { return false; } - - ++itr; - ++otherItr; } return true; @@ -569,9 +565,9 @@ namespace AppInstaller::Repository { if (other) { - for (const auto& entry : m_availablePackages) + for (const auto& availablePackage : m_availablePackages) { - if (other->IsSame(entry.second.AvailablePackage.get())) + if (other->IsSame(availablePackage.AvailablePackage.get())) { return true; } @@ -601,8 +597,7 @@ namespace AppInstaller::Repository TrySetOverrideInstalledVersion(availablePackage); } - auto sourceId = availablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetSource().GetIdentifier(); - m_availablePackages[sourceId] = CompositeAvailablePackage{ std::move(availablePackage) }; + m_availablePackages.emplace_back(std::move(availablePackage)); } } @@ -618,17 +613,15 @@ namespace AppInstaller::Repository { // If the package is installed, we need to add the pin information to the available packages from any source. // If the package is not installed, we clean up stale pin information here. - for (auto& entry : m_availablePackages) + for (auto& availablePackage : m_availablePackages) { - auto availablePackage = entry.second.AvailablePackage; - - auto pinKey = GetPinKey(availablePackage.get()); + auto pinKey = GetPinKey(availablePackage.AvailablePackage.get()); if (m_installedPackage) { auto pin = pinningIndex.GetPin(pinKey); if (pin.has_value()) { - entry.second.Pin = std::move(pin.value()); + availablePackage.Pin = std::move(pin.value()); } } else if (pinningIndex.GetPin(pinKey)) @@ -661,8 +654,7 @@ namespace AppInstaller::Repository std::shared_ptr m_trackingPackage; std::shared_ptr m_trackingPackageVersion; std::string m_overrideInstalledVersion; - // Available packages indexed by source ID. - std::map m_availablePackages; + std::vector m_availablePackages; }; // The comparator compares the ResultMatch by MatchType first, then Field in a predefined order. diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h index 6443f9d253..f2a8191712 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h +++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h @@ -236,11 +236,9 @@ namespace AppInstaller::Repository bool operator<(const PackageVersionKey& other) const { - Utility::VersionAndChannel vac({ Version }, { Channel }); - Utility::VersionAndChannel otherVac({ other.Version }, { other.Channel }); - if (vac < otherVac) return true; - if (otherVac < vac) return false; - return SourceId < other.SourceId; + // Sort using only the version and channel. + // The order for the sources depends on the context. + return Utility::VersionAndChannel({ Version }, { Channel }) < Utility::VersionAndChannel({ other.Version }, { other.Channel }); } }; From 83f27f4f6871aa71d4e29844cd1bc58120d4d281 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 2 Jan 2023 18:45:53 -0800 Subject: [PATCH 12/55] Fix remaining failing tests --- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 11 ++++--- src/AppInstallerCLITests/PinFlow.cpp | 29 ++++++------------- src/AppInstallerCLITests/TestHooks.h | 18 ++++++++++++ src/AppInstallerCLITests/UpdateFlow.cpp | 10 +++---- .../Microsoft/PinningIndex.cpp | 15 +++++++++- 5 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 99bd9e6882..5205366111 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -90,8 +90,7 @@ namespace AppInstaller::CLI::Workflow auto installedVersionString = installedVersion->GetProperty(PackageVersionProperty::Version); - std::vector pinsToAdd; - std::vector pinsToUpdate; + std::vector pinsToAddOrUpdate; std::set sources; auto pinningIndex = context.Get(); @@ -131,7 +130,7 @@ namespace AppInstaller::CLI::Workflow { AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); context.Reporter.Warn() << Resource::String::PinExistsOverwriting << std::endl; - pinsToUpdate.push_back(std::move(pin)); + pinsToAddOrUpdate.push_back(std::move(pin)); } else { @@ -141,13 +140,13 @@ namespace AppInstaller::CLI::Workflow } else { - pinsToAdd.push_back(std::move(pin)); + pinsToAddOrUpdate.push_back(std::move(pin)); } } - if (!pinsToAdd.empty()) + if (!pinsToAddOrUpdate.empty()) { - for (auto pin : pinsToAdd) + for (auto pin : pinsToAddOrUpdate) { pinningIndex->AddOrUpdatePin(pin); } diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index a2b527eaa7..d6d6a97cfc 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "WorkflowCommon.h" +#include "TestHooks.h" #include #include #include @@ -13,24 +14,13 @@ using namespace AppInstaller::CLI::Workflow; using namespace AppInstaller::Repository::Microsoft; using namespace AppInstaller::Pinning; -void OverrideForOpenPinningIndex(TestContext& context, const std::filesystem::path& indexPath) -{ - context.Override({ OpenPinningIndex, [=](TestContext& context) - { - auto pinningIndex = std::filesystem::exists(indexPath) ? - PinningIndex::Open(indexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite) : - PinningIndex::CreateNew(indexPath.u8string()); - context.Add(std::make_shared(std::move(pinningIndex))); - } }); -} - TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); std::ostringstream pinAddOutput; TestContext addContext{ pinAddOutput, std::cin }; - OverrideForOpenPinningIndex(addContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(addContext); addContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); addContext.Args.AddArg(Execution::Args::Type::BlockingPin); @@ -51,7 +41,6 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") std::ostringstream pinListOutput; TestContext listContext{ pinListOutput, std::cin }; - OverrideForOpenPinningIndex(listContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(listContext); listContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); @@ -66,7 +55,6 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") { std::ostringstream pinRemoveOutput; TestContext removeContext{ pinRemoveOutput, std::cin }; - OverrideForOpenPinningIndex(removeContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(removeContext); removeContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); @@ -82,7 +70,6 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") { std::ostringstream pinResetOutput; TestContext resetContext{ pinResetOutput, std::cin }; - OverrideForOpenPinningIndex(resetContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(resetContext); SECTION("Without --force") @@ -112,7 +99,6 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") { std::ostringstream pinUpdateOutput; TestContext updateContext{ pinUpdateOutput, std::cin }; - OverrideForOpenPinningIndex(updateContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(updateContext); updateContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); @@ -146,6 +132,9 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") TEST_CASE("PinFlow_Add_NotFound", "[PinFlow][workflow]") { + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + std::ostringstream pinAddOutput; TestContext addContext{ pinAddOutput, std::cin }; OverrideForCompositeInstalledSource(addContext); @@ -161,10 +150,10 @@ TEST_CASE("PinFlow_Add_NotFound", "[PinFlow][workflow]") TEST_CASE("PinFlow_Add_NotInstalled", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); std::ostringstream pinAddOutput; TestContext addContext{ pinAddOutput, std::cin }; - OverrideForOpenPinningIndex(addContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(addContext); addContext.Args.AddArg(Execution::Args::Type::Query, "TestExeInstallerWithNothingInstalled"sv); @@ -182,10 +171,10 @@ TEST_CASE("PinFlow_Add_NotInstalled", "[PinFlow][workflow]") TEST_CASE("PinFlow_ListEmpty", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); std::ostringstream pinListOutput; TestContext listContext{ pinListOutput, std::cin }; - OverrideForOpenPinningIndex(listContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(listContext); PinListCommand pinList({}); @@ -198,10 +187,10 @@ TEST_CASE("PinFlow_ListEmpty", "[PinFlow][workflow]") TEST_CASE("PinFlow_RemoveNonExisting", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); std::ostringstream pinRemoveOutput; TestContext removeContext{ pinRemoveOutput, std::cin }; - OverrideForOpenPinningIndex(removeContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(removeContext); removeContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); @@ -215,10 +204,10 @@ TEST_CASE("PinFlow_RemoveNonExisting", "[PinFlow][workflow]") TEST_CASE("PinFlow_ResetEmpty", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); std::ostringstream pinResetOutput; TestContext resetContext{ pinResetOutput, std::cin }; - OverrideForOpenPinningIndex(resetContext, indexFile.GetPath()); OverrideForCompositeInstalledSource(resetContext); resetContext.Args.AddArg(Execution::Args::Type::Force); diff --git a/src/AppInstallerCLITests/TestHooks.h b/src/AppInstallerCLITests/TestHooks.h index ee0c56573d..0496d4a20f 100644 --- a/src/AppInstallerCLITests/TestHooks.h +++ b/src/AppInstallerCLITests/TestHooks.h @@ -34,6 +34,11 @@ namespace AppInstaller void TestHook_ClearSourceFactoryOverrides(); } + namespace Repository::Microsoft + { + void TestHook_SetPinningIndex_Override(std::optional&& indexPath); + } + namespace Logging { void TestHook_SetTelemetryOverride(std::shared_ptr ttl); @@ -88,4 +93,17 @@ namespace TestHook private: bool m_status; }; + + struct SetPinningIndex_Override + { + SetPinningIndex_Override(const std::filesystem::path& indexPath) + { + AppInstaller::Repository::Microsoft::TestHook_SetPinningIndex_Override(indexPath); + } + + ~SetPinningIndex_Override() + { + AppInstaller::Repository::Microsoft::TestHook_SetPinningIndex_Override({}); + } + }; } \ No newline at end of file diff --git a/src/AppInstallerCLITests/UpdateFlow.cpp b/src/AppInstallerCLITests/UpdateFlow.cpp index e2d51fac60..c1439b53b9 100644 --- a/src/AppInstallerCLITests/UpdateFlow.cpp +++ b/src/AppInstallerCLITests/UpdateFlow.cpp @@ -292,7 +292,7 @@ TEST_CASE("UpdateFlow_NoArgs_UnknownVersion", "[UpdateFlow][workflow]") INFO(updateOutput.str()); // Verify --include-unknown help text is displayed if update is executed with no args and an unknown version package is available for upgrade. - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionCount).get()) != std::string::npos); + REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) != std::string::npos); } TEST_CASE("UpdateFlow_IncludeUnknown", "[UpdateFlow][workflow]") @@ -309,7 +309,7 @@ TEST_CASE("UpdateFlow_IncludeUnknown", "[UpdateFlow][workflow]") INFO(updateOutput.str()); // Verify unknown version package is displayed available for upgrade. - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionCount).get()) == std::string::npos); + REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) == std::string::npos); REQUIRE(updateOutput.str().find("unknown") != std::string::npos); } @@ -519,7 +519,7 @@ TEST_CASE("UpdateFlow_UpdateAllApplicable", "[UpdateFlow][workflow]") INFO(updateOutput.str()); // Verify that --include-unknown help message is displayed. - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionCount).get()) != std::string::npos); + REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) != std::string::npos); REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeUnknownVersion") == std::string::npos); // Verify installers are called. @@ -752,7 +752,7 @@ TEST_CASE("UpdateFlow_RequireExplicit", "[UpdateFlow][workflow]") REQUIRE(pinnedPackagesHeaderPosition != std::string::npos); REQUIRE(pinnedPackageLinePosition != std::string::npos); REQUIRE(pinnedPackagesHeaderPosition < pinnedPackageLinePosition); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeRequireExplicitCount)) == std::string::npos); + REQUIRE(updateOutput.str().find(Resource::String::UpgradeRequireExplicitCount(1)) == std::string::npos); } SECTION("Upgrade all except pinned") @@ -771,7 +771,7 @@ TEST_CASE("UpdateFlow_RequireExplicit", "[UpdateFlow][workflow]") auto s = updateOutput.str(); // Verify message is printed for skipped package - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeRequireExplicitCount)) != std::string::npos); + REQUIRE(updateOutput.str().find(Resource::String::UpgradeRequireExplicitCount(1)) != std::string::npos); // Verify package is not installed, but all others are REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index adeb1ca4a8..a2e6bd4f18 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -26,9 +26,22 @@ namespace AppInstaller::Repository::Microsoft return result; } +#ifndef AICLI_DISABLE_TEST_HOOKS + std::optional s_PinningIndexOverride{}; + void TestHook_SetPinningIndex_Override(std::optional&& indexPath) + { + s_PinningIndexOverride = std::move(indexPath); + } +#endif + PinningIndex PinningIndex::OpenOrCreateDefault(OpenDisposition disposition) { - const auto indexPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; + const auto DefaultIndexPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; +#ifndef AICLI_DISABLE_TEST_HOOKS + const auto indexPath = s_PinningIndexOverride.has_value() ? s_PinningIndexOverride.value() : DefaultIndexPath; +#else + const auto indexPath = DefaultIndexPath; +#endif AICLI_LOG(Repo, Info, << "Opening pinning index"); From 93c0fa965037916590889516c7948d37b1dc9476 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 3 Jan 2023 09:50:04 -0800 Subject: [PATCH 13/55] Typos & prevent unneeded op --- src/AppInstallerRepositoryCore/CompositeSource.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 8fe7b06e7e..45b95f2682 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -359,14 +359,14 @@ namespace AppInstaller::Repository { if (Pin->GetType() == Pinning::PinType::Blocking) { - AICLI_LOG(Repo, Info, << "Ignoring available versions from source [" << Pin->GetSourceId() << "] for package [ " << Pin->GetPackageId() << "] due to Blocking pin"); + AICLI_LOG(Repo, Info, << "Ignoring available versions from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] due to Blocking pin"); return {}; } else if (Pin->GetType() == Pinning::PinType::Pinning) { if (pinBehavior != PinBehavior::IncludePinned) { - AICLI_LOG(Repo, Info, << "Ignoring available versions from source [" << Pin->GetSourceId() << "] for package [ " << Pin->GetPackageId() << "] due to Pinning pin"); + AICLI_LOG(Repo, Info, << "Ignoring available versions from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] due to Pinning pin"); return {}; } } @@ -389,14 +389,14 @@ namespace AppInstaller::Repository { if (Pin->GetType() == Pinning::PinType::Blocking) { - AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [ " << Pin->GetPackageId() << "] due to Blocking pin"); + AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] due to Blocking pin"); return {}; } else if (Pin->GetType() == Pinning::PinType::Pinning) { if (pinBehavior != PinBehavior::IncludePinned) { - AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [ " << Pin->GetPackageId() << "] due to Pinning pin"); + AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] due to Pinning pin"); return {}; } } @@ -405,7 +405,7 @@ namespace AppInstaller::Repository Utility::GatedVersion gatedVersion(Pin->GetVersionString()); if (!gatedVersion.IsValidVersion(versionKey.Version)) { - AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [ " << Pin->GetPackageId() << "] as it does not satisfy Gating pin"); + AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] as it does not satisfy Gating pin"); return {}; } } @@ -1191,7 +1191,7 @@ namespace AppInstaller::Repository // Optimization for the "everything installed" case, no need to allow for reverse correlations if (request.IsForEverything() && m_searchBehavior == CompositeSearchBehavior::Installed) { - if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && !result.Matches.empty()) { // Look up any pins for the packages found auto pinningIndex = PinningIndex::OpenOrCreateDefault(); @@ -1314,7 +1314,7 @@ namespace AppInstaller::Repository result.Matches.erase(result.Matches.begin() + request.MaximumResults, result.Matches.end()); } - if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && !result.Matches.empty()) { // Look up any pins for the packages found auto pinningIndex = PinningIndex::OpenOrCreateDefault(); From 43653df5b09baa548b3db1eadfebba6e341d1fb2 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 3 Jan 2023 11:55:59 -0800 Subject: [PATCH 14/55] Add version gating --- src/AppInstallerCLITests/Versions.cpp | 18 +++++++++ .../Public/AppInstallerVersions.h | 16 ++++---- src/AppInstallerCommonCore/Versions.cpp | 40 +++++++++++++++++-- 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/AppInstallerCLITests/Versions.cpp b/src/AppInstallerCLITests/Versions.cpp index 32a6b36d68..22c4e4eafc 100644 --- a/src/AppInstallerCLITests/Versions.cpp +++ b/src/AppInstallerCLITests/Versions.cpp @@ -317,3 +317,21 @@ TEST_CASE("VersionRange", "[versions]") REQUIRE(VersionRange{ Version{ "0.5" }, Version{ "1.0" } } < VersionRange{ Version{ "1.5" }, Version{ "2.0" } }); REQUIRE_FALSE(VersionRange{ Version{ "1.5" }, Version{ "2.0" } } < VersionRange{ Version{ "0.5" }, Version{ "1.0" } }); } + +TEST_CASE("GatedVersion", "[versions]") +{ + REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1.0.1" })); + REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1.0" })); + REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1" })); + REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1.0.alpha" })); + REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1.0.1.2.3" })); + REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1.0.*" })); + REQUIRE_FALSE(GatedVersion("1.0.*").IsValidVersion({ "1.1.1" })); + + REQUIRE(GatedVersion("1.*.*").IsValidVersion({ "1.*.1" })); + REQUIRE(GatedVersion("1.*.*").IsValidVersion({ "1.*.*" })); + REQUIRE_FALSE(GatedVersion("1.*.*").IsValidVersion({ "1.1.1" })); + + REQUIRE(GatedVersion("1.0.1").IsValidVersion({ "1.0.1" })); + REQUIRE_FALSE(GatedVersion("1.0.1").IsValidVersion({ "1.1.1" })); +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h index b453ff5466..b611fe0a2f 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h @@ -95,7 +95,7 @@ namespace AppInstaller::Utility std::string Other; }; - // Gets the part breakdown for a given version; used for tests. + // Gets the part breakdown for a given version. const std::vector& GetParts() const { return m_parts; } // Gets the part at the given index; or the implied zero part if past the end. @@ -172,18 +172,20 @@ namespace AppInstaller::Utility // A range of versions indicated by a version and optionally a wildcard at the end. struct GatedVersion { - // TODO #476 - // For now, using dummy implementation that just holds a string GatedVersion() {} - GatedVersion(std::string_view s) : m_tmp(s) {} + GatedVersion(const std::string& versionString) : m_version(versionString) {} + // Determines whether a given version falls within this Geted version. + // I.e., whether it matches up to the wildcard bool IsValidVersion(Version version) const; - bool operator==(const GatedVersion& other) const { return m_tmp == other.m_tmp; } - std::string ToString() const { return m_tmp; } + bool operator==(const GatedVersion& other) const { return m_version == other.m_version; } + std::string ToString() const { return m_version.ToString(); } private: - std::string m_tmp; + // Hold the version string as a Version object that makes it easy to access each of + // the version's parts. The real magic is in IsValidVersion() + Version m_version; }; // A channel string; existing solely to give a type. diff --git a/src/AppInstallerCommonCore/Versions.cpp b/src/AppInstallerCommonCore/Versions.cpp index a7e7b43a6a..b881f7a0ce 100644 --- a/src/AppInstallerCommonCore/Versions.cpp +++ b/src/AppInstallerCommonCore/Versions.cpp @@ -498,10 +498,44 @@ namespace AppInstaller::Utility return m_maxVersion; } - bool GatedVersion::IsValidVersion(Version) const + bool GatedVersion::IsValidVersion(Version version) const { - // TODO #476: Implement actual gating logic - return false; + auto gateParts = m_version.GetParts(); + if (gateParts.empty()) + { + return false; + } + + if (gateParts.back() != Version::Part("*")) + { + // Without wildcards, revert to direct comparison + return m_version == version; + } + + auto versionParts = version.GetParts(); + for (size_t i = 0; i < gateParts.size() - 1; ++i) + { + if (versionParts.size() > i) + { + if (gateParts[i] == versionParts[i]) + { + continue; + } + else + { + // Mismatch with the gated version + return false; + } + } + else + { + // Assume trailing 0s on the version + if (gateParts[i] != Version::Part(0)) + { + return false; + } + } + } } bool HasOverlapInVersionRanges(const std::vector& ranges) From 035f94975d269dd07ecefb85011ee22f60a17b0d Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 3 Jan 2023 13:35:08 -0800 Subject: [PATCH 15/55] Fix compilation errors --- src/AppInstallerCLITests/PinningIndex.cpp | 4 +-- src/AppInstallerCLITests/Versions.cpp | 28 +++++++++---------- .../Public/AppInstallerVersions.h | 1 + src/AppInstallerCommonCore/Versions.cpp | 3 ++ 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/AppInstallerCLITests/PinningIndex.cpp b/src/AppInstallerCLITests/PinningIndex.cpp index b22e97c762..c0216e204d 100644 --- a/src/AppInstallerCLITests/PinningIndex.cpp +++ b/src/AppInstallerCLITests/PinningIndex.cpp @@ -99,7 +99,7 @@ TEST_CASE("PinningIndex_AddUpdateRemove", "[pinningIndex]") TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; INFO("Using temporary file named: " << tempFile.GetPath()); - Pin pin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*" }); + Pin pin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*"sv }); Pin updatedPin = Pin::CreatePinningPin({ "pkgId", "srcId" }); { @@ -158,7 +158,7 @@ TEST_CASE("PinningIndex_AddDuplicatePin", "[pinningIndex]") TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; INFO("Using temporary file named: " << tempFile.GetPath()); - Pin pin = Pin::CreateGatingPin({ "pkg", "src" }, { "1.*" }); + Pin pin = Pin::CreateGatingPin({ "pkg", "src" }, { "1.*"sv }); PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); index.AddPin(pin); diff --git a/src/AppInstallerCLITests/Versions.cpp b/src/AppInstallerCLITests/Versions.cpp index 22c4e4eafc..181db0fe3d 100644 --- a/src/AppInstallerCLITests/Versions.cpp +++ b/src/AppInstallerCLITests/Versions.cpp @@ -320,18 +320,18 @@ TEST_CASE("VersionRange", "[versions]") TEST_CASE("GatedVersion", "[versions]") { - REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1.0.1" })); - REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1.0" })); - REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1" })); - REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1.0.alpha" })); - REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1.0.1.2.3" })); - REQUIRE(GatedVersion("1.0.*").IsValidVersion({ "1.0.*" })); - REQUIRE_FALSE(GatedVersion("1.0.*").IsValidVersion({ "1.1.1" })); - - REQUIRE(GatedVersion("1.*.*").IsValidVersion({ "1.*.1" })); - REQUIRE(GatedVersion("1.*.*").IsValidVersion({ "1.*.*" })); - REQUIRE_FALSE(GatedVersion("1.*.*").IsValidVersion({ "1.1.1" })); - - REQUIRE(GatedVersion("1.0.1").IsValidVersion({ "1.0.1" })); - REQUIRE_FALSE(GatedVersion("1.0.1").IsValidVersion({ "1.1.1" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.1" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.alpha" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.1.2.3" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.*" })); + REQUIRE_FALSE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.1.1" })); + + REQUIRE(GatedVersion("1.*.*"sv).IsValidVersion({ "1.*.1" })); + REQUIRE(GatedVersion("1.*.*"sv).IsValidVersion({ "1.*.*" })); + REQUIRE_FALSE(GatedVersion("1.*.*"sv).IsValidVersion({ "1.1.1" })); + + REQUIRE(GatedVersion("1.0.1"sv).IsValidVersion({ "1.0.1" })); + REQUIRE_FALSE(GatedVersion("1.0.1"sv).IsValidVersion({ "1.1.1" })); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h index b611fe0a2f..6002bd18a6 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h @@ -173,6 +173,7 @@ namespace AppInstaller::Utility struct GatedVersion { GatedVersion() {} + GatedVersion(std::string_view versionString) : m_version(std::string{ versionString }) {} GatedVersion(const std::string& versionString) : m_version(versionString) {} // Determines whether a given version falls within this Geted version. diff --git a/src/AppInstallerCommonCore/Versions.cpp b/src/AppInstallerCommonCore/Versions.cpp index b881f7a0ce..286a2cb9fb 100644 --- a/src/AppInstallerCommonCore/Versions.cpp +++ b/src/AppInstallerCommonCore/Versions.cpp @@ -536,6 +536,9 @@ namespace AppInstaller::Utility } } } + + // All version parts matched + return true; } bool HasOverlapInVersionRanges(const std::vector& ranges) From dca6f926832a4c4b7ca4ed3e183fcf9b0eed46da Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 3 Jan 2023 13:35:20 -0800 Subject: [PATCH 16/55] PR comments --- .../Commands/PinCommand.cpp | 1 + src/AppInstallerCLICore/Workflows/PinFlow.cpp | 18 ------------------ src/AppInstallerCLICore/Workflows/PinFlow.h | 2 +- .../CompositeSource.cpp | 3 --- 4 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index 7fd1c89f79..ccec1ab498 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -293,6 +293,7 @@ namespace AppInstaller::CLI void PinResetCommand::ExecuteInternal(Execution::Context& context) const { + // Like 'source reset', if we don't get the --force argument we will only list existing pins. context << Workflow::OpenPinningIndex << Workflow::GetAllPins << diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 5205366111..0f5b782cf6 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -74,11 +74,6 @@ namespace AppInstaller::CLI::Workflow context.Add(std::move(pins)); } - // Adds a pin for the current package. - // A separate pin will be added for each source. - // Required Args: None - // Inputs: PinningIndex, Package - // Outputs: None void AddPin(Execution::Context& context) { auto package = context.Get(); @@ -155,7 +150,6 @@ namespace AppInstaller::CLI::Workflow } } - // Removes all the pins associated with a package. void RemovePin(Execution::Context& context) { auto package = context.Get(); @@ -179,10 +173,6 @@ namespace AppInstaller::CLI::Workflow pinningIndex->RemovePin(pinKey); } - // Report the pins in a table. - // Required Args: None - // Inputs: Pins - // Outputs: None void ReportPins(Execution::Context& context) { const auto& pins = context.Get(); @@ -215,10 +205,6 @@ namespace AppInstaller::CLI::Workflow table.Complete(); } - // Resets all the existing pins. - // Required Args: None - // Inputs: PinningIndex - // Outputs: None void ResetAllPins(Execution::Context& context) { AICLI_LOG(CLI, Info, << "Resetting all pins"); @@ -244,10 +230,6 @@ namespace AppInstaller::CLI::Workflow } } - // Updates the list of pins to include only those matching the current open source - // Required Args: None - // Inputs: Pins, Source - // Outputs: None void CrossReferencePinsWithSource(Execution::Context& context) { const auto& pins = context.Get(); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h index 35862c4940..70fe90c7d4 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.h +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -21,7 +21,7 @@ namespace AppInstaller::CLI::Workflow // There may be several if a package is available from multiple sources. // Required Args: None // Inputs: PinningIndex, Package - // Outputs Pins + // Outputs: Pins void SearchPin(Execution::Context& context); // Adds a pin for the current package. diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 45b95f2682..3855f6d364 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -323,9 +323,6 @@ namespace AppInstaller::Repository } } - // TODO #476 pin info - // result[PackageVersionMetadata::] - return result; } From 4ffe0ebd9e534544f634f879ae6254861d2dd8f7 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 3 Jan 2023 13:42:28 -0800 Subject: [PATCH 17/55] Typo --- src/AppInstallerCommonCore/Public/AppInstallerVersions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h index 6002bd18a6..e2b832184d 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h @@ -176,7 +176,7 @@ namespace AppInstaller::Utility GatedVersion(std::string_view versionString) : m_version(std::string{ versionString }) {} GatedVersion(const std::string& versionString) : m_version(versionString) {} - // Determines whether a given version falls within this Geted version. + // Determines whether a given version falls within this Gated version. // I.e., whether it matches up to the wildcard bool IsValidVersion(Version version) const; From 4af7d5d4da040ad0ee300beaf21580bd4ad397f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Tue, 3 Jan 2023 13:44:15 -0800 Subject: [PATCH 18/55] Update src/AppInstallerCommonCore/Errors.cpp Co-authored-by: Kaleb Luedtke --- src/AppInstallerCommonCore/Errors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp index 0bebb41e5f..551ce40862 100644 --- a/src/AppInstallerCommonCore/Errors.cpp +++ b/src/AppInstallerCommonCore/Errors.cpp @@ -213,7 +213,7 @@ namespace AppInstaller case APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST: return "There is no pin for the package."; case APPINSTALLER_CLI_ERROR_PACKAGE_HAS_BLOCKING_PIN: - return "The package has a blocking pin that prevents from updating."; + return "The package has a blocking pin that prevents upgrade."; // Install errors case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: From ec20b7f65944b0ec9221459affadc8251753748f Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 3 Jan 2023 15:15:10 -0800 Subject: [PATCH 19/55] Add --include-pinned argument --- src/AppInstallerCLICore/Commands/UpgradeCommand.cpp | 1 + src/AppInstallerCLICore/Resources.h | 1 + src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 3 +++ 3 files changed, 5 insertions(+) diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index 312c3ab248..297f4c6ccb 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -110,6 +110,7 @@ namespace AppInstaller::CLI Argument::ForType(Execution::Args::Type::CustomHeader), Argument{ "all"_liv, 'r', "recurse"_liv, Args::Type::All, Resource::String::UpdateAllArgumentDescription, ArgumentType::Flag }, Argument{ "include-unknown"_liv, 'u', "unknown"_liv, Args::Type::IncludeUnknown, Resource::String::IncludeUnknownArgumentDescription, ArgumentType::Flag }, + Argument{ "include-pinned"_liv, Argument::NoAlias, "pinned"_liv, Args::Type::IncludePinned, Resource::String::IncludePinnedArgumentDescription, ArgumentType::Flag}, Argument::ForType(Args::Type::Force), }; } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index be3134808e..b92ba7247d 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -122,6 +122,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ImportPackageAlreadyInstalled); WINGET_DEFINE_RESOURCE_STRINGID(ImportSearchFailed); WINGET_DEFINE_RESOURCE_STRINGID(ImportSourceNotInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(IncludePinnedArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(IncompatibleArgumentsProvided); WINGET_DEFINE_RESOURCE_STRINGID(InstallAndUpgradeCommandsReportDependencies); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 1894565e9e..100365f372 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1632,4 +1632,7 @@ Please specify one of them using the --source option to proceed. {0} package(s) have a blocking pin that needs to be removed before upgrade {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with blocking pins + + Upgrade packages even if they have a non-blocking pin + \ No newline at end of file From dc42e4dd58f42efa77821919c3b84b0f23a919a9 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 3 Jan 2023 16:20:17 -0800 Subject: [PATCH 20/55] Resolve TODOs --- src/AppInstallerCLICore/Resources.h | 1 + src/AppInstallerCLICore/Workflows/PinFlow.cpp | 78 +++++++++++++------ src/AppInstallerCLICore/Workflows/PinFlow.h | 2 +- .../Workflows/UpdateFlow.cpp | 11 ++- .../Workflows/UpdateFlow.h | 6 +- .../Shared/Strings/en-us/winget.resw | 14 ++-- src/AppInstallerCLITests/PinFlow.cpp | 4 +- src/AppInstallerCommonCore/Pin.cpp | 27 +------ .../Public/AppInstallerVersions.h | 4 +- .../Public/winget/Pin.h | 19 ++--- .../CompositeSource.cpp | 4 +- .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 10 +-- 12 files changed, 96 insertions(+), 84 deletions(-) diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index b92ba7247d..752cb31fa5 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -257,6 +257,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PinNoPinsExist); WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinRemovedSuccessfully); WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 0f5b782cf6..047edc183d 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -110,13 +110,14 @@ namespace AppInstaller::CLI::Workflow auto existingPin = pinningIndex->GetPin(pinKey); if (existingPin) { + auto packageName = availableVersion->GetProperty(PackageVersionProperty::Name); + // Pin already exists. // If it is the same, we do nothing. If it is different, check for the --force arg - // TODO #476: Add source to strings if (pin == existingPin) { AICLI_LOG(CLI, Info, << "Pin already exists"); - context.Reporter.Info() << Resource::String::PinAlreadyExists << std::endl; + context.Reporter.Info() << Resource::String::PinAlreadyExists(packageName) << std::endl; continue; } @@ -124,12 +125,12 @@ namespace AppInstaller::CLI::Workflow if (context.Args.Contains(Execution::Args::Type::Force)) { AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); - context.Reporter.Warn() << Resource::String::PinExistsOverwriting << std::endl; + context.Reporter.Warn() << Resource::String::PinExistsOverwriting(packageName) << std::endl; pinsToAddOrUpdate.push_back(std::move(pin)); } else { - context.Reporter.Error() << Resource::String::PinExistsUseForceArg << std::endl; + context.Reporter.Error() << Resource::String::PinExistsUseForceArg(packageName) << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); } } @@ -154,23 +155,41 @@ namespace AppInstaller::CLI::Workflow { auto package = context.Get(); std::vector pins; - - // TODO #476: We should support querying the multiple sources for a package, instead of just one - auto availableVersion = package->GetLatestAvailableVersion(PinBehavior::IgnorePins); + std::set sources; auto pinningIndex = context.Get(); - Pinning::PinKey pinKey{ - availableVersion->GetProperty(PackageVersionProperty::Id).get(), - availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; - AICLI_LOG(CLI, Info, << "Removing pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); - if (!pinningIndex->GetPin(pinKey)) + bool pinExists = false; + + // Note that if a source was specified in the command line, + // that will be the only one we get version keys from. + // So, we remove pins from all sources unless one was provided. + auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); + for (auto versionKey : packageVersionKeys) + { + auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); + Pinning::PinKey pinKey{ + availableVersion->GetProperty(PackageVersionProperty::Id).get(), + availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; + + if (sources.insert(pinKey.SourceId).second) + { + if (pinningIndex->GetPin(pinKey)) + { + AICLI_LOG(CLI, Info, << "Removing pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); + pinningIndex->RemovePin(pinKey); + pinExists = true; + } + } + } + + if (!pinExists) { AICLI_LOG(CLI, Warning, << "Pin does not exist"); - context.Reporter.Warn() << Resource::String::PinDoesNotExist(pinKey.PackageId) << std::endl; + context.Reporter.Warn() << Resource::String::PinDoesNotExist(package->GetProperty(PackageProperty::Name)) << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); } - pinningIndex->RemovePin(pinKey); + context.Reporter.Info() << Resource::String::PinRemovedSuccessfully << std::endl; } void ReportPins(Execution::Context& context) @@ -182,23 +201,28 @@ namespace AppInstaller::CLI::Workflow return; } - // TODO #476: Use package and source names + // Get a mapping of source IDs to names so that we can show something nicer + std::map sourceNames; + for (const auto& source : Repository::Source::GetCurrentSources()) + { + sourceNames[source.Identifier] = source.Name; + } + Execution::TableOutput<4> table(context.Reporter, { Resource::String::SearchId, Resource::String::SearchSource, - Resource::String::SearchVersion, Resource::String::PinType, + Resource::String::SearchVersion, }); for (const auto& pin : pins) { - // TODO #476: Avoid these conversions to string table.OutputLine({ pin.GetPackageId(), - std::string{ pin.GetSourceId() }, - std::string{ pin.GetVersionString() }, + sourceNames[pin.GetSourceId()], std::string{ ToString(pin.GetType()) }, + pin.GetVersion().ToString(), }); } @@ -236,12 +260,22 @@ namespace AppInstaller::CLI::Workflow const auto& source = context.Get(); std::vector matchingPins; - std::copy_if(pins.begin(), pins.end(), std::back_inserter(matchingPins), [&](Pinning::Pin pin) { + for (const auto pin : pins) + { SearchRequest searchRequest; searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pin.GetPackageId()); auto searchResult = source.Search(searchRequest); - return !searchResult.Matches.empty(); - }); + + // Ensure the match comes from the right source + for (const auto& match : searchResult.Matches) + { + auto availableVersion = match.Package->GetAvailableVersion({ pin.GetSourceId(), "", "" }, PinBehavior::IgnorePins); + if (availableVersion) + { + matchingPins.push_back(pin); + } + } + } context.Add(std::move(matchingPins)); } diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h index 70fe90c7d4..597d4b1905 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.h +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -49,7 +49,7 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void ResetAllPins(Execution::Context& context); - // Updates the list of pins to include only those matching the current open source + // Updates the list of pins to include only those matching the current open source. // Required Args: None // Inputs: Pins, Source // Outputs: None diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index b39aaa05a6..2b3698045e 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -42,6 +42,7 @@ namespace AppInstaller::CLI::Workflow { auto package = context.Get(); auto installedPackage = context.Get(); + const bool reportVersionNotFound = m_isSinglePackage; bool isUpgrade = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate);; Utility::Version installedVersion; @@ -57,7 +58,7 @@ namespace AppInstaller::CLI::Workflow if (isUpgrade && installedVersion.IsUnknown() && !context.Args.Contains(Execution::Args::Type::IncludeUnknown)) { // the package has an unknown version and the user did not request to upgrade it anyway - if (m_reportVersionNotFound) + if (reportVersionNotFound) { context.Reporter.Info() << Resource::String::UpgradeUnknownVersionExplanation << std::endl; } @@ -65,8 +66,10 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); } - // TODO #476 - PinBehavior pinBehavior = (m_reportVersionNotFound || context.Args.Contains(Execution::Args::Type::IncludePinned)) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + // If we are updating a single package or we got the --include-pinned flag, + // we include packages with Pinning pins + const PinBehavior pinBehavior = + (m_isSinglePackage || context.Args.Contains(Execution::Args::Type::IncludePinned)) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; // The version keys should have already been sorted by version const auto& versionKeys = package->GetAvailableVersionKeys(pinBehavior); @@ -122,7 +125,7 @@ namespace AppInstaller::CLI::Workflow if (!versionFound) { - if (m_reportVersionNotFound) + if (reportVersionNotFound) { if (installedTypeInapplicable) { diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.h b/src/AppInstallerCLICore/Workflows/UpdateFlow.h index 3339a43c2c..2f149009f6 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.h +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.h @@ -12,13 +12,13 @@ namespace AppInstaller::CLI::Workflow // Outputs: Manifest?, Installer? struct SelectLatestApplicableVersion : public WorkflowTask { - SelectLatestApplicableVersion(bool reportVersionNotFound) : - WorkflowTask("SelectLatestApplicableUpdate"), m_reportVersionNotFound(reportVersionNotFound) {} + SelectLatestApplicableVersion(bool isSinglePackage) : + WorkflowTask("SelectLatestApplicableUpdate"), m_isSinglePackage(isSinglePackage) {} void operator()(Execution::Context& context) const override; private: - bool m_reportVersionNotFound; + bool m_isSinglePackage; }; // Ensures the update package has higher version than installed diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 100365f372..ebd925cfad 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1601,15 +1601,16 @@ Please specify one of them using the --source option to proceed. Pin added successfully - The pin already exists + There is already a pin for package {0} + {Locked="{0}"} {0} is a placeholder that will be replaced by a package name. The message is shown when attempting to add a pin for a package that is already pinned. - A pin already exists for this package. Overwriting due to the --force argument. - {Locked="--force"} + A pin already exists for package {0}. Overwriting due to the --force argument. + {Locked="--force"}{Locked="--force","{0}"} {0} is a placeholder that will be replaced by a package name. - A pin already exists for this package. Use the --force argument to overwrite it. - {Locked="--force"} + A pin already exists for package {0}. Use the --force argument to overwrite it. + {Locked="--force","{0}"} {0} is a placeholder that will be replaced by a package name. Resetting all current pins. @@ -1635,4 +1636,7 @@ Please specify one of them using the --source option to proceed. Upgrade packages even if they have a non-blocking pin + + Pin removed successfully + \ No newline at end of file diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index d6d6a97cfc..6fc9124e10 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -7,11 +7,13 @@ #include #include #include +#include using namespace TestCommon; using namespace AppInstaller::CLI; using namespace AppInstaller::CLI::Workflow; using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Utility; using namespace AppInstaller::Pinning; TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") @@ -37,7 +39,7 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") REQUIRE(pins[0].GetType() == PinType::Blocking); REQUIRE(pins[0].GetPackageId() == "AppInstallerCliTest.TestExeInstaller"); REQUIRE(pins[0].GetSourceId() == "*TestSource"); - REQUIRE(pins[0].GetVersionString() == "1.0.0.0"); + REQUIRE(pins[0].GetVersion() == Version{ "1.0.0.0" }); std::ostringstream pinListOutput; TestContext listContext{ pinListOutput, std::cin }; diff --git a/src/AppInstallerCommonCore/Pin.cpp b/src/AppInstallerCommonCore/Pin.cpp index 83e174dcb1..0753c6c791 100644 --- a/src/AppInstallerCommonCore/Pin.cpp +++ b/src/AppInstallerCommonCore/Pin.cpp @@ -66,35 +66,10 @@ namespace AppInstaller::Pinning return { PinType::Gating, std::move(pinKey), gatedVersion.ToString() }; } - PinType Pin::GetType() const - { - return m_type; - } - - const PinKey& Pin::GetKey() const - { - return m_key; - } - - const AppInstaller::Manifest::Manifest::string_t& Pin::GetPackageId() const - { - return m_key.PackageId; - } - - std::string_view Pin::GetSourceId() const - { - return m_key.SourceId; - } - - std::string_view Pin::GetVersionString() const - { - return m_versionString; - } - bool Pin::operator==(const Pin& other) const { return m_type == other.m_type && m_key == other.m_key && - m_versionString == other.m_versionString; + m_version == other.m_version; } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h index e2b832184d..c6e1f25de2 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerVersions.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerVersions.h @@ -173,6 +173,8 @@ namespace AppInstaller::Utility struct GatedVersion { GatedVersion() {} + GatedVersion(Version&& version) : m_version(std::move(version)) {} + GatedVersion(const Version& version) : m_version(version) {} GatedVersion(std::string_view versionString) : m_version(std::string{ versionString }) {} GatedVersion(const std::string& versionString) : m_version(versionString) {} @@ -181,7 +183,7 @@ namespace AppInstaller::Utility bool IsValidVersion(Version version) const; bool operator==(const GatedVersion& other) const { return m_version == other.m_version; } - std::string ToString() const { return m_version.ToString(); } + const std::string& ToString() const { return m_version.ToString(); } private: // Hold the version string as a Version object that makes it easy to access each of diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h index 055c01e7bf..47113e2024 100644 --- a/src/AppInstallerCommonCore/Public/winget/Pin.h +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -56,23 +56,20 @@ namespace AppInstaller::Pinning static Pin CreatePinningPin(PinKey&& pinKey, Utility::Version version = {}); static Pin CreateGatingPin(PinKey&& pinKey, Utility::GatedVersion gatedVersion); - PinType GetType() const; - const PinKey& GetKey() const; - const Manifest::Manifest::string_t& GetPackageId() const; - std::string_view GetSourceId() const; - - // TODO: For now we'll keep the versions as simply a string in all cases. - // This will change once we actually start using them. - std::string_view GetVersionString() const; + PinType GetType() const { return m_type; } + const PinKey& GetKey() const { return m_key; } + const Manifest::Manifest::string_t& GetPackageId() const { return m_key.PackageId; } + const std::string& GetSourceId() const { return m_key.SourceId; } + const Utility::Version& GetVersion() const { return m_version; } bool operator==(const Pin& other) const; private: - Pin(PinType type, PinKey&& pinKey, std::string_view versionString) - : m_type(type), m_key(std::move(pinKey)), m_versionString(versionString) {} + Pin(PinType type, PinKey&& pinKey, Utility::Version&& version) + : m_type(type), m_key(std::move(pinKey)), m_version(std::move(version)) {} PinType m_type = PinType::Unknown; PinKey m_key; - std::string m_versionString; + Utility::Version m_version; }; } diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 3855f6d364..09c13fff58 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -369,7 +369,7 @@ namespace AppInstaller::Repository } else if (Pin->GetType() == Pinning::PinType::Gating) { - Utility::GatedVersion gatedVersion(Pin->GetVersionString()); + Utility::GatedVersion gatedVersion(Pin->GetVersion()); auto versionKeys = AvailablePackage->GetAvailableVersionKeys(PinBehavior::IgnorePins); std::vector result; std::copy_if(versionKeys.begin(), versionKeys.end(), std::back_inserter(result), [&](const PackageVersionKey& pvk) { return gatedVersion.IsValidVersion(pvk.Version); }); @@ -399,7 +399,7 @@ namespace AppInstaller::Repository } else if (Pin->GetType() == Pinning::PinType::Gating) { - Utility::GatedVersion gatedVersion(Pin->GetVersionString()); + Utility::GatedVersion gatedVersion(Pin->GetVersion()); if (!gatedVersion.IsValidVersion(versionKey.Version)) { AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] as it does not satisfy Gating pin"); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index 5b26df7a35..033a2dca0e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -65,8 +65,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 std::optional PinTable::GetByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) { - // TODO #476: The statement builder requires that the bound parameters can be converted to T&&, - // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) .Where(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) @@ -86,8 +84,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) { - // TODO #476: The statement builder requires that the bound parameters can be converted to T&&, - // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.InsertInto(s_PinTable_Table_Name) .Columns({ @@ -99,7 +95,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 (std::string_view)pin.GetPackageId(), pin.GetSourceId(), pin.GetType(), - pin.GetVersionString()); + pin.GetVersion().ToString()); builder.Execute(connection); return connection.GetLastInsertRowID(); @@ -107,14 +103,12 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 bool PinTable::UpdatePin(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) { - // TODO #476: The statement builder requires that the bound parameters can be converted to T&&, - // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.Update(s_PinTable_Table_Name).Set() .Column(s_PinTable_PackageId_Column).Equals((std::string_view)pin.GetPackageId()) .Column(s_PinTable_SourceId_Column).Equals(pin.GetSourceId()) .Column(s_PinTable_Type_Column).Equals(pin.GetType()) - .Column(s_PinTable_Version_Column).Equals(pin.GetVersionString()) + .Column(s_PinTable_Version_Column).Equals(pin.GetVersion().ToString()) .Where(SQLite::RowIDName).Equals(pinId); builder.Execute(connection); From 1a244f20a29d3ae10dfdb9ebd4f41f64a2d56d1a Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Wed, 4 Jan 2023 15:40:33 -0800 Subject: [PATCH 21/55] Add tests --- src/AppInstallerCLITests/CompositeSource.cpp | 157 ++++++++++++++++++- 1 file changed, 155 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index 00199b41ce..d5b4dc5ca3 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -6,12 +6,15 @@ #include "TestHooks.h" #include #include +#include #include +#include using namespace std::string_literals; using namespace std::string_view_literals; using namespace TestCommon; using namespace AppInstaller; +using namespace AppInstaller::Pinning; using namespace AppInstaller::Repository; using namespace AppInstaller::Repository::Microsoft; using namespace AppInstaller::Utility; @@ -95,14 +98,14 @@ struct Criteria : public PackageMatchFilter Criteria(PackageMatchField field) : PackageMatchFilter(field, MatchType::Wildcard, ""sv) {} }; -Manifest::Manifest MakeDefaultManifest() +Manifest::Manifest MakeDefaultManifest(std::string_view version = "1.0"sv) { Manifest::Manifest result; result.Id = "Id"; result.DefaultLocalization.Add("Name"); result.DefaultLocalization.Add("Publisher"); - result.Version = "1.0"; + result.Version = version; result.Installers.push_back({}); return result; @@ -1026,3 +1029,153 @@ TEST_CASE("CompositeSource_NullAvailableVersion", "[CompositeSource]") SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); } + +struct ExpectedResultWithPin +{ + bool IsUpdateAvailable; + std::optional LatestAvailableVersion; + std::vector AvailableVersions; + std::vector UnavailableVersions; +}; + +void RequireExpectedResultsWithPin(std::shared_ptr package, PinBehavior pinBehavior, const ExpectedResultWithPin& expectedResult) +{ + REQUIRE(package->IsUpdateAvailable(pinBehavior) == expectedResult.IsUpdateAvailable); + + auto latestAvailable = package->GetLatestAvailableVersion(pinBehavior); + if (expectedResult.LatestAvailableVersion.has_value()) + { + REQUIRE(latestAvailable); + REQUIRE(latestAvailable->GetManifest().Version == expectedResult.LatestAvailableVersion.value()); + } + else + { + REQUIRE(!latestAvailable); + } + + auto availableVersionKeys = package->GetAvailableVersionKeys(pinBehavior); + REQUIRE(availableVersionKeys.size() == expectedResult.AvailableVersions.size()); + for (size_t i = 0; i < availableVersionKeys.size(); ++i) + { + REQUIRE(availableVersionKeys[i].Version == expectedResult.AvailableVersions[i]); + } + + for (const auto& expectedAvailableVersion : expectedResult.AvailableVersions) + { + REQUIRE(package->GetAvailableVersion({ "", expectedAvailableVersion, "" }, pinBehavior)); + } + + for (const auto& expectedUnavailableVersion : expectedResult.UnavailableVersions) + { + REQUIRE(!(package->GetAvailableVersion({ "", expectedUnavailableVersion, "" }, pinBehavior))); + } +} + +TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") +{ + // We use an installed package that has 3 available versions: v1.0.0, v1.0.1 and v1.1.0. + // Installed is v1.0.1 + // We then test the 4 possible pin states (unpinned, Pinned, Blocked, Gated) + // with the 3 possible pin search behaviors (ignore, consider, include pinnned) + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + TestUserSettings userSettings; + userSettings.Set(true); + + CompositeTestSetup setup; + + auto installedPackage = TestPackage::Make(MakeDefaultManifest("1.0.1"sv), TestPackage::MetadataMap{}); + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + auto manifest1 = MakeDefaultManifest("1.0.0"sv); + auto manifest2 = MakeDefaultManifest("1.0.1"sv); + auto manifest3 = MakeDefaultManifest("1.1.0"sv); + auto package = TestPackage::Make( + std::vector{ manifest1, manifest2, manifest3 }, + setup.Available); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + // The result when ignoring pins is always the same + ExpectedResultWithPin expectedResult_ignorePins; + expectedResult_ignorePins.IsUpdateAvailable = true; + expectedResult_ignorePins.LatestAvailableVersion = "1.1.0"; + expectedResult_ignorePins.AvailableVersions = { "1.1.0", "1.0.1", "1.0.0" }; + expectedResult_ignorePins.UnavailableVersions = {}; + + ExpectedResultWithPin expectedResult_includePinned; + ExpectedResultWithPin expectedResult_considerPins; + + PinKey pinKey("Id", setup.Available->Details.Identifier); + PinningIndex pinningIndex = PinningIndex::OpenOrCreateDefault(); + + SECTION("Unpinned") + { + // If there are no pins, the result should not change if we consider them + expectedResult_considerPins = expectedResult_ignorePins; + expectedResult_includePinned = expectedResult_ignorePins; + } + SECTION("Pinned") + { + pinningIndex.AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); + + // Pinning pins are ignored with --include-pinned + expectedResult_includePinned = expectedResult_ignorePins; + + expectedResult_considerPins.IsUpdateAvailable = false; + expectedResult_considerPins.LatestAvailableVersion = {}; + expectedResult_considerPins.AvailableVersions = {}; + expectedResult_considerPins.UnavailableVersions = { "1.1.0", "1.0.1", "1.0.0" }; + } + SECTION("Blocked") + { + pinningIndex.AddPin(Pin::CreateBlockingPin(PinKey{ pinKey })); + + expectedResult_considerPins.IsUpdateAvailable = false; + expectedResult_considerPins.LatestAvailableVersion = {}; + expectedResult_considerPins.AvailableVersions = {}; + expectedResult_considerPins.UnavailableVersions = { "1.1.0", "1.0.1", "1.0.0" }; + + // Blocking pins are not affected by --include-pinned + expectedResult_includePinned = expectedResult_considerPins; + } + SECTION("Gated to 1.*") + { + pinningIndex.AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); + + expectedResult_considerPins.IsUpdateAvailable = true; + expectedResult_considerPins.LatestAvailableVersion = "1.1.0"; + expectedResult_considerPins.AvailableVersions = { "1.1.0", "1.0.1", "1.0.0" }; + expectedResult_considerPins.UnavailableVersions = {}; + + // Gating pins are not affected by --include-pinned + expectedResult_includePinned = expectedResult_considerPins; + } + SECTION("Gated to 1.0.*") + { + pinningIndex.AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.0.*"sv })); + + expectedResult_considerPins.IsUpdateAvailable = false; + expectedResult_considerPins.LatestAvailableVersion = "1.0.1"; + expectedResult_considerPins.AvailableVersions = { "1.0.1", "1.0.0" }; + expectedResult_considerPins.UnavailableVersions = { "1.1.0" }; + + // Gating pins are not affected by --include-pinned + expectedResult_includePinned = expectedResult_considerPins; + } + + SearchResult result = setup.Search(); + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + + RequireExpectedResultsWithPin(package, PinBehavior::IgnorePins, expectedResult_ignorePins); + RequireExpectedResultsWithPin(package, PinBehavior::IncludePinned, expectedResult_includePinned); + RequireExpectedResultsWithPin(package, PinBehavior::ConsiderPins, expectedResult_considerPins); +} From 48f1961f96bf2391caef2735d2d830819592fdb6 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Wed, 4 Jan 2023 16:58:26 -0800 Subject: [PATCH 22/55] Add more tests --- src/AppInstallerCLITests/CompositeSource.cpp | 155 +++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index d5b4dc5ca3..f081f12716 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -1179,3 +1179,158 @@ TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") RequireExpectedResultsWithPin(package, PinBehavior::IncludePinned, expectedResult_includePinned); RequireExpectedResultsWithPin(package, PinBehavior::ConsiderPins, expectedResult_considerPins); } + +TEST_CASE("CompositeSource_OneSourcePinned", "[CompositeSource][PinFlow]") +{ + // We use an installed package that has 2 available sources. + // If one of them is pinned, we should still get the updates from the other one. + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + TestUserSettings userSettings; + userSettings.Set(true); + + CompositeTestSetup setup; + + auto installedPackage = TestPackage::Make(MakeDefaultManifest("1.0"sv), TestPackage::MetadataMap{}); + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + auto package = TestPackage::Make(std::vector{ MakeDefaultManifest("2.0"sv) }, setup.Available); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + std::shared_ptr secondAvailable = std::make_shared("SecondTestSource"); + setup.Composite.AddAvailableSource(Source{ secondAvailable }); + secondAvailable->SearchFunction = [&](const SearchRequest&) + { + auto package = TestPackage::Make(std::vector{ MakeDefaultManifest("1.1"sv) }, secondAvailable); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + { + PinKey pinKey("Id", setup.Available->Details.Identifier); + PinningIndex pinningIndex = PinningIndex::OpenOrCreateDefault(); + pinningIndex.AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); + } + + PinBehavior pinBehavior = PinBehavior::IgnorePins; + ExpectedResultWithPin expectedResult; + SECTION("Ignore pins") + { + pinBehavior = PinBehavior::IgnorePins; + expectedResult.IsUpdateAvailable = true; + expectedResult.LatestAvailableVersion = "2.0"; + expectedResult.AvailableVersions = { "2.0", "1.1" }; + expectedResult.UnavailableVersions = {}; + } + SECTION("Include pinned") + { + pinBehavior = PinBehavior::IncludePinned; + expectedResult.IsUpdateAvailable = true; + expectedResult.LatestAvailableVersion = "2.0"; + expectedResult.AvailableVersions = { "2.0", "1.1" }; + expectedResult.UnavailableVersions = {}; + } + SECTION("Consider pins") + { + pinBehavior = PinBehavior::ConsiderPins; + expectedResult.IsUpdateAvailable = true; + expectedResult.LatestAvailableVersion = "1.1"; + expectedResult.AvailableVersions = { "1.1" }; + expectedResult.UnavailableVersions = { "2.0" }; + } + + SearchResult result = setup.Search(); + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + RequireExpectedResultsWithPin(package, pinBehavior, expectedResult); +} + +TEST_CASE("CompositeSource_OneSourceGated", "[CompositeSource][PinFlow]") +{ + // We use an installed package that has 2 available sources. + // If one of them has a gating pin, we should still get the updates from it + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + TestUserSettings userSettings; + userSettings.Set(true); + + CompositeTestSetup setup; + + auto installedPackage = TestPackage::Make(MakeDefaultManifest("1.0"sv), TestPackage::MetadataMap{}); + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + auto package = TestPackage::Make( + std::vector{ + MakeDefaultManifest("2.0"sv), + MakeDefaultManifest("1.2"sv), + }, + setup.Available); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + std::shared_ptr secondAvailable = std::make_shared("SecondTestSource"); + setup.Composite.AddAvailableSource(Source{ secondAvailable }); + secondAvailable->SearchFunction = [&](const SearchRequest&) + { + auto package = TestPackage::Make(std::vector{ MakeDefaultManifest("1.1"sv) }, secondAvailable); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + { + PinKey pinKey("Id", setup.Available->Details.Identifier); + PinningIndex pinningIndex = PinningIndex::OpenOrCreateDefault(); + pinningIndex.AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); + } + + PinBehavior pinBehavior = PinBehavior::IgnorePins; + ExpectedResultWithPin expectedResult; + SECTION("Ignore pins") + { + pinBehavior = PinBehavior::IgnorePins; + expectedResult.IsUpdateAvailable = true; + expectedResult.LatestAvailableVersion = "2.0"; + expectedResult.AvailableVersions = { "2.0", "1.2", "1.1"}; + expectedResult.UnavailableVersions = {}; + } + SECTION("Include pinned") + { + pinBehavior = PinBehavior::IncludePinned; + expectedResult.IsUpdateAvailable = true; + expectedResult.LatestAvailableVersion = "1.2"; + expectedResult.AvailableVersions = { "1.2", "1.1" }; + expectedResult.UnavailableVersions = { "2.0" }; + } + SECTION("Include pinned") + { + pinBehavior = PinBehavior::ConsiderPins; + expectedResult.IsUpdateAvailable = true; + expectedResult.LatestAvailableVersion = "1.2"; + expectedResult.AvailableVersions = { "1.2", "1.1"}; + expectedResult.UnavailableVersions = { "2.0" }; + } + + SearchResult result = setup.Search(); + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + RequireExpectedResultsWithPin(package, pinBehavior, expectedResult); +} \ No newline at end of file From 766aafa59fa65047b952da8f398729f0622bd972 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Wed, 4 Jan 2023 17:01:19 -0800 Subject: [PATCH 23/55] Typo --- src/AppInstallerCLITests/CompositeSource.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index f081f12716..0786d7b5e5 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -1076,7 +1076,7 @@ TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") // We use an installed package that has 3 available versions: v1.0.0, v1.0.1 and v1.1.0. // Installed is v1.0.1 // We then test the 4 possible pin states (unpinned, Pinned, Blocked, Gated) - // with the 3 possible pin search behaviors (ignore, consider, include pinnned) + // with the 3 possible pin search behaviors (ignore, consider, include pinned) TempFile indexFile("pinningIndex", ".db"); TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); @@ -1319,7 +1319,7 @@ TEST_CASE("CompositeSource_OneSourceGated", "[CompositeSource][PinFlow]") expectedResult.AvailableVersions = { "1.2", "1.1" }; expectedResult.UnavailableVersions = { "2.0" }; } - SECTION("Include pinned") + SECTION("Consider pins") { pinBehavior = PinBehavior::ConsiderPins; expectedResult.IsUpdateAvailable = true; From 97b6fea5cc271b022394ffcf5223695816bb8473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Mon, 9 Jan 2023 14:46:55 -0800 Subject: [PATCH 24/55] Apply suggestions from code review Co-authored-by: yao-msft <50888816+yao-msft@users.noreply.github.com> --- .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 6 ++++-- .../Microsoft/Schema/Pinning_1_0/PinTable.h | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index 0b45fc4416..01479f0bf3 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -9,7 +9,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 { namespace { - std::optional GetPinFromRow(std::string_view packageId, std::string_view sourceId, Pinning::PinType type, const std::string& version) + std::optional GetPinFromRow(std::string_view packageId, std::string_view sourceId, Pinning::PinType type, std::string_view version) + { switch (type) { @@ -42,7 +43,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 { using namespace SQLite::Builder; - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPinTable_v1_0"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); + StatementBuilder createTableBuilder; createTableBuilder.CreateTable(s_PinTable_Table_Name).BeginColumns(); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h index 341335bb54..007c69d5f9 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h @@ -17,7 +17,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // Creates the table with named indices. static void Create(SQLite::Connection& connection); - static std::optional GetByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); + static std::optional GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); + static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); static bool UpdatePin(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); static void RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); From 177ee73a437ff3976b520c5050c78bfe44f6519a Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 9 Jan 2023 16:19:05 -0800 Subject: [PATCH 25/55] PR comments --- .github/actions/spelling/allow.txt | 4 ++ .../Commands/PinCommand.cpp | 28 ++++++--- src/AppInstallerCLICore/Resources.h | 1 + src/AppInstallerCLICore/Workflows/PinFlow.cpp | 63 +++++++------------ src/AppInstallerCLICore/Workflows/PinFlow.h | 2 +- .../Shared/Strings/en-us/winget.resw | 5 ++ src/AppInstallerCLITests/PinFlow.cpp | 26 +------- src/AppInstallerCommonCore/Pin.cpp | 18 +++--- .../Public/AppInstallerRuntime.h | 5 +- .../Public/winget/Pin.h | 17 +++-- .../Microsoft/PinningIndex.cpp | 10 +-- .../Microsoft/PinningIndex.h | 5 +- .../Microsoft/Schema/IPinningIndex.h | 5 +- .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 38 +++++------ .../Microsoft/Schema/Pinning_1_0/PinTable.h | 20 +++++- .../Pinning_1_0/PinningIndexInterface.h | 2 +- .../Pinning_1_0/PinningIndexInterface_1_0.cpp | 20 +++--- 17 files changed, 133 insertions(+), 136 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 27e25d1bcc..1c87c3ff13 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -4,6 +4,7 @@ ACTIONDATA ACTIONSTART addfile addmanifest +addpin addportablefile addstore admins @@ -94,6 +95,7 @@ cpprestsdk cppwinrt CPRWL createnew +createpintable createportabletable createtables cref @@ -442,6 +444,7 @@ regex regexp removefile removemanifest +removepin removeportablefile repolibtest requeue @@ -624,6 +627,7 @@ Unregisters untimes updatefile updatemanifest +updatepin updateportablefile UPLEVEL upvote diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index a7b07eb3e9..1e2c5d0d71 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -116,7 +116,6 @@ namespace AppInstaller::CLI void PinAddCommand::ExecuteInternal(Execution::Context& context) const { - // TODO context << Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << @@ -261,7 +260,6 @@ namespace AppInstaller::CLI void PinListCommand::ExecuteInternal(Execution::Context& context) const { - // TODO context << Workflow::OpenPinningIndex << Workflow::GetAllPins << @@ -275,6 +273,7 @@ namespace AppInstaller::CLI { return { Argument::ForType(Args::Type::Force), + Argument::ForType(Args::Type::Source), }; } @@ -295,12 +294,23 @@ namespace AppInstaller::CLI void PinResetCommand::ExecuteInternal(Execution::Context& context) const { - context << - Workflow::OpenPinningIndex << - Workflow::GetAllPins << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << - Workflow::CrossReferencePinsWithSource << - Workflow::ResetAllPins; + context << Workflow::OpenPinningIndex; + + if (context.Args.Contains(Execution::Args::Type::Force)) + { + context << Workflow::ResetAllPins; + } + else + { + AICLI_LOG(CLI, Info, << "--force argument is not present"); + context.Reporter.Info() << Resource::String::PinResetUseForceArg << std::endl; + + context << + Workflow::GetAllPins << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << + Workflow::CrossReferencePinsWithSource << + Workflow::ReportPins; + } } } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index e3f22e1b89..998074c1bc 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -258,6 +258,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetSuccessful); WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll); WINGET_DEFINE_RESOURCE_STRINGID(PinResetUseForceArg); WINGET_DEFINE_RESOURCE_STRINGID(PinType); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index a71b12d9a1..e2b626818c 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -16,7 +16,7 @@ namespace AppInstaller::CLI::Workflow namespace { // Creates a Pin appropriate for the context based on the arguments provided - Pinning::Pin CreatePin(Execution::Context& context, std::string_view packageId, std::string_view sourceId, const std::string& installedVersion) + Pinning::Pin CreatePin(Execution::Context& context, std::string_view packageId, std::string_view sourceId) { if (context.Args.Contains(Execution::Args::Type::GatedVersion)) { @@ -24,11 +24,11 @@ namespace AppInstaller::CLI::Workflow } else if (context.Args.Contains(Execution::Args::Type::BlockingPin)) { - return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }, { installedVersion }); + return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); } else { - return Pinning::Pin::CreatePinningPin({ packageId, sourceId }, { installedVersion }); + return Pinning::Pin::CreatePinningPin({ packageId, sourceId }); } } } @@ -72,19 +72,10 @@ namespace AppInstaller::CLI::Workflow context.Add(std::move(pins)); } - // Adds a pin for the current package. - // A separate pin will be added for each source. - // Required Args: None - // Inputs: PinningIndex, Package - // Outputs: None void AddPin(Execution::Context& context) { auto package = context.Get(); auto installedVersion = context.Get(); - if (!installedVersion) - { - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); - } std::vector pins; @@ -94,7 +85,7 @@ namespace AppInstaller::CLI::Workflow Pinning::PinKey pinKey{ availableVersion->GetProperty(PackageVersionProperty::Id).get(), availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; - auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId, installedVersion->GetProperty(PackageVersionProperty::Version)); + auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId); AICLI_LOG(CLI, Info, << "Adding pin with type " << ToString(pin.GetType()) << " for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]"); @@ -133,7 +124,6 @@ namespace AppInstaller::CLI::Workflow } } - // Removes all the pins associated with a package. void RemovePin(Execution::Context& context) { auto package = context.Get(); @@ -157,10 +147,6 @@ namespace AppInstaller::CLI::Workflow pinningIndex->RemovePin(pinKey); } - // Report the pins in a table. - // Required Args: None - // Inputs: Pins - // Outputs: None void ReportPins(Execution::Context& context) { const auto& pins = context.Get(); @@ -185,7 +171,7 @@ namespace AppInstaller::CLI::Workflow table.OutputLine({ pin.GetPackageId(), std::string{ pin.GetSourceId() }, - std::string{ pin.GetVersionString() }, + pin.GetGatedVersion().ToString(), std::string{ ToString(pin.GetType()) }, }); } @@ -193,39 +179,36 @@ namespace AppInstaller::CLI::Workflow table.Complete(); } - // Resets all the existing pins. - // Required Args: None - // Inputs: PinningIndex - // Outputs: None void ResetAllPins(Execution::Context& context) { AICLI_LOG(CLI, Info, << "Resetting all pins"); - if (context.Args.Contains(Execution::Args::Type::Force)) - { - context.Reporter.Info() << Resource::String::PinResettingAll << std::endl; + context.Reporter.Info() << Resource::String::PinResettingAll << std::endl; - if (context.Get().empty()) - { - context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; - } - else + std::string sourceId; + if (context.Args.Contains(Execution::Args::Type::Source)) + { + auto sourceName = context.Args.GetArg(Execution::Args::Type::Source); + auto sources = Source::GetCurrentSources(); + for (const auto& source : sources) { - auto pinningIndex = context.Get(); - pinningIndex->ResetAllPins(); + if (Utility::CaseInsensitiveEquals(source.Name, sourceName)) + { + sourceId = source.Identifier; + break; + } } } + + if (context.Get()->ResetAllPins(sourceId)) + { + context.Reporter.Info() << Resource::String::PinResetSuccessful << std::endl; + } else { - AICLI_LOG(CLI, Info, << "--force argument is not present"); - context.Reporter.Info() << Resource::String::PinResetUseForceArg << std::endl; - context << ReportPins; + context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; } } - // Updates the list of pins to include only those matching the current open source - // Required Args: None - // Inputs: Pins, Source - // Outputs: None void CrossReferencePinsWithSource(Execution::Context& context) { const auto& pins = context.Get(); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h index 35862c4940..b367853d81 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.h +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -45,7 +45,7 @@ namespace AppInstaller::CLI::Workflow // Resets all the existing pins. // Required Args: None - // Inputs: PinningIndex, Pins + // Inputs: None // Outputs: None void ResetAllPins(Execution::Context& context); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 489ee62e83..b25e360488 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1624,6 +1624,7 @@ Please specify one of them using the --source option to proceed. Use the --force argument to reset all pins. The following pins would be removed: + {Locked="--force"} Pin type @@ -1636,4 +1637,8 @@ Please specify one of them using the --source option to proceed. There are no pins configured. Shown when listing or modifying existing pins if there are none. + + Pins resetted successfully + Shown after resetting (deleting) all the pins + \ No newline at end of file diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index a2b527eaa7..c1dd94565b 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -47,7 +47,7 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") REQUIRE(pins[0].GetType() == PinType::Blocking); REQUIRE(pins[0].GetPackageId() == "AppInstallerCliTest.TestExeInstaller"); REQUIRE(pins[0].GetSourceId() == "*TestSource"); - REQUIRE(pins[0].GetVersionString() == "1.0.0.0"); + REQUIRE(pins[0].GetGatedVersion().ToString() == ""); std::ostringstream pinListOutput; TestContext listContext{ pinListOutput, std::cin }; @@ -83,10 +83,10 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") std::ostringstream pinResetOutput; TestContext resetContext{ pinResetOutput, std::cin }; OverrideForOpenPinningIndex(resetContext, indexFile.GetPath()); - OverrideForCompositeInstalledSource(resetContext); SECTION("Without --force") { + OverrideForCompositeInstalledSource(resetContext); PinResetCommand pinReset({}); pinReset.Execute(resetContext); INFO(pinResetOutput.str()); @@ -158,27 +158,6 @@ TEST_CASE("PinFlow_Add_NotFound", "[PinFlow][workflow]") REQUIRE_TERMINATED_WITH(addContext, APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); } -TEST_CASE("PinFlow_Add_NotInstalled", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - - std::ostringstream pinAddOutput; - TestContext addContext{ pinAddOutput, std::cin }; - OverrideForOpenPinningIndex(addContext, indexFile.GetPath()); - OverrideForCompositeInstalledSource(addContext); - addContext.Args.AddArg(Execution::Args::Type::Query, "TestExeInstallerWithNothingInstalled"sv); - - PinAddCommand pinAdd({}); - pinAdd.Execute(addContext); - INFO(pinAddOutput.str()); - - REQUIRE_TERMINATED_WITH(addContext, APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.empty()); -} - TEST_CASE("PinFlow_ListEmpty", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); @@ -219,7 +198,6 @@ TEST_CASE("PinFlow_ResetEmpty", "[PinFlow][workflow]") std::ostringstream pinResetOutput; TestContext resetContext{ pinResetOutput, std::cin }; OverrideForOpenPinningIndex(resetContext, indexFile.GetPath()); - OverrideForCompositeInstalledSource(resetContext); resetContext.Args.AddArg(Execution::Args::Type::Force); PinResetCommand pinReset({}); diff --git a/src/AppInstallerCommonCore/Pin.cpp b/src/AppInstallerCommonCore/Pin.cpp index acc3b2b2ba..d76d0f4ea4 100644 --- a/src/AppInstallerCommonCore/Pin.cpp +++ b/src/AppInstallerCommonCore/Pin.cpp @@ -25,19 +25,19 @@ namespace AppInstaller::Pinning } } - Pin Pin::CreateBlockingPin(PinKey&& pinKey, Version version) + Pin Pin::CreateBlockingPin(PinKey&& pinKey) { - return { PinType::Blocking, std::move(pinKey), version.ToString() }; + return { PinType::Blocking, std::move(pinKey) }; } - Pin Pin::CreatePinningPin(PinKey&& pinKey, Version version) + Pin Pin::CreatePinningPin(PinKey&& pinKey) { - return { PinType::Pinning, std::move(pinKey), version.ToString() }; + return { PinType::Pinning, std::move(pinKey) }; } - Pin Pin::CreateGatingPin(PinKey&& pinKey, GatedVersion gatedVersion) + Pin Pin::CreateGatingPin(PinKey&& pinKey, GatedVersion&& gatedVersion) { - return { PinType::Gating, std::move(pinKey), gatedVersion.ToString() }; + return { PinType::Gating, std::move(pinKey), std::move(gatedVersion) }; } PinType Pin::GetType() const @@ -60,15 +60,15 @@ namespace AppInstaller::Pinning return m_key.SourceId; } - std::string_view Pin::GetVersionString() const + Utility::GatedVersion Pin::GetGatedVersion() const { - return m_versionString; + return m_gatedVersion; } bool Pin::operator==(const Pin& other) const { return m_type == other.m_type && m_key == other.m_key && - m_versionString == other.m_versionString; + m_gatedVersion == other.m_gatedVersion; } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h index 3fbc0cc742..5ab209811c 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -62,10 +62,7 @@ namespace AppInstaller::Runtime PortableLinksUserLocation, // The location where symlinks to portable packages are stored under machine scope. PortableLinksMachineLocation, - // The location of the user settings json file. - UserSettingsFileLocation, - // The location of the user settings json file, anonymized using environment variables. - UserSettingsFileLocationForDisplay, + // The location where the database for pins is stored. PinningIndex, }; diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h index cbca0adc1b..fec59a9174 100644 --- a/src/AppInstallerCommonCore/Public/winget/Pin.h +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -43,27 +43,24 @@ namespace AppInstaller::Pinning struct Pin { - static Pin CreateBlockingPin(PinKey&& pinKey, Utility::Version version = {}); - static Pin CreatePinningPin(PinKey&& pinKey, Utility::Version version = {}); - static Pin CreateGatingPin(PinKey&& pinKey, Utility::GatedVersion gatedVersion); + static Pin CreateBlockingPin(PinKey&& pinKey); + static Pin CreatePinningPin(PinKey&& pinKey); + static Pin CreateGatingPin(PinKey&& pinKey, Utility::GatedVersion&& gatedVersion); PinType GetType() const; const PinKey& GetKey() const; const Manifest::Manifest::string_t& GetPackageId() const; std::string_view GetSourceId() const; - - // TODO: For now we'll keep the versions as simply a string in all cases. - // This will change once we actually start using them. - std::string_view GetVersionString() const; + Utility::GatedVersion GetGatedVersion() const; bool operator==(const Pin& other) const; private: - Pin(PinType type, PinKey&& pinKey, std::string_view versionString) - : m_type(type), m_key(std::move(pinKey)), m_versionString(versionString) {} + Pin(PinType type, PinKey&& pinKey, Utility::GatedVersion&& gatedVersion = {}) + : m_type(type), m_key(std::move(pinKey)), m_gatedVersion(std::move(gatedVersion)) {} PinType m_type = PinType::Unknown; PinKey m_key; - std::string m_versionString; + Utility::GatedVersion m_gatedVersion; }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index b27104dd6d..40c1a5098d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -12,7 +12,7 @@ namespace AppInstaller::Repository::Microsoft AICLI_LOG(Repo, Info, << "Creating new Pinning Index [" << version << "] at '" << filePath << "'"); PinningIndex result{ filePath, version }; - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "pinningIndex_createNew"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "pinningindex_createnew"); // Use calculated version, as incoming version could be 'latest' result.m_version.SetSchemaVersion(result.m_dbconn); @@ -31,7 +31,7 @@ namespace AppInstaller::Repository::Microsoft std::lock_guard lockInterface{ *m_interfaceLock }; AICLI_LOG(Repo, Verbose, << "Adding Pin for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "] with pin type " << Pinning::ToString(pin.GetType())); - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningIndex_addPin"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_addpin"); IdType result = m_interface->AddPin(m_dbconn, pin); @@ -47,7 +47,7 @@ namespace AppInstaller::Repository::Microsoft std::lock_guard lockInterface{ *m_interfaceLock }; AICLI_LOG(Repo, Verbose, << "Updating Pin for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "] with pin type " << Pinning::ToString(pin.GetType())); - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningIndex_updatePin"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_updatepin"); bool result = m_interface->UpdatePin(m_dbconn, pin).first; @@ -86,10 +86,10 @@ namespace AppInstaller::Repository::Microsoft return m_interface->GetAllPins(m_dbconn); } - void PinningIndex::ResetAllPins() + bool PinningIndex::ResetAllPins(std::string_view sourceId) { std::lock_guard lockInterface{ *m_interfaceLock }; - m_interface->ResetAllPins(m_dbconn); + return m_interface->ResetAllPins(m_dbconn, sourceId); } std::unique_ptr PinningIndex::CreateIPinningIndex() const diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h index e35ff91366..b449864aba 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma once #include "SQLiteWrapper.h" #include "Microsoft/Schema/IPinningIndex.h" @@ -42,7 +44,8 @@ namespace AppInstaller::Repository::Microsoft // Returns a vector containing all the existing pins. std::vector GetAllPins(); - void ResetAllPins(); + // Deletes all pins from a given source, or from all sources if none is specified + bool ResetAllPins(std::string_view sourceId = {}); private: // Constructor used to open an existing index. diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h index 732db3ba7e..c4c96a73aa 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h @@ -34,7 +34,8 @@ namespace AppInstaller::Repository::Microsoft::Schema // Returns a vector containing all the existing pins. virtual std::vector GetAllPins(SQLite::Connection& connection) = 0; - // Removes all the pins from the index - virtual void ResetAllPins(SQLite::Connection& connection) = 0; + // Removes all the pins from a given source, or from all sources if none is specified. + // Returns a value indicating whether any pin was deleted. + virtual bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) = 0; }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index 01479f0bf3..5425556d5e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -15,9 +15,9 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 switch (type) { case Pinning::PinType::Blocking: - return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }, Utility::Version{ version }); + return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); case Pinning::PinType::Pinning: - return Pinning::Pin::CreatePinningPin({ packageId, sourceId }, Utility::Version{ version }); + return Pinning::Pin::CreatePinningPin({ packageId, sourceId }); case Pinning::PinType::Gating: return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ version }); default: @@ -28,11 +28,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 using namespace std::string_view_literals; static constexpr std::string_view s_PinTable_Table_Name = "pin"sv; - static constexpr std::string_view s_PinTable_PackageId_Column = "packageId"sv; - static constexpr std::string_view s_PinTable_SourceId_Column = "sourceId"sv; + static constexpr std::string_view s_PinTable_PackageId_Column = "package_id"sv; + static constexpr std::string_view s_PinTable_SourceId_Column = "source_id"sv; static constexpr std::string_view s_PinTable_Type_Column = "type"sv; static constexpr std::string_view s_PinTable_Version_Column = "version"sv; - static constexpr std::string_view s_PinTable_Index = "pinIndex"sv; + static constexpr std::string_view s_PinTable_Index = "pin_index"sv; std::string_view PinTable::TableName() { @@ -45,7 +45,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); - StatementBuilder createTableBuilder; createTableBuilder.CreateTable(s_PinTable_Table_Name).BeginColumns(); @@ -65,10 +64,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 savepoint.Commit(); } - std::optional PinTable::GetByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + std::optional PinTable::GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) { - // TODO: The statement builder requires that the bound parameters can be converted to T&&, - // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) .Where(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) @@ -88,8 +85,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) { - // TODO: The statement builder requires that the bound parameters can be converted to T&&, - // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.InsertInto(s_PinTable_Table_Name) .Columns({ @@ -101,22 +96,20 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 (std::string_view)pin.GetPackageId(), pin.GetSourceId(), pin.GetType(), - pin.GetVersionString()); + pin.GetGatedVersion().ToString()); builder.Execute(connection); return connection.GetLastInsertRowID(); } - bool PinTable::UpdatePin(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) + bool PinTable::UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) { - // TODO: The statement builder requires that the bound parameters can be converted to T&&, - // but the package/source IDs go as const T&. Find a way to avoid the weird casting. SQLite::Builder::StatementBuilder builder; builder.Update(s_PinTable_Table_Name).Set() .Column(s_PinTable_PackageId_Column).Equals((std::string_view)pin.GetPackageId()) .Column(s_PinTable_SourceId_Column).Equals(pin.GetSourceId()) .Column(s_PinTable_Type_Column).Equals(pin.GetType()) - .Column(s_PinTable_Version_Column).Equals(pin.GetVersionString()) + .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()) .Where(SQLite::RowIDName).Equals(pinId); builder.Execute(connection); @@ -168,7 +161,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 { auto [packageId, sourceId, type, gatedVersion] = select.GetRow(); auto pin = GetPinFromRow(packageId, sourceId, type, gatedVersion); - if (pin) { + if (pin) + { pins.push_back(std::move(pin.value())); } } @@ -176,10 +170,18 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 return pins; } - void PinTable::ResetAllPins(SQLite::Connection& connection) + bool PinTable::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) { SQLite::Builder::StatementBuilder builder; builder.DeleteFrom(s_PinTable_Table_Name); + + if (!sourceId.empty()) + { + builder.Where(s_PinTable_SourceId_Column).Equals(sourceId); + } + builder.Execute(connection); + + return connection.GetChanges() != 0; } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h index 007c69d5f9..357ca2319b 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h @@ -8,7 +8,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 { - // A table struct PinTable { // Get the table name. @@ -17,13 +16,28 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // Creates the table with named indices. static void Create(SQLite::Connection& connection); + // Gets the row ID for the pin, if it exists. static std::optional GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); + // Adds a new pin. Returns the row ID of the added pin. static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); - static bool UpdatePin(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); + + // Updates an existing pin. + // Returns a value indicating whether there were any changes. + static bool UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); + + // Removes a pin given its row ID. static void RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); + + // Gets a pin by its row ID if it exists. + // Used for testing static std::optional GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId); + + // Gets all the currently existing pins. static std::vector GetAllPins(SQLite::Connection& connection); - static void ResetAllPins(SQLite::Connection& connection); + + // Resets all pins from a given source, or from all sources if none is specified. + // Returns a value indicating whether there were any changes. + static bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId = {}); }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h index c6560d5534..cfe049ebad 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h @@ -17,6 +17,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; std::vector GetAllPins(SQLite::Connection& connection) override; - void ResetAllPins(SQLite::Connection& connection) override; + bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) override; }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp index d9459529d6..ca50879474 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -10,7 +10,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 { std::optional GetExistingPinId(SQLite::Connection& connection, const Pinning::PinKey& pinKey) { - auto result = PinTable::GetByPinKey(connection, pinKey); + auto result = PinTable::GetIdByPinKey(connection, pinKey); if (!result) { @@ -30,7 +30,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 void PinningIndexInterface::CreateTables(SQLite::Connection& connection) { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPinningTables"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpinningtables_v1_0"); Pinning_V1_0::PinTable::Create(connection); savepoint.Commit(); } @@ -41,7 +41,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPin.has_value()); - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addPin_v1_0"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_0"); SQLite::rowid_t pinId = PinTable::AddPin(connection, pin); savepoint.Commit(); @@ -55,8 +55,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // If the pin doesn't exist, fail the update THROW_HR_IF(E_NOT_SET, !existingPinId); - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addPin_v1_0"); - bool status = PinTable::UpdatePin(connection, existingPinId.value(), pin); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatepin_v1_0"); + bool status = PinTable::UpdatePinById(connection, existingPinId.value(), pin); savepoint.Commit(); return { status, existingPinId.value() }; @@ -69,7 +69,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // If the pin doesn't exist, fail the remove THROW_HR_IF(E_NOT_SET, !existingPinId); - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removePin_v1_0"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removepin_v1_0"); PinTable::RemovePinById(connection, existingPinId.value()); savepoint.Commit(); @@ -93,10 +93,12 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 return PinTable::GetAllPins(connection); } - void PinningIndexInterface::ResetAllPins(SQLite::Connection& connection) + bool PinningIndexInterface::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "resetPins_v1_0"); - PinTable::ResetAllPins(connection); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "resetpins_v1_0"); + bool result = PinTable::ResetAllPins(connection, sourceId); savepoint.Commit(); + + return result; } } \ No newline at end of file From 9d63c89d5ef8532d58628ab200297488d7b064f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Mon, 9 Jan 2023 16:25:48 -0800 Subject: [PATCH 26/55] Update src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp Co-authored-by: yao-msft <50888816+yao-msft@users.noreply.github.com> --- .../Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp index ca50879474..bd55e2ba07 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -53,7 +53,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 auto existingPinId = GetExistingPinId(connection, pin.GetKey()); // If the pin doesn't exist, fail the update - THROW_HR_IF(E_NOT_SET, !existingPinId); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatepin_v1_0"); bool status = PinTable::UpdatePinById(connection, existingPinId.value(), pin); From ef09f687a92fd97070f632256df1752174d9f3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Mon, 9 Jan 2023 16:26:25 -0800 Subject: [PATCH 27/55] Apply suggestions from code review Co-authored-by: yao-msft <50888816+yao-msft@users.noreply.github.com> --- .../Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp index bd55e2ba07..c0e56b7cfc 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -68,7 +68,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 auto existingPinId = GetExistingPinId(connection, pinKey); // If the pin doesn't exist, fail the remove - THROW_HR_IF(E_NOT_SET, !existingPinId); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removepin_v1_0"); PinTable::RemovePinById(connection, existingPinId.value()); From 0aceec3263bc9572df197b3aca7b3d67d83652af Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 9 Jan 2023 16:29:16 -0800 Subject: [PATCH 28/55] Spelling --- .github/actions/spelling/allow.txt | 2 ++ src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 2 +- .../Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 1c87c3ff13..7231a00be5 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -392,6 +392,7 @@ pfp PGP php PII +pinningindex pipssource PKCS placeholders @@ -449,6 +450,7 @@ removeportablefile repolibtest requeue rescap +resetpins resheader resmimetype RESOLVESOURCE diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index b25e360488..aa92060f65 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1638,7 +1638,7 @@ Please specify one of them using the --source option to proceed. Shown when listing or modifying existing pins if there are none. - Pins resetted successfully + Pins reset successfully Shown after resetting (deleting) all the pins \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp index c0e56b7cfc..354dca15b2 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -30,7 +30,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 void PinningIndexInterface::CreateTables(SQLite::Connection& connection) { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpinningtables_v1_0"); + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); Pinning_V1_0::PinTable::Create(connection); savepoint.Commit(); } From 8f3d96bf16ccb44293ca06410f60f6d4663105dc Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 9 Jan 2023 16:41:05 -0800 Subject: [PATCH 29/55] PR comments --- src/AppInstallerCommonCore/Public/winget/Pin.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h index fec59a9174..3a617e18ba 100644 --- a/src/AppInstallerCommonCore/Public/winget/Pin.h +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include -#include +#include "winget/Manifest.h" +#include "AppInstallerVersions.h" namespace AppInstaller::Pinning { From 19d87b1a4f3569180bf4b362a94bde009a7373e0 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 9 Jan 2023 16:46:58 -0800 Subject: [PATCH 30/55] Move log --- src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index 40c1a5098d..e9cda61dd8 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -62,8 +62,8 @@ namespace AppInstaller::Repository::Microsoft void PinningIndex::RemovePin(const Pinning::PinKey& pinKey) { - AICLI_LOG(Repo, Verbose, << "Removing Pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Removing Pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningIndex_removePin"); From f452776281211c656ab8a429fa32579eca3ec10b Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 9 Jan 2023 17:03:49 -0800 Subject: [PATCH 31/55] Open read only --- src/AppInstallerCLICore/Commands/PinCommand.cpp | 12 +++++++----- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 5 +++-- src/AppInstallerCLICore/Workflows/PinFlow.h | 10 +++++++++- src/AppInstallerCLITests/PinFlow.cpp | 2 +- .../Public/AppInstallerRuntime.h | 2 -- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index 1e2c5d0d71..d3e3f0fef1 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -124,7 +124,7 @@ namespace AppInstaller::CLI Workflow::EnsureOneMatchFromSearchResult(false) << Workflow::GetInstalledPackageVersion << Workflow::ReportPackageIdentity << - Workflow::OpenPinningIndex << + Workflow::OpenPinningIndex() << Workflow::SearchPin << Workflow::AddPin; } @@ -196,7 +196,7 @@ namespace AppInstaller::CLI Workflow::EnsureOneMatchFromSearchResult(false) << Workflow::GetInstalledPackageVersion << Workflow::ReportPackageIdentity << - Workflow::OpenPinningIndex << + Workflow::OpenPinningIndex() << Workflow::SearchPin << Workflow::RemovePin; } @@ -261,7 +261,7 @@ namespace AppInstaller::CLI void PinListCommand::ExecuteInternal(Execution::Context& context) const { context << - Workflow::OpenPinningIndex << + Workflow::OpenPinningIndex(/* readOnly */ true) << Workflow::GetAllPins << Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << @@ -294,11 +294,12 @@ namespace AppInstaller::CLI void PinResetCommand::ExecuteInternal(Execution::Context& context) const { - context << Workflow::OpenPinningIndex; if (context.Args.Contains(Execution::Args::Type::Force)) { - context << Workflow::ResetAllPins; + context << + Workflow::OpenPinningIndex() << + Workflow::ResetAllPins; } else { @@ -306,6 +307,7 @@ namespace AppInstaller::CLI context.Reporter.Info() << Resource::String::PinResetUseForceArg << std::endl; context << + Workflow::OpenPinningIndex(/* readOnly */ true) << Workflow::GetAllPins << Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index e2b626818c..fd99d436a3 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -33,14 +33,15 @@ namespace AppInstaller::CLI::Workflow } } - void OpenPinningIndex(Execution::Context& context) + void OpenPinningIndex::operator()(Execution::Context& context) const { auto indexPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; AICLI_LOG(CLI, Info, << "Opening pinning index"); + SQLiteStorageBase::OpenDisposition openDisposition = m_readOnly ? SQLiteStorageBase::OpenDisposition::Read : SQLiteStorageBase::OpenDisposition::ReadWrite; auto pinningIndex = std::filesystem::exists(indexPath) ? - PinningIndex::Open(indexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite) : + PinningIndex::Open(indexPath.u8string(), openDisposition) : PinningIndex::CreateNew(indexPath.u8string()); context.Add(std::make_shared(std::move(pinningIndex))); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h index b367853d81..fd4a5fce2c 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.h +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -9,7 +9,15 @@ namespace AppInstaller::CLI::Workflow // Required Args: None // Inputs: None // Outputs: PinningIndex - void OpenPinningIndex(Execution::Context& context); + struct OpenPinningIndex : public WorkflowTask + { + OpenPinningIndex(bool readOnly = false) : WorkflowTask("OpenPinningIndex"), m_readOnly(readOnly) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_readOnly; + }; // Gets all the pins from the index. // Required Args: None diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index c1dd94565b..02d3bcc0f5 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -15,7 +15,7 @@ using namespace AppInstaller::Pinning; void OverrideForOpenPinningIndex(TestContext& context, const std::filesystem::path& indexPath) { - context.Override({ OpenPinningIndex, [=](TestContext& context) + context.Override({ "OpenPinningIndex", [=](TestContext& context) { auto pinningIndex = std::filesystem::exists(indexPath) ? PinningIndex::Open(indexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite) : diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h index 5ab209811c..f7df896850 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -62,8 +62,6 @@ namespace AppInstaller::Runtime PortableLinksUserLocation, // The location where symlinks to portable packages are stored under machine scope. PortableLinksMachineLocation, - // The location where the database for pins is stored. - PinningIndex, }; // The principal that an ACE applies to. From 18ba3304c73fc9f62b9f3a5ab34d892161afea63 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 9 Jan 2023 17:20:40 -0800 Subject: [PATCH 32/55] Move openpinningindex --- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 11 ++--------- .../Microsoft/PinningIndex.cpp | 15 +++++++++++++++ .../Microsoft/PinningIndex.h | 4 ++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index fd99d436a3..e84bf2d6f6 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -35,15 +35,8 @@ namespace AppInstaller::CLI::Workflow void OpenPinningIndex::operator()(Execution::Context& context) const { - auto indexPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; - - AICLI_LOG(CLI, Info, << "Opening pinning index"); - SQLiteStorageBase::OpenDisposition openDisposition = m_readOnly ? SQLiteStorageBase::OpenDisposition::Read : SQLiteStorageBase::OpenDisposition::ReadWrite; - - auto pinningIndex = std::filesystem::exists(indexPath) ? - PinningIndex::Open(indexPath.u8string(), openDisposition) : - PinningIndex::CreateNew(indexPath.u8string()); - + auto openDisposition = m_readOnly ? SQLiteStorageBase::OpenDisposition::Read : SQLiteStorageBase::OpenDisposition::ReadWrite; + auto pinningIndex = PinningIndex::OpenOrCreateDefault(openDisposition); context.Add(std::make_shared(std::move(pinningIndex))); } diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index e9cda61dd8..cbcce5f867 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -26,6 +26,21 @@ namespace AppInstaller::Repository::Microsoft return result; } + PinningIndex PinningIndex::OpenOrCreateDefault(OpenDisposition openDisposition) + { + auto indexPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; + AICLI_LOG(CLI, Info, << "Opening pinning index"); + + if (std::filesystem::exists(indexPath)) + { + return PinningIndex::Open(indexPath.u8string(), openDisposition); + } + else + { + return PinningIndex::CreateNew(indexPath.u8string()); + } + } + PinningIndex::IdType PinningIndex::AddPin(const Pinning::Pin& pin) { std::lock_guard lockInterface{ *m_interfaceLock }; diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h index b449864aba..b101ecf8ee 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -29,6 +29,10 @@ namespace AppInstaller::Repository::Microsoft return { filePath, disposition, std::move(indexFile) }; } + // Opens or creates a PinningIndex database on the default path. + // openDisposition is only used when opening an existing database. + static PinningIndex OpenOrCreateDefault(OpenDisposition openDisposition = OpenDisposition::ReadWrite); + // Adds a pin to the index. IdType AddPin(const Pinning::Pin& pin); From 1f6fe6204813ccb0b59a23dce48a64dcc5a9bfec Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 10 Jan 2023 10:57:18 -0800 Subject: [PATCH 33/55] Catch errors when opening the pinning index --- src/AppInstallerCLICore/Resources.h | 1 + src/AppInstallerCLICore/Workflows/PinFlow.cpp | 9 ++++++++- .../Shared/Strings/en-us/winget.resw | 4 ++++ src/AppInstallerCommonCore/Errors.cpp | 2 ++ .../Public/AppInstallerErrors.h | 1 + .../Microsoft/PinningIndex.cpp | 20 ++++++++++++------- .../Microsoft/PinningIndex.h | 3 ++- 7 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 998074c1bc..432c77c270 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -246,6 +246,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinAdded); WINGET_DEFINE_RESOURCE_STRINGID(PinAlreadyExists); + WINGET_DEFINE_RESOURCE_STRINGID(PinCannotOpenIndex); WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinDoesNotExist); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index e84bf2d6f6..6eb15e2987 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -37,7 +37,14 @@ namespace AppInstaller::CLI::Workflow { auto openDisposition = m_readOnly ? SQLiteStorageBase::OpenDisposition::Read : SQLiteStorageBase::OpenDisposition::ReadWrite; auto pinningIndex = PinningIndex::OpenOrCreateDefault(openDisposition); - context.Add(std::make_shared(std::move(pinningIndex))); + if (!pinningIndex) + { + AICLI_LOG(CLI, Error, << "Unable to open pinning index."); + context.Reporter.Info() << Resource::String::PinCannotOpenIndex << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX); + } + + context.Add(std::move(pinningIndex)); } void GetAllPins(Execution::Context& context) diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index aa92060f65..182670813d 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1641,4 +1641,8 @@ Please specify one of them using the --source option to proceed. Pins reset successfully Shown after resetting (deleting) all the pins + + Unable to open pin database. + Error message for when we cannot open the database containing package pins. + \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp index c27694fdd7..45fd96a2d0 100644 --- a/src/AppInstallerCommonCore/Errors.cpp +++ b/src/AppInstallerCommonCore/Errors.cpp @@ -212,6 +212,8 @@ namespace AppInstaller return "A pin already exists for the package."; case APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST: return "There is no pin for the package."; + case APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX: + return "Unable to open the pin database." // Install errors case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: diff --git a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h index 0ec281229d..f7864f751a 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h @@ -112,6 +112,7 @@ #define APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED ((HRESULT)0x8A150061) #define APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS ((HRESULT)0x8A150062) #define APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST ((HRESULT)0x8A150063) +#define APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX ((HRESULT)0x8A150064) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index cbcce5f867..fc9dbd0ac2 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -26,19 +26,25 @@ namespace AppInstaller::Repository::Microsoft return result; } - PinningIndex PinningIndex::OpenOrCreateDefault(OpenDisposition openDisposition) + std::shared_ptr PinningIndex::OpenOrCreateDefault(OpenDisposition openDisposition) { auto indexPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; AICLI_LOG(CLI, Info, << "Opening pinning index"); - if (std::filesystem::exists(indexPath)) + try { - return PinningIndex::Open(indexPath.u8string(), openDisposition); - } - else - { - return PinningIndex::CreateNew(indexPath.u8string()); + if (std::filesystem::exists(indexPath)) + { + return std::make_shared(PinningIndex::Open(indexPath.u8string(), openDisposition)); + } + else + { + return std::make_shared(PinningIndex::CreateNew(indexPath.u8string())); + } } + CATCH_LOG(); + + return {}; } PinningIndex::IdType PinningIndex::AddPin(const Pinning::Pin& pin) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h index b101ecf8ee..77180331b5 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -31,7 +31,8 @@ namespace AppInstaller::Repository::Microsoft // Opens or creates a PinningIndex database on the default path. // openDisposition is only used when opening an existing database. - static PinningIndex OpenOrCreateDefault(OpenDisposition openDisposition = OpenDisposition::ReadWrite); + // Returns nullptr in case of error. + static std::shared_ptr OpenOrCreateDefault(OpenDisposition openDisposition = OpenDisposition::ReadWrite); // Adds a pin to the index. IdType AddPin(const Pinning::Pin& pin); From 533c2c9ffe1ff7b7c0d80bedec17d7a2ff0bc2b2 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 10 Jan 2023 11:11:18 -0800 Subject: [PATCH 34/55] Update tests after merging master --- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 2 +- src/AppInstallerCLITests/PinFlow.cpp | 26 +++++++++---------- src/AppInstallerCommonCore/Errors.cpp | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 6eb15e2987..55f2294348 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -40,7 +40,7 @@ namespace AppInstaller::CLI::Workflow if (!pinningIndex) { AICLI_LOG(CLI, Error, << "Unable to open pinning index."); - context.Reporter.Info() << Resource::String::PinCannotOpenIndex << std::endl; + context.Reporter.Error() << Resource::String::PinCannotOpenIndex << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX); } diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index 02d3bcc0f5..42989fc5da 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -31,8 +31,8 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") std::ostringstream pinAddOutput; TestContext addContext{ pinAddOutput, std::cin }; OverrideForOpenPinningIndex(addContext, indexFile.GetPath()); - OverrideForCompositeInstalledSource(addContext); - addContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR:: TestInstaller_Exe })); + addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); addContext.Args.AddArg(Execution::Args::Type::BlockingPin); PinAddCommand pinAdd({}); @@ -52,8 +52,8 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") std::ostringstream pinListOutput; TestContext listContext{ pinListOutput, std::cin }; OverrideForOpenPinningIndex(listContext, indexFile.GetPath()); - OverrideForCompositeInstalledSource(listContext); - listContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + OverrideForCompositeInstalledSource(listContext, CreateTestSource({ TSR::TestInstaller_Exe })); + listContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); PinListCommand pinList({}); pinList.Execute(listContext); @@ -67,8 +67,8 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") std::ostringstream pinRemoveOutput; TestContext removeContext{ pinRemoveOutput, std::cin }; OverrideForOpenPinningIndex(removeContext, indexFile.GetPath()); - OverrideForCompositeInstalledSource(removeContext); - removeContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + OverrideForCompositeInstalledSource(removeContext, CreateTestSource({ TSR::TestInstaller_Exe })); + removeContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); PinRemoveCommand pinRemove({}); pinRemove.Execute(removeContext); @@ -86,7 +86,7 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") SECTION("Without --force") { - OverrideForCompositeInstalledSource(resetContext); + OverrideForCompositeInstalledSource(resetContext, CreateTestSource({ TSR::TestInstaller_Exe })); PinResetCommand pinReset({}); pinReset.Execute(resetContext); INFO(pinResetOutput.str()); @@ -113,8 +113,8 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") std::ostringstream pinUpdateOutput; TestContext updateContext{ pinUpdateOutput, std::cin }; OverrideForOpenPinningIndex(updateContext, indexFile.GetPath()); - OverrideForCompositeInstalledSource(updateContext); - updateContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + OverrideForCompositeInstalledSource(updateContext, CreateTestSource({ TSR::TestInstaller_Exe })); + updateContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); SECTION("Without --force") { @@ -148,7 +148,7 @@ TEST_CASE("PinFlow_Add_NotFound", "[PinFlow][workflow]") { std::ostringstream pinAddOutput; TestContext addContext{ pinAddOutput, std::cin }; - OverrideForCompositeInstalledSource(addContext); + OverrideForCompositeInstalledSource(addContext, CreateTestSource({})); addContext.Args.AddArg(Execution::Args::Type::Query, "This package doesn't exist"sv); PinAddCommand pinAdd({}); @@ -165,7 +165,7 @@ TEST_CASE("PinFlow_ListEmpty", "[PinFlow][workflow]") std::ostringstream pinListOutput; TestContext listContext{ pinListOutput, std::cin }; OverrideForOpenPinningIndex(listContext, indexFile.GetPath()); - OverrideForCompositeInstalledSource(listContext); + OverrideForCompositeInstalledSource(listContext, CreateTestSource({})); PinListCommand pinList({}); pinList.Execute(listContext); @@ -181,8 +181,8 @@ TEST_CASE("PinFlow_RemoveNonExisting", "[PinFlow][workflow]") std::ostringstream pinRemoveOutput; TestContext removeContext{ pinRemoveOutput, std::cin }; OverrideForOpenPinningIndex(removeContext, indexFile.GetPath()); - OverrideForCompositeInstalledSource(removeContext); - removeContext.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv); + OverrideForCompositeInstalledSource(removeContext, CreateTestSource({ TSR::TestInstaller_Exe })); + removeContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); PinRemoveCommand pinRemove({}); pinRemove.Execute(removeContext); diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp index 45fd96a2d0..524920f0fe 100644 --- a/src/AppInstallerCommonCore/Errors.cpp +++ b/src/AppInstallerCommonCore/Errors.cpp @@ -213,7 +213,7 @@ namespace AppInstaller case APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST: return "There is no pin for the package."; case APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX: - return "Unable to open the pin database." + return "Unable to open the pin database."; // Install errors case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: From 51c25be3f80fd1fbd9ea77b32b5adec6d6c224ee Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 10 Jan 2023 12:15:25 -0800 Subject: [PATCH 35/55] Fix errors from merge --- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 2 +- src/AppInstallerCLITests/CompositeSource.cpp | 21 +++++++++++-------- .../CompositeSource.cpp | 20 +++++++++++------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index d9911f3c20..72fdace62f 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -108,7 +108,7 @@ namespace AppInstaller::CLI::Workflow continue; } - auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId, installedVersionString); + auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId); AICLI_LOG(CLI, Info, << "Evaluating pin with type " << ToString(pin.GetType()) << " for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]"); auto existingPin = pinningIndex->GetPin(pinKey); diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index 0786d7b5e5..288955c86c 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -1113,7 +1113,8 @@ TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") ExpectedResultWithPin expectedResult_considerPins; PinKey pinKey("Id", setup.Available->Details.Identifier); - PinningIndex pinningIndex = PinningIndex::OpenOrCreateDefault(); + auto pinningIndex = PinningIndex::OpenOrCreateDefault(); + REQUIRE(pinningIndex); SECTION("Unpinned") { @@ -1123,7 +1124,7 @@ TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") } SECTION("Pinned") { - pinningIndex.AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); + pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); // Pinning pins are ignored with --include-pinned expectedResult_includePinned = expectedResult_ignorePins; @@ -1135,7 +1136,7 @@ TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") } SECTION("Blocked") { - pinningIndex.AddPin(Pin::CreateBlockingPin(PinKey{ pinKey })); + pinningIndex->AddPin(Pin::CreateBlockingPin(PinKey{ pinKey })); expectedResult_considerPins.IsUpdateAvailable = false; expectedResult_considerPins.LatestAvailableVersion = {}; @@ -1147,7 +1148,7 @@ TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") } SECTION("Gated to 1.*") { - pinningIndex.AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); + pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); expectedResult_considerPins.IsUpdateAvailable = true; expectedResult_considerPins.LatestAvailableVersion = "1.1.0"; @@ -1159,7 +1160,7 @@ TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") } SECTION("Gated to 1.0.*") { - pinningIndex.AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.0.*"sv })); + pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.0.*"sv })); expectedResult_considerPins.IsUpdateAvailable = false; expectedResult_considerPins.LatestAvailableVersion = "1.0.1"; @@ -1217,8 +1218,9 @@ TEST_CASE("CompositeSource_OneSourcePinned", "[CompositeSource][PinFlow]") { PinKey pinKey("Id", setup.Available->Details.Identifier); - PinningIndex pinningIndex = PinningIndex::OpenOrCreateDefault(); - pinningIndex.AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); + auto pinningIndex = PinningIndex::OpenOrCreateDefault(); + REQUIRE(pinningIndex); + pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); } PinBehavior pinBehavior = PinBehavior::IgnorePins; @@ -1297,8 +1299,9 @@ TEST_CASE("CompositeSource_OneSourceGated", "[CompositeSource][PinFlow]") { PinKey pinKey("Id", setup.Available->Details.Identifier); - PinningIndex pinningIndex = PinningIndex::OpenOrCreateDefault(); - pinningIndex.AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); + auto pinningIndex = PinningIndex::OpenOrCreateDefault(); + REQUIRE(pinningIndex); + pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); } PinBehavior pinBehavior = PinBehavior::IgnorePins; diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 09c13fff58..bfd9e92e69 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -369,10 +369,9 @@ namespace AppInstaller::Repository } else if (Pin->GetType() == Pinning::PinType::Gating) { - Utility::GatedVersion gatedVersion(Pin->GetVersion()); auto versionKeys = AvailablePackage->GetAvailableVersionKeys(PinBehavior::IgnorePins); std::vector result; - std::copy_if(versionKeys.begin(), versionKeys.end(), std::back_inserter(result), [&](const PackageVersionKey& pvk) { return gatedVersion.IsValidVersion(pvk.Version); }); + std::copy_if(versionKeys.begin(), versionKeys.end(), std::back_inserter(result), [&](const PackageVersionKey& pvk) { return Pin->GetGatedVersion().IsValidVersion(pvk.Version); }); return result; } } @@ -399,8 +398,7 @@ namespace AppInstaller::Repository } else if (Pin->GetType() == Pinning::PinType::Gating) { - Utility::GatedVersion gatedVersion(Pin->GetVersion()); - if (!gatedVersion.IsValidVersion(versionKey.Version)) + if (!Pin->GetGatedVersion().IsValidVersion(versionKey.Version)) { AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] as it does not satisfy Gating pin"); return {}; @@ -1192,9 +1190,12 @@ namespace AppInstaller::Repository { // Look up any pins for the packages found auto pinningIndex = PinningIndex::OpenOrCreateDefault(); - for (auto& match : result.Matches) + if (pinningIndex) { - match.Package->GetExistingPins(pinningIndex); + for (auto& match : result.Matches) + { + match.Package->GetExistingPins(*pinningIndex); + } } } @@ -1315,9 +1316,12 @@ namespace AppInstaller::Repository { // Look up any pins for the packages found auto pinningIndex = PinningIndex::OpenOrCreateDefault(); - for (auto& match : result.Matches) + if (pinningIndex) { - match.Package->GetExistingPins(pinningIndex); + for (auto& match : result.Matches) + { + match.Package->GetExistingPins(*pinningIndex); + } } } From 395a9f39c1337469ceea6e9bba41833f3805cc11 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 12 Jan 2023 13:13:38 -0800 Subject: [PATCH 36/55] Fix errors from merge --- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 69 ++++++++----------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 8eeda59943..068b3bf2e0 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -59,9 +59,6 @@ namespace AppInstaller::CLI::Workflow std::vector pins; std::set sources; - // TODO: We should support querying the multiple sources for a package, instead of just one - auto availableVersion = package->GetLatestAvailableVersion(); - auto pinningIndex = context.Get(); auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); @@ -75,10 +72,10 @@ namespace AppInstaller::CLI::Workflow if (sources.insert(pinKey.SourceId).second) { auto pin = pinningIndex->GetPin(pinKey); - if (pin) - { - pins.emplace_back(std::move(pin.value())); - } + if (pin) + { + pins.emplace_back(std::move(pin.value())); + } } } @@ -101,9 +98,9 @@ namespace AppInstaller::CLI::Workflow for (auto versionKey : packageVersionKeys) { auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); - Pinning::PinKey pinKey{ - availableVersion->GetProperty(PackageVersionProperty::Id).get(), - availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; + Pinning::PinKey pinKey{ + availableVersion->GetProperty(PackageVersionProperty::Id).get(), + availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; if (!sources.insert(pinKey.SourceId).second) { @@ -111,40 +108,39 @@ namespace AppInstaller::CLI::Workflow continue; } - auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId); + auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId); AICLI_LOG(CLI, Info, << "Evaluating pin with type " << ToString(pin.GetType()) << " for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]"); - auto pinningIndex = context.Get(); - auto existingPin = pinningIndex->GetPin(pinKey); + auto existingPin = pinningIndex->GetPin(pinKey); - if (existingPin) - { + if (existingPin) + { auto packageName = availableVersion->GetProperty(PackageVersionProperty::Name); - // Pin already exists. - // If it is the same, we do nothing. If it is different, check for the --force arg - if (pin == existingPin) - { - AICLI_LOG(CLI, Info, << "Pin already exists"); + // Pin already exists. + // If it is the same, we do nothing. If it is different, check for the --force arg + if (pin == existingPin) + { + AICLI_LOG(CLI, Info, << "Pin already exists"); context.Reporter.Info() << Resource::String::PinAlreadyExists(packageName) << std::endl; continue; - } + } AICLI_LOG(CLI, Info, << "Another pin already exists for the package for source " << pinKey.SourceId); - if (context.Args.Contains(Execution::Args::Type::Force)) - { - AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); + if (context.Args.Contains(Execution::Args::Type::Force)) + { + AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); context.Reporter.Warn() << Resource::String::PinExistsOverwriting(packageName) << std::endl; pinsToAddOrUpdate.push_back(std::move(pin)); + } + else + { + context.Reporter.Error() << Resource::String::PinExistsUseForceArg(packageName) << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); + } } else { - context.Reporter.Error() << Resource::String::PinExistsUseForceArg(packageName) << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); - } - } - else - { pinsToAddOrUpdate.push_back(std::move(pin)); } } @@ -166,9 +162,6 @@ namespace AppInstaller::CLI::Workflow std::vector pins; std::set sources; - // TODO: We should support querying the multiple sources for a package, instead of just one - auto availableVersion = package->GetLatestAvailableVersion(); - auto pinningIndex = context.Get(); bool pinExists = false; @@ -179,15 +172,15 @@ namespace AppInstaller::CLI::Workflow for (auto versionKey : packageVersionKeys) { auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); - Pinning::PinKey pinKey{ - availableVersion->GetProperty(PackageVersionProperty::Id).get(), - availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; + Pinning::PinKey pinKey{ + availableVersion->GetProperty(PackageVersionProperty::Id).get(), + availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; if (sources.insert(pinKey.SourceId).second) { if (pinningIndex->GetPin(pinKey)) { - AICLI_LOG(CLI, Info, << "Removing pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); + AICLI_LOG(CLI, Info, << "Removing pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]"); pinningIndex->RemovePin(pinKey); pinExists = true; } @@ -230,13 +223,11 @@ namespace AppInstaller::CLI::Workflow for (const auto& pin : pins) { - // TODO: Avoid these conversions to string table.OutputLine({ pin.GetPackageId(), sourceNames[pin.GetSourceId()], std::string{ ToString(pin.GetType()) }, pin.GetGatedVersion().ToString(), - std::string{ ToString(pin.GetType()) }, }); } From 88ef0f10399c1c88d92e42d98ecd12bc163e6f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Wed, 18 Jan 2023 10:51:42 -0800 Subject: [PATCH 37/55] Update src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp Co-authored-by: yao-msft <50888816+yao-msft@users.noreply.github.com> --- src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index 5fb210e04a..bb307b4a7e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -43,7 +43,8 @@ namespace AppInstaller::Repository::Microsoft const auto indexPath = DefaultIndexPath; #endif - AICLI_LOG(CLI, Info, << "Opening pinning index"); + AICLI_LOG(Repo, Info, << "Opening pinning index"); + try { From 9f6f3251f385a803892ccca78cca8e4da3ab87fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Wed, 18 Jan 2023 10:54:38 -0800 Subject: [PATCH 38/55] Apply suggestions from code review Co-authored-by: yao-msft <50888816+yao-msft@users.noreply.github.com> --- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 068b3bf2e0..d186c4ec3b 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -62,7 +62,8 @@ namespace AppInstaller::CLI::Workflow auto pinningIndex = context.Get(); auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); - for (auto versionKey : packageVersionKeys) + for (const auto& versionKey : packageVersionKeys) + { auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); Pinning::PinKey pinKey{ @@ -95,7 +96,8 @@ namespace AppInstaller::CLI::Workflow auto pinningIndex = context.Get(); auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); - for (auto versionKey : packageVersionKeys) + for (const auto& versionKey : packageVersionKeys) + { auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); Pinning::PinKey pinKey{ @@ -147,7 +149,8 @@ namespace AppInstaller::CLI::Workflow if (!pinsToAddOrUpdate.empty()) { - for (auto pin : pinsToAddOrUpdate) + for (const auto& pin : pinsToAddOrUpdate) + { pinningIndex->AddOrUpdatePin(pin); } @@ -169,7 +172,8 @@ namespace AppInstaller::CLI::Workflow // that will be the only one we get version keys from. // So, we remove pins from all sources unless one was provided. auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); - for (auto versionKey : packageVersionKeys) + for (const auto& versionKey : packageVersionKeys) + { auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); Pinning::PinKey pinKey{ @@ -270,7 +274,8 @@ namespace AppInstaller::CLI::Workflow const auto& source = context.Get(); std::vector matchingPins; - for (const auto pin : pins) + for (const auto& pin : pins) + { SearchRequest searchRequest; searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pin.GetPackageId()); From 3fbb35be010107ed2dc52156405fe223ff8d66c2 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 24 Jan 2023 16:20:11 -0800 Subject: [PATCH 39/55] Move ignoring pinned packages out of CompositePackage for better reporting --- src/AppInstallerCLICore/Resources.h | 1 + .../Workflows/CompletionFlow.cpp | 4 +- .../Workflows/ImportExportFlow.cpp | 2 +- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 14 +- .../Workflows/ShowFlow.cpp | 2 +- .../Workflows/UninstallFlow.cpp | 4 +- .../Workflows/UpdateFlow.cpp | 22 +- .../Workflows/WorkflowBase.cpp | 6 +- .../Shared/Strings/en-us/winget.resw | 3 + src/AppInstallerCLITests/CompositeSource.cpp | 242 ++++++++---------- .../SQLiteIndexSource.cpp | 8 +- src/AppInstallerCLITests/TestSource.cpp | 4 +- src/AppInstallerCLITests/TestSource.h | 4 +- .../CompositeSource.cpp | 201 ++++++++++----- .../Microsoft/SQLiteIndexSource.cpp | 8 +- .../PackageInstalledStatus.cpp | 4 +- .../Public/winget/RepositorySearch.h | 21 +- .../Rest/RestSource.cpp | 6 +- .../CatalogPackage.cpp | 4 +- 19 files changed, 319 insertions(+), 241 deletions(-) diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index bbfe8d499b..524d03e556 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -437,6 +437,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnologyInNewerVersions); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeIsPinned); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation); diff --git a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp index 5cc3f6b18d..361d7a44e5 100644 --- a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp @@ -70,7 +70,7 @@ namespace AppInstaller::CLI::Workflow const std::string& word = context.Get().Word(); auto stream = context.Reporter.Completion(); - for (const auto& vc : context.Get()->GetAvailableVersionKeys(Repository::PinBehavior::IgnorePins)) + for (const auto& vc : context.Get()->GetAvailableVersionKeys()) { if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Version, word)) { @@ -86,7 +86,7 @@ namespace AppInstaller::CLI::Workflow std::vector channels; - for (const auto& vc : context.Get()->GetAvailableVersionKeys(Repository::PinBehavior::IgnorePins)) + for (const auto& vc : context.Get()->GetAvailableVersionKeys()) { if ((word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Channel, word)) && std::find(channels.begin(), channels.end(), vc.Channel) == channels.end()) diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp index ca927e0377..f7e8edb120 100644 --- a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -68,7 +68,7 @@ namespace AppInstaller::CLI::Workflow return package->GetLatestAvailableVersion(PinBehavior::IgnorePins); } - auto availablePackageVersion = package->GetAvailableVersion({ "", version, channel }, PinBehavior::IgnorePins); + auto availablePackageVersion = package->GetAvailableVersion({ "", version, channel }); if (!availablePackageVersion) { availablePackageVersion = package->GetLatestAvailableVersion(PinBehavior::IgnorePins); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index d186c4ec3b..9020e354c3 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -61,11 +61,11 @@ namespace AppInstaller::CLI::Workflow auto pinningIndex = context.Get(); - auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); + auto packageVersionKeys = package->GetAvailableVersionKeys(); for (const auto& versionKey : packageVersionKeys) { - auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); + auto availableVersion = package->GetAvailableVersion(versionKey); Pinning::PinKey pinKey{ availableVersion->GetProperty(PackageVersionProperty::Id).get(), availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; @@ -95,11 +95,11 @@ namespace AppInstaller::CLI::Workflow auto pinningIndex = context.Get(); - auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); + auto packageVersionKeys = package->GetAvailableVersionKeys(); for (const auto& versionKey : packageVersionKeys) { - auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); + auto availableVersion = package->GetAvailableVersion(versionKey); Pinning::PinKey pinKey{ availableVersion->GetProperty(PackageVersionProperty::Id).get(), availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; @@ -171,11 +171,11 @@ namespace AppInstaller::CLI::Workflow // Note that if a source was specified in the command line, // that will be the only one we get version keys from. // So, we remove pins from all sources unless one was provided. - auto packageVersionKeys = package->GetAvailableVersionKeys(PinBehavior::IgnorePins); + auto packageVersionKeys = package->GetAvailableVersionKeys(); for (const auto& versionKey : packageVersionKeys) { - auto availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); + auto availableVersion = package->GetAvailableVersion(versionKey); Pinning::PinKey pinKey{ availableVersion->GetProperty(PackageVersionProperty::Id).get(), availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() }; @@ -284,7 +284,7 @@ namespace AppInstaller::CLI::Workflow // Ensure the match comes from the right source for (const auto& match : searchResult.Matches) { - auto availableVersion = match.Package->GetAvailableVersion({ pin.GetSourceId(), "", "" }, PinBehavior::IgnorePins); + auto availableVersion = match.Package->GetAvailableVersion({ pin.GetSourceId(), "", "" }); if (availableVersion) { matchingPins.push_back(pin); diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index 8b36db3e8c..e3e4820f97 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -227,7 +227,7 @@ namespace AppInstaller::CLI::Workflow void ShowAppVersions(Execution::Context& context) { - auto versions = context.Get()->GetAvailableVersionKeys(PinBehavior::IgnorePins); + auto versions = context.Get()->GetAvailableVersionKeys(); Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); for (const auto& version : versions) diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp index 04c42e687d..f049a20094 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -233,9 +233,9 @@ namespace AppInstaller::CLI::Workflow correlatedSources.AddIfRemoteAndNotPresent(package->GetInstalledVersion()); // Then look through all available versions - for (const auto& versionKey : package->GetAvailableVersionKeys(PinBehavior::IgnorePins)) + for (const auto& versionKey : package->GetAvailableVersionKeys()) { - correlatedSources.AddIfRemoteAndNotPresent(package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins)); + correlatedSources.AddIfRemoteAndNotPresent(package->GetAvailableVersion(versionKey)); } // Finally record the uninstall for each found value diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 2b3698045e..3fd3c2288b 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -54,6 +54,7 @@ namespace AppInstaller::CLI::Workflow ManifestComparator manifestComparator(context, isUpgrade ? installedPackage->GetMetadata() : IPackageVersion::Metadata{}); bool versionFound = false; bool installedTypeInapplicable = false; + bool packagePinned = false; if (isUpgrade && installedVersion.IsUnknown() && !context.Args.Contains(Execution::Args::Type::IncludeUnknown)) { @@ -68,17 +69,26 @@ namespace AppInstaller::CLI::Workflow // If we are updating a single package or we got the --include-pinned flag, // we include packages with Pinning pins - const PinBehavior pinBehavior = - (m_isSinglePackage || context.Args.Contains(Execution::Args::Type::IncludePinned)) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + const bool includePinned = m_isSinglePackage || context.Args.Contains(Execution::Args::Type::IncludePinned); // The version keys should have already been sorted by version - const auto& versionKeys = package->GetAvailableVersionKeys(pinBehavior); + const auto& versionKeys = package->GetAvailableVersionKeys(); for (const auto& key : versionKeys) { // Check Applicable Version if (!isUpgrade || IsUpdateVersionApplicable(installedVersion, Utility::Version(key.Version))) { - auto packageVersion = package->GetAvailableVersion(key, PinBehavior::IgnorePins); + // Check if the package is pinned + if (key.PinnedState == Pinning::PinType::Blocking || + key.PinnedState == Pinning::PinType::Gating || + (key.PinnedState == Pinning::PinType::Pinning && !includePinned)) + { + AICLI_LOG(CLI, Info, << "Version [" << key.Version << "] from Source [" << key.SourceId << "] has a Pin with type [" << ToString(key.PinnedState) << "]"); + packagePinned = true; + continue; + } + + auto packageVersion = package->GetAvailableVersion(key); auto manifest = packageVersion->GetManifest(); // Check applicable Installer @@ -131,6 +141,10 @@ namespace AppInstaller::CLI::Workflow { context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnologyInNewerVersions << std::endl; } + else if (packagePinned) + { + context.Reporter.Info() << Resource::String::UpgradeIsPinned << std::endl; + } else if (isUpgrade) { context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl; diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 8205325a17..1bcedfc5ee 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -862,7 +862,11 @@ namespace AppInstaller::CLI::Workflow void GetManifestWithVersionFromPackage::operator()(Execution::Context& context) const { PackageVersionKey key("", m_version, m_channel); - auto requestedVersion = context.Get()->GetAvailableVersion(key, PinBehavior::ConsiderPins); + auto requestedVersionAndPin = context.Get()->GetAvailableVersionAndPin(key); + auto requestedVersion = requestedVersionAndPin.first; + std::ignore = requestedVersionAndPin.second; + + // TODO: Use pin std::optional manifest; if (requestedVersion) diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 581107a887..f38e73fc1a 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1654,4 +1654,7 @@ Please specify one of them using the --source option to proceed. Pin removed successfully + + A newer version was found, but the package has a pin that prevents from updating it. + \ No newline at end of file diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index 288955c86c..cc5c2220da 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -213,7 +213,7 @@ TEST_CASE("CompositeSource_PackageFamilyName_NotAvailable", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).empty()); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().empty()); } TEST_CASE("CompositeSource_PackageFamilyName_Available", "[CompositeSource]") @@ -235,7 +235,7 @@ TEST_CASE("CompositeSource_PackageFamilyName_Available", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); } TEST_CASE("CompositeSource_ProductCode_NotAvailable", "[CompositeSource]") @@ -249,7 +249,7 @@ TEST_CASE("CompositeSource_ProductCode_NotAvailable", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).empty()); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().empty()); } TEST_CASE("CompositeSource_ProductCode_Available", "[CompositeSource]") @@ -271,7 +271,7 @@ TEST_CASE("CompositeSource_ProductCode_Available", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); } TEST_CASE("CompositeSource_NameAndPublisher_Match", "[CompositeSource]") @@ -291,7 +291,7 @@ TEST_CASE("CompositeSource_NameAndPublisher_Match", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); } TEST_CASE("CompositeSource_MultiMatch_FindsStrongMatch", "[CompositeSource]") @@ -312,7 +312,7 @@ TEST_CASE("CompositeSource_MultiMatch_FindsStrongMatch", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Name).get() == name); REQUIRE(!Version(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Version)).IsUnknown()); } @@ -333,7 +333,7 @@ TEST_CASE("CompositeSource_MultiMatch_DoesNotFindStrongMatch", "[CompositeSource REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 0); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 0); } TEST_CASE("CompositeSource_FoundByBothRootSearches", "[CompositeSource]") @@ -368,7 +368,7 @@ TEST_CASE("CompositeSource_FoundByBothRootSearches", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); } TEST_CASE("CompositeSource_OnlyAvailableFoundByRootSearch", "[CompositeSource]") @@ -399,7 +399,7 @@ TEST_CASE("CompositeSource_OnlyAvailableFoundByRootSearch", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); } TEST_CASE("CompositeSource_FoundByAvailableRootSearch_NotInstalled", "[CompositeSource]") @@ -447,7 +447,7 @@ TEST_CASE("CompositeSource_UpdateWithBetterMatchCriteria", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); REQUIRE(result.Matches[0].MatchCriteria.Type == originalType); // Now make the source root search find it with a better criteria @@ -466,7 +466,7 @@ TEST_CASE("CompositeSource_UpdateWithBetterMatchCriteria", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); REQUIRE(result.Matches[0].MatchCriteria.Type == type); } @@ -521,14 +521,14 @@ TEST_CASE("CompositePackage_AvailableVersions_ChannelFilteredOut", "[CompositeSo SearchResult result; result.Matches.emplace_back(TestPackage::Make(std::vector{ noChannel, hasChannel }, setup.Available), Criteria()); - REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 2); + REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys().size() == 2); return result; }; SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - auto versionKeys = result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins); + auto versionKeys = result.Matches[0].Package->GetAvailableVersionKeys(); REQUIRE(versionKeys.size() == 1); REQUIRE(versionKeys[0].Channel.empty()); @@ -557,14 +557,14 @@ TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[Composite SearchResult result; result.Matches.emplace_back(TestPackage::Make(std::vector{ noChannel, hasChannel }, setup.Available), Criteria()); - REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 2); + REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys().size() == 2); return result; }; SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - auto versionKeys = result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins); + auto versionKeys = result.Matches[0].Package->GetAvailableVersionKeys(); REQUIRE(versionKeys.size() == 1); REQUIRE(versionKeys[0].Channel == channel); @@ -609,7 +609,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchAll", "[CompositeSource REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 2); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 2); REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Name).get() == firstName); } @@ -638,7 +638,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchSecond", "[CompositeSou REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion(PinBehavior::IgnorePins)->GetProperty(PackageVersionProperty::Name).get() == secondName); } @@ -668,7 +668,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_ReverseMatchBoth", "[Composi REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins).size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); } TEST_CASE("CompositeSource_IsSame", "[CompositeSource]") @@ -1030,44 +1030,51 @@ TEST_CASE("CompositeSource_NullAvailableVersion", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); } -struct ExpectedResultWithPin +struct ExpectedResultForPinBehavior { - bool IsUpdateAvailable; + ExpectedResultForPinBehavior(bool isUpdateAvailable, std::optional latestAvailableVersion) + : IsUpdateAvailable(isUpdateAvailable), LatestAvailableVersion(latestAvailableVersion) {} + ExpectedResultForPinBehavior() {} + + bool IsUpdateAvailable = false; std::optional LatestAvailableVersion; - std::vector AvailableVersions; - std::vector UnavailableVersions; }; -void RequireExpectedResultsWithPin(std::shared_ptr package, PinBehavior pinBehavior, const ExpectedResultWithPin& expectedResult) +struct ExpectedResultsForPinning { - REQUIRE(package->IsUpdateAvailable(pinBehavior) == expectedResult.IsUpdateAvailable); + std::map ResultsForPinBehavior; + std::vector AvailableVersions; +}; - auto latestAvailable = package->GetLatestAvailableVersion(pinBehavior); - if (expectedResult.LatestAvailableVersion.has_value()) - { - REQUIRE(latestAvailable); - REQUIRE(latestAvailable->GetManifest().Version == expectedResult.LatestAvailableVersion.value()); - } - else +void RequireExpectedResultsWithPin(std::shared_ptr package, const ExpectedResultsForPinning& expectedResult) +{ + for (const auto& entry : expectedResult.ResultsForPinBehavior) { - REQUIRE(!latestAvailable); - } + auto pinBehavior = entry.first; + const auto& result = entry.second; - auto availableVersionKeys = package->GetAvailableVersionKeys(pinBehavior); - REQUIRE(availableVersionKeys.size() == expectedResult.AvailableVersions.size()); - for (size_t i = 0; i < availableVersionKeys.size(); ++i) - { - REQUIRE(availableVersionKeys[i].Version == expectedResult.AvailableVersions[i]); - } + REQUIRE(package->IsUpdateAvailable(pinBehavior) == result.IsUpdateAvailable); - for (const auto& expectedAvailableVersion : expectedResult.AvailableVersions) - { - REQUIRE(package->GetAvailableVersion({ "", expectedAvailableVersion, "" }, pinBehavior)); + auto latestAvailable = package->GetLatestAvailableVersion(pinBehavior); + if (result.LatestAvailableVersion.has_value()) + { + REQUIRE(latestAvailable); + REQUIRE(latestAvailable->GetManifest().Version == result.LatestAvailableVersion.value()); + } + else + { + REQUIRE(!latestAvailable); + } } - for (const auto& expectedUnavailableVersion : expectedResult.UnavailableVersions) + auto availableVersionKeys = package->GetAvailableVersionKeys(); + REQUIRE(availableVersionKeys.size() == expectedResult.AvailableVersions.size()); + for (size_t i = 0; i < availableVersionKeys.size(); ++i) { - REQUIRE(!(package->GetAvailableVersion({ "", expectedUnavailableVersion, "" }, pinBehavior))); + REQUIRE(availableVersionKeys[i].SourceId == expectedResult.AvailableVersions[i].SourceId); + REQUIRE(availableVersionKeys[i].Version == expectedResult.AvailableVersions[i].Version); + REQUIRE(availableVersionKeys[i].PinnedState == expectedResult.AvailableVersions[i].PinnedState); + REQUIRE(package->GetAvailableVersion(expectedResult.AvailableVersions[i])); } } @@ -1102,15 +1109,9 @@ TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") return result; }; + ExpectedResultsForPinning expectedResult; // The result when ignoring pins is always the same - ExpectedResultWithPin expectedResult_ignorePins; - expectedResult_ignorePins.IsUpdateAvailable = true; - expectedResult_ignorePins.LatestAvailableVersion = "1.1.0"; - expectedResult_ignorePins.AvailableVersions = { "1.1.0", "1.0.1", "1.0.0" }; - expectedResult_ignorePins.UnavailableVersions = {}; - - ExpectedResultWithPin expectedResult_includePinned; - ExpectedResultWithPin expectedResult_considerPins; + expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1.0" }; PinKey pinKey("Id", setup.Available->Details.Identifier); auto pinningIndex = PinningIndex::OpenOrCreateDefault(); @@ -1119,56 +1120,69 @@ TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") SECTION("Unpinned") { // If there are no pins, the result should not change if we consider them - expectedResult_considerPins = expectedResult_ignorePins; - expectedResult_includePinned = expectedResult_ignorePins; + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins]; + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins]; + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Unknown }, + { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown }, + { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown }, + }; } SECTION("Pinned") { pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); // Pinning pins are ignored with --include-pinned - expectedResult_includePinned = expectedResult_ignorePins; - - expectedResult_considerPins.IsUpdateAvailable = false; - expectedResult_considerPins.LatestAvailableVersion = {}; - expectedResult_considerPins.AvailableVersions = {}; - expectedResult_considerPins.UnavailableVersions = { "1.1.0", "1.0.1", "1.0.0" }; + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins]; + + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} }; + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Pinning }, + { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Pinning }, + { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Pinning }, + }; } SECTION("Blocked") { pinningIndex->AddPin(Pin::CreateBlockingPin(PinKey{ pinKey })); - - expectedResult_considerPins.IsUpdateAvailable = false; - expectedResult_considerPins.LatestAvailableVersion = {}; - expectedResult_considerPins.AvailableVersions = {}; - expectedResult_considerPins.UnavailableVersions = { "1.1.0", "1.0.1", "1.0.0" }; + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} }; // Blocking pins are not affected by --include-pinned - expectedResult_includePinned = expectedResult_considerPins; + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins]; + + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Blocking }, + { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Blocking }, + { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Blocking }, + }; } SECTION("Gated to 1.*") { pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); - - expectedResult_considerPins.IsUpdateAvailable = true; - expectedResult_considerPins.LatestAvailableVersion = "1.1.0"; - expectedResult_considerPins.AvailableVersions = { "1.1.0", "1.0.1", "1.0.0" }; - expectedResult_considerPins.UnavailableVersions = {}; + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1.0" }; // Gating pins are not affected by --include-pinned - expectedResult_includePinned = expectedResult_considerPins; + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins]; + + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Unknown }, + { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown }, + { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown }, + }; } SECTION("Gated to 1.0.*") { pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.0.*"sv })); - - expectedResult_considerPins.IsUpdateAvailable = false; - expectedResult_considerPins.LatestAvailableVersion = "1.0.1"; - expectedResult_considerPins.AvailableVersions = { "1.0.1", "1.0.0" }; - expectedResult_considerPins.UnavailableVersions = { "1.1.0" }; + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ "1.0.1"}; // Gating pins are not affected by --include-pinned - expectedResult_includePinned = expectedResult_considerPins; + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins]; + + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Gating }, + { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown }, + { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown }, + }; } SearchResult result = setup.Search(); @@ -1176,9 +1190,7 @@ TEST_CASE("CompositeSource_PinnedAvailable", "[CompositeSource][PinFlow]") auto package = result.Matches[0].Package; REQUIRE(package); - RequireExpectedResultsWithPin(package, PinBehavior::IgnorePins, expectedResult_ignorePins); - RequireExpectedResultsWithPin(package, PinBehavior::IncludePinned, expectedResult_includePinned); - RequireExpectedResultsWithPin(package, PinBehavior::ConsiderPins, expectedResult_considerPins); + RequireExpectedResultsWithPin(package, expectedResult); } TEST_CASE("CompositeSource_OneSourcePinned", "[CompositeSource][PinFlow]") @@ -1223,38 +1235,20 @@ TEST_CASE("CompositeSource_OneSourcePinned", "[CompositeSource][PinFlow]") pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); } - PinBehavior pinBehavior = PinBehavior::IgnorePins; - ExpectedResultWithPin expectedResult; - SECTION("Ignore pins") - { - pinBehavior = PinBehavior::IgnorePins; - expectedResult.IsUpdateAvailable = true; - expectedResult.LatestAvailableVersion = "2.0"; - expectedResult.AvailableVersions = { "2.0", "1.1" }; - expectedResult.UnavailableVersions = {}; - } - SECTION("Include pinned") - { - pinBehavior = PinBehavior::IncludePinned; - expectedResult.IsUpdateAvailable = true; - expectedResult.LatestAvailableVersion = "2.0"; - expectedResult.AvailableVersions = { "2.0", "1.1" }; - expectedResult.UnavailableVersions = {}; - } - SECTION("Consider pins") - { - pinBehavior = PinBehavior::ConsiderPins; - expectedResult.IsUpdateAvailable = true; - expectedResult.LatestAvailableVersion = "1.1"; - expectedResult.AvailableVersions = { "1.1" }; - expectedResult.UnavailableVersions = { "2.0" }; - } + ExpectedResultsForPinning expectedResult; + expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1" }; + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "2.0", "", Pinning::PinType::Pinning }, + { "SecondTestSource", "1.1", "", Pinning::PinType::Unknown }, + }; SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); auto package = result.Matches[0].Package; REQUIRE(package); - RequireExpectedResultsWithPin(package, pinBehavior, expectedResult); + RequireExpectedResultsWithPin(package, expectedResult); } TEST_CASE("CompositeSource_OneSourceGated", "[CompositeSource][PinFlow]") @@ -1304,36 +1298,14 @@ TEST_CASE("CompositeSource_OneSourceGated", "[CompositeSource][PinFlow]") pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); } - PinBehavior pinBehavior = PinBehavior::IgnorePins; - ExpectedResultWithPin expectedResult; - SECTION("Ignore pins") - { - pinBehavior = PinBehavior::IgnorePins; - expectedResult.IsUpdateAvailable = true; - expectedResult.LatestAvailableVersion = "2.0"; - expectedResult.AvailableVersions = { "2.0", "1.2", "1.1"}; - expectedResult.UnavailableVersions = {}; - } - SECTION("Include pinned") - { - pinBehavior = PinBehavior::IncludePinned; - expectedResult.IsUpdateAvailable = true; - expectedResult.LatestAvailableVersion = "1.2"; - expectedResult.AvailableVersions = { "1.2", "1.1" }; - expectedResult.UnavailableVersions = { "2.0" }; - } - SECTION("Consider pins") - { - pinBehavior = PinBehavior::ConsiderPins; - expectedResult.IsUpdateAvailable = true; - expectedResult.LatestAvailableVersion = "1.2"; - expectedResult.AvailableVersions = { "1.2", "1.1"}; - expectedResult.UnavailableVersions = { "2.0" }; - } + ExpectedResultsForPinning expectedResult; + expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.2" }; + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.2" }; SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); auto package = result.Matches[0].Package; REQUIRE(package); - RequireExpectedResultsWithPin(package, pinBehavior, expectedResult); + RequireExpectedResultsWithPin(package, expectedResult); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/SQLiteIndexSource.cpp b/src/AppInstallerCLITests/SQLiteIndexSource.cpp index 79b5b99200..3c7dda48bd 100644 --- a/src/AppInstallerCLITests/SQLiteIndexSource.cpp +++ b/src/AppInstallerCLITests/SQLiteIndexSource.cpp @@ -129,7 +129,7 @@ TEST_CASE("SQLiteIndexSource_Versions", "[sqliteindexsource]") REQUIRE(results.Matches.size() == 1); REQUIRE(results.Matches[0].Package); - auto result = results.Matches[0].Package->GetAvailableVersionKeys(PinBehavior::IgnorePins); + auto result = results.Matches[0].Package->GetAvailableVersionKeys(); REQUIRE(result.size() == 1); REQUIRE(result[0].Version == manifest.Version); REQUIRE(result[0].Channel == manifest.Channel); @@ -153,7 +153,7 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") REQUIRE(results.Matches[0].Package); auto package = results.Matches[0].Package.get(); - auto specificResultVersion = package->GetAvailableVersion(PackageVersionKey("", manifest.Version, manifest.Channel), PinBehavior::IgnorePins); + auto specificResultVersion = package->GetAvailableVersion(PackageVersionKey("", manifest.Version, manifest.Channel)); REQUIRE(specificResultVersion); auto specificResult = specificResultVersion->GetManifest(); REQUIRE(specificResult.Id == manifest.Id); @@ -161,7 +161,7 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") REQUIRE(specificResult.Version == manifest.Version); REQUIRE(specificResult.Channel == manifest.Channel); - auto latestResultVersion = package->GetAvailableVersion(PackageVersionKey("", "", manifest.Channel), PinBehavior::IgnorePins); + auto latestResultVersion = package->GetAvailableVersion(PackageVersionKey("", "", manifest.Channel)); REQUIRE(latestResultVersion); auto latestResult = latestResultVersion->GetManifest(); REQUIRE(latestResult.Id == manifest.Id); @@ -169,7 +169,7 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") REQUIRE(latestResult.Version == manifest.Version); REQUIRE(latestResult.Channel == manifest.Channel); - auto noResultVersion = package->GetAvailableVersion(PackageVersionKey("", "blargle", "flargle"), PinBehavior::IgnorePins); + auto noResultVersion = package->GetAvailableVersion(PackageVersionKey("", "blargle", "flargle")); REQUIRE(!noResultVersion); } diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp index 6413431894..8aec0e4367 100644 --- a/src/AppInstallerCLITests/TestSource.cpp +++ b/src/AppInstallerCLITests/TestSource.cpp @@ -177,7 +177,7 @@ namespace TestCommon return InstalledVersion; } - std::vector TestPackage::GetAvailableVersionKeys(PinBehavior) const + std::vector TestPackage::GetAvailableVersionKeys() const { std::vector result; for (const auto& version : AvailableVersions) @@ -197,7 +197,7 @@ namespace TestCommon return AvailableVersions[0]; } - std::shared_ptr TestPackage::GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior) const + std::shared_ptr TestPackage::GetAvailableVersion(const PackageVersionKey& versionKey) const { for (const auto& version : AvailableVersions) { diff --git a/src/AppInstallerCLITests/TestSource.h b/src/AppInstallerCLITests/TestSource.h index 8e1bd57d02..af6a4fc9dd 100644 --- a/src/AppInstallerCLITests/TestSource.h +++ b/src/AppInstallerCLITests/TestSource.h @@ -63,9 +63,9 @@ namespace TestCommon AppInstaller::Utility::LocIndString GetProperty(AppInstaller::Repository::PackageProperty property) const override; std::shared_ptr GetInstalledVersion() const override; - std::vector GetAvailableVersionKeys(AppInstaller::Repository::PinBehavior) const override; + std::vector GetAvailableVersionKeys() const override; std::shared_ptr GetLatestAvailableVersion(AppInstaller::Repository::PinBehavior) const override; - std::shared_ptr GetAvailableVersion(const AppInstaller::Repository::PackageVersionKey& versionKey, AppInstaller::Repository::PinBehavior) const override; + std::shared_ptr GetAvailableVersion(const AppInstaller::Repository::PackageVersionKey& versionKey) const override; bool IsUpdateAvailable(AppInstaller::Repository::PinBehavior) const override; bool IsSame(const IPackage* other) const override; diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index bfd9e92e69..392b834fbb 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -30,6 +30,39 @@ namespace AppInstaller::Repository }; } + std::optional GetLatestAvailableVersionKeySatisfyingPin(const std::vector& availableVersionKeys, PinBehavior pinBehavior) + { + if (availableVersionKeys.empty()) + { + return {}; + } + + std::optional pvk; + if (pinBehavior == PinBehavior::IgnorePins) + { + pvk = availableVersionKeys.front(); + } + else + { + // Skip until we find a version that isn't pinned + for (const auto& availableVersion : availableVersionKeys) + { + if (availableVersion.PinnedState == Pinning::PinType::Blocking || + availableVersion.PinnedState == Pinning::PinType::Gating || + (availableVersion.PinnedState == Pinning::PinType::Pinning && pinBehavior != PinBehavior::IncludePinned)) + { + continue; + } + + pvk = availableVersion; + break; + } + } + + return pvk; + } + + // Returns true for fields that provide a strong match; one that is not based on a heuristic. bool IsStrongMatchField(PackageMatchField field) { @@ -106,9 +139,9 @@ namespace AppInstaller::Repository std::chrono::system_clock::time_point resultTime{}; std::shared_ptr resultVersion; - for (const auto& key : trackingPackage->GetAvailableVersionKeys(PinBehavior::IgnorePins)) + for (const auto& key : trackingPackage->GetAvailableVersionKeys()) { - auto version = trackingPackage->GetAvailableVersion(key, PinBehavior::IgnorePins); + auto version = trackingPackage->GetAvailableVersion(key); if (version) { auto metadata = version->GetMetadata(); @@ -136,18 +169,22 @@ namespace AppInstaller::Repository return { resultTime, std::move(resultVersion) }; } + // An installed package's version reported in ARP does not necessarily match the versions used for the manifest. + // This function uses the data in the manifest to map the installed version string to the version used by the manifest. + // // TODO: Note: Currently this function assumes the all versions in the available package is from one source. - // If one day we start adding support for available package from multiple sources, this function needs to be revisited. + // Even though a composite package can have available packages from multiple sources, we only call this function + // for the default (first) available package. If we ever need to consider other sources, this function needs to be revisited. std::string GetMappedInstalledVersion(const std::string& installedVersion, const std::shared_ptr& availablePackage) { // Stores raw versions value strings to run a preliminary check whether version mapping is needed. std::vector> rawVersionValues; - auto versionKeys = availablePackage->GetAvailableVersionKeys(PinBehavior::IgnorePins); + auto versionKeys = availablePackage->GetAvailableVersionKeys(); bool shouldTryPerformMapping = false; for (auto const& versionKey : versionKeys) { - auto availableVersion = availablePackage->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); + auto availableVersion = availablePackage->GetAvailableVersion(versionKey); std::string arpMinVersion = availableVersion->GetProperty(PackageVersionProperty::ArpMinVersion); std::string arpMaxVersion = availableVersion->GetProperty(PackageVersionProperty::ArpMaxVersion); @@ -333,16 +370,18 @@ namespace AppInstaller::Repository std::shared_ptr m_trackingPackageVersion; }; - struct CompositeAvailablePackage + // Wrapper around an available package to add pinning functionality for composite packages. + // Most of the methods are only here for completeness of the interface and are not actually used. + struct CompositeAvailablePackage : public IPackage { CompositeAvailablePackage() {} CompositeAvailablePackage(std::shared_ptr availablePackage, std::optional pin = {}) : AvailablePackage(availablePackage), Pin(pin) { - auto lastAvailable = AvailablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins); - if (lastAvailable) + auto latestAvailable = AvailablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins); + if (latestAvailable) { - SourceId = lastAvailable->GetSource().GetIdentifier(); + SourceId = latestAvailable->GetSource().GetIdentifier(); } } @@ -350,63 +389,86 @@ namespace AppInstaller::Repository std::shared_ptr AvailablePackage; std::optional Pin; - std::vector GetAvailableVersionKeys(PinBehavior pinBehavior) const + Utility::LocIndString GetProperty(PackageProperty property) const override + { + return AvailablePackage->GetProperty(property); + } + + std::shared_ptr GetInstalledVersion() const override + { + return {}; + } + + std::vector GetAvailableVersionKeys() const override { - if (Pin.has_value() && pinBehavior != PinBehavior::IgnorePins && ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + auto result = AvailablePackage->GetAvailableVersionKeys(); + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && Pin.has_value()) { - if (Pin->GetType() == Pinning::PinType::Blocking) + for (auto& pvk : result) { - AICLI_LOG(Repo, Info, << "Ignoring available versions from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] due to Blocking pin"); - return {}; - } - else if (Pin->GetType() == Pinning::PinType::Pinning) - { - if (pinBehavior != PinBehavior::IncludePinned) + if (Pin->GetType() == Pinning::PinType::Blocking || + Pin->GetType() == Pinning::PinType::Pinning || + (Pin->GetType() == Pinning::PinType::Gating && !Pin->GetGatedVersion().IsValidVersion(pvk.Version))) { - AICLI_LOG(Repo, Info, << "Ignoring available versions from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] due to Pinning pin"); - return {}; + pvk.PinnedState = Pin->GetType(); } } - else if (Pin->GetType() == Pinning::PinType::Gating) - { - auto versionKeys = AvailablePackage->GetAvailableVersionKeys(PinBehavior::IgnorePins); - std::vector result; - std::copy_if(versionKeys.begin(), versionKeys.end(), std::back_inserter(result), [&](const PackageVersionKey& pvk) { return Pin->GetGatedVersion().IsValidVersion(pvk.Version); }); - return result; - } } - return AvailablePackage->GetAvailableVersionKeys(PinBehavior::IgnorePins); + return result; + } + + std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const override + { + return GetAvailableVersionAndPin(versionKey).first; + } + + std::shared_ptr GetLatestAvailableVersion(PinBehavior pinBehavior) const override + { + auto availableVersionKeys = GetAvailableVersionKeys(); + auto latestVersionKey = GetLatestAvailableVersionKeySatisfyingPin(availableVersionKeys, pinBehavior); + if (!latestVersionKey) + { + return {}; + } + + return GetAvailableVersion(latestVersionKey.value()); } - std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior pinBehavior) const + virtual std::pair, Pinning::PinType> GetAvailableVersionAndPin(const PackageVersionKey& versionKey) const override { - if (Pin.has_value() && pinBehavior != PinBehavior::IgnorePins && ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + Pinning::PinType pinType = Pinning::PinType::Unknown; + + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && Pin.has_value()) { - if (Pin->GetType() == Pinning::PinType::Blocking) + // A gating pin behaves the same as no pin when the version fits the gated version + if (!(pinType == Pinning::PinType::Gating && Pin->GetGatedVersion().IsValidVersion(versionKey.Version))) { - AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] due to Blocking pin"); - return {}; - } - else if (Pin->GetType() == Pinning::PinType::Pinning) - { - if (pinBehavior != PinBehavior::IncludePinned) - { - AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] due to Pinning pin"); - return {}; - } - } - else if (Pin->GetType() == Pinning::PinType::Gating) - { - if (!Pin->GetGatedVersion().IsValidVersion(versionKey.Version)) - { - AICLI_LOG(Repo, Info, << "Ignoring available version from source [" << Pin->GetSourceId() << "] for package [" << Pin->GetPackageId() << "] as it does not satisfy Gating pin"); - return {}; - } + pinType = Pin->GetType(); } } - return AvailablePackage->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); + return { AvailablePackage->GetAvailableVersion(versionKey), pinType }; + } + + bool IsUpdateAvailable(PinBehavior) const override + { + return false; + } + + bool IsSame(const IPackage* other) const override + { + const CompositeAvailablePackage* otherAvailable = dynamic_cast(other); + + if (otherAvailable) + { + return + SourceId == otherAvailable->SourceId && + Pin == otherAvailable->Pin && + AvailablePackage->IsSame(otherAvailable->AvailablePackage.get()); + } + + return false; } }; @@ -466,13 +528,13 @@ namespace AppInstaller::Repository return {}; } - std::vector GetAvailableVersionKeys(PinBehavior pinBehavior) const override + std::vector GetAvailableVersionKeys() const override { std::vector result; for (const auto& availablePackage : m_availablePackages) { - auto versionKeys = availablePackage.GetAvailableVersionKeys(pinBehavior); + auto versionKeys = availablePackage.GetAvailableVersionKeys(); std::copy(versionKeys.begin(), versionKeys.end(), std::back_inserter(result)); } @@ -490,16 +552,22 @@ namespace AppInstaller::Repository std::shared_ptr GetLatestAvailableVersion(PinBehavior pinBehavior) const override { - auto availableVersionKeys = GetAvailableVersionKeys(pinBehavior); - if (availableVersionKeys.empty()) + auto availableVersionKeys = GetAvailableVersionKeys(); + auto latestVersionKey = GetLatestAvailableVersionKeySatisfyingPin(availableVersionKeys, pinBehavior); + if (!latestVersionKey) { return {}; } - return GetAvailableVersion(availableVersionKeys.front(), PinBehavior::IgnorePins); + return GetAvailableVersion(latestVersionKey.value()); } - std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior pinBehavior) const override + std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const override + { + return GetAvailableVersionAndPin(versionKey).first; + } + + std::pair, Pinning::PinType> GetAvailableVersionAndPin(const PackageVersionKey& versionKey) const override { for (const auto& availablePackage : m_availablePackages) { @@ -508,10 +576,10 @@ namespace AppInstaller::Repository continue; } - auto package = availablePackage.GetAvailableVersion(versionKey, pinBehavior); - if (package) + auto result = availablePackage.GetAvailableVersionAndPin(versionKey); + if (result.first) { - return package; + return result; } } @@ -586,10 +654,11 @@ namespace AppInstaller::Repository { if (availablePackage) { - if (m_availablePackages.empty()) + if (!m_defaultAvailablePackage) { // Set override only with the first available version found - TrySetOverrideInstalledVersion(availablePackage); + m_defaultAvailablePackage = availablePackage; + TrySetOverrideInstalledVersion(m_defaultAvailablePackage); } m_availablePackages.emplace_back(std::move(availablePackage)); @@ -627,6 +696,7 @@ namespace AppInstaller::Repository } private: + // Try to set a version that will override the version string from the installed package void TrySetOverrideInstalledVersion(std::shared_ptr availablePackage) { if (m_installedPackage && availablePackage) @@ -649,6 +719,7 @@ namespace AppInstaller::Repository std::shared_ptr m_trackingPackage; std::shared_ptr m_trackingPackageVersion; std::string m_overrideInstalledVersion; + std::shared_ptr m_defaultAvailablePackage; std::vector m_availablePackages; }; @@ -792,9 +863,9 @@ namespace AppInstaller::Repository } PackageData result; - for (auto const& versionKey : availableMatch.Package->GetAvailableVersionKeys(PinBehavior::IgnorePins)) + for (auto const& versionKey : availableMatch.Package->GetAvailableVersionKeys()) { - auto packageVersion = availableMatch.Package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); + auto packageVersion = availableMatch.Package->GetAvailableVersion(versionKey); AddSystemReferenceStrings(packageVersion.get(), result); } return result; @@ -820,9 +891,9 @@ namespace AppInstaller::Repository } PackageData result; - for (auto const& versionKey : trackingMatch.Package->GetAvailableVersionKeys(PinBehavior::IgnorePins)) + for (auto const& versionKey : trackingMatch.Package->GetAvailableVersionKeys()) { - auto packageVersion = trackingMatch.Package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); + auto packageVersion = trackingMatch.Package->GetAvailableVersion(versionKey); AddSystemReferenceStrings(packageVersion.get(), result); } return result; diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp index cd49643664..58b737c851 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp @@ -239,7 +239,7 @@ namespace AppInstaller::Repository::Microsoft return {}; } - std::vector GetAvailableVersionKeys(PinBehavior) const override + std::vector GetAvailableVersionKeys() const override { std::shared_ptr source = GetReferenceSource(); std::vector versions = source->GetIndex().GetVersionKeysById(m_idId); @@ -257,7 +257,7 @@ namespace AppInstaller::Repository::Microsoft return GetLatestVersionInternal(); } - std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior) const override + std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const override { std::shared_ptr source = GetReferenceSource(); @@ -311,7 +311,7 @@ namespace AppInstaller::Repository::Microsoft return GetLatestVersionInternal(); } - std::vector GetAvailableVersionKeys(PinBehavior) const override + std::vector GetAvailableVersionKeys() const override { return {}; } @@ -321,7 +321,7 @@ namespace AppInstaller::Repository::Microsoft return {}; } - std::shared_ptr GetAvailableVersion(const PackageVersionKey&, PinBehavior) const override + std::shared_ptr GetAvailableVersion(const PackageVersionKey&) const override { return {}; } diff --git a/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp b/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp index d7a944392e..bc242075c3 100644 --- a/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp +++ b/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp @@ -120,14 +120,14 @@ namespace AppInstaller::Repository { // Use the base version as available version if installed version is mapped to be an approximate. versionKey.Version = installedVersionAsVersion.GetBaseVersion().ToString(); - availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); + availableVersion = package->GetAvailableVersion(versionKey); // It's unexpected if the installed version is already mapped to some version. THROW_HR_IF(E_UNEXPECTED, !availableVersion); } else { versionKey.Version = installedVersionAsVersion.ToString(); - availableVersion = package->GetAvailableVersion(versionKey, PinBehavior::IgnorePins); + availableVersion = package->GetAvailableVersion(versionKey); if (availableVersion) { checkFileHash = true; diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h index f2a8191712..574a1834c6 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h +++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -222,8 +223,9 @@ namespace AppInstaller::Repository { PackageVersionKey() = default; - PackageVersionKey(Utility::NormalizedString sourceId, Utility::NormalizedString version, Utility::NormalizedString channel) : - SourceId(std::move(sourceId)), Version(std::move(version)), Channel(std::move(channel)) {} + // TODO #219 check all uses + PackageVersionKey(Utility::NormalizedString sourceId, Utility::NormalizedString version, Utility::NormalizedString channel, Pinning::PinType pinnedState = Pinning::PinType::Unknown) : + SourceId(std::move(sourceId)), Version(std::move(version)), Channel(std::move(channel)), PinnedState(pinnedState) {} // The source id that this version came from. std::string SourceId; @@ -234,6 +236,10 @@ namespace AppInstaller::Repository // The channel. Utility::NormalizedString Channel; + // The pin state for this package version, if it came from a list of available versions. + // When used to look up a package version, this field is not considered. + Pinning::PinType PinnedState = Pinning::PinType::Unknown; + bool operator<(const PackageVersionKey& other) const { // Sort using only the version and channel. @@ -323,18 +329,25 @@ namespace AppInstaller::Repository // Note on pins: // Pins only make sense when there is both an installed and an available version. + // Only for the composite source will GetAvailableVersionKeys() include pinned state, + // and GetLatestAvailableVersion() consider the pin behavior. // Gets all available versions of this package. // The versions will be returned in sorted, descending order. // Ex. { 4, 3, 2, 1 } // The list may contain versions from multiple sources. - virtual std::vector GetAvailableVersionKeys(PinBehavior pinBehavior) const = 0; + virtual std::vector GetAvailableVersionKeys() const = 0; // Gets a specific version of this package. virtual std::shared_ptr GetLatestAvailableVersion(PinBehavior pinBehavior) const = 0; // Gets a specific version of this package. - virtual std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior pinBehavior) const = 0; + virtual std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const = 0; + + virtual std::pair, Pinning::PinType> GetAvailableVersionAndPin(const PackageVersionKey& versionKey) const + { + return { GetAvailableVersion(versionKey), Pinning::PinType::Unknown }; + } // Gets a value indicating whether an available version is newer than the installed version. virtual bool IsUpdateAvailable(PinBehavior pinBehavior) const = 0; diff --git a/src/AppInstallerRepositoryCore/Rest/RestSource.cpp b/src/AppInstallerRepositoryCore/Rest/RestSource.cpp index 042c36f7c3..2578b487fc 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestSource.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestSource.cpp @@ -57,7 +57,7 @@ namespace AppInstaller::Repository::Rest return {}; } - std::vector GetAvailableVersionKeys(PinBehavior) const override + std::vector GetAvailableVersionKeys() const override { std::shared_ptr source = GetReferenceSource(); std::scoped_lock versionsLock{ m_packageVersionsLock }; @@ -78,7 +78,7 @@ namespace AppInstaller::Repository::Rest return GetLatestVersionInternal(); } - std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior) const override; + std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const override; bool IsUpdateAvailable(PinBehavior) const override { @@ -356,7 +356,7 @@ namespace AppInstaller::Repository::Rest IRestClient::VersionInfo m_versionInfo; }; - std::shared_ptr AvailablePackage::GetAvailableVersion(const PackageVersionKey& versionKey, PinBehavior) const + std::shared_ptr AvailablePackage::GetAvailableVersion(const PackageVersionKey& versionKey) const { std::shared_ptr source = GetReferenceSource(); std::scoped_lock versionsLock{ m_packageVersionsLock }; diff --git a/src/Microsoft.Management.Deployment/CatalogPackage.cpp b/src/Microsoft.Management.Deployment/CatalogPackage.cpp index edc7cc3e15..8ae39a78d5 100644 --- a/src/Microsoft.Management.Deployment/CatalogPackage.cpp +++ b/src/Microsoft.Management.Deployment/CatalogPackage.cpp @@ -51,7 +51,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation [&]() { // Vector hasn't been populated yet. - for (auto const& versionKey : m_package.get()->GetAvailableVersionKeys(AppInstaller::Repository::PinBehavior::IgnorePins)) + for (auto const& versionKey : m_package.get()->GetAvailableVersionKeys()) { auto packageVersionId = winrt::make_self>(); @@ -84,7 +84,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; ::AppInstaller::Repository::PackageVersionKey internalVersionKey(winrt::to_string(versionKey.PackageCatalogId()), winrt::to_string(versionKey.Version()), winrt::to_string(versionKey.Channel())); - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> availableVersion = m_package.get()->GetAvailableVersion(internalVersionKey, AppInstaller::Repository::PinBehavior::IgnorePins); + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> availableVersion = m_package.get()->GetAvailableVersion(internalVersionKey); if (availableVersion) { auto packageVersionInfoImpl = winrt::make_self Date: Wed, 25 Jan 2023 13:12:29 -0800 Subject: [PATCH 40/55] Add more reporting around pinned packages --- .../Commands/ShowCommand.cpp | 2 +- src/AppInstallerCLICore/Resources.h | 2 + .../Workflows/UpdateFlow.cpp | 6 +- .../Workflows/WorkflowBase.cpp | 81 +++++++++++++++++-- .../Workflows/WorkflowBase.h | 36 ++++++--- .../Shared/Strings/en-us/winget.resw | 8 ++ src/AppInstallerCommonCore/Errors.cpp | 4 +- .../Public/AppInstallerErrors.h | 2 +- 8 files changed, 116 insertions(+), 25 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.cpp b/src/AppInstallerCLICore/Commands/ShowCommand.cpp index 981dd465b7..0e5266cd78 100644 --- a/src/AppInstallerCLICore/Commands/ShowCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ShowCommand.cpp @@ -99,7 +99,7 @@ namespace AppInstaller::CLI else { context << - Workflow::GetManifest << + Workflow::GetManifest(/* considerPins */ false) << Workflow::ReportManifestIdentity << Workflow::SelectInstaller << Workflow::ShowManifestInfo; diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 524d03e556..a596057f14 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -242,6 +242,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsPrompt); WINGET_DEFINE_RESOURCE_STRINGID(PackageAlreadyInstalled); WINGET_DEFINE_RESOURCE_STRINGID(PackageDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(PackageIsPinned); WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError); WINGET_DEFINE_RESOURCE_STRINGID(PinAddBlockingArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandLongDescription); @@ -441,6 +442,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUserPinnedCount); WINGET_DEFINE_RESOURCE_STRINGID(Usage); WINGET_DEFINE_RESOURCE_STRINGID(UserSettings); WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandLongDescription); diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 3fd3c2288b..e7ac620691 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -286,16 +286,14 @@ namespace AppInstaller::CLI::Workflow if (context.Args.Contains(Execution::Args::Type::Version)) { // If version specified, use the version and verify applicability - context << - GetManifestFromPackage; + context << GetManifestFromPackage(/* considerPins */ true); if (m_isUpgrade) { context << EnsureUpdateVersionApplicable; } - context << - SelectInstaller; + context << SelectInstaller; } else { diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 1bcedfc5ee..97540999b7 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -6,6 +6,7 @@ #include "ManifestComparator.h" #include "PromptFlow.h" #include "TableOutput.h" +#include #include #include @@ -13,6 +14,7 @@ using namespace std::string_literals; using namespace AppInstaller::Utility::literals; using namespace AppInstaller::Pinning; using namespace AppInstaller::Repository; +using namespace AppInstaller::Settings; namespace AppInstaller::CLI::Workflow { @@ -706,6 +708,7 @@ namespace AppInstaller::CLI::Workflow int availableUpgradesCount = 0; int packagesWithUnknownVersionSkipped = 0; + int packagesWithUserPinsSkipped = 0; auto &source = context.Get(); bool shouldShowSource = source.IsComposite() && source.GetAvailableSources().size() > 1; @@ -726,6 +729,16 @@ namespace AppInstaller::CLI::Workflow continue; } + if (m_onlyShowUpgrades && !updateAvailable && ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + { + bool updateAvailableWithoutPins = match.Package->IsUpdateAvailable(PinBehavior::IgnorePins); + if (updateAvailableWithoutPins) + { + ++packagesWithUserPinsSkipped; + continue; + } + } + // The only time we don't want to output a line is when filtering and no update is available. if (updateAvailable || !m_onlyShowUpgrades) { @@ -799,6 +812,12 @@ namespace AppInstaller::CLI::Workflow AICLI_LOG(CLI, Info, << packagesWithUnknownVersionSkipped << " package(s) skipped due to unknown installed version"); context.Reporter.Info() << Resource::String::UpgradeUnknownVersionCount(packagesWithUnknownVersionSkipped) << std::endl; } + + if (packagesWithUserPinsSkipped > 0) + { + AICLI_LOG(CLI, Info, << packagesWithUserPinsSkipped << " package(s) skipped due to user pins"); + context.Reporter.Info() << Resource::String::UpgradeUserPinnedCount(packagesWithUserPinsSkipped) << std::endl; + } } } @@ -862,11 +881,54 @@ namespace AppInstaller::CLI::Workflow void GetManifestWithVersionFromPackage::operator()(Execution::Context& context) const { PackageVersionKey key("", m_version, m_channel); - auto requestedVersionAndPin = context.Get()->GetAvailableVersionAndPin(key); - auto requestedVersion = requestedVersionAndPin.first; - std::ignore = requestedVersionAndPin.second; - // TODO: Use pin + std::shared_ptr package = context.Get(); + std::shared_ptr requestedVersion; + + if (m_considerPins && ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + { + bool isPinned = false; + + // TODO: The logic here will probably have to get more difficult once we support channels + if (Utility::IsEmptyOrWhitespace(m_version) && Utility::IsEmptyOrWhitespace(m_channel)) + { + PinBehavior pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + requestedVersion = package->GetLatestAvailableVersion(pinBehavior); + + if (!requestedVersion) + { + // Check whether we didn't find the latest version because it was pinned or because there wasn't one + auto latestVersion = package->GetLatestAvailableVersion(PinBehavior::IgnorePins); + if (latestVersion) + { + isPinned = true; + } + } + } + else + { + auto requestedVersionAndPin = package->GetAvailableVersionAndPin(key); + requestedVersion = requestedVersionAndPin.first; + auto pin = requestedVersionAndPin.second; + + isPinned = + pin == Pinning::PinType::Blocking || + pin == Pinning::PinType::Gating || + (pin == Pinning::PinType::Pinning && !context.Args.Contains(Execution::Args::Type::IncludePinned)); + } + + if (isPinned) + { + AICLI_LOG(CLI, Error, << "The requested package version is unavailable because of a pin"); + context.Reporter.Error() << Resource::String::PackageIsPinned << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED); + } + } + else + { + // The simple case: Just look up the requested version + requestedVersion = package->GetAvailableVersion(key); + } std::optional manifest; if (requestedVersion) @@ -903,9 +965,12 @@ namespace AppInstaller::CLI::Workflow context.Add(std::move(requestedVersion)); } - void GetManifestFromPackage(Execution::Context& context) + void GetManifestFromPackage::operator()(Execution::Context& context) const { - context << GetManifestWithVersionFromPackage(context.Args.GetArg(Execution::Args::Type::Version), context.Args.GetArg(Execution::Args::Type::Channel)); + context << GetManifestWithVersionFromPackage( + context.Args.GetArg(Execution::Args::Type::Version), + context.Args.GetArg(Execution::Args::Type::Channel), + m_considerPins); } void VerifyFile::operator()(Execution::Context& context) const @@ -976,7 +1041,7 @@ namespace AppInstaller::CLI::Workflow ReportIdentity(context, m_prefix, m_label, manifest.CurrentLocalization.Get(), manifest.Id, manifest.Version, m_level); } - void GetManifest(Execution::Context& context) + void GetManifest::operator()(Execution::Context& context) const { if (context.Args.Contains(Execution::Args::Type::Manifest)) { @@ -990,7 +1055,7 @@ namespace AppInstaller::CLI::Workflow SearchSourceForSingle << HandleSearchResultFailures << EnsureOneMatchFromSearchResult(false) << - GetManifestFromPackage; + GetManifestFromPackage(m_considerPins); } } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index 0c7f9f2d5b..35357d7e49 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -235,29 +235,38 @@ namespace AppInstaller::CLI::Workflow }; // Gets the manifest from package. - // Required Args: Version and channel; can be empty + // Required Args: Version and channel; can be empty. A flag indicating whether to consider package pins // Inputs: Package // Outputs: Manifest, PackageVersion struct GetManifestWithVersionFromPackage : public WorkflowTask { - GetManifestWithVersionFromPackage(const Utility::VersionAndChannel& versionAndChannel) : - WorkflowTask("GetManifestWithVersionFromPackage"), m_version(versionAndChannel.GetVersion().ToString()), m_channel(versionAndChannel.GetChannel().ToString()) {} + GetManifestWithVersionFromPackage(std::string_view version, std::string_view channel, bool considerPins) : + WorkflowTask("GetManifestWithVersionFromPackage"), m_version(version), m_channel(channel), m_considerPins(considerPins) {} - GetManifestWithVersionFromPackage(std::string_view version, std::string_view channel) : - WorkflowTask("GetManifestWithVersionFromPackage"), m_version(version), m_channel(channel) {} + GetManifestWithVersionFromPackage(const Utility::VersionAndChannel& versionAndChannel, bool considerPins) : + GetManifestWithVersionFromPackage(versionAndChannel.GetVersion().ToString(), versionAndChannel.GetChannel().ToString(), considerPins) {} void operator()(Execution::Context& context) const override; private: std::string_view m_version; std::string_view m_channel; + bool m_considerPins; }; // Gets the manifest from package. - // Required Args: None - // Inputs: Package + // Required Args: A value indicating whether to consider pins + // Inputs: Package. Optionally Version and Channel // Outputs: Manifest, PackageVersion - void GetManifestFromPackage(Execution::Context& context); + struct GetManifestFromPackage : public WorkflowTask + { + GetManifestFromPackage(bool considerPins) : WorkflowTask("GetManifestFromPackage"), m_considerPins(considerPins) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_considerPins; + }; // Ensures the file exists and is not a directory. // Required Args: the one given @@ -328,7 +337,16 @@ namespace AppInstaller::CLI::Workflow // Required Args: None // Inputs: None // Outputs: Manifest - void GetManifest(Execution::Context& context); + struct GetManifest : public WorkflowTask + { + GetManifest(bool considerPins) : WorkflowTask("GetManifest"), m_considerPins(considerPins) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_considerPins; + }; + // Selects the installer from the manifest, if one is applicable. // Required Args: None diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index f38e73fc1a..6019f03cd0 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1657,4 +1657,12 @@ Please specify one of them using the --source option to proceed. A newer version was found, but the package has a pin that prevents from updating it. + + The package is pinned and cannot be updated. Use the 'winget pin' command to view and edit pins. Some pin types can be bypassed with the --include-pinned argument. + {Locked="winget pin","--include-pinned"} Error shown when we block an update due to the package being pinned + + + {0} package(s) have pins that prevent upgrade. Use the 'winget pin' command to view and edit pins. Using the --include-pinned argument may show more results. + {Locked="winget pin","--include-pinned"} {0} is a placeholder replaced by an integer number of packages + \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp index 5d9a728e3c..675d25bc34 100644 --- a/src/AppInstallerCommonCore/Errors.cpp +++ b/src/AppInstallerCommonCore/Errors.cpp @@ -214,8 +214,8 @@ namespace AppInstaller return "There is no pin for the package."; case APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX: return "Unable to open the pin database."; - case APPINSTALLER_CLI_ERROR_PACKAGE_HAS_BLOCKING_PIN: - return "The package has a blocking pin that prevents upgrade."; + case APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED: + return "The package has a pin that prevents upgrade."; // Install errors case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: diff --git a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h index 904029ab50..8562432c06 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h @@ -113,7 +113,7 @@ #define APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS ((HRESULT)0x8A150062) #define APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST ((HRESULT)0x8A150063) #define APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX ((HRESULT)0x8A150064) -#define APPINSTALLER_CLI_ERROR_PACKAGE_HAS_BLOCKING_PIN ((HRESULT)0x8A150065) +#define APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED ((HRESULT)0x8A150065) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) From 9c5d03ff9d747666cdba6c717e2d4ab3862783b3 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Wed, 25 Jan 2023 13:43:14 -0800 Subject: [PATCH 41/55] Show latest available for upgrade --- src/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 9f0b64ab20..531edf85dd 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -712,7 +712,18 @@ namespace AppInstaller::CLI::Workflow auto &source = context.Get(); bool shouldShowSource = source.IsComposite() && source.GetAvailableSources().size() > 1; - PinBehavior pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + PinBehavior pinBehavior; + if (m_onlyShowUpgrades) + { + // For listing upgrades, show the version we would upgrade to with the given pins. + pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + } + else + { + // For listing installed apps, show the latest available. + pinBehavior = PinBehavior::IgnorePins; + } + for (const auto& match : searchResult.Matches) { auto installedVersion = match.Package->GetInstalledVersion(); From a7c449389626d9a7a1beca7507e7667585432794 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Fri, 27 Jan 2023 16:45:40 -0800 Subject: [PATCH 42/55] Add missing expected value --- src/AppInstallerCLITests/CompositeSource.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index cc5c2220da..9cc177706f 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -1302,6 +1302,11 @@ TEST_CASE("CompositeSource_OneSourceGated", "[CompositeSource][PinFlow]") expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.2" }; expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.2" }; + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "2.0", "", Pinning::PinType::Gating }, + { "AvailableTestSource1", "1.2", "", Pinning::PinType::Unknown }, + { "SecondTestSource", "1.1", "", Pinning::PinType::Unknown }, + }; SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); From 2e656e87d26aadac17a5ed8f96f580ac9571fee6 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Fri, 27 Jan 2023 16:57:06 -0800 Subject: [PATCH 43/55] Support --force argument --- .../Workflows/UpdateFlow.cpp | 11 +++++-- .../Workflows/WorkflowBase.cpp | 29 +++++++++++++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index e7ac620691..fadab367b7 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -84,8 +84,15 @@ namespace AppInstaller::CLI::Workflow (key.PinnedState == Pinning::PinType::Pinning && !includePinned)) { AICLI_LOG(CLI, Info, << "Version [" << key.Version << "] from Source [" << key.SourceId << "] has a Pin with type [" << ToString(key.PinnedState) << "]"); - packagePinned = true; - continue; + if (context.Args.Contains(Execution::Args::Type::Force)) + { + AICLI_LOG(CLI, Info, << "Ignoring pin due to --force argument"); + } + else + { + packagePinned = true; + continue; + } } auto packageVersion = package->GetAvailableVersion(key); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 531edf85dd..d3aaac1698 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -713,14 +713,14 @@ namespace AppInstaller::CLI::Workflow bool shouldShowSource = source.IsComposite() && source.GetAvailableSources().size() > 1; PinBehavior pinBehavior; - if (m_onlyShowUpgrades) + if (m_onlyShowUpgrades && !context.Args.Contains(Execution::Args::Type::Force)) { // For listing upgrades, show the version we would upgrade to with the given pins. pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; } else { - // For listing installed apps, show the latest available. + // For listing installed apps or if we are ignoring pins due to --force, show the latest available. pinBehavior = PinBehavior::IgnorePins; } @@ -903,7 +903,17 @@ namespace AppInstaller::CLI::Workflow // TODO: The logic here will probably have to get more difficult once we support channels if (Utility::IsEmptyOrWhitespace(m_version) && Utility::IsEmptyOrWhitespace(m_channel)) { - PinBehavior pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + PinBehavior pinBehavior; + if (context.Args.Contains(Execution::Args::Type::Force)) + { + // --force ignores any pins + pinBehavior = PinBehavior::IgnorePins; + } + else + { + pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + } + requestedVersion = package->GetLatestAvailableVersion(pinBehavior); if (!requestedVersion) @@ -930,9 +940,16 @@ namespace AppInstaller::CLI::Workflow if (isPinned) { - AICLI_LOG(CLI, Error, << "The requested package version is unavailable because of a pin"); - context.Reporter.Error() << Resource::String::PackageIsPinned << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED); + if (context.Args.Contains(Execution::Args::Type::Force)) + { + AICLI_LOG(CLI, Info, << "Ignoring pin on package due to --force argument"); + } + else + { + AICLI_LOG(CLI, Error, << "The requested package version is unavailable because of a pin"); + context.Reporter.Error() << Resource::String::PackageIsPinned << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED); + } } } else From f15df739b2a9b7fa432924e5d4698d4eaca7dfc6 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Fri, 27 Jan 2023 17:05:48 -0800 Subject: [PATCH 44/55] Remove code duplication --- .../CompositeSource.cpp | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 392b834fbb..d25f61c66c 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -62,7 +62,6 @@ namespace AppInstaller::Repository return pvk; } - // Returns true for fields that provide a strong match; one that is not based on a heuristic. bool IsStrongMatchField(PackageMatchField field) { @@ -673,7 +672,7 @@ namespace AppInstaller::Repository } // Gets the information about the pins that exist for this package - void GetExistingPins(PinningIndex& pinningIndex) + void GetExistingPins(PinningIndex& pinningIndex, bool cleanUpStalePins) { // If the package is installed, we need to add the pin information to the available packages from any source. // If the package is not installed, we clean up stale pin information here. @@ -688,7 +687,7 @@ namespace AppInstaller::Repository availablePackage.Pin = std::move(pin.value()); } } - else if (pinningIndex.GetPin(pinKey)) + else if (pinningIndex.GetPin(pinKey) && cleanUpStalePins) { pinningIndex.RemovePin(pinKey); } @@ -1061,6 +1060,25 @@ namespace AppInstaller::Repository return {}; } + + // Adds all the pin information to the results from a search to a CompositeSource. + // This function assumes that the CompositeSource included an InstalledSource so that we + // can clean up stale pins where the package is no longer installed. + void AddPinInfoToCompositeSearchResult(CompositeResult& result) + { + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && !result.Matches.empty()) + { + // Look up any pins for the packages found + auto pinningIndex = PinningIndex::OpenOrCreateDefault(); + if (pinningIndex) + { + for (auto& match : result.Matches) + { + match.Package->GetExistingPins(*pinningIndex, /* cleanUpStalePins */ true); + } + } + } + } } CompositeSource::CompositeSource(std::string identifier) @@ -1257,19 +1275,7 @@ namespace AppInstaller::Repository // Optimization for the "everything installed" case, no need to allow for reverse correlations if (request.IsForEverything() && m_searchBehavior == CompositeSearchBehavior::Installed) { - if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && !result.Matches.empty()) - { - // Look up any pins for the packages found - auto pinningIndex = PinningIndex::OpenOrCreateDefault(); - if (pinningIndex) - { - for (auto& match : result.Matches) - { - match.Package->GetExistingPins(*pinningIndex); - } - } - } - + AddPinInfoToCompositeSearchResult(result); return std::move(result); } } @@ -1383,19 +1389,7 @@ namespace AppInstaller::Repository result.Matches.erase(result.Matches.begin() + request.MaximumResults, result.Matches.end()); } - if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && !result.Matches.empty()) - { - // Look up any pins for the packages found - auto pinningIndex = PinningIndex::OpenOrCreateDefault(); - if (pinningIndex) - { - for (auto& match : result.Matches) - { - match.Package->GetExistingPins(*pinningIndex); - } - } - } - + AddPinInfoToCompositeSearchResult(result); return std::move(result); } From 6910211b0d5aa79ada99fab65daef77a136327d2 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 30 Jan 2023 10:55:20 -0800 Subject: [PATCH 45/55] Add e2e tests --- .../AppInstallerCLIE2ETests.csproj | 5 + src/AppInstallerCLIE2ETests/Pinning.cs | 174 ++++++++++++++++++ .../Manifests/TestExeInstaller.1.0.1.0.yaml | 20 ++ .../Manifests/TestExeInstaller.1.1.0.0.yaml | 20 ++ src/AppInstallerCLIE2ETests/TestIndexSetup.cs | 18 +- .../CompositeSource.cpp | 5 +- 6 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 src/AppInstallerCLIE2ETests/Pinning.cs create mode 100644 src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml create mode 100644 src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj index e2288ba35e..2bfd0a5edd 100644 --- a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj +++ b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -46,6 +46,11 @@ + + + + + PreserveNewest diff --git a/src/AppInstallerCLIE2ETests/Pinning.cs b/src/AppInstallerCLIE2ETests/Pinning.cs new file mode 100644 index 0000000000..060c1a114c --- /dev/null +++ b/src/AppInstallerCLIE2ETests/Pinning.cs @@ -0,0 +1,174 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using NUnit.Framework; + using static AppInstallerCLIE2ETests.TestCommon; + + /// + /// Test upgrading pinned packages. + /// + public class Pinning : BaseCommand + { + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.ConfigureFeature("pinning", true); + } + + /// + /// Set up for all tests. + /// + [SetUp] + public void Setup() + { + // All tests use TestExeInstaller; try to clean it up for failure cases, + // then install the base version for pinning + TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); + TestCommon.RunAICLICommand("install", "AppInstallerTest.TestExeInstaller -v 1.0.0.0"); + TestCommon.RunAICLICommand("pin remove", "AppInstallerTest.TestExeInstaller"); + } + + /// + /// Clean up done after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTearDown() + { + TestCommon.RunAICLICommand("pin remove", "AppInstallerTest.TestExeInstaller"); + TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); + } + + // All tests do roughly the same with different types of pins: + // * Check that the available version shown by list is the latest + // * Check that the available version shown by upgrade is appropriate for the pin, + // including checks with flags to include pinned. + // * Check that an upgrade installs the right version + + /// + /// Tests upgrading a package when there are no pins on it. + /// + [Test] + public void UpgradeWithNoPins() + { + RunCommandResult result; + + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); + + result = TestCommon.RunAICLICommand("upgrade", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "The latest upgrade-able version is the same if there are no pins"); + } + + /// + /// Tests upgrading a package when it has a pinning pin. + /// + [Test] + public void UpgradeWithPinningPin() + { + RunCommandResult result; + + result = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); + + result = TestCommon.RunAICLICommand("upgrade", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); + Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); + + result = TestCommon.RunAICLICommand("upgrade", "--all"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + result = TestCommon.RunAICLICommand("upgrade", "--include-pinned"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "Argument makes available version show up"); + + result = TestCommon.RunAICLICommand("upgrade", "--all --include-pinned"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); + } + + /// + /// Tests upgrading a package when it has a gating pin that allows updating to another version. + /// + [Test] + public void UpgradeWithGatingPin() + { + RunCommandResult result; + + var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --version 1.0.*"); + Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); + + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); + + result = TestCommon.RunAICLICommand("upgrade", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); + Assert.IsTrue(result.StdOut.Contains("1.0.1.0"), "Version matching pin gated version shows up"); + + result = TestCommon.RunAICLICommand("upgrade", "--all"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); + } + + /// + /// Tests upgrading a package when it has a gating pin that blocks all other versions. + /// + [Test] + public void UpgradeWithGatingPinToCurrent() + { + RunCommandResult result; + + result = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --version 1.0.0.*"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); + + result = TestCommon.RunAICLICommand("upgrade", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); + Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); + + result = TestCommon.RunAICLICommand("upgrade", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_UPDATE_NOT_APPLICABLE, result.ExitCode, "No upgrades available"); + } + + /// + /// Tests upgrading a package when it has a blocking pin. + /// + [Test] + public void UpgradeWithBlockingPin() + { + RunCommandResult result; + + var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --blocking"); + Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); + + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); + + result = TestCommon.RunAICLICommand("upgrade", string.Empty); + Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); + Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); + + result = TestCommon.RunAICLICommand("upgrade", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_UPDATE_NOT_APPLICABLE, result.ExitCode, "No upgrades available"); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml new file mode 100644 index 0000000000..073e3c5c71 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml @@ -0,0 +1,20 @@ +Id: AppInstallerTest.TestExeInstaller +Name: TestExeInstaller +Version: 1.0.1.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 1.0.1.0 + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml new file mode 100644 index 0000000000..2cf660aa43 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml @@ -0,0 +1,20 @@ +Id: AppInstallerTest.TestExeInstaller +Name: TestExeInstaller +Version: 1.1.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 1.1.0.0 + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestIndexSetup.cs b/src/AppInstallerCLIE2ETests/TestIndexSetup.cs index 0f564b19ad..65574991d2 100644 --- a/src/AppInstallerCLIE2ETests/TestIndexSetup.cs +++ b/src/AppInstallerCLIE2ETests/TestIndexSetup.cs @@ -81,13 +81,27 @@ public static void DeleteDirectoryContents(DirectoryInfo directory) // Leave the server certificate file if present if (file.Name.ToLower() != Constants.TestSourceServerCertificateFileName) { - file.Delete(); + try + { + file.Delete(); + } + catch + { + // Just ignore errors in this setup step... + } } } foreach (DirectoryInfo dir in directory.GetDirectories()) { - dir.Delete(true); + try + { + dir.Delete(true); + } + catch + { + // Just ignore errors in this setup step... + } } } diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index d25f61c66c..68b0323365 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -378,10 +378,7 @@ namespace AppInstaller::Repository : AvailablePackage(availablePackage), Pin(pin) { auto latestAvailable = AvailablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins); - if (latestAvailable) - { - SourceId = latestAvailable->GetSource().GetIdentifier(); - } + SourceId = latestAvailable->GetSource().GetIdentifier(); } std::string SourceId; From ea176409da2f13716a9903e3a0db80e5e5064665 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 30 Jan 2023 11:01:33 -0800 Subject: [PATCH 46/55] Respect pins in dependencies --- .../Workflows/DependencyNodeProcessor.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp index 1b11269d04..d65e56c8f6 100644 --- a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp +++ b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp @@ -41,7 +41,18 @@ namespace AppInstaller::CLI::Workflow const auto& package = match.Package; auto packageId = package->GetProperty(PackageProperty::Id); m_nodePackageInstalledVersion = package->GetInstalledVersion(); - m_nodePackageLatestVersion = package->GetLatestAvailableVersion(PinBehavior::ConsiderPins); + + PinBehavior pinBehavior; + if (m_context.Args.Contains(Execution::Args::Type::Force)) + { + pinBehavior = PinBehavior::IgnorePins; + } + else + { + pinBehavior = m_context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + } + + m_nodePackageLatestVersion = package->GetLatestAvailableVersion(pinBehavior); if (m_nodePackageInstalledVersion && dependencyNode.IsVersionOk(Utility::Version(m_nodePackageInstalledVersion->GetProperty(PackageVersionProperty::Version)))) { From 80117108b1157f5da50776ede16b95ddbb68f094 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 30 Jan 2023 11:41:16 -0800 Subject: [PATCH 47/55] Keep single available package if not using pinning --- src/AppInstallerRepositoryCore/CompositeSource.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 68b0323365..e560092ea6 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -1228,9 +1228,12 @@ namespace AppInstaller::Repository } } + bool addedAvailablePackage = false; + // Directly search for the available package from tracking information. if (trackingPackage) { + addedAvailablePackage = true; compositePackage->AddAvailablePackage(GetTrackedPackageFromAvailableSource(result, trackedSource, trackingPackage->GetProperty(PackageProperty::Id))); compositePackage->SetTracking(std::move(trackedSource), std::move(trackingPackage), std::move(trackingPackageVersion)); } @@ -1238,6 +1241,13 @@ namespace AppInstaller::Repository // Search sources and add to result for (const auto& source : m_availableSources) { + if (addedAvailablePackage && !ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning)) + { + // Having multiple available packages is a new behavior introduced for package pinning, + // so we gate it with the same feature in case it causes problems. + break; + } + // Do not attempt to correlate local packages against this source if (!source.GetDetails().SupportInstalledSearchCorrelation) { @@ -1261,6 +1271,7 @@ namespace AppInstaller::Repository AICLI_LOG(Repo, Warning, << " Appropriate available package could not be determined"); }); + addedAvailablePackage = true; compositePackage->AddAvailablePackage(std::move(availablePackage)); } } From a9c56443c20a196716a2df5bddaa84c982b7442c Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 30 Jan 2023 11:44:47 -0800 Subject: [PATCH 48/55] Change search order for properties --- src/AppInstallerRepositoryCore/CompositeSource.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index e560092ea6..1f43d622f3 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -489,7 +489,11 @@ namespace AppInstaller::Repository Utility::LocIndString GetProperty(PackageProperty property) const override { - std::shared_ptr truth = GetLatestAvailableVersion(PinBehavior::IgnorePins); + std::shared_ptr truth; + if (m_defaultAvailablePackage) + { + truth = m_defaultAvailablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins); + } if (!truth) { truth = m_trackingPackageVersion; @@ -498,6 +502,10 @@ namespace AppInstaller::Repository { truth = GetInstalledVersion(); } + if (!truth) + { + truth = GetLatestAvailableVersion(PinBehavior::IgnorePins); + } switch (property) { From 16cb8ad3288bdd61fb8c4b428b0f5bc119def479 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 30 Jan 2023 13:17:26 -0800 Subject: [PATCH 49/55] Fix tests again... --- src/AppInstallerCLITests/CompositeSource.cpp | 3 +++ src/AppInstallerRepositoryCore/CompositeSource.cpp | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index 9cc177706f..629e6f8300 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -577,6 +577,9 @@ TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[Composite TEST_CASE("CompositeSource_MultipleAvailableSources_MatchAll", "[CompositeSource]") { + TestCommon::TestUserSettings testSettings; + testSettings.Set(true); + std::string pfn = "sortof_apfn"; std::string firstName = "Name1"; std::string secondName = "Name2"; diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 1f43d622f3..8190f437cc 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -378,7 +378,10 @@ namespace AppInstaller::Repository : AvailablePackage(availablePackage), Pin(pin) { auto latestAvailable = AvailablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins); - SourceId = latestAvailable->GetSource().GetIdentifier(); + if (latestAvailable) + { + SourceId = latestAvailable->GetSource().GetIdentifier(); + } } std::string SourceId; From ea902acf4af50f32715d61164c17f2541a340757 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 31 Jan 2023 10:19:01 -0800 Subject: [PATCH 50/55] Fix test data file inclusion --- src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj index 2bfd0a5edd..e2288ba35e 100644 --- a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj +++ b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -46,11 +46,6 @@ - - - - - PreserveNewest From 4b853f844d957a8f547fc68ca30b45d4586c4080 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Mon, 6 Feb 2023 14:38:42 -0800 Subject: [PATCH 51/55] Use different product code for test zip installer --- .../TestData/Manifests/TestZipInstaller_Exe.yaml | 4 ++-- .../TestZipInstaller_Exe_InvalidRelativeFilePath.yaml | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe.yaml index 506812f7d6..9e71098bc3 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe.yaml @@ -9,13 +9,13 @@ Installers: - Architecture: x64 InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip InstallerType: zip - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + ProductCode: '{E1880465-8CC2-4033-90AE-DE4E7FDBA26E}' InstallerSha256: NestedInstallerType: exe NestedInstallerFiles: - RelativeFilePath: AppInstallerTestExeInstaller.exe InstallerSwitches: - Custom: /execustom + Custom: /execustom /productID {E1880465-8CC2-4033-90AE-DE4E7FDBA26E} SilentWithProgress: /exeswp Silent: /exesilent Interactive: /exeinteractive diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe_InvalidRelativeFilePath.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe_InvalidRelativeFilePath.yaml index 6a62959ba1..8277e1f45f 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe_InvalidRelativeFilePath.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe_InvalidRelativeFilePath.yaml @@ -9,10 +9,12 @@ Installers: - Architecture: x64 InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip InstallerType: zip - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + ProductCode: '{E1880465-8CC2-4033-90AE-DE4E7FDBA26E}' InstallerSha256: NestedInstallerType: exe NestedInstallerFiles: - RelativeFilePath: ../../AppInstallerTestExeInstaller.exe + InstallerSwitches: + Custom: /productID {E1880465-8CC2-4033-90AE-DE4E7FDBA26E} ManifestType: singleton ManifestVersion: 1.4.0 \ No newline at end of file From 21247a4315c455cad8665ce7d279ab36cf21935b Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 7 Feb 2023 11:37:43 -0800 Subject: [PATCH 52/55] update -> upgrade; remove TODO --- src/AppInstallerCLICore/Resources.h | 2 +- src/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 2 +- .../Shared/Strings/en-us/winget.resw | 14 +++++++------- .../Public/winget/RepositorySearch.h | 1 - 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index a73fca5348..753a0a3985 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -442,10 +442,10 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnologyInNewerVersions); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeIsPinned); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradePinnedByUserCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUserPinnedCount); WINGET_DEFINE_RESOURCE_STRINGID(Usage); WINGET_DEFINE_RESOURCE_STRINGID(UserSettings); WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandLongDescription); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 60cfef72f8..e18c9f2d3a 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -841,7 +841,7 @@ namespace AppInstaller::CLI::Workflow if (packagesWithUserPinsSkipped > 0) { AICLI_LOG(CLI, Info, << packagesWithUserPinsSkipped << " package(s) skipped due to user pins"); - context.Reporter.Info() << Resource::String::UpgradeUserPinnedCount(packagesWithUserPinsSkipped) << std::endl; + context.Reporter.Info() << Resource::String::UpgradePinnedByUserCount(packagesWithUserPinsSkipped) << std::endl; } } } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 84cb07c22f..e0b83e4657 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1528,14 +1528,14 @@ Please specify one of them using the --source option to proceed. {Locked="{0}"} The value will be replaced with the feature name - Add a new pin. A pin can limit the Windows Package Manager from updating a package to specific ranges of versions, or it can prevent it from updating the package altogether. A pinned package may still update on its own and be updated from outside the Windows Package Manager. By default, a pinned package can be updated by mentioning it explicitly in the 'upgrade' command or by adding the '--include-pinned' flag to 'winget upgrade --all'. + Add a new pin. A pin can limit the Windows Package Manager from upgrade a package to specific ranges of versions, or it can prevent it from upgrading the package altogether. A pinned package may still upgrade on its own and be upgraded from outside the Windows Package Manager. By default, a pinned package can be upgraded by mentioning it explicitly in the 'upgrade' command or by adding the '--include-pinned' flag to 'winget upgrade --all'. {Locked{"'upgrade'"} Locked{"--include-pinned"} Locked{"winget upgrade --all"} Add a new pin - Manage package pins with the sub-commands. A pin can limit the Windows Package Manager from updating a package to specific ranges of versions, or it can prevent it from updating the package altogether. A pinned package may still update on its own and be updated from outside the Windows Package Manager. + Manage package pins with the sub-commands. A pin can limit the Windows Package Manager from upgrading a package to specific ranges of versions, or it can prevent it from upgrading the package altogether. A pinned package may still upgrade on its own and be upgraded from outside the Windows Package Manager. Manage package pins @@ -1562,7 +1562,7 @@ Please specify one of them using the --source option to proceed. Version to which to pin the package. The wildcard '*' can be used as the last version part - Block from updating until the pin is removed, preventing override arguments + Block from upgrading until the pin is removed, preventing override arguments Export settings as JSON @@ -1667,13 +1667,13 @@ Please specify one of them using the --source option to proceed. Pin removed successfully - A newer version was found, but the package has a pin that prevents from updating it. + A newer version was found, but the package has a pin that prevents from upgrading it. - The package is pinned and cannot be updated. Use the 'winget pin' command to view and edit pins. Some pin types can be bypassed with the --include-pinned argument. - {Locked="winget pin","--include-pinned"} Error shown when we block an update due to the package being pinned + The package is pinned and cannot be upgraded. Use the 'winget pin' command to view and edit pins. Some pin types can be bypassed with the --include-pinned argument. + {Locked="winget pin","--include-pinned"} Error shown when we block an upgrade due to the package being pinned - + {0} package(s) have pins that prevent upgrade. Use the 'winget pin' command to view and edit pins. Using the --include-pinned argument may show more results. {Locked="winget pin","--include-pinned"} {0} is a placeholder replaced by an integer number of packages diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h index 574a1834c6..4ae48bc037 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h +++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h @@ -223,7 +223,6 @@ namespace AppInstaller::Repository { PackageVersionKey() = default; - // TODO #219 check all uses PackageVersionKey(Utility::NormalizedString sourceId, Utility::NormalizedString version, Utility::NormalizedString channel, Pinning::PinType pinnedState = Pinning::PinType::Unknown) : SourceId(std::move(sourceId)), Version(std::move(version)), Channel(std::move(channel)), PinnedState(pinnedState) {} From 90ff9b0f2eca7e2d1216a82440d56fb9902cfc69 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 7 Feb 2023 13:54:06 -0800 Subject: [PATCH 53/55] Update E2E tests --- src/AppInstallerCLICore/Argument.cpp | 2 +- .../Workflows/UpdateFlow.cpp | 3 +- src/AppInstallerCLIE2ETests/Constants.cs | 1 + src/AppInstallerCLIE2ETests/Pinning.cs | 78 ++++++++++++++++++- src/AppInstallerCLIE2ETests/TestCommon.cs | 17 +++- 5 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index d3db9301e2..5e994b43c7 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -150,7 +150,7 @@ namespace AppInstaller::CLI case Execution::Args::Type::IncludeUnknown: return { type, "include-unknown"_liv, 'u', "unknown"_liv }; case Execution::Args::Type::IncludePinned: - return { type, "include-pinned"_liv, "pinned"_liv }; + return { type, "include-pinned"_liv, "pinned"_liv, ArgTypeCategory::CopyFlagToSubContext }; case Execution::Args::Type::UninstallPrevious: return { type, "uninstall-previous"_liv, ArgTypeCategory::InstallerBehavior }; diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 99d6731d8c..49cbe66b44 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -83,7 +83,7 @@ namespace AppInstaller::CLI::Workflow key.PinnedState == Pinning::PinType::Gating || (key.PinnedState == Pinning::PinType::Pinning && !includePinned)) { - AICLI_LOG(CLI, Info, << "Version [" << key.Version << "] from Source [" << key.SourceId << "] has a Pin with type [" << ToString(key.PinnedState) << "]"); + AICLI_LOG(CLI, Info, << "Package [" << package->GetProperty(PackageProperty::Id) << " with Version[" << key.Version << "] from Source[" << key.SourceId << "] has a Pin with type[" << ToString(key.PinnedState) << "]"); if (context.Args.Contains(Execution::Args::Type::Force)) { AICLI_LOG(CLI, Info, << "Ignoring pin due to --force argument"); @@ -151,6 +151,7 @@ namespace AppInstaller::CLI::Workflow else if (packagePinned) { context.Reporter.Info() << Resource::String::UpgradeIsPinned << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED); } else if (isUpgrade) { diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index a2d5acb3d2..99209261b0 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -229,6 +229,7 @@ public class ErrorCode public const int ERROR_MULTIPLE_INSTALL_FAILED = unchecked((int)0x8A150065); public const int ERROR_MULTIPLE_UNINSTALL_FAILED = unchecked((int)0x8A150066); public const int ERROR_NOT_ALL_QUERIES_FOUND_SINGLE = unchecked((int)0x8A150067); + public const int ERROR_PACKAGE_IS_PINNED = unchecked((int)0x8A150068); public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101); public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102); diff --git a/src/AppInstallerCLIE2ETests/Pinning.cs b/src/AppInstallerCLIE2ETests/Pinning.cs index 060c1a114c..ae65a4a942 100644 --- a/src/AppInstallerCLIE2ETests/Pinning.cs +++ b/src/AppInstallerCLIE2ETests/Pinning.cs @@ -6,6 +6,7 @@ namespace AppInstallerCLIE2ETests { + using System.IO; using NUnit.Framework; using static AppInstallerCLIE2ETests.TestCommon; @@ -76,6 +77,10 @@ public void UpgradeWithNoPins() public void UpgradeWithPinningPin() { RunCommandResult result; + string installDir = Path.GetTempPath(); + + // The base version of this app does not log /Version, but it still includes the version number in the log file name. + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "1.0.0.0"), "Base version installed"); result = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); @@ -90,7 +95,9 @@ public void UpgradeWithPinningPin() Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); result = TestCommon.RunAICLICommand("upgrade", "--all"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds with nothing to upgrade"); + + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "1.0.0.0"), "No newer version installed"); result = TestCommon.RunAICLICommand("upgrade", "--include-pinned"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); @@ -98,6 +105,7 @@ public void UpgradeWithPinningPin() result = TestCommon.RunAICLICommand("upgrade", "--all --include-pinned"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0")); } /// @@ -107,6 +115,7 @@ public void UpgradeWithPinningPin() public void UpgradeWithGatingPin() { RunCommandResult result; + string installDir = Path.GetTempPath(); var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --version 1.0.*"); Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); @@ -122,6 +131,7 @@ public void UpgradeWithGatingPin() result = TestCommon.RunAICLICommand("upgrade", "--all"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 1.0.1.0")); } /// @@ -145,7 +155,7 @@ public void UpgradeWithGatingPinToCurrent() Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); result = TestCommon.RunAICLICommand("upgrade", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_UPDATE_NOT_APPLICABLE, result.ExitCode, "No upgrades available"); + Assert.AreEqual(Constants.ErrorCode.ERROR_PACKAGE_IS_PINNED, result.ExitCode, "No upgrades available due to pin"); } /// @@ -168,7 +178,69 @@ public void UpgradeWithBlockingPin() Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); result = TestCommon.RunAICLICommand("upgrade", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_UPDATE_NOT_APPLICABLE, result.ExitCode, "No upgrades available"); + Assert.AreEqual(Constants.ErrorCode.ERROR_PACKAGE_IS_PINNED, result.ExitCode, "No upgrades available due to pin"); + } + + /// + /// Tests upgrading a package when it has a pinning pin and the --force flag is used. + /// + [Test] + public void ForceUpgradeWithPinningPin() + { + RunCommandResult result; + string installDir = Path.GetTempPath(); + + result = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + result = TestCommon.RunAICLICommand("upgrade", "--force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "--force argument shows latest version"); + + result = TestCommon.RunAICLICommand("upgrade", "--all --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0"), "--force argument installs last version despite pin"); + } + + /// + /// Tests upgrading a package when it has a gating pin and the --force flag is used. + /// + [Test] + public void ForceUpgradeWithGatingPin() + { + RunCommandResult result; + string installDir = Path.GetTempPath(); + + var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --version 1.0.*"); + Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); + + result = TestCommon.RunAICLICommand("upgrade", "--force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "--force argument shows latest version"); + + result = TestCommon.RunAICLICommand("upgrade", "--all --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0")); + } + + /// + /// Tests upgrading a package when it has a blocking pin and the --force flag is used. + /// + [Test] + public void ForceUpgradeWithBlockingPin() + { + RunCommandResult result; + string installDir = Path.GetTempPath(); + + var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --blocking"); + Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); + + result = TestCommon.RunAICLICommand("upgrade", "--force"); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "--force argument shows latest version"); + + result = TestCommon.RunAICLICommand("upgrade", "--all --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0")); } } } diff --git a/src/AppInstallerCLIE2ETests/TestCommon.cs b/src/AppInstallerCLIE2ETests/TestCommon.cs index fd07462c6f..6a75907be0 100644 --- a/src/AppInstallerCLIE2ETests/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/TestCommon.cs @@ -558,13 +558,14 @@ public static string GetTestServerCertificateHexString() return Convert.ToHexString(File.ReadAllBytes(Path.Combine(StaticFileRootPath, Constants.TestSourceServerCertificateFileName))); } + /// /// Verify exe installer correctly. /// /// Install directory. /// Optional expected content. /// True if success. - public static bool VerifyTestExeInstalledAndCleanup(string installDir, string expectedContent = null) + public static bool VerifyTestExeInstalled(string installDir, string expectedContent = null) { bool verifyInstallSuccess = true; @@ -581,6 +582,20 @@ public static bool VerifyTestExeInstalledAndCleanup(string installDir, string ex verifyInstallSuccess = content.Contains(expectedContent); } + return verifyInstallSuccess; + + } + + /// + /// Verify exe installer correctly and then uninstall it. + /// + /// Install directory. + /// Optional expected content. + /// True if success. + public static bool VerifyTestExeInstalledAndCleanup(string installDir, string expectedContent = null) + { + bool verifyInstallSuccess = VerifyTestExeInstalled(installDir, expectedContent); + // Always try clean up and ignore clean up failure var uninstallerPath = Path.Combine(installDir, Constants.TestExeUninstallerFileName); if (File.Exists(uninstallerPath)) From c35e0d97330ed0998d9400611293d4525f3fb236 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 7 Feb 2023 14:23:15 -0800 Subject: [PATCH 54/55] Fix whitespace --- src/AppInstallerCLIE2ETests/TestCommon.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/AppInstallerCLIE2ETests/TestCommon.cs b/src/AppInstallerCLIE2ETests/TestCommon.cs index 6a75907be0..423f51e59c 100644 --- a/src/AppInstallerCLIE2ETests/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/TestCommon.cs @@ -558,7 +558,6 @@ public static string GetTestServerCertificateHexString() return Convert.ToHexString(File.ReadAllBytes(Path.Combine(StaticFileRootPath, Constants.TestSourceServerCertificateFileName))); } - /// /// Verify exe installer correctly. /// @@ -583,7 +582,6 @@ public static bool VerifyTestExeInstalled(string installDir, string expectedCont } return verifyInstallSuccess; - } /// From 1aa53697570d4ffd72e6bf90d79b26567104879c Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Wed, 8 Feb 2023 14:48:55 -0800 Subject: [PATCH 55/55] PR comments --- src/AppInstallerCLICore/Argument.cpp | 2 +- .../CompositeSource.cpp | 73 ++++++++++++------- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 5e994b43c7..5a83c6e050 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -152,7 +152,7 @@ namespace AppInstaller::CLI case Execution::Args::Type::IncludePinned: return { type, "include-pinned"_liv, "pinned"_liv, ArgTypeCategory::CopyFlagToSubContext }; case Execution::Args::Type::UninstallPrevious: - return { type, "uninstall-previous"_liv, ArgTypeCategory::InstallerBehavior }; + return { type, "uninstall-previous"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; // Show command case Execution::Args::Type::ListVersions: diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 8190f437cc..15ae93b791 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -375,22 +375,38 @@ namespace AppInstaller::Repository { CompositeAvailablePackage() {} CompositeAvailablePackage(std::shared_ptr availablePackage, std::optional pin = {}) - : AvailablePackage(availablePackage), Pin(pin) + : m_availablePackage(availablePackage), m_pin(pin) { - auto latestAvailable = AvailablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins); + auto latestAvailable = m_availablePackage->GetLatestAvailableVersion(PinBehavior::IgnorePins); if (latestAvailable) { - SourceId = latestAvailable->GetSource().GetIdentifier(); + m_sourceId = latestAvailable->GetSource().GetIdentifier(); } } - std::string SourceId; - std::shared_ptr AvailablePackage; - std::optional Pin; + const std::string& GetSourceId() const + { + return m_sourceId; + } + + const std::shared_ptr& GetAvailablePackage() const + { + return m_availablePackage; + } + + const std::optional& GetPin() const + { + return m_pin; + } + + void SetPin(Pinning::Pin&& pin) + { + m_pin = std::move(pin); + } Utility::LocIndString GetProperty(PackageProperty property) const override { - return AvailablePackage->GetProperty(property); + return m_availablePackage->GetProperty(property); } std::shared_ptr GetInstalledVersion() const override @@ -400,16 +416,16 @@ namespace AppInstaller::Repository std::vector GetAvailableVersionKeys() const override { - auto result = AvailablePackage->GetAvailableVersionKeys(); - if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && Pin.has_value()) + auto result = m_availablePackage->GetAvailableVersionKeys(); + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && m_pin.has_value()) { for (auto& pvk : result) { - if (Pin->GetType() == Pinning::PinType::Blocking || - Pin->GetType() == Pinning::PinType::Pinning || - (Pin->GetType() == Pinning::PinType::Gating && !Pin->GetGatedVersion().IsValidVersion(pvk.Version))) + if (m_pin->GetType() == Pinning::PinType::Blocking || + m_pin->GetType() == Pinning::PinType::Pinning || + (m_pin->GetType() == Pinning::PinType::Gating && !m_pin->GetGatedVersion().IsValidVersion(pvk.Version))) { - pvk.PinnedState = Pin->GetType(); + pvk.PinnedState = m_pin->GetType(); } } } @@ -438,16 +454,16 @@ namespace AppInstaller::Repository { Pinning::PinType pinType = Pinning::PinType::Unknown; - if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && Pin.has_value()) + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Pinning) && m_pin.has_value()) { // A gating pin behaves the same as no pin when the version fits the gated version - if (!(pinType == Pinning::PinType::Gating && Pin->GetGatedVersion().IsValidVersion(versionKey.Version))) + if (!(pinType == Pinning::PinType::Gating && m_pin->GetGatedVersion().IsValidVersion(versionKey.Version))) { - pinType = Pin->GetType(); + pinType = m_pin->GetType(); } } - return { AvailablePackage->GetAvailableVersion(versionKey), pinType }; + return { m_availablePackage->GetAvailableVersion(versionKey), pinType }; } bool IsUpdateAvailable(PinBehavior) const override @@ -462,13 +478,18 @@ namespace AppInstaller::Repository if (otherAvailable) { return - SourceId == otherAvailable->SourceId && - Pin == otherAvailable->Pin && - AvailablePackage->IsSame(otherAvailable->AvailablePackage.get()); + m_sourceId == otherAvailable->m_sourceId && + m_pin == otherAvailable->m_pin && + m_availablePackage->IsSame(otherAvailable->m_availablePackage.get()); } return false; } + + private: + std::string m_sourceId; + std::shared_ptr m_availablePackage; + std::optional m_pin; }; // A composite package for the CompositeSource. @@ -578,7 +599,7 @@ namespace AppInstaller::Repository { for (const auto& availablePackage : m_availablePackages) { - if (!Utility::IsEmptyOrWhitespace(versionKey.SourceId) && versionKey.SourceId != availablePackage.SourceId) + if (!Utility::IsEmptyOrWhitespace(versionKey.SourceId) && versionKey.SourceId != availablePackage.GetSourceId()) { continue; } @@ -621,8 +642,8 @@ namespace AppInstaller::Repository for (size_t i = 0; i < m_availablePackages.size(); ++i) { - if (m_availablePackages[i].SourceId != otherComposite->m_availablePackages[i].SourceId || - !m_availablePackages[i].AvailablePackage->IsSame(otherComposite->m_availablePackages[i].AvailablePackage.get())) + if (m_availablePackages[i].GetSourceId() != otherComposite->m_availablePackages[i].GetSourceId() || + !m_availablePackages[i].GetAvailablePackage()->IsSame(otherComposite->m_availablePackages[i].GetAvailablePackage().get())) { return false; } @@ -637,7 +658,7 @@ namespace AppInstaller::Repository { for (const auto& availablePackage : m_availablePackages) { - if (other->IsSame(availablePackage.AvailablePackage.get())) + if (other->IsSame(availablePackage.GetAvailablePackage().get())) { return true; } @@ -686,13 +707,13 @@ namespace AppInstaller::Repository // If the package is not installed, we clean up stale pin information here. for (auto& availablePackage : m_availablePackages) { - auto pinKey = GetPinKey(availablePackage.AvailablePackage.get()); + auto pinKey = GetPinKey(availablePackage.GetAvailablePackage().get()); if (m_installedPackage) { auto pin = pinningIndex.GetPin(pinKey); if (pin.has_value()) { - availablePackage.Pin = std::move(pin.value()); + availablePackage.SetPin(std::move(pin.value())); } } else if (pinningIndex.GetPin(pinKey) && cleanUpStalePins)