diff --git a/Cargo.lock b/Cargo.lock index 56c4ed72b..b4a689a60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,6 +156,7 @@ dependencies = [ "chrono", "clap 3.2.25", "component", + "fuelup", "once_cell", "semver", "serde", @@ -313,6 +314,7 @@ version = "0.0.0" dependencies = [ "anyhow", "component", + "fuelup", "semver", "serde", "serde_json", diff --git a/ci/build-channel/Cargo.toml b/ci/build-channel/Cargo.toml index c56e57cf3..bb3fd0e38 100644 --- a/ci/build-channel/Cargo.toml +++ b/ci/build-channel/Cargo.toml @@ -11,6 +11,7 @@ anyhow = "1" chrono = "0.4" clap = { version = "3.2", features = ["cargo", "derive", "env"] } component = { path = "../../component" } +fuelup = { path = "../../" } once_cell = "1.17.0" semver = { version = "1", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } diff --git a/ci/build-channel/src/main.rs b/ci/build-channel/src/main.rs index c719b5fee..83445d55b 100644 --- a/ci/build-channel/src/main.rs +++ b/ci/build-channel/src/main.rs @@ -33,6 +33,7 @@ use anyhow::{bail, Result}; use clap::Parser; use component::{Component, Components}; +use fuelup::constants::GITHUB_API_ORG_URL; use once_cell::sync::Lazy; use semver::Version; use serde::{Deserialize, Serialize}; @@ -113,8 +114,8 @@ fn get_version(component: &Component) -> Result { let mut data = Vec::new(); let url = format!( - "https://api.github.com/repos/FuelLabs/{}/releases/latest", - component.repository_name + "{}{}/releases/latest", + GITHUB_API_ORG_URL, component.repository_name ); let resp = handle.get(&url).call()?; @@ -152,8 +153,8 @@ This could result in incompatibility between forc and fuel-core." fn write_nightly_document(document: &mut Document, components: Vec) -> Result<()> { let mut data = Vec::new(); let nightly_release_url = format!( - "https://api.github.com/repos/FuelLabs/sway-nightly-binaries/releases/tags/nightly-{}", - *TODAY + "{}{}/releases/tags/nightly-{}", + GITHUB_API_ORG_URL, "sway-nightly-binaries", *TODAY ); let resp = ureq::get(&nightly_release_url).call()?; diff --git a/ci/compare-versions/Cargo.toml b/ci/compare-versions/Cargo.toml index ea3059e7b..f39aeb319 100644 --- a/ci/compare-versions/Cargo.toml +++ b/ci/compare-versions/Cargo.toml @@ -9,6 +9,7 @@ publish = false [dependencies] anyhow = "1" component = { path = "../../component" } +fuelup = { path = "../../" } semver = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/ci/compare-versions/src/main.rs b/ci/compare-versions/src/main.rs index 087ad910d..6af00bf30 100644 --- a/ci/compare-versions/src/main.rs +++ b/ci/compare-versions/src/main.rs @@ -16,17 +16,14 @@ use anyhow::{bail, Result}; use component::Components; +use fuelup::constants::{CHANNEL_LATEST_URL_ACTUAL, GITHUB_API_ORG_URL}; use semver::Version; use serde::{Deserialize, Serialize}; use std::{io::Read, os::unix::process::CommandExt, process::Command, str::FromStr}; use toml_edit::Document; -const GITHUB_API_REPOS_BASE_URL: &str = "https://api.github.com/repos/FuelLabs/"; -const ACTIONS_RUNS: &str = "actions/runs"; const SWAY_REPO: &str = "sway"; const FUEL_CORE_REPO: &str = "fuel-core"; -const CHANNEL_FUEL_LATEST_TOML_URL: &str = - "https://raw.githubusercontent.com/FuelLabs/fuelup/gh-pages/channel-fuel-latest.toml"; #[derive(Debug, Serialize, Deserialize)] struct WorkflowRunApiResponse { @@ -61,8 +58,8 @@ const MAX_VERSIONS: usize = 3; fn get_workflow_runs(repo: &str) -> Result { let github_actions_runs_api_url = format!( - "{}{}/{}?event=release&status=success", - GITHUB_API_REPOS_BASE_URL, repo, ACTIONS_RUNS + "{}{}/actions/runs?event=release&status=success", + GITHUB_API_ORG_URL, repo ); let handle = ureq::builder().user_agent("fuelup").build(); let resp = handle @@ -78,11 +75,7 @@ fn get_workflow_runs(repo: &str) -> Result { } fn get_latest_release_version(repo: &str) -> Result { - let url = format!( - "https://api.github.com/repos/FuelLabs/{}/releases/latest", - repo - ); - + let url = format!("{}{}/releases/latest", GITHUB_API_ORG_URL, repo); let handle = ureq::builder().user_agent("fuelup").build(); let response: LatestReleaseApiResponse = match handle.get(&url).call() { Ok(r) => serde_json::from_reader(r.into_reader())?, @@ -159,7 +152,7 @@ fn print_selected_versions(forc_versions: &[Version], fuel_core_versions: &[Vers fn compare_rest() -> Result<()> { let handle = ureq::builder().user_agent("fuelup").build(); - let toml_resp = match handle.get(CHANNEL_FUEL_LATEST_TOML_URL).call() { + let toml_resp = match handle.get(CHANNEL_LATEST_URL_ACTUAL).call() { Ok(r) => r .into_string() .expect("Could not convert channel to string"), @@ -231,7 +224,7 @@ fn get_latest_version(repo: &str) -> Result { fn compare_compatibility() -> Result<()> { let handle = ureq::builder().user_agent("fuelup").build(); - let toml_resp = match handle.get(CHANNEL_FUEL_LATEST_TOML_URL).call() { + let toml_resp = match handle.get(CHANNEL_LATEST_URL_ACTUAL).call() { Ok(r) => r .into_string() .expect("Could not convert channel to string"), @@ -239,7 +232,7 @@ fn compare_compatibility() -> Result<()> { eprintln!( "Error {}: Could not download channel-fuel-latest.toml from {}; re-generating channel.", r.status(), - &CHANNEL_FUEL_LATEST_TOML_URL + &CHANNEL_LATEST_URL_ACTUAL ); let sway_runs = get_workflow_runs(SWAY_REPO)?; diff --git a/src/channel.rs b/src/channel.rs index a0cfe7ba0..e00bd2c0d 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -1,9 +1,9 @@ use crate::{ constants::{ - CHANNEL_BETA_1_FILE_NAME, CHANNEL_BETA_2_FILE_NAME, CHANNEL_BETA_3_FILE_NAME, - CHANNEL_BETA_4_FILE_NAME, CHANNEL_BETA_5_FILE_NAME, CHANNEL_DEVNET_FILE_NAME, - CHANNEL_LATEST_FILE_NAME, CHANNEL_NIGHTLY_FILE_NAME, CHANNEL_TESTNET_FILE_NAME, - DATE_FORMAT_URL_FRIENDLY, FUELUP_GH_PAGES, + BETA_CHANNELS, CHANNEL_BETA_1_FILE_NAME, CHANNEL_BETA_2_FILE_NAME, + CHANNEL_BETA_3_FILE_NAME, CHANNEL_BETA_4_FILE_NAME, CHANNEL_BETA_5_FILE_NAME, + CHANNEL_DEVNET_FILE_NAME, CHANNEL_LATEST_FILE_NAME, CHANNEL_NIGHTLY_FILE_NAME, + CHANNEL_TESTNET_FILE_NAME, DATE_FORMAT_URL_FRIENDLY, GITHUB_USER_CONTENT_URL, }, download::{download, DownloadCfg}, toolchain::{DistToolchainDescription, DistToolchainName}, @@ -17,21 +17,6 @@ use time::Date; use toml_edit::de; use tracing::warn; -pub const LATEST: &str = "latest"; -pub const STABLE: &str = "stable"; -pub const BETA_1: &str = "beta-1"; -pub const BETA_2: &str = "beta-2"; -pub const BETA_3: &str = "beta-3"; -pub const BETA_4: &str = "beta-4"; -pub const BETA_5: &str = "beta-5"; -pub const DEVNET: &str = "devnet"; -pub const TESTNET: &str = "testnet"; -pub const NIGHTLY: &str = "nightly"; - -pub const CHANNELS: [&str; 9] = [ - LATEST, NIGHTLY, BETA_1, BETA_2, BETA_3, BETA_4, BETA_5, DEVNET, TESTNET, -]; - #[derive(Debug, Deserialize, Serialize)] pub struct HashedBinary { pub url: String, @@ -50,14 +35,11 @@ pub struct Package { pub fuels_version: Option, } -pub fn is_beta_toolchain(name: &str) -> bool { - name == BETA_1 - || name == BETA_2 - || name == BETA_3 - || name == BETA_4 - || name == BETA_5 - || name == DEVNET - || name == TESTNET +pub fn is_beta_toolchain(name: &str) -> Option<&str> { + BETA_CHANNELS + .iter() + .find(|&beta_channel| name.starts_with(beta_channel)) + .copied() } fn format_nightly_url(date: &Date) -> Result { @@ -68,7 +50,7 @@ fn format_nightly_url(date: &Date) -> Result { } fn construct_channel_url(desc: &DistToolchainDescription) -> Result { - let mut url = FUELUP_GH_PAGES.to_owned(); + let mut url = format!("{}{}/gh-pages/", GITHUB_USER_CONTENT_URL, "fuelup"); match desc.name { DistToolchainName::Latest => { if let Some(date) = desc.date { @@ -136,7 +118,7 @@ If this component should be downloadable, try running `fuelup self update` and r #[cfg(test)] mod tests { use super::*; - use crate::{download::DownloadCfg, file::read_file}; + use crate::{constants::CHANNELS, download::DownloadCfg, file::read_file}; #[test] fn channel_from_toml() { @@ -246,4 +228,14 @@ mod tests { assert_eq!(cfgs[1].name, "fuel-core"); assert_eq!(cfgs[1].version, Version::parse("0.9.4").unwrap()); } + + #[test] + fn test_all_channels_for_beta() { + for channel in CHANNELS.iter() { + assert_eq!( + is_beta_toolchain(channel).is_some(), + BETA_CHANNELS.contains(channel) + ); + } + } } diff --git a/src/commands/toolchain.rs b/src/commands/toolchain.rs index 035fad7e6..9f786a568 100644 --- a/src/commands/toolchain.rs +++ b/src/commands/toolchain.rs @@ -1,6 +1,6 @@ +use crate::constants::CHANNELS; use crate::ops::fuelup_toolchain::{install::install, new::new, uninstall::uninstall}; use crate::target_triple::TargetTriple; -use crate::toolchain::RESERVED_TOOLCHAIN_NAMES; use anyhow::{bail, Result}; use clap::Parser; @@ -45,7 +45,7 @@ fn name_allowed(s: &str) -> Result { None => s, }; - if RESERVED_TOOLCHAIN_NAMES.contains(&name) { + if CHANNELS.contains(&name) { bail!( "Cannot use distributable toolchain name '{}' as a custom toolchain name", s diff --git a/src/config.rs b/src/config.rs index 696e29b1a..fe7e3968f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ +use crate::constants::CHANNELS; use crate::fmt::format_toolchain_with_target; use crate::path::toolchains_dir; -use crate::toolchain::RESERVED_TOOLCHAIN_NAMES; use anyhow::Result; use std::{fs, io, path::PathBuf}; @@ -25,7 +25,7 @@ impl Config { .filter(|e| e.file_type().map(|f| f.is_dir()).unwrap_or(false)) { let toolchain = dir_entry.file_name().to_string_lossy().to_string(); - if RESERVED_TOOLCHAIN_NAMES + if CHANNELS .iter() .any(|t| toolchain == format_toolchain_with_target(t)) { @@ -54,7 +54,7 @@ impl Config { .map(|e| e.file_name().into_string().ok().unwrap_or_default()) .collect(); - for name in RESERVED_TOOLCHAIN_NAMES { + for name in CHANNELS { let dist_toolchain = format_toolchain_with_target(name); if installed_toolchains.contains(&dist_toolchain) { dist_toolchains.push(name.to_string()); diff --git a/src/constants.rs b/src/constants.rs index bff4e86cb..30cd1cb01 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,11 +1,62 @@ use time::{format_description::FormatItem, macros::format_description}; -pub const FUELUP_GH_PAGES: &str = "https://raw.githubusercontent.com/FuelLabs/fuelup/gh-pages/"; -pub const FUEL_TOOLCHAIN_TOML_FILE: &str = "fuel-toolchain.toml"; -pub const FUELS_VERSION_FILE: &str = "fuels_version"; +// Channels + +pub const LATEST: &str = "latest"; +pub const NIGHTLY: &str = "nightly"; +pub const BETA_1: &str = "beta-1"; +pub const BETA_2: &str = "beta-2"; +pub const BETA_3: &str = "beta-3"; +pub const BETA_4: &str = "beta-4"; +pub const BETA_5: &str = "beta-5"; +pub const DEVNET: &str = "devnet"; +pub const TESTNET: &str = "testnet"; +// Stable is reserved, although currently unused. +pub const STABLE: &str = "stable"; + +pub const BETA_CHANNELS: [&str; 7] = [BETA_1, BETA_2, BETA_3, BETA_4, BETA_5, DEVNET, TESTNET]; + +#[allow(clippy::indexing_slicing)] +pub const fn generate_all_channels() -> [&'static str; N] { + let mut channels = [""; N]; + + channels[0] = LATEST; + channels[1] = NIGHTLY; + + let mut i = 0; + + while i < BETA_CHANNELS.len() { + channels[2 + i] = BETA_CHANNELS[i]; + i += 1; + } + + channels +} + +pub const CHANNELS: [&str; 9] = generate_all_channels::<9>(); +// URLs + +pub const GITHUB_API_ORG_URL: &str = "https://api.github.com/repos/FuelLabs/"; +pub const GITHUB_USER_CONTENT_URL: &str = "https://raw.githubusercontent.com/FuelLabs/"; + +// NOTE: Although this variable is named "LATEST", it needs to point to +// "testnet" until mainnet has launched. Once launched, we can then merge this +// variable with the `CHANNEL_LATEST_URL_ACTUAL` variable pub const CHANNEL_LATEST_URL: &str = "https://raw.githubusercontent.com/FuelLabs/fuelup/gh-pages/channel-fuel-testnet.toml"; + +// We need to point to the latest URL but we can't use `CHANNEL_LATEST_URL` +// until mainnet has launched (as `CHANNEL_LATEST_URL` currently points to +// testnet). So we dupilcate here for now and we can cleanup sometime later +pub const CHANNEL_LATEST_URL_ACTUAL: &str = + "https://raw.githubusercontent.com/FuelLabs/fuelup/gh-pages/channel-fuel-latest.toml"; + +// Filenames + +pub const FUEL_TOOLCHAIN_TOML_FILE: &str = "fuel-toolchain.toml"; +pub const FUELS_VERSION_FILE: &str = "fuels_version"; + pub const CHANNEL_LATEST_FILE_NAME: &str = "channel-fuel-testnet.toml"; pub const CHANNEL_NIGHTLY_FILE_NAME: &str = "channel-fuel-nightly.toml"; pub const CHANNEL_BETA_1_FILE_NAME: &str = "channel-fuel-beta-1.toml"; @@ -16,5 +67,7 @@ pub const CHANNEL_BETA_5_FILE_NAME: &str = "channel-fuel-beta-5.toml"; pub const CHANNEL_DEVNET_FILE_NAME: &str = "channel-fuel-devnet.toml"; pub const CHANNEL_TESTNET_FILE_NAME: &str = "channel-fuel-testnet.toml"; +// Misc + pub const DATE_FORMAT: &[FormatItem] = format_description!("[year]-[month]-[day]"); pub const DATE_FORMAT_URL_FRIENDLY: &[FormatItem] = format_description!("[year]/[month]/[day]"); diff --git a/src/download.rs b/src/download.rs index 4d562311d..68f084fd3 100644 --- a/src/download.rs +++ b/src/download.rs @@ -1,6 +1,6 @@ use crate::{ channel::{Channel, Package}, - constants::CHANNEL_LATEST_URL, + constants::{CHANNEL_LATEST_URL, GITHUB_API_ORG_URL, GITHUB_USER_CONTENT_URL}, target_triple::TargetTriple, toolchain::DistToolchainDescription, }; @@ -128,9 +128,8 @@ pub fn get_latest_version(name: &str) -> Result { let handle = build_agent()?; let mut data = Vec::new(); if name == FUELUP { - const FUELUP_RELEASES_API_URL: &str = - "https://api.github.com/repos/FuelLabs/fuelup/releases/latest"; - let resp = handle.get(FUELUP_RELEASES_API_URL).call()?; + let fuelup_latest_url = format!("{}{}/releases/latest", GITHUB_API_ORG_URL, FUELUP); + let resp = handle.get(&fuelup_latest_url).call()?; resp.into_reader().read_to_end(&mut data)?; let response: LatestReleaseApiResponse = serde_json::from_str(&String::from_utf8_lossy(&data))?; @@ -397,13 +396,13 @@ fn fuels_version_from_toml(toml: toml_edit::Document) -> Result { pub fn fetch_fuels_version(cfg: &DownloadCfg) -> Result { let url = match cfg.name.as_str() { "forc" => format!( - "https://raw.githubusercontent.com/FuelLabs/sway/v{}/test/src/sdk-harness/Cargo.toml", - cfg.version + "{}{}/v{}/test/src/sdk-harness/Cargo.toml", + GITHUB_USER_CONTENT_URL, "sway", cfg.version ), "forc-wallet" => { format!( - "https://raw.githubusercontent.com/FuelLabs/forc-wallet/v{}/Cargo.toml", - cfg.version + "{}{}/v{}/Cargo.toml", + GITHUB_USER_CONTENT_URL, "forc-wallet", cfg.version ) } _ => bail!("invalid component to fetch fuels version for"), @@ -562,7 +561,11 @@ mod tests { fn test_agent() -> anyhow::Result<()> { // this test case is used to illustrate the bug of ureq that sometimes doesn't return "Content-Length" header let handle = build_agent()?; - let response = handle.get("https://raw.githubusercontent.com/FuelLabs/fuelup/gh-pages/channel-fuel-beta-4.toml").call()?; + let url = format!( + "{}{}/gh-pages/channel-fuel-beta-4.toml", + GITHUB_USER_CONTENT_URL, "fuelup" + ); + let response = handle.get(&url).call()?; assert!(response.header("Content-Length").is_none()); Ok(()) } diff --git a/src/fmt.rs b/src/fmt.rs index a17d00a37..96ee0f3b6 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -57,4 +57,14 @@ pub fn ask_user_yes_no_question(question: &str) -> io::Result { return Ok(result); } } + + // This is the dialoguer version of the prompter, but it's not testable + // as dialoguer requires an interactive terminal to work without failing. + // + // use dialoguer::{theme::ColorfulTheme, Confirm}; + // + // Confirm::with_theme(&ColorfulTheme::default()) + // .with_prompt(question) + // .interact() + // .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string())) } diff --git a/src/fuelup_cli.rs b/src/fuelup_cli.rs index 4c97fc7f6..a8e12a6a6 100644 --- a/src/fuelup_cli.rs +++ b/src/fuelup_cli.rs @@ -7,9 +7,14 @@ use crate::commands::{ toolchain::{self, ToolchainCommand}, upgrade::{self, UpgradeCommand}, }; -use crate::ops::{fuelup_show, fuelup_update}; -use anyhow::Result; +use anyhow::{bail, Context, Result}; use clap::Parser; +use crate::fmt::ask_user_yes_no_question; +use crate::ops::{fuelup_show, fuelup_toolchain, fuelup_update}; +use crate::toolchain::{DistToolchainDescription, Toolchain}; +use crate::toolchain_override::ToolchainOverride; +use std::str::FromStr; +use tracing::info; #[derive(Debug, Parser)] #[clap(name = "fuelup", about = "Fuel Toolchain Manager", version)] @@ -46,6 +51,41 @@ enum Commands { pub fn fuelup_cli() -> Result<()> { let cli = Cli::parse(); + if let Some(toolchain_override) = ToolchainOverride::from_project_root() { + let override_path = toolchain_override.cfg.toolchain.channel.to_string(); + let toolchain = match DistToolchainDescription::from_str(&override_path) { + Ok(desc) => Toolchain::from_path(&desc.to_string()), + Err(_) => Toolchain::from_path(&override_path), + }; + + info!("Using override toolchain '{}'", &toolchain.name); + + if !toolchain.exists() { + match cli.command { + Commands::Toolchain(_) => { + // User is managing their toolchains, so we fall through + } + _ => { + let should_install = ask_user_yes_no_question( + "Override toolchain is not installed. Do you want to install it now?", + ) + .context("Console I/O")?; + + if should_install { + fuelup_toolchain::install::install(toolchain::InstallCommand { + name: toolchain.name, + })?; + } else { + bail!( + "Override toolchain is not installed. Please run: 'fuelup toolchain install {}'", + &toolchain.name, + ) + } + } + } + } + } + match cli.command { Commands::Check(command) => check::exec(command), Commands::Completions(command) => completions::exec(command), diff --git a/src/ops/fuelup_upgrade.rs b/src/ops/fuelup_upgrade.rs index 90b615ad7..7b7c9824f 100644 --- a/src/ops/fuelup_upgrade.rs +++ b/src/ops/fuelup_upgrade.rs @@ -1,5 +1,5 @@ use super::{fuelup_default, fuelup_self, fuelup_update}; -use crate::channel::LATEST; +use crate::constants::LATEST; use anyhow::Result; pub fn upgrade(force: bool) -> Result<()> { diff --git a/src/toolchain.rs b/src/toolchain.rs index 30cc47671..02e4293f8 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -1,6 +1,6 @@ use crate::{ - channel::{self, Channel}, - constants::DATE_FORMAT, + channel::Channel, + constants::{self, CHANNELS, DATE_FORMAT}, download::DownloadCfg, file::{get_bin_version, hard_or_symlink_file, is_executable}, path::{ @@ -24,19 +24,6 @@ use std::{ use time::Date; use tracing::{error, info}; -pub const RESERVED_TOOLCHAIN_NAMES: &[&str] = &[ - channel::LATEST, - channel::BETA_1, - channel::BETA_2, - channel::BETA_3, - channel::BETA_4, - channel::BETA_5, - channel::NIGHTLY, - channel::DEVNET, - // Stable is reserved, although currently unused. - channel::STABLE, -]; - #[derive(Debug, Eq, PartialEq)] pub enum DistToolchainName { Beta1, @@ -53,15 +40,15 @@ pub enum DistToolchainName { impl fmt::Display for DistToolchainName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - DistToolchainName::Latest => write!(f, "{}", channel::LATEST), - DistToolchainName::Nightly => write!(f, "{}", channel::NIGHTLY), - DistToolchainName::Beta1 => write!(f, "{}", channel::BETA_1), - DistToolchainName::Beta2 => write!(f, "{}", channel::BETA_2), - DistToolchainName::Beta3 => write!(f, "{}", channel::BETA_3), - DistToolchainName::Beta4 => write!(f, "{}", channel::BETA_4), - DistToolchainName::Beta5 => write!(f, "{}", channel::BETA_5), - DistToolchainName::Devnet => write!(f, "{}", channel::DEVNET), - DistToolchainName::Testnet => write!(f, "{}", channel::TESTNET), + DistToolchainName::Latest => write!(f, "{}", constants::LATEST), + DistToolchainName::Nightly => write!(f, "{}", constants::NIGHTLY), + DistToolchainName::Beta1 => write!(f, "{}", constants::BETA_1), + DistToolchainName::Beta2 => write!(f, "{}", constants::BETA_2), + DistToolchainName::Beta3 => write!(f, "{}", constants::BETA_3), + DistToolchainName::Beta4 => write!(f, "{}", constants::BETA_4), + DistToolchainName::Beta5 => write!(f, "{}", constants::BETA_5), + DistToolchainName::Devnet => write!(f, "{}", constants::DEVNET), + DistToolchainName::Testnet => write!(f, "{}", constants::TESTNET), } } } @@ -70,15 +57,15 @@ impl FromStr for DistToolchainName { type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s { - channel::LATEST => Ok(Self::Latest), - channel::NIGHTLY => Ok(Self::Nightly), - channel::BETA_1 => Ok(Self::Beta1), - channel::BETA_2 => Ok(Self::Beta2), - channel::BETA_3 => Ok(Self::Beta3), - channel::BETA_4 => Ok(Self::Beta4), - channel::BETA_5 => Ok(Self::Beta5), - channel::DEVNET => Ok(Self::Devnet), - channel::TESTNET => Ok(Self::Testnet), + constants::LATEST => Ok(Self::Latest), + constants::NIGHTLY => Ok(Self::Nightly), + constants::BETA_1 => Ok(Self::Beta1), + constants::BETA_2 => Ok(Self::Beta2), + constants::BETA_3 => Ok(Self::Beta3), + constants::BETA_4 => Ok(Self::Beta4), + constants::BETA_5 => Ok(Self::Beta5), + constants::DEVNET => Ok(Self::Devnet), + constants::TESTNET => Ok(Self::Testnet), _ => bail!("Unknown name for toolchain: {}", s), } } @@ -297,7 +284,7 @@ impl Toolchain { } pub fn is_distributed(&self) -> bool { - RESERVED_TOOLCHAIN_NAMES.contains(&self.name.split_once('-').unwrap_or((&self.name, "")).0) + CHANNELS.contains(&self.name.split_once('-').unwrap_or((&self.name, "")).0) } pub fn exists(&self) -> bool { @@ -510,7 +497,7 @@ impl Toolchain { #[cfg(test)] mod tests { - use crate::channel::{CHANNELS, STABLE}; + use crate::constants::{CHANNELS, STABLE}; use super::*; diff --git a/src/toolchain_override.rs b/src/toolchain_override.rs index 5fdbc3f82..b6b11fc3a 100644 --- a/src/toolchain_override.rs +++ b/src/toolchain_override.rs @@ -1,6 +1,6 @@ use crate::{ - channel::{is_beta_toolchain, LATEST, NIGHTLY}, - constants::{DATE_FORMAT, FUEL_TOOLCHAIN_TOML_FILE}, + channel::is_beta_toolchain, + constants::{DATE_FORMAT, FUEL_TOOLCHAIN_TOML_FILE, LATEST, NIGHTLY}, download::DownloadCfg, file, path::get_fuel_toolchain_toml, @@ -88,9 +88,9 @@ impl fmt::Display for Channel { impl FromStr for Channel { type Err = anyhow::Error; fn from_str(s: &str) -> Result { - if is_beta_toolchain(s) { + if let Some(beta_channel) = is_beta_toolchain(s) { return Ok(Self { - name: s.to_string(), + name: beta_channel.to_string(), date: None, }); }; @@ -213,7 +213,7 @@ impl OverrideCfg { #[cfg(test)] mod tests { use super::*; - use crate::channel::{BETA_1, BETA_2, BETA_3, NIGHTLY}; + use crate::constants::{BETA_1, BETA_2, BETA_3, NIGHTLY}; use indoc::indoc; #[test] diff --git a/tests/default.rs b/tests/default.rs index 88930954e..cd97aa874 100644 --- a/tests/default.rs +++ b/tests/default.rs @@ -99,13 +99,50 @@ fn fuelup_default_nightly_and_nightly_date() -> Result<()> { Ok(()) } +#[test] +fn fuelup_default_override_skip_install() -> Result<()> { + testcfg::setup(FuelupState::LatestWithBetaOverride, &|cfg| { + let output = cfg.fuelup_with_input(&["default"], b"N\n"); + let triple = TargetTriple::from_host().unwrap(); + + assert!(output + .stdout + .starts_with(&format!("Using override toolchain 'beta-1-{triple}'"))); + + assert!(output + .stdout + .contains("Override toolchain is not installed. Do you want to install it now?")); + + assert!(output + .stdout + .contains(&format!("Override toolchain is not installed. Please run: 'fuelup toolchain install beta-1-{triple}'"))); + })?; + Ok(()) +} + #[test] fn fuelup_default_override() -> Result<()> { testcfg::setup(FuelupState::LatestWithBetaOverride, &|cfg| { - let output = cfg.fuelup(&["default"]); + let output = cfg.fuelup_with_input(&["default"], b"Y\n"); let triple = TargetTriple::from_host().unwrap(); - let expected_stdout = format!("beta-1-{triple} (override), latest-{triple} (default)\n"); - assert_eq!(output.stdout, expected_stdout); + + assert!(output + .stdout + .starts_with(&format!("Using override toolchain 'beta-1-{triple}'"))); + + assert!(output + .stdout + .contains("Override toolchain is not installed. Do you want to install it now?")); + + assert!(output.stdout.contains("Downloading:")); + + assert!(output + .stdout + .contains("The Fuel toolchain is installed and up to date")); + + assert!(output + .stdout + .contains(&format!("beta-1-{triple} (override)"))); })?; Ok(()) } diff --git a/tests/show.rs b/tests/show.rs index 6a557f7f4..d8e027e04 100644 --- a/tests/show.rs +++ b/tests/show.rs @@ -173,39 +173,28 @@ fn fuelup_show_custom() -> Result<()> { #[test] fn fuelup_show_override() -> Result<()> { testcfg::setup(FuelupState::LatestWithBetaOverride, &|cfg| { - let stripped = strip_ansi_escapes::strip(cfg.fuelup(&["show"]).stdout); + let stripped = strip_ansi_escapes::strip(cfg.fuelup_with_input(&["show"], b"y\n").stdout); let stdout = String::from_utf8_lossy(&stripped); let target = TargetTriple::from_host().unwrap(); - let fuelup_home = cfg.fuelup_dir(); - let fuelup_home_str = fuelup_home.to_string_lossy(); - let expected_stdout = formatdoc! { - r#"Default host: {target} - fuelup home: {fuelup_home_str} - Installed toolchains - -------------------- - latest-{target} (default) + assert!(stdout + .starts_with(&format!("Using override toolchain 'beta-1-{target}'"))); - active toolchain - ---------------- - beta-1-{target} (override), path: {} - forc : not found - - forc-client - - forc-deploy : not found - - forc-run : not found - - forc-crypto : not found - - forc-debug : not found - - forc-doc : not found - - forc-fmt : not found - - forc-lsp : not found - - forc-tx : not found - - forc-wallet : not found - fuel-core : not found - fuel-core-keygen : not found - "#, cfg.home.join(FUEL_TOOLCHAIN_TOML_FILE).display() - }; - assert_eq!(stdout, expected_stdout); - })?; + assert!(stdout + .contains("Override toolchain is not installed. Do you want to install it now?")); + + assert!(stdout.contains("Downloading:")); + + assert!(stdout.contains(&formatdoc! {" + Installed: + - forc 0.26.0 + - forc-wallet 0.1.2 + - fuel-core 0.10.1 + "})); + + assert!(stdout + .contains(&format!("beta-1-{target} (override)"))); + })?; Ok(()) } @@ -267,7 +256,8 @@ fn fuelup_show_latest_then_override() -> Result<()> { stdout = String::from_utf8_lossy(&stripped); let expected_stdout = formatdoc! { - r#"Default host: {target} + r#"Using override toolchain 'nightly-2022-08-30-{target}' + Default host: {target} fuelup home: {fuelup_home_str} Installed toolchains diff --git a/tests/testcfg/mod.rs b/tests/testcfg/mod.rs index 43bd70475..cb4721a3a 100644 --- a/tests/testcfg/mod.rs +++ b/tests/testcfg/mod.rs @@ -1,17 +1,17 @@ use anyhow::Result; -use fuelup::channel::{BETA_1, LATEST, NIGHTLY}; -use fuelup::constants::FUEL_TOOLCHAIN_TOML_FILE; +use fuelup::constants::{BETA_1, FUEL_TOOLCHAIN_TOML_FILE, LATEST, NIGHTLY}; use fuelup::file::hard_or_symlink_file; use fuelup::settings::SettingsFile; use fuelup::target_triple::TargetTriple; use fuelup::toolchain_override::{self, OverrideCfg, ToolchainCfg, ToolchainOverride}; use semver::Version; +use std::io::Write; use std::os::unix::fs::OpenOptionsExt; use std::str::FromStr; use std::{ env, fs, path::{Path, PathBuf}, - process::{Command, ExitStatus}, + process::{Command, ExitStatus, Stdio}, }; use tempfile::tempdir; @@ -130,7 +130,7 @@ impl TestCfg { .args(args) .current_dir(&self.home) .env("HOME", &self.home) - .env("CARGO_HOME", &self.home.join(".cargo")) + .env("CARGO_HOME", self.home.join(".cargo")) .env( "PATH", format!( @@ -163,6 +163,47 @@ impl TestCfg { pub fn fuelup(&mut self, args: &[&str]) -> TestOutput { self.exec("fuelup", args) } + + /// A convenience wrapper for executing 'fuelup' within the fuelup test configuration. + /// It takes an array ref of bytes to write into stdin. + pub fn fuelup_with_input(&mut self, args: &[&str], input: &[u8]) -> TestOutput { + let path = self.fuelup_bin_dirpath.join("fuelup"); + let mut child = Command::new(path) + .args(args) + .current_dir(&self.home) + .env("HOME", &self.home) + .env("CARGO_HOME", self.home.join(".cargo")) + .env( + "PATH", + format!( + "{}:{}:{}", + &self.home.join(".local/bin").display(), + &self.home.join(".cargo/bin").display(), + &self.home.join(".fuelup/bin").display(), + ), + ) + .env("TERM", "dumb") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) // Inherit so we can see it install + .spawn() + .expect("Failed to execute command"); + + let stdin = child.stdin.as_mut().expect("Failed to open stdin"); + + for byte in input { + stdin.write_all(&[*byte]).expect("Failed to write to stdin"); + } + + let output = child.wait_with_output().expect("Failed to read output"); + let stdout = String::from_utf8(output.stdout).unwrap(); + let stderr = String::from_utf8(output.stderr).unwrap(); + + TestOutput { + stdout, + stderr, + status: output.status, + } + } } #[cfg(unix)] diff --git a/tests/toolchain.rs b/tests/toolchain.rs index fc9bdc444..36abd18ad 100644 --- a/tests/toolchain.rs +++ b/tests/toolchain.rs @@ -4,7 +4,8 @@ pub mod testcfg; use anyhow::Result; use chrono::{Duration, Utc}; use expects::expect_files_exist; -use fuelup::{channel, fmt::format_toolchain_with_target, target_triple::TargetTriple}; +use fuelup::{constants, fmt::format_toolchain_with_target, target_triple::TargetTriple}; +use indoc::indoc; use testcfg::{FuelupState, ALL_BINS, CUSTOM_TOOLCHAIN_NAME, DATE}; fn yesterday() -> String { @@ -140,10 +141,10 @@ fn fuelup_toolchain_uninstall() -> Result<()> { fn fuelup_toolchain_new() -> Result<()> { testcfg::setup(FuelupState::Empty, &|cfg| { let output = cfg.fuelup(&["toolchain", "new", CUSTOM_TOOLCHAIN_NAME]); - let expected_stdout = format!( - "New toolchain initialized: {CUSTOM_TOOLCHAIN_NAME} -Default toolchain set to '{CUSTOM_TOOLCHAIN_NAME}'\n" - ); + let expected_stdout = format!(indoc! {" + New toolchain initialized: {} + Default toolchain set to '{}' + "}, CUSTOM_TOOLCHAIN_NAME, CUSTOM_TOOLCHAIN_NAME); assert_eq!(output.stdout, expected_stdout); assert!(cfg.toolchain_bin_dir(CUSTOM_TOOLCHAIN_NAME).is_dir()); let default = cfg.default_toolchain(); @@ -155,7 +156,7 @@ Default toolchain set to '{CUSTOM_TOOLCHAIN_NAME}'\n" #[test] fn fuelup_toolchain_new_disallowed() -> Result<()> { testcfg::setup(FuelupState::Empty, &|cfg| { - for toolchain in [channel::LATEST, channel::NIGHTLY] { + for toolchain in [constants::LATEST, constants::NIGHTLY] { let output = cfg.fuelup(&["toolchain", "new", toolchain]); let expected_stderr = format!("error: invalid value '{toolchain}' for '': Cannot use distributable toolchain name '{toolchain}' as a custom toolchain name\n\nFor more information, try '--help'.\n"); assert_eq!(output.stderr, expected_stderr);