这些都是用来办理编程措辞多版本管理的工具,如果你是个程序员肯定认识或是用过几个,但是刚打仗编程的小白,就会有些挠头了。
啥是编程措辞版本管理工具?它们有什么用呢?
举个例子,用 Java 的开拓者可能会遇见的问题,公司的项目是万年不变 JDK 8,但个人项目用的是最新的 JDK 21。这种情形下,在一台电脑上开拓公司和个人项目的时候,就须要切换一下当前开拓环境对应的 JDK 版本,否则项目跑不起来。编程措辞版本管理工具便是用来切换/管理编程措辞不同版本的工具,比如 Java 措辞对应的工具是 jenv。

每一种编程措辞都有一个对应的版本管理工具,对付多措辞开拓者来说就须要安装、配置、学习各种版本管理工具,影象不同工具的利用命令,这和紧箍咒无异。那咋办啊?
莫慌,本日 HelloGitHub 带来的是一款跨平台版本、支持多措辞的版本管理工具——vfox,让你无忧应对多编程措辞、不同版本的开拓环境。该项目由国人(99 年的小伙)开拓,更贴合海内开拓者的利用习气。
GitHub 地址:github.com/version-fox/vfox
接下来,让我们一起走近 vfox 理解它的功能、上手利用、技能事理和强大的插件系统吧!
vfox 是一个类 nvm、fvm、sdkman、asdf 的版本管理工具,具有跨平台、通用、易拓展的特性:
大略:安装大略,一套命令管理所有措辞跨平台:支持 Windows、Linux、macOS人性化:换项目时自动切换到对应编程措辞、支持自动补全扩展性:随意马虎上手的插件系统,添加冷门的编程措辞浸染域:支持 Global、Project、Session 三种浸染域质疑声:同类型的项目挺多的啊,不能一个国人开拓、开源就来求 Star 吧?
下面,我们就来和在 GitHub 上有 20k Star 的同类型工具 asdf PK 一下,看看 vfox 是不是重复造轮子,到底能不能打!
这里紧张从操作系统兼容性、性能和插件换源三个方面进行比拟。
2.1 兼容性首先,asdf 是用 shell 脚本实现的工具,以是并不支持原生 Windows 环境。而 vfox 是用 Go + Lua 实现的,因此天生支持 Windows 和其他操作系统。
2.2 性能上图是对两个工具最核心的切换版本功能进行基准测试的结果,很随意马虎就能得出结论:vfox 比 asdf 快 5 倍。
技能解析:asdf 实行切换版本的速率之以是较慢,紧张是由于其垫片机制。大略来说,当你考试测验运行如 node 这样的命令时,asdf 会首先查找对应的垫片,然后根据 .tool-versions 文件或全局设置来确定利用哪个版本的 node 。这个查找和确定版本的过程会花费一定的韶光,从而影响了命令的实行速率。
比较之下,vfox 则采取了直接操作环境变量的办法来管理版本,它会直接设置和切换环境变量,从而避免了查找和确定版本的过程。因此,在实行速率上要比利用垫片机制的 asdf 快得多。
虽然 asdf 很强,但是它对 Windows 原生无能为力。虽然 vfox 很新,但在性能和跨平台方面做得更好。
2.3 插件换源大多数时候,我们会被网络问题而困扰,以是切换下载源的操作是必不可少的。
下面以切换 Node.js 源为例,比拟 asdf 和 vfox 在换源时的差异。
asdf 是通过 asdf-vm/asdf-nodejs 插件实现了对付 Node.js 的支持,但该插件是须要手动预定义一个环境变量来修正下载源,多措辞换源还须要设置多个不同的环境变量。
优点:可以灵巧切换任何镜像源缺陷:须要手动设置,操作不友好vfox 选择了另一种方法,即一个镜像源对应一个插件。
$ vfox add nodejs/nodejs # 利用官方下载源$ vfox add nodejs/npmmirror # 利用 npmmirror 镜像$ vfox add python/python # 官方下载源$ vfox add python/npmmirror
虽然这样会使仓库的插件变多,但利用起来降落了包袱,也没有乱七八糟的环境变量须要配置,对用户非常友好!
三、上手说了这么多,还没上手玩一下切实其实忍不了。
3.1. 安装Windows 用户只须要下载安装器进行安装即可,Linux 用户可以利用 APT 或 YUM 来快速安装,macOS 用户可以利用 Homebrew 安装。更详细的安装办法可查看文档
$ brew tap version-fox/tap$ brew install vfox
安装完成之后,须要将 vfox 挂载到你的 shell 中,从下面条款中选择一条适宜你 shell 的。
echo 'eval "$(vfox activate bash)"' >> ~/.bashrcecho 'eval "$(vfox activate zsh)"' >> ~/.zshrcecho 'vfox activate fish | source' >> ~/.config/fish/config.fish# 对付 Powershell 用户,将下面行添加到你的 $PROFILE 文件中Invoke-Expression "$(vfox activate pwsh)"
3.2 利用
安装好了,但你还做不了任何事情,由于 vfox 是利用插件作为扩展,按需安装。
不知道该当添加哪些插件,可以用 vfox available 命令查看所有可用插件
以是你还须要安装插件,以 Node.js 为例,为了得到更好的体验,我们添加 npmmirror 镜像源插件:vfox add nodejs/npmmirror。
在插件成功安装之后,你就可以玩起来了!
笔墨表达远不如图片来的更直不雅观,我们直接上效果图。
四、技能事理
vfox 支持 Global、Session、Project 三种浸染域,这三种浸染域能够知足我们日常开拓所需的场景。
那么你对它们的实现事理感兴趣吗?咱们废话不多说,直接看事理图!
vfox 是基于 shell 的 hook 机制实现的,hook 机制大略来说便是每当我们实行完命令之后,shell 都会调用一下你配置的钩子函数(hook),即 vfox env <shell-name> 命令,我们后面阐明这个命令是干什么的。
说回到浸染域上来,vofox 是通过 .tool-versions 文件来记录每个 SDK 对应的版本号信息。对付三种浸染域,会分别在不同的地方创建 .tool-versions 文件,用于记录浸染域内所须要的 SDK 版本信息。
Global -> $HOME/.version-fox/.tool-versionsProject -> 当前项目目录Session -> $HOME/.version-fox/tmp/<shell-pid>/.tool-versions代码如下:
func newSdkManagerWithSource(sources ...RecordSource) Manager { meta, err := newPathMeta() if err != nil { panic("Init path meta error") } var paths []string for _, source := range sources { // 根据不同的浸染域选择性加载不同位置的.tool-versions文件 switch source { case GlobalRecordSource: paths = append(paths, meta.ConfigPath) case ProjectRecordSource: // 当前目录 curDir, err := os.Getwd() if err != nil { panic("Get current dir error") } paths = append(paths, curDir) case SessionRecordSource: // Shell会话临时目录 paths = append(paths, meta.CurTmpPath) } } // env.Record是用来专门操作.tool-versions文件的, 增编削查 var record env.Record if len(paths) == 0 { record = env.EmptyRecord } else if len(paths) == 1 { r, err := env.NewRecord(paths[0]) if err != nil { panic(err) } record = r } else { r, err := env.NewRecord(paths[0], paths[1:]...) if err != nil { panic(err) } record = r } // SdkManager是用来专门管理Sdk的组件, 到这里Manager就可以通过Record来获取和修正Sdk版本信息咯 return newSdkManager(record, meta)}
上面提到,最核心的实在是 hook 机制调用的 vfox env <shell-name> 命令,那它到底干了件什么事情呢?
func envCmd(ctx cli.Context) error { ... // 拿到对应shell的组件 s := shell.NewShell(shellName) if s == nil { return fmt.Errorf("unknow target shell %s", shellName) } // 上面提到的加载.tool-versions信息到Manager中 manager := internal.NewSdkManagerWithSource(internal.SessionRecordSource, internal.ProjectRecordSource) defer manager.Close() // 获取须要配置的环境变量信息 envKeys, err := manager.EnvKeys() if err != nil { return err } // 将环境变量信息, 翻译成符合对应shell的命令 exportStr := s.Export(envKeys) fmt.Println(exportStr) return nil }}func (m Manager) EnvKeys() (env.Envs, error) { shellEnvs := make(env.Envs) var paths []string // 这里便是前面说的, Record包含了所有的版本信息, 只须要取出来即可 for k, v := range m.Record.Export() { if lookupSdk, err := m.LookupSdk(k); err == nil { if keys, err := lookupSdk.EnvKeys(Version(v)); err == nil { for key, value := range keys { if key == "PATH" { paths = append(paths, value) } else { shellEnvs[key] = value } } } } } ... return shellEnvs, nil}
没看懂代码没紧要,用一句话概括这段代码的功能:将 .tool-versions 记录的 SDK 版本信息,翻译成详细 shell 可实行的命令,实在核心技能就这么朴实无华。
五、插件系统插件系统是 vfox 的核心,它授予 vfox 无限的可能性,不仅仅局限于单一的 SDK。通过插件系统,vfox 能够灵巧地适应任何 SDK 的需求,无论是现有的还是未来可能涌现的。
更主要的是,插件系统利用 Lua 作为插件的开拓措辞,内置了一些常用模块,如 http、json、html、file 等,这使得插件系统不仅功能强大,而且易于开拓和自定义。用户可以根据自己的需求,轻松编写和定制自己的脚本,从而实现更多的功能。
口说无凭,我们直接写一个大略的插件来体验一下,以写一个 Windows 环境下可用的 Python 插件为例。
5.1 插件模板构造在开工之前,我们首先须要理解一下插件构造是什么样子,以及都供应了哪些钩子函数供我们实现。
--- 内置全局变量: 操作系统和架构类型OS_TYPE = ""ARCH_TYPE = ""--- 描述当前插件的基本信息, 插件名称、版本、最低运行时版本等信息PLUGIN = { name = "xxx", author = "xxx", version = "0.0.1", description = "xxx", updateUrl = "https://localhost/xxx.lua", minRuntimeVersion = "0.2.3",}--- 1.预安装钩子函数。vfox 会根据供应的元信息, 帮你提前下载好所需的文件(如果是压缩包,会帮你解压)放到指定目录。function PLUGIN:PreInstall(ctx) return { version = "0.1.1", sha256 = "xxx", --- 可选 sha1 = "xxx", --- 可选 url = "文件地址" }end--- 2.后置钩子函数。这里紧张是做一些额外操作, 例如编译源码。function PLUGIN:PostInstall(ctx)end--- 3.可用钩子函数。 见告 vfox 当前插件都有哪些可用版本。function PLUGIN:Available(ctx) end--- 4.环境信息钩子函数。 见告 vfox 当前SDK所须要配置的环境变量有哪些。function PLUGIN:EnvKeys(ctx)end
统共就 4 个钩子函数,是不是非常大略。
5.2 Python 插件实现OK,万事俱备那我们正式开始实现 Python 插件咯~
--- vfox 供应的库local http = require("http") --- 发起 http 要求local html = require("html") --- 解析 htmlOS_TYPE = ""ARCH_TYPE = ""--- python 下载源地址信息local PYTHON_URL = "python.org/ftp/python/"local DOWNLOAD_SOURCE = { --- ... EXE = "python.org/ftp/python/%s/python-%s%s.exe", SOURCE = "python.org/ftp/python/%s/Python-%s.tar.xz"}PLUGIN = { name = "python", author = "aooohan", version = "0.0.1", minRuntimeVersion = "0.2.3", }function PLUGIN:PreInstall(ctx) --- 拿到用户输入版本号, 解析成详细版本号 local version = ctx.version if version == "latest" then version = self:Available({})[1].version end if OS_TYPE == "windows" then local url, filename = checkAvailableReleaseForWindows(version) return { version = version, url = url, note = filename } else --- 非 Windows 环境实现, 略 endendfunction checkAvailableReleaseForWindows(version) --- 处理架构类型, 同一架构的不同名称 local archType = ARCH_TYPE if ARCH_TYPE == "386" then archType = "" else archType = "-" .. archType end --- 检讨是否存在 exe 安装器, 当然 Python 还供应了其他安装器, 例如 msi、web-installer 等 local url = DOWNLOAD_SOURCE.EXE:format(version, version, archType) local resp, err = http.head({ url = url }) if err ~= nil or resp.status_code ~= 200 then error("No available installer found for current version") end return url, "python-" .. version .. archType .. ".exe"end--- vfox 会在 PreInstall 实行完之后, 实行当前钩子函数.function PLUGIN:PostInstall(ctx) if OS_TYPE == "windows" then return windowsCompile(ctx) else --- 略 endendfunction windowsCompile(ctx) local sdkInfo = ctx.sdkInfo['python'] --- vfox 分配的安装路径 local path = sdkInfo.path local filename = sdkInfo.note --- exe 安装器路径 local qInstallFile = path .. "\\" .. filename local qInstallPath = path --- 实行安装器 local exitCode = os.execute(qInstallFile .. ' /quiet InstallAllUsers=0 PrependPath=0 TargetDir=' .. qInstallPath) if exitCode ~= 0 then error("error installing python") end --- 清理安装器 os.remove(qInstallFile)end--- 见告 vfox 可用版本function PLUGIN:Available(ctx) return parseVersion()endfunction parseVersion() --- 这里便是解析对应的 html 页面, 通过正则匹配详细版本号了 local resp, err = http.get({ url = PYTHON_URL }) if err ~= nil or resp.status_code ~= 200 then error("paring release info failed." .. err) end local result = {} --- 解析 html 略 return resultend--- 配置环境变量, 紧张是 PATH, 但是把稳 Windows 和 Unix-like 路径不一致, 以是要区分function PLUGIN:EnvKeys(ctx) local mainPath = ctx.path if OS_TYPE == "windows" then return { { key = "PATH", value = mainPath } } else return { { key = "PATH", value = mainPath .. "/bin" } } endend
至此,我们就完成了一个 Windows 环境下可用的 Python 插件啦~
当然,这只是为了方便演示如何自己实现插件,vfox 目前已经供应了完善的 Python 插件,可以通过 vfox add python/npmmirror 命令直接安装利用哦。
vfox 目前已支持 12 种插件,还在努力丰富中
Python ✅ -> python/npmmirrorNodejs ✅ -> nodejs/npmmirrorJava ✅ -> java/adoptium-jdkGolang ✅ -> golang/golangDart ✅ -> dart/dartFlutter ✅ -> flutter/flutter-cn.Net ✅ -> dotnet/dotnetDeno ✅ -> deno/denoZig ✅ -> zig/zigMaven ✅ -> maven/mavenGraalvm ✅ -> java/graalvmKotlin ✅ -> kotlin/kotlinRuby ⌛️PHP ⌛️六、结束我的初衷是不管什么措辞,只假如须要版本管理,只须要一个工具就能大略高效的完成。以是我创建了 vfox,它是一款专注于多措辞、多版本管理的生态工具,目标只有一个:让所有的编程措辞版本管理变得大略易用。无论你是 JavaScript、Java 还是 Python 的开拓者,vfox 都能为你供应一站式的办理方案。
我们的愿景是创建一个适宜国人利用的、大略易用的多措辞、多版本管理工具。我们相信,只有真正理解开拓者的需求,才能创造出真正有代价的工具。vfox 便是这样的工具,它是为理解决开拓者在日常事情中碰着的版本管理问题而生。
GitHub 地址:github.com/version-fox/vfox
末了,感谢 HelloGitHub 供应的机会,让我能向更多人先容 vfox。作为一个开源项目的创作者,我深感开源的力量。它不仅仅是代码的共享,更是知识和履历的共享。希望 vfox 能成为我们沟通的桥梁,欢迎各种形式的反馈和建议,让我们一起变强!
- END -