diff --git a/Cargo.lock b/Cargo.lock index bd5fd57..65c2644 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,6 +409,7 @@ dependencies = [ "openssl", "reqwest", "strum", + "strum_macros", "tar", "tempfile", "xz2", diff --git a/Cargo.toml b/Cargo.toml index 2cb5d71..f8c1cbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ log = "0.4.17" env_logger = "0.9.0" embuild = { version = "0.30.4", features = ["espidf", "git"] } strum = { version = "0.24", features = ["derive"] } +strum_macros = "0.24.3" [target.aarch64-unknown-linux-gnu.dependencies] openssl = { version = "0.10", features = ["vendored"] } diff --git a/src/host_triple.rs b/src/host_triple.rs new file mode 100644 index 0000000..44e533f --- /dev/null +++ b/src/host_triple.rs @@ -0,0 +1,42 @@ +use crate::emoji; +use anyhow::{Context, Result}; +use guess_host_triple::guess_host_triple; +use std::str::FromStr; +use strum::Display; +use strum_macros::EnumString; + +#[derive(Display, Debug, Clone, EnumString)] +pub enum HostTriple { + /// 64-bit Linux + #[strum(serialize = "x86_64-unknown-linux-gnu")] + X86_64UnknownLinuxGnu = 0, + /// ARM64 Linux + #[strum(serialize = "aarch64-unknown-linux-gnu")] + Aarch64UnknownLinuxGnu, + /// 64-bit MSVC + #[strum(serialize = "x86_64-pc-windows-msvc")] + X86_64PcWindowsMsvc, + /// 64-bit MinGW + #[strum(serialize = "x86_64-pc-windows-gnu")] + X86_64PcWindowsGnu, + /// 64-bit macOS + #[strum(serialize = "x86_64-apple-darwin")] + X86_64AppleDarwin, + /// ARM64 macOS + #[strum(serialize = "aarch64-apple-darwin")] + Aarch64AppleDarwin, +} + +/// Parse the host triple if specified, otherwise guess it. +pub fn get_host_triple(host_triple_arg: Option) -> Result { + if let Some(host_triple_arg) = host_triple_arg { + HostTriple::from_str(&host_triple_arg).context(format!( + "{} Host triple '{}' is not supported.", + emoji::ERROR, + host_triple_arg, + )) + } else { + HostTriple::from_str(guess_host_triple().unwrap()) + .context(format!("{} Unable to guess host triple.", emoji::ERROR,)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8c9c467..106ebee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod emoji; +pub mod host_triple; pub mod targets; pub mod toolchain; pub mod logging { diff --git a/src/main.rs b/src/main.rs index 3377c71..1d8dfbf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use clap::Parser; use embuild::espidf::{parse_esp_idf_git_ref, EspIdfRemote}; use espup::{ emoji, + host_triple::get_host_triple, logging::initialize_logger, targets::{parse_targets, Target}, toolchain::{ @@ -56,6 +57,9 @@ pub enum SubCommand { #[derive(Debug, Parser)] pub struct InstallOpts { + /// Target triple of the host. + #[arg(short = 'd', long, required = false)] + pub default_host: Option, /// ESP-IDF version to install. If empty, no esp-idf is installed. Version format: /// /// - `commit:`: Uses the commit `` of the `esp-idf` repository. @@ -95,6 +99,9 @@ pub struct InstallOpts { #[derive(Debug, Parser)] pub struct UpdateOpts { + /// Target triple of the host. + #[arg(short = 'd', long, required = false)] + pub default_host: Option, /// Verbosity level of the logs. #[arg(short = 'l', long, default_value = "info", value_parser = ["debug", "info", "warn", "error"])] pub log_level: String, @@ -132,20 +139,22 @@ fn install(args: InstallOpts) -> Result<()> { info!("{} Installing esp-rs", emoji::DISC); let targets: HashSet = parse_targets(&args.targets).unwrap(); + let host_triple = get_host_triple(args.default_host)?; let mut extra_crates: HashSet = args.extra_crates.split(',').map(RustCrate::new).collect(); let mut exports: Vec = Vec::new(); let export_file = args.export_file.clone(); - let rust_toolchain = RustToolchain::new(args.toolchain_version.clone()); + let rust_toolchain = RustToolchain::new(&args.toolchain_version, &host_triple); // Complete LLVM is failing for Windows, aarch64 MacOs, and aarch64 Linux, so we are using always minified. #[cfg(all(target_arch = "x86_64", target_os = "linux"))] - let llvm = LlvmToolchain::new(args.profile_minimal); + let llvm = LlvmToolchain::new(args.profile_minimal, &host_triple); #[cfg(any(not(target_arch = "x86_64"), not(target_os = "linux")))] - let llvm = LlvmToolchain::new(true); + let llvm = LlvmToolchain::new(true, &host_triple); debug!( "{} Arguments: + - Host triple: {} - Targets: {:?} - ESP-IDF version: {:?} - Export file: {:?} @@ -156,6 +165,7 @@ fn install(args: InstallOpts) -> Result<()> { - Profile Minimal: {:?} - Toolchain version: {:?}", emoji::INFO, + host_triple, targets, &args.espidf_version, export_file, @@ -185,7 +195,7 @@ fn install(args: InstallOpts) -> Result<()> { exports.extend(repo.install()?); extra_crates.insert(RustCrate::new("ldproxy")); } else { - exports.extend(install_gcc_targets(targets)?); + exports.extend(install_gcc_targets(targets, &host_triple)?); } debug!( @@ -252,18 +262,21 @@ fn uninstall(args: UninstallOpts) -> Result<()> { fn update(args: UpdateOpts) -> Result<()> { initialize_logger(&args.log_level); info!("{} Updating Xtensa Rust toolchain", emoji::DISC); + let host_triple = get_host_triple(args.default_host)?; debug!( "{} Arguments: + - Host triple: {} - Toolchain version: {}", emoji::INFO, + host_triple, &args.toolchain_version, ); info!("{} Deleting previous Xtensa Rust toolchain", emoji::WRENCH); remove_dir_all(get_rustup_home().join("toolchains").join("esp"))?; - let rust_toolchain = RustToolchain::new(args.toolchain_version); + let rust_toolchain = RustToolchain::new(&args.toolchain_version, &host_triple); rust_toolchain.install_xtensa_rust()?; info!("{} Update suscesfully completed!", emoji::CHECK); diff --git a/src/targets.rs b/src/targets.rs index 6a39e6d..d1f8434 100644 --- a/src/targets.rs +++ b/src/targets.rs @@ -1,12 +1,13 @@ //! ESP32 chip variants support. use crate::emoji; +use anyhow::Context; use log::debug; use std::{collections::HashSet, str::FromStr}; use strum::Display; +use strum_macros::EnumString; -#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, Display)] - +#[derive(Clone, Copy, EnumString, PartialEq, Hash, Eq, Debug, Display)] pub enum Target { /// Xtensa LX7 based dual core #[strum(serialize = "esp32")] @@ -22,20 +23,6 @@ pub enum Target { ESP32C3, } -impl FromStr for Target { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "esp32" => Ok(Target::ESP32), - "esp32s2" => Ok(Target::ESP32S2), - "esp32s3" => Ok(Target::ESP32S3), - "esp32c3" => Ok(Target::ESP32C3), - _ => Err(()), - } - } -} - /// Returns a vector of Chips from a comma or space separated string. pub fn parse_targets(targets_str: &str) -> Result, String> { debug!("{} Parsing targets: {}", emoji::DEBUG, targets_str); @@ -54,7 +41,15 @@ pub fn parse_targets(targets_str: &str) -> Result, String> { }; for target in targets_str { - targets.insert(FromStr::from_str(target).unwrap()); + targets.insert( + Target::from_str(target) + .context(format!( + "{} Target '{}' is not supported", + emoji::ERROR, + target + )) + .unwrap(), + ); } debug!("{} Parsed targets: {:?}", emoji::DEBUG, targets); Ok(targets) diff --git a/src/toolchain/gcc_toolchain.rs b/src/toolchain/gcc_toolchain.rs index a1e1ef6..39928d7 100644 --- a/src/toolchain/gcc_toolchain.rs +++ b/src/toolchain/gcc_toolchain.rs @@ -2,6 +2,7 @@ use crate::{ emoji, + host_triple::HostTriple, targets::Target, toolchain::{download_file, espidf::get_tool_path}, }; @@ -16,14 +17,16 @@ const DEFAULT_GCC_VERSION: &str = "8_4_0"; #[derive(Debug)] pub struct GccToolchain { - /// The repository containing GCC sources. - pub repository_url: String, + /// Host triple. + pub host_triple: HostTriple, /// Repository release version to use. pub release: String, - /// GCC Version. - pub version: String, + /// The repository containing GCC sources. + pub repository_url: String, /// GCC Toolchain target. pub toolchain_name: String, + /// GCC Version. + pub version: String, } impl GccToolchain { @@ -41,15 +44,14 @@ impl GccToolchain { let target_dir = format!("{}/{}-{}", self.toolchain_name, self.release, self.version); let gcc_path = get_tool_path(&target_dir); - let host_triple = guess_host_triple::guess_host_triple().unwrap(); - let extension = get_artifact_extension(host_triple); + let extension = get_artifact_extension(&self.host_triple); debug!("{} GCC path: {}", emoji::DEBUG, gcc_path); let gcc_file = format!( "{}-gcc{}-{}-{}.{}", self.toolchain_name, self.version, self.release, - get_arch(host_triple).unwrap(), + get_arch(&self.host_triple).unwrap(), extension ); let gcc_dist_url = format!("{}/{}/{}", self.repository_url, self.release, gcc_file); @@ -63,35 +65,32 @@ impl GccToolchain { } /// Create a new instance with default values and proper toolchain name. - pub fn new(target: Target) -> Self { + pub fn new(target: Target, host_triple: &HostTriple) -> Self { Self { - repository_url: DEFAULT_GCC_REPOSITORY.to_string(), + host_triple: host_triple.clone(), release: DEFAULT_GCC_RELEASE.to_string(), - version: DEFAULT_GCC_VERSION.to_string(), + repository_url: DEFAULT_GCC_REPOSITORY.to_string(), toolchain_name: get_toolchain_name(target), + version: DEFAULT_GCC_VERSION.to_string(), } } } /// Gets the name of the GCC arch based on the host triple. -fn get_arch(host_triple: &str) -> Result<&str, String> { +fn get_arch(host_triple: &HostTriple) -> Result<&str> { match host_triple { - "aarch64-apple-darwin" | "x86_64-apple-darwin" => Ok("macos"), - "aarch64-unknown-linux-gnu" => Ok("linux-arm64"), - "x86_64-unknown-linux-gnu" => Ok("linux-amd64"), - "x86_64-pc-windows-msvc" | "x86_64-pc-windows-gnu" => Ok("win64"), - _ => Err(format!( - "No GCC arch found for the host triple: {}", - host_triple - )), + HostTriple::Aarch64AppleDarwin | HostTriple::X86_64AppleDarwin => Ok("macos"), + HostTriple::X86_64UnknownLinuxGnu => Ok("linux-amd64"), + HostTriple::Aarch64UnknownLinuxGnu => Ok("linux-arm64"), + HostTriple::X86_64PcWindowsMsvc | HostTriple::X86_64PcWindowsGnu => Ok("win64"), } } -/// Gets the artifact extension based on the host architecture. -fn get_artifact_extension(host_triple: &str) -> &str { +/// Gets the artifact extension based on the host triple. +fn get_artifact_extension(host_triple: &HostTriple) -> &str { match host_triple { - "x86_64-pc-windows-msvc" | "x86_64-pc-windows-gnu" => "zip", - _ => "tar.gz", + HostTriple::X86_64PcWindowsMsvc | HostTriple::X86_64PcWindowsGnu => "zip", + _ => "tar.xz", } } @@ -128,11 +127,14 @@ pub fn get_ulp_toolchain_name(target: Target, version: Option<&EspIdfVersion>) - } /// Installs GCC toolchain the selected targets. -pub fn install_gcc_targets(targets: HashSet) -> Result> { +pub fn install_gcc_targets( + targets: HashSet, + host_triple: &HostTriple, +) -> Result> { info!("{} Installing gcc for build targets", emoji::WRENCH); let mut exports: Vec = Vec::new(); for target in targets { - let gcc = GccToolchain::new(target); + let gcc = GccToolchain::new(target, host_triple); gcc.install()?; #[cfg(windows)] diff --git a/src/toolchain/llvm_toolchain.rs b/src/toolchain/llvm_toolchain.rs index 8c82677..80953ac 100644 --- a/src/toolchain/llvm_toolchain.rs +++ b/src/toolchain/llvm_toolchain.rs @@ -2,6 +2,7 @@ use crate::{ emoji, + host_triple::HostTriple, toolchain::{download_file, espidf::get_tool_path}, }; use anyhow::{bail, Ok, Result}; @@ -16,35 +17,37 @@ const DEFAULT_LLVM_VERSION: &str = "esp-14.0.0-20220415"; #[derive(Debug)] pub struct LlvmToolchain { + /// LLVM Toolchain file name. + pub file_name: String, + /// Host triple. + pub host_triple: HostTriple, + /// LLVM Toolchain path. + pub path: PathBuf, /// The repository containing LVVM sources. pub repository_url: String, /// Repository release version to use. pub version: String, - /// LLVM Toolchain file name. - pub file_name: String, - /// LLVM Toolchain path. - pub path: PathBuf, } impl LlvmToolchain { /// Gets the name of the LLVM arch based on the host triple. - fn get_arch(host_triple: &str) -> Result { + fn get_arch(host_triple: &HostTriple) -> Result<&str> { match host_triple { - "aarch64-apple-darwin" | "x86_64-apple-darwin" => Ok("macos".to_string()), - "x86_64-unknown-linux-gnu" => Ok("linux-amd64".to_string()), - "x86_64-pc-windows-msvc" | "x86_64-pc-windows-gnu" => Ok("win64".to_string()), + HostTriple::Aarch64AppleDarwin | HostTriple::X86_64AppleDarwin => Ok("macos"), + HostTriple::X86_64UnknownLinuxGnu => Ok("linux-amd64"), + HostTriple::X86_64PcWindowsMsvc | HostTriple::X86_64PcWindowsGnu => Ok("win64"), _ => bail!( - "{} No LLVM arch found for the host triple: {}", + "{} No LLVM arch found for the host triple: '{}'", emoji::ERROR, host_triple ), } } - /// Gets the artifact extension based on the host architecture. - fn get_artifact_extension(host_triple: &str) -> &str { + /// Gets the artifact extension based on the host triple. + fn get_artifact_extension(host_triple: &HostTriple) -> &str { match host_triple { - "x86_64-pc-windows-msvc" | "x86_64-pc-windows-gnu" => "zip", + HostTriple::X86_64PcWindowsMsvc | HostTriple::X86_64PcWindowsGnu => "zip", _ => "tar.xz", } } @@ -74,7 +77,7 @@ impl LlvmToolchain { self.repository_url.clone(), &format!( "idf_tool_xtensa_elf_clang.{}", - Self::get_artifact_extension(guess_host_triple::guess_host_triple().unwrap()) + Self::get_artifact_extension(&self.host_triple) ), self.path.to_str().unwrap(), true, @@ -95,10 +98,9 @@ impl LlvmToolchain { } /// Create a new instance with default values and proper toolchain version. - pub fn new(minified: bool) -> Self { - let host_triple = guess_host_triple::guess_host_triple().unwrap(); - let version = DEFAULT_LLVM_VERSION.to_string(); + pub fn new(minified: bool, host_triple: &HostTriple) -> Self { let file_name: String; + let version = DEFAULT_LLVM_VERSION.to_string(); let repository_url: String; if minified { file_name = format!( @@ -130,10 +132,11 @@ impl LlvmToolchain { ) .into(); Self { + file_name, + host_triple: host_triple.clone(), + path, repository_url, version, - file_name, - path, } } } diff --git a/src/toolchain/rust_toolchain.rs b/src/toolchain/rust_toolchain.rs index 56ff92a..b9ca2bc 100644 --- a/src/toolchain/rust_toolchain.rs +++ b/src/toolchain/rust_toolchain.rs @@ -2,6 +2,7 @@ use crate::{ emoji, + host_triple::HostTriple, toolchain::{download_file, espidf::get_dist_path, get_home_dir}, }; use anyhow::{bail, Result}; @@ -15,20 +16,22 @@ const DEFAULT_XTENSA_RUST_REPOSITORY: &str = #[derive(Debug)] pub struct RustToolchain { + /// Path to the cargo home directory. + pub cargo_home: PathBuf, /// Xtensa Rust toolchain file. pub dist_file: String, /// Xtensa Rust toolchain URL. pub dist_url: String, + /// Host triple. + pub host_triple: String, + /// Path to the rustup home directory. + pub rustup_home: PathBuf, #[cfg(unix)] /// Xtensa Src Rust toolchain file. pub src_dist_file: String, #[cfg(unix)] /// Xtensa Src Rust toolchain URL. pub src_dist_url: String, - /// Path to the cargo home directory. - pub cargo_home: PathBuf, - /// Path to the rustup home directory. - pub rustup_home: PathBuf, /// Xtensa Rust toolchain destination path. pub toolchain_destination: PathBuf, /// Xtensa Rust Toolchain version. @@ -57,7 +60,6 @@ impl RustToolchain { #[cfg(unix)] if cfg!(unix) { - let host_triple = guess_host_triple::guess_host_triple().unwrap(); download_file( self.dist_url.clone(), "rust.tar.xz", @@ -69,7 +71,7 @@ impl RustToolchain { let arguments = format!( "{}/rust-nightly-{}/install.sh --destdir={} --prefix='' --without=rust-docs", get_dist_path("rust"), - host_triple, + &self.host_triple, self.toolchain_destination.display() ); cmd!("/bin/bash", "-c", arguments).run()?; @@ -104,10 +106,9 @@ impl RustToolchain { } /// Create a new instance. - pub fn new(toolchain_version: String) -> Self { - let host_triple = guess_host_triple::guess_host_triple().unwrap(); + pub fn new(toolchain_version: &str, host_triple: &HostTriple) -> Self { let artifact_extension = get_artifact_extension(host_triple); - let version = toolchain_version; + let version = toolchain_version.to_string(); let dist = format!("rust-{}-{}", version, host_triple); let dist_file = format!("{}.{}", dist, artifact_extension); let dist_url = format!( @@ -130,14 +131,15 @@ impl RustToolchain { #[cfg(windows)] let toolchain_destination = rustup_home.join("toolchains"); Self { + cargo_home, dist_file, dist_url, + host_triple: host_triple.to_string(), + rustup_home, #[cfg(unix)] src_dist_file, #[cfg(unix)] src_dist_url, - cargo_home, - rustup_home, toolchain_destination, version, } @@ -175,9 +177,9 @@ impl RustCrate { } /// Gets the artifact extension based on the host architecture. -fn get_artifact_extension(host_triple: &str) -> &str { +fn get_artifact_extension(host_triple: &HostTriple) -> &str { match host_triple { - "x86_64-pc-windows-msvc" | "x86_64-pc-windows-gnu" => "zip", + HostTriple::X86_64PcWindowsMsvc | HostTriple::X86_64PcWindowsGnu => "zip", _ => "tar.xz", } }