3333import java .io .OutputStreamWriter ;
3434import java .lang .ProcessBuilder .Redirect ;
3535import java .util .ArrayList ;
36+ import java .util .Arrays ;
37+ import java .util .Collections ;
3638import 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