Skip to content

Commit

Permalink
Merge pull request #15 from LuuuXXX/luuuxxx
Browse files Browse the repository at this point in the history
支持 Rust 工具链在线安装
  • Loading branch information
J-ZhengLi authored Jul 27, 2024
2 parents 1e407c4 + 6b42def commit d275f4c
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 10 deletions.
18 changes: 14 additions & 4 deletions src/cli/install.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Separated module to handle installation related behaviors in command line.
use std::collections::BTreeMap;

use crate::{
core::{InstallConfiguration, Installation},
utils,
core::manifest::RustToolchain, core::manifest::ToolsetManifest, core::InstallConfiguration,
core::Installation, rustup::Rustup, utils,
};

use super::{GlobalOpt, Subcommands};
Expand Down Expand Up @@ -38,8 +40,16 @@ pub(super) fn execute(subcommand: &Subcommands, _opt: GlobalOpt) -> Result<()> {
config.init()?;
config.config_rustup_env_vars()?;
config.config_cargo()?;
// TODO: download rustup then install
// TODO: install rust toolchian via rustup

// TODO: Download manifest form remote server.
let manifest = ToolsetManifest {
rust: RustToolchain::new("stable"),
target: BTreeMap::default(),
tools: BTreeMap::default(),
};

Rustup::init().download_toolchain(&config, &manifest)?;

// TODO: install third-party tools via cargo that got installed by rustup

unimplemented!("`install` is not fully yet implemented.")
Expand Down
2 changes: 1 addition & 1 deletion src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Including configuration, toolchain, toolset management.
mod cargo_config;
mod manifest;
pub mod manifest;
mod os;

use std::path::{Path, PathBuf};
Expand Down
61 changes: 57 additions & 4 deletions src/rustup.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use std::path::Path;
use std::path::PathBuf;

use anyhow::{Context, Result};

use crate::core::manifest::ToolsetManifest;
use crate::core::InstallConfiguration;
use crate::utils::cli::download_from_start;
use crate::utils::cmd_output;
use crate::utils::cmd_output_with_input;
use crate::utils::create_executable_file;
use crate::utils::HostTriple;

// FIXME: remove this `allow` before 0.1.0 release.
Expand All @@ -15,6 +21,11 @@ const RUSTUP_INIT: &str = "rustup-init.exe";
#[cfg(not(windows))]
const RUSTUP_INIT: &str = "rustup-init";

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

pub struct Rustup {
triple: HostTriple,
}
Expand All @@ -32,16 +43,58 @@ impl Default for Rustup {
}

impl Rustup {
pub fn new() -> Self {
pub fn init() -> Self {
Self::default()
}

pub fn download(&self, dest: &Path) -> Result<()> {
fn download_rustup_init(&self, dest: &Path) -> Result<()> {
let download_url = url::Url::parse(&format!(
"{}/{}/{}/{}",
RUSTUP_UPDATE_ROOT, "dist", self.triple, RUSTUP_INIT
))
.context("Failed to init rustup download url")?;
download_from_start(RUSTUP_INIT, &download_url, dest).context("Failed to download rustup")
.context("Failed to init rustup download url.")?;
download_from_start(RUSTUP_INIT, &download_url, dest).context("Failed to download rustup.")
}

fn generate_rustup(&self, rustup_init: &PathBuf) -> Result<()> {
let args = ["--default-toolchain", "none"];
let input = b"\n";
cmd_output_with_input(rustup_init, &args, input)
}

fn download_rust_toolchain(&self, rustup: &Path, manifest: &ToolsetManifest) -> Result<()> {
// TODO: check local manifest.
let version = manifest.rust.version.clone();
let args = ["toolchain", "install", &version, "--no-self-update"];
cmd_output(rustup, &args)
}

fn download_rust_component(&self, rustup: &Path, compoent: &String) -> Result<()> {
let args = ["component", "add", compoent];
cmd_output(rustup, &args)
}

pub(crate) fn download_toolchain(
&self,
config: &InstallConfiguration,
manifest: &ToolsetManifest,
) -> Result<()> {
let rustup_init = config.install_dir.join(RUSTUP_INIT);
// Download rustup-init.
self.download_rustup_init(&rustup_init)?;
// File permission
create_executable_file(&rustup_init)?;
// Install rustup.
self.generate_rustup(&rustup_init)?;
// Install rust toolchain via rustup.
let rustup = config.cargo_home().join("bin").join(RUSTUP);
self.download_rust_toolchain(&rustup, manifest)?;
// Install extral rust component via rustup.
if let Some(compoents) = &manifest.rust.components {
for cpt in compoents {
self.download_rust_component(&rustup, cpt)?;
}
}
Ok(())
}
}
18 changes: 18 additions & 0 deletions src/utils/file_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,21 @@ where
fs::copy(from, dest)?;
Ok(())
}

/// Set file permissions (executable)
/// rwxr-xr-x: 0o755
#[cfg(not(windows))]
pub fn create_executable_file(path: &Path) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
// 设置文件权限为可执行
let metadata = std::fs::metadata(path)?;
let mut permissions = metadata.permissions();
permissions.set_mode(0o755); // rwxr-xr-x
std::fs::set_permissions(path, permissions)?;
Ok(())
}

#[cfg(windows)]
pub fn create_executable_file(path: &str, content: &str) -> Result<()> {
Ok(())
}
65 changes: 64 additions & 1 deletion src/utils/process.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::env;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::io::Write;
use std::process::{Command, Output};
use std::process::{Command, Output, Stdio};

use anyhow::{Context, Result};

Expand Down Expand Up @@ -91,3 +92,65 @@ pub fn cmd_exist(cmd: &str) -> bool {
.map(|p| p.join(&cmd))
.any(|p| p.exists())
}

/// Execute a command as child process, wait for it to finish then collect its std output.
pub fn cmd_output<P, A>(program: P, args: &[A]) -> Result<()>
where
P: AsRef<OsStr> + Debug,
A: AsRef<OsStr>,
{
let child = Command::new(program.as_ref())
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap_or_else(|_| panic!("Failed to spawn {:?} process.", program));

let output = child.wait_with_output().expect("Failed to read stdout");

// 检查子进程的退出状态
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(anyhow::anyhow!(
"{:?} failed with error: {}",
program,
stderr
));
}

Ok(())
}

/// Execute a command as child process with input, wait for it to finish then collect its std output.
pub fn cmd_output_with_input<P, A>(program: P, args: &[A], input: &[u8]) -> Result<()>
where
P: AsRef<OsStr> + Debug,
A: AsRef<OsStr>,
{
let mut child = Command::new(program.as_ref())
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap_or_else(|_| panic!("Failed to spawn {:?} process.", program));

if let Some(mut stdin) = child.stdin.take() {
stdin
.write_all(input)
.unwrap_or_else(|_| panic!("Failed to spawn {:?} process.", program))
}

let output = child.wait_with_output().expect("Failed to read stdout");

// 检查子进程的退出状态
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(anyhow::anyhow!(
"{:?} failed with error: {}",
program,
stderr
));
}

Ok(())
}

0 comments on commit d275f4c

Please sign in to comment.