diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp index a5edc3767f..1091cb1356 100644 --- a/src/AppInstallerCLICore/Command.cpp +++ b/src/AppInstallerCLICore/Command.cpp @@ -27,8 +27,8 @@ namespace AppInstaller::CLI void Command::OutputIntroHeader(Execution::Reporter& reporter) const { reporter.Info() << - "AppInstaller Command Line v" << Runtime::GetClientVersion() << std::endl << - "Copyright (c) Microsoft Corporation" << std::endl; + "Windows Package Manager v" << Runtime::GetClientVersion() << std::endl << + "Copyright (c) Microsoft Corporation. All rights reserved." << std::endl; } void Command::OutputHelp(Execution::Reporter& reporter, const CommandException* exception) const diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp index 30815fd802..076296071f 100644 --- a/src/AppInstallerCLICore/Commands/RootCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -30,6 +30,7 @@ namespace AppInstaller::CLI return { Argument{ "version", 'v', Execution::Args::Type::ListVersions, LOCME("Display the version of the tool"), ArgumentType::Flag, Visibility::Help }, + Argument{ "info", APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER, Execution::Args::Type::Info, LOCME("Display general info of the tool"), ArgumentType::Flag, Visibility::Help }, }; } @@ -40,7 +41,18 @@ namespace AppInstaller::CLI void RootCommand::ExecuteInternal(Execution::Context& context) const { - if (context.Args.Contains(Execution::Args::Type::ListVersions)) + if (context.Args.Contains(Execution::Args::Type::Info)) + { + OutputIntroHeader(context.Reporter); + + context.Reporter.Info() << std::endl << + "Links:" << std::endl << + " Privacy Statement: https://aka.ms/winget-privacy" << std::endl << + " License agreement: https://aka.ms/winget-license" << std::endl << + " 3rd Party Notices: https://aka.ms/winget-3rdPartyNotice" << std::endl << + " Homepage: https://aka.ms/winget" << std::endl; + } + else if (context.Args.Contains(Execution::Args::Type::ListVersions)) { context.Reporter.Info() << 'v' << Runtime::GetClientVersion() << std::endl; } diff --git a/src/AppInstallerCLICore/Commands/ValidateCommand.cpp b/src/AppInstallerCLICore/Commands/ValidateCommand.cpp index 421bfa7b55..d5fbed0d95 100644 --- a/src/AppInstallerCLICore/Commands/ValidateCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ValidateCommand.cpp @@ -36,13 +36,21 @@ namespace AppInstaller::CLI try { - (void)Manifest::Manifest::CreateFromPath(inputFile, true); + (void)Manifest::Manifest::CreateFromPath(inputFile, true, true); context.Reporter.Info() << "Manifest validation succeeded." << std::endl; } catch (const Manifest::ManifestException& e) { - context.Reporter.Warn() << "Manifest validation failed." << std::endl; - context.Reporter.Warn() << e.GetManifestErrorMessage() << std::endl; + if (e.IsWarningOnly()) + { + context.Reporter.Warn() << "Manifest validation succeeded with warnings." << std::endl; + } + else + { + context.Reporter.Error() << "Manifest validation failed." << std::endl; + } + + context.Reporter.Info() << e.GetManifestErrorMessage() << std::endl; } }; } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index d913db5a38..ccadc763fa 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -57,6 +57,7 @@ namespace AppInstaller::CLI::Execution PlainStyle, // Makes progress display as plain RainbowStyle, // Makes progress display as a rainbow Help, // Show command usage + Info, // Show general info about WinGet }; bool Contains(Type arg) const { return (m_parsedArgs.count(arg) != 0); } diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 0de3763cd3..93acf2ce87 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -122,80 +122,110 @@ TEST_CASE("ReadGoodManifestWithSpaces", "[ManifestValidation]") REQUIRE(manifest.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" }); } -void TestManifest(const std::filesystem::path& manifestPath, const std::string& expectedError = {}) +struct ManifestExceptionMatcher : public Catch::MatcherBase { - if (expectedError.empty()) + ManifestExceptionMatcher(std::string expectedMessage, bool expectedWarningOnly) : + m_expectedMessage(expectedMessage), m_expectedWarningOnly(expectedWarningOnly) {} + + // Performs the test for this matcher + bool match(ManifestException const& e) const override + { + return e.GetManifestErrorMessage().find(m_expectedMessage) != std::string::npos && + e.IsWarningOnly() == m_expectedWarningOnly; + } + + virtual std::string describe() const override { + std::ostringstream ss; + ss << std::boolalpha << "Expected exception message: " << m_expectedMessage << "Expected IsWarningOnly: " << m_expectedWarningOnly; + return ss.str(); + } + +private: + std::string m_expectedMessage; + bool m_expectedWarningOnly; +}; + +void TestManifest(const std::filesystem::path& manifestPath, const std::string& expectedMessage = {}, bool expectedWarningOnly = false) +{ + if (expectedMessage.empty()) { - CHECK_NOTHROW(Manifest::CreateFromPath(TestDataFile(manifestPath), true)); + CHECK_NOTHROW(Manifest::CreateFromPath(TestDataFile(manifestPath), true, true)); } else { - CHECK_THROWS_WITH(Manifest::CreateFromPath(TestDataFile(manifestPath), true), Catch::Contains(expectedError)); + CHECK_THROWS_MATCHES(Manifest::CreateFromPath(TestDataFile(manifestPath), true, true), ManifestException, ManifestExceptionMatcher(expectedMessage, expectedWarningOnly)); } } +struct ManifestTestCase +{ + std::string TestFile; + std::string ExpectedMessage = {}; + bool IsWarningOnly = false; +}; + TEST_CASE("ReadGoodManifests", "[ManifestValidation]") { - std::string TestCases[] = + ManifestTestCase TestCases[] = { - "Manifest-Good-InstallerTypeExeRoot-Silent.yaml", - "Manifest-Good-InstallerTypeExeRoot-SilentRoot.yaml", - "Manifest-Good-InstallerTypeExe-Silent.yaml", - "Manifest-Good-InstallerTypeExe-SilentRoot.yaml", - "Manifest-Good-Installeruniqueness-DefaultLang.yaml", - "Manifest-Good-Installeruniqueness-DiffLangs.yaml", - "Manifest-Good-InstallerUniqueness-DiffScope.yaml", - "Manifest-Good-Minimum.yaml", - "Manifest-Good-Minimum-InstallerType.yaml", - "Manifest-Good-Switches.yaml", + { "Manifest-Good-InstallerTypeExeRoot-Silent.yaml" }, + { "Manifest-Good-InstallerTypeExeRoot-SilentRoot.yaml" }, + { "Manifest-Good-InstallerTypeExe-Silent.yaml" }, + { "Manifest-Good-InstallerTypeExe-SilentRoot.yaml" }, + { "Manifest-Good-Installeruniqueness-DefaultLang.yaml" }, + { "Manifest-Good-Installeruniqueness-DiffLangs.yaml" }, + { "Manifest-Good-InstallerUniqueness-DiffScope.yaml" }, + { "Manifest-Good-Minimum.yaml" }, + { "Manifest-Good-Minimum-InstallerType.yaml" }, + { "Manifest-Good-Switches.yaml" }, }; for (auto const& testCase : TestCases) { - TestManifest(testCase); + TestManifest(testCase.TestFile); } } TEST_CASE("ReadBadManifests", "[ManifestValidation]") { - std::pair TestCases[] = + ManifestTestCase TestCases[] = { - { "Manifest-Bad-ArchInvalid.yaml", "Manifest: Invalid field value. Field: Arch" }, - { "Manifest-Bad-ArchMissing.yaml", "Manifest: Required field missing. Field: Arch" }, - { "Manifest-Bad-Channel-NotSupported.yaml", "Manifest: Field is not supported. Field: Channel" }, - { "Manifest-Bad-DifferentCase-camelCase.yaml", "Manifest: All field names should be PascalCased. Field: installerType" }, - { "Manifest-Bad-DifferentCase-lower.yaml", "Manifest: All field names should be PascalCased. Field: installertype" }, - { "Manifest-Bad-DifferentCase-UPPER.yaml", "Manifest: All field names should be PascalCased. Field: INSTALLERTYPE" }, - { "Manifest-Bad-DuplicateKey.yaml", "Manifest: Duplicate field found in the manifest." }, - { "Manifest-Bad-DuplicateKey-DifferentCase.yaml", "Manifest: Duplicate field found in the manifest." }, - { "Manifest-Bad-DuplicateKey-DifferentCase-lower.yaml", "Manifest: Duplicate field found in the manifest." }, - { "Manifest-Bad-IdInvalid.yaml", "Manifest: Invalid field value. Field: Id" }, - { "Manifest-Bad-IdMissing.yaml", "Manifest: Required field missing. Field: Id" }, - { "Manifest-Bad-InstallersMissing.yaml", "Manifest: Required field missing. Field: Installers" }, - { "Manifest-Bad-InstallerTypeExe-NoSilent.yaml", "Manifest: Silent switches are required for InstallerType exe." }, - { "Manifest-Bad-InstallerTypeExe-NoSilentRoot.yaml", "Manifest: Silent switches are required for InstallerType exe." }, - { "Manifest-Bad-InstallerTypeExeRoot-NoSilent.yaml", "Manifest: Silent switches are required for InstallerType exe." }, - { "Manifest-Bad-InstallerTypeExeRoot-NoSilentRoot.yaml", "Manifest: Silent switches are required for InstallerType exe." }, - { "Manifest-Bad-InstallerTypeInvalid.yaml", "Manifest: Invalid field value. Field: InstallerType" }, - { "Manifest-Bad-InstallerTypeMissing.yaml", "Manifest: Invalid field value. Field: InstallerType" }, - { "Manifest-Bad-InstallerUniqueness.yaml", "Manifest: Duplicate installer entry found." }, - { "Manifest-Bad-InstallerUniqueness-DefaultScope.yaml", "Manifest: Duplicate installer entry found." }, - { "Manifest-Bad-InstallerUniqueness-DefaultValues.yaml", "Manifest: Duplicate installer entry found." }, - { "Manifest-Bad-InstallerUniqueness-SameLang.yaml", "Manifest: Duplicate installer entry found." }, - { "Manifest-Bad-NameMissing.yaml", "Manifest: Required field missing. Field: Name" }, - { "Manifest-Bad-PublisherMissing.yaml", "Manifest: Required field missing. Field: Publisher" }, - { "Manifest-Bad-Sha256Invalid.yaml", "Manifest: Invalid field value. Field: Sha256" }, - { "Manifest-Bad-Sha256Missing.yaml", "Manifest: Required field missing. Field: Sha256" }, - { "Manifest-Bad-SwitchInvalid.yaml", "Manifest: Unknown field. Field: NotASwitch" }, - { "Manifest-Bad-UnknownProperty.yaml", "Manifest: Unknown field. Field: Fake" }, - { "Manifest-Bad-UrlInvalid.yaml", "Manifest: Invalid field value. Field: Url" }, - { "Manifest-Bad-UrlMissing.yaml", "Manifest: Required field missing. Field: Url" }, - { "Manifest-Bad-VersionInvalid.yaml", "Manifest: Invalid field value. Field: Version" }, - { "Manifest-Bad-VersionMissing.yaml", "Manifest: Required field missing. Field: Version" }, + { "Manifest-Bad-ArchInvalid.yaml", "Invalid field value. Field: Arch" }, + { "Manifest-Bad-ArchMissing.yaml", "Required field missing. Field: Arch" }, + { "Manifest-Bad-Channel-NotSupported.yaml", "Field is not supported. Field: Channel" }, + { "Manifest-Bad-DifferentCase-camelCase.yaml", "All field names should be PascalCased. Field: installerType" }, + { "Manifest-Bad-DifferentCase-lower.yaml", "All field names should be PascalCased. Field: installertype" }, + { "Manifest-Bad-DifferentCase-UPPER.yaml", "All field names should be PascalCased. Field: INSTALLERTYPE" }, + { "Manifest-Bad-DuplicateKey.yaml", "Duplicate field found in the manifest." }, + { "Manifest-Bad-DuplicateKey-DifferentCase.yaml", "Duplicate field found in the manifest." }, + { "Manifest-Bad-DuplicateKey-DifferentCase-lower.yaml", "Duplicate field found in the manifest." }, + { "Manifest-Bad-IdInvalid.yaml", "Invalid field value. Field: Id" }, + { "Manifest-Bad-IdMissing.yaml", "Required field missing. Field: Id" }, + { "Manifest-Bad-InstallersMissing.yaml", "Required field missing. Field: Installers" }, + { "Manifest-Bad-InstallerTypeExe-NoSilent.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, + { "Manifest-Bad-InstallerTypeExe-NoSilentRoot.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, + { "Manifest-Bad-InstallerTypeExeRoot-NoSilent.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, + { "Manifest-Bad-InstallerTypeExeRoot-NoSilentRoot.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, + { "Manifest-Bad-InstallerTypeInvalid.yaml", "Invalid field value. Field: InstallerType" }, + { "Manifest-Bad-InstallerTypeMissing.yaml", "Invalid field value. Field: InstallerType" }, + { "Manifest-Bad-InstallerUniqueness.yaml", "Duplicate installer entry found." }, + { "Manifest-Bad-InstallerUniqueness-DefaultScope.yaml", "Duplicate installer entry found." }, + { "Manifest-Bad-InstallerUniqueness-DefaultValues.yaml", "Duplicate installer entry found." }, + { "Manifest-Bad-InstallerUniqueness-SameLang.yaml", "Duplicate installer entry found." }, + { "Manifest-Bad-NameMissing.yaml", "Required field missing. Field: Name" }, + { "Manifest-Bad-PublisherMissing.yaml", "Required field missing. Field: Publisher" }, + { "Manifest-Bad-Sha256Invalid.yaml", "Invalid field value. Field: Sha256" }, + { "Manifest-Bad-Sha256Missing.yaml", "Required field missing. Field: Sha256" }, + { "Manifest-Bad-SwitchInvalid.yaml", "Unknown field. Field: NotASwitch" }, + { "Manifest-Bad-UnknownProperty.yaml", "Unknown field. Field: Fake" }, + { "Manifest-Bad-UrlInvalid.yaml", "Invalid field value. Field: Url" }, + { "Manifest-Bad-UrlMissing.yaml", "Required field missing. Field: Url" }, + { "Manifest-Bad-VersionInvalid.yaml", "Invalid field value. Field: Version" }, + { "Manifest-Bad-VersionMissing.yaml", "Required field missing. Field: Version" }, }; for (auto const& testCase : TestCases) { - TestManifest(testCase.first, testCase.second); + TestManifest(testCase.TestFile, testCase.ExpectedMessage, testCase.IsWarningOnly); } } diff --git a/src/AppInstallerRepositoryCore/Manifest/Manifest.cpp b/src/AppInstallerRepositoryCore/Manifest/Manifest.cpp index a10b243153..0ea0c80a42 100644 --- a/src/AppInstallerRepositoryCore/Manifest/Manifest.cpp +++ b/src/AppInstallerRepositoryCore/Manifest/Manifest.cpp @@ -158,7 +158,7 @@ namespace AppInstaller::Manifest return resultErrors; } - Manifest Manifest::CreateFromPath(const std::filesystem::path& inputFile, bool fullValidation) + Manifest Manifest::CreateFromPath(const std::filesystem::path& inputFile, bool fullValidation, bool throwOnWarning) { Manifest manifest; std::vector errors; @@ -170,19 +170,23 @@ namespace AppInstaller::Manifest } catch (const std::exception& e) { - AICLI_LOG(YAML, Error, << "Failed to create manifest from file: " << inputFile.u8string()); THROW_EXCEPTION_MSG(ManifestException(), e.what()); } if (!errors.empty()) { - THROW_EXCEPTION(ManifestException(std::move(errors))); + ManifestException ex{ std::move(errors) }; + + if (throwOnWarning || !ex.IsWarningOnly()) + { + THROW_EXCEPTION(ex); + } } return manifest; } - Manifest Manifest::Create(const std::string& input, bool fullValidation) + Manifest Manifest::Create(const std::string& input, bool fullValidation, bool throwOnWarning) { Manifest manifest; std::vector errors; @@ -194,13 +198,17 @@ namespace AppInstaller::Manifest } catch (const std::exception& e) { - AICLI_LOG(YAML, Error, << "Failed to create manifest: " << input); THROW_EXCEPTION_MSG(ManifestException(), e.what()); } if (!errors.empty()) { - THROW_EXCEPTION(ManifestException(std::move(errors))); + ManifestException ex{ std::move(errors) }; + + if (throwOnWarning || !ex.IsWarningOnly()) + { + THROW_EXCEPTION(ex); + } } return manifest; diff --git a/src/AppInstallerRepositoryCore/Manifest/Manifest.h b/src/AppInstallerRepositoryCore/Manifest/Manifest.h index ae9002ba59..ff499cc304 100644 --- a/src/AppInstallerRepositoryCore/Manifest/Manifest.h +++ b/src/AppInstallerRepositoryCore/Manifest/Manifest.h @@ -67,10 +67,11 @@ namespace AppInstaller::Manifest std::vector PopulateManifestFields(const YAML::Node& rootNode, bool fullValidation); - // fullValidation Bool to set if manifest creation should perform extra validation that client does not need. - // e.g. Channel should be null. Client code does not need this check to work properly. - static Manifest CreateFromPath(const std::filesystem::path& inputFile, bool fullValidation = false); + // fullValidation: Bool to set if manifest creation should perform extra validation that client does not need. + // e.g. Channel should be null. Client code does not need this check to work properly. + // throwOnWarning: Bool to indicate if an exception should be thrown with only warnings detected in the manifest. + static Manifest CreateFromPath(const std::filesystem::path& inputFile, bool fullValidation = false, bool throwOnWarning = false); - static Manifest Create(const std::string& input, bool fullValidation = false); + static Manifest Create(const std::string& input, bool fullValidation = false, bool throwOnWarning = false); }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.cpp b/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.cpp index f78d940519..d8b191fb26 100644 --- a/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.cpp +++ b/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.cpp @@ -59,7 +59,7 @@ namespace AppInstaller::Manifest (Switches.find(InstallerSwitchType::SilentWithProgress) == Switches.end() || Switches.find(InstallerSwitchType::Silent) == Switches.end())) { - resultErrors.emplace_back(ManifestError::ExeInstallerMissingSilentSwitches); + resultErrors.emplace_back(ManifestError::ExeInstallerMissingSilentSwitches, ValidationError::Level::Warning); } // Check empty string before calling IsValidUrl to avoid duplicate error reporting. diff --git a/src/AppInstallerRepositoryCore/Manifest/ManifestValidation.h b/src/AppInstallerRepositoryCore/Manifest/ManifestValidation.h index 6fdf2078c1..25ed9e254c 100644 --- a/src/AppInstallerRepositoryCore/Manifest/ManifestValidation.h +++ b/src/AppInstallerRepositoryCore/Manifest/ManifestValidation.h @@ -13,28 +13,53 @@ namespace AppInstaller::Manifest { namespace ManifestError { - const char* const InvalidRootNode = "Manifest: Encountered unexpected root node."; - const char* const FieldUnknown = "Manifest: Unknown field."; - const char* const FieldIsNotPascalCase = "Manifest: All field names should be PascalCased."; - const char* const FieldDuplicate = "Manifest: Duplicate field found in the manifest."; - const char* const RequiredFieldEmpty = "Manifest: Required field with empty value."; - const char* const RequiredFieldMissing = "Manifest: Required field missing."; - const char* const InvalidFieldValue = "Manifest: Invalid field value."; - const char* const ExeInstallerMissingSilentSwitches = "Manifest: Silent switches are required for InstallerType exe."; - const char* const FieldNotSupported = "Manifest: Field is not supported."; - const char* const DuplicateInstallerEntry = "Manifest: Duplicate installer entry found."; + const char* const ErrorMessagePrefix = "Manifest Error: "; + const char* const WarningMessagePrefix = "Manifest Warning: "; + + const char* const InvalidRootNode = "Encountered unexpected root node."; + const char* const FieldUnknown = "Unknown field."; + const char* const FieldIsNotPascalCase = "All field names should be PascalCased."; + const char* const FieldDuplicate = "Duplicate field found in the manifest."; + const char* const RequiredFieldEmpty = "Required field with empty value."; + const char* const RequiredFieldMissing = "Required field missing."; + const char* const InvalidFieldValue = "Invalid field value."; + const char* const ExeInstallerMissingSilentSwitches = "Silent and SilentWithProgress switches are not specified for InstallerType exe. Please make sure the installer can run unattended."; + const char* const FieldNotSupported = "Field is not supported."; + const char* const DuplicateInstallerEntry = "Duplicate installer entry found."; } struct ValidationError { + enum class Level + { + Warning, + Error + }; + std::string Message; - std::string Field; - std::string Value; - int Line; - int Column; + std::string Field = {}; + std::string Value = {}; + int Line = -1; + int Column = -1; + Level ErrorLevel = Level::Error; + + ValidationError(std::string message) : + Message(std::move(message)) {} + + ValidationError(std::string message, Level level) : + Message(std::move(message)), ErrorLevel(level) {} - ValidationError(std::string message, std::string field = {}, std::string value = {}, int line = -1, int column = -1) : + ValidationError(std::string message, std::string field) : + Message(std::move(message)), Field(std::move(field)) {} + + ValidationError(std::string message, std::string field, std::string value) : + Message(std::move(message)), Field(std::move(field)), Value(std::move(value)) {} + + ValidationError(std::string message, std::string field, std::string value, int line, int column) : Message(std::move(message)), Field(std::move(field)), Value(std::move(value)), Line(line), Column(column) {} + + ValidationError(std::string message, std::string field, std::string value, int line, int column, Level level) : + Message(std::move(message)), Field(std::move(field)), Value(std::move(value)), Line(line), Column(column), ErrorLevel(level) {} }; // This struct contains individual app manifest field info @@ -55,7 +80,14 @@ namespace AppInstaller::Manifest struct ManifestException : public wil::ResultException { ManifestException(std::vector&& errors = {}) : - m_errors(std::move(errors)), wil::ResultException(APPINSTALLER_CLI_ERROR_MANIFEST_FAILED) {} + m_errors(std::move(errors)), wil::ResultException(APPINSTALLER_CLI_ERROR_MANIFEST_FAILED) + { + auto p = [&](ValidationError const& e) { + return e.ErrorLevel == ValidationError::Level::Error; + }; + + m_warningOnly = !m_errors.empty() && std::find_if(m_errors.begin(), m_errors.end(), p) == m_errors.end(); + } // Error message without wil diagnostic info const std::string& GetManifestErrorMessage() const noexcept @@ -71,7 +103,16 @@ namespace AppInstaller::Manifest { for (auto const& error : m_errors) { + if (error.ErrorLevel == ValidationError::Level::Error) + { + m_manifestErrorMessage += ManifestError::ErrorMessagePrefix; + } + else if (error.ErrorLevel == ValidationError::Level::Warning) + { + m_manifestErrorMessage += ManifestError::WarningMessagePrefix; + } m_manifestErrorMessage += error.Message; + if (!error.Field.empty()) { m_manifestErrorMessage += " Field: " + error.Field; @@ -91,6 +132,11 @@ namespace AppInstaller::Manifest return m_manifestErrorMessage; } + bool IsWarningOnly() const noexcept + { + return m_warningOnly; + } + const char* what() const noexcept override { if (m_whatMessage.empty()) @@ -109,5 +155,6 @@ namespace AppInstaller::Manifest std::vector m_errors; mutable std::string m_whatMessage; mutable std::string m_manifestErrorMessage; + bool m_warningOnly; }; } \ No newline at end of file diff --git a/src/WinGetUtil/Exports.cpp b/src/WinGetUtil/Exports.cpp index b874c2b259..ac55ad3523 100644 --- a/src/WinGetUtil/Exports.cpp +++ b/src/WinGetUtil/Exports.cpp @@ -160,22 +160,22 @@ extern "C" WINGET_UTIL_API WinGetValidateManifest( WINGET_STRING manifestPath, BOOL* succeeded, - WINGET_STRING_OUT* failureMessage) try + WINGET_STRING_OUT* message) try { THROW_HR_IF(E_INVALIDARG, !manifestPath); THROW_HR_IF(E_INVALIDARG, !succeeded); try { - (void)Manifest::CreateFromPath(manifestPath, true); + (void)Manifest::CreateFromPath(manifestPath, true, true); *succeeded = TRUE; } catch (const ManifestException& e) { - *succeeded = FALSE; - if (failureMessage) + *succeeded = e.IsWarningOnly(); + if (message) { - *failureMessage = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); + *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); } } diff --git a/src/WinGetUtil/WinGetUtil.h b/src/WinGetUtil/WinGetUtil.h index 3d0344fa14..5cdc908f24 100644 --- a/src/WinGetUtil/WinGetUtil.h +++ b/src/WinGetUtil/WinGetUtil.h @@ -73,7 +73,7 @@ extern "C" WINGET_UTIL_API WinGetValidateManifest( WINGET_STRING manifestPath, BOOL* succeeded, - WINGET_STRING_OUT* failureMessage); + WINGET_STRING_OUT* message); // Downloads a file to the given path, returning the SHA 256 hash of the file. WINGET_UTIL_API WinGetDownload(