Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error: spawn php ENOENT on Macos #705

Closed
kaysonwu opened this issue Jan 4, 2022 · 10 comments
Closed

Error: spawn php ENOENT on Macos #705

kaysonwu opened this issue Jan 4, 2022 · 10 comments
Assignees

Comments

@kaysonwu
Copy link
Contributor

kaysonwu commented Jan 4, 2022

PHP version: v7.2.34
Xdebug version: v3.0.4
VS Code extension version: v1.22.0

Your launch.json:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 0,
            "log": true,
            "runtimeArgs": [
                "-dxdebug.start_with_request=yes"
            ],
            "env": {
                "XDEBUG_MODE": "debug,develop",
                "XDEBUG_CONFIG": "client_port=${port}"
            }
        },
    ]
}

Xdebug php.ini config:

zend_extension="xdebug.so"

[xdebug]
xdebug.mode = "coverage"

Xdebug logfile (from setting xdebug.log in php.ini):
VS Code extension logfile (from setting "log": true in launch.json):

<- launchResponse
Response {
  seq: 0,
  type: 'response',
  request_seq: 2,
  command: 'launch',
  success: true
}
<- initializedEvent
InitializedEvent { seq: 0, type: 'event', event: 'initialized' }
<- outputEvent
OutputEvent {
  seq: 0,
  type: 'event',
  event: 'output',
  body: {
    category: 'console',
    output: 'Error: spawn php ENOENT\n' +
      '    at Process.ChildProcess._handle.onexit (internal/child_process.js:269:19)\n' +
      '    at onErrorNT (internal/child_process.js:465:16)\n' +
      '    at processTicksAndRejections (internal/process/task_queues.js:80:21) {\n' +
      '  errno: -2,\n' +
      "  code: 'ENOENT',\n" +
      "  syscall: 'spawn php',\n" +
      "  path: 'php',\n" +
      '  spawnargs: [\n' +
      "    '-dxdebug.start_with_request=yes',\n" +
      "    '/private/var/www/jinmao/jinmao-backend-store/a.php'\n" +
      '  ]\n' +
      '}\n'
  }
}
Error: spawn php ENOENT
    at Process.ChildProcess._handle.onexit (internal/child_process.js:269:19)
    at onErrorNT (internal/child_process.js:465:16)
    at processTicksAndRejections (internal/process/task_queues.js:80:21) {
  errno: -2,
  code: 'ENOENT',
  syscall: 'spawn php',
  path: 'php',
  spawnargs: [
    '-dxdebug.start_with_request=yes',
    '/private/var/www/jinmao/jinmao-backend-store/a.php'
  ]
}
-> setBreakpointsRequest
{
  command: 'setBreakpoints',
  arguments: {
    source: {
      name: 'a.php',
      path: '/private/var/www/jinmao/jinmao-backend-store/a.php'
    },
    lines: [ 4 ],
    breakpoints: [ { line: 4 } ],
    sourceModified: false
  },
  type: 'request',
  seq: 3
}
<- setBreakpointsResponse
Response {
  seq: 0,
  type: 'response',
  request_seq: 3,
  command: 'setBreakpoints',
  success: true,
  body: {
    breakpoints: [
      {
        verified: true,
        line: 4,
        source: {
          name: 'a.php',
          path: '/private/var/www/jinmao/jinmao-backend-store/a.php'
        },
        id: 1
      }
    ]
  }
}
-> setFunctionBreakpointsRequest
{
  command: 'setFunctionBreakpoints',
  arguments: { breakpoints: [] },
  type: 'request',
  seq: 4
}
<- setFunctionBreakpointsResponse
Response {
  seq: 0,
  type: 'response',
  request_seq: 4,
  command: 'setFunctionBreakpoints',
  success: true,
  body: { breakpoints: [] }
}
-> setExceptionBreakpointsRequest
{
  command: 'setExceptionBreakpoints',
  arguments: { filters: [] },
  type: 'request',
  seq: 5
}
<- setExceptionBreakpointsResponse
Response {
  seq: 0,
  type: 'response',
  request_seq: 5,
  command: 'setExceptionBreakpoints',
  success: true,
  body: { breakpoints: [] }
}
-> configurationDoneRequest
{ command: 'configurationDone', type: 'request', seq: 6 }
<- configurationDoneResponse
Response {
  seq: 0,
  type: 'response',
  request_seq: 6,
  command: 'configurationDone',
  success: true
}
-> threadsRequest
{ command: 'threads', type: 'request', seq: 7 }
<- threadsResponse
Response {
  seq: 0,
  type: 'response',
  request_seq: 7,
  command: 'threads',
  success: true,
  body: { threads: [] }
}

Code snippet to reproduce:

<?php

$a = 1;

echo $a;
@kaysonwu
Copy link
Contributor Author

kaysonwu commented Jan 4, 2022

I use PHP installed by homebrew. It seems that the Xdebug extension can't find the php command, but I can use it normally on the terminal.

Now I have to explicitly specify the PHP path to run the extension properly. The configuration is as follows:

 {
    ...,
   "runtimeExecutable": "/opt/homebrew/bin/php"
}

@kaysonwu
Copy link
Contributor Author

kaysonwu commented Jan 4, 2022

I'm sure the problem occurs at L261, which discards process.env, which will cause the environment variable of the system to be invalid.

kaysonwu added a commit to kaysonwu/vscode-php-debug that referenced this issue Jan 4, 2022
@zobo
Copy link
Contributor

zobo commented Jan 4, 2022

Hi! Thanks for a comprehensive bug report!

Although I know that currently setting env in launch.json actually overwrites and not appends the process.env and this probably should be changed, this should however not affect the ability of the extension to find php in the PATH.
I am speculation your VS Code already does no have access to brew php.

I'm currently a bit limited on my abilities to test things on MacOS, so can you please try the following for me:

  1. Open the terminal inside VS Code and see if php -v works
  2. Remove the env from Launch currently open script in launch.json - debugging might not work, but the process should start and output should be seen in Debug Console
  3. If both of those work, put the env back how it was in launch.json and try to change the code of the extension. The installation should be somewhere under your home directory, search for a directory ...extensions/felixfbecker.php-debug-.... There is the out/phpDebug.js and try the change...

If the code change isn't what solves the problem, and you can't get VS Code to find php in your path you can also use the php.debug.executablePath setting and it will be used if no runtimeExecutable is set in launch.json.

@kaysonwu
Copy link
Contributor Author

kaysonwu commented Jan 4, 2022

ok, the following is the default configuration:

xdebug-launch-test-for-vscode

Next, I'll remove the env from Launch currently open script in launch.json

xdebug-launch-test-for-vscode-02

The debugging process starts normally. Finally, I'll update the extension code and undo the env configuration

xdebug-launch-test-for-vscode-03

By merging environment variables, the extension can find PHP command.

@zobo
Copy link
Contributor

zobo commented Jan 4, 2022

Thank you for testing this... I'll do the same tests on my end to confirm linux and windows and will prepare a release. It might be a few days before I can push an release out though.

@zobo zobo self-assigned this Jan 4, 2022
@zobo
Copy link
Contributor

zobo commented Jan 4, 2022

Hi. I went and tested this on all 3 systems. I occurred to me that what you are describing here doesn't normally happen. I went and looked at the documentation again:
https://nodejs.org/api/child_process.html

The command lookup is performed using the options.env.PATH environment variable if it is in the options object. Otherwise, process.env.PATH is used.

So what I am suspecting is happening to you is that the php command being executed is some sort of proxy executable that uses the path to find the right one...

But again, as said before, the behavior and the patch should be merged and released.

@kaysonwu
Copy link
Contributor Author

kaysonwu commented Jan 5, 2022

Well, I have looked through the child_process history documentation and have the following hints since v12.16.3

The command lookup will be performed using options.env.PATH environment variable if passed in options object, otherwise process.env.PATH will be used.

Then, after v14.13.1, it was changed to the following prompt:

The command lookup is performed using the options.env.PATH environment variable if it is in the options object. Otherwise, process.env.PATH is used.

But my node version is v14.17.4, Then, I checked the update log and found that there were no code commits other than document updates.

@kaysonwu
Copy link
Contributor Author

kaysonwu commented Jan 5, 2022

const { spawn } = require("child_process");

const command = "php";
const args = ['-v'];
const env = { };

console.log("With env: \n");
const stream = spawn(command, args, { env });

stream.on('error', function () {

});

console.log("\nWithout env:\n");
spawn(command, args, { });

uh. I run the above code with NODE_DEBUG and get the following information:

$ NODE_DEBUG=child_process node hello.js
With env: 

CHILD_PROCESS 40651: spawn {
  env: {},
  args: [ 'php', '-v' ],
  detached: false,
  envPairs: [],
  file: 'php',
  windowsHide: false,
  windowsVerbatimArguments: false
}

Without env:

CHILD_PROCESS 40651: spawn {
  args: [ 'php', '-v' ],
  detached: false,
  envPairs: [
    'USER=kayson',
    '__CFBundleIdentifier=com.microsoft.VSCode',
    'COMMAND_MODE=unix2003',
    'LOGNAME=kayson',
    'PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/composer/bin:/usr/local/composer/vendor/bin:/usr/local/mysql/bin:/Library/Apple/usr/bin:/usr/local/node/bin:/opt/homebrew/bin:/opt/homebrew/sbin',
    'SHELL=/bin/zsh',
    'TMPDIR=/var/folders/tn/_cb_j1ss0570vpbntrd714ww0000gn/T/',
    'XPC_SERVICE_NAME=0',
    'XPC_FLAGS=0x0',
    'ORIGINAL_XDG_CURRENT_DESKTOP=undefined',
    'SHLVL=1',
    'PWD=/private/var/www/jinmao/jinmao-backend-store',
    'OLDPWD=/private/var/www/jinmao/jinmao-backend-store',
    'HOMEBREW_PREFIX=/opt/homebrew',
    'HOMEBREW_CELLAR=/opt/homebrew/Cellar',
    'HOMEBREW_REPOSITORY=/opt/homebrew',
    'HOMEBREW_SHELLENV_PREFIX=/opt/homebrew',
    'MANPATH=/usr/share/man:/usr/local/share/man:/opt/homebrew/share/man',
    'INFOPATH=/opt/homebrew/share/info:',
    'COMPOSER_HOME=/usr/local/composer',
    'TERM_PROGRAM=vscode',
    'TERM_PROGRAM_VERSION=1.63.2',
    'LANG=zh_CN.UTF-8',
    'COLORTERM=truecolor',
    'TERM=xterm-256color',
    'NODE_DEBUG=child_process',
    '_=/usr/local/node/bin/node'
  ],
  file: 'php',
  windowsHide: false,
  windowsVerbatimArguments: false
}

It turns out that Nodejs does not append PATH to env, and in the source code, I did not find that nodejs will automatically set the PATH.

@zobo
Copy link
Contributor

zobo commented Jan 6, 2022

The documentation change seems to be only language ("will be" to "is").

I went and researched a bit the node code, trying to confirm what the documentation states.

If options.env.PATH is set, use that, if not, use process.env.PATH.

However I was not able to find this logic and ended up down in https://github.com/nodejs/node/blob/c61870c376e2f5b0dbaa939972c46745e21cdbdd/deps/uv/src/unix/process.c#L337

On Linux and OSX it uses the execvpe/execvp system calls. https://linux.die.net/man/3/execvp and https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/execvp.3.html

What the documentation states here is:

The functions execlp(), execvp(), and execvP() will duplicate the actions
of the shell in searching for an executable file if the specified file
name does not contain a slash ''/'' character. For execlp() and
execvp(), search path is the path specified in the environment by
''PATH'' variable. If this variable isn't specified, the default path is
set according to the _PATH_DEFPATH definition in <paths.h>, which is set
to ''/usr/bin:/bin''.

So not process.env.PATH but rather system default /usr/bin:/bin.

Since your PHP is in /opt/homebrew/bin/php it can't be found, where as in my case it's always in /usr/bin/php so it did work...
If you don't pass the env object to spawn the process.env is used that contains the inherited PATH...

Let me just reiterate, the fix you proposed is still valid and will be applied as soon as possible.

Thanks!

PS:
The logic on windows is different and there, I think, it actually works as described in the docs: https://github.com/nodejs/node/blob/c61870c376e2f5b0dbaa939972c46745e21cdbdd/deps/uv/src/win/process.c#L1017

@zobo
Copy link
Contributor

zobo commented Jan 6, 2022

I proposed a doc change nodejs/node#41418 and we'll see if there was something I misunderstood.

zobo pushed a commit to kaysonwu/vscode-php-debug that referenced this issue Jan 16, 2022
zobo pushed a commit that referenced this issue Jan 21, 2022
* fix: Merge environment variables for #705
zobo pushed a commit that referenced this issue Jan 22, 2022
* fix: Merge environment variables for #705
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants