Skip to content

Commit 1a3daeb

Browse files
committed
Enable regenerating the runsettings file for a particular configuration so OmniSharp will run tests against the correct runtime.
Refactor OutputConfiguration into a separate file from userPrompts. Fixes #8 as best as possible. This still depends on either the PATH and DOTNET_ROOT being set to the correct folder (like how the -vs flag on the dotnet/runtime build.cmd script has), a global SDK being installed with a new enough version, or dotnet/vscode-csharp#4738 being implemented.
1 parent fde4ea9 commit 1a3daeb

16 files changed

+149
-53
lines changed

.editorconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[*.cs]
2+
dotnet_diagnostic.VSTHRD200.severity = none

.vscode/launch.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@
2222
"type": "coreclr",
2323
"request": "attach"
2424
},
25+
{
26+
"name": "Launch Assistant Server",
27+
"type": "coreclr",
28+
"request": "launch",
29+
"preLaunchTask": "Server - Single Build",
30+
"program": "${workspaceFolder}/out/server/AssistantServer.dll",
31+
"args": [],
32+
"cwd": "${workspaceFolder}/out",
33+
"stopAtEntry": true,
34+
"console": "internalConsole",
35+
},
2536
{
2637
"name": "Extension Tests",
2738
"type": "extensionHost",
@@ -36,4 +47,4 @@
3647
"preLaunchTask": "${defaultBuildTask}"
3748
}
3849
]
39-
}
50+
}

.vscode/tasks.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
}
4242
},
4343
{
44+
"label": "Server - Single Build",
4445
"type": "npm",
4546
"script": "publish-server",
4647
"problemMatcher": "$msCompile",

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
"Other"
1919
],
2020
"activationEvents": [
21-
"onCommand:dotnet-runtime-test-assistant.importSettingsFromDevContainer",
2221
"onCommand:dotnet-runtime-test-assistant.startServerIfNotStarted",
22+
"onCommand:dotnet-runtime-test-assistant.importSettingsFromDevContainer",
23+
"onCommand:dotnet-runtime-test-assistant.configureRunSettingsFileForTestRun",
2324
"onDebugResolve:rt-runtimetest",
2425
"onDebugDynamicConfigurations:rt-runtimetest",
2526
"onDebugResolve:rt-cg2corelib",
@@ -39,6 +40,10 @@
3940
{
4041
"command": "dotnet-runtime-test-assistant.importSettingsFromDevContainer",
4142
"title": "DN/RT-Test: Import settings from .devcontainer file"
43+
},
44+
{
45+
"command": "dotnet-runtime-test-assistant.configureRunSettingsFileForTestRun",
46+
"title": "DN/RT-Test: Configure the .runsettings file for running tests in VSCode"
4247
}
4348
],
4449
"debuggers": [

src/commands.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import path = require('path');
22
import * as vscode from 'vscode';
33
import { parse } from 'jsonc-parser';
4-
import { getRuntimeWorkspaceFolder } from './userPrompts';
4+
import { getRuntimeWorkspaceFolder, promptUserForTargetConfiguration } from './userPrompts';
55
import { readFile } from 'fs/promises';
6+
import { tryCreateVsCodeRunSettings } from './server';
67

78
// Import settings from the devcontainer configuration file into the local .vscode/settings.json file
89
// if the setting is not already specified.
@@ -26,4 +27,21 @@ export async function importSettingsFromDevContainer() {
2627
}
2728
}
2829
}
30+
}
31+
32+
export async function configureRunSettingsFileForTestRun() {
33+
const workspace = await getRuntimeWorkspaceFolder();
34+
if (!workspace) {
35+
return;
36+
}
37+
const configuration = await promptUserForTargetConfiguration({ promptPrefix: 'Libraries Test Host', showChecked: false, defaultConfiguration: 'Debug' });
38+
if (!configuration) {
39+
return;
40+
}
41+
const success = await tryCreateVsCodeRunSettings(path.join(workspace.fsPath, 'src', 'libraries', 'pretest.proj'), configuration);
42+
if (success) {
43+
vscode.window.showInformationMessage('Regenerated the runsettings file for running .NET tests');
44+
} else {
45+
vscode.window.showErrorMessage('Unable to regenerate runsettings file. Check the dotnet-runtime-assistant-server log output for details');
46+
}
2947
}

src/extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import * as crossgen2CoreLibTestProvider from './providers/crossgen2-corelib';
44
import * as crossgen2TestProvider from './providers/crossgen2';
55
import * as libsNativeTestProvider from './providers/libs-native';
66
import { setServerPathFromExtensionContext, getOrStartServerConnection } from './server';
7-
import { importSettingsFromDevContainer } from './commands';
7+
import { configureRunSettingsFileForTestRun, importSettingsFromDevContainer } from './commands';
88

99
// this method is called when your extension is activated
1010
// your extension is activated the very first time the command is executed
1111
export function activate(context: vscode.ExtensionContext) {
1212
context.subscriptions.push(vscode.commands.registerCommand('dotnet-runtime-test-assistant.startServerIfNotStarted', getOrStartServerConnection));
1313
context.subscriptions.push(vscode.commands.registerCommand('dotnet-runtime-test-assistant.importSettingsFromDevContainer', importSettingsFromDevContainer));
14+
context.subscriptions.push(vscode.commands.registerCommand('dotnet-runtime-test-assistant.configureRunSettingsFileForTestRun', configureRunSettingsFileForTestRun));
1415

1516
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider(runtimeTestProvider.DEBUG_CONFIGURATION_TYPE, runtimeTestProvider, vscode.DebugConfigurationProviderTriggerKind.Dynamic));
1617
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider(runtimeTestProvider.DEBUG_CONFIGURATION_TYPE, runtimeTestProvider));

src/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
import * as path from 'path';
33
import { DebugConfiguration, Uri } from 'vscode';
4-
import { OutputConfiguration } from './userPrompts';
4+
import { OutputConfiguration } from './outputConfiguration';
55

66
export function transformEnvBlock(env: { [key: string]: string }): { key: string, value: string }[] {
77
let result = [];

src/outputConfiguration.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
export type OutputConfiguration = { os: string; arch: string; configuration: string; };

src/providers/crossgen2-corelib.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import * as path from 'path';
22
import * as vscode from 'vscode';
33
import * as userPrompts from '../userPrompts';
44
import { DebugConfigurationBase, getCoreClrOutputRootPath } from '../helpers';
5+
import { OutputConfiguration } from '../outputConfiguration';
56

67
type DebugConfigurationType = 'rt-cg2corelib';
78

8-
export const DEBUG_CONFIGURATION_TYPE : DebugConfigurationType = 'rt-cg2corelib';
9+
export const DEBUG_CONFIGURATION_TYPE: DebugConfigurationType = 'rt-cg2corelib';
910

10-
interface Crossgen2CoreLibConfiguration extends DebugConfigurationBase
11-
{
11+
interface Crossgen2CoreLibConfiguration extends DebugConfigurationBase {
1212
type: DebugConfigurationType;
1313
separateConfig?: boolean;
1414
}
@@ -19,8 +19,8 @@ function isCrossgen2CoreLibConfiguration(debugConfiguration: vscode.DebugConfigu
1919

2020
export function transformConfigForCoreLib(debugConfiguration: Crossgen2CoreLibConfiguration, crossgenBuildCoreClrBin: string, corelibBuildCoreClrBin: string, targetArch: string) {
2121
return {
22-
name : debugConfiguration.name,
23-
type : 'coreclr',
22+
name: debugConfiguration.name,
23+
type: 'coreclr',
2424
request: 'launch',
2525
program: path.join(crossgenBuildCoreClrBin, 'crossgen2', 'crossgen2.dll'),
2626
args: [
@@ -51,8 +51,8 @@ export async function resolveDebugConfiguration(folder: vscode.WorkspaceFolder |
5151
if (!folder) {
5252
return undefined;
5353
}
54-
let crossgen2Config: userPrompts.OutputConfiguration | undefined;
55-
let corelibConfig: userPrompts.OutputConfiguration | undefined;
54+
let crossgen2Config: OutputConfiguration | undefined;
55+
let corelibConfig: OutputConfiguration | undefined;
5656
if (debugConfiguration.separateConfig) {
5757
crossgen2Config = await userPrompts.promptUserForTargetConfiguration({ promptPrefix: 'Crossgen2', showChecked: true, defaultConfiguration: 'Debug' });
5858
if (crossgen2Config) {

src/providers/crossgen2.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { copyFile, writeFile, mkdir } from 'fs/promises';
66
import { promisify } from 'util';
77
import { glob } from 'glob';
88
import { existsSync } from 'fs';
9+
import { OutputConfiguration } from '../outputConfiguration';
910

1011
type DebugConfigurationType = 'rt-cg2rt';
1112

@@ -81,8 +82,8 @@ export async function resolveDebugConfiguration(folder: vscode.WorkspaceFolder |
8182
if (!folder) {
8283
return undefined;
8384
}
84-
let crossgen2Config: userPrompts.OutputConfiguration | undefined;
85-
let testConfig: userPrompts.OutputConfiguration | undefined;
85+
let crossgen2Config: OutputConfiguration | undefined;
86+
let testConfig: OutputConfiguration | undefined;
8687
if (debugConfiguration.separateConfig) {
8788
crossgen2Config = await userPrompts.promptUserForTargetConfiguration({ promptPrefix: 'Crossgen2', showChecked: true, defaultConfiguration: 'Debug' });
8889
if (crossgen2Config) {

src/server.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import * as path from 'path';
55
import { getRuntimeWorkspaceFolder } from './userPrompts';
66
import * as os from 'os';
77
import log from './log';
8-
import { RequestType2 } from 'vscode-jsonrpc/node';
8+
import { RequestType2, RequestType4 } from 'vscode-jsonrpc/node';
9+
import { OutputConfiguration } from './outputConfiguration';
910

1011
export function setServerPathFromExtensionContext(context: vscode.ExtensionContext) {
1112
serverPath = path.join(context.extensionUri.fsPath, 'out', 'server', 'AssistantServer.dll');
@@ -38,7 +39,7 @@ export async function getOrStartServerConnection(): Promise<rpc.MessageConnectio
3839
log(`starting server process at path: '${serverPath}'`);
3940
let serverProc = cp.spawn(
4041
path.join(runtimeWorkspaceFolder.fsPath, `dotnet${getDotnetScriptExtension()}`),
41-
[ serverPath ]);
42+
[serverPath]);
4243
serverProc.stderr.setEncoding('utf8');
4344
serverProc.stderr.on('data', data => {
4445
serverLog.append(data.toString());
@@ -61,27 +62,37 @@ export async function getOrStartServerConnection(): Promise<rpc.MessageConnectio
6162
return currentServerConnection;
6263
}
6364

64-
namespace Protocol {
65+
namespace GetProjectProperties {
66+
export const method = 'GetProjectProperties';
6567

66-
export namespace GetProjectProperties {
67-
export const method = 'GetProjectProperties';
68+
type Response<TProperties extends string[]> = {
69+
[key in TProperties[number]]: string;
70+
};
6871

69-
type Response<TProperties extends string[]> = {
70-
[key in TProperties[number]]: string;
71-
};
72-
73-
export function getRequestType<TProperties extends string[]>() {
74-
return new RequestType2<string, TProperties, Response<TProperties>, void>(method);
75-
}
72+
export function getRequestType<TProperties extends string[]>() {
73+
return new RequestType2<string, TProperties, Response<TProperties>, void>(method);
7674
}
7775
}
7876

7977
export async function getProjectProperties<TProperty extends string>(projectFilePath: string, ...properties: TProperty[]) {
78+
log(`Getting the following properties defined in the project file at '${projectFilePath}': ${properties}`);
8079
const serverConnection = await getOrStartServerConnection();
81-
return await serverConnection.sendRequest(Protocol.GetProjectProperties.getRequestType<TProperty[]>(), projectFilePath, properties);
80+
return await serverConnection.sendRequest(GetProjectProperties.getRequestType<TProperty[]>(), projectFilePath, properties);
8281
}
8382

8483
export async function getNetCoreAppCurrentProperty() {
8584
const runtimeWorkspaceFolder = await getRuntimeWorkspaceFolder();
8685
return (await getProjectProperties(path.join(runtimeWorkspaceFolder!.fsPath, 'Build.proj'), 'NetCoreAppCurrent')).NetCoreAppCurrent;
8786
}
87+
88+
namespace TryCreateVsCodeRunSettings {
89+
export const method = 'TryCreateVsCodeRunSettings';
90+
91+
export const type = new RequestType4<string, string, string, string, boolean, void>(method);
92+
}
93+
94+
export async function tryCreateVsCodeRunSettings(preTestProjectPath: string, configuration: OutputConfiguration) {
95+
log(`Regenerating the vscode RunSettings file for the provided configuration '${configuration.os}-${configuration.configuration}-${configuration.arch}'`);
96+
const serverConnection = await getOrStartServerConnection();
97+
return await serverConnection.sendRequest(TryCreateVsCodeRunSettings.type, preTestProjectPath, configuration.os, configuration.arch, configuration.configuration);
98+
}

src/server/Log.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,11 @@ internal static partial class Log
2424
Message = "Getting properties '{properties}' from project file '{projectFile}'."
2525
)]
2626
public static partial void GetProjectProperties(this ILogger logger, string projectFile, string[] properties);
27+
28+
[LoggerMessage(
29+
EventId = 3,
30+
Level = LogLevel.Information,
31+
Message = "MSBuild: {message}"
32+
)]
33+
public static partial void LogMSBuildOutput(this ILogger logger, string message);
2734
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Microsoft.Build.Locator;
2+
3+
namespace AssistantServer;
4+
5+
internal static class MSBuildLocatorRegistration
6+
{
7+
static MSBuildLocatorRegistration()
8+
{
9+
MSBuildLocator.RegisterInstance(MSBuildLocator.QueryVisualStudioInstances(new VisualStudioInstanceQueryOptions { DiscoveryTypes = DiscoveryType.DotNetSdk }).First());
10+
}
11+
12+
public static void Register() { }
13+
}

src/server/MSBuildRunner.cs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,61 @@
1-
using Microsoft.Build.Construction;
2-
using Microsoft.Build.Evaluation;
1+
using Microsoft.Build.Evaluation;
2+
using Microsoft.Build.Execution;
33
using Microsoft.Build.Locator;
4+
using Microsoft.Build.Logging;
45
using Microsoft.Extensions.Logging;
6+
using MSBuildLoggerVerbosity = Microsoft.Build.Framework.LoggerVerbosity;
57

68
namespace AssistantServer;
79

810
internal sealed partial class MSBuildRunner
911
{
12+
private readonly ProjectCollection projectCollection;
13+
private readonly BuildManager buildManager;
1014
private readonly ILogger logger;
1115

1216
public MSBuildRunner(ILogger logger)
1317
{
1418
this.logger = logger;
15-
}
16-
17-
static MSBuildRunner()
18-
{
19-
MSBuildLocator.RegisterInstance(MSBuildLocator.QueryVisualStudioInstances(new VisualStudioInstanceQueryOptions { DiscoveryTypes = DiscoveryType.DotNetSdk }).First());
19+
projectCollection = new ProjectCollection();
20+
// Forward the MSBuild logging through our logging mechanism
21+
projectCollection.RegisterLogger(new ConsoleLogger(MSBuildLoggerVerbosity.Normal, line => logger.LogMSBuildOutput(line), _ => { }, () => { }));
22+
buildManager = new BuildManager("AssistantServer");
23+
buildManager.BeginBuild(new BuildParameters(projectCollection));
2024
}
2125

2226
public string[] GetProjectTargetFrameworks(string projectPath)
2327
{
2428
logger.GetProjectTargetFrameworks(projectPath);
25-
var project = new Project(ProjectRootElement.Open(projectPath));
29+
var project = projectCollection.LoadProject(projectPath);
2630
var targetFrameworks = project.GetPropertyValue("TargetFrameworks");
2731
return !string.IsNullOrEmpty(targetFrameworks) ? targetFrameworks.Split(';') : new string[] { project.GetPropertyValue("TargetFramework") };
2832
}
2933

3034
public Dictionary<string, string> GetProjectProperties(string projectPath, string[] properties)
3135
{
3236
logger.GetProjectProperties(projectPath, properties);
33-
var project = new Project(ProjectRootElement.Open(projectPath));
37+
var project = projectCollection.LoadProject(projectPath);
3438
Dictionary<string, string> results = new();
3539
foreach (var property in properties)
3640
{
3741
results[property] = project.GetPropertyValue(property);
3842
}
3943
return results;
4044
}
45+
46+
public bool TryCreateVsCodeRunSettings(string preTestProject, string operatingSystem, string architecture, string configuration)
47+
{
48+
var projectInstance = buildManager.GetProjectInstanceForBuild(
49+
projectCollection.LoadProject(preTestProject, new Dictionary<string, string>
50+
{
51+
{ "TargetOS", operatingSystem },
52+
{ "TargetArchitecture", architecture },
53+
{ "Configuration", configuration },
54+
{ "CreateVsCodeRunSettingsFile", "true" }
55+
},
56+
projectCollection.DefaultToolsVersion));
57+
var submission = buildManager.BuildRequest(new BuildRequestData(projectInstance, new[] { "GenerateRunSettingsFile" }));
58+
59+
return submission.OverallResult == BuildResultCode.Success;
60+
}
4161
}

src/server/RpcService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken)
1717
{
1818
logger.Startup(Environment.ProcessId);
1919

20+
MSBuildLocatorRegistration.Register();
2021
var rpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput(), new MSBuildRunner(logger));
2122

2223
stoppingToken.Register(() => rpc.Dispose());

0 commit comments

Comments
 (0)