Skip to content

Commit 8d1e1e4

Browse files
authored
Merge pull request #24214 from dotnet-maestro-bot/merge/release/6.0.3xx-to-main
[automated] Merge branch 'release/6.0.3xx' => 'main'
2 parents a7e26eb + e71ac5c commit 8d1e1e4

31 files changed

+330
-73
lines changed

build/RunTestsOnHelix.cmd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,5 @@ robocopy %HELIX_CORRELATION_PAYLOAD%\t\TestExecutionDirectoryFiles %TestExecutio
2727

2828
REM call dotnet new so the first run message doesn't interfere with the first test
2929
dotnet new
30+
REM avoid potetial cocurrency issues when nuget is creating nuget.config
31+
dotnet nuget list source

build/RunTestsOnHelix.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ cp -a $HELIX_CORRELATION_PAYLOAD/t/TestExecutionDirectoryFiles/. $TestExecutionD
1616

1717
# call dotnet new so the first run message doesn't interfere with the first test
1818
dotnet new
19+
# avoid potetial concurrency issues when nuget is creating nuget.config
20+
dotnet nuget list source

eng/Versions.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
<DeploymentReleasesVersion>1.0.0-preview1.1.21112.1</DeploymentReleasesVersion>
3939
<SystemManagementPackageVersion>4.6.0</SystemManagementPackageVersion>
4040
<SystemCommandLineVersion>2.0.0-beta3.22151.2</SystemCommandLineVersion>
41-
<MicrosoftDeploymentDotNetReleasesVersion>1.0.0-preview2.6.21561.1</MicrosoftDeploymentDotNetReleasesVersion>
41+
<MicrosoftDeploymentDotNetReleasesVersion>1.0.0-preview3.6.22115.1</MicrosoftDeploymentDotNetReleasesVersion>
42+
<MicrosoftVisualStudioSetupConfigurationInteropVersion>3.0.4496</MicrosoftVisualStudioSetupConfigurationInteropVersion>
4243
</PropertyGroup>
4344
<PropertyGroup>
4445
<!-- Dependencies from https://github.com/dotnet/runtime -->

src/Cli/dotnet/commands/dotnet-workload/install/NetSdkManagedInstaller.cs renamed to src/Cli/dotnet/commands/dotnet-workload/install/FileBasedInstaller.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
namespace Microsoft.DotNet.Workloads.Workload.Install
2222
{
23-
internal class NetSdkManagedInstaller : IWorkloadPackInstaller
23+
internal class FileBasedInstaller : IWorkloadPackInstaller
2424
{
2525
private readonly IReporter _reporter;
2626
private readonly string _workloadMetadataDir;
@@ -31,13 +31,13 @@ internal class NetSdkManagedInstaller : IWorkloadPackInstaller
3131
private readonly INuGetPackageDownloader _nugetPackageDownloader;
3232
private readonly IWorkloadResolver _workloadResolver;
3333
private readonly SdkFeatureBand _sdkFeatureBand;
34-
private readonly NetSdkManagedInstallationRecordRepository _installationRecordRepository;
34+
private readonly FileBasedInstallationRecordRepository _installationRecordRepository;
3535
private readonly PackageSourceLocation _packageSourceLocation;
3636
private readonly RestoreActionConfig _restoreActionConfig;
3737

3838
public int ExitCode => 0;
3939

40-
public NetSdkManagedInstaller(IReporter reporter,
40+
public FileBasedInstaller(IReporter reporter,
4141
SdkFeatureBand sdkFeatureBand,
4242
IWorkloadResolver workloadResolver,
4343
string userProfileDir,
@@ -62,7 +62,7 @@ public NetSdkManagedInstaller(IReporter reporter,
6262
_reporter = reporter;
6363
_sdkFeatureBand = sdkFeatureBand;
6464
_workloadResolver = workloadResolver;
65-
_installationRecordRepository = new NetSdkManagedInstallationRecordRepository(_workloadMetadataDir);
65+
_installationRecordRepository = new FileBasedInstallationRecordRepository(_workloadMetadataDir);
6666
_packageSourceLocation = packageSourceLocation;
6767
}
6868

@@ -78,7 +78,7 @@ public IWorkloadPackInstaller GetPackInstaller()
7878

7979
public IWorkloadInstaller GetWorkloadInstaller()
8080
{
81-
throw new Exception("NetSdkManagedInstaller is not a workload installer.");
81+
throw new Exception($"{nameof(FileBasedInstaller)} is not a workload installer.");
8282
}
8383

8484
public IWorkloadInstallationRecordRepository GetWorkloadInstallationRecordRepository()
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99

1010
namespace Microsoft.DotNet.Workloads.Workload.Install.InstallRecord
1111
{
12-
internal class NetSdkManagedInstallationRecordRepository : IWorkloadInstallationRecordRepository
12+
internal class FileBasedInstallationRecordRepository : IWorkloadInstallationRecordRepository
1313
{
1414
private readonly string _workloadMetadataDir;
1515
private const string InstalledWorkloadDir = "InstalledWorkloads";
1616

17-
public NetSdkManagedInstallationRecordRepository(string workloadMetadataDir)
17+
public FileBasedInstallationRecordRepository(string workloadMetadataDir)
1818
{
1919
_workloadMetadataDir = workloadMetadataDir;
2020
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static IInstaller GetWorkloadInstaller(
4848

4949
userProfileDir ??= CliFolderPathCalculator.DotnetUserProfileFolderPath;
5050

51-
return new NetSdkManagedInstaller(reporter,
51+
return new FileBasedInstaller(reporter,
5252
sdkFeatureBand,
5353
workloadResolver,
5454
userProfileDir,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using Microsoft.NET.Sdk.WorkloadManifestReader;
7+
8+
namespace Microsoft.DotNet.Workloads.Workload.List
9+
{
10+
internal class InstalledWorkloadsCollection
11+
{
12+
private Dictionary<string, string> _workloads;
13+
14+
/// <summary>
15+
/// Creates a new <see cref="InstalledWorkloadsCollection"/> instance using a collection of workload IDs
16+
/// and a common installation source.
17+
/// </summary>
18+
/// <param name="workloadIds">A collection of workload identifiers.</param>
19+
/// <param name="installationSource">A string describing the installation source of the workload identifiers.</param>
20+
public InstalledWorkloadsCollection(IEnumerable<WorkloadId> workloadIds, string installationSource)
21+
{
22+
_workloads = new Dictionary<string, string>(workloadIds.Select(id => new KeyValuePair<string, string>(id.ToString(), installationSource)));
23+
}
24+
25+
public IEnumerable<KeyValuePair<string, string>> AsEnumerable() =>
26+
_workloads.AsEnumerable();
27+
28+
/// <summary>
29+
/// Adds a new workload ID and installation source. If the ID already exists, the source is appended.
30+
/// </summary>
31+
/// <param name="workloadId">The ID of the workload to update.</param>
32+
/// <param name="installationSource">A string describing the installation soruce of the workload.</param>
33+
public void Add(string workloadId, string installationSource)
34+
{
35+
if (!_workloads.ContainsKey(workloadId))
36+
{
37+
_workloads[workloadId] = installationSource;
38+
}
39+
else
40+
{
41+
_workloads[workloadId] += $", {installationSource}";
42+
}
43+
}
44+
}
45+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
<data name="WorkloadListFooter" xml:space="preserve">
130130
<value>Use `dotnet workload search` to find additional workloads to install.</value>
131131
</data>
132-
<data name="WorkloadListHeader" xml:space="preserve">
133-
<value>This command lists only workloads that were installed via `dotnet workload install` in this version of the SDK and not those that were installed via Visual Studio.</value>
132+
<data name="WorkloadSourceColumn" xml:space="preserve">
133+
<value>Installation Source</value>
134134
</data>
135-
</root>
135+
</root>
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Runtime.InteropServices;
8+
using System.Runtime.Versioning;
9+
using Microsoft.Deployment.DotNet.Releases;
10+
using Microsoft.DotNet.Workloads.Workload.Install.InstallRecord;
11+
using Microsoft.NET.Sdk.WorkloadManifestReader;
12+
using Microsoft.VisualStudio.Setup.Configuration;
13+
14+
#nullable disable
15+
16+
namespace Microsoft.DotNet.Workloads.Workload.List
17+
{
18+
/// <summary>
19+
/// Provides functionality to query the status of .NET workloads in Visual Studio.
20+
/// </summary>
21+
#if NETCOREAPP
22+
[SupportedOSPlatform("windows")]
23+
#endif
24+
internal static class VisualStudioWorkloads
25+
{
26+
private const int REGDB_E_CLASSNOTREG = unchecked((int)0x80040154);
27+
28+
/// <summary>
29+
/// Visual Studio product ID filters. We dont' want to query SKUs such as Server, TeamExplorer, TestAgent
30+
/// TestController and BuildTools.
31+
/// </summary>
32+
private static readonly string[] s_visualStudioProducts = new string[]
33+
{
34+
"Microsoft.VisualStudio.Product.Community",
35+
"Microsoft.VisualStudio.Product.Professional",
36+
"Microsoft.VisualStudio.Product.Enterprise",
37+
};
38+
39+
/// <summary>
40+
/// The SWIX package ID wrapping the SDK installer in Visual Studio. The ID should contain
41+
/// the SDK version as a suffix, e.g., "Microsoft.NetCore.Toolset.5.0.403".
42+
/// </summary>
43+
private static readonly string s_visualStudioSdkPackageIdPrefix = "Microsoft.NetCore.Toolset.";
44+
45+
/// <summary>
46+
/// Gets a set of workload components based on the available set of workloads for the current SDK.
47+
/// </summary>
48+
/// <param name="workloadResolver">The workload resolver used to obtain available workloads.</param>
49+
/// <returns>A collection of Visual Studio component IDs corresponding to workload IDs.</returns>
50+
internal static IEnumerable<string> GetAvailableVisualStudioWorkloads(IWorkloadResolver workloadResolver) =>
51+
workloadResolver.GetAvailableWorkloads().Select(w => w.Id.ToString().Replace('-', '.'));
52+
53+
/// <summary>
54+
/// Finds all workloads installed by all Visual Studio instances given that the
55+
/// SDK installed by an instance matches the feature band of the currently executing SDK.
56+
/// </summary>
57+
/// <param name="workloadResolver">The workload resolver used to obtain available workloads.</param>
58+
/// <param name="sdkFeatureBand">The feature band of the executing SDK.</param>
59+
/// <param name="installedWorkloads">The collection of installed workloads to update.</param>
60+
internal static void GetInstalledWorkloads(IWorkloadResolver workloadResolver, SdkFeatureBand sdkFeatureBand,
61+
InstalledWorkloadsCollection installedWorkloads)
62+
{
63+
IEnumerable<string> visualStudioWorkloadIds = GetAvailableVisualStudioWorkloads(workloadResolver);
64+
HashSet<string> installedWorkloadComponents = new();
65+
66+
// Visual Studio instances contain a large set of packages and we have to perform a linear
67+
// search to determine whether a matching SDK was installed and look for each installable
68+
// workload from the SDK. The search is optimized to only scan each set of packages once.
69+
foreach (ISetupInstance2 instance in GetVisualStudioInstances())
70+
{
71+
ISetupPackageReference[] packages = instance.GetPackages();
72+
bool hasMatchingSdk = false;
73+
installedWorkloadComponents.Clear();
74+
75+
for (int i = 0; i < packages.Length; i++)
76+
{
77+
string packageId = packages[i].GetId();
78+
79+
if (string.IsNullOrWhiteSpace(packageId))
80+
{
81+
// Visual Studio already verifies the setup catalog at build time. If the package ID is empty
82+
// the catalog is likely corrupted.
83+
continue;
84+
}
85+
86+
if (packageId.StartsWith(s_visualStudioSdkPackageIdPrefix))
87+
{
88+
// After trimming the package prefix we should be left with a valid semantic version. If we can't
89+
// parse the version we'll skip this instance.
90+
if (!ReleaseVersion.TryParse(packageId.Substring(s_visualStudioSdkPackageIdPrefix.Length),
91+
out ReleaseVersion visualStudioSdkVersion))
92+
{
93+
break;
94+
}
95+
96+
SdkFeatureBand visualStudioSdkFeatureBand = new SdkFeatureBand(visualStudioSdkVersion);
97+
98+
// The feature band of the SDK in VS must match that of the SDK on which we're running.
99+
if (!visualStudioSdkFeatureBand.Equals(sdkFeatureBand))
100+
{
101+
break;
102+
}
103+
104+
hasMatchingSdk = true;
105+
106+
continue;
107+
}
108+
109+
if (visualStudioWorkloadIds.Contains(packageId, StringComparer.OrdinalIgnoreCase))
110+
{
111+
// Normalize back to an SDK style workload ID.
112+
installedWorkloadComponents.Add(packageId.Replace('.', '-'));
113+
}
114+
}
115+
116+
if (hasMatchingSdk)
117+
{
118+
foreach (string id in installedWorkloadComponents)
119+
{
120+
installedWorkloads.Add(id, $"VS {instance.GetInstallationVersion()}");
121+
}
122+
}
123+
}
124+
}
125+
126+
/// <summary>
127+
/// Gets a list of all Visual Studio instances.
128+
/// </summary>
129+
/// <returns>A list of Visual Studio instances.</returns>
130+
private static List<ISetupInstance> GetVisualStudioInstances()
131+
{
132+
List<ISetupInstance> vsInstances = new();
133+
134+
try
135+
{
136+
SetupConfiguration setupConfiguration = new();
137+
ISetupConfiguration2 setupConfiguration2 = setupConfiguration;
138+
IEnumSetupInstances setupInstances = setupConfiguration2.EnumInstances();
139+
ISetupInstance[] instances = new ISetupInstance[1];
140+
int fetched = 0;
141+
142+
do
143+
{
144+
setupInstances.Next(1, instances, out fetched);
145+
146+
if (fetched > 0)
147+
{
148+
ISetupInstance2 instance = (ISetupInstance2)instances[0];
149+
150+
// .NET Workloads only shipped in 17.0 and later and we should only look at IDE based SKUs
151+
// such as community, professional, and enterprise.
152+
if (Version.TryParse(instance.GetInstallationVersion(), out Version version) &&
153+
version.Major >= 17 &&
154+
s_visualStudioProducts.Contains(instance.GetProduct().GetId()))
155+
{
156+
vsInstances.Add(instances[0]);
157+
}
158+
}
159+
}
160+
while (fetched > 0);
161+
162+
}
163+
catch (COMException e) when (e.ErrorCode == REGDB_E_CLASSNOTREG)
164+
{
165+
// Query API not registered, good indication there are no VS installations of 15.0 or later.
166+
// Other exceptions are passed through since that likely points to a real error.
167+
}
168+
169+
return vsInstances;
170+
}
171+
}
172+
}

src/Cli/dotnet/commands/dotnet-workload/list/WorkloadListCommand.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ internal class WorkloadListCommand : CommandBase
3636
private readonly VerbosityOptions _verbosity;
3737
private readonly IWorkloadManifestUpdater _workloadManifestUpdater;
3838
private readonly IWorkloadInstallationRecordRepository _workloadRecordRepo;
39+
private readonly IWorkloadResolver _workloadResolver;
3940

4041
public WorkloadListCommand(
4142
ParseResult result,
@@ -80,14 +81,14 @@ public WorkloadListCommand(
8081
new FirstPartyNuGetPackageSigningVerifier(tempPackagesDir, nullLogger),
8182
verboseLogger: nullLogger,
8283
restoreActionConfig: _parseResult.ToRestoreActionConfig());
83-
workloadResolver ??= WorkloadResolver.Create(workloadManifestProvider, _dotnetPath, currentSdkReleaseVersion.ToString(), _userProfileDir);
84+
_workloadResolver = workloadResolver ?? WorkloadResolver.Create(workloadManifestProvider, _dotnetPath, currentSdkReleaseVersion.ToString(), _userProfileDir);
8485

8586
_workloadRecordRepo = workloadRecordRepo ??
86-
WorkloadInstallerFactory.GetWorkloadInstaller(reporter, _currentSdkFeatureBand, workloadResolver, _verbosity, _userProfileDir,
87+
WorkloadInstallerFactory.GetWorkloadInstaller(reporter, _currentSdkFeatureBand, _workloadResolver, _verbosity, _userProfileDir,
8788
elevationRequired: false).GetWorkloadInstallationRecordRepository();
8889

8990
_workloadManifestUpdater = workloadManifestUpdater ?? new WorkloadManifestUpdater(_reporter,
90-
workloadResolver, _nugetPackageDownloader, _userProfileDir, _tempDirPath, _workloadRecordRepo);
91+
_workloadResolver, _nugetPackageDownloader, _userProfileDir, _tempDirPath, _workloadRecordRepo);
9192
}
9293

9394
public override int Execute()
@@ -116,17 +117,20 @@ public override int Execute()
116117
}
117118
else
118119
{
120+
InstalledWorkloadsCollection installedWorkloads = new(installedList, $"SDK {_currentSdkFeatureBand}");
121+
119122
if (OperatingSystem.IsWindows())
120123
{
121-
_reporter.WriteLine();
122-
_reporter.WriteLine(LocalizableStrings.WorkloadListHeader);
124+
VisualStudioWorkloads.GetInstalledWorkloads(_workloadResolver, _currentSdkFeatureBand, installedWorkloads);
123125
}
126+
124127
_reporter.WriteLine();
125128

126-
PrintableTable<WorkloadId> table = new();
127-
table.AddColumn(LocalizableStrings.WorkloadIdColumn, workloadId => workloadId.ToString());
129+
PrintableTable<KeyValuePair<string, string>> table = new();
130+
table.AddColumn(LocalizableStrings.WorkloadIdColumn, workload => workload.Key);
131+
table.AddColumn(LocalizableStrings.WorkloadSourceColumn, workload => workload.Value);
128132

129-
table.PrintRows(installedList, l => _reporter.WriteLine(l));
133+
table.PrintRows(installedWorkloads.AsEnumerable(), l => _reporter.WriteLine(l));
130134

131135
_reporter.WriteLine();
132136
_reporter.WriteLine(LocalizableStrings.WorkloadListFooter);

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
<target state="translated">Pokud chcete najít další úlohy, které se mají nainstalovat, použijte vyhledávání úloh dotnet.</target>
1818
<note />
1919
</trans-unit>
20-
<trans-unit id="WorkloadListHeader">
21-
<source>This command lists only workloads that were installed via `dotnet workload install` in this version of the SDK and not those that were installed via Visual Studio.</source>
22-
<target state="translated">Tento příkaz vypíše pouze úlohy nainstalované prostřednictvím instalace úlohy dotnet v této verzi sady SDK a ne ty, které byly nainstalovány prostřednictvím sady Visual Studio.</target>
20+
<trans-unit id="WorkloadSourceColumn">
21+
<source>Installation Source</source>
22+
<target state="new">Installation Source</target>
2323
<note />
2424
</trans-unit>
2525
<trans-unit id="WorkloadUpdatesAvailable">

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
<target state="translated">Verwenden Sie „dotnet workload search“, um zusätzliche zu installierende Workloads zu finden.</target>
1818
<note />
1919
</trans-unit>
20-
<trans-unit id="WorkloadListHeader">
21-
<source>This command lists only workloads that were installed via `dotnet workload install` in this version of the SDK and not those that were installed via Visual Studio.</source>
22-
<target state="translated">Dieser Befehl führt nur Workloads auf, die über „dotnet workload install“ in dieser SDK-Version installiert wurden, und nicht die Workloads, die über Visual Studio installiert wurden.</target>
20+
<trans-unit id="WorkloadSourceColumn">
21+
<source>Installation Source</source>
22+
<target state="new">Installation Source</target>
2323
<note />
2424
</trans-unit>
2525
<trans-unit id="WorkloadUpdatesAvailable">

0 commit comments

Comments
 (0)