Skip to content

Commit be92c7e

Browse files
authored
Windows fixes (#100)
Wrong args passed, must pass shell=true due to recent security fix Backed out the use of secure spawn() call on Windows since I couldn't make it do anything but hang (even with shell=True and stdio=[ignore,pipe,pipe]) and the main thing is for the linux use to be as good as it can be Also added a progress notificiation while running commands since apama_project takes so long
1 parent 9612c65 commit be92c7e

5 files changed

Lines changed: 50 additions & 101 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
## v2.2.1
2-
* Fixed an issue with the extension not locating the Apama installation when executed in a Docker development container.
2+
* Fixed an issue with the extension not locating the Apama installation when executed in a Docker development container, and on Windows.
33

44
## v2.2.0
55
* The "Apama Projects" view now only searches the top level directory in each workspace folder for projects. It no longer searches for projects nested under the top level workspace folder.

src/apama_project/apamaProjectView.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ export class ApamaProjectView
282282
}
283283
}
284284

285+
// nb: in future we might use the --json output mode added instead of parsing the text output
285286
apama_project
286287
.run(project.fsDir, ["list", "bundles"])
287288
.then((result) => {

src/apama_util/apamaenvironment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async function getApamaExecutableCommand(command: ApamaExecutables, showError=tr
4141
return ok({command: `${apamaBin}/${command}`, args: []})
4242
}
4343
} else {
44-
return ok({command: `${apamaBin}/apama_env.bat`, args: [command]});
44+
return ok({command: `${apamaBin}\\apama_env.bat`, args: [command]});
4545
}
4646
}
4747

src/apama_util/apamarunner.ts

Lines changed: 38 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,58 @@
1-
import { ChildProcess, spawn as spawnCallback, execFile as execFileCallback} from "child_process";
2-
import { Logger } from "../logger/logger";
1+
import { exec, execFile} from "child_process";
32
import { platform } from "os";
43
import { promisify } from "util";
4+
import { logger } from "../extension";
5+
import {
6+
ProgressLocation,
7+
window
8+
} from "vscode";
9+
10+
const execPromisify = promisify(exec);
11+
const execFilePromisify = promisify(execFile);
512

6-
const spawn = promisify(spawnCallback);
7-
const execFile = promisify(execFileCallback);
813

914
export class ApamaRunner {
1015
stdout = "";
1116
stderr = "";
1217

1318
constructor(
19+
/** Display name for this process */
1420
public name: string,
21+
/** e.g. [XXX/apama_env, apama_project] or [XXX/apama_project] */
1522
public command: string[],
1623
) {}
1724

1825
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1926
async run(workingDir: string, args: string[]): Promise<any> {
20-
//if fails returns promise.reject including err
21-
if (platform() === "win32") {
22-
return await spawn(
23-
this.command[0],
24-
[...args.slice(1), ...args],
25-
{cwd: workingDir}
26-
)
27-
} else {
28-
return await execFile(
29-
this.command[0],
30-
[...this.command.slice(1), ...args],
31-
{cwd: workingDir}
32-
)
33-
}
34-
}
35-
}
36-
37-
export class ApamaAsyncRunner {
38-
stdout = "";
39-
stderr = "";
40-
child?: ChildProcess;
41-
42-
constructor(
43-
public name: string,
44-
public command: string,
45-
private logger: Logger,
46-
) {}
47-
48-
//
49-
// If you call this withShell = true it will run the command under that shell
50-
// this means the process may need to be managed separately as it may get detached
51-
// when you kill the parent (correlator behaves that way)
52-
// I use engine_management to control the running correlator
53-
//
54-
// TODO: pipes configuration might be worth passing as an argument
55-
//
56-
public start(
57-
args: string[],
58-
withShell: boolean,
59-
defaultHandlers: boolean,
60-
): ChildProcess {
61-
//N.B. this potentially will leave the correlator running - future work required...
62-
if (this.child && !this.child.killed) {
63-
this.logger.appendLine(this.name + " already started, stopping...");
64-
this.child.kill("SIGKILL");
65-
}
66-
67-
this.logger.appendLine("Starting " + this.name);
68-
this.child = spawnCallback(this.command[0], [...this.command.slice(1), ...args], {
69-
shell: withShell,
70-
stdio: ["pipe", "pipe", "pipe"],
71-
});
72-
73-
//Running with process Id
74-
this.logger.appendLine(this.name + " started, PID:" + this.child.pid);
75-
76-
//Notify the logger if it stopped....
77-
this.child.once("exit", (exitCode) =>
78-
this.logger.appendLine(this.name + " stopped, exit code: " + exitCode),
79-
);
80-
81-
if (defaultHandlers) {
82-
this.child.stdout?.setEncoding("utf8");
83-
this.child.stdout?.on("data", (data: string) => {
84-
if (this.logger) {
85-
this.logger.appendLine(data);
86-
}
87-
});
88-
}
89-
90-
return this.child;
91-
}
92-
93-
public stop(): Promise<void> {
94-
return new Promise((resolve) => {
95-
if (this.child && !this.child.killed) {
96-
this.child.once("exit", () => {
97-
resolve();
98-
});
99-
100-
this.logger.appendLine("Process " + this.name + " stopping...");
101-
this.child.kill("SIGINT");
102-
const attemptedToKill = this.child;
103-
setTimeout(() => {
104-
if (!attemptedToKill.killed) {
105-
this.logger.appendLine(
106-
"Failed to stop shell in 5 seconds, killing...",
27+
return await window.withProgress(
28+
{
29+
// since sometimes (e.g. apama_project) the command takes a long time, giving a progress notification is helpful
30+
location: ProgressLocation.Notification,
31+
title: `Running ${this.name}...`,
32+
cancellable: false,
33+
},
34+
async () =>
35+
{
36+
try {
37+
if (platform() === "win32") {
38+
logger.debug("Running command: " + this.command[0] + " " + [...this.command.slice(1), ...args].join(" "));
39+
return await execPromisify([...this.command, ...args].join(" "), { cwd: workingDir });
40+
} else {
41+
return await execFilePromisify(
42+
this.command[0],
43+
[...this.command.slice(1), ...args],
44+
{ cwd: workingDir }
10745
);
108-
attemptedToKill.kill("SIGKILL");
10946
}
110-
}, 5000);
111-
} else {
112-
resolve();
47+
} catch (error) {
48+
const enhancedError = new Error(
49+
`Error running command '${this.command[0]} ${[...this.command.slice(1), ...args].join(" ")}': ${error}`
50+
);
51+
if (error instanceof Error) enhancedError.stack = error.stack;
52+
throw enhancedError;
53+
}
11354
}
114-
});
55+
);
11556
}
11657
}
58+

src/extension.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use_strict";
22

33
import * as path from "path";
4+
import { platform } from "os";
45

56
import {
67
ExtensionContext,
@@ -36,7 +37,7 @@ import { ExecutableResolver } from "./settings/ExecutableResolver";
3637
/** VSCode clients for talking to each language server, keyed by workspace folder URI */
3738
const servers = new Map<string, LanguageClient>();
3839

39-
const logger = new Logger("Apama Extension Client");
40+
export const logger = new Logger("Apama Extension Client");
4041

4142
/** The ExtensionContext.globalState */
4243
let globalState: Memento;
@@ -190,6 +191,7 @@ async function startLanguageServers(
190191
transport: TransportKind.stdio, // passes the "--stdio" option implicitly
191192
command: eplBuddyCommand.command,
192193
args: [...eplBuddyCommand.args, "-s"], // "-s" is just for backwards compat with pre-10.15.6.2 eplbuddy
194+
options: {shell: platform() === "win32"}
193195
};
194196

195197
const initializationOptions = {
@@ -218,15 +220,19 @@ async function startLanguageServers(
218220
workspaceFolder: folder,
219221
};
220222

221-
// TODO: we should really reload this if the APAMA_HOME config changes
222223
const languageClient = new LanguageClient(
223224
"apamaLanguageClient",
224225
`Apama Lang Server [${folder.name}]`,
225226
serverOptions,
226227
clientOptions,
227228
);
228229
servers.set(folder.uri.toString(), languageClient);
229-
await languageClient.start();
230+
try {
231+
await languageClient.start();
232+
} catch (e) {
233+
logger.error(`Failed to start language server using "${serverOptions.command}" with args [${serverOptions.args}] : ${e}`);
234+
throw e;
235+
}
230236
serverVersion = languageClient.initializeResult?.serverInfo?.version;
231237
}
232238

0 commit comments

Comments
 (0)