Skip to content

Commit 4121e72

Browse files
committed
TS extractor: Allow the Node.js runtime to be configured via environment variables.
`SEMMLE_TYPESCRIPT_NODE_RUNTIME` can be used to provide the path to the Node.js runtime executable. If this is omitted, the extractor defaults to the current behaviour of looking for `node` on the PATH. `SEMMLE_TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS` can be used to provide additional arguments to the Node.js runtime. These are passed first, before the arguments supplied by the extractor. These changes are designed to allow TypeScript extraction in controlled customer environments where we cannot control the PATH, or must use custom Node.js executables with certain arguments set.
1 parent 4e10e28 commit 4121e72

File tree

1 file changed

+72
-7
lines changed

1 file changed

+72
-7
lines changed

javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,21 @@
3333
import java.io.OutputStreamWriter;
3434
import java.lang.ProcessBuilder.Redirect;
3535
import java.util.ArrayList;
36+
import java.util.Arrays;
37+
import java.util.Collections;
3638
import java.util.List;
3739

3840
/**
3941
* The Java half of our wrapper for invoking the TypeScript parser.
4042
*
4143
* <p>The Node.js half of the wrapper is expected to live at {@code
4244
* $SEMMLE_DIST/tools/typescript-parser-wrapper/main.js}; non-standard locations can be configured
43-
* using the property {@link #PARSER_WRAPPER_PATH_ENV_VAR}.
45+
* using the property {@value #PARSER_WRAPPER_PATH_ENV_VAR}.
46+
*
47+
* <p>The script launches the Node.js wrapper in the Node.js runtime, looking for {@code node}
48+
* on the {@code PATH} by default. Non-standard locations can be configured using the property
49+
* {@value #TYPESCRIPT_NODE_RUNTIME_VAR}, and additional arguments can be configured using the
50+
* property {@value #TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS_VAR}.
4451
*
4552
* <p>The script is started upon parsing the first TypeScript file and then is kept running in the
4653
* background, passing it requests for parsing files and getting JSON-encoded ASTs as responses.
@@ -52,6 +59,18 @@ public class TypeScriptParser {
5259
*/
5360
public static final String PARSER_WRAPPER_PATH_ENV_VAR = "SEMMLE_TYPESCRIPT_PARSER_WRAPPER";
5461

62+
/**
63+
* An environment variable that can be set to indicate the ___location of the Node.js runtime,
64+
* as an alternative to adding Node to the PATH.
65+
*/
66+
public static final String TYPESCRIPT_NODE_RUNTIME_VAR = "SEMMLE_TYPESCRIPT_NODE_RUNTIME";
67+
68+
/**
69+
* An environment variable that can be set to provide additional arguments to the Node.js runtime
70+
* each time it is invoked. Arguments should be separated by spaces.
71+
*/
72+
public static final String TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS_VAR = "SEMMLE_TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS";
73+
5574
/**
5675
* An environment variable that can be set to specify a timeout to use when verifying the
5776
* TypeScript installation, in milliseconds. Default is 10000.
@@ -91,6 +110,15 @@ public class TypeScriptParser {
91110

92111
private String nodeJsVersionString;
93112

113+
/** Command to launch the Node.js runtime. Initialised by {@link #verifyNodeInstallation}. */
114+
private String nodeJsRuntime;
115+
116+
/**
117+
* Arguments to pass to the Node.js runtime each time it is invoked.
118+
* Initialised by {@link #verifyNodeInstallation}.
119+
*/
120+
private List<String> nodeJsRuntimeExtraArgs = Collections.emptyList();
121+
94122
/** If non-zero, we use this instead of relying on the corresponding environment variable. */
95123
private int typescriptRam = 0;
96124

@@ -102,12 +130,16 @@ public void setTypescriptRam(int megabytes) {
102130
/**
103131
* Verifies that Node.js and TypeScript are installed and throws an exception otherwise.
104132
*
105-
* @param verbose if true, log the version strings and NODE_PATH.
133+
* @param verbose if true, log the Node.js executable path, version strings, and any additional arguments.
106134
*/
107135
public void verifyInstallation(boolean verbose) {
108136
verifyNodeInstallation();
109137
if (verbose) {
138+
System.out.println("Found Node.js at: " + nodeJsRuntime);
110139
System.out.println("Found Node.js version: " + nodeJsVersionString);
140+
if (!nodeJsRuntimeExtraArgs.isEmpty()) {
141+
System.out.println("Additional arguments for Node.js: " + nodeJsRuntimeExtraArgs);
142+
}
111143
}
112144
}
113145

@@ -117,7 +149,24 @@ public String verifyNodeInstallation() {
117149

118150
ByteArrayOutputStream out = new ByteArrayOutputStream();
119151
ByteArrayOutputStream err = new ByteArrayOutputStream();
120-
Builder b = new Builder(out, err, getParserWrapper().getParentFile(), "node", "--version");
152+
153+
// Determine where to find the Node.js runtime.
154+
String explicitNodeJsRuntime = Env.systemEnv().get(TYPESCRIPT_NODE_RUNTIME_VAR);
155+
if (explicitNodeJsRuntime != null) {
156+
// Use the specified Node.js executable.
157+
nodeJsRuntime = explicitNodeJsRuntime;
158+
} else {
159+
// Look for `node` on the PATH.
160+
nodeJsRuntime = "node";
161+
}
162+
163+
// Determine any additional arguments to be passed to Node.js each time it's called.
164+
String extraArgs = Env.systemEnv().get(TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS_VAR);
165+
if (extraArgs != null) {
166+
nodeJsRuntimeExtraArgs = Arrays.asList(extraArgs.split("\\s+"));
167+
}
168+
169+
Builder b = new Builder(getNodeJsRuntimeInvocation("--version"), out, err, getParserWrapper().getParentFile());
121170
b.expectFailure(); // We want to do our own logging in case of an error.
122171

123172
int timeout = Env.systemEnv().getInt(TYPESCRIPT_TIMEOUT_VAR, 10000);
@@ -144,6 +193,21 @@ public String verifyNodeInstallation() {
144193
}
145194
}
146195

196+
/**
197+
* Gets a command line to invoke the Node.js runtime.
198+
* Any arguments in {@link TypeScriptParser#nodeJsRuntimeExtraArgs}
199+
* are passed first, followed by those in {@code args}.
200+
*/
201+
private List<String> getNodeJsRuntimeInvocation(String ...args) {
202+
List<String> result = new ArrayList<>();
203+
result.add(nodeJsRuntime);
204+
result.addAll(nodeJsRuntimeExtraArgs);
205+
for(String arg : args) {
206+
result.add(arg);
207+
}
208+
return result;
209+
}
210+
147211
private static int getMegabyteCountFromPrefixedEnv(String suffix, int defaultValue) {
148212
String envVar = "SEMMLE_" + suffix;
149213
String value = Env.systemEnv().get(envVar);
@@ -172,10 +236,11 @@ private void setupParserWrapper() {
172236
int reserveMemoryMb = getMegabyteCountFromPrefixedEnv(TYPESCRIPT_RAM_RESERVE_SUFFIX, 400);
173237

174238
File parserWrapper = getParserWrapper();
175-
List<String> cmd = new ArrayList<>();
176-
cmd.add("node");
177-
cmd.add("--max_old_space_size=" + (mainMemoryMb + reserveMemoryMb));
178-
cmd.add(parserWrapper.getAbsolutePath());
239+
240+
List<String> cmd = getNodeJsRuntimeInvocation(
241+
"--max_old_space_size=" + (mainMemoryMb + reserveMemoryMb),
242+
parserWrapper.getAbsolutePath()
243+
);
179244
ProcessBuilder pb = new ProcessBuilder(cmd);
180245
parserWrapperCommand = StringUtil.glue(" ", cmd);
181246
pb.environment().put("SEMMLE_TYPESCRIPT_MEMORY_THRESHOLD", "" + mainMemoryMb);

0 commit comments

Comments
 (0)