diff --git a/doc/src/concepts/toolchains.md b/doc/src/concepts/toolchains.md index 7f15d38668..7523ee6070 100644 --- a/doc/src/concepts/toolchains.md +++ b/doc/src/concepts/toolchains.md @@ -15,15 +15,15 @@ Standard release channel toolchain names have the following form: ``` [-][-] - = stable|beta|nightly| + = stable|beta|nightly|| = YYYY-MM-DD = ``` -'channel' is either a named release channel or an explicit version number, -such as `1.42.0`. Channel names can be optionally appended with an archive -date, as in `nightly-2014-12-18`, in which case the toolchain is downloaded -from the archive for that date. +'channel' is a named release channel, a major and minor version number such as +`1.42`, or a fully specified version number, such as `1.42.0`. Channel names +can be optionally appended with an archive date, as in `nightly-2014-12-18`, in +which case the toolchain is downloaded from the archive for that date. Finally, the host may be specified as a target triple. This is most useful for installing a 32-bit compiler on a 64-bit platform, or for installing the diff --git a/src/cli/help.rs b/src/cli/help.rs index cfa2036d4e..ce97f15fee 100644 --- a/src/cli/help.rs +++ b/src/cli/help.rs @@ -55,14 +55,15 @@ pub static TOOLCHAIN_HELP: &str = r"DISCUSSION: [-][-] - = stable|beta|nightly| + = stable|beta|nightly|| = YYYY-MM-DD = - 'channel' is either a named release channel or an explicit version - number, such as '1.42.0'. Channel names can be optionally appended - with an archive date, as in 'nightly-2017-05-09', in which case - the toolchain is downloaded from the archive for that date. + 'channel' is a named release channel, a major and minor version + number such as `1.42`, or a fully specified version number, such + as `1.42.0`. Channel names can be optionally appended with an + archive date, as in `nightly-2014-12-18`, in which case the + toolchain is downloaded from the archive for that date. The host may be specified as a target triple. This is most useful for installing a 32-bit compiler on a 64-bit platform, or for diff --git a/src/dist/dist.rs b/src/dist/dist.rs index f1fc3f84a6..1b71e58f45 100644 --- a/src/dist/dist.rs +++ b/src/dist/dist.rs @@ -27,11 +27,11 @@ static TOOLCHAIN_CHANNELS: &[&str] = &[ "nightly", "beta", "stable", - // Allow from 1.0.0 through to 9.999.99 - r"\d{1}\.\d{1,3}\.\d{1,2}", + // Allow from 1.0.0 through to 9.999.99 with optional patch version + r"\d{1}\.\d{1,3}(?:\.\d{1,2})?", ]; -#[derive(Debug)] +#[derive(Debug, PartialEq)] struct ParsedToolchainDesc { channel: String, date: Option, @@ -51,7 +51,7 @@ pub struct PartialToolchainDesc { pub target: PartialTargetTriple, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct PartialTargetTriple { pub arch: Option, pub os: Option, @@ -146,7 +146,7 @@ impl FromStr for ParsedToolchainDesc { fn from_str(desc: &str) -> Result { lazy_static! { static ref TOOLCHAIN_CHANNEL_PATTERN: String = format!( - r"^({})(?:-(\d{{4}}-\d{{2}}-\d{{2}}))?(?:-(.*))?$", + r"^({})(?:-(\d{{4}}-\d{{2}}-\d{{2}}))?(?:-(.+))?$", TOOLCHAIN_CHANNELS.join("|") ); // Note this regex gives you a guaranteed match of the channel (1) @@ -940,3 +940,135 @@ fn utc_from_manifest_date(date_str: &str) -> Option> { .ok() .map(|date| Utc.from_utc_date(&date)) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parsed_toolchain_desc_parse() { + let success_cases = vec![ + ("nightly", ("nightly", None, None)), + ("beta", ("beta", None, None)), + ("stable", ("stable", None, None)), + ("0.0", ("0.0", None, None)), + ("0.0.0", ("0.0.0", None, None)), + ("0.0.0--", ("0.0.0", None, Some("-"))), // possibly a bug? + ("9.999.99", ("9.999.99", None, None)), + ("0.0.0-anything", ("0.0.0", None, Some("anything"))), + ("0.0.0-0000-00-00", ("0.0.0", Some("0000-00-00"), None)), + // possibly unexpected behavior, if someone typos a date? + ( + "0.0.0-00000-000-000", + ("0.0.0", None, Some("00000-000-000")), + ), + // possibly unexpected behavior, if someone forgets to add target after the hyphen? + ("0.0.0-0000-00-00-", ("0.0.0", None, Some("0000-00-00-"))), + ( + "0.0.0-0000-00-00-any-other-thing", + ("0.0.0", Some("0000-00-00"), Some("any-other-thing")), + ), + ]; + + for (input, (channel, date, target)) in success_cases { + let parsed = input.parse::(); + assert!( + parsed.is_ok(), + "expected parsing of `{}` to succeed: {:?}", + input, + parsed + ); + + let expected = ParsedToolchainDesc { + channel: channel.into(), + date: date.map(String::from), + target: target.map(String::from), + }; + assert_eq!(parsed.unwrap(), expected, "input: `{}`", input); + } + + let failure_cases = vec!["anything", "00.0000.000", "3", "", "--", "0.0.0-"]; + + for input in failure_cases { + let parsed = input.parse::(); + assert!( + parsed.is_err(), + "expected parsing of `{}` to fail: {:?}", + input, + parsed + ); + + let error_message = format!("invalid toolchain name: '{}'", input); + + assert_eq!( + parsed.unwrap_err().to_string(), + error_message, + "input: `{}`", + input + ); + } + } + + #[test] + fn test_partial_target_triple_new() { + let success_cases = vec![ + ("", (None, None, None)), + ("i386", (Some("i386"), None, None)), + ("pc-windows", (None, Some("pc-windows"), None)), + ("gnu", (None, None, Some("gnu"))), + ("i386-gnu", (Some("i386"), None, Some("gnu"))), + ("pc-windows-gnu", (None, Some("pc-windows"), Some("gnu"))), + ("i386-pc-windows", (Some("i386"), Some("pc-windows"), None)), + ( + "i386-pc-windows-gnu", + (Some("i386"), Some("pc-windows"), Some("gnu")), + ), + ]; + + for (input, (arch, os, env)) in success_cases { + let partial_target_triple = PartialTargetTriple::new(input); + assert!( + partial_target_triple.is_some(), + "expected `{}` to create some partial target triple; got None", + input + ); + + let expected = PartialTargetTriple { + arch: arch.map(String::from), + os: os.map(String::from), + env: env.map(String::from), + }; + + assert_eq!( + partial_target_triple.unwrap(), + expected, + "input: `{}`", + input + ); + } + + let failure_cases = vec![ + "anything", + "any-other-thing", + "-", + "--", + "i386-", + "i386-pc-", + "i386-pc-windows-", + "-pc-windows", + "i386-pc-windows-anything", + "0000-00-00-", + "00000-000-000", + ]; + + for input in failure_cases { + let partial_target_triple = PartialTargetTriple::new(input); + assert!( + partial_target_triple.is_none(), + "expected `{}` to be `None`, was: `{:?}`", + input, + partial_target_triple + ); + } + } +}