23
0
0

Extension 多版本开发工具

2026-09-21
2026-10-26
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\

每个工具一个目录,互不干扰;想换版本就是换一个子目录。

四、环境变量策略

只写这些,且只写指向本安装目录的值:

工具变量
JavaJAVA_HOME
MavenMAVEN_HOME
NodeNODE_HOME
PHPPHP_HOME
GoGOROOT
PythonPYTHON_HOME
GradleGRADLE_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" 是两件不同的事,都能表达。

八、工作原理

  1. 向导初始化:从清单地址拉取清单(拉不到就用内置兜底清单),按清单里的节生成每个工具的版本下拉框。
  2. 点"安装"后:只下载被选中的包到临时目录。每个包最多试三组地址,配了 sha256 就校验,不匹配视为失败并自动换下一个地址。
  3. 解压就位:先解压到 {app}\<工具>\.in-<版本>,再改名成规范目录名(jdk25 / node24…)。多套一层目录的压缩包会自动剥掉外层。
  4. 自动收尾:PHP 生成 php.ini 并实测剔除加载不起来的扩展、下 CA 根证书;Python 打开 site 并引导安装 pip。
  5. 写环境变量:写入各工具的 *_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 无关:

工具许可证
OpenJDKGPL-2.0 with Classpath Exception
Apache MavenApache License 2.0
Node.jsMIT
PHPPHP License 3.01
GoBSD-3-Clause
PythonPSF License Agreement
GradleApache License 2.0

清单里配哪些版本、从哪个镜像下载,由使用者自行决定。再分发这些二进制时请自行确认各自的许可条款(尤其是署名与商标要求)。

十一、仓库信息

如果你也被 "装了三套工具链、PATH 里挂了一串 bin" 折腾过,这个安装器想解决的正是这件事。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

Extension 多版本开发工具
/archives/extension-installer
作者
Zeph
发布于
2026-09-21
许可协议
CC BY-NC-SA 4.0

评论

欢迎来到我的博客!