diff --git a/UET/Redpoint.PackageManagement/HomebrewPackageManager.cs b/UET/Redpoint.PackageManagement/HomebrewPackageManager.cs new file mode 100644 index 00000000..90bab336 --- /dev/null +++ b/UET/Redpoint.PackageManagement/HomebrewPackageManager.cs @@ -0,0 +1,159 @@ +namespace Redpoint.PackageManagement +{ + using Microsoft.Extensions.Logging; + using Redpoint.PathResolution; + using Redpoint.ProcessExecution; + using Redpoint.ProgressMonitor.Utils; + using Redpoint.Reservation; + using System.Runtime.Versioning; + using System.Threading; + using System.Threading.Tasks; + + [SupportedOSPlatform("macos")] + internal class HomebrewPackageManager : IPackageManager + { + private readonly ILogger _logger; + private readonly IPathResolver _pathResolver; + private readonly IProcessExecutor _processExecutor; + private readonly ISimpleDownloadProgress _simpleDownloadProgress; + private readonly IGlobalMutexReservationManager _globalMutexReservationManager; + + public HomebrewPackageManager( + ILogger logger, + IPathResolver pathResolver, + IProcessExecutor processExecutor, + ISimpleDownloadProgress simpleDownloadProgress, + IReservationManagerFactory reservationManagerFactory) + { + _logger = logger; + _pathResolver = pathResolver; + _processExecutor = processExecutor; + _simpleDownloadProgress = simpleDownloadProgress; + _globalMutexReservationManager = reservationManagerFactory.CreateGlobalMutexReservationManager(); + } + + private async Task FindHomebrewOrInstallItAsync(CancellationToken cancellationToken) + { + try + { + return await _pathResolver.ResolveBinaryPath("brew").ConfigureAwait(false); + } + catch (FileNotFoundException) + { + } + + await using (await _globalMutexReservationManager.ReserveExactAsync("HomebrewInstall", cancellationToken)) + { + try + { + return await _pathResolver.ResolveBinaryPath("brew").ConfigureAwait(false); + } + catch (FileNotFoundException) + { + } + + if (File.Exists("/opt/homebrew/bin/brew")) + { + return "/opt/homebrew/bin/brew"; + } + + _logger.LogInformation($"Downloading Homebrew..."); + using var client = new HttpClient(); + + var targetPath = Path.Combine(Path.GetTempPath(), "install.sh"); + using (var file = new FileStream(targetPath, FileMode.Create, FileAccess.Write)) + { + await _simpleDownloadProgress.DownloadAndCopyToStreamAsync( + client, + new Uri("https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh"), + async stream => await stream.CopyToAsync(file, cancellationToken).ConfigureAwait(false), + cancellationToken).ConfigureAwait(false); + } + File.SetUnixFileMode(targetPath, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + + _logger.LogInformation($"Installing Homebrew..."); + await _processExecutor.ExecuteAsync( + new ProcessSpecification + { + FilePath = @"/bin/bash", + Arguments = new LogicalProcessArgument[] + { + "-c", + targetPath, + }, + WorkingDirectory = Path.GetTempPath(), + EnvironmentVariables = new Dictionary + { + { "NONINTERACTIVE", "1" } + }, + }, CaptureSpecification.Passthrough, cancellationToken).ConfigureAwait(false); + + return "/opt/homebrew/bin/brew"; + } + } + + public async Task InstallOrUpgradePackageToLatestAsync(string packageId, CancellationToken cancellationToken) + { + var homebrew = await FindHomebrewOrInstallItAsync(cancellationToken).ConfigureAwait(false); + + await using (await _globalMutexReservationManager.TryReserveExactAsync($"PackageInstall-{packageId}").ConfigureAwait(false)) + { + _logger.LogInformation($"Checking if {packageId} is installed..."); + var exitCode = await _processExecutor.ExecuteAsync( + new ProcessSpecification + { + FilePath = homebrew, + Arguments = new LogicalProcessArgument[] + { + "list", + packageId, + }, + WorkingDirectory = Path.GetTempPath(), + EnvironmentVariables = new Dictionary + { + { "NONINTERACTIVE", "1" } + }, + }, CaptureSpecification.Silence, cancellationToken).ConfigureAwait(false); + + if (exitCode == 0) + { + _logger.LogInformation($"Ensuring {packageId} is up-to-date..."); + await _processExecutor.ExecuteAsync( + new ProcessSpecification + { + FilePath = homebrew, + Arguments = [ + "upgrade", + "git", + ], + EnvironmentVariables = new Dictionary + { + { "NONINTERACTIVE", "1" } + }, + }, + CaptureSpecification.Passthrough, + CancellationToken.None).ConfigureAwait(false); + } + else + { + _logger.LogInformation($"Installing {packageId}..."); + await _processExecutor.ExecuteAsync( + new ProcessSpecification + { + FilePath = homebrew, + Arguments = [ + "install", + "git", + ], + EnvironmentVariables = new Dictionary + { + { "NONINTERACTIVE", "1" } + }, + }, + CaptureSpecification.Passthrough, + CancellationToken.None).ConfigureAwait(false); + } + }; + } + } +} diff --git a/UET/Redpoint.PackageManagement/IPackageManager.cs b/UET/Redpoint.PackageManagement/IPackageManager.cs new file mode 100644 index 00000000..ffb2f6eb --- /dev/null +++ b/UET/Redpoint.PackageManagement/IPackageManager.cs @@ -0,0 +1,16 @@ +namespace Redpoint.PackageManagement +{ + using System.Threading; + + public interface IPackageManager + { + /// + /// Installs or upgrades the target package to the latest version. + /// + /// The package ID (platform dependent). + /// An asynchronous task that can be awaited on. + Task InstallOrUpgradePackageToLatestAsync( + string packageId, + CancellationToken cancellationToken); + } +} diff --git a/UET/Redpoint.PackageManagement/NullPackageManager.cs b/UET/Redpoint.PackageManagement/NullPackageManager.cs new file mode 100644 index 00000000..da77c77b --- /dev/null +++ b/UET/Redpoint.PackageManagement/NullPackageManager.cs @@ -0,0 +1,19 @@ +namespace Redpoint.PackageManagement +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + internal class NullPackageManager : IPackageManager + { + public Task InstallOrUpgradePackageToLatestAsync( + string packageId, + CancellationToken cancellationToken) + { + throw new PlatformNotSupportedException("This platform does not support package management."); + } + } +} diff --git a/UET/Redpoint.PackageManagement/PackageManagementServiceExtensions.cs b/UET/Redpoint.PackageManagement/PackageManagementServiceExtensions.cs new file mode 100644 index 00000000..12499ba0 --- /dev/null +++ b/UET/Redpoint.PackageManagement/PackageManagementServiceExtensions.cs @@ -0,0 +1,30 @@ +namespace Redpoint.PackageManagement +{ + using Microsoft.Extensions.DependencyInjection; + + /// + /// Provides registration functions to register an implementation of into a . + /// + public static class PathResolutionServiceExtensions + { + /// + /// Add package management services (the service) into the service collection. + /// + /// The service collection to register an implementation of to. + public static void AddPackageManagement(this IServiceCollection services) + { + if (OperatingSystem.IsWindows()) + { + services.AddSingleton(); + } + else if (OperatingSystem.IsMacOS()) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } + } + } +} \ No newline at end of file diff --git a/UET/Redpoint.PackageManagement/Redpoint.PackageManagement.csproj b/UET/Redpoint.PackageManagement/Redpoint.PackageManagement.csproj new file mode 100644 index 00000000..959b2a89 --- /dev/null +++ b/UET/Redpoint.PackageManagement/Redpoint.PackageManagement.csproj @@ -0,0 +1,18 @@ + + + + + + + Provides APIs for installing, upgrading and uninstalling packages with WinGet and Homebrew. + winget, homebrew, package, management + + + + + + + + + + diff --git a/UET/Redpoint.PackageManagement/WinGetPackageManager.cs b/UET/Redpoint.PackageManagement/WinGetPackageManager.cs new file mode 100644 index 00000000..50dffd15 --- /dev/null +++ b/UET/Redpoint.PackageManagement/WinGetPackageManager.cs @@ -0,0 +1,138 @@ +namespace Redpoint.PackageManagement +{ + using Microsoft.Extensions.Logging; + using Redpoint.Concurrency; + using Redpoint.PathResolution; + using Redpoint.ProcessExecution; + using Redpoint.ProgressMonitor.Utils; + using Redpoint.Reservation; + using System.Runtime.Versioning; + using System.Text; + using System.Threading; + + [SupportedOSPlatform("windows")] + internal class WinGetPackageManager : IPackageManager + { + private readonly ILogger _logger; + private readonly IPathResolver _pathResolver; + private readonly IProcessExecutor _processExecutor; + private readonly ISimpleDownloadProgress _simpleDownloadProgress; + private readonly IGlobalMutexReservationManager _globalMutexReservationManager; + + public WinGetPackageManager( + ILogger logger, + IPathResolver pathResolver, + IProcessExecutor processExecutor, + ISimpleDownloadProgress simpleDownloadProgress, + IReservationManagerFactory reservationManagerFactory) + { + _logger = logger; + _pathResolver = pathResolver; + _processExecutor = processExecutor; + _simpleDownloadProgress = simpleDownloadProgress; + _globalMutexReservationManager = reservationManagerFactory.CreateGlobalMutexReservationManager(); + } + + private async Task FindPwshOrInstallItAsync(CancellationToken cancellationToken) + { + // Try to find PowerShell 7 via PATH. The WinGet CLI doesn't work under SYSTEM (even with absolute path) due to MSIX nonsense, but apparently the PowerShell scripts use a COM API that does? + try + { + return await _pathResolver.ResolveBinaryPath("pwsh").ConfigureAwait(false); + } + catch (FileNotFoundException) + { + } + + await using (await _globalMutexReservationManager.ReserveExactAsync("PwshInstall", cancellationToken)) + { + try + { + return await _pathResolver.ResolveBinaryPath("pwsh").ConfigureAwait(false); + } + catch (FileNotFoundException) + { + } + + _logger.LogInformation($"Downloading PowerShell Core..."); + using var client = new HttpClient(); + + var targetPath = Path.Combine(Path.GetTempPath(), "PowerShell-7.4.6-win-x64.msi"); + using (var file = new FileStream(targetPath, FileMode.Create, FileAccess.Write)) + { + await _simpleDownloadProgress.DownloadAndCopyToStreamAsync( + client, + new Uri("https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win-x64.msi"), + async stream => await stream.CopyToAsync(file, cancellationToken).ConfigureAwait(false), + cancellationToken).ConfigureAwait(false); + } + + _logger.LogInformation($"Installing PowerShell Core..."); + await _processExecutor.ExecuteAsync( + new ProcessSpecification + { + FilePath = @"C:\WINDOWS\system32\msiexec.exe", + Arguments = new LogicalProcessArgument[] + { + "/a", + targetPath, + "/quiet", + "/qn", + "ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1", + "ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL=1", + "ADD_PATH=1", + "DISABLE_TELEMETRY=1", + "USE_MU=1", + "ENABLE_MU=1" + }, + WorkingDirectory = Path.GetTempPath() + }, CaptureSpecification.Passthrough, cancellationToken).ConfigureAwait(false); + } + + return await _pathResolver.ResolveBinaryPath("pwsh").ConfigureAwait(false); + } + + public async Task InstallOrUpgradePackageToLatestAsync(string packageId, CancellationToken cancellationToken) + { + var pwsh = await FindPwshOrInstallItAsync(cancellationToken).ConfigureAwait(false); + + await using (await _globalMutexReservationManager.TryReserveExactAsync($"PackageInstall-{packageId}").ConfigureAwait(false)) + { + _logger.LogInformation($"Ensuring {packageId} is installed and is up-to-date..."); + var script = + $$""" + if ($null -eq (Get-InstalledModule -ErrorAction SilentlyContinue -Name {{packageId}})) { + Write-Host "Installing WinGet PowerShell module because it's not currently installed..."; + Install-Module -Name Microsoft.WinGet.Client -Force; + } + $InstalledPackage = (Get-WinGetPackage -Id {{packageId}} -ErrorAction SilentlyContinue); + if ($null -eq $InstalledPackage) { + Write-Host "Installing {{packageId}} because it's not currently installed..."; + Install-WinGetPackage -Id {{packageId}} -Mode Silent; + exit 0; + } elseif ($InstalledPackage.Version -ne (Find-WinGetPackage -Id {{packageId}}).Version) { + Write-Host "Updating {{packageId}} because it's not the latest version..."; + Update-WinGetPackage -Id {{packageId}} -Mode Silent; + exit 0; + } + """; + var encodedScript = Convert.ToBase64String(Encoding.Unicode.GetBytes(script)); + + await _processExecutor.ExecuteAsync( + new ProcessSpecification + { + FilePath = pwsh, + Arguments = [ + "-NonInteractive", + "-OutputFormat", + "Text", + "-EncodedCommand", + encodedScript, + ] + }, + CaptureSpecification.Passthrough, + cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/UET/Redpoint.Reservation/IGlobalMutexReservation.cs b/UET/Redpoint.Reservation/IGlobalMutexReservation.cs new file mode 100644 index 00000000..8ea1c14c --- /dev/null +++ b/UET/Redpoint.Reservation/IGlobalMutexReservation.cs @@ -0,0 +1,10 @@ +namespace Redpoint.Reservation +{ + /// + /// Represents a lock obtained on a global mutex. You must call + /// once you are finished with the reservation. + /// + public interface IGlobalMutexReservation : IAsyncDisposable + { + } +} diff --git a/UET/Redpoint.Reservation/IGlobalMutexReservationManager.cs b/UET/Redpoint.Reservation/IGlobalMutexReservationManager.cs index 82f63106..52b7cae7 100644 --- a/UET/Redpoint.Reservation/IGlobalMutexReservationManager.cs +++ b/UET/Redpoint.Reservation/IGlobalMutexReservationManager.cs @@ -2,14 +2,6 @@ { using System.Threading.Tasks; - /// - /// Represents a lock obtained on a global mutex. You must call - /// once you are finished with the reservation. - /// - public interface IGlobalMutexReservation : IAsyncDisposable - { - } - /// /// Supports making reservations on global mutexes, such that only the code using the /// returned has the lock of the global mutex. Mutexes are shared diff --git a/UET/Redpoint.Uet.BuildPipeline.Tests/DynamicBuildGraphIncludeTests.cs b/UET/Redpoint.Uet.BuildPipeline.Tests/DynamicBuildGraphIncludeTests.cs index 6318be65..99efbc95 100644 --- a/UET/Redpoint.Uet.BuildPipeline.Tests/DynamicBuildGraphIncludeTests.cs +++ b/UET/Redpoint.Uet.BuildPipeline.Tests/DynamicBuildGraphIncludeTests.cs @@ -18,6 +18,8 @@ using Redpoint.Uet.Workspace; using Redpoint.CredentialDiscovery; using Redpoint.Uet.Core; + using Redpoint.PackageManagement; + using Redpoint.ProgressMonitor; public class DynamicBuildGraphIncludeTests { @@ -32,7 +34,9 @@ private static ServiceCollection CreateServices() services.AddUETBuildPipelineProvidersTest(); services.AddUETBuildPipelineProvidersDeployment(); services.AddUETAutomation(); - services.AddUETWorkspace(); + services.AddUetWorkspace(); + services.AddPackageManagement(); + services.AddProgressMonitor(); services.AddReservation(); services.AddCredentialDiscovery(); services.AddUETCore(skipLoggingRegistration: true); diff --git a/UET/Redpoint.Uet.Configuration.Tests/BuildConfigTests.cs b/UET/Redpoint.Uet.Configuration.Tests/BuildConfigTests.cs index 175a3e17..17cbff8e 100644 --- a/UET/Redpoint.Uet.Configuration.Tests/BuildConfigTests.cs +++ b/UET/Redpoint.Uet.Configuration.Tests/BuildConfigTests.cs @@ -15,6 +15,8 @@ namespace Redpoint.Uet.Configuration.Tests using Redpoint.Uet.Workspace; using Redpoint.CredentialDiscovery; using Redpoint.Uet.Core; + using Redpoint.PackageManagement; + using Redpoint.ProgressMonitor; public class BuildConfigTests { @@ -28,7 +30,9 @@ private static ServiceCollection CreateServices() services.AddUETBuildPipelineProvidersTest(); services.AddUETBuildPipelineProvidersDeployment(); services.AddUETAutomation(); - services.AddUETWorkspace(); + services.AddUetWorkspace(); + services.AddPackageManagement(); + services.AddProgressMonitor(); services.AddUETCore(skipLoggingRegistration: true); services.AddReservation(); services.AddCredentialDiscovery(); diff --git a/UET/Redpoint.Uet.Workspace.Tests/PhysicalGitCheckoutTests.cs b/UET/Redpoint.Uet.Workspace.Tests/PhysicalGitCheckoutTests.cs index 0715bbf8..098aea03 100644 --- a/UET/Redpoint.Uet.Workspace.Tests/PhysicalGitCheckoutTests.cs +++ b/UET/Redpoint.Uet.Workspace.Tests/PhysicalGitCheckoutTests.cs @@ -4,8 +4,10 @@ using Microsoft.Extensions.Logging; using Redpoint.CredentialDiscovery; using Redpoint.IO; + using Redpoint.PackageManagement; using Redpoint.PathResolution; using Redpoint.ProcessExecution; + using Redpoint.ProgressMonitor; using Redpoint.Reservation; using Redpoint.Uefs.Protocol; using Redpoint.Uet.Core; @@ -45,7 +47,9 @@ public async Task CanCheckoutFresh() services.AddPathResolution(); services.AddProcessExecution(); services.AddUefs(); - services.AddUETWorkspace(); + services.AddUetWorkspace(); + services.AddPackageManagement(); + services.AddProgressMonitor(); services.AddUETCore(skipLoggingRegistration: true); services.AddReservation(); services.AddCredentialDiscovery(); @@ -100,8 +104,10 @@ public async Task CanCheckoutWithGitCheckoutMissing() }); services.AddPathResolution(); services.AddProcessExecution(); + services.AddPackageManagement(); services.AddUefs(); - services.AddUETWorkspace(); + services.AddUetWorkspace(); + services.AddProgressMonitor(); services.AddUETCore(skipLoggingRegistration: true); services.AddReservation(); services.AddCredentialDiscovery(); @@ -184,8 +190,10 @@ public async Task CanCheckoutOverSsh() }); services.AddPathResolution(); services.AddProcessExecution(); + services.AddPackageManagement(); services.AddUefs(); - services.AddUETWorkspace(); + services.AddUetWorkspace(); + services.AddProgressMonitor(); services.AddUETCore(skipLoggingRegistration: true); services.AddReservation(); services.AddCredentialDiscovery(); @@ -243,8 +251,10 @@ public async Task CanCheckoutEngineFresh() }); services.AddPathResolution(); services.AddProcessExecution(); + services.AddPackageManagement(); services.AddUefs(); - services.AddUETWorkspace(); + services.AddUetWorkspace(); + services.AddProgressMonitor(); services.AddUETCore(skipLoggingRegistration: true); services.AddReservation(); services.AddCredentialDiscovery(); diff --git a/UET/Redpoint.Uet.Workspace/PhysicalGit/DefaultPhysicalGitCheckout.cs b/UET/Redpoint.Uet.Workspace/PhysicalGit/DefaultPhysicalGitCheckout.cs index f8e0be9a..bd37f81a 100644 --- a/UET/Redpoint.Uet.Workspace/PhysicalGit/DefaultPhysicalGitCheckout.cs +++ b/UET/Redpoint.Uet.Workspace/PhysicalGit/DefaultPhysicalGitCheckout.cs @@ -21,6 +21,7 @@ using System.Threading.Tasks; using System.Security.Principal; using System.Buffers.Text; + using Redpoint.PackageManagement; internal class DefaultPhysicalGitCheckout : IPhysicalGitCheckout { @@ -31,6 +32,7 @@ internal class DefaultPhysicalGitCheckout : IPhysicalGitCheckout private readonly ICredentialDiscovery _credentialDiscovery; private readonly IReservationManagerFactory _reservationManagerFactory; private readonly IWorldPermissionApplier _worldPermissionApplier; + private readonly IPackageManager _packageManager; private readonly ConcurrentDictionary _sharedReservationManagers; private readonly IGlobalMutexReservationManager _globalMutexReservationManager; @@ -41,7 +43,8 @@ public DefaultPhysicalGitCheckout( IReservationManagerForUet reservationManagerForUet, ICredentialDiscovery credentialDiscovery, IReservationManagerFactory reservationManagerFactory, - IWorldPermissionApplier worldPermissionApplier) + IWorldPermissionApplier worldPermissionApplier, + IPackageManager packageManager) { _logger = logger; _pathResolver = pathResolver; @@ -50,6 +53,7 @@ public DefaultPhysicalGitCheckout( _credentialDiscovery = credentialDiscovery; _reservationManagerFactory = reservationManagerFactory; _worldPermissionApplier = worldPermissionApplier; + _packageManager = packageManager; _sharedReservationManagers = new ConcurrentDictionary(); _globalMutexReservationManager = _reservationManagerFactory.CreateGlobalMutexReservationManager(); } @@ -74,21 +78,6 @@ private async Task UpgradeSystemWideGitIfPossibleAsync() return; } - // Try to find PowerShell 7 via PATH. The WinGet CLI doesn't work under SYSTEM (even with absolute path) due to MSIX nonsense, but apparently the PowerShell scripts use a COM API that does? - string? pwsh = null; - try - { - pwsh = await _pathResolver.ResolveBinaryPath("pwsh").ConfigureAwait(false); - } - catch (FileNotFoundException) - { - } - if (pwsh == null) - { - _logger.LogInformation("Skipping automatic upgrade/install of Git because this system does not have PowerShell 7 or later installed."); - return; - } - // If Chocolatey is installed, remove any version of Git that Chocolatey has previously installed because we'll manage it with WinGet from here on out. string? choco = null; try @@ -134,77 +123,12 @@ await _processExecutor.ExecuteAsync( } // Make sure Git is up-to-date. - await using (await _globalMutexReservationManager.TryReserveExactAsync("GitUpgrade").ConfigureAwait(false)) - { - _logger.LogInformation("Ensuring Git is up-to-date..."); - var script = - """ - if ($null -eq (Get-InstalledModule -ErrorAction SilentlyContinue -Name Microsoft.WinGet.Client)) { - Write-Host "Installing WinGet PowerShell module because it's not currently installed..."; - Install-Module -Name Microsoft.WinGet.Client -Force; - } - $InstalledPackage = (Get-WinGetPackage -Id Microsoft.Git -ErrorAction SilentlyContinue); - if ($null -eq $InstalledPackage) { - Write-Host "Installing Git because it's not currently installed..."; - Install-WinGetPackage -Id Microsoft.Git -Mode Silent; - exit 0; - } elseif ($InstalledPackage.Version -ne (Find-WinGetPackage -Id Microsoft.Git).Version) { - Write-Host "Updating Git because it's not the latest version..."; - Update-WinGetPackage -Id Microsoft.Git -Mode Silent; - exit 0; - } - """; - var encodedScript = Convert.ToBase64String(Encoding.Unicode.GetBytes(script)); - - await _processExecutor.ExecuteAsync( - new ProcessSpecification - { - FilePath = pwsh, - Arguments = [ - "-NonInteractive", - "-OutputFormat", - "Text", - "-EncodedCommand", - encodedScript, - ] - }, - CaptureSpecification.Passthrough, - CancellationToken.None).ConfigureAwait(false); - } + await _packageManager.InstallOrUpgradePackageToLatestAsync("Microsoft.Git", CancellationToken.None); } else if (OperatingSystem.IsMacOS()) { - // Make sure Homebrew is installed so we can automate install/upgrade of Git. - string? brew = null; - try - { - brew = await _pathResolver.ResolveBinaryPath("brew").ConfigureAwait(false); - } - catch (FileNotFoundException) - { - } - if (brew == null) - { - _logger.LogInformation("Skipping automatic upgrade/install of Git because Homebrew is not installed."); - return; - } - // Make sure Git is up-to-date. - await using (await _globalMutexReservationManager.TryReserveExactAsync("GitUpgrade").ConfigureAwait(false)) - { - _logger.LogInformation("Ensuring Git is up-to-date..."); - await _processExecutor.ExecuteAsync( - new ProcessSpecification - { - FilePath = brew, - Arguments = [ - File.Exists("/opt/homebrew/bin/git") ? "upgrade" : "install", - "git", - ] - }, - CaptureSpecification.Passthrough, - CancellationToken.None).ConfigureAwait(false); - } + await _packageManager.InstallOrUpgradePackageToLatestAsync("git", CancellationToken.None); } } diff --git a/UET/Redpoint.Uet.Workspace/Redpoint.Uet.Workspace.csproj b/UET/Redpoint.Uet.Workspace/Redpoint.Uet.Workspace.csproj index 48d5c1d6..d48261a9 100644 --- a/UET/Redpoint.Uet.Workspace/Redpoint.Uet.Workspace.csproj +++ b/UET/Redpoint.Uet.Workspace/Redpoint.Uet.Workspace.csproj @@ -4,6 +4,7 @@ + diff --git a/UET/Redpoint.Uet.Workspace/WorkspaceServiceExtensions.cs b/UET/Redpoint.Uet.Workspace/WorkspaceServiceExtensions.cs index a92cc389..b18196cd 100644 --- a/UET/Redpoint.Uet.Workspace/WorkspaceServiceExtensions.cs +++ b/UET/Redpoint.Uet.Workspace/WorkspaceServiceExtensions.cs @@ -10,7 +10,7 @@ public static class WorkspaceServiceExtensions { - public static void AddUETWorkspace(this IServiceCollection services) + public static void AddUetWorkspace(this IServiceCollection services) { services.AddSingleton(); services.AddSingleton(); diff --git a/UET/UET.sln b/UET/UET.sln index b0206e30..d76850ed 100644 --- a/UET/UET.sln +++ b/UET/UET.sln @@ -315,6 +315,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Redpoint.Uba", "Redpoint.Ub EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redpoint.Uba", "Redpoint.Uba\Redpoint.Uba.csproj", "{2AAD5971-F06A-4B7A-9E11-538BEC82F247}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redpoint.PackageManagement", "Redpoint.PackageManagement\Redpoint.PackageManagement.csproj", "{2136131B-7D12-45D5-9CEF-A255FCF26A44}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -857,6 +859,10 @@ Global {2AAD5971-F06A-4B7A-9E11-538BEC82F247}.Debug|Any CPU.Build.0 = Debug|Any CPU {2AAD5971-F06A-4B7A-9E11-538BEC82F247}.Release|Any CPU.ActiveCfg = Release|Any CPU {2AAD5971-F06A-4B7A-9E11-538BEC82F247}.Release|Any CPU.Build.0 = Release|Any CPU + {2136131B-7D12-45D5-9CEF-A255FCF26A44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2136131B-7D12-45D5-9CEF-A255FCF26A44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2136131B-7D12-45D5-9CEF-A255FCF26A44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2136131B-7D12-45D5-9CEF-A255FCF26A44}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/UET/uet/Commands/CommandExtensions.cs b/UET/uet/Commands/CommandExtensions.cs index b03279e6..d1bb94de 100644 --- a/UET/uet/Commands/CommandExtensions.cs +++ b/UET/uet/Commands/CommandExtensions.cs @@ -33,6 +33,7 @@ using Redpoint.CredentialDiscovery; using Redpoint.Concurrency; using Redpoint.GrpcPipes.Transport.Tcp; + using Redpoint.PackageManagement; internal static class CommandExtensions { @@ -62,6 +63,7 @@ private static void AddGeneralServices(IServiceCollection services, LogLevel min services.AddSdkManagement(); services.AddGrpcPipes(); services.AddUefs(); + services.AddPackageManagement(); services.AddUETAutomation(); services.AddUETUAT(); services.AddUETBuildPipeline(); @@ -70,7 +72,7 @@ private static void AddGeneralServices(IServiceCollection services, LogLevel min services.AddUetBuildPipelineProvidersPrepare(); services.AddUETBuildPipelineProvidersTest(); services.AddUETBuildPipelineProvidersDeployment(); - services.AddUETWorkspace(); + services.AddUetWorkspace(); services.AddUETCore(minimumLogLevel: minimumLogLevel, permitRunbackLogging: permitRunbackLogging); services.AddCredentialDiscovery(); services.AddSingleton();