前言
大家好,我是祯民。今天我想给大家分享一个 vscode 插件开发中比较难排查的偶现问题,报错信息看上去很像一个普通的本地环境问题,但实际并不简单。全文会被分为背景 、排查 和解决三个模块,希望可以给遇到类似问题的同学一点启发,下面我们直接进入正题。
背景
前段时间我写了一个 duplication-lint 的 vscode 插件,可以做到代码重复场景防劣化的 lint,当编码阶段有工作空间维度的重复代码时,会警告报错,并提供一些修复方式,比如 AI 修复等,类似下面的效果。
在给业务方同学去使用的时候,有一些同学会偶现下面的报错。
因为依赖的一些特殊历史原因,部分逻辑非常规的调包,而是插件的逻辑中直接使用 node 去执行一个可执行文件,如果没有的话,会走 npm 安装。在逻辑中的体现为,使用 exec 去执行终端命令调起。
当然这个问题我早期并没有放在心上,一个是这个报错信息太明显了,就是内置环境找不到全局的命令(比如 npm 或者 node),另一方面又是小概率事件,很少的一部分用户复现,而且还不是稳定复现。我当时感觉这个问题大概率就是用户本地环境没装好,或者基础命令并不是全局安装。
直到年后回来,我重启了一下电脑,发现我也复现了这个问题(中途我没有进行任何操作)。这次是 node not found 的报错,但估计也是 exec 位置处引起的。
其实开发过程中我也有复现过类似问题,但通常是一闪而过,或者 reload window 就可以解决,vscode 插件运行时是魔改后的 node 运行时,现在回顾起来这样未排查就武断凭 node 开发经验下结果的确是太自负了。所以我决定这次彻底搞清楚到底是什么原因导致的~
问题排查
猜测1:node 可执行文件是否存在
第一个猜测是针对本地环境的,也是对于这个报错最直接的常见问题。当然在终端试验过后,本地终端环境都是正常的。
猜测2:exec 命令是否可执行
既然本地环境没问题,难道是我的 exec 命令本身就跑不起来?但这个可能性很小,毕竟报错信息是 node 这种全局命令找不到,而不是脚本的报错。但出于死马当活马医的心态,我还是决定尝试一下排查。
当然结果也是没问题的,而且快得飞起...
猜测3:cwd 是否异常
排查了 node 本地环境和 exec 的可能后,我的第一反应是,也许是在某些场景下,逻辑中注入了异常的 cwd,导致执行目录出来了问题。
验证后仍然正常...
猜测4:仅限 node ?还是所有的全局命令失效
到这里其实我的心态已经有点崩了,我在想既然直接执行 exec 跑 node 找不到命令,那么就说明至少在当时的执行环境下 node 不是一个全局命令。既然如此,我也许可以用 which node
拿到 node 可执行文件所在的文件路径,直接执行可执行文件。
这个想法看上去很靠谱,但依然失败了
看来不仅是 node 挂了,所有的全局命令全部都不行了。不过 which 这个命令挂了倒的确给了我一个启发,which
命令是用来查找并显示在 $PATH
环境变量所定义的目录列表中是否存在指定命令的可执行文件。 难道当前执行环境 $PATH 本身就是错的?
猜测5:vscode内置shell与系统终端不匹配?
在讲这个猜测前,这里我们先简单温习一下$PATH
是什么?
$PATH
是一个在 Unix-like 操作系统(包括 Linux、macOS 等)和 Windows 操作系统中的环境变量。它是一个字符串,包含了多个由冒号(Unix-like 系统)或分号(Windows 系统)分隔的目录路径列表。 通过配置$PATH
,用户可以轻松地执行位于系统不同目录下的命令,而无需每次都输入完整的路径。这对于频繁使用的命令或自定义工具尤其有用。
简单来说,就是存放全局命令的根目录,全局命令的可执行文件路径需要是 <math xmlns="http://www.w3.org/1998/Math/MathML"> P A T H 的子集。 ' PATH 的子集。` </math>PATH的子集。'PATH` 不同,那就不得不怀疑一下,vscode 的内置 shell 是不是设置得不对了。
我系统中默认使用的终端就是 zsh,看上去仍然是正确的~
猜测6:vscode 内置终端 和 vscode 插件运行时 $PATH 不同?
到这里只剩下最后一个原因了,那就是 vscode 插件运行时的 $PATH
和 vscode 内置终端的 $PATH
并不相同。vscode 插件运行时的 $PATH
继承于启动 vscode 的父进程环境,当然我们设置过默认终端,所以理论上说这两者应该是相同的。
但这里的确也没有其他的可能性了,我们先按这个思路来排查,毕竟也是一个偶现的小概率事件。
我们先看 vscode 内置终端中 $PATH
是什么。
这个是符合预期的,也就是它会先在 /Users/bytedance/.local/bin 下找所有的可执行文件,找不到再依次降级,我所有的个人文件都在这里面,所以是可以找到的。
这个通过前面猜测推断,也肯定是正常的,不然不可能都能正常执行。我们接着来看vscode 插件运行时的 $PATH,这个我们需要使用脚本输出,因为它是独立的一个进程。
的确不一样,vscode 插件运行时中的 $PATH
中并没有包含任何全局路径。所以我们也就定位到了原因,vscode 插件运行时在某些情况下并没有顺利继承默认终端中的环境变量 $PATH
,而是使用了类Unix操作系统(如Linux、macOS)中的默认命令行路径。(不排除是 vscode 中配置的兜底,在某些场景下继承默认终端变量异常时,就使用系统默认 $PATH
)
解决方案
既然定位到了原因,我们就可以开始考虑怎么解决了。我们只需要使得 vscode 插件运行时能顺利继承终端中的环境变量,这个问题就可以迎刃而解了。
方案1:code 启动,自动继承终端的环境变量
第一个方案是,使用 code 命令启动 vscode,这时候 vscode 将会自动继承启动终端的环境变量。还不知道什么是 code 命令的同学可以网上搜下,简单来说就是 vscode 提供的一个全局命令,可以通过 code 直接打开 vscode。
方案2:exec 取 terminal.integrated.env 作为优先级更高的兜底
terminal.integrated.env
是VSCode中用来配置集成终端环境变量的一个设置选项。这个设置项允许你为不同操作系统(环境)自定义集成终端的环境变量。
macOS 的配置示例如下
json
"terminal.integrated.env.osx": {
"PATH": "${env:PATH}:/Users/user/custom/path"
}
我们可以配置对应的环境变量到配置项中,然后在执行 child_process 模块的时候,通过 vscode API 取对应的环境变量配置项作为 env 参数的兜底,从而达到在运行时封闭进程内同步外部进程环境变量的效果。
方案3:换依赖调用,而非 exec
这个是我最后用的方案,不再使用 exec,而通过依赖包调用。当然这个不算从根源解决,只是为了让整个流程更开箱即用,逃避了问题而已,并不是所有的场景都有比较合适的方式替代的。
方案4:插件开发阶段,可配置 launch.json path 变量手动注入环境变量
上面的三种方案都是针对用户使用阶段,出现类似问题的解决方案。如果是在插件的开发阶段,我们也可以通过在 launch.json 中配置 path 变量手动注入环境变量。这样通过该 launch.json 启动的环境将强制被配置的环境变量覆盖。
json
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
],
"path": "/Users/bytedance/.local/bin"
}
]
}
小结
到这里本文的课程就结束了,一个看似简单的偶现环境问题,实际也暗藏着不少知识点。相信经过这次学习,对大家都有或多或少的启发,在以后遇到类似全局命令失效的问题时,大家也可以从终端环境、cwd、$PATH
展开,针对问题的实际情况深入排查。
感谢大家的时间,有问题欢迎在评论区交流讨论。