diff --git a/src/Cli/dotnet/commands/InstallingWorkloadCommand.cs b/src/Cli/dotnet/commands/InstallingWorkloadCommand.cs index a3f67c195b96..507b0e903586 100644 --- a/src/Cli/dotnet/commands/InstallingWorkloadCommand.cs +++ b/src/Cli/dotnet/commands/InstallingWorkloadCommand.cs @@ -30,6 +30,7 @@ internal abstract class InstallingWorkloadCommand : WorkloadCommandBase protected readonly string _downloadToCacheOption; protected readonly string _dotnetPath; protected readonly string _userProfileDir; + protected readonly string _workloadRootDir; protected readonly bool _checkIfManifestExist; protected readonly ReleaseVersion _sdkVersion; protected readonly SdkFeatureBand _sdkFeatureBand; @@ -92,8 +93,9 @@ public InstallingWorkloadCommand( _dotnetPath = creationResult.DotnetPath; _userProfileDir = creationResult.UserProfileDir; - _sdkVersion = creationResult.SdkVersion; _sdkFeatureBand = new SdkFeatureBand(creationResult.SdkVersion); + _workloadRootDir = WorkloadFileBasedInstall.IsUserLocal(_dotnetPath, _sdkFeatureBand.ToString()) ? _userProfileDir : _dotnetPath; + _sdkVersion = creationResult.SdkVersion; _workloadResolver = creationResult.WorkloadResolver; _targetSdkVersion ??= _sdkVersion; @@ -125,7 +127,7 @@ protected static Dictionary GetInstallStateContents(IEnumerable< InstallStateContents GetCurrentInstallState() { - return GetCurrentInstallState(_sdkFeatureBand, _dotnetPath); + return GetCurrentInstallState(_sdkFeatureBand, _workloadRootDir); } static InstallStateContents GetCurrentInstallState(SdkFeatureBand sdkFeatureBand, string dotnetDir) @@ -141,7 +143,7 @@ public static bool ShouldUseWorkloadSetMode(SdkFeatureBand sdkFeatureBand, strin protected void UpdateWorkloadManifests(ITransactionContext context, DirectoryPath? offlineCache) { - var updateToLatestWorkloadSet = ShouldUseWorkloadSetMode(_sdkFeatureBand, _dotnetPath); + var updateToLatestWorkloadSet = ShouldUseWorkloadSetMode(_sdkFeatureBand, _workloadRootDir); if (UseRollback && updateToLatestWorkloadSet) { // Rollback files are only for loose manifests. Update the mode to be loose manifests. @@ -157,7 +159,7 @@ protected void UpdateWorkloadManifests(ITransactionContext context, DirectoryPat // If a workload set version is specified, then switch to workload set update mode // Check to make sure the value needs to be changed, as updating triggers a UAC prompt // for MSI-based installs. - if (!ShouldUseWorkloadSetMode(_sdkFeatureBand, _dotnetPath)) + if (!ShouldUseWorkloadSetMode(_sdkFeatureBand, _workloadRootDir)) { _workloadInstaller.UpdateInstallMode(_sdkFeatureBand, true); } diff --git a/src/Cli/dotnet/commands/dotnet-workload/WorkloadCommandParser.cs b/src/Cli/dotnet/commands/dotnet-workload/WorkloadCommandParser.cs index 17056aac8969..12b8cc3f2407 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/WorkloadCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/WorkloadCommandParser.cs @@ -61,7 +61,7 @@ internal static void ShowWorkloadsInfo(ParseResult parseResult = null, WorkloadI reporter.WriteLine($" Workload version: {workloadInfoHelper.ManifestProvider.GetWorkloadVersion()}"); } - var useWorkloadSets = InstallStateContents.FromPath(Path.Combine(WorkloadInstallType.GetInstallStateFolder(workloadInfoHelper._currentSdkFeatureBand, workloadInfoHelper.DotnetPath), "default.json")).UseWorkloadSets; + var useWorkloadSets = InstallStateContents.FromPath(Path.Combine(WorkloadInstallType.GetInstallStateFolder(workloadInfoHelper._currentSdkFeatureBand, workloadInfoHelper.UserLocalPath), "default.json")).UseWorkloadSets; var workloadSetsString = useWorkloadSets == true ? "workload sets" : "loose manifests"; reporter.WriteLine(string.Format(CommonStrings.WorkloadManifestInstallationConfiguration, workloadSetsString)); diff --git a/src/Cli/dotnet/commands/dotnet-workload/WorkloadInfoHelper.cs b/src/Cli/dotnet/commands/dotnet-workload/WorkloadInfoHelper.cs index ec6a7641db54..6c828ccf3337 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/WorkloadInfoHelper.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/WorkloadInfoHelper.cs @@ -18,6 +18,7 @@ internal class WorkloadInfoHelper : IWorkloadInfoHelper public readonly SdkFeatureBand _currentSdkFeatureBand; private readonly string _targetSdkVersion; public string DotnetPath { get; } + public string UserLocalPath { get; } public WorkloadInfoHelper( bool isInteractive, @@ -61,6 +62,8 @@ public WorkloadInfoHelper( shouldLog: false); WorkloadRecordRepo = workloadRecordRepo ?? Installer.GetWorkloadInstallationRecordRepository(); + + UserLocalPath = dotnetDir ?? (WorkloadFileBasedInstall.IsUserLocal(DotnetPath, _currentSdkFeatureBand.ToString()) ? userProfileDir : DotnetPath); } public IInstaller Installer { get; private init; } diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/FileBasedInstaller.cs b/src/Cli/dotnet/commands/dotnet-workload/install/FileBasedInstaller.cs index caeb77562da4..cda156b8dd14 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/FileBasedInstaller.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/FileBasedInstaller.cs @@ -26,6 +26,7 @@ internal class FileBasedInstaller : IInstaller private const string InstalledWorkloadSetsDir = "InstalledWorkloadSets"; protected readonly string _dotnetDir; protected readonly string _userProfileDir; + protected readonly string _workloadRootDir; protected readonly DirectoryPath _tempPackagesDir; private readonly INuGetPackageDownloader _nugetPackageDownloader; private IWorkloadResolver _workloadResolver; @@ -57,7 +58,8 @@ public FileBasedInstaller(IReporter reporter, new FirstPartyNuGetPackageSigningVerifier(), logger, restoreActionConfig: _restoreActionConfig); bool userLocal = WorkloadFileBasedInstall.IsUserLocal(_dotnetDir, sdkFeatureBand.ToString()); - _workloadMetadataDir = Path.Combine(userLocal ? _userProfileDir : _dotnetDir, "metadata", "workloads"); + _workloadRootDir = userLocal ? _userProfileDir : _dotnetDir; + _workloadMetadataDir = Path.Combine(_workloadRootDir, "metadata", "workloads"); _reporter = reporter; _sdkFeatureBand = sdkFeatureBand; _workloadResolver = workloadResolver; @@ -92,7 +94,7 @@ public WorkloadSet InstallWorkloadSet(ITransactionContext context, string worklo string workloadSetPackageVersion = WorkloadSet.WorkloadSetVersionToWorkloadSetPackageVersion(workloadSetVersion, out workloadSetFeatureBand); var workloadSetPackageId = GetManifestPackageId(new ManifestId("Microsoft.NET.Workloads"), workloadSetFeatureBand); - var workloadSetPath = Path.Combine(_dotnetDir, "sdk-manifests", _sdkFeatureBand.ToString(), "workloadsets", workloadSetVersion); + var workloadSetPath = Path.Combine(_workloadRootDir, "sdk-manifests", _sdkFeatureBand.ToString(), "workloadsets", workloadSetVersion); try { @@ -226,9 +228,7 @@ public void RepairWorkloads(IEnumerable workloadIds, SdkFeatureBand string GetManifestInstallDirForFeatureBand(string sdkFeatureBand) { - string rootInstallDir = WorkloadFileBasedInstall.IsUserLocal(_dotnetDir, _sdkFeatureBand.ToString()) ? _userProfileDir : _dotnetDir; - var manifestInstallDir = Path.Combine(rootInstallDir, "sdk-manifests", sdkFeatureBand); - return manifestInstallDir; + return Path.Combine(_workloadRootDir, "sdk-manifests", sdkFeatureBand); } public void InstallWorkloadManifest(ManifestVersionUpdate manifestUpdate, ITransactionContext transactionContext, DirectoryPath? offlineCache = null) @@ -341,7 +341,7 @@ public IEnumerable GetDownloads(IEnumerable worklo public void GarbageCollect(Func getResolverForWorkloadSet, DirectoryPath? offlineCache = null, bool cleanAllPacks = false) { - var garbageCollector = new WorkloadGarbageCollector(_dotnetDir, _sdkFeatureBand, _installationRecordRepository.GetInstalledWorkloads(_sdkFeatureBand), getResolverForWorkloadSet, Reporter.Verbose); + var garbageCollector = new WorkloadGarbageCollector(_workloadRootDir, _sdkFeatureBand, _installationRecordRepository.GetInstalledWorkloads(_sdkFeatureBand), getResolverForWorkloadSet, Reporter.Verbose); garbageCollector.Collect(); var featureBandsWithWorkloadInstallRecords = _installationRecordRepository.GetFeatureBandsWithInstallationRecords(); @@ -479,46 +479,36 @@ public void GarbageCollect(Func getResolverForWorkloa public void AdjustWorkloadSetInInstallState(SdkFeatureBand sdkFeatureBand, string workloadVersion) { - string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetDir), "default.json"); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - var installStateContents = InstallStateContents.FromPath(path); - installStateContents.WorkloadVersion = workloadVersion; - File.WriteAllText(path, installStateContents.ToString()); + UpdateInstallState(sdkFeatureBand, contents => contents.WorkloadVersion = workloadVersion); } public void RemoveManifestsFromInstallState(SdkFeatureBand sdkFeatureBand) { - string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetDir), "default.json"); - - if (File.Exists(path)) - { - var installStateContents = InstallStateContents.FromString(File.ReadAllText(path)); - installStateContents.Manifests = null; - File.WriteAllText(path, installStateContents.ToString()); - } + UpdateInstallState(sdkFeatureBand, contents => contents.Manifests = null); } public void SaveInstallStateManifestVersions(SdkFeatureBand sdkFeatureBand, Dictionary manifestContents) { - string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetDir), "default.json"); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - var installStateContents = InstallStateContents.FromPath(path); - installStateContents.Manifests = manifestContents; - File.WriteAllText(path, installStateContents.ToString()); + UpdateInstallState(sdkFeatureBand, contents => contents.Manifests = manifestContents); } public void UpdateInstallMode(SdkFeatureBand sdkFeatureBand, bool? newMode) { - string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, _dotnetDir), "default.json"); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - var installStateContents = InstallStateContents.FromPath(path); - installStateContents.UseWorkloadSets = newMode; - File.WriteAllText(path, installStateContents.ToString()); + UpdateInstallState(sdkFeatureBand, contents => contents.UseWorkloadSets = newMode); var newModeString = newMode == null ? "" : (newMode.Value ? WorkloadConfigCommandParser.UpdateMode_WorkloadSet : WorkloadConfigCommandParser.UpdateMode_Manifests); _reporter.WriteLine(string.Format(LocalizableStrings.UpdatedWorkloadMode, newModeString)); } + private void UpdateInstallState(SdkFeatureBand sdkFeatureBand, Action update) + { + string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, _workloadRootDir), "default.json"); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + var installStateContents = InstallStateContents.FromPath(path); + update(installStateContents); + File.WriteAllText(path, installStateContents.ToString()); + } + /// /// Remove all workload installation records that aren't from Visual Studio. /// diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs index 82b31852d434..c21a32dac4e4 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs @@ -151,7 +151,7 @@ public override int Execute() if (!_skipManifestUpdate) { - var installStateFilePath = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetPath), "default.json"); + var installStateFilePath = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _workloadRootDir), "default.json"); if (string.IsNullOrWhiteSpace(_fromRollbackDefinition) && !SpecifiedWorkloadSetVersionOnCommandLine && !SpecifiedWorkloadSetVersionInGlobalJson && diff --git a/src/Cli/dotnet/commands/dotnet-workload/list/WorkloadListCommand.cs b/src/Cli/dotnet/commands/dotnet-workload/list/WorkloadListCommand.cs index c1b698126b4b..da95b42d356e 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/list/WorkloadListCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/list/WorkloadListCommand.cs @@ -78,7 +78,7 @@ public override int Execute() Reporter.WriteLine(); var shouldPrintTable = globalJsonInformation?.WorkloadVersionInstalled != false; var shouldShowWorkloadSetVersion = globalJsonInformation is not null || - InstallStateContents.FromPath(Path.Combine(WorkloadInstallType.GetInstallStateFolder(_workloadListHelper._currentSdkFeatureBand, _workloadListHelper.DotnetPath), "default.json")).UseWorkloadSets == true; + InstallStateContents.FromPath(Path.Combine(WorkloadInstallType.GetInstallStateFolder(_workloadListHelper._currentSdkFeatureBand, _workloadListHelper.UserLocalPath), "default.json")).UseWorkloadSets == true; if (shouldShowWorkloadSetVersion) { diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommand.cs b/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommand.cs index b315272a9817..c8cbab09d197 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommand.cs @@ -82,7 +82,7 @@ public override int Execute() { _workloadManifestUpdater.UpdateAdvertisingManifestsAsync( _includePreviews, - ShouldUseWorkloadSetMode(_sdkFeatureBand, _dotnetPath), + ShouldUseWorkloadSetMode(_sdkFeatureBand, _workloadRootDir), string.IsNullOrWhiteSpace(_fromCacheOption) ? null : new DirectoryPath(_fromCacheOption)) diff --git a/src/Common/WorkloadFileBasedInstall.cs b/src/Common/WorkloadFileBasedInstall.cs index c252671ce64d..3104d7f78a75 100644 --- a/src/Common/WorkloadFileBasedInstall.cs +++ b/src/Common/WorkloadFileBasedInstall.cs @@ -1,5 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.NET.Sdk.WorkloadManifestReader; namespace Microsoft.DotNet.Workloads.Workload { @@ -34,7 +35,7 @@ static int Last2DigitsTo0(int versionBuild) sdkFeatureBand = $"{sdkVersionParsed.Major}.{sdkVersionParsed.Minor}.{Last2DigitsTo0(sdkVersionParsed.Build)}"; } - return Path.Combine(dotnetDir, "metadata", "workloads", sdkFeatureBand, "userlocal"); + return Path.Combine(dotnetDir, "metadata", "workloads", new SdkFeatureBand(sdkFeatureBand).ToString(), "userlocal"); } } } diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/SdkDirectoryWorkloadManifestProvider.cs b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/SdkDirectoryWorkloadManifestProvider.cs index 51285fdcef14..eb1eb7cad932 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/SdkDirectoryWorkloadManifestProvider.cs +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/SdkDirectoryWorkloadManifestProvider.cs @@ -14,6 +14,7 @@ public partial class SdkDirectoryWorkloadManifestProvider : IWorkloadManifestPro public const string WorkloadSetsFolderName = "workloadsets"; private readonly string _sdkRootPath; + private readonly string _sdkOrUserLocalPath; private readonly SdkFeatureBand _sdkVersionBand; private readonly string[] _manifestRoots; private static HashSet _outdatedManifestIds = new(StringComparer.OrdinalIgnoreCase) { "microsoft.net.workload.android", "microsoft.net.workload.blazorwebassembly", "microsoft.net.workload.ios", @@ -67,6 +68,23 @@ internal SdkDirectoryWorkloadManifestProvider(string sdkRootPath, string sdkVers _workloadSetVersionFromConstructor = workloadSetVersion; _globalJsonPathFromConstructor = globalJsonPath; + string? userManifestsRoot = userProfileDir is null ? null : Path.Combine(userProfileDir, "sdk-manifests"); + string dotnetManifestRoot = Path.Combine(_sdkRootPath, "sdk-manifests"); + if (userManifestsRoot != null && WorkloadFileBasedInstall.IsUserLocal(_sdkRootPath, _sdkVersionBand.ToString()) && Directory.Exists(userManifestsRoot)) + { + _sdkOrUserLocalPath = userProfileDir ?? _sdkRootPath; + if (getEnvironmentVariable(EnvironmentVariableNames.WORKLOAD_MANIFEST_IGNORE_DEFAULT_ROOTS) == null) + { + _manifestRoots = new[] { userManifestsRoot, dotnetManifestRoot }; + } + } + else if (getEnvironmentVariable(EnvironmentVariableNames.WORKLOAD_MANIFEST_IGNORE_DEFAULT_ROOTS) == null) + { + _manifestRoots = new[] { dotnetManifestRoot }; + } + + _sdkOrUserLocalPath ??= _sdkRootPath; + var knownManifestIdsFilePath = Path.Combine(_sdkRootPath, "sdk", sdkVersion, "KnownWorkloadManifests.txt"); if (!File.Exists(knownManifestIdsFilePath)) { @@ -83,20 +101,6 @@ internal SdkDirectoryWorkloadManifestProvider(string sdkRootPath, string sdkVers } } - if (getEnvironmentVariable(EnvironmentVariableNames.WORKLOAD_MANIFEST_IGNORE_DEFAULT_ROOTS) == null) - { - string? userManifestsRoot = userProfileDir is null ? null : Path.Combine(userProfileDir, "sdk-manifests"); - string dotnetManifestRoot = Path.Combine(_sdkRootPath, "sdk-manifests"); - if (userManifestsRoot != null && WorkloadFileBasedInstall.IsUserLocal(_sdkRootPath, _sdkVersionBand.ToString()) && Directory.Exists(userManifestsRoot)) - { - _manifestRoots = new[] { userManifestsRoot, dotnetManifestRoot }; - } - else - { - _manifestRoots = new[] { dotnetManifestRoot }; - } - } - var manifestDirectoryEnvironmentVariable = getEnvironmentVariable(EnvironmentVariableNames.WORKLOAD_MANIFEST_ROOTS); if (manifestDirectoryEnvironmentVariable != null) { @@ -179,7 +183,7 @@ bool TryGetWorkloadSet(string workloadSetVersion, out WorkloadSet? workloadSet) if (_workloadSet is null) { - var installStateFilePath = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkVersionBand, _sdkRootPath), "default.json"); + var installStateFilePath = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkVersionBand, _sdkOrUserLocalPath), "default.json"); if (File.Exists(installStateFilePath)) { var installState = InstallStateContents.FromPath(installStateFilePath); diff --git a/test/dotnet-workload-update.Tests/GivenDotnetWorkloadUpdate.cs b/test/dotnet-workload-update.Tests/GivenDotnetWorkloadUpdate.cs index b6ae6ba8007c..bfc6f3ff6667 100644 --- a/test/dotnet-workload-update.Tests/GivenDotnetWorkloadUpdate.cs +++ b/test/dotnet-workload-update.Tests/GivenDotnetWorkloadUpdate.cs @@ -161,7 +161,17 @@ public void GivenWorkloadUpdateAcrossFeatureBandsItUpdatesPacks(bool userLocal) var updateParseResult = Parser.Instance.Parse(new string[] { "dotnet", "workload", "update", "--from-previous-sdk" }); var updateCommand = new WorkloadUpdateCommand(updateParseResult, reporter: _reporter, workloadResolverFactory, nugetPackageDownloader: nugetDownloader, workloadManifestUpdater: manifestUpdater, tempDirPath: testDirectory); + var installStatePath = Path.Combine(WorkloadInstallType.GetInstallStateFolder(new SdkFeatureBand(sdkFeatureVersion), installRoot), "default.json"); + var oldInstallState = InstallStateContents.FromPath(installStatePath); + oldInstallState.Manifests = new Dictionary() + { + {installingWorkload, $"6.0.102/{sdkFeatureVersion}" } + }; + Directory.CreateDirectory(Path.GetDirectoryName(installStatePath)); + File.WriteAllText(installStatePath, oldInstallState.ToString()); updateCommand.Execute(); + var newInstallState = InstallStateContents.FromPath(installStatePath); + newInstallState.Manifests.Should().BeNull(); foreach (var pack in workloadPacks) {