经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
上来就对标 20k Star 的开源项目,是自不量力还是后起之秀?
来源:cnblogs  作者:削微寒  时间:2024/3/7 9:08:33  对本文有异议

先来一段紧箍咒:nvm、fvm、gvm、sdkman、fnm、n、g、rvm、jenv、phpbrew、rustup、swiftenv、pyenv、rbenv...

这些都是用来解决编程语言多版本管理的工具,如果你是个程序员肯定认识或是用过几个,但是刚接触编程的小白,就会有些挠头了。

啥是编程语言版本管理工具?它们有什么用呢?

举个例子,用 Java 的开发者可能会遇见的问题,公司的项目是万年不变 JDK 8,但个人项目用的是最新的 JDK 21。这种情况下,在一台电脑上开发公司和个人项目的时候,就需要切换一下当前开发环境对应的 JDK 版本,否则项目跑不起来。编程语言版本管理工具就是用来切换/管理编程语言不同版本的工具,比如 Java 语言对应的工具是 jenv

每一种编程语言都有一个对应的版本管理工具,对于多语言开发者来说就需要安装、配置、学习各种版本管理工具,记忆不同工具的使用命令,这和紧箍咒无异。那咋办啊?

莫慌,今天 HelloGitHub 带来的是一款跨平台版本、支持多语言的版本管理工具——vfox,让你无忧应对多编程语言、不同版本的开发环境。该项目由国人(99 年的小伙)开发,更贴合国内开发者的使用习惯。

GitHub 地址:https://github.com/version-fox/vfox

接下来,让我们一起走近 vfox 了解它的功能、上手使用、技术原理和强大的插件系统吧!

一、介绍

vfox 是一个类 nvm、fvm、sdkman、asdf 的版本管理工具,具有跨平台通用易拓展的特性:

  • 简单:安装简单,一套命令管理所有语言
  • 跨平台:支持 Windows、Linux、macOS
  • 人性化:换项目时自动切换到对应编程语言、支持自动补全
  • 扩展性:容易上手的插件系统,添加冷门的编程语言
  • 作用域:支持 Global、Project、Session 三种作用域

质疑声:同类型的项目挺多的啊,不能一个国人开发、开源就来求 Star 吧?

下面,我们就来和在 GitHub 上有 20k Star 的同类型工具 asdf PK 一下,看看 vfox 是不是重复造轮子,到底能不能打!

二、对比 asdf

这里主要从操作系统兼容性、性能和插件换源三个方面进行对比。

2.1 兼容性

兼容性 Windows Linux macOS
asdf ? ? ?
vfox ? ? ?

首先,asdf 是用 shell 脚本实现的工具,所以并不支持原生 Windows 环境。而 vfox 是用 Go + Lua 实现的,因此天生支持 Windows 和其他操作系统。

2.2 性能

上图是对两个工具最核心的切换版本功能进行基准测试的结果,很容易就能得出结论:vfox 比 asdf 快 5 倍

速度 平均 最快 最慢
asdf 158.7 ms 154 ms 168.4 ms
vfox 28.1ms 27.1 ms 32.3 ms

技术解析: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 选择了另一种方法,即一个镜像源对应一个插件。

  1. $ vfox add nodejs/nodejs # 使用官方下载源
  2. $ vfox add nodejs/npmmirror # 使用 npmmirror 镜像
  3. $ vfox add python/python # 官方下载源
  4. $ vfox add python/npmmirror

虽然这样会使仓库的插件变多,但使用起来降低了负担,也没有乱七八糟的环境变量需要配置,对用户非常友好!

三、上手

说了这么多,还没上手玩一下简直忍不了。

3.1. 安装

Windows 用户只需要下载安装器进行安装即可,Linux 用户可以使用 APT 或 YUM 来快速安装,macOS 用户可以使用 Homebrew 安装。更详细的安装方式可查看文档

  1. $ brew tap version-fox/tap
  2. $ brew install vfox

安装完成之后,需要将 vfox 挂载到你的 shell 中,从下面条目中选择一条适合你 shell 的。

  1. echo 'eval "$(vfox activate bash)"' >> ~/.bashrc
  2. echo 'eval "$(vfox activate zsh)"' >> ~/.zshrc
  3. echo 'vfox activate fish | source' >> ~/.config/fish/config.fish
  4. # 对于 Powershell 用户,将下面行添加到你的 $PROFILE 文件中
  5. Invoke-Expression "$(vfox activate pwsh)"

3.2 使用

安装好了,但你还做不了任何事情,因为 vfox 是使用插件作为扩展,按需安装。

不知道应该添加哪些插件,可以用 vfox available 命令查看所有可用插件

所以你还需要安装插件,以 Node.js 为例,为了获得更好的体验,我们添加 npmmirror 镜像源插件:vfox add nodejs/npmmirror

在插件成功安装之后,你就可以玩起来了!

  • 安装指定版本:vfox install nodejs@<version>
  • 安装最新版本:vfox install nodejs@latest
  • 切换版本:vfox use nodejs[@<version>]

文字表达远不如图片来的更直观,我们直接上效果图。

四、技术原理

vfox 支持 Global、Session、Project 三种作用域,这三种作用域能够满足我们日常开发所需的场景。

作用域 命令 说明
Global vfox use -g <sdk-name> 全局范围有效
Session vfox use -s <sdk-name> 当前 shell 会话有效
Project vfox use -p <sdk-name> 当前项目下有效

那么你对它们的实现原理感兴趣吗?咱们废话不多说,直接看原理图!

vfox 是基于 shell 的 hook 机制实现的,hook 机制简单来说就是每当我们执行完命令之后,shell 都会调用一下你配置的钩子函数(hook),即 vfox env <shell-name> 命令,我们后面解释这个命令是干什么的。

说回到作用域上来,vofox 是通过 .tool-versions 文件来记录每个 SDK 对应的版本号信息。对于三种作用域,会分别在不同的地方创建 .tool-versions 文件,用于记录作用域内所需要的 SDK 版本信息。

  • Global -> $HOME/.version-fox/.tool-versions
  • Project -> 当前项目目录
  • Session -> $HOME/.version-fox/tmp/<shell-pid>/.tool-versions

代码如下:

  1. func newSdkManagerWithSource(sources ...RecordSource) *Manager {
  2. meta, err := newPathMeta()
  3. if err != nil {
  4. panic("Init path meta error")
  5. }
  6. var paths []string
  7. for _, source := range sources {
  8. // 根据不同的作用域选择性加载不同位置的.tool-versions文件
  9. switch source {
  10. case GlobalRecordSource:
  11. paths = append(paths, meta.ConfigPath)
  12. case ProjectRecordSource:
  13. // 当前目录
  14. curDir, err := os.Getwd()
  15. if err != nil {
  16. panic("Get current dir error")
  17. }
  18. paths = append(paths, curDir)
  19. case SessionRecordSource:
  20. // Shell会话临时目录
  21. paths = append(paths, meta.CurTmpPath)
  22. }
  23. }
  24. // env.Record是用来专门操作.tool-versions文件的, 增删改查
  25. var record env.Record
  26. if len(paths) == 0 {
  27. record = env.EmptyRecord
  28. } else if len(paths) == 1 {
  29. r, err := env.NewRecord(paths[0])
  30. if err != nil {
  31. panic(err)
  32. }
  33. record = r
  34. } else {
  35. r, err := env.NewRecord(paths[0], paths[1:]...)
  36. if err != nil {
  37. panic(err)
  38. }
  39. record = r
  40. }
  41. // SdkManager是用来专门管理Sdk的组件, 到这里Manager就可以通过Record来获取和修改Sdk版本信息咯
  42. return newSdkManager(record, meta)
  43. }

上面提到,最核心的其实是 hook 机制调用的 vfox env <shell-name> 命令,那它到底干了件什么事情呢?

  1. func envCmd(ctx *cli.Context) error {
  2. ...
  3. // 拿到对应shell的组件
  4. s := shell.NewShell(shellName)
  5. if s == nil {
  6. return fmt.Errorf("unknow target shell %s", shellName)
  7. }
  8. // 上面提到的加载.tool-versions信息到Manager中
  9. manager := internal.NewSdkManagerWithSource(internal.SessionRecordSource, internal.ProjectRecordSource)
  10. defer manager.Close()
  11. // 获取需要配置的环境变量信息
  12. envKeys, err := manager.EnvKeys()
  13. if err != nil {
  14. return err
  15. }
  16. // 将环境变量信息, 翻译成符合对应shell的命令
  17. exportStr := s.Export(envKeys)
  18. fmt.Println(exportStr)
  19. return nil
  20. }
  21. }
  22. func (m *Manager) EnvKeys() (env.Envs, error) {
  23. shellEnvs := make(env.Envs)
  24. var paths []string
  25. // 这里就是前面说的, Record包含了所有的版本信息, 只需要取出来即可
  26. for k, v := range m.Record.Export() {
  27. if lookupSdk, err := m.LookupSdk(k); err == nil {
  28. if keys, err := lookupSdk.EnvKeys(Version(v)); err == nil {
  29. for key, value := range keys {
  30. if key == "PATH" {
  31. paths = append(paths, *value)
  32. } else {
  33. shellEnvs[key] = value
  34. }
  35. }
  36. }
  37. }
  38. }
  39. ...
  40. return shellEnvs, nil
  41. }

没看懂代码没关系,用一句话概括这段代码的功能:.tool-versions 记录的 SDK 版本信息,翻译成具体 shell 可执行的命令,其实核心技术就这么朴实无华。

五、插件系统

插件系统是 vfox 的核心,它赋予 vfox 无限的可能性,不仅仅局限于单一的 SDK。通过插件系统,vfox 能够灵活地适应任何 SDK 的需求,无论是现有的还是未来可能出现的。

更重要的是,插件系统使用 Lua 作为插件的开发语言,内置了一些常用模块,如 httpjsonhtmlfile 等,这使得插件系统不仅功能强大,而且易于开发和自定义。用户可以根据自己的需求,轻松编写和定制自己的脚本,从而实现更多的功能。

口说无凭,我们直接写一个简单的插件来体验一下,以写一个 Windows 环境下可用的 Python 插件为例。

5.1 插件模板结构

在开工之前,我们首先需要了解一下插件结构是什么样子,以及都提供了哪些钩子函数供我们实现。

  1. --- 内置全局变量: 操作系统和架构类型
  2. OS_TYPE = ""
  3. ARCH_TYPE = ""
  4. --- 描述当前插件的基本信息, 插件名称、版本、最低运行时版本等信息
  5. PLUGIN = {
  6. name = "xxx",
  7. author = "xxx",
  8. version = "0.0.1",
  9. description = "xxx",
  10. updateUrl = "https://localhost/xxx.lua",
  11. minRuntimeVersion = "0.2.3",
  12. }
  13. --- 1.预安装钩子函数。vfox 会根据提供的元信息, 帮你提前下载好所需的文件(如果是压缩包,会帮你解压)放到指定目录。
  14. function PLUGIN:PreInstall(ctx)
  15. return {
  16. version = "0.1.1",
  17. sha256 = "xxx", --- 可选
  18. sha1 = "xxx", --- 可选
  19. url = "文件地址"
  20. }
  21. end
  22. --- 2.后置钩子函数。这里主要是做一些额外操作, 例如编译源码。
  23. function PLUGIN:PostInstall(ctx)
  24. end
  25. --- 3.可用钩子函数。 告诉 vfox 当前插件都有哪些可用版本。
  26. function PLUGIN:Available(ctx)
  27. end
  28. --- 4.环境信息钩子函数。 告诉 vfox 当前SDK所需要配置的环境变量有哪些。
  29. function PLUGIN:EnvKeys(ctx)
  30. end

总共就 4 个钩子函数,是不是非常简单。

5.2 Python 插件实现

OK,万事俱备那我们正式开始实现 Python 插件咯~

  1. --- vfox 提供的库
  2. local http = require("http") --- 发起 http 请求
  3. local html = require("html") --- 解析 html
  4. OS_TYPE = ""
  5. ARCH_TYPE = ""
  6. --- python 下载源地址信息
  7. local PYTHON_URL = "https://www.python.org/ftp/python/"
  8. local DOWNLOAD_SOURCE = {
  9. --- ...
  10. EXE = "https://www.python.org/ftp/python/%s/python-%s%s.exe",
  11. SOURCE = "https://www.python.org/ftp/python/%s/Python-%s.tar.xz"
  12. }
  13. PLUGIN = {
  14. name = "python",
  15. author = "aooohan",
  16. version = "0.0.1",
  17. minRuntimeVersion = "0.2.3",
  18. }
  19. function PLUGIN:PreInstall(ctx)
  20. --- 拿到用户输入版本号, 解析成具体版本号
  21. local version = ctx.version
  22. if version == "latest" then
  23. version = self:Available({})[1].version
  24. end
  25. if OS_TYPE == "windows" then
  26. local url, filename = checkAvailableReleaseForWindows(version)
  27. return {
  28. version = version,
  29. url = url,
  30. note = filename
  31. }
  32. else
  33. --- Windows 环境实现,
  34. end
  35. end
  36. function checkAvailableReleaseForWindows(version)
  37. --- 处理架构类型, 同一架构的不同名称
  38. local archType = ARCH_TYPE
  39. if ARCH_TYPE == "386" then
  40. archType = ""
  41. else
  42. archType = "-" .. archType
  43. end
  44. --- 检查是否存在 exe 安装器, 当然 Python 还提供了其他安装器, 例如 msiweb-installer
  45. local url = DOWNLOAD_SOURCE.EXE:format(version, version, archType)
  46. local resp, err = http.head({
  47. url = url
  48. })
  49. if err ~= nil or resp.status_code ~= 200 then
  50. error("No available installer found for current version")
  51. end
  52. return url, "python-" .. version .. archType .. ".exe"
  53. end
  54. --- vfox 会在 PreInstall 执行完之后, 执行当前钩子函数.
  55. function PLUGIN:PostInstall(ctx)
  56. if OS_TYPE == "windows" then
  57. return windowsCompile(ctx)
  58. else
  59. ---
  60. end
  61. end
  62. function windowsCompile(ctx)
  63. local sdkInfo = ctx.sdkInfo['python']
  64. --- vfox 分配的安装路径
  65. local path = sdkInfo.path
  66. local filename = sdkInfo.note
  67. --- exe 安装器路径
  68. local qInstallFile = path .. "\\" .. filename
  69. local qInstallPath = path
  70. --- 执行安装器
  71. local exitCode = os.execute(qInstallFile .. ' /quiet InstallAllUsers=0 PrependPath=0 TargetDir=' .. qInstallPath)
  72. if exitCode ~= 0 then
  73. error("error installing python")
  74. end
  75. --- 清理安装器
  76. os.remove(qInstallFile)
  77. end
  78. --- 告诉 vfox 可用版本
  79. function PLUGIN:Available(ctx)
  80. return parseVersion()
  81. end
  82. function parseVersion()
  83. --- 这里就是解析对应的 html 页面, 通过正则匹配具体版本号了
  84. local resp, err = http.get({
  85. url = PYTHON_URL
  86. })
  87. if err ~= nil or resp.status_code ~= 200 then
  88. error("paring release info failed." .. err)
  89. end
  90. local result = {}
  91. --- 解析 html
  92. return result
  93. end
  94. --- 配置环境变量, 主要是 PATH, 但是注意 Windows Unix-like 路径不一致, 所以要区分
  95. function PLUGIN:EnvKeys(ctx)
  96. local mainPath = ctx.path
  97. if OS_TYPE == "windows" then
  98. return {
  99. {
  100. key = "PATH",
  101. value = mainPath
  102. }
  103. }
  104. else
  105. return {
  106. {
  107. key = "PATH",
  108. value = mainPath .. "/bin"
  109. }
  110. }
  111. end
  112. end

至此,我们就完成了一个 Windows 环境下可用的 Python 插件啦~??

当然,这只是为了方便演示如何自己实现插件,vfox 目前已经提供了完善的 Python 插件,可以通过 vfox add python/npmmirror 命令直接安装使用哦。

vfox 目前已支持 12 种插件,还在努力丰富中??????

  • Python ? -> python/npmmirror
  • Nodejs ? -> nodejs/npmmirror
  • Java ? -> java/adoptium-jdk
  • Golang ? -> golang/golang
  • Dart ? -> dart/dart
  • Flutter ? -> flutter/flutter-cn
  • .Net ? -> dotnet/dotnet
  • Deno ? -> deno/deno
  • Zig ? -> zig/zig
  • Maven ? -> maven/maven
  • Graalvm ? -> java/graalvm
  • Kotlin ? -> kotlin/kotlin
  • Ruby ??
  • PHP ??

六、结束

我的初衷是不管什么语言,只要是需要版本管理,只需要一个工具就能简单高效的完成。所以我创建了 vfox,它是一款专注于多语言多版本管理的生态工具,目标只有一个:让所有的编程语言版本管理变得简单易用。无论你是 JavaScript、Java 还是 Python 的开发者,vfox 都能为你提供一站式的解决方案。

我们的愿景是创建一个适合国人使用的、简单易用的多语言多版本管理工具。我们相信,只有真正理解开发者的需求,才能创造出真正有价值的工具。vfox 就是这样的工具,它是为了解决开发者在日常工作中遇到的版本管理问题而生。

GitHub 地址:https://github.com/version-fox/vfox

最后,感谢 HelloGitHub 提供的机会,让我能向更多人介绍 vfox。作为一个开源项目的创作者,我深感开源的力量。它不仅仅是代码的共享,更是知识和经验的共享。希望 vfox 能成为我们沟通的桥梁,欢迎各种形式的反馈和建议,让我们一起变强!

原文链接:https://www.cnblogs.com/xueweihan/p/18058075

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号