diff --git a/src/commands/pickFuncProcess.ts b/src/commands/pickFuncProcess.ts index ef2a89e72..74f8f2005 100644 --- a/src/commands/pickFuncProcess.ts +++ b/src/commands/pickFuncProcess.ts @@ -7,18 +7,19 @@ import { sendRequestWithTimeout, type AzExtRequestPrepareOptions } from '@micros import { callWithTelemetryAndErrorHandling, parseError, UserCancelledError, type IActionContext } from '@microsoft/vscode-azext-utils'; import * as unixPsTree from 'ps-tree'; import * as vscode from 'vscode'; -import { hostStartTaskName } from '../constants'; +import { hostStartTaskName, ProjectLanguage } from '../constants'; import { preDebugValidate, type IPreDebugValidateResult } from '../debug/validatePreDebug'; import { ext } from '../extensionVariables'; -import { AzureFunctionTaskDefinition, getFuncPortFromTaskOrProject, isFuncHostTask, runningFuncTaskMap, stopFuncTaskIfRunning, type IRunningFuncTask } from '../funcCoreTools/funcHostTask'; +import { buildPathToWorkspaceFolderMap, getFuncPortFromTaskOrProject, isFuncHostTask, runningFuncTaskMap, stopFuncTaskIfRunning, type IRunningFuncTask } from '../funcCoreTools/funcHostTask'; import { localize } from '../localize'; import { delay } from '../utils/delay'; import { requestUtils } from '../utils/requestUtils'; import { taskUtils } from '../utils/taskUtils'; import { getWindowsProcessTree, ProcessDataFlag, type IProcessInfo, type IWindowsProcessTree } from '../utils/windowsProcessTree'; import { getWorkspaceSetting } from '../vsCodeConfig/settings'; +import { getCompiledProjectInfo } from '../workspace/listLocalProjects'; -const funcTaskReadyEmitter = new vscode.EventEmitter(); +const funcTaskReadyEmitter = new vscode.EventEmitter(); export const onDotnetFuncTaskReady = funcTaskReadyEmitter.event; export async function startFuncProcessFromApi( @@ -32,15 +33,6 @@ export async function startFuncProcessFromApi( error: '' }; - const uriFile: vscode.Uri = vscode.Uri.file(buildPath) - - const azFuncTaskDefinition: AzureFunctionTaskDefinition = { - // VS Code will only run a single instance of a task `type`, - // the path will be used here to make each project be unique. - type: `func ${uriFile.fsPath}`, - functionsApp: uriFile.fsPath - } - let funcHostStartCmd: string = 'func host start'; if (args) { funcHostStartCmd += ` ${args.join(' ')}`; @@ -48,16 +40,31 @@ export async function startFuncProcessFromApi( await callWithTelemetryAndErrorHandling('azureFunctions.api.startFuncProcess', async (context: IActionContext) => { try { - await waitForPrevFuncTaskToStop(azFuncTaskDefinition.functionsApp); - const funcTask = new vscode.Task(azFuncTaskDefinition, - vscode.TaskScope.Global, - hostStartTaskName, 'func', + let workspaceFolder: vscode.WorkspaceFolder | undefined = buildPathToWorkspaceFolderMap.get(buildPath); + + if (workspaceFolder === undefined) { + workspaceFolder = { + uri: vscode.Uri.parse(buildPath), + name: buildPath, + index: -1 + } + } + + await waitForPrevFuncTaskToStop(workspaceFolder); + + buildPathToWorkspaceFolderMap.set(buildPath, workspaceFolder); + + const funcTask = new vscode.Task({ type: `func ${buildPath}` }, + workspaceFolder, + hostStartTaskName, + `func`, new vscode.ShellExecution(funcHostStartCmd, { cwd: buildPath, - env: env + env })); - const taskInfo = await startFuncTask(context, funcTask); + // funcTask.execution?.options.cwd to get build path for later reference + const taskInfo = await startFuncTask(context, workspaceFolder, buildPath, funcTask); result.processId = await pickChildProcess(taskInfo); result.success = true; } catch (err) { @@ -75,7 +82,9 @@ export async function pickFuncProcess(context: IActionContext, debugConfig: vsco throw new UserCancelledError('preDebugValidate'); } - await waitForPrevFuncTaskToStop(result.workspace.uri.fsPath); + const projectInfo = await getCompiledProjectInfo(context, result.workspace.uri.fsPath, ProjectLanguage.CSharp); + const buildPath: string = projectInfo?.compiledProjectPath || result.workspace.uri.fsPath; + await waitForPrevFuncTaskToStop(result.workspace, buildPath); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const preLaunchTaskName: string | undefined = debugConfig.preLaunchTask; @@ -88,17 +97,17 @@ export async function pickFuncProcess(context: IActionContext, debugConfig: vsco throw new Error(localize('noFuncTask', 'Failed to find "{0}" task.', preLaunchTaskName || hostStartTaskName)); } - const taskInfo = await startFuncTask(context, funcTask); + const taskInfo = await startFuncTask(context, result.workspace, buildPath, funcTask); return await pickChildProcess(taskInfo); } -async function waitForPrevFuncTaskToStop(functionApp: string): Promise { - stopFuncTaskIfRunning(functionApp); +async function waitForPrevFuncTaskToStop(workspaceFolder: vscode.WorkspaceFolder, buildPath?: string): Promise { + stopFuncTaskIfRunning(workspaceFolder, buildPath); const timeoutInSeconds: number = 30; const maxTime: number = Date.now() + timeoutInSeconds * 1000; while (Date.now() < maxTime) { - if (!runningFuncTaskMap.has(functionApp)) { + if (!runningFuncTaskMap.has(workspaceFolder)) { return; } await delay(1000); @@ -106,7 +115,7 @@ async function waitForPrevFuncTaskToStop(functionApp: string): Promise { throw new Error(localize('failedToFindFuncHost', 'Failed to stop previous running Functions host within "{0}" seconds. Make sure the task has stopped before you debug again.', timeoutInSeconds)); } -async function startFuncTask(context: IActionContext, funcTask: vscode.Task): Promise { +async function startFuncTask(context: IActionContext, workspaceFolder: vscode.WorkspaceFolder, buildPath: string, funcTask: vscode.Task): Promise { const settingKey: string = 'pickProcessTimeout'; const settingValue: number | undefined = getWorkspaceSetting(settingKey); const timeoutInSeconds: number = Number(settingValue); @@ -115,71 +124,64 @@ async function startFuncTask(context: IActionContext, funcTask: vscode.Task): Pr } context.telemetry.properties.timeoutInSeconds = timeoutInSeconds.toString(); - if (AzureFunctionTaskDefinition.is(funcTask.definition)) { - let taskError: Error | undefined; - const errorListener: vscode.Disposable = vscode.tasks.onDidEndTaskProcess((e: vscode.TaskProcessEndEvent) => { - if (AzureFunctionTaskDefinition.is(e.execution.task.definition) && e.execution.task.definition.functionsApp === funcTask.definition.functionsApp && e.exitCode !== 0) { - context.errorHandling.suppressReportIssue = true; - // Throw if _any_ task fails, not just funcTask (since funcTask often depends on build/clean tasks) - taskError = new Error(localize('taskFailed', 'Error exists after running preLaunchTask "{0}". View task output for more information.', e.execution.task.name, e.exitCode)); - errorListener.dispose(); - } - }); + let taskError: Error | undefined; + const errorListener: vscode.Disposable = vscode.tasks.onDidEndTaskProcess((e: vscode.TaskProcessEndEvent) => { + if (e.execution.task.scope === workspaceFolder && e.exitCode !== 0) { + context.errorHandling.suppressReportIssue = true; + // Throw if _any_ task fails, not just funcTask (since funcTask often depends on build/clean tasks) + taskError = new Error(localize('taskFailed', 'Error exists after running preLaunchTask "{0}". View task output for more information.', e.execution.task.name, e.exitCode)); + errorListener.dispose(); + } + }); - const workspaceFolder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(vscode.Uri.parse(funcTask.definition.functionsApp)) + try { + // The "IfNotActive" part helps when the user starts, stops and restarts debugging quickly in succession. We want to use the already-active task to avoid two func tasks causing a port conflict error + // The most common case we hit this is if the "clean" or "build" task is running when we get here. It's unlikely the "func host start" task is active, since we would've stopped it in `waitForPrevFuncTaskToStop` above + await taskUtils.executeIfNotActive(funcTask); + + const intervalMs: number = 500; + const funcPort: string = await getFuncPortFromTaskOrProject(context, funcTask, workspaceFolder); + let statusRequestTimeout: number = intervalMs; + const maxTime: number = Date.now() + timeoutInSeconds * 1000; + while (Date.now() < maxTime) { + if (taskError !== undefined) { + throw taskError; + } - try { - // The "IfNotActive" part helps when the user starts, stops and restarts debugging quickly in succession. We want to use the already-active task to avoid two func tasks causing a port conflict error - // The most common case we hit this is if the "clean" or "build" task is running when we get here. It's unlikely the "func host start" task is active, since we would've stopped it in `waitForPrevFuncTaskToStop` above - await taskUtils.executeIfNotActive(funcTask); - - const intervalMs: number = 500; - const funcPort: string = await getFuncPortFromTaskOrProject(context, funcTask, workspaceFolder); - let statusRequestTimeout: number = intervalMs; - const maxTime: number = Date.now() + timeoutInSeconds * 1000; - while (Date.now() < maxTime) { - if (taskError !== undefined) { - throw taskError; - } + const taskInfo: IRunningFuncTask | undefined = runningFuncTaskMap.get(workspaceFolder, buildPath); + if (taskInfo) { + for (const scheme of ['http', 'https']) { + const statusRequest: AzExtRequestPrepareOptions = { url: `${scheme}://localhost:${funcPort}/admin/host/status`, method: 'GET' }; + if (scheme === 'https') { + statusRequest.rejectUnauthorized = false; + } - const taskInfo: IRunningFuncTask | undefined = runningFuncTaskMap.get(funcTask.definition.functionsApp); - if (taskInfo) { - for (const scheme of ['http', 'https']) { - const statusRequest: AzExtRequestPrepareOptions = { url: `${scheme}://localhost:${funcPort}/admin/host/status`, method: 'GET' }; - if (scheme === 'https') { - statusRequest.rejectUnauthorized = false; + try { + // wait for status url to indicate functions host is running + const response = await sendRequestWithTimeout(context, statusRequest, statusRequestTimeout, undefined); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + if (response.parsedBody.state.toLowerCase() === 'running') { + funcTaskReadyEmitter.fire(workspaceFolder); + return taskInfo; } - - try { - // wait for status url to indicate functions host is running - const response = await sendRequestWithTimeout(context, statusRequest, statusRequestTimeout, undefined); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - if (response.parsedBody.state.toLowerCase() === 'running') { - funcTaskReadyEmitter.fire(funcTask.definition.functionsApp); - return taskInfo; - } - } catch (error) { - if (requestUtils.isTimeoutError(error)) { - // Timeout likely means localhost isn't ready yet, but we'll increase the timeout each time it fails just in case it's a slow computer that can't handle a request that fast - statusRequestTimeout *= 2; - context.telemetry.measurements.maxStatusTimeout = statusRequestTimeout; - } else { - // ignore - } + } catch (error) { + if (requestUtils.isTimeoutError(error)) { + // Timeout likely means localhost isn't ready yet, but we'll increase the timeout each time it fails just in case it's a slow computer that can't handle a request that fast + statusRequestTimeout *= 2; + context.telemetry.measurements.maxStatusTimeout = statusRequestTimeout; + } else { + // ignore } } } - - await delay(intervalMs); } - throw new Error(localize('failedToFindFuncHost', 'Failed to detect running Functions host within "{0}" seconds. You may want to adjust the "{1}" setting.', timeoutInSeconds, `${ext.prefix}.${settingKey}`)); - } finally { - errorListener.dispose(); + await delay(intervalMs); } - } - else { - throw new Error(localize('failedToFindFuncTask', 'Failed to detect AzFunctions Task')); + + throw new Error(localize('failedToFindFuncHost', 'Failed to detect running Functions host within "{0}" seconds. You may want to adjust the "{1}" setting.', timeoutInSeconds, `${ext.prefix}.${settingKey}`)); + } finally { + errorListener.dispose(); } } diff --git a/src/funcCoreTools/funcHostTask.ts b/src/funcCoreTools/funcHostTask.ts index f97d75223..d1093d2fc 100644 --- a/src/funcCoreTools/funcHostTask.ts +++ b/src/funcCoreTools/funcHostTask.ts @@ -7,25 +7,15 @@ import { registerEvent, type IActionContext } from '@microsoft/vscode-azext-util import * as path from 'path'; import * as vscode from 'vscode'; import { tryGetFunctionProjectRoot } from '../commands/createNewProject/verifyIsProject'; -import { func, localSettingsFileName } from '../constants'; +import { localSettingsFileName } from '../constants'; import { getLocalSettingsJson } from '../funcConfig/local.settings'; +import { localize } from '../localize'; import { getWorkspaceSetting } from '../vsCodeConfig/settings'; export interface IRunningFuncTask { taskExecution: vscode.TaskExecution; processId: number; -} - -export class AzureFunctionTaskDefinition implements vscode.TaskDefinition { - type: string; - // This is either: - // - vscode.WorkspaceFolder.uri.fsPath (used for most of the scenarios in this extension.) - // - If using the exported API 'startFuncProcessFromApi', it will be the binary path wrapped with vscode.Uri.file().fsPath - functionsApp: string - - static is(taskDefinition: vscode.TaskDefinition): taskDefinition is AzureFunctionTaskDefinition { - return taskDefinition.type.startsWith(func) && "functionsApp" in taskDefinition; - } + portNumber: string; } interface DotnetDebugDebugConfiguration extends vscode.DebugConfiguration { @@ -38,12 +28,56 @@ namespace DotnetDebugDebugConfiguration { } } -export const runningFuncTaskMap: Map = new Map(); +class RunningFunctionTaskMap { + private _map: Map = new Map(); + + public set(key: vscode.WorkspaceFolder | vscode.TaskScope, value: IRunningFuncTask): void { + const values = this._map.get(key) || []; + values.push(value) + this._map.set(key, values); + } + + public get(key: vscode.WorkspaceFolder | vscode.TaskScope, buildPath?: string): IRunningFuncTask | undefined { + const values = this._map.get(key) || []; + return values.find(t => { + const taskExecution = t.taskExecution.task.execution as vscode.ShellExecution; + // the cwd will include ${workspaceFolder} from our tasks.json so we need to replace it with the actual path + const taskDirectory = taskExecution.options?.cwd?.replace('${workspaceFolder}', (t.taskExecution.task?.scope as vscode.WorkspaceFolder).uri?.path) + buildPath = buildPath?.replace('${workspaceFolder}', (t.taskExecution.task?.scope as vscode.WorkspaceFolder).uri?.path) + return taskDirectory && buildPath && normalizePath(taskDirectory) === normalizePath(buildPath); + }); + } + + public getAll(key: vscode.WorkspaceFolder | vscode.TaskScope): (IRunningFuncTask | undefined)[] { + return this._map.get(key) || []; + } + + public has(key: vscode.WorkspaceFolder | vscode.TaskScope, buildPath?: string): boolean { + return !!this.get(key, buildPath); + } + + public delete(key: vscode.WorkspaceFolder | vscode.TaskScope, buildPath?: string): void { + const value = this.get(key, buildPath) + const values = this._map.get(key) || []; + + if (value) { + // remove the individual entry from the array + values.splice(values.indexOf(value), 1); + this._map.set(key, values); + } + + if (values?.length === 0) { + this._map.delete(key); + } + } +} + +export const runningFuncTaskMap: RunningFunctionTaskMap = new RunningFunctionTaskMap(); -const funcTaskStartedEmitter = new vscode.EventEmitter(); +const funcTaskStartedEmitter = new vscode.EventEmitter(); export const onFuncTaskStarted = funcTaskStartedEmitter.event; -export const runningFuncPortMap = new Map(); +export const buildPathToWorkspaceFolderMap = new Map(); const defaultFuncPort: string = '7071'; export function isFuncHostTask(task: vscode.Task): boolean { @@ -55,20 +89,19 @@ export function registerFuncHostTaskEvents(): void { registerEvent('azureFunctions.onDidStartTask', vscode.tasks.onDidStartTaskProcess, async (context: IActionContext, e: vscode.TaskProcessStartEvent) => { context.errorHandling.suppressDisplay = true; context.telemetry.suppressIfSuccessful = true; - if (AzureFunctionTaskDefinition.is(e.execution.task.definition) && isFuncHostTask(e.execution.task)) { - const workspaceFolder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(vscode.Uri.parse(e.execution.task.definition.functionsApp)) - - runningFuncTaskMap.set(e.execution.task.definition.functionsApp, { taskExecution: e.execution, processId: e.processId }); - runningFuncPortMap.set(e.execution.task.definition.functionsApp, await getFuncPortFromTaskOrProject(context, e.execution.task, workspaceFolder)); - funcTaskStartedEmitter.fire(e.execution.task.definition.functionsApp); + if (e.execution.task.scope !== undefined && isFuncHostTask(e.execution.task)) { + const portNumber = await getFuncPortFromTaskOrProject(context, e.execution.task, e.execution.task.scope); + const runningFuncTask = { processId: e.processId, taskExecution: e.execution, portNumber }; + runningFuncTaskMap.set(e.execution.task.scope, runningFuncTask); + funcTaskStartedEmitter.fire(e.execution.task.scope); } }); registerEvent('azureFunctions.onDidEndTask', vscode.tasks.onDidEndTaskProcess, (context: IActionContext, e: vscode.TaskProcessEndEvent) => { context.errorHandling.suppressDisplay = true; context.telemetry.suppressIfSuccessful = true; - if (AzureFunctionTaskDefinition.is(e.execution.task.definition) && isFuncHostTask(e.execution.task)) { - runningFuncTaskMap.delete(e.execution.task.definition.functionsApp); + if (e.execution.task.scope !== undefined && isFuncHostTask(e.execution.task)) { + runningFuncTaskMap.delete(e.execution.task.scope, (e.execution.task.execution as vscode.ShellExecution).options?.cwd); } }); @@ -79,34 +112,58 @@ export function registerFuncHostTaskEvents(): void { // Used to stop the task started with pickFuncProcess.ts startFuncProcessFromApi. if (DotnetDebugDebugConfiguration.is(debugSession.configuration) && debugSession.configuration.launchServiceData.buildPath) { const buildPathUri: vscode.Uri = vscode.Uri.file(debugSession.configuration.launchServiceData.buildPath) - stopFuncTaskIfRunning(buildPathUri.fsPath, /* terminate */ true) + + const workspaceFolder = buildPathToWorkspaceFolderMap.get(debugSession.configuration.launchServiceData.buildPath) + if (workspaceFolder === undefined) { + throw new Error(localize('noWorkspaceFolderForBuildPath', 'No workspace folder found for path "{0}".', buildPathUri.fsPath)); + } + + stopFuncTaskIfRunning(workspaceFolder, buildPathUri.fsPath, false, true) + + buildPathToWorkspaceFolderMap.delete(debugSession.configuration.launchServiceData.buildPath) } // NOTE: Only stop the func task if this is the root debug session (aka does not have a parentSession) to fix https://github.com/microsoft/vscode-azurefunctions/issues/2925 if (getWorkspaceSetting('stopFuncTaskPostDebug') && !debugSession.parentSession && debugSession.workspaceFolder) { - stopFuncTaskIfRunning(debugSession.workspaceFolder.uri.fsPath); + // TODO: Find the exact function task from the debug session, but for now just stop all tasks in the workspace folder + stopFuncTaskIfRunning(debugSession.workspaceFolder, undefined, true); } }); } -export function stopFuncTaskIfRunning(functionApp: string, terminate: boolean = false): void { - const runningFuncTask: IRunningFuncTask | undefined = runningFuncTaskMap.get(functionApp); +export function stopFuncTaskIfRunning(workspaceFolder: vscode.WorkspaceFolder | vscode.TaskScope, buildPath?: string, killAll?: boolean, terminate?: boolean): void { + let runningFuncTask: (IRunningFuncTask | undefined)[] | undefined; + if (killAll) { + // get all is needed here + runningFuncTask = runningFuncTaskMap.getAll(workspaceFolder); + } else { + runningFuncTask = [runningFuncTaskMap.get(workspaceFolder, buildPath)]; + } + if (runningFuncTask !== undefined) { - if (terminate) { - // Tasks that are spun up by an API will execute quickly that process.kill does not terminate the func host fast enough - // that it hangs on to the port needed by a debug-restart event. - runningFuncTask.taskExecution.terminate(); + + + for (const runningFuncTaskItem of runningFuncTask) { + if (!runningFuncTaskItem) break; + if (terminate) { + runningFuncTaskItem.taskExecution.terminate() + } else { + // Use `process.kill` because `TaskExecution.terminate` closes the terminal pane and erases all output + // Also to hopefully fix https://github.com/microsoft/vscode-azurefunctions/issues/1401 + process.kill(runningFuncTaskItem.processId); + } } - else { - // Use `process.kill` because `TaskExecution.terminate` closes the terminal pane and erases all output - // Also to hopefully fix https://github.com/microsoft/vscode-azurefunctions/issues/1401 - process.kill(runningFuncTask.processId); + if (buildPath) { + runningFuncTaskMap.delete(workspaceFolder, buildPath); } - runningFuncTaskMap.delete(functionApp); + } + + if (killAll) { + runningFuncTaskMap.delete(workspaceFolder); } } -export async function getFuncPortFromTaskOrProject(context: IActionContext, funcTask: vscode.Task | undefined, projectPathOrTaskScope: string | vscode.WorkspaceFolder | vscode.TaskScope | undefined): Promise { +export async function getFuncPortFromTaskOrProject(context: IActionContext, funcTask: vscode.Task | undefined, projectPathOrTaskScope: string | vscode.WorkspaceFolder | vscode.TaskScope): Promise { try { // First, check the task itself if (funcTask && funcTask.execution instanceof vscode.ShellExecution) { @@ -140,3 +197,7 @@ export async function getFuncPortFromTaskOrProject(context: IActionContext, func // Finally, fall back to the default port return defaultFuncPort; } + +function normalizePath(fsPath: string): string { + return vscode.Uri.parse(path.normalize(fsPath).replace(/^(\/|\\)+|(\/|\\)+$/g, '')).fsPath; +} diff --git a/src/tree/localProject/LocalProjectTreeItem.ts b/src/tree/localProject/LocalProjectTreeItem.ts index 9e3b1b41d..a23a83b12 100644 --- a/src/tree/localProject/LocalProjectTreeItem.ts +++ b/src/tree/localProject/LocalProjectTreeItem.ts @@ -5,7 +5,7 @@ import { callWithTelemetryAndErrorHandling, type AzExtParentTreeItem, type AzExtTreeItem, type IActionContext } from '@microsoft/vscode-azext-utils'; import * as path from 'path'; -import { Disposable, type WorkspaceFolder } from 'vscode'; +import { Disposable, type TaskScope, type WorkspaceFolder } from 'vscode'; import { type FuncVersion } from '../../FuncVersion'; import { onDotnetFuncTaskReady } from '../../commands/pickFuncProcess'; import { functionJsonFileName, localSettingsFileName, type ProjectLanguage } from '../../constants'; @@ -105,9 +105,9 @@ export class LocalProjectTreeItem extends LocalProjectTreeItemBase implements Di await this.project.setApplicationSetting(context, key, value); } - private async onFuncTaskChanged(scope: string): Promise { + private async onFuncTaskChanged(scope: WorkspaceFolder | TaskScope | undefined): Promise { await callWithTelemetryAndErrorHandling('onFuncTaskChanged', async (context: IActionContext) => { - if (this.workspaceFolder.uri.fsPath === scope) { + if (this.workspaceFolder === scope) { context.errorHandling.suppressDisplay = true; context.telemetry.suppressIfSuccessful = true; await this.refresh(context); diff --git a/src/workspace/LocalProject.ts b/src/workspace/LocalProject.ts index 5981c2508..e8659c682 100644 --- a/src/workspace/LocalProject.ts +++ b/src/workspace/LocalProject.ts @@ -9,7 +9,7 @@ import { type FuncVersion } from "../FuncVersion"; import { hostFileName, localSettingsFileName } from "../constants"; import { parseHostJson, type IParsedHostJson } from "../funcConfig/host"; import { MismatchBehavior, getLocalSettingsJson, setLocalAppSetting, type ILocalSettingsJson } from "../funcConfig/local.settings"; -import { getFuncPortFromTaskOrProject, isFuncHostTask, runningFuncPortMap } from "../funcCoreTools/funcHostTask"; +import { getFuncPortFromTaskOrProject, isFuncHostTask, runningFuncTaskMap } from "../funcCoreTools/funcHostTask"; import { type ApplicationSettings, type FuncHostRequest } from "../tree/IProjectTreeItem"; import { ProjectSource } from "../tree/projectContextValues"; import { requestUtils } from "../utils/requestUtils"; @@ -42,7 +42,7 @@ export class LocalProject implements LocalProjectInternal { } public async getHostRequest(context: IActionContext): Promise { - let port = runningFuncPortMap.get(this.options.folder.uri.fsPath); + let port = runningFuncTaskMap.get(this.options.folder)?.portNumber; if (!port) { const funcTask: Task | undefined = (await tasks.fetchTasks()).find(t => t.scope === this.options.folder && isFuncHostTask(t)); port = await getFuncPortFromTaskOrProject(context, funcTask, this.options.effectiveProjectPath); diff --git a/src/workspace/listLocalFunctions.ts b/src/workspace/listLocalFunctions.ts index f2e4bd2fb..7879be5b8 100644 --- a/src/workspace/listLocalFunctions.ts +++ b/src/workspace/listLocalFunctions.ts @@ -76,7 +76,7 @@ function getHostStartTimeoutMS(): number { * Some projects (e.g. .NET Isolated and PyStein (i.e. Python model >=2)) don't have typical "function.json" files, so we'll have to ping localhost to get functions (only available if the project is running) */ async function getFunctionsForHostedProject(context: IActionContext, project: LocalProjectInternal): Promise { - if (runningFuncTaskMap.has(project.options.folder.uri.fsPath)) { + if (runningFuncTaskMap.has(project.options.folder, project.options.effectiveProjectPath)) { const hostRequest = await project.getHostRequest(context); const timeout = getHostStartTimeoutMS(); const startTime = Date.now(); diff --git a/src/workspace/listLocalProjects.ts b/src/workspace/listLocalProjects.ts index 8362bd034..096bc2969 100644 --- a/src/workspace/listLocalProjects.ts +++ b/src/workspace/listLocalProjects.ts @@ -92,7 +92,7 @@ export async function listLocalProjects(): Promise { type CompiledProjectInfo = { compiledProjectPath: string; isIsolated: boolean }; -async function getCompiledProjectInfo(context: IActionContext, projectPath: string, projectLanguage: ProjectLanguage): Promise { +export async function getCompiledProjectInfo(context: IActionContext, projectPath: string, projectLanguage: ProjectLanguage): Promise { if (projectLanguage === ProjectLanguage.CSharp || projectLanguage === ProjectLanguage.FSharp) { const projFiles: dotnetUtils.ProjectFile[] = await dotnetUtils.getProjFiles(context, projectLanguage, projectPath); if (projFiles.length === 1) {