mirror of https://github.com/jdx/mise
909 lines
34 KiB
Rust
909 lines
34 KiB
Rust
use color_eyre::eyre::Result;
|
|
use console::strip_ansi_codes;
|
|
use indoc::formatdoc;
|
|
|
|
use crate::cli::command::Command;
|
|
use crate::cli::Cli;
|
|
use crate::config::Config;
|
|
use crate::output::Output;
|
|
|
|
/// internal command to generate markdown from help
|
|
#[derive(Debug, clap::Args)]
|
|
#[clap(hide = true)]
|
|
pub struct RenderHelp {}
|
|
|
|
impl Command for RenderHelp {
|
|
fn run(self, _config: Config, out: &mut Output) -> Result<()> {
|
|
let mut cli = Cli::command()
|
|
.term_width(80)
|
|
.max_term_width(80)
|
|
.disable_help_subcommand(true)
|
|
.disable_help_flag(true);
|
|
out.stdout.write(formatdoc!(
|
|
r#"
|
|
<!-- THIS FILE IS AUTOGENERATED FROM src/cli/render_help.rs, DO NOT EDIT -->
|
|
# [rtx](https://github.com/jdxcode/rtx)
|
|
|
|
[](https://crates.io/crates/rtx-cli)
|
|
[](https://github.com/jdxcode/rtx/blob/main/LICENSE)
|
|
[](https://github.com/jdxcode/rtx/actions/workflows/rtx.yml)
|
|
[](https://codecov.io/gh/jdxcode/rtx)
|
|
[](https://discord.gg/mABnUDvP57)
|
|
|
|
_{about}_
|
|
|
|
## 30 Second Demo
|
|
|
|
The following shows using rtx to install [nodejs](https://nodejs.org) and
|
|
[jq](https://stedolan.github.io/jq/) into a project using a `.tool-versions` file.
|
|
[hyperfine](https://github.com/sharkdp/hyperfine) is used to show the performance using
|
|
rtx vs asdf. (See [Performance](#performance)).
|
|
Note that calling `which node` gives us a real path to the binary, not a shim.
|
|
|
|
[](./docs/demo.gif)
|
|
|
|
## Features
|
|
|
|
- **asdf-compatible** - rtx is compatible with asdf plugins and `.tool-versions` files. It can be used as a drop-in replacement.
|
|
- **Polyglot** - compatible with any language, so no more figuring out how nvm, nodenv, pyenv, etc work individually—just use 1 tool.
|
|
- **Fast** - rtx is written in Rust and is very fast. 20x-200x faster than asdf.
|
|
- **No shims** - shims (used by asdf) cause problems, they break `which node`, and add overhead. We don't use them by default.
|
|
- **Better UX** - asdf is full of strange UX decisions (like `asdf plugin add` but also `asdf install`). We've taken care to make rtx easy to use.
|
|
- **Fuzzy matching and aliases** - no need to specify exact version numbers like with asdf.
|
|
- **One command install** - No need to manually install each plugin, just run `rtx install` and it will install all the plugins you need.
|
|
|
|
## Quickstart
|
|
|
|
Install rtx (other methods [here](#installation)):
|
|
|
|
```sh-session
|
|
$ curl https://rtx.pub/rtx-latest-macos-arm64 > ~/bin/rtx
|
|
$ chmod +x ~/bin/rtx
|
|
$ rtx --version
|
|
rtx {version}
|
|
```
|
|
|
|
Hook rtx into to your shell. This will automatically add `~/bin` to `PATH` if it isn't already.
|
|
(choose one, and open a new shell session for the changes to take effect):
|
|
|
|
```sh-session
|
|
$ echo 'eval "$(~/bin/rtx activate bash)"' >> ~/.bashrc
|
|
$ echo 'eval "$(~/bin/rtx activate zsh)"' >> ~/.zshrc
|
|
$ echo '~/bin/rtx activate fish | source' >> ~/.config/fish/config.fish
|
|
```
|
|
|
|
> **Warning**
|
|
>
|
|
> If you use direnv, you will want to activate direnv _before_ rtx. There is also
|
|
> an alternative way to use rtx inside of direnv, see [here](#direnv).
|
|
|
|
Install a runtime and set it as the default:
|
|
|
|
```sh-session
|
|
$ rtx install nodejs@18
|
|
$ rtx global nodejs@18
|
|
$ node -v
|
|
v18.10.9
|
|
```
|
|
|
|
> **Note**
|
|
>
|
|
> `rtx install` is optional, `rtx global` will prompt to install the runtime if it's not
|
|
> already installed. This is configurable in [`~/.config/rtx/config.toml`](#configuration).
|
|
|
|
## Table of Contents
|
|
|
|
<!--ts-->
|
|
<!--te-->
|
|
|
|
## About
|
|
|
|
rtx is a tool for managing programming language and tool versions. For example, use this to install
|
|
a particular version of node.js and ruby for a project. Using `rtx activate`, you can have your
|
|
shell automatically switch to the correct node and ruby versions when you `cd` into the project's
|
|
directory. Other projects on your machine can use a different set of versions.
|
|
|
|
rtx is inspired by [asdf](https://asdf-vm.com) and uses asdf's vast [plugin ecosystem](https://github.com/asdf-vm/asdf-plugins)
|
|
under the hood. However, it is _much_ faster than asdf and has a more friendly user experience.
|
|
For more on how rtx compares to asdf, [see below](#comparison-to-asdf). The goal of this project
|
|
was to create a better front-end to asdf.
|
|
|
|
It uses the same `.tool-versions` file that asdf uses. It's also compatible with idiomatic version
|
|
files like `.node-version` and `.ruby-version`. See [Legacy Version Files](#legacy-version-files) below.
|
|
|
|
Come chat about rtx on [discord](https://discord.gg/mABnUDvP57).
|
|
|
|
### How it works
|
|
|
|
rtx installs as a shell extension (e.g. `rtx activate zsh`) that sets the `PATH`
|
|
environment variable to point your shell to the correct runtime binaries. When you `cd` into a
|
|
directory containing a `.tool-versions` file, rtx will automatically activate the correct versions.
|
|
|
|
Every time your prompt starts it will call `rtx hook-env` to fetch new environment variables. This
|
|
should be very fast and it exits early if the the directory wasn't changed or the `.tool-versions`
|
|
files haven't been updated. On my machine this takes 4ms in the fast case, 14ms in the slow case. See [Performance](#performance) for more on this topic.
|
|
|
|
Unlike asdf which uses shim files to dynamically locate runtimes when they're called, rtx modifies
|
|
`PATH` ahead of time so the runtimes are called directly. This is not only faster since it avoids
|
|
any overhead, but it also makes it so commands like `which node` work as expected. This also
|
|
means there isn't any need to run `asdf reshim` after installing new runtime binaries.
|
|
|
|
rtx does not directly install runtimes. Instead, it uses asdf plugins to install runtimes. See
|
|
[plugins](#plugins) below.
|
|
|
|
### Common example commands
|
|
|
|
rtx install nodejs@18.0.0 Install a specific version number
|
|
rtx install nodejs@18.0 Install a fuzzy version number
|
|
rtx local nodejs@18 Use node-18.x in current project
|
|
rtx global nodejs@18 Use node-18.x as default
|
|
|
|
rtx install nodejs Install the version specified in .tool-versions
|
|
rtx local nodejs@latest Use latest node in current directory
|
|
rtx global nodejs@system Use system node as default
|
|
|
|
rtx x nodejs@18 -- node app.js Run `node app.js` with the PATH pointing to node-18.x
|
|
|
|
## Installation
|
|
|
|
### Standalone
|
|
|
|
Note that it isn't necessary for `rtx` to be on `PATH`. If you run the activate script in your rc
|
|
file, rtx will automatically add itself to `PATH`.
|
|
|
|
```sh-session
|
|
$ curl https://rtx.pub/install.sh | sh
|
|
```
|
|
|
|
or if you're allergic to `| sh`:
|
|
|
|
```sh-session
|
|
$ curl https://rtx.pub/rtx-latest-macos-arm64 > /usr/local/bin/rtx
|
|
```
|
|
|
|
It doesn't matter where you put it. So use `~/bin`, `/usr/local/bin`, `~/.local/share/rtx/bin/rtx`
|
|
or whatever.
|
|
|
|
Supported architectures:
|
|
|
|
- `x64`
|
|
- `arm64`
|
|
|
|
Supported platforms:
|
|
|
|
- `macos`
|
|
- `linux`
|
|
|
|
If you need something else, compile it with [cargo](#cargo).
|
|
|
|
### Homebrew
|
|
|
|
There are 2 ways to install rtx with Homebrew. The recommended method is to use
|
|
the custom tap which will always contain the latest release.
|
|
|
|
```sh-session
|
|
$ brew install jdxcode/tap/rtx
|
|
```
|
|
|
|
Alternatively, you can use the built-in tap (homebrew-core), which will be updated
|
|
once Homebrew maintainers merge the PR for a new release:
|
|
|
|
```sh-session
|
|
$ brew install rtx
|
|
```
|
|
|
|
### Cargo
|
|
|
|
Build from source with Cargo:
|
|
|
|
```sh-session
|
|
$ cargo install rtx-cli
|
|
|
|
Do it faster with [cargo-binstall](https://github.com/cargo-bins/cargo-binstall):
|
|
|
|
```sh-session
|
|
$ cargo install cargo-binstall
|
|
$ cargo binstall rtx-cli
|
|
```
|
|
|
|
Build from the latest commit in main:
|
|
|
|
```sh-session
|
|
$ cargo install rtx-cli --git https://github.com/jdxcode/rtx --branch main
|
|
```
|
|
|
|
### npm
|
|
|
|
rtx is available on npm as precompiled binaries. This isn't a node.js package, just distributed
|
|
via npm. It can be useful for JS projects that want to setup rtx via `package.json` or `npx`.
|
|
|
|
```sh-session
|
|
$ npm install -g @jdxcode/rtx
|
|
```
|
|
|
|
Or use npx if you just want to test it out for a single command without fully installing:
|
|
|
|
```sh-session
|
|
$ npx @jdxcode/rtx exec python@3.11 -- python some_script.py
|
|
```
|
|
|
|
### GitHub Releases
|
|
|
|
Download the latest release from [GitHub](https://github.com/jdxcode/rtx/releases).
|
|
|
|
```sh-session
|
|
$ curl https://github.com/jdxcode/rtx/releases/download/v1.18.2/rtx-v1.18.2-linux-x64 | tar -xJv
|
|
$ mv rtx/bin/rtx /usr/local/bin
|
|
```
|
|
|
|
### apt
|
|
|
|
For installation on Ubuntu/Debian:
|
|
|
|
```sh-session
|
|
wget -qO - https://rtx.pub/gpg-key.pub | gpg --dearmor | sudo tee /usr/share/keyrings/rtx-archive-keyring.gpg 1> /dev/null
|
|
echo "deb [signed-by=/usr/share/keyrings/rtx-archive-keyring.gpg arch=amd64] https://rtx.pub/deb stable main" | sudo tee /etc/apt/sources.list.d/rtx.list
|
|
sudo apt update
|
|
sudo apt install -y rtx
|
|
```
|
|
|
|
> **Warning**
|
|
>
|
|
> If you're on arm64 you'll need to run the following:
|
|
> ```
|
|
> echo "deb [signed-by=/usr/share/keyrings/rtx-archive-keyring.gpg arch=arm64] https://rtx.pub/deb stable main" | sudo tee /etc/apt/sources.list.d/rtx.list
|
|
> ```
|
|
|
|
### dnf
|
|
|
|
For Fedora, CentOS, Amazon Linux, RHEL and other dnf-based distributions:
|
|
|
|
```sh-session
|
|
dnf install -y dnf-plugins-core
|
|
dnf config-manager --add-repo https://rtx.pub/rpm/rtx.repo
|
|
dnf install -y rtx
|
|
```
|
|
|
|
### yum
|
|
|
|
```sh-session
|
|
yum install -y yum-utils
|
|
yum-config-manager --add-repo https://rtx.pub/rpm/rtx.repo
|
|
yum install -y rtx
|
|
```
|
|
|
|
### ~~apk~~ (coming soon)
|
|
|
|
For Alpine Linux:
|
|
|
|
```sh-session
|
|
apk add rtx --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/
|
|
```
|
|
|
|
### aur
|
|
|
|
For Arch Linux:
|
|
|
|
```sh-session
|
|
git clone https://aur.archlinux.org/rtx.git
|
|
cd rtx
|
|
makepkg -si
|
|
```
|
|
|
|
## Other Shells
|
|
|
|
### Bash
|
|
|
|
```sh-session
|
|
$ echo 'eval "$(rtx activate bash)"' >> ~/.bashrc
|
|
```
|
|
|
|
### Fish
|
|
|
|
```sh-session
|
|
$ echo 'rtx activate fish | source' >> ~/.config/fish/config.fish
|
|
```
|
|
|
|
### Xonsh
|
|
|
|
Since `.xsh` files are [not compiled](https://github.com/xonsh/xonsh/issues/3953) you may shave a bit off startup time by using a pure Python import: add the code below to, for example, `~/.config/xonsh/rtx.py` config file and `import rtx` it in `~/.config/xonsh/rc.xsh`:
|
|
```xsh
|
|
from pathlib import Path
|
|
from xonsh.built_ins import XSH
|
|
|
|
ctx = XSH.ctx
|
|
rtx_init = subprocess.run([Path('~/bin/rtx').expanduser(),'activate','xonsh'],capture_output=True,encoding="UTF-8").stdout
|
|
XSH.builtins.execx(rtx_init,'exec',ctx,filename='rtx')
|
|
```
|
|
|
|
Or continue to use `rc.xsh`/`.xonshrc`:
|
|
```xsh
|
|
echo 'execx($(~/bin/rtx activate xonsh))' >> ~/.config/xonsh/rc.xsh # or ~/.xonshrc
|
|
```
|
|
|
|
Given that `rtx` replaces both shell env `$PATH` and OS environ `PATH`, watch out that your configs don't have these two set differently (might throw `os.environ['PATH'] = xonsh.built_ins.XSH.env.get_detyped('PATH')` at the end of a config to make sure they match)
|
|
|
|
### Something else?
|
|
|
|
Adding a new shell is not hard at all since very little shell code is
|
|
in this project.
|
|
[See here](https://github.com/jdxcode/rtx/tree/main/src/shell) for how
|
|
the others are implemented. If your shell isn't currently supported
|
|
I'd be happy to help you get yours integrated.
|
|
|
|
## Uninstalling
|
|
|
|
Use `rtx implode` to uninstall rtx. This will remove the rtx binary and all of its data. Use
|
|
`rtx implode --help` for more information.
|
|
|
|
Alternatively, manually remove the following directories to fully clean up:
|
|
|
|
* `~/.local/share/rtx` (can also be `RTX_DATA_DIR` or `XDG_DATA_HOME/rtx`)
|
|
* `~/.config/rtx` (can also be `RTX_CONFIG_DIR` or `XDG_CONFIG_HOME/rtx`)
|
|
* on Linux: `~/.cache/rtx` (can also be `RTX_CACHE_DIR` or `XDG_CACHE_HOME/rtx`)
|
|
* on macOS: `~/Library/Caches/rtx` (can also be `RTX_CACHE_DIR`)
|
|
|
|
## Configuration
|
|
|
|
### `.tool-versions`
|
|
|
|
The `.tool-versions` file is used to specify the runtime versions for a project. An example of this
|
|
is:
|
|
|
|
```
|
|
nodejs 18.0.0 # comments are allowed
|
|
ruby 3 # can be fuzzy version
|
|
shellcheck latest # also supports "latest"
|
|
jq 1.6
|
|
erlang ref:master # compile from vcs ref
|
|
golang prefix:1.19 # uses the latest 1.19.x version—needed in case "1.19" is an exact match
|
|
shfmt path:./shfmt # use a custom runtime
|
|
```
|
|
|
|
Create `.tool-versions` files manually, or use [`rtx local`](#rtx-local) to create them automatically.
|
|
See [the asdf docs](https://asdf-vm.com/manage/configuration.html#tool-versions) for more info on this file format.
|
|
|
|
### Legacy version files
|
|
|
|
rtx supports "legacy version files" just like asdf. They're language-specific files like `.node-version`
|
|
and `.python-version`. These are ideal for setting the runtime version of a project without forcing
|
|
other developers to use a specific tool like rtx/asdf.
|
|
|
|
They support aliases, which means you can have an `.nvmrc` file with `lts/hydrogen` and it will work
|
|
in rtx and nvm. Here are some of the supported legacy version files:
|
|
|
|
| Plugin | "Legacy" (Idiomatic) Files |
|
|
| --------- | -------------------------------------------------- |
|
|
| crystal | `.crystal-version` |
|
|
| elixir | `.exenv-version` |
|
|
| golang | `.go-version`, `go.mod` |
|
|
| java | `.java-version` |
|
|
| nodejs | `.nvmrc`, `.node-version` |
|
|
| python | `.python-version` |
|
|
| ruby | `.ruby-version`, `Gemfile` |
|
|
| terraform | `.terraform-version`, `.packer-version`, `main.tf` |
|
|
| yarn | `.yvmrc` |
|
|
|
|
In rtx these are enabled by default. You can disable them with `rtx settings set legacy_version_file false`.
|
|
There is a performance cost to having these when they're parsed as it's performed by the plugin in
|
|
`bin/parse-version-file`. However these are [cached](#cache-behavior) so it's not a huge deal.
|
|
You may not even notice.
|
|
|
|
> **Note**
|
|
>
|
|
> asdf calls these "legacy version files" so we do too. I think this is a bad name since it implies
|
|
> that they shouldn't be used—which is definitely not the case IMO. I prefer the term "idiomatic"
|
|
> version files since they're version files not specific to asdf/rtx and can be used by other tools.
|
|
> (`.nvmrc` being a notable exception, which is tied to a specific tool.)
|
|
|
|
### Global config: `~/.config/rtx/config.toml`
|
|
|
|
rtx can be configured in `~/.config/rtx/config.toml`. The following options are available (defaults shown):
|
|
|
|
```toml
|
|
# whether to prompt to install plugins and runtimes if they're not already installed
|
|
missing_runtime_behavior = 'prompt' # other options: 'ignore', 'warn', 'prompt', 'autoinstall'
|
|
|
|
# plugins can read the versions files used by other version managers (if enabled by the plugin)
|
|
# for example, .nvmrc in the case of nodejs's nvm
|
|
legacy_version_file = true # enabled by default (different than asdf)
|
|
|
|
# configure `rtx install` to always keep the downloaded archive
|
|
always_keep_download = false # deleted after install by default
|
|
|
|
# configure how frequently (in minutes) to fetch updated plugin repository changes
|
|
# this is updated whenever a new runtime is installed
|
|
# (note: this isn't currently implemented but there are plans to add it: https://github.com/jdxcode/rtx/issues/128)
|
|
plugin_autoupdate_last_check_duration = 10080 # (one week) set to 0 to disable updates
|
|
|
|
verbose = false # set to true to see full installation output, see `RTX_VERBOSE`
|
|
asdf_compat = false # set to true to ensure .tool-versions will be compatible with asdf, see `RTX_ASDF_COMPAT`
|
|
jobs = 4 # number of plugins or runtimes to install in parallel. The default is `4`.
|
|
raw = false # set to true to directly pipe plugins to stdin/stdout/stderr
|
|
|
|
shorthands_file = '~/.config/rtx/shorthands.toml' # path to the shorthands file, see `RTX_SHORTHANDS_FILE`
|
|
disable_default_shorthands = false # disable the default shorthands, see `RTX_DISABLE_DEFAULT_SHORTHANDS`
|
|
|
|
experimental = false # enable experimental features such as shims
|
|
shims_dir = '~/.local/share/rtx/shims' # [experimental] directory where shims are stored
|
|
|
|
[alias.nodejs]
|
|
my_custom_node = '18' # makes `rtx install nodejs@my_custom_node` install node-18.x
|
|
# this can also be specified in a plugin (see below in "Aliases")
|
|
```
|
|
|
|
These settings can also be managed with `rtx settings ls|get|set|unset`.
|
|
|
|
### Environment variables
|
|
|
|
rtx can also be configured via environment variables. The following options are available:
|
|
|
|
#### `RTX_MISSING_RUNTIME_BEHAVIOR`
|
|
|
|
This is the same as the `missing_runtime_behavior` config option in `~/.config/rtx/config.toml`.
|
|
|
|
```sh-session
|
|
$ RTX_MISSING_RUNTIME_BEHAVIOR=ignore rtx install nodejs@18
|
|
$ RTX_NODEJS_VERSION=18 rtx exec -- node --version
|
|
```
|
|
|
|
#### `RTX_DATA_DIR`
|
|
|
|
This is the directory where rtx stores its data. The default is `~/.local/share/rtx`.
|
|
|
|
#### `RTX_CACHE_DIR`
|
|
|
|
This is the directory where rtx stores cache. The default is `~/.cache/rtx` on Linux and `~/Library/Caches/rtx` on macOS.
|
|
|
|
#### `RTX_CONFIG_FILE`
|
|
|
|
This is the path to the config file. The default is `~/.config/rtx/config.toml`.
|
|
(Or `$XDG_CONFIG_HOME/config.toml` if that is set)
|
|
|
|
#### `RTX_DEFAULT_TOOL_VERSIONS_FILENAME`
|
|
|
|
Set to something other than ".tool-versions" to have rtx look for configuration with alternate names.
|
|
|
|
#### `RTX_${{PLUGIN}}_VERSION`
|
|
|
|
Set the version for a runtime. For example, `RTX_NODEJS_VERSION=18` will use nodejs@18.x regardless
|
|
of what is set in `.tool-versions`.
|
|
|
|
#### `RTX_LEGACY_VERSION_FILE`
|
|
|
|
Plugins can read the versions files used by other version managers (if enabled by the plugin)
|
|
for example, .nvmrc in the case of nodejs's nvm.
|
|
|
|
#### `RTX_LOG_LEVEL=trace|debug|info|warn|error`
|
|
|
|
Can also use `RTX_DEBUG=1`, `RTX_TRACE=1`, and `RTX_QUIET=1`. These adjust the log
|
|
output to the screen.
|
|
|
|
#### `RTX_LOG_FILE=~/.rtx/rtx.log`
|
|
|
|
Output logs to a file.
|
|
|
|
#### `RTX_LOG_FILE_LEVEL=trace|debug|info|warn|error`
|
|
|
|
Same as `RTX_LOG_LEVEL` but for the log file output level. This is useful if you want
|
|
to store the logs but not have them litter your display.
|
|
|
|
#### `RTX_VERBOSE=1`
|
|
|
|
This shows the installation output during `rtx install` and `rtx plugin install`.
|
|
This should likely be merged so it behaves the same as `RTX_DEBUG=1` and we don't have
|
|
2 configuration for the same thing, but for now it is it's own config.
|
|
|
|
#### `RTX_ASDF_COMPAT=1`
|
|
|
|
Only output `.tool-versions` files in `rtx local|global` which will be usable by asdf.
|
|
|
|
#### `RTX_JOBS=1`
|
|
|
|
Set the number plugins or runtimes to install in parallel. The default is `4`.
|
|
|
|
#### `RTX_RAW=1`
|
|
|
|
Set to "1" to directly pipe plugin scripts to stdin/stdout/stderr. By default stdin is disabled
|
|
because when installing a bunch of plugins in parallel you won't see the prompt. Use this if a
|
|
plugin accepts input or otherwise does not seem to be installing correctly.
|
|
|
|
Sets `RTX_JOBS=1` because only 1 plugin script can be executed at a time.
|
|
|
|
#### `RTX_SHORTHANDS_FILE=~/.config/rtx/shorthands.toml`
|
|
|
|
Use a custom file for the shorthand aliases. This is useful if you want to share plugins within
|
|
an organization.
|
|
|
|
The file should be in toml format:
|
|
|
|
```toml
|
|
elixir = "https://github.com/my-org/rtx-elixir.git"
|
|
nodejs = "https://github.com/my-org/rtx-nodejs.git"
|
|
```
|
|
|
|
#### `RTX_DISABLE_DEFAULT_SHORTHANDS=1`
|
|
|
|
Disables the shorthand aliases for installing plugins. You will have to specify full urls when
|
|
installing plugins, e.g.: `rtx plugin install nodejs https://github.com/asdf-vm/asdf-nodejs.git`
|
|
|
|
Currently this disables the following:
|
|
|
|
* `--fuzzy` as default behavior (`rtx local nodejs@18` will save exact version)
|
|
|
|
#### `RTX_HIDE_OUTDATED_BUILD=1`
|
|
|
|
If a release is 12 months old, it will show a warning message every time it launches:
|
|
|
|
```
|
|
rtx has not been updated in over a year. Please update to the latest version.
|
|
```
|
|
|
|
You likely do not want to be using rtx if it is that old. I'm doing this instead of
|
|
autoupdating. If, for some reason, you want to stay on some old version, you can hide
|
|
this message with `RTX_HIDE_OUTDATED_BUILD=1`.
|
|
|
|
#### `RTX_EXPERIMENTAL=1`
|
|
|
|
Enables experimental features such as shims.
|
|
|
|
#### [experimental] `RTX_SHIMS_DIR=~/.local/share/rtx/shims`
|
|
|
|
Set a directory to output shims when running `rtx reshim`. Requires `experimental = true`.
|
|
|
|
## Aliases
|
|
|
|
rtx supports aliasing the versions of runtimes. One use-case for this is to define aliases for LTS
|
|
versions of runtimes. For example, you may want to specify `lts/hydrogen` as the version for nodejs@18.x.
|
|
So you can use the runtime with `nodejs lts/hydrogen` in `.tool-versions`.
|
|
|
|
User aliases can be created by adding an `alias.<PLUGIN>` section to `~/.config/rtx/config.toml`:
|
|
|
|
```toml
|
|
[alias.nodejs]
|
|
my_custom_18 = '18'
|
|
```
|
|
|
|
Plugins can also provide aliases via a `bin/list-aliases` script. Here is an example showing node.js
|
|
versions:
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
|
|
echo "lts/hydrogen 18"
|
|
echo "lts/gallium 16"
|
|
echo "lts/fermium 14"
|
|
```
|
|
|
|
> **Note:**
|
|
>
|
|
> Because this is rtx-specific functionality not currently used by asdf it isn't likely to be in any
|
|
> plugin currently, but plugin authors can add this script without impacting asdf users.
|
|
|
|
## Plugins
|
|
|
|
rtx uses asdf's plugin ecosystem under the hood. These plugins contain shell scripts like
|
|
`bin/install` (for installing) and `bin/list-all` (for listing all of the available versions).
|
|
|
|
See https://github.com/asdf-vm/asdf-plugins for the list of built-in plugins shorthands. See asdf's
|
|
[Create a Plugin](https://asdf-vm.com/plugins/create.html) for how to create your own or just learn
|
|
more about how they work.
|
|
|
|
## FAQs
|
|
|
|
### I don't want to put a `.tool-versions` file into my project since git shows it as an untracked file.
|
|
|
|
You can make git ignore these files in 3 different ways:
|
|
|
|
- Adding `.tool-versions` to project's `.gitignore` file. This has the downside that you need to commit the change to the ignore file.
|
|
- Adding `.tool-versions` to project's `.git/info/exclude`. This file is local to your project so there is no need to commit it.
|
|
- Adding `.tool-versions` to global gitignore (`core.excludesFile`). This will cause git to ignore `.tool-versions` files in all projects. You can explicitly add one to a project if needed with `git add --force .tool-versions`.
|
|
|
|
### How do I create my own plugin?
|
|
|
|
Just follow the [asdf docs](https://asdf-vm.com/plugins/create.html). Everything should work the same.
|
|
If it isn't, please open an issue.
|
|
|
|
### rtx is failing or not working right
|
|
|
|
First try setting `RTX_LOG_LEVEL=debug` or `RTX_LOG_LEVEL=trace` and see if that gives you more information.
|
|
You can also set `RTX_LOG_FILE=/path/to/logfile` to write the logs to a file.
|
|
|
|
If something is happening with the activate hook, you can try disabling it and calling `eval "$(rtx hook-env)"` manually.
|
|
It can also be helpful to use `rtx env` to see what environment variables it wants to use.
|
|
|
|
Lastly, there is an `rtx doctor` command. It doesn't have much in it but I hope to add more functionality
|
|
to that to help debug issues.
|
|
|
|
### Windows support?
|
|
|
|
This is something we'd like to add! https://github.com/jdxcode/rtx/discussions/66
|
|
|
|
It's not a near-term goal and it would require plugin modifications, but
|
|
it should be feasible.
|
|
|
|
## Commands
|
|
|
|
"#,
|
|
version = env!("CARGO_PKG_VERSION"),
|
|
about = cli.get_about().unwrap(),
|
|
));
|
|
for command in cli.get_subcommands_mut() {
|
|
match command.has_subcommands() {
|
|
true => {
|
|
let name = command.get_name().to_string();
|
|
for subcommand in command.get_subcommands_mut() {
|
|
if let Some(output) = render_command(Some(&name), subcommand) {
|
|
out.stdout.write(output);
|
|
}
|
|
}
|
|
}
|
|
false => {
|
|
if let Some(output) = render_command(None, command) {
|
|
out.stdout.write(output);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rtxprintln!(
|
|
out,
|
|
r#"
|
|
## Comparison to asdf
|
|
|
|
rtx is mostly a clone of asdf, but there are notable areas where improvements have been made.
|
|
|
|
### Performance
|
|
|
|
asdf made (what I consider) a poor design decision to use shims that go between a call to a runtime
|
|
and the runtime itself. e.g.: when you call `node` it will call an asdf shim file `~/.asdf/shims/node`,
|
|
which then calls `asdf exec`, which then calls the correct version of node.
|
|
|
|
These shims have terrible performance, adding ~120ms to every runtime call. rtx does not use shims and instead
|
|
updates `PATH` so that it doesn't have any overhead when simply calling binaries. These shims are the main reason that I wrote this. Note that in the demo gif at the top of this README
|
|
that `rtx` isn't actually used when calling `node -v` for this reason. The performance is
|
|
identical to running node without using rtx.
|
|
|
|
I don't think it's possible for asdf to fix these issues. The author of asdf did a great writeup
|
|
of [performance problems](https://stratus3d.com/blog/2022/08/11/asdf-performance/). asdf is written
|
|
in bash which certainly makes it challenging to be performant, however I think the real problem is the
|
|
shim design. I don't think it's possible to fix that without a complete rewrite.
|
|
|
|
rtx does call an internal command `rtx hook-env` every time the directory has changed, but because
|
|
it's written in Rust, this is very quick—taking ~10ms on my machine. 4ms if there are no changes, 14ms if it's
|
|
a full reload.
|
|
|
|
tl;dr: asdf adds overhead (~120ms) when calling a runtime, rtx adds a small amount of overhead (~10ms)
|
|
when the prompt loads.
|
|
|
|
### Environment variables
|
|
|
|
asdf only helps manage runtime executables. However, some tools are managed via environment variables
|
|
(notably Java which switches via `JAVA_HOME`). This isn't supported very well in asdf and requires
|
|
a separate shell extension just to manage.
|
|
|
|
However asdf _plugins_ have a `bin/exec-env` script that is used for exporting environment variables
|
|
like [`JAVA_HOME`](https://github.com/halcyon/asdf-java/blob/master/bin/exec-env). rtx simply exports
|
|
the environment variables from the `bin/exec-env` script in the plugin but places them in the shell
|
|
for _all_ commands. In asdf it only exports those commands when the shim is called. This means if you
|
|
call `java` it will set `JAVA_HOME`, but not if you call some Java tool like `mvn`.
|
|
|
|
This means we're just using the existing plugin script but because rtx doesn't use shims it can be
|
|
used for more things. It would be trivial to make a plugin that exports arbitrary environment
|
|
variables like [dotenv](https://github.com/motdotla/dotenv) or [direnv](https://github.com/direnv/direnv).
|
|
|
|
### UX
|
|
|
|
Some commands are the same in asdf but others have been changed. Everything that's possible
|
|
in asdf should be possible in rtx but may use slightly different syntax. rtx has more forgiving commands,
|
|
such as using fuzzy-matching, e.g.: `rtx install nodejs@18`. While in asdf you _can_ run
|
|
`asdf install nodejs latest:18`, you can't use `latest:18` in a `.tool-versions` file or many other places.
|
|
In `rtx` you can use fuzzy-matching everywhere.
|
|
|
|
asdf requires several steps to install a new runtime if the plugin isn't installed, e.g.:
|
|
|
|
```sh-session
|
|
$ asdf plugin add nodejs
|
|
$ asdf install nodejs latest:18
|
|
$ asdf local nodejs latest:18
|
|
```
|
|
|
|
In `rtx` this can all be done in a single step to set the local runtime version. If the plugin
|
|
and/or runtime needs to be installed it will prompt:
|
|
|
|
```sh-session
|
|
$ asdf local nodejs@18
|
|
rtx: Would you like to install nodejs@18.13.0? [Y/n] Y
|
|
Trying to update node-build... ok
|
|
Downloading node-v18.13.0-darwin-arm64.tar.gz...
|
|
-> https://nodejs.org/dist/v18.13.0/node-v18.13.0-darwin-arm64.tar.gz
|
|
Installing node-v18.13.0-darwin-arm64...
|
|
Installed node-v18.13.0-darwin-arm64 to /Users/jdx/.local/share/rtx/installs/nodejs/18.13.0
|
|
$ node -v
|
|
v18.13.0
|
|
```
|
|
|
|
I've found asdf to be particularly rigid and difficult to learn. It also made strange decisions like
|
|
having `asdf list all` but `asdf latest --all` (why is one a flag and one a positional argument?).
|
|
`rtx` makes heavy use of aliases so you don't need to remember if it's `rtx plugin add nodejs` or
|
|
`rtx plugin install nodejs`. If I can guess what you meant, then I'll try to get rtx to respond
|
|
in the right way.
|
|
|
|
That said, there are a lot of great things about asdf. It's the best multi-runtime manager out there
|
|
and I've really been impressed with the plugin system. Most of the design decisions the authors made
|
|
were very good. I really just have 2 complaints: the shims and the fact it's written in Bash.
|
|
|
|
### CI/CD
|
|
|
|
Using rtx in CI/CD is a great way to synchronize tool versions for dev/build.
|
|
|
|
### GitHub Actions
|
|
|
|
Use [`jdxcode/rtx-action`](https://github.com/jdxcode/rtx-action):
|
|
|
|
```yaml
|
|
- uses: jdxcode/rtx-action@v1
|
|
- run: node -v # will be the node version from `.tool-versions`
|
|
```
|
|
|
|
## Shims
|
|
|
|
While the PATH design of rtx works great in most cases, there are some situations where shims are
|
|
preferable. One example is when calling rtx binaries from an IDE.
|
|
|
|
To support this, there is experimental support for using rtx in a "shim" mode. To use:
|
|
|
|
```
|
|
$ rtx settings set experimental true
|
|
$ rtx settings set shim_dir ~/.rtx/shims
|
|
$ rtx i nodejs@18.0.0
|
|
$ rtx reshim
|
|
$ ~/.rtx/shims/node -v
|
|
v18.0.0
|
|
```
|
|
|
|
## direnv
|
|
|
|
[direnv](https://direnv.net) and rtx both manage environment variables based on directory. Because they both analyze
|
|
the current environment variables before and after their respective "hook" commands are run, they can conflict with each other.
|
|
As a result, there were a [number of issues with direnv](https://github.com/jdxcode/rtx/issues/8).
|
|
However, we think we've mitigated these. If you find that rtx and direnv are not working well together,
|
|
please comment on that ticket ideally with a good description of your directory layout so we can
|
|
reproduce the problem.
|
|
|
|
If there are remaining issues, they're likely to do with the ordering of PATH. This means it would
|
|
really only be a problem if you were trying to manage the same runtime with direnv and rtx. For example,
|
|
you may use `layout python` in an `.envrc` but also be maintaining a `.tool-versions` file with python
|
|
in it as well.
|
|
|
|
A more typical usage of direnv would be to set some arbitrary environment variables, or add unrelated
|
|
binaries to PATH. In these cases, rtx will not interfere with direnv.
|
|
|
|
As mentioned in the Quick Start, it is important to make sure that `rtx activate` is called after `direnv hook`
|
|
in the shell rc file. rtx overrides some of the internal direnv state (`DIRENV_DIFF`) so calling
|
|
direnv first gives rtx the opportunity to make those changes to direnv's state.
|
|
|
|
### rtx inside of direnv (`use rtx` in `.envrc`)
|
|
|
|
If you do encounter issues with `rtx activate`, or just want to use direnv in an alternate way,
|
|
this is a simpler setup that's less likely to cause issues.
|
|
|
|
To do this, first use `rtx` to build a `use_rtx` function that you can use in `.envrc` files:
|
|
|
|
```sh-session
|
|
$ rtx direnv activate > ~/.config/direnv/lib/use_rtx.sh
|
|
```
|
|
|
|
Now in your `.envrc` file add the following:
|
|
|
|
```sh-session
|
|
use rtx
|
|
```
|
|
|
|
direnv will now call rtx to export its environment variables. You'll need to make sure to add `use_rtx`
|
|
to all projects that use rtx (or use direnv's `source_up` to load it from a subdirectory). You can also add `use rtx` to `~/.config/direnv/direnvrc`.
|
|
|
|
Note that in this method direnv typically won't know to refresh `.tool-versions` files
|
|
unless they're at the same level as a `.envrc` file. You'll likely always want to have
|
|
a `.envrc` file next to your `.tool-versions` for this reason. To make this a little
|
|
easier to manage, I encourage _not_ actually using `.tool-versions` at all, and instead
|
|
setting environment variables entirely in `.envrc`:
|
|
|
|
```
|
|
export RTX_NODEJS_VERSION=18.0.0
|
|
export RTX_PYTHON_VERSION=3.11
|
|
```
|
|
|
|
Of course if you use `rtx activate`, then these steps won't have been necessary and you can use rtx
|
|
as if direnv was not used.
|
|
|
|
## Cache Behavior
|
|
|
|
rtx makes use of caching in many places in order to be efficient. The details about how long to keep
|
|
cache for should eventually all be configurable. There may be gaps in the current behavior where
|
|
things are hardcoded but I'm happy to add more settings to cover whatever config is needed.
|
|
|
|
Below I explain the behavior it uses around caching. If you're seeing behavior where things don't appear
|
|
to be updating, this is a good place to start.
|
|
|
|
### Plugin Cache
|
|
|
|
Each plugin has a cache that's stored in `~/$RTX_CACHE_DIR/plugins/<PLUGIN>`. It stores
|
|
the list of versions available for that plugin (`rtx ls-remote <PLUGIN>`), the legacy filenames (see below),
|
|
the list of aliases, and the bin directories within each runtime installation.
|
|
|
|
Remote versions are updated daily by default or anytime that `rtx ls-remote` is called explicitly. The file is
|
|
zlib messagepack, if you want to view it you can run the following (requires [msgpack-cli](https://github.com/msgpack/msgpack-cli)).
|
|
|
|
```sh-session
|
|
cat ~/$RTX_CACHE_DIR/nodejs/remote_versions.msgpack.zlib | perl -e 'use Compress::Raw::Zlib;my $d=new Compress::Raw::Zlib::Inflate();my $o;undef $/;$d->inflate(<>,$o);print $o;' | msgpack-cli decode
|
|
```
|
|
|
|
### Legacy File Cache
|
|
|
|
If enabled, rtx will read the legacy filenames such as `.node-version` for
|
|
[asdf-nodejs](https://github.com/asdf-vm/asdf-nodejs). This leverages cache in 2 places where the
|
|
plugin is called:
|
|
|
|
- [`list-legacy-filenames`](https://github.com/asdf-vm/asdf-nodejs/blob/master/bin/list-legacy-filenames)
|
|
In every plugin I've seen this simply returns a static list of filenamed like ".nvmrc .node-version".
|
|
It's cached alongside the standard "runtime" cache which is refreshed daily by default.
|
|
- [`parse-legacy-file`](https://github.com/asdf-vm/asdf-nodejs/blob/master/bin/parse-legacy-file)
|
|
This plugin binary is called to parse a legacy file to get the version out of it. It's relatively
|
|
expensive so every file that gets parsed as a legacy file is cached into `~/.local/share/rtx/legacy_cache`.
|
|
It will remain cached until the file is modified. This is a simple text file that has the path to the
|
|
legacy file stored as a hash for the filename.
|
|
|
|
## Development
|
|
|
|
Run tests with `just`:
|
|
|
|
```sh-session
|
|
$ just test
|
|
```
|
|
|
|
Lint the codebase with:
|
|
|
|
```sh-session
|
|
$ just lint-fix
|
|
```
|
|
"#
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn render_command(parent: Option<&str>, c: &mut clap::Command) -> Option<String> {
|
|
let mut c = c.clone().disable_help_flag(true);
|
|
if c.is_hide_set() {
|
|
return None;
|
|
}
|
|
let name = match parent {
|
|
Some(p) => format!("{} {}", p, c.get_name()),
|
|
None => c.get_name().to_string(),
|
|
};
|
|
Some(formatdoc!(
|
|
"
|
|
### `rtx {name}`
|
|
|
|
```
|
|
{about}
|
|
```
|
|
",
|
|
name = name,
|
|
about = strip_ansi_codes(&c.render_long_help().to_string()).trim(),
|
|
))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::assert_cli;
|
|
|
|
#[test]
|
|
fn test_render_help() {
|
|
let stdout = assert_cli!("render-help");
|
|
assert!(stdout.contains("Quickstart"));
|
|
}
|
|
}
|