asdf 配置完全指南:从.tool-versions、.asdfrc到环境变量与插件钩子
【免费下载链接】asdfExtendable version manager with support for Ruby, Node.js, Elixir, Erlang & more项目地址: https://gitcode.com/GitHub_Trending/as/asdf
asdf 是一款可扩展的版本管理器,支持 Ruby、Node.js、Elixir、Erlang 等语言工具的统一安装与版本切换。本篇指南以 docs/zh-hans/manage/configuration.md 为核心脉络,系统讲解 asdf 的三层配置体系:可共享的.tool-versions版本声明文件、用户特定的.asdfrc配置文件以及相关环境变量。读完本文,你将掌握版本文件的全部格式(含ref:、path:、system等特殊值)、.asdfrc中每个键的含义与默认值、插件钩子的编写方法,并理解这些配置在 asdf 源码(Go 实现)中的底层解析逻辑,从而能按团队或个人的实际需求定制 asdf 行为。
概述:asdf 的三层配置体系
asdf 的配置分为两类、三个来源:
- 可共享的版本声明:
.tool-versions文件声明"当前目录树中各个工具使用什么版本",可以提交到 Git 仓库与团队成员共享; - 用户特定的行为配置:
.asdfrc文件(默认位于$HOME/.asdfrc)定义单台机器上的个性化行为,如是否读取传统版本文件、是否保留下载缓存、插件仓库同步频率等; - 环境变量:
ASDF_CONFIG_FILE、ASDF_TOOL_VERSIONS_FILENAME、ASDF_DIR、ASDF_DATA_DIR、ASDF_CONCURRENCY等,用于覆盖默认路径与并发设置。
在 Go 实现的源码中,这一体系由 internal/config/config.go 统一建模:Config结构体承载路径类配置,Settings结构体承载.asdfrc中的键值,环境变量在LoadConfig()中优先于默认值被读取。下文依次展开每一层的具体用法。
.tool-versions:可共享的工具版本声明
作用范围与基本格式
无论何时.tool-versions出现在某个目录中,它所声明的工具版本将会被用于该目录及其任意子目录。这是 asdf 目录级版本管理的核心:每个项目目录都可以放一个属于自己的.tool-versions。
文件格式非常简单,每行一个工具,格式为工具名 版本号:
ruby 2.5.3 nodejs 10.15.0注释支持
.tool-versions支持#注释,可以出现在行尾或独占一行:
ruby 2.5.3 # 这是一个注释 # 这是另一个注释 nodejs 10.15.0在源码层面,internal/toolversions/toolversions.go 的parseLine函数通过strings.Cut(line, "#")将一行切分为"版本段 + 注释段",因此注释可以出现在行的任意位置;版本段内的空白 token 会被过滤掉,空 token 不会被误当作版本号。
版本号的四种格式
.tool-versions中的版本号支持以下格式:
| 格式 | 说明 |
|---|---|
10.15.0 | 实际的版本号。支持下载二进制文件的插件会直接下载对应的二进制文件。 |
ref:v1.0.2-a或ref:39cb398vb39 | 指定标签 / 提交 / 分支,从 Git 仓库下载并编译。 |
path:~/src/elixir | 使用本地已编译好的自定义工具路径,主要供语言开发者等场景使用。 |
system | 让 asdf 透传系统上未由 asdf 管理的工具版本。 |
这四种类型在 internal/toolversions/toolversions.go 的Parse函数中被解析为Version结构体的Type字段(version/ref/path/system,命令行中还额外支持latest)。当版本以ref:开头时,internal/toolversions/toolversions.go 的FormatForFS会将其转换为ref-<value>形式作为安装目录名,避免文件系统中出现冒号等不兼容字符。
多版本回退(空格分隔)
一行中可以声明多个版本,asdf 会按优先级逐个尝试:
python 3.7.2 2.7.15 system上面这行表示:优先使用 Python3.7.2,找不到时回退到2.7.15,最后回退到systemPython。这在需要渐进迁移版本、且团队中某些机器尚未安装新版本时非常实用。在源码中,这一行会被解析为ToolVersions{Versions: []string{"3.7.2", "2.7.15", "system"}},安装流程(internal/versions/versions.go 的Install)会对每个版本逐个尝试。
一键安装全部或指定工具
在包含.tool-versions文件的目录中:
- 执行
asdf install,将安装文件中声明的所有工具(对应源码中InstallAll,见 internal/versions/versions.go); - 执行
asdf install <name>,仅安装指定工具,且安装版本取.tool-versions中声明的版本。
通过命令更新版本
可以直接手动编辑.tool-versions文件,也可以使用asdf local(写入当前目录的.tool-versions)或asdf global(写入$HOME/.tool-versions)来更新。此外,较新版本还提供了asdf set <tool> <version>命令,其实现位于 internal/cli/set/set.go,支持--home与--parent标志分别写入家目录或向上查找最近的版本文件,并通过 internal/toolversions/toolversions.go 的WriteToolVersionsToFile保留原有行与注释、按需追加或更新条目。
版本解析顺序:从源码看 asdf 如何找到版本
理解.tool-versions的生效逻辑有助于排查"为什么用的是别的版本"。在 internal/resolve/resolve.go 中,版本解析遵循以下顺序:
- 环境变量优先:若存在形如
ASDF_<TOOL>_VERSION的环境变量(如ASDF_RUBY_VERSION),直接采用,其优先级高于所有文件(variableVersionName会将工具名转为大写并替换-为_); - 逐级向上查找:从当前目录开始,逐级向父目录查找
.tool-versions,直到文件系统根目录; - 家目录兜底:若一路找到根目录仍无结果,则尝试
$HOME下的版本文件; - 传统版本文件:当
.asdfrc中legacy_version_file = yes时,插件还可通过list-legacy-filenames回调声明传统文件(如.ruby-version),asdf 会优先在这些文件中查找。
在每一级目录中,asdf 先找.tool-versions(文件名可通过ASDF_TOOL_VERSIONS_FILENAME覆盖),再根据配置尝试传统版本文件,实现位于findVersionsInDir。
.asdfrc:用户特定的行为配置
.asdfrc文件定义了用户机器的特定配置。默认位置为$HOME/.asdfrc,可通过环境变量ASDF_CONFIG_FILE修改。文件格式本质上是 INI 风格(源码中由gopkg.in/ini.v1解析,见 internal/config/config.go)。
以下文件展示了所需格式及其默认值(与仓库根目录的 defaults 保持一致):
legacy_version_file = no use_release_candidates = no always_keep_download = no plugin_repository_last_check_duration = 60 disable_plugin_short_name_repository = no concurrency = autolegacy_version_file
插件支持读取其他版本管理器使用的版本文件,例如 Rubyrbenv的.ruby-version文件。
| 选项 | 描述 |
|---|---|
no(默认) | 从.tool-versions文件读取版本 |
yes | 如果可行的话,从传统版本文件读取版本(如.ruby-version) |
该值被解析为Settings.LegacyVersionFile布尔字段,并在版本解析时决定是否调用插件的list-legacy-filenames与parse-legacy-version-file回调(见 internal/resolve/resolve.go 的findVersionsInLegacyFile)。
always_keep_download
配置asdf install命令在安装完成后保留还是删除下载的源代码或二进制文件。
| 选项 | 描述 |
|---|---|
no(默认) | 在成功安装后删除源代码或二进制文件 |
yes | 在安装后保留源代码或二进制文件 |
对应 internal/versions/versions.go 中InstallOneVersion的收尾逻辑:安装成功并生成 shims、执行完 post-install 钩子后,若AlwaysKeepDownload()为假则调用os.RemoveAll(downloadDir)清理下载目录。保留下载对离线重装或排查插件脚本问题有帮助。
plugin_repository_last_check_duration
配置自上次 asdf 插件仓库同步到下一次同步的持续时间。命令asdf plugin add <name>或asdf plugin list all会触发时长检查,如果持续时间已过,则执行同步。
| 选项 | 描述 |
|---|---|
从1到999999999的数字(默认60) | 若距上次同步的时长已过,触发器事件发生时执行同步 |
0 | 每个触发器事件发生时都执行同步 |
never | 从不同步 |
同步事件发生在执行以下命令时:
asdf plugin add <name>asdf plugin list all
注意:asdf plugin add <name> <git-url>(带 Git URL 的完整形式)不会触发插件仓库同步。
⚠️ 将值设置为
never并不会阻止插件仓库的初始同步,如需彻底禁用初始同步,请配合下面的disable_plugin_short_name_repository使用。
在源码中,internal/pluginindex/pluginindex.go 的Refresh会读取插件索引目录下repo-updated时间戳文件,计算距上次更新的纳秒间隔,并与updateDurationMinutes(由本配置换算而来)比较,超过才执行git pull更新;internal/config/config.go 的newPluginRepoCheckDuration负责把字符串解析为"永不 / 每 N 秒"结构体,解析失败时回退到默认值 60。
disable_plugin_short_name_repository
禁用 asdf 插件的缩写仓库(short name repository)同步功能。若缩写仓库被禁用,同步事件将提前退出。
| 选项 | 描述 |
|---|---|
no(默认) | 在同步事件发生时克隆或更新 asdf 插件仓库 |
yes | 禁用插件缩写仓库 |
同步事件与上一条相同(asdf plugin add <name>、asdf plugin list all),asdf plugin add <name> <git-url>同样不触发同步。
⚠️ 禁用插件缩写仓库不会删除该仓库本身(如果它已经同步过)。如需删除插件仓库,可使用
rm --recursive --trash $ASDF_DATA_DIR/repository命令。⚠️ 禁用插件缩写仓库不会删除从该源之前安装的插件。可使用
asdf plugin remove <name>删除插件;删除插件将移除该工具的所有已安装版本。
concurrency
编译源代码时使用的默认核心数。
| 选项 | 描述 |
|---|---|
integer | 编译源代码时使用的核心数 |
auto | 自动探测:先使用nproc命令计算核心数量,然后尝试sysctl hw.ncpu,接着查看/proc/cpuinfo文件;如果都无法获取则默认使用1 |
注意:如果设置了环境变量ASDF_CONCURRENCY,则该环境变量具有优先级。
在源码层面,internal/config/config.go 的getConcurrency先检查ASDF_CONCURRENCY环境变量,非空则直接采用;auto或空值在 Go 实现中通过runtime.NumCPU()解析为当前 CPU 核数。最终该值会以ASDF_CONCURRENCY环境变量注入插件安装流程(见 internal/versions/versions.go 中InstallOneVersion构造的 env map),插件脚本可据此决定并行编译的进程数。相关行为在 internal/config/config_test.go 中有测试覆盖,包括ASDF_CONCURRENCY=99覆盖.asdfrc值、auto解析为runtime.NumCPU()等场景。
插件钩子(plugin hooks)
.asdfrc还支持定义钩子(hook),在特定事件前后执行自定义代码:
- 在插件安装、重新加载、更新或卸载之前或之后;
- 在执行插件命令之前或之后。
例如,如果安装了一个名为foo的插件并提供了bar可执行文件,则可以使用以下钩子在执行插件命令之前先执行自定义代码:
pre_foo_bar = echo Executing with args: $@支持以下钩子模式:
pre_<plugin_name>_<command>pre_asdf_download_<plugin_name>{pre,post}_asdf_{install,reshim,uninstall}_<plugin_name>$1:完整版本
{pre,post}_asdf_plugin_{add,update,remove,reshim}$1:插件名称
{pre,post}_asdf_plugin_{add,update,remove}_<plugin_name>
钩子的执行机制在 internal/hook/hook.go 中实现:RunWithOutput通过config.GetHook(hookName)从.asdfrc读取对应键的值(源码 internal/config/config.go 的GetHook会惰性加载 INI 配置并返回原始键值),若为空则直接跳过,否则交给execute.NewExpression以 Shell 表达式方式执行并把$1、$@等参数传入。以安装为例,internal/versions/versions.go 的InstallOneVersion会在插件download回调前执行pre_asdf_download_<plugin>、在install回调前后执行pre_asdf_install_<plugin>与post_asdf_install_<plugin>,卸载流程中同样有 pre/post-uninstall 钩子。
请查看 创建插件 了解在哪些命令执行之前或之后会运行哪些命令钩子。
环境变量:覆盖默认路径与行为
设置环境变量因系统和 Shell 而异。默认位置取决于 asdf 的安装位置和方法(Git 克隆、Homebrew、AUR 等)。环境变量通常应在加载asdf.sh/asdf.fish等文件之前设置;对于 Elvish,应在use asdf之前设置。以下以 Bash Shell 为例说明用法。
ASDF_CONFIG_FILE
.asdfrc配置文件的路径,可设置为任何位置,必须是绝对路径。
- 未设置时:使用
$HOME/.asdfrc; - 用法:
export ASDF_CONFIG_FILE=/home/john_doe/.config/asdf/.asdfrc
ASDF_TOOL_VERSIONS_FILENAME
用于存储工具名称和版本的文件名,可以是任何合法的文件名。通常不建议设置,除非你希望忽略.tool-versions文件(例如改用tool_versions或其他自定义文件名)。
- 未设置时:使用
.tool-versions; - 用法:
export ASDF_TOOL_VERSIONS_FILENAME=tool_versions
源码备注:在 internal/config/config.go 的
LoadConfig中,该变量兼容旧名ASDF_DEFAULT_TOOL_VERSIONS_FILENAME,二者均可生效。
ASDF_DIR
asdf 核心脚本的位置,可设置为任何位置,必须是绝对路径。
- 未设置时:使用
bin/asdf可执行文件的父目录; - 用法:
export ASDF_DIR=/home/john_doe/.config/asdf
ASDF_DATA_DIR
asdf 安装插件、垫片(shims)和工具版本的位置,可设置为任何位置,必须是绝对路径。数据目录内部结构见 internal/data/data.go:downloads/、installs/、plugins/分别存放下载缓存、工具版本安装目录和插件本体。
- 未设置时:若
$HOME/.asdf存在则使用它,否则使用ASDF_DIR的值; - 用法:
export ASDF_DATA_DIR=/home/john_doe/.asdf
ASDF_CONCURRENCY
编译源代码时使用的 CPU 核心数。如果设置了该值,它将优先于 asdf 配置中的concurrency值。
- 未设置时:使用 asdf 配置中的
concurrency值; - 用法:
export ASDF_CONCURRENCY=32
全配置样例:默认值推导演示
按照以下简单的 asdf 配置:
- 使用 Bash Shell;
- 安装位置为
$HOME/.asdf; - 通过 Git 安装;
- 未设置任何环境变量;
- 没有自定义的
.asdfrc文件;
将会产生以下结果:
| 配置 | 值 | 如何计算 |
|---|---|---|
| 配置文件位置 | $HOME/.asdfrc | ASDF_CONFIG_FILE为空,所以使用$HOME/.asdfrc |
| 默认工具版本声明文件名 | .tool-versions | ASDF_TOOL_VERSIONS_FILENAME为空,所以使用.tool-versions |
| asdf 目录 | $HOME/.asdf | ASDF_DIR为空,所以使用bin/asdf的父目录 |
| asdf 数据目录 | $HOME/.asdf | ASDF_DATA_DIR为空,所以使用$HOME/.asdf(因为$HOME/.asdf存在) |
| concurrency | auto | ASDF_CONCURRENCY为空,所以依赖于默认配置的concurrency值 |
| legacy_version_file | no | 没有自定义.asdfrc,所以使用默认配置 |
| use_release_candidates | no | 没有自定义.asdfrc,所以使用默认配置 |
| always_keep_download | no | 没有自定义.asdfrc,所以使用默认配置 |
| plugin_repository_last_check_duration | 60 | 没有自定义.asdfrc,所以使用默认配置 |
| disable_plugin_short_name_repository | no | 没有自定义.asdfrc,所以使用默认配置 |
上述默认值完整保存在仓库根目录的 defaults 文件中,它与源码中的默认常量一一对应:~/.asdf、~/.asdfrc、.tool-versions、Every: 60等(见 internal/config/config.go)。值得一提的是,defaults中列出的use_release_candidates在 Go 实现中暂未纳入Settings结构体(源码注释明确说明该设置不应被 Go 实现支持),若你从旧版 Bash 版 asdf 迁移配置,请留意这一点。
结语:配置优先级速查
综合上文,asdf 各项配置的优先级可归纳为:
- 环境变量最高:
ASDF_CONFIG_FILE、ASDF_TOOL_VERSIONS_FILENAME、ASDF_DIR、ASDF_DATA_DIR、ASDF_CONCURRENCY以及ASDF_<TOOL>_VERSION(版本解析层面); .asdfrc次之:legacy_version_file、always_keep_download、plugin_repository_last_check_duration、disable_plugin_short_name_repository、concurrency及各类插件钩子;- 默认值兜底:仓库根目录的 defaults 与源码常量(见 internal/config/config.go)。
按此优先级排查配置问题时,通常能快速定位"为什么实际行为与预期不符"。若需进一步了解版本文件解析细节,可阅读 internal/toolversions/toolversions.go 与 internal/resolve/resolve.go;若关注配置加载的完整逻辑,可结合 internal/config/config.go 及其测试 internal/config/config_test.go 深入研究。
【免费下载链接】asdfExtendable version manager with support for Ruby, Node.js, Elixir, Erlang & more项目地址: https://gitcode.com/GitHub_Trending/as/asdf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考