commit 9858113911cbfabed75455b1a093750d8d37f1c9 parent dc48bac09740488e8802f1a274e48a486c32d75b Author: JohnMcPMS <johnmcp@microsoft.com> Date: Wed, 29 Apr 2020 17:46:32 -0700 Convert from optional package to app extension for source data (#92) Diffstat:
29 files changed, 261 insertions(+), 139 deletions(-)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml @@ -73,12 +73,29 @@ jobs: /p:AppxBundle=Always /p:UapAppxPackageBuildMode=StoreUpload' + - task: PowerShell@2 + displayName: Install Tests Dependencies + inputs: + targetType: 'inline' + script: | + Add-AppxPackage AppInstallerCLIPackage_0.0.0.2_Test\Dependencies\x86\Microsoft.VCLibs.x86.14.00.Desktop.appx + Add-AppxPackage AppInstallerCLIPackage_0.0.0.2_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx + workingDirectory: $(appxPackageDir) + + - task: VisualStudioTestPlatformInstaller@1 + displayName: Prepare VSTest for E2E Tests + inputs: + packageFeedSelector: 'nugetOrg' + +# TODO: Convert tests to run based on the whether things have worked up to this point + - task: CmdLine@2 displayName: Run Unit Tests Unpackaged x64 inputs: script: | AppInstallerCLITests.exe -logto AICLI-Unpackaged-x64.log -s -r junit -o TEST-AppInstallerCLI-Unpackaged-x64.xml workingDirectory: 'src\x64\Release\AppInstallerCLITests\' + condition: succeededOrFailed() - task: PublishBuildArtifacts@1 displayName: Publish Unit Tests Unpackaged Log x64 @@ -97,19 +114,12 @@ jobs: condition: succeededOrFailed() - task: PowerShell@2 - displayName: Install Tests Dependencies x64 - inputs: - targetType: 'inline' - script: | - Add-AppxPackage AppInstallerCLIPackage_0.0.0.2_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx - workingDirectory: $(appxPackageDir) - - - task: PowerShell@2 displayName: Run Unit Tests Packaged x64 inputs: filePath: 'src\AppInstallerCLITests\Run-TestsInPackage.ps1' arguments: '-Args "~[pips]" -BuildRoot x64\Release -PackageRoot AppInstallerCLIPackage\bin\x64\Release -LogTarget x64\Release\AICLI-Packaged-x64.log -TestResultsTarget x64\Release\TEST-AppInstallerCLI-Packaged-x64.xml -ScriptWait' workingDirectory: 'src' + condition: succeededOrFailed() - task: PublishBuildArtifacts@1 displayName: Publish Unit Tests Packaged Log x64 @@ -133,6 +143,7 @@ jobs: script: | AppInstallerCLITests.exe -logto AICLI-Unpackaged-x86.log -s -r junit -o TEST-AppInstallerCLI-Unpackaged-x86.xml workingDirectory: 'src\x86\Release\AppInstallerCLITests\' + condition: succeededOrFailed() - task: PublishBuildArtifacts@1 displayName: Publish Unit Tests Unpackaged Log x86 @@ -151,19 +162,12 @@ jobs: condition: succeededOrFailed() - task: PowerShell@2 - displayName: Install Tests Dependencies x86 - inputs: - targetType: 'inline' - script: | - Add-AppxPackage AppInstallerCLIPackage_0.0.0.2_Test\Dependencies\x86\Microsoft.VCLibs.x86.14.00.Desktop.appx - workingDirectory: $(appxPackageDir) - - - task: PowerShell@2 displayName: Run Unit Tests Packaged x86 inputs: filePath: 'src\AppInstallerCLITests\Run-TestsInPackage.ps1' arguments: '-Args "~[pips]" -BuildRoot x86\Release -PackageRoot AppInstallerCLIPackage\bin\x86\Release -LogTarget x86\Release\AICLI-Packaged-x86.log -TestResultsTarget x86\Release\TEST-AppInstallerCLI-Packaged-x86.xml -ScriptWait' workingDirectory: 'src' + condition: succeededOrFailed() - task: PublishBuildArtifacts@1 displayName: Publish Unit Tests Packaged Log x86 @@ -189,11 +193,6 @@ jobs: failTaskOnFailedTests: true condition: succeededOrFailed() - - task: VisualStudioTestPlatformInstaller@1 - displayName: Prepare VSTest for E2E Tests - inputs: - packageFeedSelector: 'nugetOrg' - - task: VSTest@2 displayName: Run E2E Tests Unpackaged x64 inputs: @@ -202,6 +201,7 @@ jobs: runSettingsFile: 'src\AnyCPU\Release\AppInstallerCLIE2ETests\Test.runsettings' overrideTestrunParameters: '-PackagedContext false -AICLIPath $(system.defaultWorkingDirectory)\src\x64\Release\AppInstallerCLI\AppInstallerCLI.exe' + condition: succeededOrFailed() - task: VSTest@2 displayName: Run E2E Tests Packaged x64 @@ -214,6 +214,7 @@ jobs: -AICLIPath AppInstallerCLI\AppInstallerCLI.exe -LooseFileRegistration true -InvokeCommandInDesktopPackage true' + condition: succeededOrFailed() - task: VSTest@2 displayName: Run E2E Tests Unpackaged x86 @@ -223,6 +224,7 @@ jobs: runSettingsFile: 'src\AnyCPU\Release\AppInstallerCLIE2ETests\Test.runsettings' overrideTestrunParameters: '-PackagedContext false -AICLIPath $(system.defaultWorkingDirectory)\src\x86\Release\AppInstallerCLI\AppInstallerCLI.exe' + condition: succeededOrFailed() - task: VSTest@2 displayName: Run E2E Tests Packaged x86 @@ -235,6 +237,7 @@ jobs: -AICLIPath AppInstallerCLI\AppInstallerCLI.exe -LooseFileRegistration true -InvokeCommandInDesktopPackage true' + condition: succeededOrFailed() - task: PublishBuildArtifacts@1 displayName: Publish CLI Binary diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -89,4 +89,18 @@ namespace AppInstaller::CLI::Execution Reporter.SetStyle(VisualStyle::Rainbow); } } + + void Context::Terminate(HRESULT hr) + { + if (m_isTerminated && m_terminationHR == hr && hr == E_ABORT) + { + // If things aren't terminating fast enough for the user, they will probably press CTRL+C again. + // In that case, we should forcibly terminate. + // Unless we want to spin a separate thread for all work, we have to just exit here. + std::exit(hr); + } + + m_isTerminated = true; + m_terminationHR = hr; + } } diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h @@ -156,7 +156,7 @@ namespace AppInstaller::CLI::Execution HRESULT GetTerminationHR() const { return m_terminationHR; } // Set the context to the terminated state. - void Terminate(HRESULT hr) { m_isTerminated = true; m_terminationHR = hr; } + void Terminate(HRESULT hr); // Adds a value to the context data, or overwrites an existing entry. // This must be used to create the intial data entry, but Get can be used to modify. diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -210,7 +210,7 @@ namespace AppInstaller::CLI::Workflow DeploymentOptions deploymentOptions = DeploymentOptions::ForceApplicationShutdown | DeploymentOptions::ForceTargetApplicationShutdown; - context.Reporter.ExecuteWithProgress(std::bind(Deployment::RequestAddPackageAsync, uri, deploymentOptions, std::placeholders::_1)); + context.Reporter.ExecuteWithProgress(std::bind(Deployment::RequestAddPackage, uri, deploymentOptions, std::placeholders::_1)); } catch (const wil::ResultException& re) { diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs @@ -24,10 +24,7 @@ namespace AppInstallerCLIE2ETests public const string AICLIPackageName = "AppInstallerCLI"; public const string AICLIAppId = "AppInst"; - // Todo: there's a deployment bug that if the last optional package is removed, the main package is also removed. - // We should remove this when the deployment bug is fixed. - public const string PlaceholderPackageFile = "PlaceholderPackage.msix"; - public const string PlaceholderPackageName = "AppInstallerSQLiteIndex-e2etest-placeholder"; + public const string TestPackage = "TestPackage.msix"; public class ErrorCode { diff --git a/src/AppInstallerCLIE2ETests/HashCommand.cs b/src/AppInstallerCLIE2ETests/HashCommand.cs @@ -16,7 +16,7 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("9b4c49ad7e47afd97d2e666e93347745e1647c55f1a7ebba6d31b7dd5f69ee68")); // Hash msix - result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile(Constants.PlaceholderPackageFile) + " -m"); + result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile(Constants.TestPackage) + " -m"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("08917b781939a7796746b5e2349e1f1d83b6c15599b60cd3f62816f15e565fc4")); Assert.True(result.StdOut.Contains("223b318c4b1154a1fb72b1bc23422810faa5ce899a8e774ba2a02834b2058f00")); diff --git a/src/AppInstallerCLIE2ETests/SetUpFixture.cs b/src/AppInstallerCLIE2ETests/SetUpFixture.cs @@ -75,7 +75,6 @@ namespace AppInstallerCLIE2ETests { Assert.True(TestCommon.InstallMsix(TestCommon.AICLIPackagePath)); } - Assert.True(TestCommon.InstallMsix(TestCommon.GetTestDataFile(Constants.PlaceholderPackageFile))); } } @@ -92,7 +91,6 @@ namespace AppInstallerCLIE2ETests if (TestCommon.PackagedContext) { - TestCommon.RemoveMsix(Constants.PlaceholderPackageName); TestCommon.RemoveMsix(Constants.AICLIPackageName); } } diff --git a/src/AppInstallerCLIE2ETests/SourceCommand.cs b/src/AppInstallerCLIE2ETests/SourceCommand.cs @@ -47,7 +47,7 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("SourceTestSource")); Assert.True(result.StdOut.Contains("https://winget-int.azureedge.net/cache")); - Assert.True(result.StdOut.Contains("AppInstallerSQLiteIndex-int")); + Assert.True(result.StdOut.Contains("Microsoft.Winget.Source.int")); Assert.True(result.StdOut.Contains("Updated")); // List when source name does not match diff --git a/src/AppInstallerCLIE2ETests/TestCommon.cs b/src/AppInstallerCLIE2ETests/TestCommon.cs @@ -139,6 +139,11 @@ namespace AppInstallerCLIE2ETests waitedTime += 1000; } + if (waitedTime >= timeOut) + { + throw new TimeoutException("Command run timed out."); + } + RunCommandResult result = new RunCommandResult(); result.ExitCode = File.Exists(exitCodeFile) ? int.Parse(File.ReadAllText(exitCodeFile).Trim()) : unchecked((int)0x80004005); diff --git a/src/AppInstallerCLIE2ETests/TestData/IndexPackageManifest.xml b/src/AppInstallerCLIE2ETests/TestData/IndexPackageManifest.xml @@ -9,19 +9,33 @@ Version="1.0.0.0" /> <Properties> - <DisplayName>AppInstallerSQLiteIndex for AppInstallerCLI.</DisplayName> + <DisplayName>Microsoft WinGet Source</DisplayName> <PublisherDisplayName>Microsoft Corporation</PublisherDisplayName> <Logo>Assets\AppPackageStoreLogo.png</Logo> </Properties> <Dependencies> <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.16299.0" MaxVersionTested="10.0.18287.0" /> - <uap4:MainPackageDependency Name="Microsoft.DesktopAppInstaller" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"/> - <uap4:MainPackageDependency Name="AppInstallerCLI" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"/> </Dependencies> + <Applications> + <Application Id="SourceData"> + <uap:VisualElements DisplayName="Source data for Microsoft WinGet" Square150x150Logo="Assets\AppPackageStoreLogo.scale-150.png" Square44x44Logo="Assets\AppPackageStoreLogo.scale-100.png" Description="Source data for Microsoft WinGet." BackgroundColor="#0078d7" AppListEntry="none" /> + <Extensions> + <uap3:Extension Category="windows.appExtension"> + <uap3:AppExtension Name="com.microsoft.winget.source" + Id="IndexDB" + DisplayName="Source data for Microsoft WinGet" + Description="Source data for Microsoft WinGet" + PublicFolder="Public"> + </uap3:AppExtension> + </uap3:Extension> + </Extensions> + </Application> + </Applications> + <Resources> - <Resource Language="en-US" /> + <Resource Language="und" /> </Resources> </Package> \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/PlaceholderPackage.msix b/src/AppInstallerCLIE2ETests/TestData/TestPackage.msix Binary files differ. diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest @@ -2,9 +2,10 @@ <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" + xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" - IgnorableNamespaces="uap uap5 rescap"> + IgnorableNamespaces="uap uap3 uap5 rescap"> <Identity Name="AppInstallerCLI" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="0.0.0.2" /> <Properties> <DisplayName>App Installer CLI</DisplayName> @@ -23,6 +24,11 @@ <Application Id="AppInst" Executable="AppInstallerCLI\AppInstallerCLI.exe" EntryPoint="Windows.FullTrustApplication"> <uap:VisualElements DisplayName="App Installer Client" Square150x150Logo="Images\Square150x150Logo.png" Square44x44Logo="Images\Square44x44Logo.png" Description="The App Installer client application." BackgroundColor="#0078d7" AppListEntry="none" /> <Extensions> + <uap3:Extension Category="windows.appExtensionHost"> + <uap3:AppExtensionHost> + <uap3:Name>com.microsoft.winget.source</uap3:Name> + </uap3:AppExtensionHost> + </uap3:Extension> <uap5:Extension Category="windows.appExecutionAlias"> <uap5:AppExecutionAlias> <uap5:ExecutionAlias Alias="AppInst.exe" /> diff --git a/src/AppInstallerCLITests/MsixInfo.cpp b/src/AppInstallerCLITests/MsixInfo.cpp @@ -60,7 +60,7 @@ TEST_CASE("MsixInfo_WriteFile", "[msixinfo]") TempFile file{ "msixtest_file"s, ".bin"s }; ProgressCallback callback; - msix.WriteToFile("index.db", file, callback); + msix.WriteToFile("Public\\index.db", file, callback); REQUIRE(1 == std::filesystem::file_size(file)); } diff --git a/src/AppInstallerCLITests/PreIndexedPackageSource.cpp b/src/AppInstallerCLITests/PreIndexedPackageSource.cpp @@ -23,7 +23,7 @@ constexpr std::string_view s_MsixFile_1 = "index.1.0.0.0.msix"; constexpr std::string_view s_MsixFile_2 = "index.2.0.0.0.msix"; constexpr std::string_view s_Msix_FamilyName = "AppInstallerCLITestsFakeIndex_125rzkzqaqjwj"; constexpr std::string_view s_AppxManifestFileName = "AppxManifest.xml"sv; -constexpr std::string_view s_IndexMsixName = "index.msix"sv; +constexpr std::string_view s_IndexMsixName = "source.msix"sv; constexpr std::string_view s_IndexFileName = "index.db"sv; void CopyIndexFileToDirectory(const fs::path& from, const fs::path& to) diff --git a/src/AppInstallerCLITests/TestData/index.1.0.0.0.msix b/src/AppInstallerCLITests/TestData/index.1.0.0.0.msix Binary files differ. diff --git a/src/AppInstallerCLITests/TestData/index.2.0.0.0.msix b/src/AppInstallerCLITests/TestData/index.2.0.0.0.msix Binary files differ. diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -192,6 +192,7 @@ <ClInclude Include="Public\AppInstallerLogging.h" /> <ClInclude Include="Public\AppInstallerArchitecture.h" /> <ClInclude Include="Public\AppInstallerVersions.h" /> + <ClInclude Include="Public\winget\ExtensionCatalog.h" /> <ClInclude Include="Telemetry\MicrosoftTelemetry.h" /> <ClInclude Include="Telemetry\TraceLogging.h" /> <ClInclude Include="Telemetry\WinEventLogLevels.h" /> @@ -203,6 +204,7 @@ <ClCompile Include="Deployment.cpp" /> <ClCompile Include="Downloader.cpp" /> <ClCompile Include="Errors.cpp" /> + <ClCompile Include="ExtensionCatalog.cpp" /> <ClCompile Include="FileLogger.cpp" /> <ClCompile Include="HttpStream\HttpClientWrapper.cpp" /> <ClCompile Include="HttpStream\HttpLocalCache.cpp" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -22,6 +22,9 @@ <Filter Include="HttpStream"> <UniqueIdentifier>{a9c14af9-ca74-4945-a19c-9e99df23a5ae}</UniqueIdentifier> </Filter> + <Filter Include="Public\winget"> + <UniqueIdentifier>{41035fd6-dc74-4464-b9b1-4ffe95d6789c}</UniqueIdentifier> + </Filter> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h"> @@ -93,6 +96,9 @@ <ClInclude Include="Public\AppInstallerVersions.h"> <Filter>Public</Filter> </ClInclude> + <ClInclude Include="Public\winget\ExtensionCatalog.h"> + <Filter>Public\winget</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -152,6 +158,9 @@ <ClCompile Include="Errors.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="ExtensionCatalog.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/AppInstallerLogging.cpp b/src/AppInstallerCommonCore/AppInstallerLogging.cpp @@ -39,6 +39,7 @@ namespace AppInstaller::Logging case Channel::SQL: return "SQL"; case Channel::Repo: return "REPO"; case Channel::YAML: return "YAML"; + case Channel::Core: return "CORE"; case Channel::Test: return "TEST"; default: return "NONE"; } diff --git a/src/AppInstallerCommonCore/Deployment.cpp b/src/AppInstallerCommonCore/Deployment.cpp @@ -24,6 +24,8 @@ namespace AppInstaller::Deployment size_t id, IProgressCallback& callback) { + AICLI_LOG(Core, Info, << "Begin waiting for deployment #" << id); + AsyncOperationProgressHandler<DeploymentResult, DeploymentProgress> progressCallback( [&callback](const IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress>&, DeploymentProgress progress) { @@ -35,6 +37,9 @@ namespace AppInstaller::Deployment deployOperation.Progress(progressCallback); auto removeCancel = callback.SetCancellationFunction([&]() { deployOperation.Cancel(); }); + + AICLI_LOG(Core, Info, << "Begin blocking for deployment #" << id); + auto deployResult = deployOperation.get(); if (!SUCCEEDED(deployResult.ExtendedErrorCode())) @@ -49,47 +54,9 @@ namespace AppInstaller::Deployment AICLI_LOG(Core, Info, << "Successfully deployed #" << id); } } - - // Type that exists simply to enabled a fire and forget register call as we exit. - struct DelayRegisterStorage - { - DelayRegisterStorage() = default; - - ~DelayRegisterStorage() - { - if (!m_familyNames.empty()) - { - PackageManager packageManager; - - for (const auto& fn : m_familyNames) - { - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting RegisterPackageByFamilyName operation #" << id << ": " << fn); - - winrt::hstring familyName = Utility::ConvertToUTF16(fn).c_str(); - (void)packageManager.RegisterPackageByFamilyNameAsync( - familyName, - nullptr, - winrt::Windows::Management::Deployment::DeploymentOptions::None, - nullptr, - nullptr); - } - } - } - - void Add(std::string_view familyName) - { - m_familyNames.emplace_back(familyName); - } - - private: - std::vector<std::string> m_familyNames; - }; - - DelayRegisterStorage s_delayRegisterStorage; } - void RequestAddPackageAsync( + void RequestAddPackage( const winrt::Windows::Foundation::Uri& uri, winrt::Windows::Management::Deployment::DeploymentOptions options, IProgressCallback& callback) @@ -111,36 +78,17 @@ namespace AppInstaller::Deployment WaitForDeployment(deployOperation, id, callback); } - void StageAndDelayRegisterPackageAsync( - std::string_view packageFamilyName, - const winrt::Windows::Foundation::Uri& uri, - winrt::Windows::Management::Deployment::DeploymentOptions stageOptions, - winrt::Windows::Management::Deployment::DeploymentOptions, + void RemovePackage( + std::string_view packageFullName, IProgressCallback& callback) { size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting StagePackage operation #" << id << ": " << Utility::ConvertToUTF8(uri.AbsoluteUri().c_str())); + AICLI_LOG(Core, Info, << "Starting RemovePackage operation #" << id << ": " << packageFullName); PackageManager packageManager; - - // RequestAddPackageAsync will invoke smart screen. - auto deployOperation = packageManager.StagePackageAsync( - uri, - nullptr, /*dependencyPackageUris*/ - stageOptions, - nullptr, /*targetVolume*/ - nullptr, /*optionalAndRelatedPackageFamilyNames*/ - nullptr /*relatedPackageUris*/); + winrt::hstring fullName = Utility::ConvertToUTF16(packageFullName).c_str(); + auto deployOperation = packageManager.RemovePackageAsync(fullName, RemovalOptions::None); WaitForDeployment(deployOperation, id, callback); - - s_delayRegisterStorage.Add(packageFamilyName); - } - - void RemovePackageFireAndForget(std::string_view packageFullName) - { - PackageManager packageManager; - winrt::hstring fullName = Utility::ConvertToUTF16(packageFullName).c_str(); - (void)packageManager.RemovePackageAsync(fullName, RemovalOptions::None); } } diff --git a/src/AppInstallerCommonCore/ExtensionCatalog.cpp b/src/AppInstallerCommonCore/ExtensionCatalog.cpp @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "pch.h" +#include "winget/ExtensionCatalog.h" +#include "AppInstallerLogging.h" +#include "AppInstallerStrings.h" + +namespace AppInstaller::Deployment +{ + namespace AppExt = winrt::Windows::ApplicationModel::AppExtensions; + + Extension::Extension(AppExt::AppExtension extension) : m_extension(extension) {} + + std::filesystem::path Extension::GetPackagePath() const + { + return m_extension.Package().InstalledLocation().Path().c_str(); + } + + std::filesystem::path Extension::GetPublicFolderPath() const + { + return m_extension.GetPublicFolderAsync().get().Path().c_str(); + } + + winrt::Windows::ApplicationModel::PackageVersion Extension::GetPackageVersion() const + { + return m_extension.Package().Id().Version(); + } + + ExtensionCatalog::ExtensionCatalog(std::wstring_view extensionName) + { + m_catalog = AppExt::AppExtensionCatalog::Open(winrt::hstring(extensionName)); + } + + std::optional<Extension> ExtensionCatalog::FindByPackageFamilyAndId(std::string_view packageFamilyName, std::wstring_view id) const + { + std::wstring wpfn = Utility::ConvertToUTF16(packageFamilyName); + std::optional<Extension> result; + + auto extensions = m_catalog.FindAllAsync().get(); + for (const auto& extension : extensions) + { + auto info = extension.AppInfo(); + + AICLI_LOG(Core, Info, << "Examining extension: PFN = " << Utility::ConvertToUTF8(info.PackageFamilyName()) << ", ID = " << Utility::ConvertToUTF8(extension.Id())); + + if (info.PackageFamilyName() == wpfn && extension.Id() == id) + { + AICLI_LOG(Core, Info, << "Found matching extension."); + result = Extension{ extension }; + break; + } + } + + if (!result) + { + AICLI_LOG(Core, Info, << "Did not find extension: PFN = " << packageFamilyName << ", ID = " << Utility::ConvertToUTF8(id)); + } + + return result; + } +} diff --git a/src/AppInstallerCommonCore/MsixInfo.cpp b/src/AppInstallerCommonCore/MsixInfo.cpp @@ -27,6 +27,17 @@ namespace AppInstaller::Msix return result; } + // Gets the UINT64 version from the version struct. + UINT64 GetVersionFromVersion(const winrt::Windows::ApplicationModel::PackageVersion& version) + { + UINT64 result = version.Major; + result = (result << 16) | version.Minor; + result = (result << 16) | version.Build; + result = (result << 16) | version.Revision; + + return result; + } + // Writes the stream (from current location) to the given file. void WriteStreamToFile(IStream* stream, UINT64 expectedSize, const std::filesystem::path& target, IProgressCallback& progress) { @@ -332,6 +343,16 @@ namespace AppInstaller::Msix return (GetVersionFromManifestReader(manifestReader.Get()) > GetVersionFromManifestReader(otherReader.Get())); } + bool MsixInfo::IsNewerThan(const winrt::Windows::ApplicationModel::PackageVersion& otherVersion) + { + THROW_HR_IF(E_NOT_VALID_STATE, m_isBundle); + + ComPtr<IAppxManifestReader> manifestReader; + THROW_IF_FAILED(m_packageReader->GetManifest(&manifestReader)); + + return (GetVersionFromManifestReader(manifestReader.Get()) > GetVersionFromVersion(otherVersion)); + } + void MsixInfo::WriteToFile(std::string_view packageFile, const std::filesystem::path& target, IProgressCallback& progress) { std::wstring fileUTF16 = Utility::ConvertToUTF16(packageFile); diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h @@ -8,23 +8,13 @@ namespace AppInstaller::Deployment { // Calls winrt::Windows::Management::Deployment::PackageManager::RequestAddPackageAsync - void RequestAddPackageAsync( + void RequestAddPackage( const winrt::Windows::Foundation::Uri& uri, winrt::Windows::Management::Deployment::DeploymentOptions options, IProgressCallback& callback); - // Stages the package, and then attempts to register it without waiting. - // This enables us to work around the fact that we cannot call SetPackageInUse, - // and thus cannot actually update an optional package while we are running. - void StageAndDelayRegisterPackageAsync( - std::string_view packageFamilyName, - const winrt::Windows::Foundation::Uri& uri, - winrt::Windows::Management::Deployment::DeploymentOptions stageOptions, - winrt::Windows::Management::Deployment::DeploymentOptions registerOptions, + // Calls winrt::Windows::Management::Deployment::PackageManager::RemovePackageAsync + void RemovePackage( + std::string_view packageFullName, IProgressCallback& callback); - - // Calls winrt::Windows::Management::Deployment::PackageManager::RemovePackageAsync, - // but *DOES NOT WAIT FOR A RESULT*. As this is used for removing an optional package - // we will simply complete our actions and exit the process. - void RemovePackageFireAndForget(std::string_view packageFullName); } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerMsixInfo.h b/src/AppInstallerCommonCore/Public/AppInstallerMsixInfo.h @@ -3,7 +3,10 @@ #pragma once #include <AppInstallerProgress.h> #include <AppxPackaging.h> + #include <wrl/client.h> +#include <winrt/Windows.ApplicationModel.h> + #include <filesystem> #include <optional> #include <string> @@ -60,6 +63,8 @@ namespace AppInstaller::Msix // Gets a value indicating whether the referenced info is newer than the given manifest. bool IsNewerThan(const std::filesystem::path& otherManifest); + bool IsNewerThan(const winrt::Windows::ApplicationModel::PackageVersion& otherVersion); + // Writes the package file to the given path. void WriteToFile(std::string_view packageFile, const std::filesystem::path& target, IProgressCallback& progress); diff --git a/src/AppInstallerCommonCore/Public/winget/ExtensionCatalog.h b/src/AppInstallerCommonCore/Public/winget/ExtensionCatalog.h @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <winrt/Windows.ApplicationModel.h> +#include <winrt/Windows.ApplicationModel.AppExtensions.h> + +#include <filesystem> +#include <string_view> +#include <optional> + +namespace AppInstaller::Deployment +{ + using namespace std::string_view_literals; + constexpr std::wstring_view SourceExtensionName = L"com.microsoft.winget.source"sv; + + constexpr std::wstring_view IndexDBId = L"IndexDB"sv; + + // Wraps an AppExtension. + struct Extension + { + Extension(winrt::Windows::ApplicationModel::AppExtensions::AppExtension extension); + + // Gets the location of the package root. + std::filesystem::path GetPackagePath() const; + + // Gets the location of the directory shared by the extension. + std::filesystem::path GetPublicFolderPath() const; + + // Get the version of the package. + winrt::Windows::ApplicationModel::PackageVersion GetPackageVersion() const; + + private: + winrt::Windows::ApplicationModel::AppExtensions::AppExtension m_extension; + }; + + // Wraps an AppExtensionCatalog. + struct ExtensionCatalog + { + ExtensionCatalog(std::wstring_view extensionName); + + // Finds an extension by its package family name and id. + std::optional<Extension> FindByPackageFamilyAndId(std::string_view packageFamilyName, std::wstring_view id) const; + + private: + winrt::Windows::ApplicationModel::AppExtensions::AppExtensionCatalog m_catalog = nullptr; + }; +} diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h @@ -17,6 +17,7 @@ #include <wil/resource.h> #include <winrt/Windows.ApplicationModel.h> +#include <winrt/Windows.ApplicationModel.AppExtensions.h> #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Security.Cryptography.h> diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -13,9 +13,11 @@ namespace AppInstaller::Repository::Microsoft { namespace { - static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageFileName = "index.msix"sv; + static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageFileName = "source.msix"sv; static constexpr std::string_view s_PreIndexedPackageSourceFactory_AppxManifestFileName = "AppxManifest.xml"sv; static constexpr std::string_view s_PreIndexedPackageSourceFactory_IndexFileName = "index.db"sv; + // TODO: This being hard coded to force using the Public directory name is not ideal. + static constexpr std::string_view s_PreIndexedPackageSourceFactory_IndexFilePath = "Public\\index.db"sv; // Construct the package location from the given details. // Currently expects that the arg is an https uri pointing to the root of the data. @@ -90,7 +92,7 @@ namespace AppInstaller::Repository::Microsoft { AICLI_LOG(Repo, Info, << "Initializing source from: " << details.Name << " => " << packageLocation); - // If not initialized, we need to open the package and get the family name. + // If not initialized, we need to open the package and get the full name. Msix::MsixInfo packageInfo(packageLocation); THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, packageInfo.GetIsBundle()); details.Data = packageInfo.GetPackageFullName(); @@ -122,24 +124,25 @@ namespace AppInstaller::Repository::Microsoft struct PackagedContextFactory : public PreIndexedFactoryBase { // *Should only be called when under a CrossProcessReaderWriteLock* - std::optional<std::filesystem::path> GetPackageLocationFromDetails(const SourceDetails& details) + std::optional<Deployment::Extension> GetExtensionFromDetails(const SourceDetails& details) { - return Msix::GetPackageLocationFromFullName(GetPackageFullNameFromDetails(details)); + Deployment::ExtensionCatalog catalog(Deployment::SourceExtensionName); + return catalog.FindByPackageFamilyAndId(GetPackageFamilyNameFromDetails(details), Deployment::IndexDBId); } std::shared_ptr<ISource> CreateInternal(const SourceDetails& details, Synchronization::CrossProcessReaderWriteLock&& lock) override { - auto optionalPackage = GetPackageLocationFromDetails(details); - if (!optionalPackage) + auto extension = GetExtensionFromDetails(details); + if (!extension) { AICLI_LOG(Repo, Info, << "Package not found " << details.Data); THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); } - std::filesystem::path packageLocation = optionalPackage.value(); - packageLocation /= s_PreIndexedPackageSourceFactory_IndexFileName; + std::filesystem::path indexLocation = extension->GetPublicFolderPath(); + indexLocation /= s_PreIndexedPackageSourceFactory_IndexFileName; - SQLiteIndex index = SQLiteIndex::Open(packageLocation.u8string(), SQLiteIndex::OpenDisposition::Immutable); + SQLiteIndex index = SQLiteIndex::Open(indexLocation.u8string(), SQLiteIndex::OpenDisposition::Immutable); return std::make_shared<SQLiteIndexSource>(details, std::move(index), std::move(lock)); } @@ -148,8 +151,8 @@ namespace AppInstaller::Repository::Microsoft { // Check if the package is newer before calling into deployment. // This can save us a lot of time over letting deployment detect same version. - auto optionalPackage = GetPackageLocationFromDetails(details); - if (optionalPackage) + auto extension = GetExtensionFromDetails(details); + if (extension) { Msix::MsixInfo packageInfo(packageLocation); THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, packageInfo.GetIsBundle()); @@ -160,10 +163,7 @@ namespace AppInstaller::Repository::Microsoft return; } - std::filesystem::path packagePath = optionalPackage.value(); - std::filesystem::path manifestPath = packagePath / s_PreIndexedPackageSourceFactory_AppxManifestFileName; - - if (!packageInfo.IsNewerThan(manifestPath)) + if (!packageInfo.IsNewerThan(extension->GetPackageVersion())) { AICLI_LOG(Repo, Info, << "Remote source data was not newer than existing, no update needed"); return; @@ -179,20 +179,17 @@ namespace AppInstaller::Repository::Microsoft } winrt::Windows::Foundation::Uri uri(Utility::ConvertToUTF16(packageLocation)); - Deployment::StageAndDelayRegisterPackageAsync( - GetPackageFamilyNameFromDetails(details), + Deployment::RequestAddPackage( uri, winrt::Windows::Management::Deployment::DeploymentOptions::None, - winrt::Windows::Management::Deployment::DeploymentOptions::None, progress); } - void RemoveInternal(const SourceDetails& details, IProgressCallback&) override + void RemoveInternal(const SourceDetails& details, IProgressCallback& callback) override { // Begin package removal, but let it run its course without waiting. - // This pattern is required due to the inability to use SetInUseAsync from a full trust process. AICLI_LOG(Repo, Info, << "Removing package " << GetPackageFullNameFromDetails(details)); - Deployment::RemovePackageFireAndForget(GetPackageFullNameFromDetails(details)); + Deployment::RemovePackage(GetPackageFullNameFromDetails(details), callback); } }; @@ -258,7 +255,7 @@ namespace AppInstaller::Repository::Microsoft return; } - packageInfo.WriteToFile(s_PreIndexedPackageSourceFactory_IndexFileName, indexPath, progress); + packageInfo.WriteToFile(s_PreIndexedPackageSourceFactory_IndexFilePath, indexPath, progress); packageInfo.WriteManifestToFile(manifestPath, progress); } diff --git a/src/AppInstallerRepositoryCore/pch.h b/src/AppInstallerRepositoryCore/pch.h @@ -17,6 +17,7 @@ #include <AppInstallerStrings.h> #include <AppInstallerSynchronization.h> #include <AppInstallerVersions.h> +#include <winget/ExtensionCatalog.h> #include <yaml-cpp/yaml.h> #include <wil/result_macros.h> diff --git a/src/IndexCreationTool/Program.cs b/src/IndexCreationTool/Program.cs @@ -10,7 +10,8 @@ namespace IndexCreationTool class Program { public const string IndexName = @"index.db"; - public const string IndexPackageName = @"index.msix"; + public const string IndexPathInPackage = @"Public\index.db"; + public const string IndexPackageName = @"source.msix"; static void Main(string[] args) { @@ -61,7 +62,7 @@ namespace IndexCreationTool using (StreamWriter outputFile = new StreamWriter("MappingFile.txt")) { outputFile.WriteLine("[Files]"); - outputFile.WriteLine($"\"{IndexName}\" \"{IndexName}\""); + outputFile.WriteLine($"\"{IndexName}\" \"{IndexPathInPackage}\""); outputFile.WriteLine($"\"{appxManifestPath}\" \"AppxManifest.xml\""); }