Page MenuHomePhorge

`arc lint` can't run `jshint` on Windows
Closed, ResolvedPublic

Description

arc lint can't run jshint on Windows. Here's how it fails when trying to lint rP:

C:\dev\phorge\phorge>..\arcanist\bin\arc lint
 Exception
Command failed with error #1!
COMMAND
jshint --version

STDOUT
(empty)

STDERR
Call to "proc_open()" to open a subprocess failed: proc_open(): CreateProcess failed, error code - 2
(Run with `--trace` for a full exception trace.)

I do have jshint installed in the usual manner:

C:\dev\phorge\phorge>where jshint
C:\Users\a\AppData\Roaming\npm\jshint
C:\Users\a\AppData\Roaming\npm\jshint.cmd

(jshint is a Bash script for Linux-like environments; jshint.cmd is the batch script we want to run)

And I can run it:

C:\dev\phorge\phorge>jshint --version
jshint v2.13.6

Despite that, the error code 2 indicates ERROR_FILE_NOT_FOUND (https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-).


Through some trial and error, and some Windows documentation spelunking, I have guessed that it's looking for a jshint.exe, which indeed does not exist, and that this is ultimately due to using the 'bypass_shell' option for the PHP function proc_open. The following script demonstrates this:

<?php

function test($command, $opts = []) {
	echo "$command " . json_encode($opts) . "\n";

	$desc = [
		// silence STDERR output
		2 => [ "pipe", "w" ],
	];
	$pipes = [];

	$process = @proc_open($command, $desc, $pipes, null, null, $opts);
	if (is_resource($process)) {
		var_dump(proc_close($process));
	} else {
		echo "FAIL\n";
	}
}

test('jshint --version');
test('jshint --version', ['bypass_shell' => true]);
test('jshint.cmd --version');
test('jshint.cmd --version', ['bypass_shell' => true]);
test('jshint.exe --version');
test('jshint.exe --version', ['bypass_shell' => true]);

Output:

jshint --version []
int(0)
jshint --version {"bypass_shell":true}
FAIL
jshint.cmd --version []
int(0)
jshint.cmd --version {"bypass_shell":true}
int(0)
jshint.exe --version []
int(1)
jshint.exe --version {"bypass_shell":true}
FAIL

As you can see, with bypass_shell, jshint.exe and jshint fail in the same way, while jshint.cmd works.


There seem to be good reasons for using bypass_shell related to problems with escaping command parameters on Windows, so we can't just remove that. Instead, I think we have to find the right executable ourselves, and pass the full path to proc_open. Fortunately, Arcanist already includes an utility for this: Filesystem::resolveBinary. Unfortunately, it does not work correctly – it returns the first result from where, which may not be an executable (and is not, in our case). Fortunately, that's easy enough to fix too.

Revisions and Commits