Skip to content

Commit 0c58854

Browse files
authored
Support package@version syntax for dotnet workload search/update/install version Fixes #42367 (#45156)
Fixes #42367 Using dotnet workload search version, you should now be able to provide package@version as an argument, and it will find the highest-valued workload version that has that package at that version. Sample when no such workload version was found: image When 3 were found: image (Note that the other two were 9.0.200.1 and 9.0.200, so this is the highest version number.) There are three total uses of dotnet workload search version that previously existed: Without any argument, it lists out the most recent SDKs (in the current feature band): dotnet workload search version --format json image When provided with a workload version, it lists out all the workloads in that workload version: dotnet workload search version 9.0.201 image (Note that this was a manually created 9.0.201 version, so those versions do not align with a real one.) And as mentioned earlier, when one or more workloads along with versions (concatenated with '@'), it finds a workload version that includes all of the provided workloads at the associated versions. dotnet workload search version [email protected] [email protected] image This PR also dramatically changes the help text either when you use dotnet workload search version -? or when you mistype a command. It now looks like this: image
1 parent 98842bc commit 0c58854

File tree

57 files changed

+1024
-162
lines changed

Some content is hidden

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

57 files changed

+1024
-162
lines changed

src/Cli/dotnet/CommonLocalizableStrings.resx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,10 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is
734734
<data name="ResponseFileNotFound" xml:space="preserve">
735735
<value>Response file '{0}' does not exist.</value>
736736
</data>
737+
<data name="ShortWorkloadSearchVersionDescription" xml:space="preserve">
738+
<value>Search for available workload versions or what comprises a workload version. Use 'dotnet workload search version --help' for more information.</value>
739+
<comment>{Locked="dotnet workload search version --help"}</comment>
740+
</data>
737741
<data name="CorruptSolutionProjectFolderStructure" xml:space="preserve">
738742
<value>The solution file '{0}' is missing EndProject tags or has invalid child-parent project folder mappings around project GUID: '{1}'. Manually repair the solution or try to open and save it in Visual Studio."</value>
739743
</data>

src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,8 @@ await Task.WhenAll(
567567

568568
if (stableVersions.Any())
569569
{
570-
return stableVersions.OrderByDescending(r => r.package.Identity.Version).Take(numberOfResults);
570+
var results = stableVersions.OrderByDescending(r => r.package.Identity.Version);
571+
return numberOfResults > 0 /* 0 indicates 'all' */ ? results.Take(numberOfResults) : results;
571572
}
572573
}
573574

src/Cli/dotnet/Parser.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ private static void SetHelpCustomizations(HelpBuilder builder)
237237
};
238238
builder.CustomizeSymbol(option, secondColumnText: descriptionCallback);
239239
}
240+
241+
builder.CustomizeSymbol(WorkloadSearchVersionsCommandParser.GetCommand(), secondColumnText: CommonLocalizableStrings.ShortWorkloadSearchVersionDescription);
240242
}
241243

242244
public void additionalOption(HelpContext context)

src/Cli/dotnet/commands/InstallingWorkloadCommand.cs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.CommandLine;
5-
using System.IO;
6-
using System.Linq;
7-
using System.Net.Http.Json;
8-
using System.Runtime.CompilerServices;
9-
using System.Text.Json;
10-
using System.Text.Json.Nodes;
115
using Microsoft.Deployment.DotNet.Releases;
126
using Microsoft.DotNet.Cli;
137
using Microsoft.DotNet.Cli.Commands.DotNetWorkloads;
@@ -16,6 +10,7 @@
1610
using Microsoft.DotNet.ToolPackage;
1711
using Microsoft.DotNet.Workloads.Workload.History;
1812
using Microsoft.DotNet.Workloads.Workload.Install;
13+
using Microsoft.DotNet.Workloads.Workload.Search;
1914
using Microsoft.DotNet.Workloads.Workload.Update;
2015
using Microsoft.Extensions.EnvironmentAbstractions;
2116
using Microsoft.NET.Sdk.WorkloadManifestReader;
@@ -41,7 +36,7 @@ internal abstract class InstallingWorkloadCommand : WorkloadCommandBase
4136
protected readonly string _fromRollbackDefinition;
4237
protected int _fromHistorySpecified;
4338
protected bool _historyManifestOnlyOption;
44-
protected string _workloadSetVersionFromCommandLine;
39+
protected IEnumerable<string> _workloadSetVersionFromCommandLine;
4540
protected string _globalJsonPath;
4641
protected string _workloadSetVersionFromGlobalJson;
4742
protected readonly PackageSourceLocation _packageSourceLocation;
@@ -55,7 +50,7 @@ internal abstract class InstallingWorkloadCommand : WorkloadCommandBase
5550

5651
protected bool UseRollback => !string.IsNullOrWhiteSpace(_fromRollbackDefinition);
5752
protected bool FromHistory => _fromHistorySpecified != 0;
58-
protected bool SpecifiedWorkloadSetVersionOnCommandLine => !string.IsNullOrWhiteSpace(_workloadSetVersionFromCommandLine);
53+
protected bool SpecifiedWorkloadSetVersionOnCommandLine => _workloadSetVersionFromCommandLine?.Any() == true;
5954
protected bool SpecifiedWorkloadSetVersionInGlobalJson => !string.IsNullOrWhiteSpace(_workloadSetVersionFromGlobalJson);
6055
protected WorkloadHistoryState _WorkloadHistoryRecord
6156
{
@@ -186,7 +181,7 @@ protected void UpdateWorkloadManifests(WorkloadHistoryRecorder recorder, ITransa
186181
{
187182
// This is essentially the same as updating to a specific workload set version, and we're now past the error check,
188183
// so we can just use the same code path.
189-
_workloadSetVersionFromCommandLine = _WorkloadHistoryRecord.WorkloadSetVersion;
184+
_workloadSetVersionFromCommandLine = [_WorkloadHistoryRecord.WorkloadSetVersion];
190185
}
191186
else if ((UseRollback || FromHistory) && updateToLatestWorkloadSet)
192187
{
@@ -214,7 +209,39 @@ protected void UpdateWorkloadManifests(WorkloadHistoryRecorder recorder, ITransa
214209
}
215210
}
216211

217-
string resolvedWorkloadSetVersion = _workloadSetVersionFromGlobalJson ??_workloadSetVersionFromCommandLine;
212+
string resolvedWorkloadSetVersion = null;
213+
214+
if (_workloadSetVersionFromCommandLine?.Any(v => v.Contains('@')) == true)
215+
{
216+
var versions = WorkloadSearchVersionsCommand.FindBestWorkloadSetsFromComponents(
217+
_sdkFeatureBand,
218+
_workloadInstaller is not NetSdkMsiInstallerClient ? _workloadInstaller : null,
219+
_sdkFeatureBand.IsPrerelease,
220+
PackageDownloader,
221+
_workloadSetVersionFromCommandLine,
222+
_workloadResolver,
223+
numberOfWorkloadSetsToTake: 1);
224+
225+
if (versions is null)
226+
{
227+
return;
228+
}
229+
else if (!versions.Any())
230+
{
231+
Reporter.WriteLine(Update.LocalizableStrings.NoWorkloadUpdateFound);
232+
return;
233+
}
234+
else
235+
{
236+
resolvedWorkloadSetVersion = versions.Single();
237+
}
238+
}
239+
else if (SpecifiedWorkloadSetVersionOnCommandLine)
240+
{
241+
resolvedWorkloadSetVersion = _workloadSetVersionFromCommandLine.Single();
242+
}
243+
244+
resolvedWorkloadSetVersion = _workloadSetVersionFromGlobalJson ?? resolvedWorkloadSetVersion;
218245
if (string.IsNullOrWhiteSpace(resolvedWorkloadSetVersion) && !UseRollback && !FromHistory)
219246
{
220247
_workloadManifestUpdater.UpdateAdvertisingManifestsAsync(_includePreviews, updateToLatestWorkloadSet, offlineCache).Wait();
@@ -441,9 +468,10 @@ protected IEnumerable<WorkloadId> WriteSDKInstallRecordsForVSWorkloads(IEnumerab
441468

442469
internal static class InstallingWorkloadCommandParser
443470
{
444-
public static readonly CliOption<string> WorkloadSetVersionOption = new("--version")
471+
public static readonly CliOption<IEnumerable<string>> WorkloadSetVersionOption = new("--version")
445472
{
446-
Description = Strings.WorkloadSetVersionOptionDescription
473+
Description = Strings.WorkloadSetVersionOptionDescription,
474+
AllowMultipleArgumentsPerToken = true
447475
};
448476

449477
public static readonly CliOption<bool> PrintDownloadLinkOnlyOption = new("--print-download-link-only")

src/Cli/dotnet/commands/dotnet-workload/install/IInstaller.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ internal interface IInstaller : IWorkloadManifestInstaller
1414
{
1515
int ExitCode { get; }
1616

17+
WorkloadSet GetWorkloadSetContents(string workloadVersion);
18+
1719
void InstallWorkloads(IEnumerable<WorkloadId> workloadIds, SdkFeatureBand sdkFeatureBand, ITransactionContext transactionContext, DirectoryPath? offlineCache = null);
1820

1921
void RepairWorkloads(IEnumerable<WorkloadId> workloadIds, SdkFeatureBand sdkFeatureBand, DirectoryPath? offlineCache = null);

src/Cli/dotnet/commands/dotnet-workload/install/LocalizableStrings.resx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@
327327
</data>
328328
<data name="CannotUseSkipManifestWithGlobalJsonWorkloadVersion" xml:space="preserve">
329329
<value>Cannot use the {0} option when workload version is specified in global.json. Remove the {0} option, or remove the 'workloadVersion' element from {1}.</value>
330-
<comment>"workloadVersion" and "global.json" are literal values and should not be translated.</comment>
330+
<comment>{Locked="workloadVersion"} Locked={"global.json"}</comment>
331331
</data>
332332
<data name="SkipSignCheckOptionDescription" xml:space="preserve">
333333
<value>Skip signature verification of workload packages and installers.</value>
@@ -348,7 +348,7 @@
348348
<value>Manifest MSI not found in NuGet package {0}</value>
349349
</data>
350350
<data name="WorkloadSetVersionOptionDescription" xml:space="preserve">
351-
<value>Update to the specified workload version.</value>
351+
<value>A workload version to display or one or more workloads and their versions joined by the '@' character.</value>
352352
</data>
353353
<data name="NewWorkloadSet" xml:space="preserve">
354354
<value>Installing workload version {0}.</value>
@@ -365,4 +365,8 @@
365365
<data name="FailedToInstallWorkloadSet" xml:space="preserve">
366366
<value>Failed to install workload version {0}: {1}</value>
367367
</data>
368+
<data name="CannotSpecifyVersionAndWorkloadIdsByComponent" xml:space="preserve">
369+
<value>Cannot specify a workload version using the --version option while also specifying versions to install using workload@version syntax.</value>
370+
<comment>{Locked="--version"}</comment>
371+
</data>
368372
</root>

src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerClient.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,5 +1183,10 @@ void IInstaller.UpdateInstallMode(SdkFeatureBand sdkFeatureBand, bool? newMode)
11831183
string newModeString = newMode == null ? "<null>" : newMode.Value ? WorkloadConfigCommandParser.UpdateMode_WorkloadSet : WorkloadConfigCommandParser.UpdateMode_Manifests;
11841184
Reporter.WriteLine(string.Format(LocalizableStrings.UpdatedWorkloadMode, newModeString));
11851185
}
1186+
1187+
// This method should never be called for this kind of installer. It is challenging to get this information from an MSI
1188+
// and totally unnecessary as the information is identical from a file-based installer. It was added to IInstaller only
1189+
// to facilitate testing. As a consequence, it does not need to be implemented.
1190+
public WorkloadSet GetWorkloadSetContents(string workloadVersion) => throw new NotImplementedException();
11861191
}
11871192
}

src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
using Microsoft.NET.Sdk.WorkloadManifestReader;
1212
using NuGet.Common;
1313
using NuGet.Versioning;
14-
using static Microsoft.NET.Sdk.WorkloadManifestReader.WorkloadResolver;
15-
using System.Text;
1614

1715
namespace Microsoft.DotNet.Workloads.Workload.Install
1816
{
@@ -39,7 +37,22 @@ public WorkloadInstallCommand(
3937
tempDirPath: tempDirPath)
4038
{
4139
_skipManifestUpdate = skipWorkloadManifestUpdate ?? parseResult.GetValue(WorkloadInstallCommandParser.SkipManifestUpdateOption);
42-
_workloadIds = workloadIds ?? parseResult.GetValue(WorkloadInstallCommandParser.WorkloadIdArgument).ToList().AsReadOnly();
40+
var unprocessedWorkloadIds = workloadIds ?? parseResult.GetValue(WorkloadInstallCommandParser.WorkloadIdArgument);
41+
if (unprocessedWorkloadIds?.Any(id => id.Contains('@')) == true)
42+
{
43+
_workloadIds = unprocessedWorkloadIds.Select(id => id.Split('@')[0]).ToList().AsReadOnly();
44+
if (SpecifiedWorkloadSetVersionOnCommandLine)
45+
{
46+
throw new GracefulException(LocalizableStrings.CannotSpecifyVersionAndWorkloadIdsByComponent, isUserError: true);
47+
}
48+
49+
_workloadSetVersionFromCommandLine = unprocessedWorkloadIds;
50+
}
51+
else
52+
{
53+
_workloadIds = unprocessedWorkloadIds.ToList().AsReadOnly();
54+
}
55+
4356
var resolvedReporter = _printDownloadLinkOnly ? NullReporter.Instance : Reporter;
4457

4558
_workloadInstaller = _workloadInstallerFromConstructor ??

src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.cs.xlf

Lines changed: 9 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.de.xlf

Lines changed: 9 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)