14
14
using Microsoft . DotNet . Cli . Utils ;
15
15
using Microsoft . DotNet . ToolPackage ;
16
16
using Microsoft . DotNet . Workloads . Workload . Install ;
17
+ using Microsoft . DotNet . Workloads . Workload . Update ;
17
18
using Microsoft . Extensions . EnvironmentAbstractions ;
18
19
using Microsoft . NET . Sdk . WorkloadManifestReader ;
19
20
using NuGet . Versioning ;
@@ -34,7 +35,8 @@ internal abstract class InstallingWorkloadCommand : WorkloadCommandBase
34
35
protected readonly SdkFeatureBand _sdkFeatureBand ;
35
36
protected readonly ReleaseVersion _targetSdkVersion ;
36
37
protected readonly string _fromRollbackDefinition ;
37
- protected string _workloadSetVersion ;
38
+ protected string _workloadSetVersionFromCommandLine ;
39
+ protected string _globalJsonPath ;
38
40
protected string _workloadSetVersionFromGlobalJson ;
39
41
protected readonly PackageSourceLocation _packageSourceLocation ;
40
42
protected readonly IWorkloadResolverFactory _workloadResolverFactory ;
@@ -44,6 +46,10 @@ internal abstract class InstallingWorkloadCommand : WorkloadCommandBase
44
46
protected IInstaller _workloadInstaller ;
45
47
protected IWorkloadManifestUpdater _workloadManifestUpdater ;
46
48
49
+ protected bool UseRollback => ! string . IsNullOrWhiteSpace ( _fromRollbackDefinition ) ;
50
+ protected bool SpecifiedWorkloadSetVersionOnCommandLine => ! string . IsNullOrWhiteSpace ( _workloadSetVersionFromCommandLine ) ;
51
+ protected bool SpecifiedWorkloadSetVersionInGlobalJson => ! string . IsNullOrWhiteSpace ( _workloadSetVersionFromGlobalJson ) ;
52
+
47
53
public InstallingWorkloadCommand (
48
54
ParseResult parseResult ,
49
55
IReporter reporter ,
@@ -60,6 +66,8 @@ public InstallingWorkloadCommand(
60
66
_downloadToCacheOption = parseResult . GetValue ( InstallingWorkloadCommandParser . DownloadToCacheOption ) ;
61
67
62
68
_fromRollbackDefinition = parseResult . GetValue ( InstallingWorkloadCommandParser . FromRollbackFileOption ) ;
69
+ _workloadSetVersionFromCommandLine = parseResult . GetValue ( InstallingWorkloadCommandParser . WorkloadSetVersionOption ) ;
70
+
63
71
var configOption = parseResult . GetValue ( InstallingWorkloadCommandParser . ConfigOption ) ;
64
72
var sourceOption = parseResult . GetValue ( InstallingWorkloadCommandParser . SourceOption ) ;
65
73
_packageSourceLocation = string . IsNullOrEmpty ( configOption ) && ( sourceOption == null || ! sourceOption . Any ( ) ) ? null :
@@ -91,70 +99,162 @@ public InstallingWorkloadCommand(
91
99
92
100
_workloadInstallerFromConstructor = workloadInstaller ;
93
101
_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
94
119
}
95
120
96
121
protected static Dictionary < string , string > GetInstallStateContents ( IEnumerable < ManifestVersionUpdate > manifestVersionUpdates ) =>
97
122
WorkloadSet . FromManifests (
98
123
manifestVersionUpdates . Select ( update => new WorkloadManifestInfo ( update . ManifestId . ToString ( ) , update . NewVersion . ToString ( ) , /* We don't actually use the directory here */ string . Empty , update . NewFeatureBand ) )
99
124
) . ToDictionaryForJson ( ) ;
100
125
101
- public static bool ShouldUseWorkloadSetMode ( SdkFeatureBand sdkFeatureBand , string dotnetDir )
126
+ InstallStateContents GetCurrentInstallState ( )
102
127
{
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 ) ;
106
129
}
107
130
108
- protected void ErrorIfGlobalJsonAndCommandLineMismatch ( string globaljsonPath )
131
+ static InstallStateContents GetCurrentInstallState ( SdkFeatureBand sdkFeatureBand , string dotnetDir )
109
132
{
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 ) ;
114
135
}
115
136
116
- protected bool TryHandleWorkloadUpdateFromVersion ( ITransactionContext context , DirectoryPath ? offlineCache , out IEnumerable < ManifestVersionUpdate > updates )
137
+ public static bool ShouldUseWorkloadSetMode ( SdkFeatureBand sdkFeatureBand , string dotnetDir )
117
138
{
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 ;
128
140
}
129
141
130
- public bool TryInstallWorkloadSet ( ITransactionContext context , out IEnumerable < ManifestVersionUpdate > updates , bool throwOnFailure = false )
142
+ protected void UpdateWorkloadManifests ( ITransactionContext context , DirectoryPath ? offlineCache )
131
143
{
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 )
134
146
{
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 ;
138
151
}
139
- else if ( _workloadInstaller is FileBasedInstaller || _workloadInstaller is NetSdkMsiInstallerClient )
152
+
153
+ if ( SpecifiedWorkloadSetVersionOnCommandLine )
140
154
{
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 ) )
143
161
{
144
- throw new NuGetPackageNotFoundException ( string . Format ( Update . LocalizableStrings . WorkloadVersionRequestedNotFound , _workloadSetVersionFromGlobalJson ?? _workloadSetVersion ) ) ;
162
+ _workloadInstaller . UpdateInstallMode ( _sdkFeatureBand , true ) ;
145
163
}
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 )
147
171
{
148
- Reporter . WriteLine ( Update . LocalizableStrings . NoWorkloadUpdateFound ) ;
172
+ resolvedWorkloadSetVersion = _workloadManifestUpdater . GetAdvertisedWorkloadSetVersion ( ) ;
149
173
}
150
- updates = null ;
151
- return false ;
152
174
}
153
175
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 ) ;
158
258
}
159
259
160
260
private void PrintWorkloadSetTransition ( string newVersion )
@@ -258,6 +358,17 @@ protected IEnumerable<WorkloadId> GetInstalledWorkloads(bool fromPreviousSdk)
258
358
return workloads ?? Enumerable . Empty < WorkloadId > ( ) ;
259
359
}
260
360
}
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
+ }
261
372
}
262
373
263
374
internal static class InstallingWorkloadCommandParser
0 commit comments