From 2badbd2d7ed51bf33c1b547b4c837b600574bd4a Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Wed, 25 May 2022 09:15:21 -0700 Subject: [PATCH] refactor: force semver versions, change updater `should_install` sig (#4215) --- .changes/package-info-version.md | 5 ++ .changes/should-install-refactor.md | 5 ++ core/tauri-codegen/Cargo.toml | 1 + core/tauri-codegen/src/context.rs | 5 +- core/tauri-codegen/src/embedded_assets.rs | 5 +- core/tauri-utils/Cargo.toml | 1 + core/tauri-utils/src/config.rs | 22 +++++-- core/tauri-utils/src/io.rs | 2 +- core/tauri-utils/src/lib.rs | 3 +- core/tauri/src/api/cli.rs | 7 +- core/tauri/src/endpoints/app.rs | 2 +- core/tauri/src/test/mod.rs | 2 +- core/tauri/src/updater/core.rs | 79 ++++++++++++----------- core/tauri/src/updater/mod.rs | 14 ++-- examples/api/src-tauri/Cargo.lock | 2 + 15 files changed, 100 insertions(+), 55 deletions(-) create mode 100644 .changes/package-info-version.md create mode 100644 .changes/should-install-refactor.md diff --git a/.changes/package-info-version.md b/.changes/package-info-version.md new file mode 100644 index 000000000..3082876f4 --- /dev/null +++ b/.changes/package-info-version.md @@ -0,0 +1,5 @@ +--- +"tauri": patch +--- + +**Breaking change:** `PackageInfo::version` is now a `semver::Version` instead of a `String`. diff --git a/.changes/should-install-refactor.md b/.changes/should-install-refactor.md new file mode 100644 index 000000000..8db122c1a --- /dev/null +++ b/.changes/should-install-refactor.md @@ -0,0 +1,5 @@ +--- +"tauri": patch +--- + +**Breaking change**: `UpdateBuilder::should_update` now takes the current version as a `semver::Version` and a `RemoteRelease` struct, allowing you to check other release fields. diff --git a/core/tauri-codegen/Cargo.toml b/core/tauri-codegen/Cargo.toml index aec7decc3..03f7c1fcd 100644 --- a/core/tauri-codegen/Cargo.toml +++ b/core/tauri-codegen/Cargo.toml @@ -25,6 +25,7 @@ walkdir = "2" brotli = { version = "3", optional = true, default-features = false, features = [ "std" ] } regex = { version = "1.5.6", optional = true } uuid = { version = "1", features = [ "v4" ] } +semver = "1" [target."cfg(windows)".dependencies] ico = "0.1" diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index 537f71cea..0cc9f57a4 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use std::ffi::OsStr; use std::path::{Path, PathBuf}; +use std::{ffi::OsStr, str::FromStr}; use proc_macro2::TokenStream; use quote::quote; @@ -220,6 +220,7 @@ pub fn context_codegen(data: ContextData) -> Result Result::Ok(state) }, )?; diff --git a/core/tauri-utils/Cargo.toml b/core/tauri-utils/Cargo.toml index 79c68680f..7635ab8af 100644 --- a/core/tauri-utils/Cargo.toml +++ b/core/tauri-utils/Cargo.toml @@ -33,6 +33,7 @@ json-patch = "0.2" glob = { version = "0.3.0", optional = true } walkdir = { version = "2", optional = true } memchr = "2.4" +semver = "1" [target."cfg(target_os = \"linux\")".dependencies] heck = "0.4" diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index cc06b710b..555ca3251 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -14,6 +14,7 @@ use heck::ToKebabCase; #[cfg(feature = "schema")] use schemars::JsonSchema; +use semver::Version; use serde::{ de::{Deserializer, Error as DeError, Visitor}, Deserialize, Serialize, Serializer, @@ -27,6 +28,7 @@ use std::{ fmt::{self, Display}, fs::read_to_string, path::PathBuf, + str::FromStr, }; /// Items to help with parsing content into a [`Config`]. @@ -2182,13 +2184,25 @@ impl<'d> serde::Deserialize<'d> for PackageVersion { .get("version") .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))? .as_str() - .ok_or_else(|| DeError::custom("`version` must be a string"))?; - Ok(PackageVersion(version.into())) + .ok_or_else(|| { + DeError::custom(format!("`{} > version` must be a string", path.display())) + })?; + Ok(PackageVersion( + Version::from_str(version) + .map_err(|_| DeError::custom("`package > version` must be a semver string"))? + .to_string(), + )) } else { - Err(DeError::custom("value is not a path to a JSON object")) + Err(DeError::custom( + "`package > version` value is not a path to a JSON object", + )) } } else { - Ok(PackageVersion(value.into())) + Ok(PackageVersion( + Version::from_str(value) + .map_err(|_| DeError::custom("`package > version` must be a semver string"))? + .to_string(), + )) } } } diff --git a/core/tauri-utils/src/io.rs b/core/tauri-utils/src/io.rs index 0e736eafa..cd5c7c22e 100644 --- a/core/tauri-utils/src/io.rs +++ b/core/tauri-utils/src/io.rs @@ -8,7 +8,7 @@ use std::io::BufRead; /// Read a line breaking in both \n and \r. /// -/// Adapted from https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_line +/// Adapted from . pub fn read_line(r: &mut R, buf: &mut Vec) -> std::io::Result { let mut read = 0; loop { diff --git a/core/tauri-utils/src/lib.rs b/core/tauri-utils/src/lib.rs index b5f96c6c2..55aba1c9d 100644 --- a/core/tauri-utils/src/lib.rs +++ b/core/tauri-utils/src/lib.rs @@ -7,6 +7,7 @@ use std::fmt::Display; +use semver::Version; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub mod assets; @@ -27,7 +28,7 @@ pub struct PackageInfo { /// App name pub name: String, /// App version - pub version: String, + pub version: Version, /// The crate authors. pub authors: &'static str, /// The crate description. diff --git a/core/tauri/src/api/cli.rs b/core/tauri/src/api/cli.rs index 8b08260d9..7622dea4e 100644 --- a/core/tauri/src/api/cli.rs +++ b/core/tauri/src/api/cli.rs @@ -102,7 +102,8 @@ pub fn get_matches(cli: &CliConfig, package_info: &PackageInfo) -> crate::api::R .description() .unwrap_or(&package_info.description.to_string()) .to_string(); - let app = get_app(package_info, &package_info.name, Some(&about), cli); + let version = &*package_info.version.to_string(); + let app = get_app(package_info, version, &package_info.name, Some(&about), cli); match app.try_get_matches() { Ok(matches) => Ok(get_matches_internal(cli, &matches)), Err(e) => match ErrorExt::kind(&e) { @@ -178,13 +179,14 @@ fn map_matches(config: &CliConfig, matches: &ArgMatches, cli_matches: &mut Match fn get_app<'a>( package_info: &'a PackageInfo, + version: &'a str, command_name: &'a str, about: Option<&'a String>, config: &'a CliConfig, ) -> App<'a> { let mut app = App::new(command_name) .author(package_info.authors) - .version(&*package_info.version); + .version(version); if let Some(about) = about { app = app.about(&**about); @@ -210,6 +212,7 @@ fn get_app<'a>( for (subcommand_name, subcommand) in subcommands { let clap_subcommand = get_app( package_info, + version, subcommand_name, subcommand.description(), subcommand, diff --git a/core/tauri/src/endpoints/app.rs b/core/tauri/src/endpoints/app.rs index 58e6fe4bc..b357c9e4c 100644 --- a/core/tauri/src/endpoints/app.rs +++ b/core/tauri/src/endpoints/app.rs @@ -23,7 +23,7 @@ pub enum Cmd { impl Cmd { fn get_app_version(context: InvokeContext) -> super::Result { - Ok(context.package_info.version) + Ok(context.package_info.version.to_string()) } fn get_app_name(context: InvokeContext) -> super::Result { diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index 7bfb71332..1771c4484 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -70,7 +70,7 @@ pub fn mock_context(assets: A) -> crate::Context { system_tray_icon: None, package_info: crate::PackageInfo { name: "test".into(), - version: "0.1.0".into(), + version: "0.1.0".parse().unwrap(), authors: "Tauri", description: "Tauri test", }, diff --git a/core/tauri/src/updater/core.rs b/core/tauri/src/updater/core.rs index 6a0483248..6566812b5 100644 --- a/core/tauri/src/updater/core.rs +++ b/core/tauri/src/updater/core.rs @@ -6,10 +6,7 @@ use super::error::{Error, Result}; #[cfg(feature = "updater")] use crate::api::file::{ArchiveFormat, Extract, Move}; use crate::{ - api::{ - http::{ClientBuilder, HttpRequestBuilder}, - version, - }, + api::http::{ClientBuilder, HttpRequestBuilder}, AppHandle, Manager, Runtime, }; use base64::decode; @@ -63,14 +60,14 @@ pub enum RemoteReleaseInner { #[derive(Debug, Serialize)] pub struct RemoteRelease { /// Version to install. - pub version: Version, + version: Version, /// Release notes. - pub notes: Option, + notes: Option, /// Release date. - pub pub_date: String, + pub_date: String, /// Release data. #[serde(flatten)] - pub data: RemoteReleaseInner, + data: RemoteReleaseInner, } impl<'de> Deserialize<'de> for RemoteRelease { @@ -144,18 +141,22 @@ where } impl RemoteRelease { + /// The release version. pub fn version(&self) -> &Version { &self.version } - pub fn notes(&self) -> &Option { - &self.notes + /// The release notes. + pub fn notes(&self) -> Option<&String> { + self.notes.as_ref() } + /// The release date. pub fn pub_date(&self) -> &String { &self.pub_date } + /// The release's download URL for the given target. pub fn download_url(&self, target: &str) -> Result<&Url> { match self.data { RemoteReleaseInner::Dynamic(ref platform) => Ok(&platform.url), @@ -167,6 +168,7 @@ impl RemoteRelease { } } + /// The release's signature for the given target. pub fn signature(&self, target: &str) -> Result<&String> { match self.data { RemoteReleaseInner::Dynamic(ref platform) => Ok(&platform.signature), @@ -196,14 +198,14 @@ pub struct UpdateBuilder { /// Application handle. pub app: AppHandle, /// Current version we are running to compare with announced version - pub current_version: String, + pub current_version: Version, /// The URLs to checks updates. We suggest at least one fallback on a different domain. pub urls: Vec, /// The platform the updater will check and install the update. Default is from `get_updater_target` pub target: Option, /// The current executable path. Default is automatically extracted. pub executable_path: Option, - should_install: Option bool + Send>>, + should_install: Option bool + Send>>, timeout: Option, headers: HeaderMap, } @@ -230,7 +232,8 @@ impl UpdateBuilder { urls: Vec::new(), target: None, executable_path: None, - current_version: env!("CARGO_PKG_VERSION").into(), + // safe to unwrap: CARGO_PKG_VERSION is also a valid semver value + current_version: env!("CARGO_PKG_VERSION").parse().unwrap(), should_install: None, timeout: None, headers: Default::default(), @@ -263,8 +266,8 @@ impl UpdateBuilder { /// Set the current app version, used to compare against the latest available version. /// The `cargo_crate_version!` macro can be used to pull the version from your `Cargo.toml` - pub fn current_version(mut self, ver: impl Into) -> Self { - self.current_version = ver.into(); + pub fn current_version(mut self, ver: Version) -> Self { + self.current_version = ver; self } @@ -281,7 +284,10 @@ impl UpdateBuilder { self } - pub fn should_install bool + Send + 'static>(mut self, f: F) -> Self { + pub fn should_install bool + Send + 'static>( + mut self, + f: F, + ) -> Self { self.should_install.replace(Box::new(f)); self } @@ -359,7 +365,7 @@ impl UpdateBuilder { // The main objective is if the update URL is defined via the Cargo.toml // the URL will be generated dynamicly let fixed_link = url - .replace("{{current_version}}", &self.current_version) + .replace("{{current_version}}", &self.current_version.to_string()) .replace("{{target}}", &target) .replace("{{arch}}", arch); @@ -411,10 +417,9 @@ impl UpdateBuilder { // did the announced version is greated than our current one? let should_update = if let Some(comparator) = self.should_install.take() { - comparator(&self.current_version, &final_release.version().to_string()) + comparator(&self.current_version, &final_release) } else { - version::is_greater(&self.current_version, &final_release.version().to_string()) - .unwrap_or(false) + final_release.version() > &self.current_version }; headers.remove("Accept"); @@ -427,9 +432,9 @@ impl UpdateBuilder { should_update, version: final_release.version().to_string(), date: final_release.pub_date().to_string(), - current_version: self.current_version.to_owned(), + current_version: self.current_version, download_url: final_release.download_url(&json_target)?.to_owned(), - body: final_release.notes().to_owned(), + body: final_release.notes().cloned(), signature: final_release.signature(&json_target)?.to_owned(), #[cfg(target_os = "windows")] with_elevated_task: final_release.with_elevated_task(&json_target)?, @@ -454,7 +459,7 @@ pub struct Update { /// Version announced pub version: String, /// Running version - pub current_version: String, + pub current_version: Version, /// Update publish date pub date: String, /// Target @@ -1017,7 +1022,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) - .current_version("0.0.0") + .current_version("0.0.0".parse().unwrap()) .url(mockito::server_url()) .build()); @@ -1036,7 +1041,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) - .current_version("0.0.0") + .current_version("0.0.0".parse().unwrap()) .url(mockito::server_url()) .build()); @@ -1055,7 +1060,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) - .current_version("0.0.0") + .current_version("0.0.0".parse().unwrap()) .target("windows-x86_64") .url(mockito::server_url()) .build()); @@ -1081,7 +1086,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) - .current_version("10.0.0") + .current_version("10.0.0".parse().unwrap()) .url(mockito::server_url()) .build()); @@ -1104,7 +1109,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) - .current_version("1.0.0") + .current_version("1.0.0".parse().unwrap()) .url(format!( "{}/darwin-aarch64/{{{{current_version}}}}", mockito::server_url() @@ -1130,7 +1135,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) - .current_version("1.0.0") + .current_version("1.0.0".parse().unwrap()) .url( url::Url::parse(&format!( "{}/darwin-aarch64/{{{{current_version}}}}", @@ -1147,7 +1152,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) - .current_version("1.0.0") + .current_version("1.0.0".parse().unwrap()) .urls(&[url::Url::parse(&format!( "{}/darwin-aarch64/{{{{current_version}}}}", mockito::server_url() @@ -1176,7 +1181,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) - .current_version("1.0.0") + .current_version("1.0.0".parse().unwrap()) .url(format!( "{}/windows-x86_64/{{{{current_version}}}}", mockito::server_url() @@ -1202,7 +1207,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) - .current_version("10.0.0") + .current_version("10.0.0".parse().unwrap()) .url(format!( "{}/darwin-aarch64/{{{{current_version}}}}", mockito::server_url() @@ -1226,7 +1231,7 @@ mod test { let check_update = block!(builder(app.handle()) .url("http://badurl.www.tld/1".into()) .url(mockito::server_url()) - .current_version("0.0.1") + .current_version("0.0.1".parse().unwrap()) .build()); let updater = check_update.expect("Can't check remote update"); @@ -1245,7 +1250,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) .urls(&["http://badurl.www.tld/1".into(), mockito::server_url(),]) - .current_version("0.0.1") + .current_version("0.0.1".parse().unwrap()) .build()); let updater = check_update.expect("Can't check remote update"); @@ -1377,7 +1382,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) .url(mockito::server_url()) - .current_version("0.0.1") + .current_version("0.0.1".parse().unwrap()) .target("test-target") .build()); if let Err(e) = check_update { @@ -1469,7 +1474,7 @@ mod test { let app = crate::test::mock_app(); let check_update = block!(builder(app.handle()) .url(mockito::server_url()) - .current_version("0.0.1") + .current_version("0.0.1".parse().unwrap()) .target("test-target") .build()); if let Err(e) = check_update { @@ -1561,7 +1566,7 @@ mod test { // test path -- in production you shouldn't have to provide it .executable_path(my_executable) // make sure we force an update - .current_version("1.0.0") + .current_version("1.0.0".parse().unwrap()) .build()); #[cfg(target_os = "linux")] diff --git a/core/tauri/src/updater/mod.rs b/core/tauri/src/updater/mod.rs index 25562dc4e..54ac14633 100644 --- a/core/tauri/src/updater/mod.rs +++ b/core/tauri/src/updater/mod.rs @@ -449,8 +449,9 @@ mod error; use std::time::Duration; use http::header::{HeaderName, HeaderValue}; +use semver::Version; -pub use self::error::Error; +pub use self::{core::RemoteRelease, error::Error}; /// Alias for [`std::result::Result`] using our own [`Error`]. pub type Result = std::result::Result; @@ -626,7 +627,10 @@ impl UpdateBuilder { /// Ok(()) /// }); /// ``` - pub fn should_install bool + Send + 'static>(mut self, f: F) -> Self { + pub fn should_install bool + Send + 'static>( + mut self, + f: F, + ) -> Self { self.inner = self.inner.should_install(f); self } @@ -737,7 +741,7 @@ impl UpdateResponse { } /// The current version of the application as read by the updater. - pub fn current_version(&self) -> &str { + pub fn current_version(&self) -> &Version { &self.update.current_version } @@ -774,7 +778,7 @@ pub(crate) async fn check_update_with_dialog(handle: AppHandle) { let mut builder = self::core::builder(handle.clone()) .urls(&endpoints[..]) - .current_version(&package_info.version); + .current_version(package_info.version); if let Some(target) = &handle.updater_settings.target { builder = builder.target(target); } @@ -865,7 +869,7 @@ pub fn builder(handle: AppHandle) -> UpdateBuilder { let mut builder = self::core::builder(handle.clone()) .urls(&endpoints[..]) - .current_version(&package_info.version); + .current_version(package_info.version); if let Some(target) = &handle.updater_settings.target { builder = builder.target(target); } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index e8ec76e07..b6e26c631 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -3136,6 +3136,7 @@ dependencies = [ "proc-macro2", "quote", "regex", + "semver 1.0.7", "serde", "serde_json", "sha2", @@ -3208,6 +3209,7 @@ dependencies = [ "phf 0.10.1", "proc-macro2", "quote", + "semver 1.0.7", "serde", "serde_json", "serde_with",