diff --git a/src/desktop/env-manager.test.ts b/src/desktop/env-manager.test.ts index abe67d66..a04db740 100644 --- a/src/desktop/env-manager.test.ts +++ b/src/desktop/env-manager.test.ts @@ -439,6 +439,23 @@ describe('EnvironmentManager', () => { }); }); + describe('updateEnvironment - fires onDidChangeEnvVars', () => { + it('fires on init and config change', async () => { + const listener = jest.fn(); + environmentManager.onDidChangeEnvVars(listener); + + await environmentManager.activate(mockContext); + expect(listener).toHaveBeenCalledTimes(1); + + listener.mockClear(); + configurationProviderMock.getConfigVariableOrDefault.mockReturnValue({ + MY_VAR: 'new_value', + }); + configurationProviderMock.fireOnChangeConfiguration(CONFIG_ENVIRONMENT_VARIABLES); + expect(listener).toHaveBeenCalledTimes(1); + }); + }); + describe('updateEnvironment - mixed variables', () => { it('handles both PATH and non-PATH variables together', async () => { configurationProviderMock = configurationProviderFactory({ diff --git a/src/desktop/env-manager.ts b/src/desktop/env-manager.ts index 51959d2a..64c635a3 100644 --- a/src/desktop/env-manager.ts +++ b/src/desktop/env-manager.ts @@ -30,6 +30,8 @@ const DEFAULT_PATH_VAR = (process.platform === 'win32') ? 'Path' : 'PATH'; export interface EnvironmentManager { activate(context: vscode.ExtensionContext): Promise; augmentEnv(env: Environment | undefined): Environment; + + readonly onDidChangeEnvVars: vscode.Event; } export interface Environment { @@ -175,10 +177,14 @@ class PythonEnvironmentExtensionWrapper { } class EnvironmentManagerImpl implements EnvironmentManager { + private lastEnvVars: string | undefined; private pyEnvWrapper: Optional = undefined; private context: vscode.ExtensionContext | undefined = undefined; + private readonly envVarsChangeEmitter = new vscode.EventEmitter(); + public readonly onDidChangeEnvVars = this.envVarsChangeEmitter.event; + constructor( private readonly configurationProvider: ConfigurationProvider, ) { @@ -192,6 +198,7 @@ class EnvironmentManagerImpl implements EnvironmentManager { context.subscriptions.push( vscode.extensions.onDidChange(() => this.updateEnvironment(context)), + this.envVarsChangeEmitter, ); this.configurationProvider.onChangeConfiguration( @@ -234,6 +241,16 @@ class EnvironmentManagerImpl implements EnvironmentManager { }); } + const vars = envSettings.vars; + const currentEnvVars = JSON.stringify( + Object.entries(vars) + .filter(([, v]) => v !== undefined) + .sort(([a], [b]) => a.localeCompare(b)), + ); + if (currentEnvVars === this.lastEnvVars) { + return; + } + context.environmentVariableCollection.clear(); for (const [key, value] of Object.entries(envSettings.vars)) { if (value !== undefined) { @@ -243,8 +260,9 @@ class EnvironmentManagerImpl implements EnvironmentManager { context.environmentVariableCollection.replace(key, value); } } - } + this.lastEnvVars = currentEnvVars; + this.envVarsChangeEmitter.fire(); } } diff --git a/src/desktop/extension.ts b/src/desktop/extension.ts index 9d1cf288..ea405d01 100644 --- a/src/desktop/extension.ts +++ b/src/desktop/extension.ts @@ -146,7 +146,8 @@ export const activate = async (context: ExtensionContext): Promise { let loadStateChangeListener: jest.Mock; let changeConfigurationEmitter: EventEmitter; let commandsProvider: MockCommandsProvider; + let configurationProviderMock: MockConfigurationProvider; let changeSolutionFilesEmitter: EventEmitter; let vcpkgActivateEmitter: EventEmitter; let environmentManagerApi: Pick; + let environmentManager: EnvironmentManager; let eventHub: SolutionEventHub; let convertMock: jest.Mock; let loadBuildFilesListener: jest.Mock; @@ -116,6 +121,9 @@ describe('SolutionManager', () => { commandsProvider = commandsProviderFactory(); csolutionService = csolutionServiceFactory(); + configurationProviderMock = configurationProviderFactory({ + [CONFIG_ENVIRONMENT_VARIABLES]: {}, + }); const device: Device = { id: 'device-id' }; const board: Board = { id: 'board-id' }; csolutionService.getDeviceInfo.mockResolvedValue({ success: true, device }); @@ -123,6 +131,7 @@ describe('SolutionManager', () => { csolutionService.loadSolution.mockResolvedValue({ success: true }); csolutionService.getVariables.mockResolvedValue({ success: true, variables: {} }); rpcData = new SolutionRpcData(csolutionService); + environmentManager = new EnvironmentManager(configurationProviderMock); solutionManager = new SolutionManagerImpl( mockActiveSolutionTracker as unknown as ActiveSolutionTracker, @@ -130,6 +139,7 @@ describe('SolutionManager', () => { rpcData, commandsProvider, extensionApiProviderFactory(environmentManagerApi), + environmentManager, ); loadStateChangeListener = jest.fn(); solutionManager.onDidChangeLoadState(loadStateChangeListener); @@ -279,4 +289,36 @@ describe('SolutionManager', () => { ]), ); }); + + it('requests restartRpc when envVars change after solution is activated', async () => { + mockActiveSolutionTracker.activeSolution = testSolutionPath; + changeActiveSolutionEmitter.fire(); + await waitTimeout(100); + + await environmentManager.activate({ + subscriptions: [], + environmentVariableCollection: { + clear: jest.fn(), + prepend: jest.fn(), + replace: jest.fn(), + } as unknown as vscode.GlobalEnvironmentVariableCollection, + } as unknown as ExtensionContext); + + convertMock.mockClear(); + configurationProviderMock.getConfigVariableOrDefault.mockReturnValue({ + NEW_VAR: 'new_value', + }); + configurationProviderMock.fireOnChangeConfiguration(CONFIG_ENVIRONMENT_VARIABLES); + await waitTimeout(600); + + expect(convertMock).toHaveBeenCalledTimes(1); + expect(convertMock).toHaveBeenLastCalledWith( + expect.objectContaining({ + solutionPath: testSolutionPath, + updateRte: false, + restartRpc: true, + lockAbort: false, + }), + ); + }); }); diff --git a/src/solutions/solution-manager.ts b/src/solutions/solution-manager.ts index 9fe6df74..7e508c6c 100644 --- a/src/solutions/solution-manager.ts +++ b/src/solutions/solution-manager.ts @@ -27,6 +27,7 @@ import { EnvironmentManagerApiV1 } from '@arm-software/vscode-environment-manage import { ETextFileResult } from '../generic/text-file'; import { debounce } from 'lodash'; import { SolutionRpcData } from './solution-rpc-data'; +import { EnvironmentManager } from '../desktop/env-manager'; export interface SolutionLoadState { @@ -94,7 +95,7 @@ export class SolutionManagerImpl implements SolutionManager { private readonly rpcData: SolutionRpcData, private readonly commandsProvider: CommandsProvider, private readonly environmentManagerApiProvider: ExtensionApiProvider>, - + private readonly environmentManager: EnvironmentManager, ) { } public async activate(context: vscode.ExtensionContext): Promise { @@ -111,6 +112,9 @@ export class SolutionManagerImpl implements SolutionManager { this.debouncedHandleEnvironmentChange(); }, undefined, context.subscriptions); }), + this.environmentManager.onDidChangeEnvVars(() => { + this.debouncedHandleEnvironmentChange(); + }, undefined, context.subscriptions), this.loadStateChangeEmitter, this.loadBuildFilesEmitter, this.updatedCompileCommandsEmitter,