Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4378,6 +4378,14 @@
},
"default": []
},
"env": {
"type": "object",
"description": "%c_cpp.debuggers.env.description%",
Comment thread
8prashant marked this conversation as resolved.
"additionalProperties": {
"type": "string"
},
"default": {}
},
"envFile": {
"type": "string",
"description": "%c_cpp.debuggers.envFile.description%",
Expand Down Expand Up @@ -6046,6 +6054,14 @@
},
"default": []
},
"env": {
"type": "object",
"description": "%c_cpp.debuggers.env.description%",
"additionalProperties": {
"type": "string"
},
"default": {}
},
"envFile": {
"type": "string",
"description": "%c_cpp.debuggers.envFile.description%",
Expand Down
1 change: 1 addition & 0 deletions Extension/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,7 @@
"{Locked=\"[ { \\\"name\\\": \\\"\"} {Locked=\"\\\", \\\"value\\\": \\\"\"} {Locked=\"\\\" } ]\"} {Locked=\"[ { \\\"\"} {Locked=\"\\\": \\\"\"} {Locked=\"\\\" } ]\"}"
]
},
"c_cpp.debuggers.env.description": "Object of environment variables to add to the environment for the program. Example: { \"MY_VAR\": \"value\" }. Use `environment` for the array-of-objects form.",
"c_cpp.debuggers.envFile.description": "Absolute path to a file containing environment variable definitions. This file has key value pairs separated by an equals sign per line. E.g. KEY=VALUE.",
"c_cpp.debuggers.additionalSOLibSearchPath.description": "Semicolon separated list of directories to use to search for .so files. Example: \"c:\\dir1;c:\\dir2\".",
"c_cpp.debuggers.MIMode.description": "Indicates the console debugger that the MIDebugEngine will connect to. Allowed values are \"gdb\" \"lldb\".",
Expand Down
33 changes: 33 additions & 0 deletions Extension/src/Debugger/configurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,10 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
// Add environment variables from .env file
this.resolveEnvFile(config, folder);

// cppdbg (MIEngine) consumes the legacy `environment` array, not `env`.
// Convert here so both syntaxes work while preserving `env` precedence.
this.resolveEnvObjectForCppdbg(config);

await this.expand(config, folder);

this.resolveSourceFileMapVariables(config);
Expand Down Expand Up @@ -706,6 +710,35 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
}
}

private resolveEnvObjectForCppdbg(config: CppDebugConfiguration): void {
if (config.type !== DebuggerType.cppdbg || config.request !== 'launch') {
return;
}

const envObject = config.env;
if (!util.isObject(envObject)) {
return;
}

const environment: Environment[] = util.isArray(config.environment) ? config.environment : [];
const mergedEnvironment = new Map<string, string>();

for (const entry of environment) {
if (util.isString(entry?.name) && util.isString(entry?.value)) {
mergedEnvironment.set(entry.name, entry.value);
}
}

for (const [name, value] of Object.entries(envObject)) {
if (util.isString(value)) {
mergedEnvironment.set(name, value);
}
}

config.environment = Array.from(mergedEnvironment.entries()).map(([name, value]) => ({ name, value }));
delete config.env;
}

private resolveSourceFileMapVariables(config: CppDebugConfiguration): void {
const messages: string[] = [];
if (config.sourceFileMap) {
Expand Down
30 changes: 20 additions & 10 deletions Extension/src/Debugger/runWithoutDebuggingAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ import { isWindows } from '../constants';
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize = nls.loadMessageBundle();

type LaunchEnvironmentEntry = { name: string; value: string; };

type LaunchConfiguration = {
program?: string;
args?: string[];
cwd?: string;
environment?: LaunchEnvironmentEntry[];
env?: Record<string, string>;
console?: string;
externalConsole?: boolean;
};

/**
* A minimal inline Debug Adapter that runs the target program directly without a debug adapter
* when the user invokes "Run Without Debugging".
Expand Down Expand Up @@ -59,26 +71,24 @@ export class RunWithoutDebuggingAdapter implements vscode.DebugAdapter {
}

private async launch(request: { command: string; seq: number; arguments?: any; }): Promise<void> {
const config = request.arguments as {
program?: string;
args?: string[];
cwd?: string;
environment?: { name: string; value: string; }[];
console?: string;
externalConsole?: boolean;
};
const config = request.arguments as LaunchConfiguration;

const program: string = config.program ?? '';
const args: string[] = config.args ?? [];
const cwd: string | undefined = config.cwd;
const environment: { name: string; value: string; }[] = config.environment ?? [];
const environment: LaunchEnvironmentEntry[] = config.environment ?? [];
const envObject: Record<string, string> = config.env ?? {};
const consoleMode: string = config.console ?? (config.externalConsole ? 'externalTerminal' : 'integratedTerminal');

// Merge the launch config's environment variables on top of the inherited process environment.
// Merge environment values in this order: inherited process environment, legacy
// `environment` entries, then shorthand `env` values (higher precedence).
const env: NodeJS.ProcessEnv = { ...process.env };
for (const e of environment) {
env[e.name] = e.value;
}
for (const [key, value] of Object.entries(envObject)) {
env[key] = value;
}

this.sendResponse(request, {});

Expand Down
21 changes: 21 additions & 0 deletions Extension/test/scenarios/RunWithoutDebugging/assets/envTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <cstdlib>
#include <fstream>

int main(int argc, char *argv[]) {
if (argc < 3) {
return 1;
}

const char *value = std::getenv(argv[1]);

std::ofstream resultFile(argv[2]);
if (!resultFile) {
return 2;
}

if (value) {
resultFile << value;
}

return 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,39 @@ async function waitForResultFileValue(filePath: string, timeoutMs: number): Prom
assert.fail(`Timed out waiting for numeric result in ${filePath}. Last contents: ${lastContents}`);
}

async function waitForResultFileText(filePath: string, timeoutMs: number): Promise<string> {
const deadline = Date.now() + timeoutMs;
let lastContents = '';

while (Date.now() < deadline) {
try {
lastContents = await util.readFileText(filePath, 'utf8');
return lastContents.trim();
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
throw error;
}
}

await new Promise<void>(resolve => setTimeout(resolve, 100));
}

assert.fail(`Timed out waiting for output in ${filePath}. Last contents: ${lastContents}`);
}

suite('Run Without Debugging Test', function (): void {
const expectedResultValue = 37;
const workspaceFolder = vscode.workspace.workspaceFolders?.[0] ?? assert.fail('No workspace folder available');
const workspacePath = workspaceFolder.uri.fsPath;
const sourceFile = path.join(workspacePath, 'debugTest.cpp');
const envSourceFile = path.join(workspacePath, 'envTest.cpp');
const sourceUri = vscode.Uri.file(sourceFile);
const resultFilePath = path.join(workspacePath, 'runWithoutDebuggingResult.txt');
const envResultFilePath = path.join(workspacePath, 'runWithoutDebuggingEnvResult.txt');
const executableName = isWindows ? 'debugTestProgram.exe' : 'debugTestProgram';
const executablePath = path.join(workspacePath, executableName);
const envExecutableName = isWindows ? 'envTestProgram.exe' : 'envTestProgram';
const envExecutablePath = path.join(workspacePath, envExecutableName);
const sessionName = 'Run Without Debugging Result File';
const debugType = isWindows ? 'cppvsdbg' : 'cppdbg';
const miMode = isMacOS ? 'lldb' : 'gdb';
Expand All @@ -165,6 +189,7 @@ suite('Run Without Debugging Test', function (): void {
await extension.activate();
}
await compileProgram(workspacePath, sourceFile, executablePath);
await compileProgram(workspacePath, envSourceFile, envExecutablePath);
});

suiteTeardown(async function (): Promise<void> {
Expand All @@ -175,6 +200,37 @@ suite('Run Without Debugging Test', function (): void {

setup(async function (): Promise<void> {
await util.deleteFile(resultFilePath);
await util.deleteFile(envResultFilePath);
});

test('Run Without Debugging should apply env and prefer it over environment entries', async () => {
const testVarName = 'CPPTOOLS_NO_DEBUG_ENV_TEST';
const expectedValue = 'value-from-env-object';
const fallbackValue = 'value-from-environment-array';
const envConfig: Record<string, string> = {
[testVarName]: expectedValue
};

const started = await vscode.debug.startDebugging(
workspaceFolder,
{
name: `${sessionName} Env`,
type: debugType,
request: 'launch',
program: envExecutablePath,
args: [testVarName, envResultFilePath],
cwd: workspacePath,
environment: [{ name: testVarName, value: fallbackValue }],
env: envConfig,
externalConsole: debugType === 'cppdbg' ? false : undefined,
console: debugType === 'cppvsdbg' ? 'internalConsole' : undefined
},
{ noDebug: true });

assert.strictEqual(started, true, 'The noDebug launch with env did not start successfully.');
const actualValue = await waitForResultFileText(envResultFilePath, 10000);

assert.strictEqual(actualValue, expectedValue, 'Expected env object values to be applied and take precedence over environment entries.');
});

test('Run Without Debugging should not break on breakpoints and write the expected result file', async () => {
Expand Down Expand Up @@ -211,6 +267,7 @@ suite('Run Without Debugging Test', function (): void {
tracker.dispose();
vscode.debug.removeBreakpoints([breakpoint]);
await util.deleteFile(resultFilePath);
await util.deleteFile(envResultFilePath);
}
});

Expand Down Expand Up @@ -261,6 +318,7 @@ suite('Run Without Debugging Test', function (): void {
tracker.dispose();
vscode.debug.removeBreakpoints([breakpoint]);
await util.deleteFile(resultFilePath);
await util.deleteFile(envResultFilePath);
}
});

Expand Down
16 changes: 16 additions & 0 deletions Extension/tools/OptionsSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,14 @@
},
"default": []
},
"env": {
"type": "object",
"description": "%c_cpp.debuggers.env.description%",
"additionalProperties": {
"type": "string"
},
"default": {}
},
"envFile": {
"type": "string",
"description": "%c_cpp.debuggers.envFile.description%",
Expand Down Expand Up @@ -1013,6 +1021,14 @@
},
"default": []
},
"env": {
"type": "object",
"description": "%c_cpp.debuggers.env.description%",
"additionalProperties": {
"type": "string"
},
"default": {}
},
"envFile": {
"type": "string",
"description": "%c_cpp.debuggers.envFile.description%",
Expand Down