Extension 多版本开发工具
Extension 是一个基于 Inno Setup 的 Windows 安装器,把 Java / Maven / Node / PHP / Go / Python / Gradle 的多版本开发工具链装进同一个目录,环境变量只往 PATH 里加一条,各版本共存、随时切换。
安装包本体只有 2.8 MB——所有工具都在安装时从镜像下载,所以安装包不会因为多支持几个版本而变大。
安装包 2.8 MB → 运行时按清单下载选中的包 → 解压就位 → 写环境变量 + 生成转发脚本项目地址:github.com/Freewind72/extension,采用 GPL-3.0 许可。
一、它解决什么问题
开发机上的工具链通常是这样攒起来的:JDK 一个安装器、Node 一个安装器、PHP 一个、Go 一个、Python 再来一个。每个都会往系统里写自己的环境变量,每个都会往 PATH 里塞自己的 bin。
用久了就会遇到三件事:
- PATH 越来越长:装了三套工具链的机器,
PATH里可能挂着七八条 bin 目录,排查"为什么跑的是那个版本"要翻半天。 - 换版本要重装:想从 JDK 17 换到 25,常规做法是卸载再装,或者手工改
JAVA_HOME。 - 多版本共存麻烦:项目 A 要 Node 18、项目 B 要 Node 24,要么装 nvm 之类的版本管理器,要么两个都装着然后手动切。
Extension 的做法是把这些工具统一装进一个目录、统一用一层转发脚本暴露命令,从根上避开上面三件事。
二、核心设计
- 安装包极小(2.8 MB):载荷全部在线下载,加版本不用重新打包。
- 清单驱动:版本列表来自一份纯文本清单,增删版本不用重编译安装包。
- 三态选择:勾选框决定"装不装",下拉框决定"装哪个版本 / 要不要写环境变量",两者独立。
- PATH 只加一条:所有命令通过自动生成的
.cmd转发脚本暴露,PATH里永远只有{app}\bin一条。 - 主备地址 + 校验:每个包支持
url/url2/url3三级自动切换,配了sha256就逐字节校验。 - 自动收尾:PHP 的
php.ini与扩展、CA 根证书;Python 的site与pip,全部装完自动配好。 - 卸载干净:只删自己写过的环境变量和
PATH条目,不动系统里其它配置。
三、安装后的目录结构
D:\Extension\
├── bin\ ← PATH 里唯一的一条,全是自动生成的 .cmd 转发脚本
│ ├── java.cmd javac.cmd jshell.cmd ...
│ ├── mvn.cmd node.cmd npm.cmd
│ ├── php.cmd php-cgi.cmd
│ └── pip.cmd python.cmd
├── java\jdk25\
├── maven\maven-3.9.16\
├── node\node24\
├── php\PHP85\
├── go\Go1.27.1\
├── python\Python3.14\
└── gradle\Gradle9.7.1\每个工具一个目录,互不干扰;想换版本就是换一个子目录。
四、环境变量策略
只写这些,且只写指向本安装目录的值:
| 工具 | 变量 |
|---|---|
| Java | JAVA_HOME |
| Maven | MAVEN_HOME |
| Node | NODE_HOME |
| PHP | PHP_HOME |
| Go | GOROOT |
| Python | PYTHON_HOME |
| Gradle | GRADLE_HOME |
PATH 里只加一条 {app}\bin,位置在最前面。
几个刻意的决定:
- 不设
M2_HOME:Maven 3.9 用MAVEN_HOME;M2_HOME只在卸载时的清理清单里,用来扫掉历史遗留。 - 不设
PYTHONHOME:它会导致 Python 忽略pyvenv.cfg,虚拟环境直接失效——这是个经典坑。用PYTHON_HOME代替。 - 不设
GOPATH:交给 Go 自己的默认值(%USERPROFILE%\go)。
卸载时会检查并清理的历史变量:JAVA_HOME、MAVEN_HOME、M2_HOME、NODE_HOME、PHP_HOME、PHPRC、GOROOT、GOPATH、PYTHON_HOME、PYTHONHOME、GRADLE_HOME。
五、为什么用转发脚本,而不是符号链接
这一节是整个项目里最值得看的部分——因为三种链接方式全部失败:
| 方式 | 结果 |
|---|---|
| 符号链接(symlink) | java.exe 直接退出,退出码 -1073741515(缺少依赖 DLL) |
| 目录联接(junction) | 报 could not find java.dll |
| 硬链接(hardlink) | 同样失败 |
原因是 java.exe、php.exe 这类程序会去自己所在的目录找同级的 jvm.dll、php8.dll——链接过去之后,相对位置就变了。
所以最终方案是在 {app}\bin 里生成一层 .cmd 转发脚本:
@echo off
"%~dp0..\java\jdk25\bin\java.exe" %*另外有个细节:.cmd / .bat 形式的启动器(比如 Gradle 的 gradle.bat)会额外用 call 调用,否则拿不到正确的返回码。
六、清单格式
清单就是一份纯文本,文件名和后缀随意(安装器按内容解析,不看扩展名),只要能通过 HTTP 访问:
[meta]
default_Java = jdk25
default_Node = node24
[Java|jdk25]
url = https://mirror.example.com/openjdk/25.0.2/openjdk-25.0.2_windows-x64_bin.zip
url2 = https://mirror2.example.com/openjdk/25.0.2/openjdk-25.0.2_windows-x64_bin.zip
size = 221671696
sha256 = 74784a0c07258f32d36e9224dd79187c566d831c30d47dc06888d4212087331d
[Node|node24]
url = https://mirror.example.com/nodejs/v24.21.0/node-v24.21.0-win-x64.zip
size = 37618919
sha256 = 158f7685b44de51f6c0df1d153526cbcd3e1bc739a8dfc607721cef75de9e541| 字段 | 说明 |
|---|---|
节名 [工具|版本标签] | 工具名要和脚本里的工具定义对得上;版本标签就是下拉框里显示的文字。有几节就有几个可选版本。 |
url | 主地址,必填。 |
url2 / url3 | 备用地址,主地址失败后依次重试。 |
size | 包大小(字节),用于在选择页实时估算磁盘占用。 |
sha256 | 有就校验,不匹配视为下载失败并自动换下一个地址。 |
[meta] default_工具名 | 该工具默认选中的版本。 |
⚠️ url2 / url3 必须和 url 是同一份字节(同一厂商的不同镜像)。因为 sha256 是单值,换到字节不同的备用源会校验失败、看起来像 "下载失败"。
清单拉不到时,安装器会退回脚本里内置的那份兜底清单,所以清单服务器临时挂掉不会让安装器直接不可用。
七、三态选择
这是交互上的核心设计,两个维度完全独立:
- 勾选框:决定这个工具装不装。
- 下拉框:决定装哪个版本,以及要不要写环境变量。
所以 "装 Java 但不改环境变量" 和 "不装 Java" 是两件不同的事,都能表达。
八、工作原理
- 向导初始化:从清单地址拉取清单(拉不到就用内置兜底清单),按清单里的节生成每个工具的版本下拉框。
- 点"安装"后:只下载被选中的包到临时目录。每个包最多试三组地址,配了
sha256就校验,不匹配视为失败并自动换下一个地址。 - 解压就位:先解压到
{app}\<工具>\.in-<版本>,再改名成规范目录名(jdk25/node24…)。多套一层目录的压缩包会自动剥掉外层。 - 自动收尾:PHP 生成
php.ini并实测剔除加载不起来的扩展、下 CA 根证书;Python 打开site并引导安装pip。 - 写环境变量:写入各工具的
*_HOME,PATH里加入{app}\bin,并在{app}\bin里为每个命令生成转发脚本。
九、常见问题
装完为什么要新开终端?
环境变量是安装时写进注册表的,已经打开的终端不会自动刷新。新开一个终端,或者注销重登。
某个工具下载特别慢或者失败怎么办?
给清单里那一节加 url2 / url3(必须是同一份字节)。安装器只在失败时切换地址,慢不会触发切换。
想装两个 Java 版本怎么办?
清单里加两节([Java|jdk17]、[Java|jdk25]),下拉框里就能选。装完目录里 java\jdk17 和 java\jdk25 并存,JAVA_HOME 指向你选的那个。想换版本重跑安装器选另一个即可。
能装到别的盘吗?
能。改 DefaultDirName,或者运行时在 "选择目标位置" 那一页直接改。
十、许可与第三方组件
本项目基于 GNU General Public License v3.0 发布。
本项目不分发任何第三方工具,只是在安装时从各官方站点或镜像下载,因此每个工具仍然受其原有许可证约束,与本项目的 GPL-3.0 无关:
| 工具 | 许可证 |
|---|---|
| OpenJDK | GPL-2.0 with Classpath Exception |
| Apache Maven | Apache License 2.0 |
| Node.js | MIT |
| PHP | PHP License 3.01 |
| Go | BSD-3-Clause |
| Python | PSF License Agreement |
| Gradle | Apache License 2.0 |
清单里配哪些版本、从哪个镜像下载,由使用者自行决定。再分发这些二进制时请自行确认各自的许可条款(尤其是署名与商标要求)。
十一、仓库信息
- 仓库:github.com/Freewind72/extension
- 文件构成:
Extension.iss(Inno Setup 脚本主体)、README.md、LICENSE - 许可证:GPL-3.0
如果你也被 "装了三套工具链、PATH 里挂了一串 bin" 折腾过,这个安装器想解决的正是这件事。





