mirror of
https://github.com/esp-rs/espup.git
synced 2025-09-27 04:40:27 +00:00
Introduce VersionNotFound, avoid accessing github in tests (#525)
* Introduce VersionNotFound, avoid accessing github in tests * Accept more version formats
This commit is contained in:
parent
a8fcaac931
commit
265b643fc7
@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Updated default GCC / Crosstools version to latest, [`esp-14.2.0_20241119`](https://github.com/espressif/crosstool-NG/releases/tag/esp-14.2.0_20241119) (#508)
|
- Updated default GCC / Crosstools version to latest, [`esp-14.2.0_20241119`](https://github.com/espressif/crosstool-NG/releases/tag/esp-14.2.0_20241119) (#508)
|
||||||
|
- `espup install -v` now accepts version strings with 1-4 parts. (#525)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
@ -38,6 +38,12 @@ pub enum Error {
|
|||||||
)]
|
)]
|
||||||
InvalidVersion(String),
|
InvalidVersion(String),
|
||||||
|
|
||||||
|
#[diagnostic(code(espup::toolchain::rust::version_not_found))]
|
||||||
|
#[error(
|
||||||
|
"The toolchain version '{0}' was not found. Verify that the release exists in https://github.com/esp-rs/rust-build/releases"
|
||||||
|
)]
|
||||||
|
VersionNotFound(String),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ pub async fn install(args: InstallOpts, install_mode: InstallMode) -> Result<()>
|
|||||||
let host_triple = get_host_triple(args.default_host)?;
|
let host_triple = get_host_triple(args.default_host)?;
|
||||||
let xtensa_rust_version = if let Some(toolchain_version) = &args.toolchain_version {
|
let xtensa_rust_version = if let Some(toolchain_version) = &args.toolchain_version {
|
||||||
if !args.skip_version_parse {
|
if !args.skip_version_parse {
|
||||||
XtensaRust::parse_version(toolchain_version)?
|
XtensaRust::find_latest_version_on_github(toolchain_version)?
|
||||||
} else {
|
} else {
|
||||||
toolchain_version.clone()
|
toolchain_version.clone()
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,10 @@ const XTENSA_RUST_API_URL: &str =
|
|||||||
"https://api.github.com/repos/esp-rs/rust-build/releases?page=1&per_page=100";
|
"https://api.github.com/repos/esp-rs/rust-build/releases?page=1&per_page=100";
|
||||||
|
|
||||||
/// Xtensa Rust Toolchain version regex.
|
/// Xtensa Rust Toolchain version regex.
|
||||||
pub const RE_EXTENDED_SEMANTIC_VERSION: &str = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)\.(?P<subpatch>0|[1-9]\d*)?$";
|
pub const RE_EXTENDED_SEMANTIC_VERSION: &str = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)\.(?P<subpatch>0|[1-9]\d*)$";
|
||||||
const RE_SEMANTIC_VERSION: &str =
|
/// Matches version strings with 1-4 parts.
|
||||||
r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)?$";
|
pub const RE_ANY_SEMANTIC_VERSION: &str =
|
||||||
|
r"^(0|[1-9]\d*)(\.(0|[1-9]\d*)(\.(0|[1-9]\d*)(\.(0|[1-9]\d*))?)?)?$";
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct XtensaRust {
|
pub struct XtensaRust {
|
||||||
@ -93,9 +94,10 @@ impl XtensaRust {
|
|||||||
version.retain(|c| c != 'v' && c != '"');
|
version.retain(|c| c != 'v' && c != '"');
|
||||||
|
|
||||||
// Validate the version format - handle both spawning and parsing errors
|
// Validate the version format - handle both spawning and parsing errors
|
||||||
let parse_task = tokio::task::spawn_blocking(move || Self::parse_version(&version))
|
let parse_task =
|
||||||
.await
|
tokio::task::spawn_blocking(move || Self::find_latest_version_on_github(&version))
|
||||||
.map_err(|_| Error::SerializeJson)?;
|
.await
|
||||||
|
.map_err(|_| Error::SerializeJson)?;
|
||||||
|
|
||||||
let validated_version = parse_task?;
|
let validated_version = parse_task?;
|
||||||
|
|
||||||
@ -136,48 +138,67 @@ impl XtensaRust {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the version of the Xtensa toolchain.
|
/// Retrieves the latest version of the Xtensa toolchain.
|
||||||
pub fn parse_version(arg: &str) -> Result<String, Error> {
|
///
|
||||||
debug!("Parsing Xtensa Rust version: {arg}");
|
/// Note that this function issues a GitHub API request to retrieve the latest version of the Xtensa toolchain.
|
||||||
let re_extended = Regex::new(RE_EXTENDED_SEMANTIC_VERSION).unwrap();
|
pub fn find_latest_version_on_github(version: &str) -> Result<String, Error> {
|
||||||
let re_semver = Regex::new(RE_SEMANTIC_VERSION).unwrap();
|
debug!("Parsing Xtensa Rust version: {version}");
|
||||||
let json = github_query(XTENSA_RUST_API_URL)?;
|
let json = github_query(XTENSA_RUST_API_URL)?;
|
||||||
if re_semver.is_match(arg) {
|
|
||||||
let mut extended_versions: Vec<String> = Vec::new();
|
let mut candidates: Vec<String> = Vec::new();
|
||||||
for release in json.as_array().unwrap() {
|
for release in json.as_array().unwrap() {
|
||||||
let tag_name = release["tag_name"].to_string().replace(['\"', 'v'], "");
|
candidates.push(release["tag_name"].to_string().replace(['\"', 'v'], ""));
|
||||||
if tag_name.starts_with(arg) {
|
}
|
||||||
extended_versions.push(tag_name);
|
|
||||||
}
|
Self::find_latest_version(version, &candidates)
|
||||||
}
|
}
|
||||||
if extended_versions.is_empty() {
|
|
||||||
return Err(Error::InvalidVersion(arg.to_string()));
|
/// Find the latest matching version of the Xtensa toolchain.
|
||||||
}
|
///
|
||||||
let mut max_version = extended_versions.pop().unwrap();
|
/// This function takes a version string and a list of candidate versions and returns the latest matching version.
|
||||||
let mut max_subpatch = 0;
|
/// If no matching version is found, it returns an error.
|
||||||
for version in extended_versions {
|
///
|
||||||
let subpatch: i8 = re_extended
|
/// The list of candidate versions is expected to be given in the extended semantic version format.
|
||||||
.captures(&version)
|
fn find_latest_version(version: &str, candidates: &[String]) -> Result<String, Error> {
|
||||||
.and_then(|cap| {
|
lazy_static::lazy_static! {
|
||||||
cap.name("subpatch")
|
static ref RE_EXTENDED: Regex = Regex::new(RE_EXTENDED_SEMANTIC_VERSION).unwrap();
|
||||||
.map(|subpatch| subpatch.as_str().parse().unwrap())
|
static ref RE_ANY_SEMVER: Regex = Regex::new(RE_ANY_SEMANTIC_VERSION).unwrap();
|
||||||
})
|
};
|
||||||
.unwrap();
|
|
||||||
if subpatch > max_subpatch {
|
if !RE_ANY_SEMVER.is_match(version) {
|
||||||
max_subpatch = subpatch;
|
return Err(Error::InvalidVersion(version.to_string()));
|
||||||
max_version = version;
|
}
|
||||||
}
|
|
||||||
}
|
let extract_version_components = |version: &str| -> (u8, u8, u8, u8) {
|
||||||
return Ok(max_version);
|
RE_EXTENDED
|
||||||
} else if re_extended.is_match(arg) {
|
.captures(version)
|
||||||
for release in json.as_array().unwrap() {
|
.and_then(|cap| {
|
||||||
let tag_name = release["tag_name"].to_string().replace(['\"', 'v'], "");
|
let major = cap.name("major").unwrap().as_str().parse().ok()?;
|
||||||
if tag_name.starts_with(arg) {
|
let minor = cap.name("minor").unwrap().as_str().parse().ok()?;
|
||||||
return Ok(arg.to_string());
|
let patch = cap.name("patch").unwrap().as_str().parse().ok()?;
|
||||||
}
|
let subpatch = cap.name("subpatch").unwrap().as_str().parse().ok()?;
|
||||||
}
|
Some((major, minor, patch, subpatch))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| panic!("Version {version} is not in the extended semver format"))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure that if we are looking for 1.65.0.x, we don't consider 1.65.1.x or 1.66.0.x
|
||||||
|
let candidates = candidates.iter().filter(|v| v.starts_with(version));
|
||||||
|
|
||||||
|
// Now find the latest
|
||||||
|
let max_version = candidates
|
||||||
|
.map(move |candidate| {
|
||||||
|
let components = extract_version_components(candidate.as_str());
|
||||||
|
|
||||||
|
(candidate, components)
|
||||||
|
})
|
||||||
|
.max_by_key(|(_, components)| *components)
|
||||||
|
.map(|(version, _)| version.clone());
|
||||||
|
|
||||||
|
match max_version {
|
||||||
|
Some(version) => Ok(version),
|
||||||
|
None => Err(Error::VersionNotFound(version.to_string())),
|
||||||
}
|
}
|
||||||
Err(Error::InvalidVersion(arg.to_string()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the Xtensa Rust toolchain.
|
/// Removes the Xtensa Rust toolchain.
|
||||||
@ -474,18 +495,54 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_xtensa_rust_parse_version() {
|
fn test_xtensa_rust_parse_version() {
|
||||||
initialize_logger("debug");
|
initialize_logger("debug");
|
||||||
assert_eq!(XtensaRust::parse_version("1.65.0.0").unwrap(), "1.65.0.0");
|
let candidates = [
|
||||||
assert_eq!(XtensaRust::parse_version("1.65.0.1").unwrap(), "1.65.0.1");
|
String::from("1.64.0.0"),
|
||||||
assert_eq!(XtensaRust::parse_version("1.64.0.0").unwrap(), "1.64.0.0");
|
String::from("1.65.0.0"),
|
||||||
assert_eq!(XtensaRust::parse_version("1.82.0").unwrap(), "1.82.0.3");
|
String::from("1.65.0.1"),
|
||||||
assert_eq!(XtensaRust::parse_version("1.65.0").unwrap(), "1.65.0.1");
|
String::from("1.65.1.0"),
|
||||||
assert_eq!(XtensaRust::parse_version("1.64.0").unwrap(), "1.64.0.0");
|
String::from("1.82.0.3"),
|
||||||
assert!(XtensaRust::parse_version("422.0.0").is_err());
|
];
|
||||||
assert!(XtensaRust::parse_version("422.0.0.0").is_err());
|
assert_eq!(
|
||||||
assert!(XtensaRust::parse_version("a.1.1.1").is_err());
|
XtensaRust::find_latest_version("1.65.0.0", &candidates).unwrap(),
|
||||||
assert!(XtensaRust::parse_version("1.1.1.1.1").is_err());
|
"1.65.0.0"
|
||||||
assert!(XtensaRust::parse_version("1..1.1").is_err());
|
);
|
||||||
assert!(XtensaRust::parse_version("1._.*.1").is_err());
|
assert_eq!(
|
||||||
|
XtensaRust::find_latest_version("1.65", &candidates).unwrap(),
|
||||||
|
"1.65.1.0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
XtensaRust::find_latest_version("1.65.0.1", &candidates).unwrap(),
|
||||||
|
"1.65.0.1"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
XtensaRust::find_latest_version("1.64.0.0", &candidates).unwrap(),
|
||||||
|
"1.64.0.0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
XtensaRust::find_latest_version("1.82.0", &candidates).unwrap(),
|
||||||
|
"1.82.0.3"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
XtensaRust::find_latest_version("1.65.0", &candidates).unwrap(),
|
||||||
|
"1.65.0.1"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
XtensaRust::find_latest_version("1.64.0", &candidates).unwrap(),
|
||||||
|
"1.64.0.0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
XtensaRust::find_latest_version("1", &candidates).unwrap(),
|
||||||
|
"1.82.0.3"
|
||||||
|
);
|
||||||
|
assert!(XtensaRust::find_latest_version("1.", &candidates).is_err());
|
||||||
|
assert!(XtensaRust::find_latest_version("1.0.", &candidates).is_err());
|
||||||
|
assert!(XtensaRust::find_latest_version("1.0.0.", &candidates).is_err());
|
||||||
|
assert!(XtensaRust::find_latest_version("422.0.0", &candidates).is_err());
|
||||||
|
assert!(XtensaRust::find_latest_version("422.0.0.0", &candidates).is_err());
|
||||||
|
assert!(XtensaRust::find_latest_version("a.1.1.1", &candidates).is_err());
|
||||||
|
assert!(XtensaRust::find_latest_version("1.1.1.1.1", &candidates).is_err());
|
||||||
|
assert!(XtensaRust::find_latest_version("1..1.1", &candidates).is_err());
|
||||||
|
assert!(XtensaRust::find_latest_version("1._.*.1", &candidates).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user