Skip to content

Commit

Permalink
feat(updater): separate intel and apple silicon targets, closes #3359 (
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog committed Mar 23, 2022
1 parent bf89a05 commit 579312f
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 67 deletions.
5 changes: 5 additions & 0 deletions .changes/custom-updater-target.md
@@ -0,0 +1,5 @@
---
"tauri": patch
---

Added `updater_target` method to the `Builder` struct.
5 changes: 5 additions & 0 deletions .changes/refactor-updater-target.md
@@ -0,0 +1,5 @@
---
"tauri": patch
---

**Breaking change:** The updater default targets have been renamed to include better support for different architectures.
11 changes: 11 additions & 0 deletions core/tauri-utils/src/config.rs
Expand Up @@ -1822,6 +1822,17 @@ pub struct UpdaterConfig {
#[serde(default = "default_dialog")]
pub dialog: bool,
/// The updater endpoints. TLS is enforced on production.
///
/// The updater URL can contain the following variables:
/// - {{current_version}}: The version of the app that is requesting the update
/// - {{target}}: The operating system name (one of `linux`, `windows` or `darwin`).
/// - {{arch}}: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`).
///
/// # Examples
///
/// - "https://my.cdn.com/latest.json": a raw JSON endpoint that returns the latest version and download links for each platform.
/// - "https://updates.app.dev/{{target}}?version={{current_version}}&arch={{arch}}": a dedicated API with positional and query string arguments.
#[allow(rustdoc::bare_urls)]
pub endpoints: Option<Vec<UpdaterEndpoint>>,
/// Signature public key.
#[serde(default)] // use default just so the schema doesn't flag it as required
Expand Down
58 changes: 58 additions & 0 deletions core/tauri/src/app.rs
Expand Up @@ -167,6 +167,12 @@ impl<R: Runtime> GlobalWindowEvent<R> {
}
}

#[cfg(updater)]
#[derive(Debug, Clone, Default)]
pub(crate) struct UpdaterSettings {
pub(crate) target: Option<String>,
}

/// The path resolver is a helper for the application-specific [`crate::api::path`] APIs.
#[derive(Debug, Clone)]
pub struct PathResolver {
Expand Down Expand Up @@ -217,6 +223,9 @@ pub struct AppHandle<R: Runtime> {
clipboard_manager: R::ClipboardManager,
#[cfg(feature = "system-tray")]
tray_handle: Option<tray::SystemTrayHandle<R>>,
/// The updater configuration.
#[cfg(updater)]
pub(crate) updater_settings: UpdaterSettings,
}

impl<R: Runtime> AppHandle<R> {
Expand Down Expand Up @@ -264,6 +273,8 @@ impl<R: Runtime> Clone for AppHandle<R> {
clipboard_manager: self.clipboard_manager.clone(),
#[cfg(feature = "system-tray")]
tray_handle: self.tray_handle.clone(),
#[cfg(updater)]
updater_settings: self.updater_settings.clone(),
}
}
}
Expand Down Expand Up @@ -677,6 +688,10 @@ pub struct Builder<R: Runtime> {
/// System tray event handlers.
#[cfg(feature = "system-tray")]
system_tray_event_listeners: Vec<SystemTrayEventListener<R>>,

/// The updater configuration.
#[cfg(updater)]
updater_settings: UpdaterSettings,
}

impl<R: Runtime> Builder<R> {
Expand All @@ -702,6 +717,8 @@ impl<R: Runtime> Builder<R> {
system_tray: None,
#[cfg(feature = "system-tray")]
system_tray_event_listeners: Vec::new(),
#[cfg(updater)]
updater_settings: Default::default(),
}
}

Expand Down Expand Up @@ -1087,6 +1104,45 @@ impl<R: Runtime> Builder<R> {
self
}

/// Sets the current platform's target name for the updater.
///
/// By default Tauri looks for a target in the format "{target}-{arch}",
/// where *target* is one of `darwin`, `linux` and `windows`
/// and *arch* is one of `i686`, `x86_64`, `aarch64` and `armv7`
/// based on the running platform. You can change the target name with this function.
///
/// # Examples
///
/// - Use a macOS Universal binary target name:
///
/// ```no_run
/// let mut builder = tauri::Builder::default();
/// #[cfg(target_os = "macos")]
/// {
/// builder = builder.updater_target("darwin-universal");
/// }
/// ```
///
/// - Append debug information to the target:
///
/// ```no_run
/// let kind = if cfg!(debug_assertions) { "debug" } else { "release" };
/// tauri::Builder::default()
/// .updater_target(format!("{}-{}", tauri::updater::target().unwrap(), kind));
/// ```
///
/// - Use the platform's target triple:
///
/// ```no_run
/// tauri::Builder::default()
/// .updater_target(tauri::utils::platform::target_triple().unwrap());
/// ```
#[cfg(updater)]
pub fn updater_target<T: Into<String>>(mut self, target: T) -> Self {
self.updater_settings.target.replace(target.into());
self
}

/// Builds the application.
#[allow(clippy::type_complexity)]
pub fn build<A: Assets>(mut self, context: Context<A>) -> crate::Result<App<R>> {
Expand Down Expand Up @@ -1186,6 +1242,8 @@ impl<R: Runtime> Builder<R> {
clipboard_manager,
#[cfg(feature = "system-tray")]
tray_handle: None,
#[cfg(updater)]
updater_settings: self.updater_settings,
},
};

Expand Down
91 changes: 49 additions & 42 deletions core/tauri/src/updater/core.rs
Expand Up @@ -235,8 +235,7 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
self
}

/// Set the target (os)
/// win32, win64, darwin and linux are currently supported
/// Set the target name. Represents the string that is looked up on the updater API or response JSON.
#[allow(dead_code)]
pub fn target(mut self, target: &str) -> Self {
self.target = Some(target.to_owned());
Expand Down Expand Up @@ -266,12 +265,17 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
// If no executable path provided, we use current_exe from tauri_utils
let executable_path = self.executable_path.unwrap_or(current_exe()?);

// Did the target is provided by the config?
// Should be: linux, darwin, win32 or win64
let has_custom_target = self.target.is_some();
let target = self
.target
.or_else(get_updater_target)
.or_else(|| get_updater_target().map(Into::into))
.ok_or(Error::UnsupportedPlatform)?;
let arch = get_updater_arch().ok_or(Error::UnsupportedPlatform)?;
let json_target = if has_custom_target {
target.clone()
} else {
format!("{}-{}", target, arch)
};

// Get the extract_path from the provided executable_path
let extract_path = extract_path_from_executable(&self.app.state::<Env>(), &executable_path);
Expand All @@ -292,18 +296,17 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
// Allow fallback if more than 1 urls is provided
let mut last_error: Option<Error> = None;
for url in &self.urls {
// replace {{current_version}} and {{target}} in the provided URL
// replace {{current_version}}, {{target}} and {{arch}} in the provided URL
// this is usefull if we need to query example
// https://releases.myapp.com/update/{{target}}/{{current_version}}
// https://releases.myapp.com/update/{{target}}/{{arch}}/{{current_version}}
// will be transleted into ->
// https://releases.myapp.com/update/darwin/1.0.0
// https://releases.myapp.com/update/darwin/aarch64/1.0.0
// The main objective is if the update URL is defined via the Cargo.toml
// the URL will be generated dynamicly
let fixed_link = str::replace(
&str::replace(url, "{{current_version}}", current_version),
"{{target}}",
&target,
);
let fixed_link = url
.replace("{{current_version}}", current_version)
.replace("{{target}}", &target)
.replace("{{arch}}", arch);

// we want JSON only
let mut headers = HashMap::new();
Expand Down Expand Up @@ -335,7 +338,7 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
return Err(Error::UpToDate);
};
// Convert the remote result to our local struct
let built_release = RemoteRelease::from_release(&res.data, &target);
let built_release = RemoteRelease::from_release(&res.data, &json_target);
// make sure all went well and the remote data is compatible
// with what we need locally
match built_release {
Expand Down Expand Up @@ -745,23 +748,27 @@ fn copy_files_and_run<R: Read + Seek>(archive_buffer: R, extract_path: &Path) ->
Ok(())
}

/// Returns a target os
/// We do not use a helper function like the target_triple
/// from tauri-utils because this function return `None` if
/// the updater do not support the platform.
///
/// Available target: `linux, darwin, win32, win64`
pub fn get_updater_target() -> Option<String> {
pub(crate) fn get_updater_target() -> Option<&'static str> {
if cfg!(target_os = "linux") {
Some("linux".into())
Some("linux")
} else if cfg!(target_os = "macos") {
Some("darwin".into())
Some("darwin")
} else if cfg!(target_os = "windows") {
if cfg!(target_pointer_width = "32") {
Some("win32".into())
} else {
Some("win64".into())
}
Some("windows")
} else {
None
}
}

pub(crate) fn get_updater_arch() -> Option<&'static str> {
if cfg!(target_arch = "x86") {
Some("i686")
} else if cfg!(target_arch = "x86_64") {
Some("x86_64")
} else if cfg!(target_arch = "arm") {
Some("armv7")
} else if cfg!(target_arch = "aarch64") {
Some("aarch64")
} else {
None
}
Expand Down Expand Up @@ -859,15 +866,15 @@ mod test {
"notes": "Test version !",
"pub_date": "2020-06-22T19:25:57Z",
"platforms": {
"darwin": {
"darwin-aarch64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJZVGdpKzJmRWZ0SkRvWS9TdFpqTU9xcm1mUmJSSG5OWVlwSklrWkN1SFpWbmh4SDlBcTU3SXpjbm0xMmRjRkphbkpVeGhGcTdrdzlrWGpGVWZQSWdzPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1MDU3CWZpbGU6L1VzZXJzL3J1bm5lci9ydW5uZXJzLzIuMjYzLjAvd29yay90YXVyaS90YXVyaS90YXVyaS9leGFtcGxlcy9jb21tdW5pY2F0aW9uL3NyYy10YXVyaS90YXJnZXQvZGVidWcvYnVuZGxlL29zeC9hcHAuYXBwLnRhci5negp4ZHFlUkJTVnpGUXdDdEhydTE5TGgvRlVPeVhjTnM5RHdmaGx3c0ZPWjZXWnFwVDRNWEFSbUJTZ1ZkU1IwckJGdmlwSzJPd00zZEZFN2hJOFUvL1FDZz09Cg==",
"url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.app.tar.gz"
},
"linux": {
"linux-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOWZSM29hTFNmUEdXMHRoOC81WDFFVVFRaXdWOUdXUUdwT0NlMldqdXkyaWVieXpoUmdZeXBJaXRqSm1YVmczNXdRL1Brc0tHb1NOTzhrL1hadFcxdmdnPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE3MzQzCWZpbGU6L2hvbWUvcnVubmVyL3dvcmsvdGF1cmkvdGF1cmkvdGF1cmkvZXhhbXBsZXMvY29tbXVuaWNhdGlvbi9zcmMtdGF1cmkvdGFyZ2V0L2RlYnVnL2J1bmRsZS9hcHBpbWFnZS9hcHAuQXBwSW1hZ2UudGFyLmd6CmRUTUM2bWxnbEtTbUhOZGtERUtaZnpUMG5qbVo5TGhtZWE1SFNWMk5OOENaVEZHcnAvVW0zc1A2ajJEbWZUbU0yalRHT0FYYjJNVTVHOHdTQlYwQkF3PT0K",
"url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.AppImage.tar.gz"
},
"win64": {
"windows-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K",
"url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.x64.msi.zip"
}
Expand Down Expand Up @@ -965,7 +972,7 @@ mod test {
}

#[test]
fn simple_http_updater_raw_json_win64() {
fn simple_http_updater_raw_json_windows_x86_64() {
let _m = mockito::mock("GET", "/")
.with_status(200)
.with_header("content-type", "application/json")
Expand All @@ -975,7 +982,7 @@ mod test {
let app = crate::test::mock_app();
let check_update = block!(builder(app.handle())
.current_version("0.0.0")
.target("win64")
.target("windows-x86_64")
.url(mockito::server_url())
.build());

Expand Down Expand Up @@ -1013,7 +1020,7 @@ mod test {

#[test]
fn simple_http_updater_without_version() {
let _m = mockito::mock("GET", "/darwin/1.0.0")
let _m = mockito::mock("GET", "/darwin-aarch64/1.0.0")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(generate_sample_platform_json(
Expand All @@ -1027,7 +1034,7 @@ mod test {
let check_update = block!(builder(app.handle())
.current_version("1.0.0")
.url(format!(
"{}/darwin/{{{{current_version}}}}",
"{}/darwin-aarch64/{{{{current_version}}}}",
mockito::server_url()
))
.build());
Expand All @@ -1040,7 +1047,7 @@ mod test {

#[test]
fn simple_http_updater_percent_decode() {
let _m = mockito::mock("GET", "/darwin/1.0.0")
let _m = mockito::mock("GET", "/darwin-aarch64/1.0.0")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(generate_sample_platform_json(
Expand All @@ -1055,7 +1062,7 @@ mod test {
.current_version("1.0.0")
.url(
url::Url::parse(&format!(
"{}/darwin/{{{{current_version}}}}",
"{}/darwin-aarch64/{{{{current_version}}}}",
mockito::server_url()
))
.unwrap()
Expand All @@ -1072,7 +1079,7 @@ mod test {
let check_update = block!(builder(app.handle())
.current_version("1.0.0")
.urls(&[url::Url::parse(&format!(
"{}/darwin/{{{{current_version}}}}",
"{}/darwin-aarch64/{{{{current_version}}}}",
mockito::server_url()
))
.unwrap()
Expand All @@ -1087,7 +1094,7 @@ mod test {

#[test]
fn simple_http_updater_with_elevated_task() {
let _m = mockito::mock("GET", "/win64/1.0.0")
let _m = mockito::mock("GET", "/windows-x86_64/1.0.0")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(generate_sample_with_elevated_task_platform_json(
Expand All @@ -1102,7 +1109,7 @@ mod test {
let check_update = block!(builder(app.handle())
.current_version("1.0.0")
.url(format!(
"{}/win64/{{{{current_version}}}}",
"{}/windows-x86_64/{{{{current_version}}}}",
mockito::server_url()
))
.build());
Expand All @@ -1115,7 +1122,7 @@ mod test {

#[test]
fn http_updater_uptodate() {
let _m = mockito::mock("GET", "/darwin/10.0.0")
let _m = mockito::mock("GET", "/darwin-aarch64/10.0.0")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(generate_sample_platform_json(
Expand All @@ -1129,7 +1136,7 @@ mod test {
let check_update = block!(builder(app.handle())
.current_version("10.0.0")
.url(format!(
"{}/darwin/{{{{current_version}}}}",
"{}/darwin-aarch64/{{{{current_version}}}}",
mockito::server_url()
))
.build());
Expand Down

0 comments on commit 579312f

Please sign in to comment.