Skip to content

Commit

Permalink
feat: rust in core
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx committed Nov 27, 2024
1 parent 424a8f8 commit b01f730
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 40 deletions.
61 changes: 25 additions & 36 deletions docs/lang/rust.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,40 @@
# Rust

Rust is not currently offered as a core plugin. In fact, I don't think you
should actually use mise for rust development. Rust has an official version
manager called [`rustup`](https://rustup.rs/) that is better than what any of
the current mise plugins offer.
Rust/cargo can be installed which uses rustup under the hood. mise will install rustup if it is not
already installed and add the requested targets. By default, mise uses the default location of rustup/cargo
(`~/.rustup` and `~/.cargo`), but you can change this by setting the `MISE_RUSTUP_HOME` and `MISE_CARGO_HOME`
environment variables if you'd like to isolate mise's rustup/cargo from your other rustup/cargo installations.

You install [rustup](https://rustup.rs/) with the following:
Unlike most tools, these won't exist inside of `~/.local/share/mise/installs` because they are managed by rustup.
All mise does is set the `RUST_TOOLCHAIN` environment variable to the requested version and rustup will
automatically install it if it doesn't exist.

```sh
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```

That said, rust is still one of the most popular languages to use in mise.
A lot of users have success with it so if you'd like to keep all of your
languages configured the same, don't feel like using mise is a bad idea either. Especially if you're only a casual rust user.
## Usage

If you're a relatively heavy rust user making use of things like channel
overrides, components, and cross-compiling, then I think you really should
just be using rustup though. The experience will be better.
Use the latest stable version of rust:

If one day we could figure out a way to provide an equivalent experience with
mise, we could revisit this. We have discussed potentially using mise as a
"front-end" to rustup where there is one rustup install that mise just manages
so you could do something like this:

```toml
[tools]
rust = "nightly"
```sh
mise use -g rust
cargo build
```

Where that would basically be equivalent to:
Use the latest beta version of rust:

```sh
rustup override set nightly
mise use -g rust@beta
cargo build
```

Frankly though, this isn't high on my priority list. Use rustup. It's great.
Use a specific version of rust:

Kudos for writing rust too btw, I've really enjoyed it so far—this is my first rust project.

## Default crates
```sh
mise use -g rust@1.82
cargo build
```

mise can automatically install a default set of creates right after installing a new rust version.
To enable this feature, provide a `$HOME/.default-cargo-crates` file that lists one crate per line, for
example:
## Settings

```text
cargo-edit
stylua
```
<script setup>
import Settings from '/components/settings.vue';
</script>
<Settings child="rust" :level="3" />
2 changes: 1 addition & 1 deletion docs/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ editLink: false
| rstash | [asdf:carlduevel/asdf-rstash](https://github.com/carlduevel/asdf-rstash) |
| ruby | [core:ruby](https://mise.jdx.dev/lang/ruby.html) |
| ruff | [ubi:astral-sh/ruff](https://github.com/astral-sh/ruff) [asdf:simhem/asdf-ruff](https://github.com/simhem/asdf-ruff) |
| rust | [asdf:code-lever/asdf-rust](https://github.com/code-lever/asdf-rust) |
| rust | [core:rust](https://mise.jdx.dev/lang/rust.html) [asdf:code-lever/asdf-rust](https://github.com/code-lever/asdf-rust) |
| rust-analyzer | [aqua:rust-lang/rust-analyzer](https://github.com/rust-lang/rust-analyzer) [asdf:Xyven1/asdf-rust-analyzer](https://github.com/Xyven1/asdf-rust-analyzer) |
| rustic | [ubi:rustic-rs/rustic](https://github.com/rustic-rs/rustic) |
| rye | [aqua:astral-sh/rye](https://github.com/astral-sh/rye) [asdf:Azuki-bar/asdf-rye](https://github.com/Azuki-bar/asdf-rye) |
Expand Down
6 changes: 6 additions & 0 deletions e2e/plugins/core/test_rust
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

export MISE_RUSTUP_HOME="$MISE_DATA_DIR/rustup"
export MISE_CARGO_HOME="$MISE_DATA_DIR/cargo"

assert_contains "mise x rust@1.82.0 -- rustc --version" "rustc 1.82.0"
2 changes: 1 addition & 1 deletion registry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ rome.backends = ["asdf:kichiemon/asdf-rome"]
rstash.backends = ["asdf:carlduevel/asdf-rstash"]
ruby.backends = ["core:ruby"]
ruff.backends = ["ubi:astral-sh/ruff", "asdf:simhem/asdf-ruff"]
rust.backends = ["asdf:code-lever/asdf-rust"]
rust.backends = ["core:rust", "asdf:code-lever/asdf-rust"]
rust-analyzer.backends = ["aqua:rust-lang/rust-analyzer", "asdf:Xyven1/asdf-rust-analyzer"]
rustic.backends = ["ubi:rustic-rs/rustic"]
rye.backends = ["aqua:astral-sh/rye", "asdf:Azuki-bar/asdf-rye"]
Expand Down
4 changes: 4 additions & 0 deletions rustup/settings.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
profile = "default"
version = "12"

[overrides]
13 changes: 13 additions & 0 deletions schema/mise.json
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,19 @@
}
}
},
"rust": {
"additionalProperties": false,
"properties": {
"cargo_home": {
"description": "Path to the cargo home directory. Defaults to ~/.cargo or %USERPROFILE%\\.cargo",
"type": "string"
},
"rustup_home": {
"description": "Path to the rustup home directory. Defaults to ~/.rustup or %USERPROFILE%\\.rustup",
"type": "string"
}
}
},
"shorthands_file": {
"description": "Path to a file containing custom tool shorthands.",
"type": "string"
Expand Down
12 changes: 12 additions & 0 deletions settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,18 @@ type = "Bool"
optional = true
description = "Set to true to enable verbose output during ruby installation."

[rust.cargo_home]
env = "MISE_CARGO_HOME"
type = "Path"
optional = true
description = "Path to the cargo home directory. Defaults to ~/.cargo or %USERPROFILE%\\.cargo"

[rust.rustup_home]
env = "MISE_RUSTUP_HOME"
type = "Path"
optional = true
description = "Path to the rustup home directory. Defaults to ~/.rustup or %USERPROFILE%\\.rustup"

[shorthands_file]
env = "MISE_SHORTHANDS_FILE"
type = "Path"
Expand Down
2 changes: 0 additions & 2 deletions src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,6 @@ pub fn make_executable<P: AsRef<Path>>(path: P) -> Result<()> {

#[cfg(windows)]
pub fn make_executable<P: AsRef<Path>>(path: P) -> Result<()> {
trace!("chmod +x {}", display_path(&path));
warn!("make executable is not available on Windows, use windows_executable_extensions settings instead");
Ok(())
}

Expand Down
4 changes: 4 additions & 0 deletions src/plugins/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::plugins::core::go::GoPlugin;
use crate::plugins::core::java::JavaPlugin;
use crate::plugins::core::node::NodePlugin;
use crate::plugins::core::ruby::RubyPlugin;
use crate::plugins::core::rust::RustPlugin;
#[cfg(unix)]
use crate::plugins::core::zig::ZigPlugin;
use crate::timeout::run_with_timeout;
Expand All @@ -33,6 +34,7 @@ mod node;
mod python;
#[cfg_attr(windows, path = "ruby_windows.rs")]
mod ruby;
mod rust;
#[cfg(unix)]
mod zig;

Expand All @@ -47,6 +49,7 @@ pub static CORE_PLUGINS: Lazy<BackendMap> = Lazy::new(|| {
Arc::new(NodePlugin::new()),
Arc::new(PythonPlugin::new()),
Arc::new(RubyPlugin::new()),
Arc::new(RustPlugin::new()),
Arc::new(ZigPlugin::new()),
];
#[cfg(windows)]
Expand All @@ -59,6 +62,7 @@ pub static CORE_PLUGINS: Lazy<BackendMap> = Lazy::new(|| {
Arc::new(NodePlugin::new()),
Arc::new(PythonPlugin::new()),
Arc::new(RubyPlugin::new()),
Arc::new(RustPlugin::new()),
// Arc::new(ZigPlugin::new()),
];
plugins
Expand Down
176 changes: 176 additions & 0 deletions src/plugins/core/rust.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use eyre::Result;

use crate::backend::Backend;
use crate::cli::args::BackendArg;
use crate::cmd::CmdLineRunner;
use crate::config::{Config, CONFIG, SETTINGS};
use crate::http::HTTP;
use crate::install_context::InstallContext;
use crate::toolset::{ToolVersion, Toolset};
use crate::{dirs, file, github, plugins};

#[derive(Debug)]
pub struct RustPlugin {
ba: BackendArg,
}

impl RustPlugin {
pub fn new() -> Self {
Self {
ba: plugins::core::new_backend_arg("rust"),
}
}

fn setup_rustup(&self, ctx: &InstallContext) -> Result<()> {
if rustup_home().join("settings.toml").exists() && cargo_bin().exists() {
return Ok(());
}
ctx.pr.set_message("Downloading rustup-init".into());
HTTP.download_file(
"https://sh.rustup.rs",
&rustup_path(),
Some(ctx.pr.as_ref()),
)?;
file::make_executable(rustup_path())?;
file::create_dir_all(rustup_home())?;
let cmd = CmdLineRunner::new(rustup_path())
.with_pr(ctx.pr.as_ref())
.arg("--no-modify-path")
.arg("--default-toolchain")
.arg("none")
.arg("-y")
.env("RUSTUP_HOME", rustup_home())
.env("CARGO_HOME", cargo_home());
cmd.execute()?;
Ok(())
}

fn test_rust(&self, ctx: &InstallContext, tv: &ToolVersion) -> Result<()> {
ctx.pr.set_message(format!("{RUSTC_BIN} -V"));
CmdLineRunner::new(rustc_bin())
.with_pr(ctx.pr.as_ref())
.arg("-V")
.envs(self.exec_env(&CONFIG, CONFIG.get_toolset()?, tv)?)
.execute()
}
}

impl Backend for RustPlugin {
fn ba(&self) -> &BackendArg {
&self.ba
}

fn _list_remote_versions(&self) -> Result<Vec<String>> {
let versions = github::list_releases("rust-lang/rust")?
.into_iter()
.map(|r| r.tag_name)
.rev()
.chain(vec!["nightly".into(), "beta".into(), "stable".into()])
.collect();
Ok(versions)
}

fn idiomatic_filenames(&self) -> Result<Vec<String>> {
Ok(vec!["rust-toolchain.toml".into()])
}

fn parse_idiomatic_file(&self, path: &Path) -> Result<String> {
let toml = file::read_to_string(path)?;
let toml = toml.parse::<toml::Value>()?;
if let Some(toolchain) = toml.get("toolchain") {
if let Some(channel) = toolchain.get("channel") {
return Ok(channel.to_string());
}
}
Ok("".into())
}

fn install_version_impl(&self, ctx: &InstallContext, tv: ToolVersion) -> Result<ToolVersion> {
self.setup_rustup(ctx)?;

CmdLineRunner::new(rustup_bin())
.with_pr(ctx.pr.as_ref())
.arg("toolchain")
.arg("install")
.arg(&tv.version)
.execute()?;

self.test_rust(ctx, &tv)?;

Ok(tv)
}

fn list_bin_paths(&self, _tv: &ToolVersion) -> Result<Vec<PathBuf>> {
Ok(vec![cargo_bindir()])
}

fn exec_env(
&self,
_config: &Config,
_ts: &Toolset,
tv: &ToolVersion,
) -> Result<BTreeMap<String, String>> {
Ok([("RUSTUP_TOOLCHAIN".to_string(), tv.version.to_string())].into())
}
}

#[cfg(unix)]
const RUSTC_BIN: &str = "rustc";

#[cfg(windows)]
const RUSTC_BIN: &str = "rustc.exe";

#[cfg(unix)]
const RUSTUP_INIT_BIN: &str = "rustup-init";

#[cfg(windows)]
const RUSTUP_INIT_BIN: &str = "rustup-init.exe";

#[cfg(unix)]
const RUSTUP_BIN: &str = "rustup";

#[cfg(windows)]
const RUSTUP_BIN: &str = "rustup.exe";

#[cfg(unix)]
const CARGO_BIN: &str = "cargo";

#[cfg(windows)]
const CARGO_BIN: &str = "cargo.exe";

fn rustup_path() -> PathBuf {
dirs::CACHE.join("rust").join(RUSTUP_INIT_BIN)
}

fn rustup_home() -> PathBuf {
SETTINGS
.rust
.rustup_home
.clone()
.unwrap_or(dirs::HOME.join(".rustup"))
}

fn rustup_bin() -> PathBuf {
cargo_bindir().join(RUSTUP_BIN)
}

fn cargo_home() -> PathBuf {
SETTINGS
.rust
.cargo_home
.clone()
.unwrap_or(dirs::HOME.join(".cargo"))
}

fn cargo_bin() -> PathBuf {
cargo_bindir().join(CARGO_BIN)
}
fn cargo_bindir() -> PathBuf {
cargo_home().join("bin")
}
fn rustc_bin() -> PathBuf {
cargo_bindir().join(RUSTC_BIN)
}

0 comments on commit b01f730

Please sign in to comment.