mirror of
https://github.com/esp-rs/espup.git
synced 2025-09-26 20:30:28 +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
|
||||
- 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
|
||||
|
||||
|
@ -38,6 +38,12 @@ pub enum Error {
|
||||
)]
|
||||
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)]
|
||||
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 xtensa_rust_version = if let Some(toolchain_version) = &args.toolchain_version {
|
||||
if !args.skip_version_parse {
|
||||
XtensaRust::parse_version(toolchain_version)?
|
||||
XtensaRust::find_latest_version_on_github(toolchain_version)?
|
||||
} else {
|
||||
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";
|
||||
|
||||
/// 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*)?$";
|
||||
const RE_SEMANTIC_VERSION: &str =
|
||||
r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>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*)$";
|
||||
/// Matches version strings with 1-4 parts.
|
||||
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)]
|
||||
pub struct XtensaRust {
|
||||
@ -93,9 +94,10 @@ impl XtensaRust {
|
||||
version.retain(|c| c != 'v' && c != '"');
|
||||
|
||||
// Validate the version format - handle both spawning and parsing errors
|
||||
let parse_task = tokio::task::spawn_blocking(move || Self::parse_version(&version))
|
||||
.await
|
||||
.map_err(|_| Error::SerializeJson)?;
|
||||
let parse_task =
|
||||
tokio::task::spawn_blocking(move || Self::find_latest_version_on_github(&version))
|
||||
.await
|
||||
.map_err(|_| Error::SerializeJson)?;
|
||||
|
||||
let validated_version = parse_task?;
|
||||
|
||||
@ -136,48 +138,67 @@ impl XtensaRust {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the version of the Xtensa toolchain.
|
||||
pub fn parse_version(arg: &str) -> Result<String, Error> {
|
||||
debug!("Parsing Xtensa Rust version: {arg}");
|
||||
let re_extended = Regex::new(RE_EXTENDED_SEMANTIC_VERSION).unwrap();
|
||||
let re_semver = Regex::new(RE_SEMANTIC_VERSION).unwrap();
|
||||
/// Retrieves the latest version of the Xtensa toolchain.
|
||||
///
|
||||
/// Note that this function issues a GitHub API request to retrieve the latest version of the Xtensa toolchain.
|
||||
pub fn find_latest_version_on_github(version: &str) -> Result<String, Error> {
|
||||
debug!("Parsing Xtensa Rust version: {version}");
|
||||
let json = github_query(XTENSA_RUST_API_URL)?;
|
||||
if re_semver.is_match(arg) {
|
||||
let mut extended_versions: Vec<String> = Vec::new();
|
||||
for release in json.as_array().unwrap() {
|
||||
let tag_name = release["tag_name"].to_string().replace(['\"', 'v'], "");
|
||||
if tag_name.starts_with(arg) {
|
||||
extended_versions.push(tag_name);
|
||||
}
|
||||
}
|
||||
if extended_versions.is_empty() {
|
||||
return Err(Error::InvalidVersion(arg.to_string()));
|
||||
}
|
||||
let mut max_version = extended_versions.pop().unwrap();
|
||||
let mut max_subpatch = 0;
|
||||
for version in extended_versions {
|
||||
let subpatch: i8 = re_extended
|
||||
.captures(&version)
|
||||
.and_then(|cap| {
|
||||
cap.name("subpatch")
|
||||
.map(|subpatch| subpatch.as_str().parse().unwrap())
|
||||
})
|
||||
.unwrap();
|
||||
if subpatch > max_subpatch {
|
||||
max_subpatch = subpatch;
|
||||
max_version = version;
|
||||
}
|
||||
}
|
||||
return Ok(max_version);
|
||||
} else if re_extended.is_match(arg) {
|
||||
for release in json.as_array().unwrap() {
|
||||
let tag_name = release["tag_name"].to_string().replace(['\"', 'v'], "");
|
||||
if tag_name.starts_with(arg) {
|
||||
return Ok(arg.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let mut candidates: Vec<String> = Vec::new();
|
||||
for release in json.as_array().unwrap() {
|
||||
candidates.push(release["tag_name"].to_string().replace(['\"', 'v'], ""));
|
||||
}
|
||||
|
||||
Self::find_latest_version(version, &candidates)
|
||||
}
|
||||
|
||||
/// Find the latest matching version of the Xtensa toolchain.
|
||||
///
|
||||
/// This function takes a version string and a list of candidate versions and returns the latest matching version.
|
||||
/// If no matching version is found, it returns an error.
|
||||
///
|
||||
/// The list of candidate versions is expected to be given in the extended semantic version format.
|
||||
fn find_latest_version(version: &str, candidates: &[String]) -> Result<String, Error> {
|
||||
lazy_static::lazy_static! {
|
||||
static ref RE_EXTENDED: Regex = Regex::new(RE_EXTENDED_SEMANTIC_VERSION).unwrap();
|
||||
static ref RE_ANY_SEMVER: Regex = Regex::new(RE_ANY_SEMANTIC_VERSION).unwrap();
|
||||
};
|
||||
|
||||
if !RE_ANY_SEMVER.is_match(version) {
|
||||
return Err(Error::InvalidVersion(version.to_string()));
|
||||
}
|
||||
|
||||
let extract_version_components = |version: &str| -> (u8, u8, u8, u8) {
|
||||
RE_EXTENDED
|
||||
.captures(version)
|
||||
.and_then(|cap| {
|
||||
let major = cap.name("major").unwrap().as_str().parse().ok()?;
|
||||
let minor = cap.name("minor").unwrap().as_str().parse().ok()?;
|
||||
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.
|
||||
@ -474,18 +495,54 @@ mod tests {
|
||||
#[test]
|
||||
fn test_xtensa_rust_parse_version() {
|
||||
initialize_logger("debug");
|
||||
assert_eq!(XtensaRust::parse_version("1.65.0.0").unwrap(), "1.65.0.0");
|
||||
assert_eq!(XtensaRust::parse_version("1.65.0.1").unwrap(), "1.65.0.1");
|
||||
assert_eq!(XtensaRust::parse_version("1.64.0.0").unwrap(), "1.64.0.0");
|
||||
assert_eq!(XtensaRust::parse_version("1.82.0").unwrap(), "1.82.0.3");
|
||||
assert_eq!(XtensaRust::parse_version("1.65.0").unwrap(), "1.65.0.1");
|
||||
assert_eq!(XtensaRust::parse_version("1.64.0").unwrap(), "1.64.0.0");
|
||||
assert!(XtensaRust::parse_version("422.0.0").is_err());
|
||||
assert!(XtensaRust::parse_version("422.0.0.0").is_err());
|
||||
assert!(XtensaRust::parse_version("a.1.1.1").is_err());
|
||||
assert!(XtensaRust::parse_version("1.1.1.1.1").is_err());
|
||||
assert!(XtensaRust::parse_version("1..1.1").is_err());
|
||||
assert!(XtensaRust::parse_version("1._.*.1").is_err());
|
||||
let candidates = [
|
||||
String::from("1.64.0.0"),
|
||||
String::from("1.65.0.0"),
|
||||
String::from("1.65.0.1"),
|
||||
String::from("1.65.1.0"),
|
||||
String::from("1.82.0.3"),
|
||||
];
|
||||
assert_eq!(
|
||||
XtensaRust::find_latest_version("1.65.0.0", &candidates).unwrap(),
|
||||
"1.65.0.0"
|
||||
);
|
||||
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]
|
||||
|
Loading…
x
Reference in New Issue
Block a user