Skip to content

Commit 4f90ed8

Browse files
authored
Refactor workload set and workload install/update logic (#39991)
2 parents f9b4d89 + 979ac00 commit 4f90ed8

File tree

81 files changed

+1435
-863
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1435
-863
lines changed

src/Cli/Microsoft.DotNet.Cli.Utils/PathUtility.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public static bool TryDeleteDirectory(string directoryPath)
102102
/// and continues to its parent until it fails. Returns whether it succeeded
103103
/// in deleting the file it was intended to delete.
104104
/// </summary>
105-
public static bool DeleteFileAndEmptyParents(string path)
105+
public static bool DeleteFileAndEmptyParents(string path, int maxDirectoriesToDelete = int.MaxValue)
106106
{
107107
if (!File.Exists(path))
108108
{
@@ -112,9 +112,13 @@ public static bool DeleteFileAndEmptyParents(string path)
112112
File.Delete(path);
113113
var dir = Path.GetDirectoryName(path);
114114

115-
while (!Directory.EnumerateFileSystemEntries(dir).Any())
115+
int directoriesDeleted = 0;
116+
117+
while (!Directory.EnumerateFileSystemEntries(dir).Any() &&
118+
directoriesDeleted < maxDirectoriesToDelete)
116119
{
117120
Directory.Delete(dir);
121+
directoriesDeleted++;
118122
dir = Path.GetDirectoryName(dir);
119123
}
120124

src/Cli/dotnet/Installer/Windows/InstallMessageDispatcher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public InstallResponseMessage SendSaveInstallStateManifestVersions(SdkFeatureBan
183183
/// <param name="sdkFeatureBand">The SDK feature band of the install state file to write</param>
184184
/// <param name="newMode">Whether to use workload sets or not</param>
185185
/// <returns></returns>
186-
public InstallResponseMessage SendUpdateWorkloadModeRequest(SdkFeatureBand sdkFeatureBand, bool newMode)
186+
public InstallResponseMessage SendUpdateWorkloadModeRequest(SdkFeatureBand sdkFeatureBand, bool? newMode)
187187
{
188188
return Send(new InstallRequestMessage
189189
{

src/Cli/dotnet/Installer/Windows/InstallRequestMessage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public string WorkloadId
123123
/// <summary>
124124
/// The new mode to use: workloadset or loosemanifests
125125
/// </summary>
126-
public bool UseWorkloadSets
126+
public bool? UseWorkloadSets
127127
{
128128
get; set;
129129
}

src/Cli/dotnet/commands/InstallingWorkloadCommand.cs

Lines changed: 150 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.DotNet.Cli.Utils;
1515
using Microsoft.DotNet.ToolPackage;
1616
using Microsoft.DotNet.Workloads.Workload.Install;
17+
using Microsoft.DotNet.Workloads.Workload.Update;
1718
using Microsoft.Extensions.EnvironmentAbstractions;
1819
using Microsoft.NET.Sdk.WorkloadManifestReader;
1920
using NuGet.Versioning;
@@ -34,7 +35,8 @@ internal abstract class InstallingWorkloadCommand : WorkloadCommandBase
3435
protected readonly SdkFeatureBand _sdkFeatureBand;
3536
protected readonly ReleaseVersion _targetSdkVersion;
3637
protected readonly string _fromRollbackDefinition;
37-
protected string _workloadSetVersion;
38+
protected string _workloadSetVersionFromCommandLine;
39+
protected string _globalJsonPath;
3840
protected string _workloadSetVersionFromGlobalJson;
3941
protected readonly PackageSourceLocation _packageSourceLocation;
4042
protected readonly IWorkloadResolverFactory _workloadResolverFactory;
@@ -44,6 +46,10 @@ internal abstract class InstallingWorkloadCommand : WorkloadCommandBase
4446
protected IInstaller _workloadInstaller;
4547
protected IWorkloadManifestUpdater _workloadManifestUpdater;
4648

49+
protected bool UseRollback => !string.IsNullOrWhiteSpace(_fromRollbackDefinition);
50+
protected bool SpecifiedWorkloadSetVersionOnCommandLine => !string.IsNullOrWhiteSpace(_workloadSetVersionFromCommandLine);
51+
protected bool SpecifiedWorkloadSetVersionInGlobalJson => !string.IsNullOrWhiteSpace(_workloadSetVersionFromGlobalJson);
52+
4753
public InstallingWorkloadCommand(
4854
ParseResult parseResult,
4955
IReporter reporter,
@@ -60,6 +66,8 @@ public InstallingWorkloadCommand(
6066
_downloadToCacheOption = parseResult.GetValue(InstallingWorkloadCommandParser.DownloadToCacheOption);
6167

6268
_fromRollbackDefinition = parseResult.GetValue(InstallingWorkloadCommandParser.FromRollbackFileOption);
69+
_workloadSetVersionFromCommandLine = parseResult.GetValue(InstallingWorkloadCommandParser.WorkloadSetVersionOption);
70+
6371
var configOption = parseResult.GetValue(InstallingWorkloadCommandParser.ConfigOption);
6472
var sourceOption = parseResult.GetValue(InstallingWorkloadCommandParser.SourceOption);
6573
_packageSourceLocation = string.IsNullOrEmpty(configOption) && (sourceOption == null || !sourceOption.Any()) ? null :
@@ -91,70 +99,162 @@ public InstallingWorkloadCommand(
9199

92100
_workloadInstallerFromConstructor = workloadInstaller;
93101
_workloadManifestUpdaterFromConstructor = workloadManifestUpdater;
102+
103+
_globalJsonPath = SdkDirectoryWorkloadManifestProvider.GetGlobalJsonPath(Environment.CurrentDirectory);
104+
_workloadSetVersionFromGlobalJson = SdkDirectoryWorkloadManifestProvider.GlobalJsonReader.GetWorkloadVersionFromGlobalJson(_globalJsonPath);
105+
106+
if (SpecifiedWorkloadSetVersionInGlobalJson && (SpecifiedWorkloadSetVersionOnCommandLine || UseRollback))
107+
{
108+
throw new GracefulException(string.Format(Strings.CannotSpecifyVersionOnCommandLineAndInGlobalJson, _globalJsonPath), isUserError: true);
109+
}
110+
111+
if (SpecifiedWorkloadSetVersionOnCommandLine && UseRollback)
112+
{
113+
throw new GracefulException(string.Format(Update.LocalizableStrings.CannotCombineOptions,
114+
InstallingWorkloadCommandParser.FromRollbackFileOption.Name,
115+
InstallingWorkloadCommandParser.WorkloadSetVersionOption.Name), isUserError: true);
116+
}
117+
118+
// At this point, at most one of SpecifiedWorkloadSetVersionOnCommandLine, UseRollback, and SpecifiedWorkloadSetVersionInGlobalJson is true
94119
}
95120

96121
protected static Dictionary<string, string> GetInstallStateContents(IEnumerable<ManifestVersionUpdate> manifestVersionUpdates) =>
97122
WorkloadSet.FromManifests(
98123
manifestVersionUpdates.Select(update => new WorkloadManifestInfo(update.ManifestId.ToString(), update.NewVersion.ToString(), /* We don't actually use the directory here */ string.Empty, update.NewFeatureBand))
99124
).ToDictionaryForJson();
100125

101-
public static bool ShouldUseWorkloadSetMode(SdkFeatureBand sdkFeatureBand, string dotnetDir)
126+
InstallStateContents GetCurrentInstallState()
102127
{
103-
string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, dotnetDir), "default.json");
104-
var installStateContents = File.Exists(path) ? InstallStateContents.FromString(File.ReadAllText(path)) : new InstallStateContents();
105-
return installStateContents.UseWorkloadSets ?? false;
128+
return GetCurrentInstallState(_sdkFeatureBand, _dotnetPath);
106129
}
107130

108-
protected void ErrorIfGlobalJsonAndCommandLineMismatch(string globaljsonPath)
131+
static InstallStateContents GetCurrentInstallState(SdkFeatureBand sdkFeatureBand, string dotnetDir)
109132
{
110-
if (!string.IsNullOrWhiteSpace(_workloadSetVersionFromGlobalJson) && !string.IsNullOrWhiteSpace(_workloadSetVersion) && !_workloadSetVersion.Equals(_workloadSetVersionFromGlobalJson))
111-
{
112-
throw new Exception(string.Format(Strings.CannotSpecifyVersionOnCommandLineAndInGlobalJson, globaljsonPath));
113-
}
133+
string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, dotnetDir), "default.json");
134+
return InstallStateContents.FromPath(path);
114135
}
115136

116-
protected bool TryHandleWorkloadUpdateFromVersion(ITransactionContext context, DirectoryPath? offlineCache, out IEnumerable<ManifestVersionUpdate> updates)
137+
public static bool ShouldUseWorkloadSetMode(SdkFeatureBand sdkFeatureBand, string dotnetDir)
117138
{
118-
// Ensure workload set mode is set to 'workloadset'
119-
// Do not skip checking the mode first, as setting it triggers
120-
// an admin authorization popup for MSI-based installs.
121-
if (!ShouldUseWorkloadSetMode(_sdkFeatureBand, _dotnetPath))
122-
{
123-
_workloadInstaller.UpdateInstallMode(_sdkFeatureBand, true);
124-
}
125-
126-
_workloadManifestUpdater.DownloadWorkloadSet(_workloadSetVersionFromGlobalJson ?? _workloadSetVersion, offlineCache);
127-
return TryInstallWorkloadSet(context, out updates, throwOnFailure: true);
139+
return GetCurrentInstallState(sdkFeatureBand, dotnetDir).UseWorkloadSets ?? false;
128140
}
129141

130-
public bool TryInstallWorkloadSet(ITransactionContext context, out IEnumerable<ManifestVersionUpdate> updates, bool throwOnFailure = false)
142+
protected void UpdateWorkloadManifests(ITransactionContext context, DirectoryPath? offlineCache)
131143
{
132-
var advertisingPackagePath = Path.Combine(_userProfileDir, "sdk-advertising", _sdkFeatureBand.ToString(), "microsoft.net.workloads");
133-
if (File.Exists(Path.Combine(advertisingPackagePath, Constants.workloadSetVersionFileName)))
144+
var updateToLatestWorkloadSet = ShouldUseWorkloadSetMode(_sdkFeatureBand, _dotnetPath);
145+
if (UseRollback && updateToLatestWorkloadSet)
134146
{
135-
// This file isn't created in tests.
136-
var version = File.ReadAllText(Path.Combine(advertisingPackagePath, Constants.workloadSetVersionFileName));
137-
PrintWorkloadSetTransition(version);
147+
// Rollback files are only for loose manifests. Update the mode to be loose manifests.
148+
Reporter.WriteLine(Update.LocalizableStrings.UpdateFromRollbackSwitchesModeToLooseManifests);
149+
_workloadInstaller.UpdateInstallMode(_sdkFeatureBand, false);
150+
updateToLatestWorkloadSet = false;
138151
}
139-
else if (_workloadInstaller is FileBasedInstaller || _workloadInstaller is NetSdkMsiInstallerClient)
152+
153+
if (SpecifiedWorkloadSetVersionOnCommandLine)
140154
{
141-
// No workload sets found
142-
if (throwOnFailure)
155+
updateToLatestWorkloadSet = false;
156+
157+
// If a workload set version is specified, then switch to workload set update mode
158+
// Check to make sure the value needs to be changed, as updating triggers a UAC prompt
159+
// for MSI-based installs.
160+
if (!ShouldUseWorkloadSetMode(_sdkFeatureBand, _dotnetPath))
143161
{
144-
throw new NuGetPackageNotFoundException(string.Format(Update.LocalizableStrings.WorkloadVersionRequestedNotFound, _workloadSetVersionFromGlobalJson ?? _workloadSetVersion));
162+
_workloadInstaller.UpdateInstallMode(_sdkFeatureBand, true);
145163
}
146-
else
164+
}
165+
166+
string resolvedWorkloadSetVersion = _workloadSetVersionFromGlobalJson ??_workloadSetVersionFromCommandLine;
167+
if (string.IsNullOrWhiteSpace(resolvedWorkloadSetVersion) && !UseRollback)
168+
{
169+
_workloadManifestUpdater.UpdateAdvertisingManifestsAsync(_includePreviews, updateToLatestWorkloadSet, offlineCache).Wait();
170+
if (updateToLatestWorkloadSet)
147171
{
148-
Reporter.WriteLine(Update.LocalizableStrings.NoWorkloadUpdateFound);
172+
resolvedWorkloadSetVersion = _workloadManifestUpdater.GetAdvertisedWorkloadSetVersion();
149173
}
150-
updates = null;
151-
return false;
152174
}
153175

154-
var workloadSetPath = _workloadInstaller.InstallWorkloadSet(context, advertisingPackagePath);
155-
var files = Directory.EnumerateFiles(workloadSetPath, "*.workloadset.json");
156-
updates = _workloadManifestUpdater.ParseRollbackDefinitionFiles(files);
157-
return true;
176+
if (updateToLatestWorkloadSet && resolvedWorkloadSetVersion == null)
177+
{
178+
Reporter.WriteLine(Update.LocalizableStrings.NoWorkloadUpdateFound);
179+
return;
180+
}
181+
182+
IEnumerable<ManifestVersionUpdate> manifestsToUpdate;
183+
if (resolvedWorkloadSetVersion != null)
184+
{
185+
manifestsToUpdate = InstallWorkloadSet(context, resolvedWorkloadSetVersion);
186+
}
187+
else
188+
{
189+
manifestsToUpdate = UseRollback ? _workloadManifestUpdater.CalculateManifestRollbacks(_fromRollbackDefinition) :
190+
_workloadManifestUpdater.CalculateManifestUpdates().Select(m => m.ManifestUpdate);
191+
}
192+
193+
InstallStateContents oldInstallState = GetCurrentInstallState();
194+
195+
context.Run(
196+
action: () =>
197+
{
198+
foreach (var manifestUpdate in manifestsToUpdate)
199+
{
200+
_workloadInstaller.InstallWorkloadManifest(manifestUpdate, context, offlineCache);
201+
}
202+
203+
if (!SpecifiedWorkloadSetVersionInGlobalJson)
204+
{
205+
if (UseRollback)
206+
{
207+
_workloadInstaller.SaveInstallStateManifestVersions(_sdkFeatureBand, GetInstallStateContents(manifestsToUpdate));
208+
}
209+
else if (SpecifiedWorkloadSetVersionOnCommandLine)
210+
{
211+
_workloadInstaller.AdjustWorkloadSetInInstallState(_sdkFeatureBand, resolvedWorkloadSetVersion);
212+
}
213+
else if (this is WorkloadUpdateCommand)
214+
{
215+
// For workload updates, if you don't specify a rollback file, or a workload version then we should update to a new version of the manifests or workload set, and
216+
// should remove the install state that pins to the other version
217+
_workloadInstaller.RemoveManifestsFromInstallState(_sdkFeatureBand);
218+
_workloadInstaller.AdjustWorkloadSetInInstallState(_sdkFeatureBand, null);
219+
}
220+
}
221+
222+
_workloadResolver.RefreshWorkloadManifests();
223+
},
224+
rollback: () =>
225+
{
226+
// Reset install state
227+
var currentInstallState = GetCurrentInstallState();
228+
if (currentInstallState.UseWorkloadSets != oldInstallState.UseWorkloadSets)
229+
{
230+
_workloadInstaller.UpdateInstallMode(_sdkFeatureBand, oldInstallState.UseWorkloadSets);
231+
}
232+
233+
if ((currentInstallState.Manifests == null && oldInstallState.Manifests != null) ||
234+
(currentInstallState.Manifests != null && oldInstallState.Manifests == null) ||
235+
(currentInstallState.Manifests != null && oldInstallState.Manifests != null &&
236+
(currentInstallState.Manifests.Count != oldInstallState.Manifests.Count ||
237+
!currentInstallState.Manifests.All(m => oldInstallState.Manifests.TryGetValue(m.Key, out var val) && val.Equals(m.Value)))))
238+
{
239+
_workloadInstaller.SaveInstallStateManifestVersions(_sdkFeatureBand, oldInstallState.Manifests);
240+
}
241+
242+
if (currentInstallState.WorkloadVersion != oldInstallState.WorkloadVersion)
243+
{
244+
_workloadInstaller.AdjustWorkloadSetInInstallState(_sdkFeatureBand, oldInstallState.WorkloadVersion);
245+
}
246+
247+
// We will refresh the workload manifests to make sure that the resolver has the updated state after the rollback
248+
_workloadResolver.RefreshWorkloadManifests();
249+
});
250+
}
251+
252+
private IEnumerable<ManifestVersionUpdate> InstallWorkloadSet(ITransactionContext context, string workloadSetVersion)
253+
{
254+
PrintWorkloadSetTransition(workloadSetVersion);
255+
var workloadSet = _workloadInstaller.InstallWorkloadSet(context, workloadSetVersion);
256+
257+
return _workloadManifestUpdater.CalculateManifestUpdatesForWorkloadSet(workloadSet);
158258
}
159259

160260
private void PrintWorkloadSetTransition(string newVersion)
@@ -258,6 +358,17 @@ protected IEnumerable<WorkloadId> GetInstalledWorkloads(bool fromPreviousSdk)
258358
return workloads ?? Enumerable.Empty<WorkloadId>();
259359
}
260360
}
361+
362+
protected IEnumerable<WorkloadId> WriteSDKInstallRecordsForVSWorkloads(IEnumerable<WorkloadId> workloadsWithExistingInstallRecords)
363+
{
364+
#if !DOT_NET_BUILD_FROM_SOURCE
365+
if (OperatingSystem.IsWindows())
366+
{
367+
return VisualStudioWorkloads.WriteSDKInstallRecordsForVSWorkloads(_workloadInstaller, _workloadResolver, workloadsWithExistingInstallRecords, Reporter);
368+
}
369+
#endif
370+
return workloadsWithExistingInstallRecords;
371+
}
261372
}
262373

263374
internal static class InstallingWorkloadCommandParser

0 commit comments

Comments
 (0)