diff --git a/integration/vscode/ada/src/ExtensionState.ts b/integration/vscode/ada/src/ExtensionState.ts index d76c734d4..ed5e4ca1a 100644 --- a/integration/vscode/ada/src/ExtensionState.ts +++ b/integration/vscode/ada/src/ExtensionState.ts @@ -4,7 +4,7 @@ import { createClient } from './clients'; import { GnatTaskProvider } from './gnatTaskProvider'; import { GprTaskProvider } from './gprTaskProvider'; import { registerTaskProviders } from './taskProviders'; -import { getCustomEnvSettingName } from './helpers'; +import { TERMINAL_ENV_SETTING_NAME } from './helpers'; /** * This class encapsulates all state that should be maintained throughout the @@ -106,9 +106,7 @@ export class ExtensionState { // React to changes made in the environment variables, showing // a popup to reload the VS Code window and thus restart the // Ada extension. - const env_config_name = getCustomEnvSettingName(); - - if (e.affectsConfiguration(env_config_name)) { + if (e.affectsConfiguration(TERMINAL_ENV_SETTING_NAME)) { void this.showReloadWindowPopup(); } }; diff --git a/integration/vscode/ada/src/clients.ts b/integration/vscode/ada/src/clients.ts index 527ce17e4..2e51d6f2f 100644 --- a/integration/vscode/ada/src/clients.ts +++ b/integration/vscode/ada/src/clients.ts @@ -2,7 +2,7 @@ import { existsSync } from 'fs'; import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; import { logger } from './extension'; -import { logErrorAndThrow, setCustomEnvironment } from './helpers'; +import { logErrorAndThrow, setTerminalEnvironment } from './helpers'; export function createClient( context: vscode.ExtensionContext, @@ -63,7 +63,7 @@ export function createClient( // Copy this process's environment const serverEnv: NodeJS.ProcessEnv = { ...process.env }; // Set custom environment - setCustomEnvironment(serverEnv); + setTerminalEnvironment(serverEnv); logger.debug(`Environment for ${name}:`); for (const key in serverEnv) { diff --git a/integration/vscode/ada/src/debugConfigProvider.ts b/integration/vscode/ada/src/debugConfigProvider.ts index 014d0a551..caa70cd42 100644 --- a/integration/vscode/ada/src/debugConfigProvider.ts +++ b/integration/vscode/ada/src/debugConfigProvider.ts @@ -1,13 +1,15 @@ import assert from 'assert'; import * as vscode from 'vscode'; import { adaExtState } from './extension'; -import { AdaMain, getAdaMains, getProjectFile } from './helpers'; +import { AdaMain, exe, getAdaMains, getEvaluatedTerminalEnv, getProjectFile } from './helpers'; import { BUILD_PROJECT_TASK_NAME, getBuildTaskName } from './taskProviders'; +import path from 'path'; +import { existsSync } from 'fs'; /** * Ada Configuration for a debug session */ -interface AdaConfig extends vscode.DebugConfiguration { +export interface AdaConfig extends vscode.DebugConfiguration { MIMode: string; program: string; cwd?: string; @@ -20,8 +22,25 @@ interface AdaConfig extends vscode.DebugConfiguration { ignoreFailures: boolean; }[]; processId?: string; + miDebuggerPath?: string; } +export const adaDynamicDebugConfigProvider = { + async provideDebugConfigurations( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _folder?: vscode.WorkspaceFolder + ): Promise { + const quickpick = await createQuickPickItems('Build & Debug'); + + const configs: AdaConfig[] = quickpick.flatMap((i) => { + assert(i.adaMain); + return [initializeConfig(i.adaMain), createAttachConfig(i.adaMain)]; + }); + + return configs; + }, +}; + /** * Initialize debugging support for Ada projects. * @@ -48,21 +67,7 @@ export function initializeDebugging(ctx: vscode.ExtensionContext) { ctx.subscriptions.push( vscode.debug.registerDebugConfigurationProvider( 'ada', - { - async provideDebugConfigurations( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _folder: vscode.WorkspaceFolder | undefined - ): Promise { - const quickpick = await createQuickPickItems('Build & Debug'); - - const configs: AdaConfig[] = quickpick.flatMap((i) => { - assert(i.adaMain); - return [initializeConfig(i.adaMain), createAttachConfig(i.adaMain)]; - }); - - return configs; - }, - }, + adaDynamicDebugConfigProvider, // The 'Dynamic' trigger type only works if the package.json lists // "onDebugDynamicConfigurations:ada" as part of the // activationEvents. @@ -70,13 +75,58 @@ export function initializeDebugging(ctx: vscode.ExtensionContext) { ) ); - // TODO it is also possible to register another provider with trigger kind - // 'Dynamic', however the role of such a provider is unclear. In practical - // experiments it ends up never being called. The above provider is enough - // to make it possible to launch debug sessions without a launch.json. - return provider; } + +let cachedGdb: string | undefined | null = undefined; + +/** + * + * @returns the full path to the `gdb` executable, taking into consideration the + * `PATH` variable in the `terminal.integrated.env.*` setting if set. Otherwise, + * the `PATH` variable of the current process environment is considered. + * + * The current process environment is unlikely to change during the lifetime of + * the extension, and we already prompt the User to reload the window in case + * the `terminal.integrated.env.*` variables change. For this reason, we compute + * the value only on the first call, and cache it for subsequent calls to return + * it efficiently. + */ +function getOrFindGdb(): string | undefined { + if (cachedGdb == undefined) { + /** + * If undefined yet, try to compute it. + */ + const env = getEvaluatedTerminalEnv(); + let pathVal: string; + if (env && 'PATH' in env) { + pathVal = env.PATH; + } else if ('PATH' in process.env) { + pathVal = process.env.PATH ?? ''; + } else { + pathVal = ''; + } + + const gdb = pathVal + .split(path.delimiter) + .map((v) => path.join(v, 'gdb' + exe)) + .find(existsSync); + + if (gdb) { + // Found + cachedGdb = gdb; + return cachedGdb; + } else { + // Not found. Assign null to cache to avoid recomputing at every call. + cachedGdb = null; + } + } + + // When returning, coerce null to undefined because the distinction doesn't + // matter on the caller side. + return cachedGdb ?? undefined; +} + /** * Initialize a debug configuration based on 'cppdbg' for the given executable * if specified. Otherwise the program field includes @@ -109,6 +159,7 @@ function initializeConfig(main: AdaMain, name?: string): AdaConfig { MIMode: 'gdb', preLaunchTask: main ? getBuildTaskName(main) : BUILD_PROJECT_TASK_NAME, setupCommands: setupCmd, + miDebuggerPath: getOrFindGdb(), }; return config; @@ -364,5 +415,6 @@ function createAttachConfig(adaMain: AdaMain): AdaConfig { * to trigger an unwanted rebuild, so we don't set a preLaunchTask. */ // preLaunchTask: adaMain ? getBuildTaskName(adaMain) : BUILD_PROJECT_TASK_NAME, + miDebuggerPath: getOrFindGdb(), }; } diff --git a/integration/vscode/ada/src/extension.ts b/integration/vscode/ada/src/extension.ts index 7f12cebc1..6aaa058c0 100644 --- a/integration/vscode/ada/src/extension.ts +++ b/integration/vscode/ada/src/extension.ts @@ -30,8 +30,8 @@ import { initializeDebugging } from './debugConfigProvider'; import { initializeTestView } from './gnattest'; import { assertSupportedEnvironments, - getCustomEnvSettingName, - getEvaluatedCustomEnv, + TERMINAL_ENV_SETTING_NAME, + getEvaluatedTerminalEnv, startedInDebugMode, } from './helpers'; @@ -127,15 +127,15 @@ async function activateExtension(context: vscode.ExtensionContext) { assertSupportedEnvironments(logger); // Log the environment that the extension (and all VS Code) will be using - const customEnv = getEvaluatedCustomEnv(); + const customEnv = getEvaluatedTerminalEnv(); if (customEnv && Object.keys(customEnv).length > 0) { - logger.info(`Custom environment variables from ${getCustomEnvSettingName()}`); + logger.info(`Custom environment variables from ${TERMINAL_ENV_SETTING_NAME}`); for (const varName in customEnv) { const varValue: string = customEnv[varName]; logger.info(`${varName}=${varValue}`); } } else { - logger.debug('No custom environment variables set in %s', getCustomEnvSettingName()); + logger.debug('No custom environment variables set in %s', TERMINAL_ENV_SETTING_NAME); } // Create the Ada and GPR clients. diff --git a/integration/vscode/ada/src/helpers.ts b/integration/vscode/ada/src/helpers.ts index e5753e2a4..a4ce01c55 100644 --- a/integration/vscode/ada/src/helpers.ts +++ b/integration/vscode/ada/src/helpers.ts @@ -109,39 +109,33 @@ export function substituteVariables(str: string, recursive = false): string { return str; } -/* - Environment setting helper functions -*/ - -export function getCustomEnv() { - const env_config_name = getCustomEnvSettingName(); +/** + * Name of the `terminal.integrated.env.*` setting applicable to the current platform. + */ +export const TERMINAL_ENV_SETTING_NAME = + 'terminal.integrated.env.' + + (platform() == 'darwin' ? 'osx' : platform() == 'win32' ? 'windows' : 'linux'); +/** + * + * @returns the value of the applicable `terminal.integrated.env.*` setting, + * without evaluation of macros such as `${env:...}`. + */ +export function getTerminalEnv() { const custom_env = vscode.workspace .getConfiguration() - .get<{ [name: string]: string }>(env_config_name); + .get<{ [name: string]: string }>(TERMINAL_ENV_SETTING_NAME); return custom_env; } -export function getCustomEnvSettingName() { - const user_platform = platform(); - let env_config_name = 'terminal.integrated.env'; - - switch (user_platform) { - case 'darwin': - env_config_name += '.osx'; - break; - case 'win32': - env_config_name += '.windows'; - break; - default: - env_config_name += '.linux'; - } - return env_config_name; -} - -export function getEvaluatedCustomEnv() { - const custom_env = getCustomEnv(); +/** + * + * @returns the value of the applicable `terminal.integrated.env.*` setting, + * after evaluation of macros such as `${env:...}`. + */ +export function getEvaluatedTerminalEnv() { + const custom_env = getTerminalEnv(); if (custom_env) { for (const var_name in custom_env) { @@ -160,17 +154,13 @@ export function getEvaluatedCustomEnv() { * Read the environment variables specified in the vscode setting * `terminal.integrated.env.` and set them in the given ProcessEnv object. * - * If no targetEnv is given, `process.env` is used as a target environment. + * The targetEnv can be `process.env` to apply the changes to the environment of + * the running process. */ -export function setCustomEnvironment(targetEnv?: NodeJS.ProcessEnv) { - if (!targetEnv) { - targetEnv = process.env; - } - +export function setTerminalEnvironment(targetEnv: NodeJS.ProcessEnv) { // Retrieve the user's custom environment variables if specified in their - // settings/workspace: we'll then launch any child process with this custom - // environment - const custom_env = getEvaluatedCustomEnv(); + // settings/workspace + const custom_env = getEvaluatedTerminalEnv(); if (custom_env) { for (const var_name in custom_env) { @@ -183,7 +173,7 @@ export function setCustomEnvironment(targetEnv?: NodeJS.ProcessEnv) { export function assertSupportedEnvironments(mainChannel: winston.Logger) { // Get the ALS environment variable from the custom environment, or from the // process environment - const customEnv = getEvaluatedCustomEnv(); + const customEnv = getEvaluatedTerminalEnv(); const als = customEnv?.ALS ?? process.env.ALS; if (als) { // The User provided an external ALS executable. Do not perform any @@ -350,3 +340,11 @@ export function startedInDebugMode() { } return false; } + +/** + * This constant is set to the string `.exe` on Windows, and to the empty string + * otherwise. It is intended for computingk executable filenames conveniently by + * simply appending the constant at the end of the name and obtaining a result + * compatible with the running platform. + */ +export const exe: '.exe' | '' = process.platform == 'win32' ? '.exe' : ''; diff --git a/integration/vscode/ada/test/suite/general/debug.test.ts b/integration/vscode/ada/test/suite/general/debug.test.ts new file mode 100644 index 000000000..e4b250038 --- /dev/null +++ b/integration/vscode/ada/test/suite/general/debug.test.ts @@ -0,0 +1,42 @@ +import assert from 'assert'; +import { suite, test } from 'mocha'; +import { AdaConfig, adaDynamicDebugConfigProvider } from '../../../src/debugConfigProvider'; +import { activate } from '../utils'; + +suite('Debug Configurations', function () { + this.beforeAll(async () => { + await activate(); + }); + + test('GDB path is explicitely set in offered debug config', async () => { + const firstConfig = (await adaDynamicDebugConfigProvider.provideDebugConfigurations()).at( + 0 + ) as AdaConfig; + + assert.notEqual(firstConfig.miDebuggerPath, undefined); + }); + + test('GDB path is the same for all configs', async () => { + const configs = + (await adaDynamicDebugConfigProvider.provideDebugConfigurations()) as AdaConfig[]; + + assert(configs.length > 1); + const miDebuggerPath = configs.at(0)?.miDebuggerPath; + assert.notEqual(miDebuggerPath, ''); + for (let i = 0; i < configs.length; i++) { + const c = configs[i]; + assert.equal(c.miDebuggerPath, miDebuggerPath); + } + }); + + test('Two debug configs per main are proposed', async () => { + const configs = + (await adaDynamicDebugConfigProvider.provideDebugConfigurations()) as AdaConfig[]; + const expected = ` +Ada: Debug main - src/main1.adb +Ada: Attach debugger to running process - src/main1.adb +Ada: Debug main - src/test.adb +Ada: Attach debugger to running process - src/test.adb`.trim(); + assert.equal(configs.map((v) => `${v.name}`).join('\n'), expected); + }); +}); diff --git a/integration/vscode/ada/test/suite/general/tasks.test.ts b/integration/vscode/ada/test/suite/general/tasks.test.ts index a49afb0f4..51a3d12ae 100644 --- a/integration/vscode/ada/test/suite/general/tasks.test.ts +++ b/integration/vscode/ada/test/suite/general/tasks.test.ts @@ -3,14 +3,14 @@ import { existsSync } from 'fs'; import { suite, test } from 'mocha'; import * as vscode from 'vscode'; import { adaExtState } from '../../../src/extension'; -import { getProjectFile } from '../../../src/helpers'; +import { exe, getProjectFile } from '../../../src/helpers'; import { CustomTaskDefinition, PROJECT_FROM_CONFIG, createAdaTaskProvider, createSparkTaskProvider, } from '../../../src/taskProviders'; -import { activate, exe } from '../utils'; +import { activate } from '../utils'; suite('GPR Tasks Provider', function () { let projectPath: string; @@ -125,7 +125,6 @@ ada: Build and run main - src/test.adb - kind: buildAndRunMain`.trim(); const status = await runTaskAndGetResult(task); assert.equal(status, 0); - console.info(`cwd=${process.cwd()}`); /** * Check that the executable is produced. The project defines a * different name for the executable produced by main1.adb. diff --git a/integration/vscode/ada/test/suite/utils.ts b/integration/vscode/ada/test/suite/utils.ts index ff95679fa..d6a96af58 100644 --- a/integration/vscode/ada/test/suite/utils.ts +++ b/integration/vscode/ada/test/suite/utils.ts @@ -120,4 +120,3 @@ export function runMochaTestsuite(suiteName: string, suiteDirectory: string) { } }); } -export const exe = process.platform == 'win32' ? '.exe' : ''; diff --git a/integration/vscode/ada/test/workspaces/general/src/highlighting/lsp-ada_handlers/lsp-ada_handlers.adb.sem.tokens b/integration/vscode/ada/test/workspaces/general/src/highlighting/lsp-ada_handlers/lsp-ada_handlers.adb.sem.tokens index 12288e4f9..3abb258c5 100644 --- a/integration/vscode/ada/test/workspaces/general/src/highlighting/lsp-ada_handlers/lsp-ada_handlers.adb.sem.tokens +++ b/integration/vscode/ada/test/workspaces/general/src/highlighting/lsp-ada_handlers/lsp-ada_handlers.adb.sem.tokens @@ -79,7 +79,6 @@ line 89: column 14 - 35: function [declaration] : Impre line 90: column 7 - 10: parameter [declaration, readonly] : Self line 90: column 27 - 41: type : Message_Handler line 91: column 7 - 16: parameter [declaration, readonly] : In_Context -line 91: column 20 - 33: type : Context_Access line 92: column 7 - 14: parameter [declaration, readonly] : Position line 93: column 7 - 16: parameter [declaration] : Definition line 94: column 7 - 14: parameter [declaration, readonly] : Msg_Type @@ -168,7 +167,6 @@ line 275: column 14 - 35: function [declaration] : Impre line 276: column 7 - 10: parameter [declaration, readonly] : Self line 276: column 27 - 41: type : Message_Handler line 277: column 7 - 16: parameter [declaration, readonly] : In_Context -line 277: column 20 - 33: type : Context_Access line 278: column 7 - 14: parameter [declaration, readonly] : Position line 279: column 7 - 16: parameter [declaration] : Definition line 280: column 7 - 14: parameter [declaration, readonly] : Msg_Type @@ -241,7 +239,6 @@ line 658: column 14 - 16: namespace : LSP line 660: column 7 - 12: variable [declaration] : Params line 662: column 17 - 34: function [declaration] : Analyse_In_Context line 663: column 10 - 16: parameter [declaration, readonly] : Context -line 663: column 21 - 34: type : Context_Access line 664: column 10 - 17: parameter [declaration, readonly] : Document line 664: column 21 - 23: namespace : LSP line 665: column 10 - 15: parameter [declaration] : Result @@ -253,7 +250,6 @@ line 672: column 10 - 13: parameter [declaration, readonly] : Node line 672: column 62 - 68: enum [static, defaultLibrary] : Boolean line 676: column 17 - 28: function [declaration] : Analyse_Node line 677: column 10 - 16: parameter [declaration, readonly] : Context -line 677: column 20 - 33: type : Context_Access line 678: column 10 - 13: parameter [declaration, readonly] : Node line 679: column 10 - 15: parameter [declaration] : Result line 679: column 24 - 26: namespace : LSP @@ -273,7 +269,6 @@ line 720: column 13 - 16: variable [declaration] : Decl line 746: column 11 - 38: function : Has_Assoc_Without_Designator line 752: column 17 - 28: function [declaration] : Analyse_Node line 753: column 10 - 16: parameter [declaration, readonly] : Context -line 753: column 20 - 33: type : Context_Access line 754: column 10 - 13: parameter [declaration, readonly] : Node line 755: column 10 - 15: parameter [declaration] : Result line 755: column 24 - 26: namespace : LSP @@ -297,7 +292,6 @@ line 816: column 19 - 22: variable [declaration, readonly] : List line 831: column 11 - 22: function : Analyse_Node line 837: column 17 - 34: function [declaration] : Analyse_In_Context line 838: column 10 - 16: parameter [declaration, readonly] : Context -line 838: column 21 - 34: type : Context_Access line 839: column 10 - 17: parameter [declaration, readonly] : Document line 839: column 21 - 23: namespace : LSP line 840: column 10 - 15: parameter [declaration] : Result diff --git a/integration/vscode/ada/xfail.yaml b/integration/vscode/ada/xfail.yaml index d1950cf30..6bc7542b9 100644 --- a/integration/vscode/ada/xfail.yaml +++ b/integration/vscode/ada/xfail.yaml @@ -1,15 +1,6 @@ -xfails: # Format is , [, , ], - - [ - 'Highlighting-lsp-ada_handlers-lsp-ada_handlers-adb-semantic', - 'DIFF', - '', - '', - 'Fails in GitLab CI but not locally', - ] - - [ - 'lsp-ada_handlers-lsp-ada_handlers-adb-semantic', - 'DIFF', - '', - '', - 'Fails in GitLab CI but not locally', - ] +# Each XFAIL entry should be an array of strings with the following content: +# +# - [, , , '', ] +# +# Use an empty string for the platform if the XFAIL should apply everywhere. +xfails: