Skip to content

Commit 14ffb78

Browse files
committed
handle outdated vs environment
1 parent bebdaeb commit 14ffb78

File tree

4 files changed

+60
-29
lines changed

4 files changed

+60
-29
lines changed

Extension/src/Debugger/configurationProvider.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as util from '../common';
1515
import { isWindows } from '../constants';
1616
import { expandAllStrings, ExpansionOptions, ExpansionVars } from '../expand';
1717
import { CppBuildTask, CppBuildTaskDefinition, cppBuildTaskProvider } from '../LanguageServer/cppBuildTaskProvider';
18+
import { canFindCl } from '../LanguageServer/devcmd';
1819
import { configPrefix } from '../LanguageServer/extension';
1920
import { CppSettings, OtherSettings } from '../LanguageServer/settings';
2021
import * as logger from '../logger';
@@ -590,20 +591,34 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
590591
* `false` if the VS developer environment is available or the user chose to apply it.
591592
*/
592593
private async showErrorIfClNotAvailable(_configurationLabel: string): Promise<boolean> {
593-
if (util.hasMsvcEnvironment()) {
594-
return false; // No error to show
594+
const hasEnvironment = util.hasMsvcEnvironment();
595+
if (hasEnvironment) {
596+
if (await canFindCl()) {
597+
return false; // No error to show
598+
}
599+
// The user has an environment but cl.exe cannot be found. Prompt the user to update the environment.
595600
}
596601

597-
const applyDevEnv = localize("apply.dev.env", "Apply Developer Environment");
598-
const cancel = localize("cancel", "Cancel");
599-
const response = await vscode.window.showErrorMessage(
600-
localize({
602+
const applyButton = localize("apply.dev.env", "Apply Developer Environment");
603+
const applyMessage = localize(
604+
{
601605
key: "cl.exe.not.available",
602606
comment: ["{0} is a command option in a menu."]
603-
}, "{0} requires the Visual Studio developer environment.", `cl.exe ${this.buildAndDebugActiveFileStr()}`),
604-
applyDevEnv,
607+
},
608+
"{0} requires the Visual Studio developer environment.", `cl.exe ${this.buildAndDebugActiveFileStr()}`);
609+
const updateButton = localize("update.dev.env", "Update Developer Environment");
610+
const updateMessage = localize(
611+
{
612+
key: "update.dev.env",
613+
comment: ["{0} is a command option in a menu."]
614+
},
615+
"{0} requires the Visual Studio developer environment to be updated.", `cl.exe ${this.buildAndDebugActiveFileStr()}`);
616+
const cancel = localize("cancel", "Cancel");
617+
const response = await vscode.window.showErrorMessage(
618+
hasEnvironment ? updateMessage : applyMessage,
619+
hasEnvironment ? updateButton : applyButton,
605620
cancel);
606-
if (response === applyDevEnv) {
621+
if (response === applyButton || response === updateButton) {
607622
try {
608623
await vscode.commands.executeCommand('C_Cpp.SetVsDeveloperEnvironment', 'buildAndDebug');
609624
} catch (e: any) {
@@ -615,11 +630,12 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
615630
return true;
616631
}
617632

618-
if (util.hasMsvcEnvironment()) {
633+
if (util.hasMsvcEnvironment() && await canFindCl()) {
619634
return false;
620635
}
621-
void vscode.window.showErrorMessage(
622-
localize('dev.env.not.applied', 'The source code could not be built because the Visual Studio developer environment was not applied.'));
636+
const notAppliedMessage = localize("dev.env.not.applied", "The source code could not be built because the Visual Studio developer environment was not applied.");
637+
const notFoundMessage = localize("dev.env.not.found", "The source code could not be built because the Visual C++ compiler could not be found.");
638+
void vscode.window.showErrorMessage(hasEnvironment ? notFoundMessage : notAppliedMessage);
623639
return true;
624640
}
625641

Extension/src/LanguageServer/cppBuildTaskProvider.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
import * as cp from "child_process";
77
import * as os from 'os';
88
import * as path from 'path';
9-
import { CustomExecution, Disposable, EnvironmentVariableMutator, Event, EventEmitter, ProcessExecution, Pseudoterminal, ShellExecution, Task, TaskDefinition, TaskEndEvent, TaskExecution, TaskGroup, TaskProvider, tasks, TaskScope, TerminalDimensions, TextEditor, window, workspace, WorkspaceFolder } from 'vscode';
9+
import { CustomExecution, Disposable, Event, EventEmitter, ProcessExecution, Pseudoterminal, ShellExecution, Task, TaskDefinition, TaskEndEvent, TaskExecution, TaskGroup, TaskProvider, tasks, TaskScope, TerminalDimensions, TextEditor, window, workspace, WorkspaceFolder } from 'vscode';
1010
import * as nls from 'vscode-nls';
1111
import * as util from '../common';
1212
import * as telemetry from '../telemetry';
1313
import { Client } from './client';
1414
import * as configs from './configurations';
15-
import { isEnvironmentOverrideApplied } from "./devcmd";
15+
import { getEffectiveEnvironment, isEnvironmentOverrideApplied } from "./devcmd";
1616
import * as ext from './extension';
1717
import { OtherSettings } from './settings';
1818

@@ -431,20 +431,11 @@ class CustomBuildTaskTerminal implements Pseudoterminal {
431431
}
432432
}
433433

434-
if (isEnvironmentOverrideApplied(util.extensionContext)) {
434+
if (isEnvironmentOverrideApplied()) {
435435
// If the user has applied the developer environment to this workspace, it should apply to all newly opened terminals.
436436
// However, this does not apply to processes that we spawn ourselves in the Pseudoterminal, so we need to specify the
437437
// correct environment in order to emulate the terminal behavior properly.
438-
const env = { ...process.env };
439-
util.extensionContext?.environmentVariableCollection.forEach((variable: string, mutator: EnvironmentVariableMutator) => {
440-
if (variable.toLowerCase() === "path") {
441-
// Path is special because it has a placeholder to insert the current Path into.
442-
env[variable] = util.resolveVariables(mutator.value);
443-
} else {
444-
env[variable] = mutator.value;
445-
}
446-
});
447-
this.options.env = env;
438+
this.options.env = getEffectiveEnvironment();
448439
}
449440

450441
const splitWriteEmitter = (lines: string | Buffer) => {

Extension/src/LanguageServer/devcmd.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { vswhere } from 'node-vswhere';
99
import * as path from 'path';
1010
import * as vscode from 'vscode';
1111
import * as nls from 'vscode-nls';
12+
import { extensionContext, isString, resolveVariables, whichAsync } from '../common';
1213
import { isWindows } from '../constants';
1314
import { CppSettings } from './settings';
1415

@@ -27,8 +28,31 @@ const advancedOptionsDescription = localize('advanced.options.desc', 'Select a s
2728
const selectToolsetVersion = localize('select.toolset', 'Select a toolset version');
2829
const selectHostTargetArch = localize('select.host.target', 'Select a host and target architecture');
2930

30-
export function isEnvironmentOverrideApplied(context?: vscode.ExtensionContext) {
31-
return context?.environmentVariableCollection.get('VCToolsInstallDir') !== undefined;
31+
export function isEnvironmentOverrideApplied(): boolean {
32+
return extensionContext?.environmentVariableCollection.get('VCToolsInstallDir') !== undefined;
33+
}
34+
35+
export function getEffectiveEnvironment(): { [key: string]: string | undefined } {
36+
const env = { ...process.env };
37+
extensionContext?.environmentVariableCollection.forEach((variable: string, mutator: vscode.EnvironmentVariableMutator) => {
38+
if (variable.toLowerCase() === "path") {
39+
// Path is special because it has a placeholder to insert the current Path into.
40+
env[variable] = resolveVariables(mutator.value);
41+
} else {
42+
env[variable] = mutator.value;
43+
}
44+
});
45+
return env;
46+
}
47+
48+
export async function canFindCl(): Promise<boolean> {
49+
if (!isWindows) {
50+
return false;
51+
}
52+
const pathOverride = resolveVariables(extensionContext?.environmentVariableCollection.get('Path')?.value);
53+
const path = pathOverride ?? process.env['Path'];
54+
const found = await whichAsync('cl.exe', path);
55+
return isString(found);
3256
}
3357

3458
export async function setEnvironment(context?: vscode.ExtensionContext) {

Extension/src/common.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,9 +1515,9 @@ export interface ISshLocalForwardInfo {
15151515
remoteSocket?: string;
15161516
}
15171517

1518-
export function whichAsync(name: string): Promise<string | undefined> {
1518+
export function whichAsync(name: string, path?: string): Promise<string | undefined> {
15191519
return new Promise<string | undefined>(resolve => {
1520-
which(name, (err, resolved) => {
1520+
which(name, path ? { path } : {}, (err, resolved) => {
15211521
if (err) {
15221522
resolve(undefined);
15231523
} else {

0 commit comments

Comments
 (0)