From 9a429156dd5deaf25b7b6f7ccda3405a843c8b0e Mon Sep 17 00:00:00 2001 From: Garrett Campbell Date: Mon, 21 Jul 2025 12:39:32 -0400 Subject: [PATCH 1/6] implement a version of the MakefileTools api --- package.json | 3 +- src/api.ts | 19 ++++++ src/build/customBuildTask.ts | 74 ++++++++++++++++++++++ src/configuration.ts | 35 +++++----- src/extension.ts | 23 +++++-- src/launch.ts | 2 +- src/make.ts | 95 ++++++++++++++-------------- src/parser.ts | 3 +- src/test/fakeSuite/extension.test.ts | 10 +-- src/util.ts | 52 ++++++++++++--- yarn.lock | 34 +++++++++- 11 files changed, 265 insertions(+), 85 deletions(-) create mode 100644 src/api.ts create mode 100644 src/build/customBuildTask.ts diff --git a/package.json b/package.json index 2356a614..86a29604 100644 --- a/package.json +++ b/package.json @@ -911,7 +911,8 @@ "vscode-cpptools": "^6.1.0", "vscode-nls": "^5.0.0", "@vscode/extension-telemetry": "^0.9.6", - "vscode-jsonrpc": "^3.6.2" + "vscode-jsonrpc": "^3.6.2", + "vscode-makefile-tools-api": "file:../gcampbell/vscode-makefile-tools-api" }, "resolutions": { "ansi-regex": "^5.0.1", diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 00000000..71f65cfc --- /dev/null +++ b/src/api.ts @@ -0,0 +1,19 @@ +import * as api from "vscode-makefile-tools-api"; +import * as vscode from "vscode"; +import * as make from "./make"; +import { getBuildTargets } from "./configuration"; + +export class MakefileToolsApiImpl implements api.MakefileToolsApi { + constructor(public version: api.Version = api.Version.v1) { + } + + build(target?: string, clean?: boolean, cancellationToken?: vscode.CancellationToken): Promise { + return make.buildTarget(make.TriggeredBy.api, target ?? "", clean ?? false, cancellationToken); + } + clean(cancellationToken: vscode.CancellationToken): Promise { + return make.buildTarget(make.TriggeredBy.api, "clean", false, cancellationToken); + } + async listBuildTargets(): Promise { + return getBuildTargets(); + } +} diff --git a/src/build/customBuildTask.ts b/src/build/customBuildTask.ts new file mode 100644 index 00000000..d55b4a12 --- /dev/null +++ b/src/build/customBuildTask.ts @@ -0,0 +1,74 @@ +import * as vscode from 'vscode'; +import * as util from '../util'; +import { getCurPID } from '../make'; + +abstract class CommandConsumer { + output(line: string): void { + this._stdout.push(line); + } + error(error: string): void { + this._stderr.push(error); + } + get stdout() { + return this._stdout.join('\n'); + } + protected readonly _stdout = new Array(); + + get stderr() { + return this._stderr.join('\n'); + } + protected readonly _stderr = new Array(); +} + +const endOfLine: string = "\r\n"; + +export class CustomBuildTaskTerminal extends CommandConsumer implements vscode.Pseudoterminal { + + constructor(private command: string, private args: string[], private cwd: string, private env?: { [key: string]: string }) { + super(); + } + + private writeEmitter = new vscode.EventEmitter(); + private closeEmitter = new vscode.EventEmitter(); + public get onDidWrite(): vscode.Event { + return this.writeEmitter.event; + } + public get onDidClose(): vscode.Event { + return this.closeEmitter.event; + } + + override output(line: string): void { + this.writeEmitter.fire(line + endOfLine); + super.output(line); + } + + override error(error: string): void { + this.writeEmitter.fire(error + endOfLine); + super.error(error); + } + + private _process: util.SpawnProcess | undefined; + async open(_initialDimensions: vscode.TerminalDimensions | undefined): Promise { + this._process = util.spawnChildProcess( + this.command, + this.args, + { + workingDirectory: this.cwd, + stdoutCallback: (line: string) => this.output(line), + stderrCallback: (error: string) => this.error(error), + env: this.env + } + ) + const res: util.SpawnProcessResult = await this._process.result; + this.closeEmitter.fire(res.returnCode); + } + async close(): Promise { + if (this._process) { + if (this._process.child) { + await util.killTree(getCurPID()); + } + this._process = undefined; + } + } + +} \ No newline at end of file diff --git a/src/configuration.ts b/src/configuration.ts index 9d5c8de4..e45d1243 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -2429,6 +2429,25 @@ export async function setTargetByName(targetName: string): Promise { extension._projectOutlineProvider.updateBuildTarget(targetName); } +export async function getTargets(): Promise { + // Ensure "all" is always available as a target to select. + // There are scenarios when "all" might not be present in the list of available targets, + // for example when the extension is using a build log or dryrun cache of a previous state + // when a particular target was selected and a dryrun applied on that is producing a subset of targets, + // making it impossible to select "all" back again without resetting the Makefile Tools state + // or switching to a different makefile configuration or implementing an editable target quick pick. + // Another situation where "all" would inconveniently miss from the quick pick is when the user is + // providing a build log without the required verbosity for parsing targets (-p or --print-data-base switches). + // When the extension is not reading from build log or dryrun cache, we have logic to prevent + // "all" from getting lost: make sure the target is not appended to the make invocation + // whose output is used to parse the targets (as opposed to parsing for IntelliSense or launch targets + // when the current target must be appended to the make command). + if (!buildTargets.includes("all")) { + buildTargets.push("all"); + } + return buildTargets; +} + // Fill a drop-down with all the target names run by building the makefile for the current configuration // Triggers a cpptools configuration provider update after selection. // TODO: change the UI list to multiple selections mode and store an array of current active targets @@ -2466,21 +2485,7 @@ export async function selectTarget(): Promise { } } - // Ensure "all" is always available as a target to select. - // There are scenarios when "all" might not be present in the list of available targets, - // for example when the extension is using a build log or dryrun cache of a previous state - // when a particular target was selected and a dryrun applied on that is producing a subset of targets, - // making it impossible to select "all" back again without resetting the Makefile Tools state - // or switching to a different makefile configuration or implementing an editable target quick pick. - // Another situation where "all" would inconveniently miss from the quick pick is when the user is - // providing a build log without the required verbosity for parsing targets (-p or --print-data-base switches). - // When the extension is not reading from build log or dryrun cache, we have logic to prevent - // "all" from getting lost: make sure the target is not appended to the make invocation - // whose output is used to parse the targets (as opposed to parsing for IntelliSense or launch targets - // when the current target must be appended to the make command). - if (!buildTargets.includes("all")) { - buildTargets.push("all"); - } + getBuildTargets(); const chosen: string | undefined = await vscode.window.showQuickPick( buildTargets diff --git a/src/extension.ts b/src/extension.ts index 88223107..74a1c702 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -20,6 +20,8 @@ import * as cpp from "vscode-cpptools"; import * as nls from "vscode-nls"; import { TelemetryEventProperties } from "@vscode/extension-telemetry"; +import { Version, MakefileToolsApi, MakefileToolsExtensionExports } from "vscode-makefile-tools-api"; +import { MakefileToolsApiImpl } from "./api"; nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone, @@ -31,7 +33,7 @@ let launcher: launch.Launcher = launch.getLauncher(); export let extension: MakefileToolsExtension; -export class MakefileToolsExtension { +export class MakefileToolsExtension implements MakefileToolsExtensionExports { public readonly _projectOutlineProvider = new tree.ProjectOutlineProvider(); private readonly _projectOutlineTreeView = vscode.window.createTreeView( "makefile.outline", @@ -51,10 +53,18 @@ export class MakefileToolsExtension { private cppToolsAPI?: cpp.CppToolsApi; private cppConfigurationProviderRegister?: Promise; private compilerFullPath?: string; + public api: MakefileToolsApiImpl; public constructor( - public readonly extensionContext: vscode.ExtensionContext - ) {} + public readonly extensionContext: vscode.ExtensionContext, + apiVersion: Version = Version.v1 + ) { + this.api = new MakefileToolsApiImpl(apiVersion); + } + + getApi(version: Version): MakefileToolsApi { + return new MakefileToolsApiImpl(version); + } public updateBuildLogPresent(newValue: boolean): void { vscode.commands.executeCommand( @@ -258,7 +268,7 @@ export class MakefileToolsExtension { export async function activate( context: vscode.ExtensionContext -): Promise { +): Promise { if (process.env["MAKEFILE_TOOLS_TESTING"] === "1") { await vscode.commands.executeCommand( "setContext", @@ -858,6 +868,11 @@ export async function activate( if (telemetryProperties && util.hasProperties(telemetryProperties)) { telemetry.logEvent("settings", telemetryProperties); } + + return { getApi: (_version: Version) => { + return extension.api; + } + }; } export async function deactivate(): Promise { diff --git a/src/launch.ts b/src/launch.ts index 7538e7c8..f323a233 100644 --- a/src/launch.ts +++ b/src/launch.ts @@ -290,7 +290,7 @@ export class Launcher implements vscode.Disposable { make.TriggeredBy.buildTarget, currentBuildTarget, false - )) === make.ConfigureBuildReturnCodeTypes.success; + )).result === make.ConfigureBuildReturnCodeTypes.success; if (!buildSuccess) { logger.message( localize( diff --git a/src/make.ts b/src/make.ts index 308bafa3..1b648d43 100644 --- a/src/make.ts +++ b/src/make.ts @@ -17,6 +17,8 @@ import * as vscode from "vscode"; import { v4 as uuidv4 } from "uuid"; import * as nls from "vscode-nls"; +import { CustomBuildTaskTerminal } from "./build/customBuildTask"; +import { CommandResult } from "vscode-makefile-tools-api"; nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone, @@ -118,6 +120,7 @@ export enum TriggeredBy { configureBeforeLaunchTargetChange = "configureDirty (before launch target change), settings (configureAfterCommand)", launch = "Launch (debug|run)", tests = "Makefile Tools Regression Tests", + api = "Makefile Tools API" } let fileIndex: Map = new Map< @@ -284,14 +287,15 @@ const makefileBuildTaskName: string = "Makefile Tools Build Task"; export async function buildTarget( triggeredBy: TriggeredBy, target: string, - clean: boolean = false -): Promise { + clean: boolean = false, + cancellationToken?: vscode.CancellationToken +): Promise { if (blockedByOp(Operations.build)) { - return ConfigureBuildReturnCodeTypes.blocked; + return { result: ConfigureBuildReturnCodeTypes.blocked }; } if (!saveAll()) { - return ConfigureBuildReturnCodeTypes.saveFailed; + return { result: ConfigureBuildReturnCodeTypes.saveFailed }; } // Same start time for build and an eventual configure. @@ -304,7 +308,7 @@ export async function buildTarget( logger.message( localize( "project.needs.configure.for.build", - "The project needs to configure in order to build properly the current target." + "The project needs to configure in order to build properly." ) ); if (configuration.getConfigureAfterCommand()) { @@ -327,7 +331,7 @@ export async function buildTarget( configuration.getCurrentMakefileConfiguration(); let configAndTarget: string = config; if (target) { - target = target.trimLeft(); + target = target.trimStart(); if (target !== "") { configAndTarget += "/" + target; } @@ -356,7 +360,8 @@ export async function buildTarget( cancellable: true, }, async (progress, cancel) => { - cancel.onCancellationRequested(async () => { + const combinedToken = util.createCombinedCancellationToken(cancel, cancellationToken); + combinedToken.onCancellationRequested(async () => { progress.report({ increment: 1, message: localize("make.build.cancelling", "Cancelling..."), @@ -405,7 +410,7 @@ export async function buildTarget( await doBuildTarget(progress, "clean", clearOutput); } - let retc: number = await doBuildTarget( + let retc: CommandResult = await doBuildTarget( progress, target, clearOutput && !clean @@ -414,7 +419,7 @@ export async function buildTarget( // We need to know whether this build was cancelled by the user // more than the real exit code of the make process in this circumstance. if (cancelBuild) { - retc = ConfigureBuildReturnCodeTypes.cancelled; + retc.result = ConfigureBuildReturnCodeTypes.cancelled; } let buildElapsedTime: number = util.elapsedTimeSince(buildStartTime); @@ -451,18 +456,10 @@ export async function doBuildTarget( progress: vscode.Progress<{}>, target: string, clearTerminalOutput: boolean -): Promise { +): Promise { let makeArgs: string[] = prepareBuildTarget(target); try { - const quotingStlye: vscode.ShellQuoting = vscode.ShellQuoting.Strong; const quotingStyleName: string = "Strong"; - let myTaskCommand: vscode.ShellQuotedString = { - value: configuration.getConfigurationMakeCommand(), - quoting: quotingStlye, - }; - let myTaskArgs: vscode.ShellQuotedString[] = makeArgs.map((arg) => { - return { value: arg, quoting: quotingStlye }; - }); const cwd: string = configuration.makeBaseDirectory(); if (!util.checkDirectoryExistsSync(cwd)) { @@ -474,31 +471,29 @@ export async function doBuildTarget( cwd ) ); - return ConfigureBuildReturnCodeTypes.notFound; + return { result: ConfigureBuildReturnCodeTypes.notFound }; } - let myTaskOptions: vscode.ShellExecutionOptions = { - // Only pass a defined environment if there are modified environment variables. - env: - Object.keys(util.modifiedEnvironmentVariables).length > 0 - ? util.mergeEnvironment( - util.modifiedEnvironmentVariables as util.EnvironmentVariables - ) - : undefined, - cwd, - }; - - let shellExec: vscode.ShellExecution = new vscode.ShellExecution( - myTaskCommand, - myTaskArgs, - myTaskOptions - ); + const customBuildTask = new CustomBuildTaskTerminal( + configuration.getConfigurationMakeCommand(), + makeArgs, + cwd, + Object.keys(util.modifiedEnvironmentVariables).length > 0 + ? util.mergeEnvironment( + util.modifiedEnvironmentVariables as util.EnvironmentVariables + ) + : undefined + ); let myTask: vscode.Task = new vscode.Task( { type: "shell", group: "build", label: makefileBuildTaskName }, vscode.TaskScope.Workspace, makefileBuildTaskName, "makefile", - shellExec + new vscode.CustomExecution( + async(): Promise => { + return customBuildTask; + } + ) ); myTask.problemMatchers = configuration.getConfigurationProblemMatchers(); @@ -511,25 +506,31 @@ export async function doBuildTarget( 'Executing task: "{0}" with quoting style "{1}"\n command name: {2}\n command args {3}', myTask.name, quotingStyleName, - myTaskCommand.value, + configuration.getConfigurationMakeCommand(), makeArgs.join() ), "Debug" ); await vscode.tasks.executeTask(myTask); - const result: number = await new Promise((resolve) => { + const result: CommandResult = await new Promise((resolve) => { let disposable: vscode.Disposable = vscode.tasks.onDidEndTaskProcess( (e: vscode.TaskProcessEndEvent) => { if (e.execution.task.name === makefileBuildTaskName) { disposable.dispose(); - resolve(e.exitCode ?? ConfigureBuildReturnCodeTypes.other); + if (e.exitCode !== undefined) { + resolve({ + result: e.exitCode, + stdout: customBuildTask.stdout, + stderr: customBuildTask.stderr, + }) + } } } ); }); - if (result !== ConfigureBuildReturnCodeTypes.success) { + if (result.result !== ConfigureBuildReturnCodeTypes.success) { logger.message( localize( "target.failed.to.build", @@ -547,11 +548,11 @@ export async function doBuildTarget( ); } - return result; + return result } catch (error) { // No need for notification popup, since the build result is visible already in the output channel logger.message(error); - return ConfigureBuildReturnCodeTypes.notFound; + return { result: ConfigureBuildReturnCodeTypes.notFound }; } } @@ -805,11 +806,12 @@ export async function generateParseContent( stdoutCallback: stdout, stderrCallback: stderr, }; - const result: util.SpawnProcessResult = await util.spawnChildProcess( + const spawnProcess: util.SpawnProcess = util.spawnChildProcess( configuration.getConfigurationMakeCommand(), makeArgs, opts ); + const result: util.SpawnProcessResult = await spawnProcess.result; clearInterval(timeout); let elapsedTime: number = util.elapsedTimeSince(startTime); logger.message( @@ -930,7 +932,7 @@ export async function prePostConfigureHelper( cancellable: false, }, async (progress) => { - await util.killTree(progress, curPID); + await util.killTree(curPID, progress); } ); }); @@ -1185,11 +1187,12 @@ export async function runPrePostConfigureScript( stdoutCallback: stdout, stderrCallback: stderr, }; - const result: util.SpawnProcessResult = await util.spawnChildProcess( + const spawnProcess: util.SpawnProcess = util.spawnChildProcess( runCommand, concreteScriptArgs, opts ); + const result: util.SpawnProcessResult = await spawnProcess.result; if (result.returnCode === ConfigureBuildReturnCodeTypes.success) { if (someErr) { // Depending how the preconfigure scripts (and any inner called sub-scripts) are written, @@ -1550,7 +1553,7 @@ export async function configure( cancellable: false, }, async (progress) => { - return util.killTree(progress, curPID); + return util.killTree(curPID, progress); } ); } else { diff --git a/src/parser.ts b/src/parser.ts index d229c42d..19e44720 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -661,11 +661,12 @@ async function parseAnySwitchFromToolArguments( stdoutCallback: stdout, stderrCallback: stderr, }; - const result: util.SpawnProcessResult = await util.spawnChildProcess( + const spawnProcess: util.SpawnProcess = util.spawnChildProcess( runCommand, scriptArgs, opts ); + const result: util.SpawnProcessResult = await spawnProcess.result; if (result.returnCode !== 0) { logger.message( localize( diff --git a/src/test/fakeSuite/extension.test.ts b/src/test/fakeSuite/extension.test.ts index 5dfcb038..78ceb993 100644 --- a/src/test/fakeSuite/extension.test.ts +++ b/src/test/fakeSuite/extension.test.ts @@ -359,7 +359,7 @@ suite("Fake dryrun parsing", () => { ); let status: string = await vscode.commands.executeCommand( "makefile.validateLaunchConfiguration" - ); + ) ?? ""; let launchConfiguration: configuration.LaunchConfiguration | undefined; if (status === launch.LaunchStatuses.success) { launchConfiguration = await vscode.commands.executeCommand( @@ -476,7 +476,7 @@ suite("Fake dryrun parsing", () => { ); let status: string = await vscode.commands.executeCommand( "makefile.validateLaunchConfiguration" - ); + ) ?? ""; let launchConfiguration: configuration.LaunchConfiguration | undefined; if (status === launch.LaunchStatuses.success) { launchConfiguration = await vscode.commands.executeCommand( @@ -571,7 +571,7 @@ suite("Fake dryrun parsing", () => { ); let status: string = await vscode.commands.executeCommand( "makefile.validateLaunchConfiguration" - ); + ) ?? ""; let launchConfiguration: configuration.LaunchConfiguration | undefined; if (status === launch.LaunchStatuses.success) { launchConfiguration = await vscode.commands.executeCommand( @@ -671,7 +671,7 @@ suite("Fake dryrun parsing", () => { ); let status: string = await vscode.commands.executeCommand( "makefile.validateLaunchConfiguration" - ); + ) ?? ""; let launchConfiguration: configuration.LaunchConfiguration | undefined; if (status === launch.LaunchStatuses.success) { launchConfiguration = await vscode.commands.executeCommand( @@ -814,7 +814,7 @@ suite("Fake dryrun parsing", () => { "makefile.expandVariablesInSetting", "defaultLaunchConfiguration.stopAtEntry", "${config:makefile.panel.visibility.debug}" - ); + ) ?? ""; let tmpDefaultLaunchConfiguration: configuration.DefaultLaunchConfiguration = { miDebuggerPath: diff --git a/src/util.ts b/src/util.ts index 9bef4a04..91294382 100644 --- a/src/util.ts +++ b/src/util.ts @@ -74,6 +74,7 @@ export interface ProcOptions { ensureQuoted?: boolean; stdoutCallback?: (stdout: string) => void; stderrCallback?: (stderr: string) => void; + env?: { [key: string]: string }; } export function checkFileExistsSync(filePath: string): boolean { @@ -238,8 +239,8 @@ function taskKill(pid: number): Promise { } export async function killTree( - progress: vscode.Progress<{}>, - pid: number + pid: number, + progress?: vscode.Progress<{}>, ): Promise { if (process.platform === "win32") { try { @@ -272,11 +273,12 @@ export async function killTree( stdoutCallback: stdout, }; // pgrep should run on english, regardless of the system setting. - const result: SpawnProcessResult = await spawnChildProcess( + const process: SpawnProcess = spawnChildProcess( "pgrep", ["-P", pid.toString()], opts ); + const result: SpawnProcessResult = await process.result; if (!!stdoutStr.length) { children = stdoutStr .split("\n") @@ -291,7 +293,7 @@ export async function killTree( ); for (const other of children) { if (other) { - await killTree(progress, other); + await killTree(other, progress); } } } @@ -304,7 +306,7 @@ export async function killTree( logger.message( localize("killing.process", "Killing process PID = {0}", pid) ); - progress.report({ + progress?.report({ increment: 1, message: localize( "utils.terminate.process", @@ -356,13 +358,18 @@ export interface SpawnProcessResult { signal: string; } +export interface SpawnProcess { + result: Promise; + child: child_process.ChildProcess | undefined; +} + // Helper to spawn a child process, hooked to callbacks that are processing stdout/stderr // forceEnglish is true when the caller relies on parsing english words from the output. export function spawnChildProcess( processName: string, args: string[], options: ProcOptions -): Promise { +): SpawnProcess { const localeOverride: EnvironmentVariables = { LANG: "C", LC_ALL: "C", @@ -374,10 +381,13 @@ export function spawnChildProcess( : {}; const finalEnvironment: EnvironmentVariables = mergeEnvironment( process.env as EnvironmentVariables, + options.env ? options.env : {}, environment ); - return new Promise((resolve, reject) => { + let child: child_process.ChildProcess | undefined; + + const result = new Promise((resolve, reject) => { // Honor the "terminal.integrated.automationShell." setting. // According to documentation (and settings.json schema), the three allowed values for are "windows", "linux" and "osx". // child_process.SpawnOptions accepts a string (which can be read from the above setting) or the boolean true to let VSCode pick a default @@ -427,7 +437,7 @@ export function spawnChildProcess( ); } - const child: child_process.ChildProcess = child_process.spawn( + child = child_process.spawn( qProcessName, qArgs, { @@ -464,6 +474,8 @@ export function spawnChildProcess( reject(new Error(`Failed to spawn process: ${processName} ${args}`)); } }); + + return { child, result }; } export async function replaceCommands( @@ -1178,7 +1190,7 @@ export async function expandVariablesInSetting( telemetryProperties.pattern = result[4]; telemetryProperties.info = result[5]; try { - toStr = await vscode.commands.executeCommand(result[5]); + toStr = await vscode.commands.executeCommand(result[5]) ?? ""; } catch (e) { toStr = "unknown"; logger.message( @@ -1374,3 +1386,25 @@ export function thisExtensionPackage(): PackageJSON { export function thisExtensionPath(): string { return thisExtension().extensionPath; } + +export function createCombinedCancellationToken(...tokens: (vscode.CancellationToken | undefined)[]): vscode.CancellationToken { + const combinedSource = new vscode.CancellationTokenSource(); + + const disposables: vscode.Disposable[] = []; + + for (const token of tokens) { + if (token !== undefined) { + if (token.isCancellationRequested) { + combinedSource.cancel(); + break; + } + disposables.push(token.onCancellationRequested(() => combinedSource.cancel())); + } + } + + combinedSource.token.onCancellationRequested(() => { + disposables.forEach(d => d.dispose()); + }); + + return combinedSource.token; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5be6dac9..d43807e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5075,7 +5075,7 @@ streamfilter@^3.0.0: dependencies: readable-stream "^3.0.6" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity "sha1-JpxxF9J7Ba0uU2gwqOyJXvnG0BA= sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==" @@ -5110,6 +5110,15 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity "sha1-JpxxF9J7Ba0uU2gwqOyJXvnG0BA= sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==" + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -5133,7 +5142,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity "sha1-nibGPTD1NEPpSJSVshBdN7Z6hdk= sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==" @@ -5161,6 +5170,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity "sha1-nibGPTD1NEPpSJSVshBdN7Z6hdk= sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==" + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -5766,6 +5782,9 @@ vscode-jsonrpc@^3.6.2: resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/vscode-jsonrpc/-/vscode-jsonrpc-3.6.2.tgz#3b5eef691159a15556ecc500e9a8a0dd143470c8" integrity "sha1-O17vaRFZoVVW7MUA6aig3RQ0cMg= sha512-T24Jb5V48e4VgYliUXMnZ379ItbrXgOimweKaJshD84z+8q7ZOZjJan0MeDe+Ugb+uqERDVV8SBmemaGMSMugA==" +"vscode-makefile-tools-api@file:../gcampbell/vscode-makefile-tools-api": + version "1.0.0" + vscode-nls-dev@^3.3.2: version "3.3.2" resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/vscode-nls-dev/-/vscode-nls-dev-3.3.2.tgz#f15e0b627f948224a96dc0288da3c3b9c6dfae81" @@ -5917,7 +5936,7 @@ workerpool@6.1.0: resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" integrity "sha1-qOA4tMlFaVloUt56jqQiju/es3s= sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity "sha1-Z+FFz/UQpqaYS98RUpEdadLrnkM= sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==" @@ -5943,6 +5962,15 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity "sha1-Z+FFz/UQpqaYS98RUpEdadLrnkM= sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==" + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 62810a7f9ba43fda060a1e8710356c114c3259f2 Mon Sep 17 00:00:00 2001 From: Garrett Campbell Date: Mon, 21 Jul 2025 17:03:50 -0400 Subject: [PATCH 2/6] slightly modify name --- src/make.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/make.ts b/src/make.ts index 1b648d43..00ec1614 100644 --- a/src/make.ts +++ b/src/make.ts @@ -378,7 +378,7 @@ export async function buildTarget( // This will take care of all processes that were spawned. let myTask: vscode.TaskExecution | undefined = vscode.tasks.taskExecutions.find((tsk) => { - if (tsk.task.name === makefileBuildTaskName) { + if (tsk.task.name.startsWith(makefileBuildTaskName)) { return tsk; } }); @@ -474,8 +474,10 @@ export async function doBuildTarget( return { result: ConfigureBuildReturnCodeTypes.notFound }; } + const makeCommand = configuration.getConfigurationMakeCommand(); + const customBuildTask = new CustomBuildTaskTerminal( - configuration.getConfigurationMakeCommand(), + makeCommand, makeArgs, cwd, Object.keys(util.modifiedEnvironmentVariables).length > 0 @@ -484,10 +486,12 @@ export async function doBuildTarget( ) : undefined ); + + const label = `${makefileBuildTaskName} "${makeCommand} ${makeArgs.join(" ")}"`; let myTask: vscode.Task = new vscode.Task( - { type: "shell", group: "build", label: makefileBuildTaskName }, + { type: "shell", group: "build", label: label }, vscode.TaskScope.Workspace, - makefileBuildTaskName, + label, "makefile", new vscode.CustomExecution( async(): Promise => { @@ -506,7 +510,7 @@ export async function doBuildTarget( 'Executing task: "{0}" with quoting style "{1}"\n command name: {2}\n command args {3}', myTask.name, quotingStyleName, - configuration.getConfigurationMakeCommand(), + makeCommand, makeArgs.join() ), "Debug" @@ -516,7 +520,7 @@ export async function doBuildTarget( const result: CommandResult = await new Promise((resolve) => { let disposable: vscode.Disposable = vscode.tasks.onDidEndTaskProcess( (e: vscode.TaskProcessEndEvent) => { - if (e.execution.task.name === makefileBuildTaskName) { + if (e.execution.task.name.startsWith(makefileBuildTaskName)) { disposable.dispose(); if (e.exitCode !== undefined) { resolve({ From 968dc39f62a439979f5e1070355b177250029fb0 Mon Sep 17 00:00:00 2001 From: Garrett Campbell Date: Tue, 22 Jul 2025 15:00:44 -0400 Subject: [PATCH 3/6] add api telemetry --- src/api.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/api.ts b/src/api.ts index 71f65cfc..f080e56a 100644 --- a/src/api.ts +++ b/src/api.ts @@ -2,18 +2,30 @@ import * as api from "vscode-makefile-tools-api"; import * as vscode from "vscode"; import * as make from "./make"; import { getBuildTargets } from "./configuration"; +import { logEvent } from "./telemetry"; +import { log } from "console"; export class MakefileToolsApiImpl implements api.MakefileToolsApi { constructor(public version: api.Version = api.Version.v1) { } build(target?: string, clean?: boolean, cancellationToken?: vscode.CancellationToken): Promise { + logApiTelemetry("build", this.version); return make.buildTarget(make.TriggeredBy.api, target ?? "", clean ?? false, cancellationToken); } clean(cancellationToken: vscode.CancellationToken): Promise { + logApiTelemetry("clean", this.version); return make.buildTarget(make.TriggeredBy.api, "clean", false, cancellationToken); } async listBuildTargets(): Promise { + logApiTelemetry("listBuildTargets", this.version); return getBuildTargets(); } } + +function logApiTelemetry(event: string, version: api.Version): void { + logEvent("api", { + event: event, + version: api.Version.v1.toString(), + }); +} From a512b9ec4c390b0a2b4e5278df9c061e0e269230 Mon Sep 17 00:00:00 2001 From: Garrett Campbell Date: Tue, 22 Jul 2025 15:05:08 -0400 Subject: [PATCH 4/6] update parameter for logApiTelemetry --- src/api.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api.ts b/src/api.ts index f080e56a..74d15f0e 100644 --- a/src/api.ts +++ b/src/api.ts @@ -23,9 +23,9 @@ export class MakefileToolsApiImpl implements api.MakefileToolsApi { } } -function logApiTelemetry(event: string, version: api.Version): void { +function logApiTelemetry(method: string, version: api.Version): void { logEvent("api", { - event: event, - version: api.Version.v1.toString(), + method: method, + version: version.toString(), }); } From 7f792cbee12d585787019afdd4bf1868843d3815 Mon Sep 17 00:00:00 2001 From: Garrett Campbell Date: Tue, 29 Jul 2025 09:02:59 -0400 Subject: [PATCH 5/6] use exitCode instead of result, also correct method call --- package.json | 2 +- src/api.ts | 5 ++--- src/configuration.ts | 4 ++-- src/launch.ts | 2 +- src/make.ts | 14 +++++++------- yarn.lock | 4 ++-- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 86a29604..7c43da08 100644 --- a/package.json +++ b/package.json @@ -912,7 +912,7 @@ "vscode-nls": "^5.0.0", "@vscode/extension-telemetry": "^0.9.6", "vscode-jsonrpc": "^3.6.2", - "vscode-makefile-tools-api": "file:../gcampbell/vscode-makefile-tools-api" + "vscode-makefile-tools-api": "file:../vscode-makefile-tools-api" }, "resolutions": { "ansi-regex": "^5.0.1", diff --git a/src/api.ts b/src/api.ts index 74d15f0e..abbe04a0 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,9 +1,8 @@ import * as api from "vscode-makefile-tools-api"; import * as vscode from "vscode"; import * as make from "./make"; -import { getBuildTargets } from "./configuration"; +import { getTargets } from "./configuration"; import { logEvent } from "./telemetry"; -import { log } from "console"; export class MakefileToolsApiImpl implements api.MakefileToolsApi { constructor(public version: api.Version = api.Version.v1) { @@ -19,7 +18,7 @@ export class MakefileToolsApiImpl implements api.MakefileToolsApi { } async listBuildTargets(): Promise { logApiTelemetry("listBuildTargets", this.version); - return getBuildTargets(); + return getTargets(); } } diff --git a/src/configuration.ts b/src/configuration.ts index e45d1243..63413f37 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -2429,7 +2429,7 @@ export async function setTargetByName(targetName: string): Promise { extension._projectOutlineProvider.updateBuildTarget(targetName); } -export async function getTargets(): Promise { +export function getTargets(): string[] { // Ensure "all" is always available as a target to select. // There are scenarios when "all" might not be present in the list of available targets, // for example when the extension is using a build log or dryrun cache of a previous state @@ -2485,7 +2485,7 @@ export async function selectTarget(): Promise { } } - getBuildTargets(); + getTargets(); const chosen: string | undefined = await vscode.window.showQuickPick( buildTargets diff --git a/src/launch.ts b/src/launch.ts index f323a233..891370e1 100644 --- a/src/launch.ts +++ b/src/launch.ts @@ -290,7 +290,7 @@ export class Launcher implements vscode.Disposable { make.TriggeredBy.buildTarget, currentBuildTarget, false - )).result === make.ConfigureBuildReturnCodeTypes.success; + )).exitCode === make.ConfigureBuildReturnCodeTypes.success; if (!buildSuccess) { logger.message( localize( diff --git a/src/make.ts b/src/make.ts index 00ec1614..0a974e1b 100644 --- a/src/make.ts +++ b/src/make.ts @@ -291,11 +291,11 @@ export async function buildTarget( cancellationToken?: vscode.CancellationToken ): Promise { if (blockedByOp(Operations.build)) { - return { result: ConfigureBuildReturnCodeTypes.blocked }; + return { exitCode: ConfigureBuildReturnCodeTypes.blocked }; } if (!saveAll()) { - return { result: ConfigureBuildReturnCodeTypes.saveFailed }; + return { exitCode: ConfigureBuildReturnCodeTypes.saveFailed }; } // Same start time for build and an eventual configure. @@ -419,7 +419,7 @@ export async function buildTarget( // We need to know whether this build was cancelled by the user // more than the real exit code of the make process in this circumstance. if (cancelBuild) { - retc.result = ConfigureBuildReturnCodeTypes.cancelled; + retc.exitCode = ConfigureBuildReturnCodeTypes.cancelled; } let buildElapsedTime: number = util.elapsedTimeSince(buildStartTime); @@ -471,7 +471,7 @@ export async function doBuildTarget( cwd ) ); - return { result: ConfigureBuildReturnCodeTypes.notFound }; + return { exitCode: ConfigureBuildReturnCodeTypes.notFound }; } const makeCommand = configuration.getConfigurationMakeCommand(); @@ -524,7 +524,7 @@ export async function doBuildTarget( disposable.dispose(); if (e.exitCode !== undefined) { resolve({ - result: e.exitCode, + exitCode: e.exitCode, stdout: customBuildTask.stdout, stderr: customBuildTask.stderr, }) @@ -534,7 +534,7 @@ export async function doBuildTarget( ); }); - if (result.result !== ConfigureBuildReturnCodeTypes.success) { + if (result.exitCode !== ConfigureBuildReturnCodeTypes.success) { logger.message( localize( "target.failed.to.build", @@ -556,7 +556,7 @@ export async function doBuildTarget( } catch (error) { // No need for notification popup, since the build result is visible already in the output channel logger.message(error); - return { result: ConfigureBuildReturnCodeTypes.notFound }; + return { exitCode: ConfigureBuildReturnCodeTypes.notFound }; } } diff --git a/yarn.lock b/yarn.lock index d43807e1..749408c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5782,8 +5782,8 @@ vscode-jsonrpc@^3.6.2: resolved "https://pkgs.dev.azure.com/azure-public/VisualCpp/_packaging/cpp_PublicPackages/npm/registry/vscode-jsonrpc/-/vscode-jsonrpc-3.6.2.tgz#3b5eef691159a15556ecc500e9a8a0dd143470c8" integrity "sha1-O17vaRFZoVVW7MUA6aig3RQ0cMg= sha512-T24Jb5V48e4VgYliUXMnZ379ItbrXgOimweKaJshD84z+8q7ZOZjJan0MeDe+Ugb+uqERDVV8SBmemaGMSMugA==" -"vscode-makefile-tools-api@file:../gcampbell/vscode-makefile-tools-api": - version "1.0.0" +"vscode-makefile-tools-api@file:../vscode-makefile-tools-api": + version "0.0.1" vscode-nls-dev@^3.3.2: version "3.3.2" From 264b914d79d76e66b3a7d20c6da99a649faa37c7 Mon Sep 17 00:00:00 2001 From: Garrett Campbell Date: Thu, 31 Jul 2025 17:33:28 -0400 Subject: [PATCH 6/6] use setImmediate to let all cancellation listeners happen. This works because setImmediate will run on the next event loop, while the cancellation listeners are on the current loop --- src/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index 91294382..c1a254bc 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1403,7 +1403,7 @@ export function createCombinedCancellationToken(...tokens: (vscode.CancellationT } combinedSource.token.onCancellationRequested(() => { - disposables.forEach(d => d.dispose()); + setImmediate(() => disposables.forEach(d => d.dispose())); }); return combinedSource.token;