Skip to content

Commit 622d67b

Browse files
committed
WIP - migrate devcmd to cpptools
1 parent 4fc36fa commit 622d67b

File tree

5 files changed

+228
-1
lines changed

5 files changed

+228
-1
lines changed

Extension/package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3541,6 +3541,16 @@
35413541
"category": "C/C++",
35423542
"icon": "$(run)"
35433543
},
3544+
{
3545+
"command": "C_Cpp.SetDevEnvironment",
3546+
"title": "%c_cpp.command.SetDevEnvironment.title%",
3547+
"category": "C/C++"
3548+
},
3549+
{
3550+
"command": "C_Cpp.ClearDevEnvironment",
3551+
"title": "%c_cpp.command.ClearDevEnvironment.title%",
3552+
"category": "C/C++"
3553+
},
35443554
{
35453555
"command": "C_Cpp.AddDebugConfiguration",
35463556
"title": "%c_cpp.command.AddDebugConfiguration.title%",
@@ -6012,6 +6022,14 @@
60126022
"command": "C_Cpp.BuildAndRunFile",
60136023
"when": "editorLangId =~ /^(c|(cuda-)?cpp)$/ && config.C_Cpp.debugShortcut && cpptools.buildAndDebug.isSourceFile"
60146024
},
6025+
{
6026+
"command": "C_Cpp.SetDevEnvironment",
6027+
"when": "workspacePlatform == windows"
6028+
},
6029+
{
6030+
"command": "C_Cpp.ClearDevEnvironment",
6031+
"when": "workspacePlatform == windows"
6032+
},
60156033
{
60166034
"command": "C_Cpp.AddDebugConfiguration",
60176035
"when": "config.C_Cpp.debugShortcut && cpptools.buildAndDebug.isFolderOpen"
@@ -6636,6 +6654,8 @@
66366654
"node-fetch": "^2.7.0",
66376655
"node-loader": "^2.0.0",
66386656
"node-stream-zip": "^1.15.0",
6657+
"node-vcvarsall": "^1.0.1",
6658+
"node-vswhere": "^1.0.2",
66396659
"plist": "^3.1.0",
66406660
"posix-getopt": "^1.2.1",
66416661
"shell-quote": "^1.8.1",

Extension/package.nls.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
"c_cpp.command.RemoveAllCodeAnalysisProblems.title": "Clear All Code Analysis Problems",
3838
"c_cpp.command.BuildAndDebugFile.title": "Debug C/C++ File",
3939
"c_cpp.command.BuildAndRunFile.title": "Run C/C++ File",
40+
"c_cpp.command.SetDevEnvironment.title": "Set Developer Environment",
41+
"c_cpp.command.ClearDevEnvironment.title": "Clear Developer Environment",
4042
"c_cpp.command.AddDebugConfiguration.title": "Add Debug Configuration",
4143
"c_cpp.command.GenerateDoxygenComment.title": "Generate Doxygen Comment",
4244
"c_cpp.command.addSshTarget.title": "Add SSH target",
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/* --------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All Rights Reserved.
3+
* See 'LICENSE' in the project root for license information.
4+
* ------------------------------------------------------------------------------------------ */
5+
6+
import { promises as fs } from 'fs';
7+
import { vcvars } from 'node-vcvarsall';
8+
import { vswhere } from 'node-vswhere';
9+
import * as path from 'path';
10+
import * as vscode from 'vscode';
11+
12+
export async function setEnvironment(context?: vscode.ExtensionContext) {
13+
if (!context) {
14+
throw new Error('No context provided');
15+
}
16+
17+
const vses = await getVSInstallations();
18+
if (!vses) {
19+
throw new Error('A Visual Studio installation with the C++ compiler was not found');
20+
}
21+
22+
let vs = await chooseVSInstallation(vses);
23+
let options: vcvars.Options | undefined;
24+
if (!vs) {
25+
const compiler = await getAdvancedConfiguration(vses);
26+
vs = compiler.vs;
27+
options = compiler.options;
28+
}
29+
const vars = await vscode.window.withProgress({
30+
cancellable: false,
31+
location: vscode.ProgressLocation.Notification,
32+
title: 'Configuring Developer Environment...'
33+
}, () => vcvars.getVCVars(vs, options));
34+
35+
if (!vars || !vars['INCLUDE']) {
36+
throw new Error(`Something went wrong: ${JSON.stringify(vars)}`);
37+
}
38+
39+
const host = vars['VSCMD_ARG_HOST_ARCH'];
40+
const target = vars['VSCMD_ARG_TGT_ARCH'];
41+
const arch = vcvars.getArchitecture({
42+
host: match(host, { 'x86': 'x86', 'x64': 'x64' }) ?? 'x64',
43+
target: match(target, { 'x86': 'x86', 'x64': 'x64', 'arm64': 'ARM64', 'arm': 'ARM' }) ?? 'x64'
44+
});
45+
const persist = vscode.workspace.getConfiguration('devcmd').get<boolean>('persistEnvironment') === true;
46+
47+
context.environmentVariableCollection.clear();
48+
for (const key of Object.keys(vars)) {
49+
context.environmentVariableCollection.replace(key, vars[key].replace(`%${key}%`, '${env:' + key + '}'));
50+
}
51+
context.environmentVariableCollection.description = (arch ? `${arch} ` : '') + 'Developer Command Prompt for ' + vs.displayName;
52+
context.environmentVariableCollection.persistent = persist;
53+
return true;
54+
}
55+
56+
async function getVSInstallations() {
57+
const installations = await vswhere.getVSInstallations({
58+
all: true,
59+
prerelease: true,
60+
sort: true,
61+
requires: ['Microsoft.VisualStudio.Component.VC.Tools.x86.x64']
62+
});
63+
64+
if (installations.length === 0) {
65+
throw new Error('A Visual Studio installation with the C++ compiler was not found');
66+
}
67+
return installations;
68+
}
69+
70+
async function chooseVSInstallation(installations: vswhere.Installation[]): Promise<vswhere.Installation | undefined> {
71+
const items: vscode.QuickPickItem[] = installations.map(installation => <vscode.QuickPickItem>{
72+
label: installation.displayName,
73+
description: `Default settings for ${installation.displayName}`
74+
});
75+
items.push({
76+
label: 'Advanced options...',
77+
description: 'Select a specific host/target architecture, toolset version, etc.'
78+
});
79+
const selection = await vscode.window.showQuickPick(items, {
80+
placeHolder: 'Select a Visual Studio installation'
81+
});
82+
if (!selection) {
83+
throw new Error('The operation was cancelled');
84+
}
85+
86+
return installations.find(installation => installation.displayName === selection.label);
87+
}
88+
89+
async function getAdvancedConfiguration(vses: vswhere.Installation[]): Promise<Compiler> {
90+
const compiler = await chooseCompiler(vses);
91+
if (!compiler) {
92+
throw new Error('The operation was cancelled');
93+
}
94+
await setOptions(compiler);
95+
return compiler;
96+
}
97+
98+
interface Compiler {
99+
version: string;
100+
vs: vswhere.Installation;
101+
options: vcvars.Options;
102+
}
103+
104+
async function chooseCompiler(vses: vswhere.Installation[]): Promise<Compiler | undefined> {
105+
const compilers: Compiler[] = [];
106+
for (const vs of vses) {
107+
const vcPath = path.join(vs.installationPath, 'VC', 'Tools', 'MSVC');
108+
const folders = await fs.readdir(vcPath);
109+
for (const version of folders) {
110+
const options: vcvars.Options = {
111+
// Don't set the version in the options if there is only one
112+
vcVersion: folders.length > 1 ? version : undefined
113+
};
114+
compilers.push({ version, vs, options });
115+
}
116+
}
117+
const items = compilers.map(compiler => <vscode.QuickPickItem>{
118+
label: compiler.version,
119+
description: compiler.vs.displayName
120+
});
121+
const selection = await vscode.window.showQuickPick(items, {
122+
placeHolder: 'Select a toolset version'
123+
});
124+
if (!selection) {
125+
throw new Error('The operation was cancelled');
126+
}
127+
return compilers.find(compiler => compiler.version === selection.label && compiler.vs.displayName === selection.description);
128+
}
129+
130+
async function setOptions(compiler: Compiler): Promise<void> {
131+
const vcPath = path.join(compiler.vs.installationPath, 'VC', 'Tools', 'MSVC', compiler.version, 'bin');
132+
const hostTargets = await getHostsAndTargets(vcPath);
133+
if (hostTargets.length > 1) {
134+
const items = hostTargets.map(ht => <vscode.QuickPickItem>{
135+
label: vcvars.getArchitecture(ht),
136+
description: `host = ${ht.host}, target = ${ht.target}`
137+
});
138+
const selection = await vscode.window.showQuickPick(items, {
139+
placeHolder: 'Select a host and target architecture'
140+
});
141+
if (!selection) {
142+
throw new Error('The operation was cancelled');
143+
}
144+
compiler.options.arch = <vcvars.Architecture>selection.label;
145+
}
146+
}
147+
148+
async function getHostsAndTargets(vcPath: string): Promise<vcvars.HostTarget[]> {
149+
const hosts = await fs.readdir(vcPath);
150+
if (hosts.length === 0) {
151+
throw new Error('No hosts found');
152+
}
153+
const hostTargets: vcvars.HostTarget[] = [];
154+
for (const host of hosts) {
155+
const h = match<'x86' | 'x64' | undefined>(host.toLowerCase(), { 'hostx86': 'x86', 'hostx64': 'x64' });
156+
if (!h) {
157+
// skip any arm/arm64 folders because there is no arm compiler
158+
continue;
159+
}
160+
const targets = await fs.readdir(path.join(vcPath, host));
161+
for (const target of targets) {
162+
hostTargets.push({
163+
host: h,
164+
target: match(target, { 'x86': 'x86', 'x64': 'x64', 'arm64': 'ARM64', 'arm': 'ARM' }) ?? 'x64'
165+
});
166+
}
167+
}
168+
return hostTargets;
169+
}
170+
171+
export function deactivate() {
172+
}
173+
174+
function match<T>(item: string, cases: { [key: string]: T }): T | undefined {
175+
return cases[item];
176+
}

Extension/src/LanguageServer/extension.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { CodeActionDiagnosticInfo, CodeAnalysisDiagnosticIdentifiersAndUri, code
2929
import { registerRelatedFilesProvider } from './copilotProviders';
3030
import { CppBuildTaskProvider } from './cppBuildTaskProvider';
3131
import { getCustomConfigProviders } from './customProviders';
32+
import { setEnvironment } from './devcmd';
3233
import { getLanguageConfig } from './languageConfig';
3334
import { CppConfigurationLanguageModelTool } from './lmTool';
3435
import { getLocaleId } from './localization';
@@ -430,6 +431,8 @@ export async function registerCommands(enabled: boolean): Promise<void> {
430431
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExtractToMemberFunction', enabled ? () => onExtractToFunction(false, true) : onDisabledCommand));
431432
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExpandSelection', enabled ? (r: Range) => onExpandSelection(r) : onDisabledCommand));
432433
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ShowCopilotHover', enabled ? () => onCopilotHover() : onDisabledCommand));
434+
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.SetDevEnvironment', enabled ? () => onSetDevEnvironment() : onDisabledCommand));
435+
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ClearDevEnvironment', enabled ? () => onClearDevEnvironment() : onDisabledCommand));
433436
}
434437

435438
function onDisabledCommand() {
@@ -1558,3 +1561,16 @@ async function showCopilotContent(copilotHoverProvider: CopilotHoverProvider, ho
15581561

15591562
return true;
15601563
}
1564+
1565+
async function onSetDevEnvironment(): Promise<void> {
1566+
try {
1567+
await setEnvironment(util.extensionContext);
1568+
void vscode.window.showInformationMessage(`${util.extensionContext?.environmentVariableCollection.description} successfully set.`);
1569+
} catch (error: any) {
1570+
void vscode.window.showErrorMessage(`Developer environment not set: ${error.message}`);
1571+
}
1572+
}
1573+
1574+
async function onClearDevEnvironment(): Promise<void> {
1575+
util.extensionContext?.environmentVariableCollection.clear();
1576+
}

Extension/yarn.lock

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3558,6 +3558,19 @@ node-stream-zip@^1.15.0:
35583558
resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea"
35593559
integrity sha1-FYrbiO2ABMbEmjlrUKal3jvKM+o=
35603560

3561+
node-vcvarsall@^1.0.1:
3562+
version "1.0.1"
3563+
resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/node-vcvarsall/-/node-vcvarsall-1.0.1.tgz#d7ee885e4ca41f0f0d814e3a277995cc95ff20b0"
3564+
integrity sha1-1+6IXkykHw8NgU46J3mVzJX/ILA=
3565+
dependencies:
3566+
node-vswhere "^1.0.2"
3567+
tmp "^0.2.1"
3568+
3569+
node-vswhere@^1.0.2:
3570+
version "1.0.2"
3571+
resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/node-vswhere/-/node-vswhere-1.0.2.tgz#f6cac2bd288042f0ab4ee7904e534e04d2f55d2c"
3572+
integrity sha1-9srCvSiAQvCrTueQTlNOBNL1XSw=
3573+
35613574
[email protected], normalize-path@^3.0.0, normalize-path@~3.0.0:
35623575
version "3.0.0"
35633576
resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
@@ -4711,7 +4724,7 @@ timers-ext@^0.1.7:
47114724
es5-ext "^0.10.64"
47124725
next-tick "^1.1.0"
47134726

4714-
tmp@^0.2.3:
4727+
tmp@^0.2.1, tmp@^0.2.3:
47154728
version "0.2.3"
47164729
resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae"
47174730
integrity sha1-63g8wivB6L69BnFHbUbqTrMqea4=

0 commit comments

Comments
 (0)