mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-02-06 11:22:04 +00:00
Merge remote-tracking branch 'upstream/dev' into wix-minimum-webview2-version
This commit is contained in:
commit
76a436f1dd
5
.changes/android-external-files-fix.md
Normal file
5
.changes/android-external-files-fix.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch:bug
|
||||
---
|
||||
|
||||
Fixed 500 error when accessing local video files in Android external storage directory via `convertFileSrc`. Added better error handling and logging for Android external storage access to help diagnose permission and accessibility issues.
|
||||
6
.changes/change-pr-13253.md
Normal file
6
.changes/change-pr-13253.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tauri-apps/cli": patch:enhance
|
||||
"tauri-cli": patch:enhance
|
||||
---
|
||||
|
||||
Allow electron to run the CLI directly
|
||||
5
.changes/change-pr-14812.md
Normal file
5
.changes/change-pr-14812.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch:bug
|
||||
---
|
||||
|
||||
fix(specta): don't use `#[specta(rename = ...)]` with `tauri::ipc::Channel`
|
||||
5
.changes/change-pr-14824.md
Normal file
5
.changes/change-pr-14824.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri-bundler': 'patch:enhance'
|
||||
---
|
||||
|
||||
feat(nsis): add Norwegian language support for installer.
|
||||
6
.changes/change-pr-14836.md
Normal file
6
.changes/change-pr-14836.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tauri-apps/cli": patch:changes
|
||||
"tauri-cli": patch:changes
|
||||
---
|
||||
|
||||
Continued refactors of tauri-cli, fix too weak atomics.
|
||||
6
.changes/fix-android-bundle-flag.md
Normal file
6
.changes/fix-android-bundle-flag.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-cli": patch:bug
|
||||
"@tauri-apps/cli": patch:bug
|
||||
---
|
||||
|
||||
Fix `android build`'s `--aab` and `--apk` flags requiring a value to be provided.
|
||||
8
.changes/fix-binary-patching.md
Normal file
8
.changes/fix-binary-patching.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
"tauri": minor:changes
|
||||
"tauri-cli": minor:changes
|
||||
"tauri-bundler": minor:changes
|
||||
"@tauri-apps/cli": minor:changes
|
||||
---
|
||||
|
||||
Change the way bundle type information is added to binary files. Instead of looking up the value of a variable we simply look for the default value.
|
||||
6
.changes/fix-empty-entitlements.md
Normal file
6
.changes/fix-empty-entitlements.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-cli": patch:bug
|
||||
"@tauri-apps/cli": patch:bug
|
||||
---
|
||||
|
||||
Fix empty associated-domains entitlements when domains are not configured for deep links.
|
||||
9
.changes/nsis-run-as-user.md
Normal file
9
.changes/nsis-run-as-user.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
"tauri-bundler": patch:bug
|
||||
"tauri-cli": patch:bug
|
||||
"@tauri-apps/cli": patch:bug
|
||||
---
|
||||
|
||||
Updated `nsis_tauri_utils` to 0.5.3:
|
||||
|
||||
- Use an alternative method `CreateProcessWithTokenW` to run programs as user, this fixed a problem that the program launched with the previous method can't query its own handle
|
||||
6
.changes/only-watch-dependencies.md
Normal file
6
.changes/only-watch-dependencies.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tauri-apps/cli": patch:bug
|
||||
"tauri-cli": patch:bug
|
||||
---
|
||||
|
||||
Only watch dependent workspace members when running `tauri dev` instead of watching on all members
|
||||
7
.changes/webview-set-simple-fullscreen.md
Normal file
7
.changes/webview-set-simple-fullscreen.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
'tauri': 'minor:feat'
|
||||
---
|
||||
|
||||
Add `set_simple_fullscreen` method to `WebviewWindow`.
|
||||
|
||||
This method was already available on the `Window` type and is now also available on `WebviewWindow` for consistency. On macOS, it toggles fullscreen mode without creating a new macOS Space. On other platforms, it falls back to regular fullscreen.
|
||||
16
.taurignore
16
.taurignore
@ -1,16 +0,0 @@
|
||||
.changes
|
||||
.devcontainer
|
||||
.docker
|
||||
.github
|
||||
.scripts
|
||||
.vscode
|
||||
audits
|
||||
bench
|
||||
packages/api
|
||||
packages/cli
|
||||
crates/tauri-cli
|
||||
crates/tauri-bundler
|
||||
crates/tauri-driver
|
||||
crates/tauri-macos-sign
|
||||
crates/tauri-schema-generator
|
||||
crates/tests
|
||||
@ -4,6 +4,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
mod category;
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
mod kmp;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
@ -15,29 +17,46 @@ mod windows;
|
||||
|
||||
use tauri_utils::{display_path, platform::Target as TargetPlatform};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
const BUNDLE_VAR_TOKEN: &[u8] = b"__TAURI_BUNDLE_TYPE_VAR_UNK";
|
||||
/// Patch a binary with bundle type information
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
fn patch_binary(binary: &PathBuf, package_type: &PackageType) -> crate::Result<()> {
|
||||
match package_type {
|
||||
#[cfg(target_os = "linux")]
|
||||
PackageType::AppImage | PackageType::Deb | PackageType::Rpm => {
|
||||
log::info!(
|
||||
"Patching binary {:?} for type {}",
|
||||
binary,
|
||||
package_type.short_name()
|
||||
);
|
||||
linux::patch_binary(binary, package_type)?;
|
||||
}
|
||||
PackageType::Nsis | PackageType::WindowsMsi => {
|
||||
log::info!(
|
||||
"Patching binary {:?} for type {}",
|
||||
binary,
|
||||
package_type.short_name()
|
||||
);
|
||||
windows::patch_binary(binary, package_type)?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
let mut file_data = std::fs::read(binary).expect("Could not read binary file.");
|
||||
|
||||
if let Some(bundle_var_index) = kmp::index_of(BUNDLE_VAR_TOKEN, &file_data) {
|
||||
#[cfg(target_os = "linux")]
|
||||
let bundle_type = match package_type {
|
||||
crate::PackageType::Deb => b"__TAURI_BUNDLE_TYPE_VAR_DEB",
|
||||
crate::PackageType::Rpm => b"__TAURI_BUNDLE_TYPE_VAR_RPM",
|
||||
crate::PackageType::AppImage => b"__TAURI_BUNDLE_TYPE_VAR_APP",
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidPackageType(
|
||||
package_type.short_name().to_owned(),
|
||||
"Linux".to_owned(),
|
||||
))
|
||||
}
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
let bundle_type = match package_type {
|
||||
crate::PackageType::Nsis => b"__TAURI_BUNDLE_TYPE_VAR_NSS",
|
||||
crate::PackageType::WindowsMsi => b"__TAURI_BUNDLE_TYPE_VAR_MSI",
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidPackageType(
|
||||
package_type.short_name().to_owned(),
|
||||
"Windows".to_owned(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
file_data[bundle_var_index..bundle_var_index + BUNDLE_VAR_TOKEN.len()]
|
||||
.copy_from_slice(bundle_type);
|
||||
|
||||
std::fs::write(binary, &file_data)
|
||||
.map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?;
|
||||
} else {
|
||||
return Err(crate::Error::MissingBundleTypeVar);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -92,22 +111,17 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||
.expect("Main binary missing in settings");
|
||||
let main_binary_path = settings.binary_path(main_binary);
|
||||
|
||||
// When packaging multiple binary types, we make a copy of the unsigned main_binary so that we can
|
||||
// restore it after each package_type step. This avoids two issues:
|
||||
// We make a copy of the unsigned main_binary so that we can restore it after each package_type step.
|
||||
// This allows us to patch the binary correctly and avoids two issues:
|
||||
// - modifying a signed binary without updating its PE checksum can break signature verification
|
||||
// - codesigning tools should handle calculating+updating this, we just need to ensure
|
||||
// (re)signing is performed after every `patch_binary()` operation
|
||||
// - signing an already-signed binary can result in multiple signatures, causing verification errors
|
||||
let main_binary_reset_required = matches!(target_os, TargetPlatform::Windows)
|
||||
&& settings.windows().can_sign()
|
||||
&& package_types.len() > 1;
|
||||
let mut unsigned_main_binary_copy = tempfile::tempfile()?;
|
||||
if main_binary_reset_required {
|
||||
let mut unsigned_main_binary = std::fs::File::open(&main_binary_path)?;
|
||||
std::io::copy(&mut unsigned_main_binary, &mut unsigned_main_binary_copy)?;
|
||||
}
|
||||
// TODO: change this to work on a copy while preserving the main binary unchanged
|
||||
let mut main_binary_copy = tempfile::tempfile()?;
|
||||
let mut main_binary_orignal = std::fs::File::open(&main_binary_path)?;
|
||||
std::io::copy(&mut main_binary_orignal, &mut main_binary_copy)?;
|
||||
|
||||
let mut main_binary_signed = false;
|
||||
let mut bundles = Vec::<Bundle>::new();
|
||||
for package_type in &package_types {
|
||||
// bundle was already built! e.g. DMG already built .app
|
||||
@ -115,22 +129,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||
continue;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
if let Err(e) = patch_binary(&main_binary_path, package_type) {
|
||||
log::warn!("Failed to add bundler type to the binary: {e}. Updater plugin may not be able to update this package. This shouldn't normally happen, please report it to https://github.com/tauri-apps/tauri/issues");
|
||||
}
|
||||
|
||||
// sign main binary for every package type after patch
|
||||
if matches!(target_os, TargetPlatform::Windows) && settings.windows().can_sign() {
|
||||
if main_binary_signed && main_binary_reset_required {
|
||||
let mut signed_main_binary = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&main_binary_path)?;
|
||||
unsigned_main_binary_copy.seek(SeekFrom::Start(0))?;
|
||||
std::io::copy(&mut unsigned_main_binary_copy, &mut signed_main_binary)?;
|
||||
}
|
||||
windows::sign::try_sign(&main_binary_path, settings)?;
|
||||
main_binary_signed = true;
|
||||
}
|
||||
|
||||
let bundle_paths = match package_type {
|
||||
@ -172,6 +178,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||
package_type: package_type.to_owned(),
|
||||
bundle_paths,
|
||||
});
|
||||
|
||||
// Restore unsigned and unpatched binary
|
||||
let mut modified_main_binary = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&main_binary_path)?;
|
||||
main_binary_copy.seek(SeekFrom::Start(0))?;
|
||||
std::io::copy(&mut main_binary_copy, &mut modified_main_binary)?;
|
||||
}
|
||||
|
||||
if let Some(updater) = settings.updater() {
|
||||
|
||||
61
crates/tauri-bundler/src/bundle/kmp/mod.rs
Normal file
61
crates/tauri-bundler/src/bundle/kmp/mod.rs
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Knuth–Morris–Pratt algorithm
|
||||
// based on https://github.com/howeih/rust_kmp
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
pub fn index_of(pattern: &[u8], target: &[u8]) -> Option<usize> {
|
||||
let failure_function = find_failure_function(pattern);
|
||||
|
||||
let mut t_i: usize = 0;
|
||||
let mut p_i: usize = 0;
|
||||
let target_len = target.len();
|
||||
let mut result_idx = None;
|
||||
let pattern_len = pattern.len();
|
||||
|
||||
while (t_i < target_len) && (p_i < pattern_len) {
|
||||
if target[t_i] == pattern[p_i] {
|
||||
if result_idx.is_none() {
|
||||
result_idx.replace(t_i);
|
||||
}
|
||||
t_i += 1;
|
||||
p_i += 1;
|
||||
if p_i >= pattern_len {
|
||||
return result_idx;
|
||||
}
|
||||
} else {
|
||||
if p_i == 0 {
|
||||
p_i = 0;
|
||||
t_i += 1;
|
||||
} else {
|
||||
p_i = failure_function[p_i - 1];
|
||||
}
|
||||
result_idx = None;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
fn find_failure_function(pattern: &[u8]) -> Vec<usize> {
|
||||
let mut i = 1;
|
||||
let mut j = 0;
|
||||
let pattern_length = pattern.len();
|
||||
let end_i = pattern_length - 1;
|
||||
let mut failure_function = vec![0usize; pattern_length];
|
||||
while i <= end_i {
|
||||
if pattern[i] == pattern[j] {
|
||||
failure_function[i] = j + 1;
|
||||
i += 1;
|
||||
j += 1;
|
||||
} else if j == 0 {
|
||||
failure_function[i] = 0;
|
||||
i += 1;
|
||||
} else {
|
||||
j = failure_function[j - 1];
|
||||
}
|
||||
}
|
||||
failure_function
|
||||
}
|
||||
282
crates/tauri-bundler/src/bundle/linux/appimage/linuxdeploy.rs
Normal file
282
crates/tauri-bundler/src/bundle/linux/appimage/linuxdeploy.rs
Normal file
@ -0,0 +1,282 @@
|
||||
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::{super::debian, write_and_make_executable};
|
||||
use crate::{
|
||||
bundle::settings::Arch,
|
||||
error::{Context, ErrorExt},
|
||||
utils::{fs_utils, http_utils::download, CommandExt},
|
||||
Settings,
|
||||
};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
/// Bundles the project.
|
||||
/// Returns a vector of PathBuf that shows where the AppImage was created.
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// generate the deb binary name
|
||||
let appimage_arch: &str = match settings.binary_arch() {
|
||||
Arch::X86_64 => "amd64",
|
||||
Arch::X86 => "i386",
|
||||
Arch::AArch64 => "aarch64",
|
||||
Arch::Armhf => "armhf",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"Unsupported architecture: {target:?}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let tools_arch = if settings.binary_arch() == Arch::Armhf {
|
||||
"armhf"
|
||||
} else {
|
||||
settings.target().split('-').next().unwrap()
|
||||
};
|
||||
|
||||
let output_path = settings.project_out_directory().join("bundle/appimage");
|
||||
if output_path.exists() {
|
||||
fs::remove_dir_all(&output_path)?;
|
||||
}
|
||||
|
||||
let tools_path = settings
|
||||
.local_tools_directory()
|
||||
.map(|d| d.join(".tauri"))
|
||||
.unwrap_or_else(|| {
|
||||
dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join("tauri"))
|
||||
});
|
||||
|
||||
fs::create_dir_all(&tools_path)?;
|
||||
|
||||
let linuxdeploy_path = prepare_tools(
|
||||
&tools_path,
|
||||
tools_arch,
|
||||
settings.log_level() != log::Level::Error,
|
||||
)?;
|
||||
|
||||
let package_dir = settings.project_out_directory().join("bundle/appimage_deb");
|
||||
|
||||
let main_binary = settings.main_binary()?;
|
||||
let product_name = settings.product_name();
|
||||
|
||||
let mut settings = settings.clone();
|
||||
if main_binary.name().contains(' ') {
|
||||
let main_binary_path = settings.binary_path(main_binary);
|
||||
let project_out_directory = settings.project_out_directory();
|
||||
|
||||
let main_binary_name_kebab = heck::AsKebabCase(main_binary.name()).to_string();
|
||||
let new_path = project_out_directory.join(&main_binary_name_kebab);
|
||||
fs::copy(main_binary_path, new_path)?;
|
||||
|
||||
let main_binary = settings.main_binary_mut()?;
|
||||
main_binary.set_name(main_binary_name_kebab);
|
||||
}
|
||||
|
||||
// generate deb_folder structure
|
||||
let (data_dir, icons) = debian::generate_data(&settings, &package_dir)
|
||||
.with_context(|| "Failed to build data folders and files")?;
|
||||
fs_utils::copy_custom_files(&settings.appimage().files, &data_dir)
|
||||
.with_context(|| "Failed to copy custom files")?;
|
||||
|
||||
fs::create_dir_all(&output_path)?;
|
||||
let app_dir_path = output_path.join(format!("{}.AppDir", settings.product_name()));
|
||||
let appimage_filename = format!(
|
||||
"{}_{}_{}.AppImage",
|
||||
settings.product_name(),
|
||||
settings.version_string(),
|
||||
appimage_arch
|
||||
);
|
||||
let appimage_path = output_path.join(&appimage_filename);
|
||||
fs_utils::create_dir(&app_dir_path, true)?;
|
||||
|
||||
fs::create_dir_all(&tools_path)?;
|
||||
let larger_icon = icons
|
||||
.iter()
|
||||
.filter(|i| i.width == i.height)
|
||||
.max_by_key(|i| i.width)
|
||||
.expect("couldn't find a square icon to use as AppImage icon");
|
||||
let larger_icon_path = larger_icon
|
||||
.path
|
||||
.strip_prefix(package_dir.join("data"))
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
log::info!(action = "Bundling"; "{} ({})", appimage_filename, appimage_path.display());
|
||||
|
||||
let app_dir_usr = app_dir_path.join("usr/");
|
||||
let app_dir_usr_bin = app_dir_usr.join("bin/");
|
||||
let app_dir_usr_lib = app_dir_usr.join("lib/");
|
||||
|
||||
fs_utils::copy_dir(&data_dir.join("usr/"), &app_dir_usr)?;
|
||||
|
||||
// Using create_dir_all for a single dir so we don't get errors if the path already exists
|
||||
fs::create_dir_all(&app_dir_usr_bin)?;
|
||||
fs::create_dir_all(app_dir_usr_lib)?;
|
||||
|
||||
// Copy bins and libs that linuxdeploy doesn't know about
|
||||
|
||||
// we also check if the user may have provided their own copy already
|
||||
// xdg-open will be handled by the `files` config instead
|
||||
if settings.deep_link_protocols().is_some() && !app_dir_usr_bin.join("xdg-open").exists() {
|
||||
fs::copy("/usr/bin/xdg-mime", app_dir_usr_bin.join("xdg-mime"))
|
||||
.fs_context("xdg-mime binary not found", "/usr/bin/xdg-mime".to_string())?;
|
||||
}
|
||||
|
||||
// we also check if the user may have provided their own copy already
|
||||
if settings.appimage().bundle_xdg_open && !app_dir_usr_bin.join("xdg-open").exists() {
|
||||
fs::copy("/usr/bin/xdg-open", app_dir_usr_bin.join("xdg-open"))
|
||||
.fs_context("xdg-open binary not found", "/usr/bin/xdg-open".to_string())?;
|
||||
}
|
||||
|
||||
let search_dirs = [
|
||||
match settings.binary_arch() {
|
||||
Arch::X86_64 => "/usr/lib/x86_64-linux-gnu/",
|
||||
Arch::X86 => "/usr/lib/i386-linux-gnu/",
|
||||
Arch::AArch64 => "/usr/lib/aarch64-linux-gnu/",
|
||||
Arch::Armhf => "/usr/lib/arm-linux-gnueabihf/",
|
||||
_ => unreachable!(),
|
||||
},
|
||||
"/usr/lib64",
|
||||
"/usr/lib",
|
||||
"/usr/libexec",
|
||||
];
|
||||
|
||||
for file in [
|
||||
"WebKitNetworkProcess",
|
||||
"WebKitWebProcess",
|
||||
"injected-bundle/libwebkit2gtkinjectedbundle.so",
|
||||
] {
|
||||
for source in search_dirs.map(PathBuf::from) {
|
||||
// TODO: Check if it's the same dir name on all systems
|
||||
let source = source.join("webkit2gtk-4.1").join(file);
|
||||
if source.exists() {
|
||||
fs_utils::copy_file(
|
||||
&source,
|
||||
&app_dir_path.join(source.strip_prefix("/").unwrap()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs::copy(
|
||||
tools_path.join(format!("AppRun-{tools_arch}")),
|
||||
app_dir_path.join("AppRun"),
|
||||
)?;
|
||||
fs::copy(
|
||||
app_dir_path.join(larger_icon_path),
|
||||
app_dir_path.join(format!("{product_name}.png")),
|
||||
)?;
|
||||
std::os::unix::fs::symlink(
|
||||
app_dir_path.join(format!("{product_name}.png")),
|
||||
app_dir_path.join(".DirIcon"),
|
||||
)?;
|
||||
std::os::unix::fs::symlink(
|
||||
app_dir_path.join(format!("usr/share/applications/{product_name}.desktop")),
|
||||
app_dir_path.join(format!("{product_name}.desktop")),
|
||||
)?;
|
||||
|
||||
let log_level = match settings.log_level() {
|
||||
log::Level::Error => "3",
|
||||
log::Level::Warn => "2",
|
||||
log::Level::Info => "1",
|
||||
_ => "0",
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(linuxdeploy_path);
|
||||
cmd.env("OUTPUT", &appimage_path);
|
||||
cmd.env("ARCH", tools_arch);
|
||||
// Looks like the cli arg isn't enough for the updated AppImage output-plugin.
|
||||
cmd.env("APPIMAGE_EXTRACT_AND_RUN", "1");
|
||||
cmd.args([
|
||||
"--appimage-extract-and-run",
|
||||
"--verbosity",
|
||||
log_level,
|
||||
"--appdir",
|
||||
&app_dir_path.display().to_string(),
|
||||
"--plugin",
|
||||
"gtk",
|
||||
]);
|
||||
if settings.appimage().bundle_media_framework {
|
||||
cmd.args(["--plugin", "gstreamer"]);
|
||||
}
|
||||
cmd.args(["--output", "appimage"]);
|
||||
|
||||
// Linuxdeploy logs everything into stderr so we have to ignore the output ourselves here
|
||||
if settings.log_level() == log::Level::Error {
|
||||
log::debug!(action = "Running"; "Command `linuxdeploy {}`", cmd.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
|
||||
if !cmd.output()?.status.success() {
|
||||
return Err(crate::Error::GenericError(
|
||||
"failed to run linuxdeploy".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
cmd.output_ok()?;
|
||||
}
|
||||
|
||||
fs::remove_dir_all(&package_dir)?;
|
||||
Ok(vec![appimage_path])
|
||||
}
|
||||
|
||||
// returns the linuxdeploy path to keep linuxdeploy_arch contained
|
||||
fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result<PathBuf> {
|
||||
let apprun = tools_path.join(format!("AppRun-{arch}"));
|
||||
if !apprun.exists() {
|
||||
let data = download(&format!(
|
||||
"https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-{arch}"
|
||||
))?;
|
||||
write_and_make_executable(&apprun, data)?;
|
||||
}
|
||||
|
||||
let linuxdeploy_arch = if arch == "i686" { "i383" } else { arch };
|
||||
let linuxdeploy = tools_path.join(format!("linuxdeploy-{linuxdeploy_arch}.AppImage"));
|
||||
if !linuxdeploy.exists() {
|
||||
let data = download(&format!("https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-{linuxdeploy_arch}.AppImage"))?;
|
||||
write_and_make_executable(&linuxdeploy, data)?;
|
||||
}
|
||||
|
||||
let gtk = tools_path.join("linuxdeploy-plugin-gtk.sh");
|
||||
if !gtk.exists() {
|
||||
let data = download("https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh")?;
|
||||
write_and_make_executable(>k, data)?;
|
||||
}
|
||||
|
||||
let gstreamer = tools_path.join("linuxdeploy-plugin-gstreamer.sh");
|
||||
if !gstreamer.exists() {
|
||||
let data = download("https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh")?;
|
||||
write_and_make_executable(&gstreamer, data)?;
|
||||
}
|
||||
|
||||
let appimage = tools_path.join("linuxdeploy-plugin-appimage.AppImage");
|
||||
if !appimage.exists() {
|
||||
// This is optional, linuxdeploy will fall back to its built-in version if the download failed.
|
||||
let data = download(&format!("https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-{arch}.AppImage"));
|
||||
match data {
|
||||
Ok(data) => write_and_make_executable(&appimage, data)?,
|
||||
Err(err) => {
|
||||
log::error!("Download of AppImage plugin failed. Using older built-in version instead.");
|
||||
if verbose {
|
||||
log::debug!("{err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should prevent linuxdeploy to be detected by appimage integration tools
|
||||
let _ = Command::new("dd")
|
||||
.args([
|
||||
"if=/dev/zero",
|
||||
"bs=1",
|
||||
"count=3",
|
||||
"seek=8",
|
||||
"conv=notrunc",
|
||||
&format!("of={}", linuxdeploy.display()),
|
||||
])
|
||||
.output();
|
||||
|
||||
Ok(linuxdeploy)
|
||||
}
|
||||
@ -1,287 +1,21 @@
|
||||
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::debian;
|
||||
use crate::{
|
||||
bundle::settings::Arch,
|
||||
error::{Context, ErrorExt},
|
||||
utils::{fs_utils, http_utils::download, CommandExt},
|
||||
Settings,
|
||||
};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
/// Bundles the project.
|
||||
/// Returns a vector of PathBuf that shows where the AppImage was created.
|
||||
use crate::Settings;
|
||||
|
||||
mod linuxdeploy;
|
||||
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// generate the deb binary name
|
||||
let appimage_arch: &str = match settings.binary_arch() {
|
||||
Arch::X86_64 => "amd64",
|
||||
Arch::X86 => "i386",
|
||||
Arch::AArch64 => "aarch64",
|
||||
Arch::Armhf => "armhf",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"Unsupported architecture: {target:?}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let tools_arch = if settings.binary_arch() == Arch::Armhf {
|
||||
"armhf"
|
||||
} else {
|
||||
settings.target().split('-').next().unwrap()
|
||||
};
|
||||
|
||||
let output_path = settings.project_out_directory().join("bundle/appimage");
|
||||
if output_path.exists() {
|
||||
fs::remove_dir_all(&output_path)?;
|
||||
}
|
||||
|
||||
let tools_path = settings
|
||||
.local_tools_directory()
|
||||
.map(|d| d.join(".tauri"))
|
||||
.unwrap_or_else(|| {
|
||||
dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join("tauri"))
|
||||
});
|
||||
|
||||
fs::create_dir_all(&tools_path)?;
|
||||
|
||||
let linuxdeploy_path = prepare_tools(
|
||||
&tools_path,
|
||||
tools_arch,
|
||||
settings.log_level() != log::Level::Error,
|
||||
)?;
|
||||
|
||||
let package_dir = settings.project_out_directory().join("bundle/appimage_deb");
|
||||
|
||||
let main_binary = settings.main_binary()?;
|
||||
let product_name = settings.product_name();
|
||||
|
||||
let mut settings = settings.clone();
|
||||
if main_binary.name().contains(' ') {
|
||||
let main_binary_path = settings.binary_path(main_binary);
|
||||
let project_out_directory = settings.project_out_directory();
|
||||
|
||||
let main_binary_name_kebab = heck::AsKebabCase(main_binary.name()).to_string();
|
||||
let new_path = project_out_directory.join(&main_binary_name_kebab);
|
||||
fs::copy(main_binary_path, new_path)?;
|
||||
|
||||
let main_binary = settings.main_binary_mut()?;
|
||||
main_binary.set_name(main_binary_name_kebab);
|
||||
}
|
||||
|
||||
// generate deb_folder structure
|
||||
let (data_dir, icons) = debian::generate_data(&settings, &package_dir)
|
||||
.with_context(|| "Failed to build data folders and files")?;
|
||||
fs_utils::copy_custom_files(&settings.appimage().files, &data_dir)
|
||||
.with_context(|| "Failed to copy custom files")?;
|
||||
|
||||
fs::create_dir_all(&output_path)?;
|
||||
let app_dir_path = output_path.join(format!("{}.AppDir", settings.product_name()));
|
||||
let appimage_filename = format!(
|
||||
"{}_{}_{}.AppImage",
|
||||
settings.product_name(),
|
||||
settings.version_string(),
|
||||
appimage_arch
|
||||
);
|
||||
let appimage_path = output_path.join(&appimage_filename);
|
||||
fs_utils::create_dir(&app_dir_path, true)?;
|
||||
|
||||
fs::create_dir_all(&tools_path)?;
|
||||
let larger_icon = icons
|
||||
.iter()
|
||||
.filter(|i| i.width == i.height)
|
||||
.max_by_key(|i| i.width)
|
||||
.expect("couldn't find a square icon to use as AppImage icon");
|
||||
let larger_icon_path = larger_icon
|
||||
.path
|
||||
.strip_prefix(package_dir.join("data"))
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
log::info!(action = "Bundling"; "{} ({})", appimage_filename, appimage_path.display());
|
||||
|
||||
let app_dir_usr = app_dir_path.join("usr/");
|
||||
let app_dir_usr_bin = app_dir_usr.join("bin/");
|
||||
let app_dir_usr_lib = app_dir_usr.join("lib/");
|
||||
|
||||
fs_utils::copy_dir(&data_dir.join("usr/"), &app_dir_usr)?;
|
||||
|
||||
// Using create_dir_all for a single dir so we don't get errors if the path already exists
|
||||
fs::create_dir_all(&app_dir_usr_bin)?;
|
||||
fs::create_dir_all(app_dir_usr_lib)?;
|
||||
|
||||
// Copy bins and libs that linuxdeploy doesn't know about
|
||||
|
||||
// we also check if the user may have provided their own copy already
|
||||
// xdg-open will be handled by the `files` config instead
|
||||
if settings.deep_link_protocols().is_some() && !app_dir_usr_bin.join("xdg-open").exists() {
|
||||
fs::copy("/usr/bin/xdg-mime", app_dir_usr_bin.join("xdg-mime"))
|
||||
.fs_context("xdg-mime binary not found", "/usr/bin/xdg-mime".to_string())?;
|
||||
}
|
||||
|
||||
// we also check if the user may have provided their own copy already
|
||||
if settings.appimage().bundle_xdg_open && !app_dir_usr_bin.join("xdg-open").exists() {
|
||||
fs::copy("/usr/bin/xdg-open", app_dir_usr_bin.join("xdg-open"))
|
||||
.fs_context("xdg-open binary not found", "/usr/bin/xdg-open".to_string())?;
|
||||
}
|
||||
|
||||
let search_dirs = [
|
||||
match settings.binary_arch() {
|
||||
Arch::X86_64 => "/usr/lib/x86_64-linux-gnu/",
|
||||
Arch::X86 => "/usr/lib/i386-linux-gnu/",
|
||||
Arch::AArch64 => "/usr/lib/aarch64-linux-gnu/",
|
||||
Arch::Armhf => "/usr/lib/arm-linux-gnueabihf/",
|
||||
_ => unreachable!(),
|
||||
},
|
||||
"/usr/lib64",
|
||||
"/usr/lib",
|
||||
"/usr/libexec",
|
||||
];
|
||||
|
||||
for file in [
|
||||
"WebKitNetworkProcess",
|
||||
"WebKitWebProcess",
|
||||
"injected-bundle/libwebkit2gtkinjectedbundle.so",
|
||||
] {
|
||||
for source in search_dirs.map(PathBuf::from) {
|
||||
// TODO: Check if it's the same dir name on all systems
|
||||
let source = source.join("webkit2gtk-4.1").join(file);
|
||||
if source.exists() {
|
||||
fs_utils::copy_file(
|
||||
&source,
|
||||
&app_dir_path.join(source.strip_prefix("/").unwrap()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs::copy(
|
||||
tools_path.join(format!("AppRun-{tools_arch}")),
|
||||
app_dir_path.join("AppRun"),
|
||||
)?;
|
||||
fs::copy(
|
||||
app_dir_path.join(larger_icon_path),
|
||||
app_dir_path.join(format!("{product_name}.png")),
|
||||
)?;
|
||||
std::os::unix::fs::symlink(
|
||||
app_dir_path.join(format!("{product_name}.png")),
|
||||
app_dir_path.join(".DirIcon"),
|
||||
)?;
|
||||
std::os::unix::fs::symlink(
|
||||
app_dir_path.join(format!("usr/share/applications/{product_name}.desktop")),
|
||||
app_dir_path.join(format!("{product_name}.desktop")),
|
||||
)?;
|
||||
|
||||
let log_level = match settings.log_level() {
|
||||
log::Level::Error => "3",
|
||||
log::Level::Warn => "2",
|
||||
log::Level::Info => "1",
|
||||
_ => "0",
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(linuxdeploy_path);
|
||||
cmd.env("OUTPUT", &appimage_path);
|
||||
cmd.env("ARCH", tools_arch);
|
||||
// Looks like the cli arg isn't enough for the updated AppImage output-plugin.
|
||||
cmd.env("APPIMAGE_EXTRACT_AND_RUN", "1");
|
||||
cmd.args([
|
||||
"--appimage-extract-and-run",
|
||||
"--verbosity",
|
||||
log_level,
|
||||
"--appdir",
|
||||
&app_dir_path.display().to_string(),
|
||||
"--plugin",
|
||||
"gtk",
|
||||
]);
|
||||
if settings.appimage().bundle_media_framework {
|
||||
cmd.args(["--plugin", "gstreamer"]);
|
||||
}
|
||||
cmd.args(["--output", "appimage"]);
|
||||
|
||||
// Linuxdeploy logs everything into stderr so we have to ignore the output ourselves here
|
||||
if settings.log_level() == log::Level::Error {
|
||||
log::debug!(action = "Running"; "Command `linuxdeploy {}`", cmd.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
|
||||
if !cmd.output()?.status.success() {
|
||||
return Err(crate::Error::GenericError(
|
||||
"failed to run linuxdeploy".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
cmd.output_ok()?;
|
||||
}
|
||||
|
||||
fs::remove_dir_all(&package_dir)?;
|
||||
Ok(vec![appimage_path])
|
||||
linuxdeploy::bundle_project(settings)
|
||||
}
|
||||
|
||||
// returns the linuxdeploy path to keep linuxdeploy_arch contained
|
||||
fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result<PathBuf> {
|
||||
let apprun = tools_path.join(format!("AppRun-{arch}"));
|
||||
if !apprun.exists() {
|
||||
let data = download(&format!(
|
||||
"https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-{arch}"
|
||||
))?;
|
||||
write_and_make_executable(&apprun, &data)?;
|
||||
}
|
||||
|
||||
let linuxdeploy_arch = if arch == "i686" { "i386" } else { arch };
|
||||
let linuxdeploy = tools_path.join(format!("linuxdeploy-{linuxdeploy_arch}.AppImage"));
|
||||
if !linuxdeploy.exists() {
|
||||
let data = download(&format!("https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-{linuxdeploy_arch}.AppImage"))?;
|
||||
write_and_make_executable(&linuxdeploy, &data)?;
|
||||
}
|
||||
|
||||
let gtk = tools_path.join("linuxdeploy-plugin-gtk.sh");
|
||||
if !gtk.exists() {
|
||||
let data = include_bytes!("./linuxdeploy-plugin-gtk.sh");
|
||||
write_and_make_executable(>k, data)?;
|
||||
}
|
||||
|
||||
let gstreamer = tools_path.join("linuxdeploy-plugin-gstreamer.sh");
|
||||
if !gstreamer.exists() {
|
||||
let data = include_bytes!("./linuxdeploy-plugin-gstreamer.sh");
|
||||
write_and_make_executable(&gstreamer, data)?;
|
||||
}
|
||||
|
||||
let appimage = tools_path.join("linuxdeploy-plugin-appimage.AppImage");
|
||||
if !appimage.exists() {
|
||||
// This is optional, linuxdeploy will fall back to its built-in version if the download failed.
|
||||
let data = download(&format!("https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-{arch}.AppImage"));
|
||||
match data {
|
||||
Ok(data) => write_and_make_executable(&appimage, &data)?,
|
||||
Err(err) => {
|
||||
log::error!("Download of AppImage plugin failed. Using older built-in version instead.");
|
||||
if verbose {
|
||||
log::debug!("{err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should prevent linuxdeploy to be detected by appimage integration tools
|
||||
let _ = Command::new("dd")
|
||||
.args([
|
||||
"if=/dev/zero",
|
||||
"bs=1",
|
||||
"count=3",
|
||||
"seek=8",
|
||||
"conv=notrunc",
|
||||
&format!("of={}", linuxdeploy.display()),
|
||||
])
|
||||
.output();
|
||||
|
||||
Ok(linuxdeploy)
|
||||
}
|
||||
|
||||
fn write_and_make_executable(path: &Path, data: &[u8]) -> std::io::Result<()> {
|
||||
fn write_and_make_executable(path: &Path, data: Vec<u8>) -> std::io::Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
fs::write(path, data)?;
|
||||
|
||||
@ -7,8 +7,3 @@ pub mod appimage;
|
||||
pub mod debian;
|
||||
pub mod freedesktop;
|
||||
pub mod rpm;
|
||||
|
||||
mod util;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use util::patch_binary;
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/// Change value of __TAURI_BUNDLE_TYPE static variable to mark which package type it was bundled in
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn patch_binary(
|
||||
binary_path: &std::path::PathBuf,
|
||||
package_type: &crate::PackageType,
|
||||
) -> crate::Result<()> {
|
||||
let mut file_data = std::fs::read(binary_path).expect("Could not read binary file.");
|
||||
|
||||
let elf = match goblin::Object::parse(&file_data)? {
|
||||
goblin::Object::Elf(elf) => elf,
|
||||
_ => return Err(crate::Error::GenericError("Not an ELF file".to_owned())),
|
||||
};
|
||||
|
||||
let offset = find_bundle_type_symbol(elf).ok_or(crate::Error::MissingBundleTypeVar)?;
|
||||
let offset = offset as usize;
|
||||
if offset + 3 <= file_data.len() {
|
||||
let chars = &mut file_data[offset..offset + 3];
|
||||
match package_type {
|
||||
crate::PackageType::Deb => chars.copy_from_slice(b"DEB"),
|
||||
crate::PackageType::Rpm => chars.copy_from_slice(b"RPM"),
|
||||
crate::PackageType::AppImage => chars.copy_from_slice(b"APP"),
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidPackageType(
|
||||
package_type.short_name().to_owned(),
|
||||
"linux".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::write(binary_path, &file_data)
|
||||
.map_err(|error| crate::Error::BinaryWriteError(error.to_string()))?;
|
||||
} else {
|
||||
return Err(crate::Error::BinaryOffsetOutOfRange);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find address of a symbol in relocations table
|
||||
#[cfg(target_os = "linux")]
|
||||
fn find_bundle_type_symbol(elf: goblin::elf::Elf<'_>) -> Option<i64> {
|
||||
for sym in elf.syms.iter() {
|
||||
if let Some(name) = elf.strtab.get_at(sym.st_name) {
|
||||
if name == "__TAURI_BUNDLE_TYPE" {
|
||||
for reloc in elf.dynrelas.iter() {
|
||||
if reloc.r_offset == sym.st_value {
|
||||
return Some(reloc.r_addend.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@ -14,5 +14,3 @@ pub use util::{
|
||||
NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME, WIX_OUTPUT_FOLDER_NAME,
|
||||
WIX_UPDATER_OUTPUT_FOLDER_NAME,
|
||||
};
|
||||
|
||||
pub use util::patch_binary;
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
LangString addOrReinstall ${LANG_NORWEGIAN} "Legg til/reinstaller komponenter"
|
||||
LangString alreadyInstalled ${LANG_NORWEGIAN} "Allerede installert"
|
||||
LangString alreadyInstalledLong ${LANG_NORWEGIAN} "${PRODUCTNAME} ${VERSION} er allerede installert. Velg operasjonen du vil utføre og klikk Neste for å fortsette."
|
||||
LangString appRunning ${LANG_NORWEGIAN} "{{product_name}} kjører! Lukk den først og prøv igjen."
|
||||
LangString appRunningOkKill ${LANG_NORWEGIAN} "{{product_name}} kjører!$\nKlikk OK for å avslutte den"
|
||||
LangString chooseMaintenanceOption ${LANG_NORWEGIAN} "Velg vedlikeholdsoperasjonen som skal utføres."
|
||||
LangString choowHowToInstall ${LANG_NORWEGIAN} "Velg hvordan du vil installere ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_NORWEGIAN} "Opprett skrivebordssnarvei"
|
||||
LangString dontUninstall ${LANG_NORWEGIAN} "Ikke avinstaller"
|
||||
LangString dontUninstallDowngrade ${LANG_NORWEGIAN} "Ikke avinstaller (nedgradering uten avinstallasjon er deaktivert for denne installasjonen)"
|
||||
LangString failedToKillApp ${LANG_NORWEGIAN} "Kunne ikke avslutte {{product_name}}. Lukk den først og prøv igjen"
|
||||
LangString installingWebview2 ${LANG_NORWEGIAN} "Installerer WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_NORWEGIAN} "En nyere versjon av ${PRODUCTNAME} er allerede installert! Det anbefales ikke at du installerer en eldre versjon. Hvis du virkelig vil installere denne eldre versjonen, er det bedre å avinstallere den nåværende versjonen først. Velg operasjonen du vil utføre og klikk Neste for å fortsette."
|
||||
LangString older ${LANG_NORWEGIAN} "eldre"
|
||||
LangString olderOrUnknownVersionInstalled ${LANG_NORWEGIAN} "En $R4-versjon av ${PRODUCTNAME} er installert på systemet ditt. Det anbefales at du avinstallerer den nåværende versjonen før installasjon. Velg operasjonen du vil utføre og klikk Neste for å fortsette."
|
||||
LangString silentDowngrades ${LANG_NORWEGIAN} "Nedgraderinger er deaktivert for denne installasjonen. Kan ikke fortsette med stille installasjon; bruk den grafiske installasjonen i stedet.$\n"
|
||||
LangString unableToUninstall ${LANG_NORWEGIAN} "Kunne ikke avinstallere!"
|
||||
LangString uninstallApp ${LANG_NORWEGIAN} "Avinstaller ${PRODUCTNAME}"
|
||||
LangString uninstallBeforeInstalling ${LANG_NORWEGIAN} "Avinstaller før installasjon"
|
||||
LangString unknown ${LANG_NORWEGIAN} "ukjent"
|
||||
LangString webview2AbortError ${LANG_NORWEGIAN} "Kunne ikke installere WebView2! Appen kan ikke kjøre uten den. Prøv å starte installasjonen på nytt."
|
||||
LangString webview2DownloadError ${LANG_NORWEGIAN} "Feil: Nedlasting av WebView2 mislyktes - $0"
|
||||
LangString webview2DownloadSuccess ${LANG_NORWEGIAN} "WebView2-bootstrapper lastet ned"
|
||||
LangString webview2Downloading ${LANG_NORWEGIAN} "Laster ned WebView2-bootstrapper..."
|
||||
LangString webview2InstallError ${LANG_NORWEGIAN} "Feil: Installering av WebView2 mislyktes med avslutningskode $1"
|
||||
LangString webview2InstallSuccess ${LANG_NORWEGIAN} "WebView2 ble installert"
|
||||
LangString deleteAppData ${LANG_NORWEGIAN} "Slett programdata"
|
||||
@ -40,8 +40,8 @@ const NSIS_URL: &str =
|
||||
#[cfg(target_os = "windows")]
|
||||
const NSIS_SHA1: &str = "EF7FF767E5CBD9EDD22ADD3A32C9B8F4500BB10D";
|
||||
const NSIS_TAURI_UTILS_URL: &str =
|
||||
"https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.5.2/nsis_tauri_utils.dll";
|
||||
const NSIS_TAURI_UTILS_SHA1: &str = "D0C502F45DF55C0465C9406088FF016C2E7E6817";
|
||||
"https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.5.3/nsis_tauri_utils.dll";
|
||||
const NSIS_TAURI_UTILS_SHA1: &str = "75197FEE3C6A814FE035788D1C34EAD39349B860";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const NSIS_REQUIRED_FILES: &[&str] = &[
|
||||
@ -872,6 +872,7 @@ fn get_lang_data(lang: &str) -> Option<(String, &[u8])> {
|
||||
"swedish" => include_bytes!("./languages/Swedish.nsh"),
|
||||
"portuguese" => include_bytes!("./languages/Portuguese.nsh"),
|
||||
"ukrainian" => include_bytes!("./languages/Ukrainian.nsh"),
|
||||
"norwegian" => include_bytes!("./languages/Norwegian.nsh"),
|
||||
_ => return None,
|
||||
};
|
||||
Some((path, content))
|
||||
|
||||
@ -77,75 +77,3 @@ pub fn os_bitness<'a>() -> Option<&'a str> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn patch_binary(binary_path: &PathBuf, package_type: &crate::PackageType) -> crate::Result<()> {
|
||||
let mut file_data = std::fs::read(binary_path)?;
|
||||
|
||||
let pe = match goblin::Object::parse(&file_data)? {
|
||||
goblin::Object::PE(pe) => pe,
|
||||
_ => {
|
||||
return Err(crate::Error::BinaryParseError(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidInput, "binary is not a PE file").into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let tauri_bundle_section = pe
|
||||
.sections
|
||||
.iter()
|
||||
.find(|s| s.name().unwrap_or_default() == ".taubndl")
|
||||
.ok_or(crate::Error::MissingBundleTypeVar)?;
|
||||
|
||||
let data_offset = tauri_bundle_section.pointer_to_raw_data as usize;
|
||||
let pointer_size = if pe.is_64 { 8 } else { 4 };
|
||||
let ptr_bytes = file_data
|
||||
.get(data_offset..data_offset + pointer_size)
|
||||
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
|
||||
// `try_into` is safe to `unwrap` here because we have already checked the slice's size through `get`
|
||||
let ptr_value = if pe.is_64 {
|
||||
u64::from_le_bytes(ptr_bytes.try_into().unwrap())
|
||||
} else {
|
||||
u32::from_le_bytes(ptr_bytes.try_into().unwrap()).into()
|
||||
};
|
||||
|
||||
let rdata_section = pe
|
||||
.sections
|
||||
.iter()
|
||||
.find(|s| s.name().unwrap_or_default() == ".rdata")
|
||||
.ok_or_else(|| {
|
||||
crate::Error::BinaryParseError(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidInput, ".rdata section not found").into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let rva = ptr_value.checked_sub(pe.image_base as u64).ok_or_else(|| {
|
||||
crate::Error::BinaryParseError(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid RVA offset").into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// see "Relative virtual address (RVA)" for explanation of offset arithmetic here:
|
||||
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#general-concepts
|
||||
let file_offset = rdata_section.pointer_to_raw_data as usize
|
||||
+ (rva as usize).saturating_sub(rdata_section.virtual_address as usize);
|
||||
|
||||
// Overwrite the string at that offset
|
||||
let string_bytes = file_data
|
||||
.get_mut(file_offset..file_offset + 3)
|
||||
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
|
||||
match package_type {
|
||||
crate::PackageType::Nsis => string_bytes.copy_from_slice(b"NSS"),
|
||||
crate::PackageType::WindowsMsi => string_bytes.copy_from_slice(b"MSI"),
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidPackageType(
|
||||
package_type.short_name().to_owned(),
|
||||
"windows".to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::write(binary_path, &file_data)
|
||||
.map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -99,19 +99,13 @@ pub enum Error {
|
||||
#[error("Wrong package type {0} for platform {1}")]
|
||||
InvalidPackageType(String, String),
|
||||
/// Bundle type symbol missing in binary
|
||||
#[cfg_attr(
|
||||
target_os = "linux",
|
||||
error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date and that symbol stripping is disabled (https://doc.rust-lang.org/cargo/reference/profiles.html#strip)")
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(target_os = "linux"),
|
||||
error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")
|
||||
)]
|
||||
#[error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")]
|
||||
MissingBundleTypeVar,
|
||||
/// Failed to write binary file changed
|
||||
#[error("Failed to write binary file changes: `{0}`")]
|
||||
BinaryWriteError(String),
|
||||
/// Invalid offset while patching binary file
|
||||
#[deprecated]
|
||||
#[error("Invalid offset while patching binary file")]
|
||||
BinaryOffsetOutOfRange,
|
||||
/// Unsupported architecture.
|
||||
|
||||
@ -11,7 +11,7 @@ use crate::{
|
||||
config::{get_config, ConfigMetadata, FrontendDist},
|
||||
},
|
||||
info::plugins::check_mismatched_packages,
|
||||
interface::{rust::get_cargo_target_dir, AppInterface, Interface},
|
||||
interface::{rust::get_cargo_target_dir, AppInterface},
|
||||
ConfigValue, Result,
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
@ -104,7 +104,7 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
|
||||
|
||||
let mut interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;
|
||||
|
||||
setup(&interface, &mut options, &config, false, &dirs)?;
|
||||
setup(&interface, &mut options, &config, &dirs, false)?;
|
||||
|
||||
if let Some(minimum_system_version) = &config.bundle.macos.minimum_system_version {
|
||||
std::env::set_var("MACOSX_DEPLOYMENT_TARGET", minimum_system_version);
|
||||
@ -129,8 +129,8 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
|
||||
&interface,
|
||||
&*app_settings,
|
||||
&config,
|
||||
&out_dir,
|
||||
&dirs,
|
||||
&out_dir,
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -141,8 +141,8 @@ pub fn setup(
|
||||
interface: &AppInterface,
|
||||
options: &mut Options,
|
||||
config: &ConfigMetadata,
|
||||
mobile: bool,
|
||||
dirs: &Dirs,
|
||||
mobile: bool,
|
||||
) -> Result<()> {
|
||||
// TODO: Maybe optimize this to run in parallel in the future
|
||||
// see https://github.com/tauri-apps/tauri/pull/13993#discussion_r2280697117
|
||||
|
||||
@ -20,7 +20,7 @@ use crate::{
|
||||
config::{get_config, ConfigMetadata},
|
||||
updater_signature,
|
||||
},
|
||||
interface::{AppInterface, AppSettings, Interface},
|
||||
interface::{AppInterface, AppSettings},
|
||||
ConfigValue,
|
||||
};
|
||||
|
||||
@ -154,8 +154,8 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> {
|
||||
&interface,
|
||||
&*app_settings,
|
||||
&config,
|
||||
&out_dir,
|
||||
&dirs,
|
||||
&out_dir,
|
||||
)
|
||||
}
|
||||
|
||||
@ -167,8 +167,8 @@ pub fn bundle<A: AppSettings>(
|
||||
interface: &AppInterface,
|
||||
app_settings: &A,
|
||||
config: &ConfigMetadata,
|
||||
out_dir: &Path,
|
||||
dirs: &Dirs,
|
||||
out_dir: &Path,
|
||||
) -> crate::Result<()> {
|
||||
let package_types: Vec<PackageType> = if let Some(bundles) = &options.bundles {
|
||||
bundles.iter().map(|bundle| bundle.0).collect::<Vec<_>>()
|
||||
|
||||
@ -10,7 +10,7 @@ use crate::{
|
||||
config::{get_config, reload_config, BeforeDevCommand, ConfigMetadata, FrontendDist},
|
||||
},
|
||||
info::plugins::check_mismatched_packages,
|
||||
interface::{AppInterface, ExitReason, Interface},
|
||||
interface::{AppInterface, ExitReason},
|
||||
CommandExt, ConfigValue, Error, Result,
|
||||
};
|
||||
|
||||
@ -25,13 +25,13 @@ use std::{
|
||||
process::{exit, Command, Stdio},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex, OnceLock,
|
||||
OnceLock,
|
||||
},
|
||||
};
|
||||
|
||||
mod builtin_dev_server;
|
||||
|
||||
static BEFORE_DEV: OnceLock<Mutex<Arc<SharedChild>>> = OnceLock::new();
|
||||
static BEFORE_DEV: OnceLock<SharedChild> = OnceLock::new();
|
||||
static KILL_BEFORE_DEV_FLAG: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[cfg(unix)]
|
||||
@ -113,22 +113,20 @@ fn command_internal(mut options: Options, dirs: Dirs) -> Result<()> {
|
||||
.map(Target::from_triple)
|
||||
.unwrap_or_else(Target::current);
|
||||
|
||||
let mut cfg = get_config(
|
||||
let mut config = get_config(
|
||||
target,
|
||||
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
|
||||
let mut interface = AppInterface::new(&cfg, options.target.clone(), dirs.tauri)?;
|
||||
let mut interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;
|
||||
|
||||
setup(&interface, &mut options, &mut cfg, &dirs)?;
|
||||
|
||||
let config = Mutex::new(cfg);
|
||||
setup(&interface, &mut options, &mut config, &dirs)?;
|
||||
|
||||
let exit_on_panic = options.exit_on_panic;
|
||||
let no_watch = options.no_watch;
|
||||
interface.dev(
|
||||
&config,
|
||||
&mut config,
|
||||
options.into(),
|
||||
move |status, reason| on_app_exit(status, reason, exit_on_panic, no_watch),
|
||||
&dirs,
|
||||
@ -207,21 +205,18 @@ pub fn setup(
|
||||
|
||||
let child = SharedChild::spawn(&mut command)
|
||||
.unwrap_or_else(|_| panic!("failed to run `{before_dev}`"));
|
||||
let child = Arc::new(child);
|
||||
let child_ = child.clone();
|
||||
|
||||
let child = BEFORE_DEV.get_or_init(move || child);
|
||||
std::thread::spawn(move || {
|
||||
let status = child_
|
||||
let status = child
|
||||
.wait()
|
||||
.expect("failed to wait on \"beforeDevCommand\"");
|
||||
if !(status.success() || KILL_BEFORE_DEV_FLAG.load(Ordering::Relaxed)) {
|
||||
if !(status.success() || KILL_BEFORE_DEV_FLAG.load(Ordering::SeqCst)) {
|
||||
log::error!("The \"beforeDevCommand\" terminated with a non-zero status code.");
|
||||
exit(status.code().unwrap_or(1));
|
||||
}
|
||||
});
|
||||
|
||||
BEFORE_DEV.set(Mutex::new(child)).unwrap();
|
||||
|
||||
let _ = ctrlc::set_handler(move || {
|
||||
kill_before_dev_process();
|
||||
exit(130);
|
||||
@ -338,11 +333,10 @@ pub fn on_app_exit(code: Option<i32>, reason: ExitReason, exit_on_panic: bool, n
|
||||
|
||||
pub fn kill_before_dev_process() {
|
||||
if let Some(child) = BEFORE_DEV.get() {
|
||||
let child = child.lock().unwrap();
|
||||
if KILL_BEFORE_DEV_FLAG.load(Ordering::Relaxed) {
|
||||
if KILL_BEFORE_DEV_FLAG.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
KILL_BEFORE_DEV_FLAG.store(true, Ordering::Relaxed);
|
||||
KILL_BEFORE_DEV_FLAG.store(true, Ordering::SeqCst);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let powershell_path = std::env::var("SYSTEMROOT").map_or_else(
|
||||
|
||||
@ -55,8 +55,7 @@ impl ConfigMetadata {
|
||||
for (ext, config) in &self.extensions {
|
||||
if let Some(identifier) = config
|
||||
.as_object()
|
||||
.and_then(|bundle_config| bundle_config.get("identifier"))
|
||||
.and_then(|id| id.as_str())
|
||||
.and_then(|bundle_config| bundle_config.get("identifier")?.as_str())
|
||||
{
|
||||
if identifier == self.inner.identifier {
|
||||
return Some(ext.clone());
|
||||
@ -164,8 +163,7 @@ fn load_config(
|
||||
|
||||
let original_identifier = config
|
||||
.as_object()
|
||||
.and_then(|config| config.get("identifier"))
|
||||
.and_then(|id| id.as_str())
|
||||
.and_then(|config| config.get("identifier")?.as_str())
|
||||
.map(ToString::to_string);
|
||||
|
||||
if let Some((platform_config, config_path)) =
|
||||
|
||||
@ -30,10 +30,7 @@ use tauri_utils::config::HookCommand;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use crate::Error;
|
||||
use crate::{
|
||||
interface::{AppInterface, Interface},
|
||||
CommandExt,
|
||||
};
|
||||
use crate::{interface::AppInterface, CommandExt};
|
||||
|
||||
pub fn command_env(debug: bool) -> HashMap<&'static str, String> {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
@ -6,7 +6,7 @@ use std::path::Path;
|
||||
use crate::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use crate::interface::{AppInterface, AppSettings, Interface};
|
||||
use crate::interface::{AppInterface, AppSettings};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(about = "Inspect values used by Tauri")]
|
||||
|
||||
@ -5,24 +5,17 @@
|
||||
pub mod rust;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
process::ExitStatus,
|
||||
sync::Arc,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::Context, helpers::app_paths::Dirs, helpers::config::Config,
|
||||
helpers::config::ConfigMetadata,
|
||||
};
|
||||
use crate::{error::Context, helpers::config::Config};
|
||||
use tauri_bundler::bundle::{PackageType, Settings, SettingsBuilder};
|
||||
|
||||
pub use rust::{MobileOptions, Options, Rust as AppInterface, WatcherOptions};
|
||||
|
||||
pub trait DevProcess {
|
||||
fn kill(&self) -> std::io::Result<()>;
|
||||
fn try_wait(&self) -> std::io::Result<Option<ExitStatus>>;
|
||||
#[allow(unused)]
|
||||
fn wait(&self) -> std::io::Result<ExitStatus>;
|
||||
#[allow(unused)]
|
||||
@ -105,33 +98,3 @@ pub enum ExitReason {
|
||||
/// Regular exit.
|
||||
NormalExit,
|
||||
}
|
||||
|
||||
pub trait Interface: Sized {
|
||||
type AppSettings: AppSettings;
|
||||
|
||||
fn new(config: &Config, target: Option<String>, tauri_dir: &Path) -> crate::Result<Self>;
|
||||
fn app_settings(&self) -> Arc<Self::AppSettings>;
|
||||
fn env(&self) -> HashMap<&str, String>;
|
||||
fn build(&mut self, options: Options, dirs: &Dirs) -> crate::Result<PathBuf>;
|
||||
fn dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
config: &Mutex<ConfigMetadata>,
|
||||
options: Options,
|
||||
on_exit: F,
|
||||
dirs: &Dirs,
|
||||
) -> crate::Result<()>;
|
||||
fn mobile_dev<R: Fn(MobileOptions) -> crate::Result<Box<dyn DevProcess + Send>>>(
|
||||
&mut self,
|
||||
config: &Mutex<ConfigMetadata>,
|
||||
options: MobileOptions,
|
||||
runner: R,
|
||||
dirs: &Dirs,
|
||||
) -> crate::Result<()>;
|
||||
fn watch<R: Fn() -> crate::Result<Box<dyn DevProcess + Send>>>(
|
||||
&mut self,
|
||||
config: &Mutex<ConfigMetadata>,
|
||||
options: WatcherOptions,
|
||||
runner: R,
|
||||
dirs: &Dirs,
|
||||
) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ use std::{
|
||||
ffi::OsStr,
|
||||
fs::FileType,
|
||||
io::{BufRead, Write},
|
||||
iter::once,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
str::FromStr,
|
||||
@ -15,7 +16,6 @@ use std::{
|
||||
};
|
||||
|
||||
use dunce::canonicalize;
|
||||
use glob::glob;
|
||||
use ignore::gitignore::{Gitignore, GitignoreBuilder};
|
||||
use notify::RecursiveMode;
|
||||
use notify_debouncer_full::new_debouncer;
|
||||
@ -27,7 +27,7 @@ use tauri_bundler::{
|
||||
};
|
||||
use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol, RunnerConfig, Updater};
|
||||
|
||||
use super::{AppSettings, DevProcess, ExitReason, Interface};
|
||||
use super::{AppSettings, DevProcess, ExitReason};
|
||||
use crate::{
|
||||
error::{Context, Error, ErrorExt},
|
||||
helpers::{
|
||||
@ -134,10 +134,8 @@ pub struct Rust {
|
||||
main_binary_name: Option<String>,
|
||||
}
|
||||
|
||||
impl Interface for Rust {
|
||||
type AppSettings = RustAppSettings;
|
||||
|
||||
fn new(config: &Config, target: Option<String>, tauri_dir: &Path) -> crate::Result<Self> {
|
||||
impl Rust {
|
||||
pub fn new(config: &Config, target: Option<String>, tauri_dir: &Path) -> crate::Result<Self> {
|
||||
let manifest = {
|
||||
let (tx, rx) = sync_channel(1);
|
||||
let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| {
|
||||
@ -177,11 +175,11 @@ impl Interface for Rust {
|
||||
})
|
||||
}
|
||||
|
||||
fn app_settings(&self) -> Arc<Self::AppSettings> {
|
||||
pub fn app_settings(&self) -> Arc<RustAppSettings> {
|
||||
self.app_settings.clone()
|
||||
}
|
||||
|
||||
fn build(&mut self, options: Options, dirs: &Dirs) -> crate::Result<PathBuf> {
|
||||
pub fn build(&mut self, options: Options, dirs: &Dirs) -> crate::Result<PathBuf> {
|
||||
desktop::build(
|
||||
options,
|
||||
&self.app_settings,
|
||||
@ -192,9 +190,9 @@ impl Interface for Rust {
|
||||
)
|
||||
}
|
||||
|
||||
fn dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
|
||||
pub fn dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
config: &Mutex<ConfigMetadata>,
|
||||
config: &mut ConfigMetadata,
|
||||
mut options: Options,
|
||||
on_exit: F,
|
||||
dirs: &Dirs,
|
||||
@ -212,7 +210,7 @@ impl Interface for Rust {
|
||||
|
||||
if options.no_watch {
|
||||
let (tx, rx) = sync_channel(1);
|
||||
self.run_dev(options, run_args, move |status, reason| {
|
||||
self.run_dev(options, &run_args, move |status, reason| {
|
||||
on_exit(status, reason);
|
||||
tx.send(()).unwrap();
|
||||
})?;
|
||||
@ -221,25 +219,28 @@ impl Interface for Rust {
|
||||
Ok(())
|
||||
} else {
|
||||
let merge_configs = options.config.iter().map(|c| &c.0).collect::<Vec<_>>();
|
||||
let run = Arc::new(|rust: &mut Rust| {
|
||||
let on_exit = on_exit.clone();
|
||||
rust.run_dev(options.clone(), run_args.clone(), move |status, reason| {
|
||||
on_exit(status, reason)
|
||||
})
|
||||
});
|
||||
self.run_dev_watcher(
|
||||
config,
|
||||
&options.additional_watch_folders,
|
||||
&merge_configs,
|
||||
run,
|
||||
|rust: &mut Rust, _config| {
|
||||
let on_exit = on_exit.clone();
|
||||
rust
|
||||
.run_dev(options.clone(), &run_args, move |status, reason| {
|
||||
on_exit(status, reason)
|
||||
})
|
||||
.map(|child| Box::new(child) as Box<dyn DevProcess + Send>)
|
||||
},
|
||||
dirs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn mobile_dev<R: Fn(MobileOptions) -> crate::Result<Box<dyn DevProcess + Send>>>(
|
||||
pub fn mobile_dev<
|
||||
R: Fn(MobileOptions, &ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>,
|
||||
>(
|
||||
&mut self,
|
||||
config: &Mutex<ConfigMetadata>,
|
||||
config: &mut ConfigMetadata,
|
||||
mut options: MobileOptions,
|
||||
runner: R,
|
||||
dirs: &Dirs,
|
||||
@ -254,7 +255,7 @@ impl Interface for Rust {
|
||||
);
|
||||
|
||||
if options.no_watch {
|
||||
runner(options)?;
|
||||
runner(options, config)?;
|
||||
Ok(())
|
||||
} else {
|
||||
self.watch(
|
||||
@ -263,31 +264,30 @@ impl Interface for Rust {
|
||||
config: options.config.clone(),
|
||||
additional_watch_folders: options.additional_watch_folders.clone(),
|
||||
},
|
||||
move || runner(options.clone()),
|
||||
move |config| runner(options.clone(), config),
|
||||
dirs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn watch<R: Fn() -> crate::Result<Box<dyn DevProcess + Send>>>(
|
||||
pub fn watch<R: Fn(&ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>>(
|
||||
&mut self,
|
||||
config: &Mutex<ConfigMetadata>,
|
||||
config: &mut ConfigMetadata,
|
||||
options: WatcherOptions,
|
||||
runner: R,
|
||||
dirs: &Dirs,
|
||||
) -> crate::Result<()> {
|
||||
let merge_configs = options.config.iter().map(|c| &c.0).collect::<Vec<_>>();
|
||||
let run = Arc::new(|_rust: &mut Rust| runner());
|
||||
self.run_dev_watcher(
|
||||
config,
|
||||
&options.additional_watch_folders,
|
||||
&merge_configs,
|
||||
run,
|
||||
|_rust: &mut Rust, config| runner(config),
|
||||
dirs,
|
||||
)
|
||||
}
|
||||
|
||||
fn env(&self) -> HashMap<&str, String> {
|
||||
pub fn env(&self) -> HashMap<&str, String> {
|
||||
let mut env = HashMap::new();
|
||||
env.insert(
|
||||
"TAURI_ENV_TARGET_TRIPLE",
|
||||
@ -363,7 +363,7 @@ fn build_ignore_matcher(dir: &Path) -> IgnoreMatcher {
|
||||
|
||||
ignore_builder.add(path);
|
||||
|
||||
if let Ok(ignore_file) = std::env::var("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
|
||||
if let Some(ignore_file) = std::env::var_os("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
|
||||
ignore_builder.add(dir.join(ignore_file));
|
||||
}
|
||||
|
||||
@ -395,7 +395,7 @@ fn lookup<F: FnMut(FileType, PathBuf)>(dir: &Path, mut f: F) {
|
||||
let mut builder = ignore::WalkBuilder::new(dir);
|
||||
builder.add_custom_ignore_filename(".taurignore");
|
||||
let _ = builder.add_ignore(default_gitignore);
|
||||
if let Ok(ignore_file) = std::env::var("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
|
||||
if let Some(ignore_file) = std::env::var_os("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
|
||||
builder.add_ignore(ignore_file);
|
||||
}
|
||||
builder.require_git(false).ignore(false).max_depth(Some(1));
|
||||
@ -449,25 +449,15 @@ fn dev_options(
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from https://github.com/rust-lang/cargo/blob/69255bb10de7f74511b5cef900a9d102247b6029/src/cargo/core/workspace.rs#L665
|
||||
fn expand_member_path(path: &Path) -> crate::Result<Vec<PathBuf>> {
|
||||
let path = path.to_str().context("path is not UTF-8 compatible")?;
|
||||
let res = glob(path).with_context(|| format!("failed to expand glob pattern for {path}"))?;
|
||||
let res = res
|
||||
.map(|p| p.with_context(|| format!("failed to expand glob pattern for {path}")))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn get_watch_folders(
|
||||
additional_watch_folders: &[PathBuf],
|
||||
tauri_dir: &Path,
|
||||
) -> crate::Result<Vec<PathBuf>> {
|
||||
let workspace_path = get_workspace_dir(tauri_dir)?;
|
||||
|
||||
// We always want to watch the main tauri folder.
|
||||
let mut watch_folders = vec![tauri_dir.to_path_buf()];
|
||||
|
||||
watch_folders.extend(get_in_workspace_dependency_paths(tauri_dir)?);
|
||||
|
||||
// Add the additional watch folders, resolving the path from the tauri path if it is relative
|
||||
watch_folders.extend(additional_watch_folders.iter().filter_map(|dir| {
|
||||
let path = if dir.is_absolute() {
|
||||
@ -486,30 +476,6 @@ fn get_watch_folders(
|
||||
canonicalized
|
||||
}));
|
||||
|
||||
// We also try to watch workspace members, no matter if the tauri cargo project is the workspace root or a workspace member
|
||||
let cargo_settings = CargoSettings::load(&workspace_path)?;
|
||||
if let Some(members) = cargo_settings.workspace.and_then(|w| w.members) {
|
||||
for p in members {
|
||||
let p = workspace_path.join(p);
|
||||
match expand_member_path(&p) {
|
||||
// Sometimes expand_member_path returns an empty vec, for example if the path contains `[]` as in `C:/[abc]/project/`.
|
||||
// Cargo won't complain unless theres a workspace.members config with glob patterns so we should support it too.
|
||||
Ok(expanded_paths) => {
|
||||
if expanded_paths.is_empty() {
|
||||
watch_folders.push(p);
|
||||
} else {
|
||||
watch_folders.extend(expanded_paths);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// If this fails cargo itself should fail too. But we still try to keep going with the unexpanded path.
|
||||
log::error!("Error watching {}: {}", p.display(), err);
|
||||
watch_folders.push(p);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(watch_folders)
|
||||
}
|
||||
|
||||
@ -526,9 +492,9 @@ impl Rust {
|
||||
fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
options: Options,
|
||||
run_args: Vec<String>,
|
||||
run_args: &[String],
|
||||
on_exit: F,
|
||||
) -> crate::Result<Box<dyn DevProcess + Send>> {
|
||||
) -> crate::Result<desktop::DevChild> {
|
||||
desktop::run_dev(
|
||||
options,
|
||||
run_args,
|
||||
@ -536,26 +502,30 @@ impl Rust {
|
||||
self.config_features.clone(),
|
||||
on_exit,
|
||||
)
|
||||
.map(|c| Box::new(c) as Box<dyn DevProcess + Send>)
|
||||
}
|
||||
|
||||
fn run_dev_watcher<F: Fn(&mut Rust) -> crate::Result<Box<dyn DevProcess + Send>>>(
|
||||
fn run_dev_watcher<
|
||||
F: Fn(&mut Rust, &ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>,
|
||||
>(
|
||||
&mut self,
|
||||
config: &Mutex<ConfigMetadata>,
|
||||
config: &mut ConfigMetadata,
|
||||
additional_watch_folders: &[PathBuf],
|
||||
merge_configs: &[&serde_json::Value],
|
||||
run: Arc<F>,
|
||||
run: F,
|
||||
dirs: &Dirs,
|
||||
) -> crate::Result<()> {
|
||||
let child = run(self)?;
|
||||
|
||||
let process = Arc::new(Mutex::new(child));
|
||||
let mut child = run(self, config)?;
|
||||
let (tx, rx) = sync_channel(1);
|
||||
|
||||
let watch_folders = get_watch_folders(additional_watch_folders, dirs.tauri)?;
|
||||
|
||||
let common_ancestor = common_path::common_path_all(watch_folders.iter().map(Path::new))
|
||||
.expect("watch_folders should not be empty");
|
||||
let common_ancestor = common_path::common_path_all(
|
||||
watch_folders
|
||||
.iter()
|
||||
.map(Path::new)
|
||||
.chain(once(self.app_settings.workspace_dir.as_path())),
|
||||
)
|
||||
.expect("watch_folders should not be empty");
|
||||
let ignore_matcher = build_ignore_matcher(&common_ancestor);
|
||||
|
||||
let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| {
|
||||
@ -593,9 +563,9 @@ impl Rust {
|
||||
if let Some(event_path) = event.paths.first() {
|
||||
if !ignore_matcher.is_ignore(event_path, event_path.is_dir()) {
|
||||
if is_configuration_file(self.app_settings.target_platform, event_path)
|
||||
&& reload_config(&mut config.lock().unwrap(), merge_configs, dirs.tauri).is_ok()
|
||||
&& reload_config(config, merge_configs, dirs.tauri).is_ok()
|
||||
{
|
||||
let (manifest, modified) = rewrite_manifest(&config.lock().unwrap(), dirs.tauri)?;
|
||||
let (manifest, modified) = rewrite_manifest(config, dirs.tauri)?;
|
||||
if modified {
|
||||
*self.app_settings.manifest.lock().unwrap() = manifest;
|
||||
// no need to run the watcher logic, the manifest was modified
|
||||
@ -609,17 +579,12 @@ impl Rust {
|
||||
display_path(event_path.strip_prefix(dirs.frontend).unwrap_or(event_path))
|
||||
);
|
||||
|
||||
let mut p = process.lock().unwrap();
|
||||
p.kill().context("failed to kill app process")?;
|
||||
child.kill().context("failed to kill app process")?;
|
||||
|
||||
// wait for the process to exit
|
||||
// note that on mobile, kill() already waits for the process to exit (duct implementation)
|
||||
loop {
|
||||
if !matches!(p.try_wait(), Ok(None)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
*p = run(self)?;
|
||||
let _ = child.wait();
|
||||
child = run(self, config)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -690,7 +655,7 @@ pub struct TomlWorkspaceField {
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
struct WorkspaceSettings {
|
||||
/// the workspace members.
|
||||
members: Option<Vec<String>>,
|
||||
// members: Option<Vec<String>>,
|
||||
package: Option<WorkspacePackageSettings>,
|
||||
}
|
||||
|
||||
@ -777,6 +742,7 @@ pub struct RustAppSettings {
|
||||
cargo_config: CargoConfig,
|
||||
target_triple: String,
|
||||
target_platform: TargetPlatform,
|
||||
workspace_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -1094,7 +1060,8 @@ impl RustAppSettings {
|
||||
}
|
||||
};
|
||||
|
||||
let ws_package_settings = CargoSettings::load(&get_workspace_dir(tauri_dir)?)
|
||||
let workspace_dir = get_workspace_dir(tauri_dir)?;
|
||||
let ws_package_settings = CargoSettings::load(&workspace_dir)
|
||||
.context("failed to load Cargo settings from workspace root")?
|
||||
.workspace
|
||||
.and_then(|v| v.package);
|
||||
@ -1189,6 +1156,7 @@ impl RustAppSettings {
|
||||
cargo_config,
|
||||
target_triple,
|
||||
target_platform,
|
||||
workspace_dir,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1208,6 +1176,23 @@ impl RustAppSettings {
|
||||
pub(crate) struct CargoMetadata {
|
||||
pub(crate) target_directory: PathBuf,
|
||||
pub(crate) workspace_root: PathBuf,
|
||||
workspace_members: Vec<String>,
|
||||
packages: Vec<Package>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Package {
|
||||
name: String,
|
||||
id: String,
|
||||
manifest_path: PathBuf,
|
||||
dependencies: Vec<Dependency>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Dependency {
|
||||
name: String,
|
||||
/// Local package
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub(crate) fn get_cargo_metadata(tauri_dir: &Path) -> crate::Result<CargoMetadata> {
|
||||
@ -1230,6 +1215,56 @@ pub(crate) fn get_cargo_metadata(tauri_dir: &Path) -> crate::Result<CargoMetadat
|
||||
serde_json::from_slice(&output.stdout).context("failed to parse cargo metadata")
|
||||
}
|
||||
|
||||
/// Get the tauri project crate's dependencies that are inside the workspace
|
||||
fn get_in_workspace_dependency_paths(tauri_dir: &Path) -> crate::Result<Vec<PathBuf>> {
|
||||
let metadata = get_cargo_metadata(tauri_dir)?;
|
||||
let tauri_project_manifest_path = tauri_dir.join("Cargo.toml");
|
||||
let tauri_project_package = metadata
|
||||
.packages
|
||||
.iter()
|
||||
.find(|package| package.manifest_path == tauri_project_manifest_path)
|
||||
.context("tauri project package doesn't exist in cargo metadata output `packages`")?;
|
||||
|
||||
let workspace_packages = metadata
|
||||
.workspace_members
|
||||
.iter()
|
||||
.map(|member_package_id| {
|
||||
metadata
|
||||
.packages
|
||||
.iter()
|
||||
.find(|package| package.id == *member_package_id)
|
||||
.context("workspace member doesn't exist in cargo metadata output `packages`")
|
||||
})
|
||||
.collect::<crate::Result<Vec<_>>>()?;
|
||||
|
||||
let mut found_dependency_paths = Vec::new();
|
||||
find_dependencies(
|
||||
tauri_project_package,
|
||||
&workspace_packages,
|
||||
&mut found_dependency_paths,
|
||||
);
|
||||
Ok(found_dependency_paths)
|
||||
}
|
||||
|
||||
fn find_dependencies(
|
||||
package: &Package,
|
||||
workspace_packages: &Vec<&Package>,
|
||||
found_dependency_paths: &mut Vec<PathBuf>,
|
||||
) {
|
||||
for dependency in &package.dependencies {
|
||||
if let Some(path) = &dependency.path {
|
||||
if let Some(package) = workspace_packages.iter().find(|workspace_package| {
|
||||
workspace_package.name == dependency.name
|
||||
&& path.join("Cargo.toml") == workspace_package.manifest_path
|
||||
&& !found_dependency_paths.contains(path)
|
||||
}) {
|
||||
found_dependency_paths.push(path.to_owned());
|
||||
find_dependencies(package, workspace_packages, found_dependency_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the cargo target directory based on the provided arguments.
|
||||
/// If "--target-dir" is specified in args, use it as the target directory (relative to current directory).
|
||||
/// Otherwise, use the target directory from cargo metadata.
|
||||
@ -1339,7 +1374,7 @@ fn tauri_config_to_bundle_settings(
|
||||
if enabled_features.contains(&"tray-icon".into())
|
||||
|| enabled_features.contains(&"tauri/tray-icon".into())
|
||||
{
|
||||
let (tray_kind, path) = std::env::var("TAURI_LINUX_AYATANA_APPINDICATOR")
|
||||
let (tray_kind, path) = std::env::var_os("TAURI_LINUX_AYATANA_APPINDICATOR")
|
||||
.map(|ayatana| {
|
||||
if ayatana == "true" || ayatana == "1" {
|
||||
(
|
||||
@ -1361,7 +1396,7 @@ fn tauri_config_to_bundle_settings(
|
||||
)
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| pkgconfig_utils::get_appindicator_library_path());
|
||||
.unwrap_or_else(pkgconfig_utils::get_appindicator_library_path);
|
||||
match tray_kind {
|
||||
pkgconfig_utils::TrayKind::Ayatana => {
|
||||
depends_deb.push("libayatana-appindicator3-1".into());
|
||||
@ -1452,14 +1487,16 @@ fn tauri_config_to_bundle_settings(
|
||||
.map(tauri_bundler::bundle::Entitlements::Path)
|
||||
} else {
|
||||
let mut app_links_entitlements = plist::Dictionary::new();
|
||||
app_links_entitlements.insert(
|
||||
"com.apple.developer.associated-domains".to_string(),
|
||||
domains
|
||||
.into_iter()
|
||||
.map(|domain| format!("applinks:{domain}").into())
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
);
|
||||
if !domains.is_empty() {
|
||||
app_links_entitlements.insert(
|
||||
"com.apple.developer.associated-domains".to_string(),
|
||||
domains
|
||||
.into_iter()
|
||||
.map(|domain| format!("applinks:{domain}").into())
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let entitlements = if let Some(user_provided_entitlements) = config.macos.entitlements {
|
||||
crate::helpers::plist::merge_plist(vec![
|
||||
PathBuf::from(user_provided_entitlements).into(),
|
||||
|
||||
@ -29,30 +29,26 @@ pub struct DevChild {
|
||||
impl DevProcess for DevChild {
|
||||
fn kill(&self) -> std::io::Result<()> {
|
||||
self.dev_child.kill()?;
|
||||
self.manually_killed_app.store(true, Ordering::Relaxed);
|
||||
self.manually_killed_app.store(true, Ordering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
|
||||
self.dev_child.try_wait()
|
||||
}
|
||||
|
||||
fn wait(&self) -> std::io::Result<ExitStatus> {
|
||||
self.dev_child.wait()
|
||||
}
|
||||
|
||||
fn manually_killed_process(&self) -> bool {
|
||||
self.manually_killed_app.load(Ordering::Relaxed)
|
||||
self.manually_killed_app.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
|
||||
options: Options,
|
||||
run_args: Vec<String>,
|
||||
run_args: &[String],
|
||||
available_targets: &mut Option<Vec<RustupTarget>>,
|
||||
config_features: Vec<String>,
|
||||
on_exit: F,
|
||||
) -> crate::Result<impl DevProcess> {
|
||||
) -> crate::Result<DevChild> {
|
||||
let mut dev_cmd = cargo_command(true, options, available_targets, config_features)?;
|
||||
let runner = dev_cmd.get_program().to_string_lossy().into_owned();
|
||||
|
||||
@ -137,7 +133,7 @@ pub fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
|
||||
status.code(),
|
||||
if status.code() == Some(101) && is_cargo_compile_error {
|
||||
ExitReason::CompilationFailed
|
||||
} else if manually_killed_app_.load(Ordering::Relaxed) {
|
||||
} else if manually_killed_app_.load(Ordering::SeqCst) {
|
||||
ExitReason::TriggeredKill
|
||||
} else {
|
||||
ExitReason::NormalExit
|
||||
@ -163,7 +159,7 @@ pub fn build(
|
||||
let out_dir = app_settings.out_dir(&options, tauri_dir)?;
|
||||
let bin_path = app_settings.app_binary_path(&options, tauri_dir)?;
|
||||
|
||||
if !std::env::var("STATIC_VCRUNTIME").is_ok_and(|v| v == "false") {
|
||||
if !std::env::var_os("STATIC_VCRUNTIME").is_some_and(|v| v == "false") {
|
||||
std::env::set_var("STATIC_VCRUNTIME", "true");
|
||||
}
|
||||
|
||||
|
||||
@ -351,10 +351,7 @@ mod tests {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(f) = item_table
|
||||
.and_then(|t| t.get("features").cloned())
|
||||
.and_then(|f| f.as_array().cloned())
|
||||
{
|
||||
if let Some(f) = item_table.and_then(|t| t.get("features")?.as_array().cloned()) {
|
||||
for feature in f.iter() {
|
||||
let feature = feature.as_str().expect("feature is not a string");
|
||||
if !dep.all_cli_managed_features.contains(&feature) {
|
||||
|
||||
@ -6,7 +6,7 @@ use super::{detect_target_ok, ensure_init, env, get_app, get_config, read_option
|
||||
use crate::{
|
||||
error::{Context, ErrorExt},
|
||||
helpers::config::{get_config as get_tauri_config, reload_config as reload_tauri_config},
|
||||
interface::{AppInterface, Interface},
|
||||
interface::AppInterface,
|
||||
mobile::CliOptions,
|
||||
Error, Result,
|
||||
};
|
||||
@ -62,20 +62,17 @@ pub fn command(options: Options) -> Result<()> {
|
||||
)?
|
||||
};
|
||||
|
||||
let (config, metadata) = {
|
||||
let (config, metadata) = get_config(
|
||||
&get_app(
|
||||
MobileTarget::Android,
|
||||
&tauri_config,
|
||||
&AppInterface::new(&tauri_config, None, dirs.tauri)?,
|
||||
dirs.tauri,
|
||||
),
|
||||
let (config, metadata) = get_config(
|
||||
&get_app(
|
||||
MobileTarget::Android,
|
||||
&tauri_config,
|
||||
&[],
|
||||
&cli_options,
|
||||
);
|
||||
(config, metadata)
|
||||
};
|
||||
&AppInterface::new(&tauri_config, None, dirs.tauri)?,
|
||||
dirs.tauri,
|
||||
),
|
||||
&tauri_config,
|
||||
&[],
|
||||
&cli_options,
|
||||
);
|
||||
|
||||
ensure_init(
|
||||
&tauri_config,
|
||||
|
||||
@ -14,7 +14,7 @@ use crate::{
|
||||
config::{get_config as get_tauri_config, ConfigMetadata},
|
||||
flock,
|
||||
},
|
||||
interface::{AppInterface, Interface, Options as InterfaceOptions},
|
||||
interface::{AppInterface, Options as InterfaceOptions},
|
||||
mobile::{android::generate_tauri_properties, write_options, CliOptions, TargetDevice},
|
||||
ConfigValue, Error, Result,
|
||||
};
|
||||
@ -28,7 +28,6 @@ use cargo_mobile2::{
|
||||
|
||||
use std::env::set_current_dir;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(
|
||||
@ -65,10 +64,12 @@ pub struct Options {
|
||||
pub split_per_abi: bool,
|
||||
/// Build APKs.
|
||||
#[clap(long)]
|
||||
pub apk: Option<bool>,
|
||||
pub apk: bool,
|
||||
/// Build AABs.
|
||||
#[clap(long)]
|
||||
pub aab: Option<bool>,
|
||||
pub aab: bool,
|
||||
#[clap(skip)]
|
||||
pub skip_bundle: bool,
|
||||
/// Open Android Studio
|
||||
#[clap(short, long)]
|
||||
pub open: bool,
|
||||
@ -119,7 +120,7 @@ pub struct BuiltApplication {
|
||||
|
||||
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplication> {
|
||||
let dirs = crate::helpers::app_paths::resolve_dirs();
|
||||
let tauri_config = Mutex::new(get_tauri_config(
|
||||
let tauri_config = get_tauri_config(
|
||||
tauri_utils::platform::Target::Android,
|
||||
&options
|
||||
.config
|
||||
@ -127,7 +128,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
|
||||
.map(|conf| &conf.0)
|
||||
.collect::<Vec<_>>(),
|
||||
dirs.tauri,
|
||||
)?);
|
||||
)?;
|
||||
run(options, noise_level, &dirs, &tauri_config)
|
||||
}
|
||||
|
||||
@ -135,7 +136,7 @@ pub fn run(
|
||||
options: Options,
|
||||
noise_level: NoiseLevel,
|
||||
dirs: &Dirs,
|
||||
tauri_config: &Mutex<ConfigMetadata>,
|
||||
tauri_config: &ConfigMetadata,
|
||||
) -> Result<BuiltApplication> {
|
||||
delete_codegen_vars();
|
||||
|
||||
@ -151,21 +152,17 @@ pub fn run(
|
||||
)
|
||||
.unwrap();
|
||||
build_options.target = Some(first_target.triple.into());
|
||||
let tauri_config = &tauri_config.lock().unwrap();
|
||||
|
||||
let (interface, config, metadata) = {
|
||||
let interface = AppInterface::new(tauri_config, build_options.target.clone(), dirs.tauri)?;
|
||||
interface.build_options(&mut Vec::new(), &mut build_options.features, true);
|
||||
let interface = AppInterface::new(tauri_config, build_options.target.clone(), dirs.tauri)?;
|
||||
interface.build_options(&mut Vec::new(), &mut build_options.features, true);
|
||||
|
||||
let app = get_app(MobileTarget::Android, tauri_config, &interface, dirs.tauri);
|
||||
let (config, metadata) = get_config(
|
||||
&app,
|
||||
tauri_config,
|
||||
&build_options.features,
|
||||
&Default::default(),
|
||||
);
|
||||
(interface, config, metadata)
|
||||
};
|
||||
let app = get_app(MobileTarget::Android, tauri_config, &interface, dirs.tauri);
|
||||
let (config, metadata) = get_config(
|
||||
&app,
|
||||
tauri_config,
|
||||
&build_options.features,
|
||||
&Default::default(),
|
||||
);
|
||||
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
@ -188,7 +185,7 @@ pub fn run(
|
||||
|
||||
generate_tauri_properties(&config, tauri_config, false)?;
|
||||
|
||||
crate::build::setup(&interface, &mut build_options, tauri_config, true, dirs)?;
|
||||
crate::build::setup(&interface, &mut build_options, tauri_config, dirs, true)?;
|
||||
|
||||
let installed_targets =
|
||||
crate::interface::rust::installation::installed_targets().unwrap_or_default();
|
||||
@ -244,10 +241,10 @@ fn run_build(
|
||||
noise_level: NoiseLevel,
|
||||
tauri_dir: &Path,
|
||||
) -> Result<OptionsHandle> {
|
||||
if !(options.apk.is_some() || options.aab.is_some()) {
|
||||
if !(options.skip_bundle || options.apk || options.aab) {
|
||||
// if the user didn't specify the format to build, we'll do both
|
||||
options.apk = Some(true);
|
||||
options.aab = Some(true);
|
||||
options.apk = true;
|
||||
options.aab = true;
|
||||
}
|
||||
|
||||
let interface_options = InterfaceOptions {
|
||||
@ -274,7 +271,7 @@ fn run_build(
|
||||
|
||||
inject_resources(config, tauri_config)?;
|
||||
|
||||
let apk_outputs = if options.apk.unwrap_or_default() {
|
||||
let apk_outputs = if options.apk {
|
||||
apk::build(
|
||||
config,
|
||||
env,
|
||||
@ -288,7 +285,7 @@ fn run_build(
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let aab_outputs = if options.aab.unwrap_or_default() {
|
||||
let aab_outputs = if options.aab {
|
||||
aab::build(
|
||||
config,
|
||||
env,
|
||||
|
||||
@ -14,7 +14,7 @@ use crate::{
|
||||
config::{get_config as get_tauri_config, ConfigMetadata},
|
||||
flock,
|
||||
},
|
||||
interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions},
|
||||
interface::{AppInterface, MobileOptions, Options as InterfaceOptions},
|
||||
mobile::{
|
||||
android::generate_tauri_properties, use_network_address_for_dev_url, write_options, CliOptions,
|
||||
DevChild, DevHost, DevProcess, TargetDevice,
|
||||
@ -35,7 +35,6 @@ use cargo_mobile2::{
|
||||
};
|
||||
use url::Host;
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::{env::set_current_dir, net::Ipv4Addr, path::PathBuf};
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
@ -184,18 +183,15 @@ fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<
|
||||
.unwrap_or_else(|| Target::all().values().next().unwrap().triple.into());
|
||||
dev_options.target = Some(target_triple);
|
||||
|
||||
let (interface, config, metadata) = {
|
||||
let interface = AppInterface::new(&tauri_config, dev_options.target.clone(), dirs.tauri)?;
|
||||
let interface = AppInterface::new(&tauri_config, dev_options.target.clone(), dirs.tauri)?;
|
||||
|
||||
let app = get_app(MobileTarget::Android, &tauri_config, &interface, dirs.tauri);
|
||||
let (config, metadata) = get_config(
|
||||
&app,
|
||||
&tauri_config,
|
||||
dev_options.features.as_ref(),
|
||||
&Default::default(),
|
||||
);
|
||||
(interface, config, metadata)
|
||||
};
|
||||
let app = get_app(MobileTarget::Android, &tauri_config, &interface, dirs.tauri);
|
||||
let (config, metadata) = get_config(
|
||||
&app,
|
||||
&tauri_config,
|
||||
dev_options.features.as_ref(),
|
||||
&Default::default(),
|
||||
);
|
||||
|
||||
set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?;
|
||||
|
||||
@ -256,8 +252,6 @@ fn run_dev(
|
||||
|
||||
crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, dirs)?;
|
||||
|
||||
let tauri_config = Mutex::new(tauri_config);
|
||||
|
||||
let interface_options = InterfaceOptions {
|
||||
debug: !dev_options.release_mode,
|
||||
target: dev_options.target.clone(),
|
||||
@ -270,7 +264,7 @@ fn run_dev(
|
||||
|
||||
configure_cargo(&mut env, config)?;
|
||||
|
||||
generate_tauri_properties(config, &tauri_config.lock().unwrap(), true)?;
|
||||
generate_tauri_properties(config, &tauri_config, true)?;
|
||||
|
||||
let installed_targets =
|
||||
crate::interface::rust::installation::installed_targets().unwrap_or_default();
|
||||
@ -306,7 +300,7 @@ fn run_dev(
|
||||
|
||||
let open = options.open;
|
||||
interface.mobile_dev(
|
||||
&tauri_config,
|
||||
&mut tauri_config,
|
||||
MobileOptions {
|
||||
debug: !options.release_mode,
|
||||
features: options.features,
|
||||
@ -315,7 +309,7 @@ fn run_dev(
|
||||
no_watch: options.no_watch,
|
||||
additional_watch_folders: options.additional_watch_folders,
|
||||
},
|
||||
|options| {
|
||||
|options, tauri_config| {
|
||||
let cli_options = CliOptions {
|
||||
dev: true,
|
||||
features: options.features.clone(),
|
||||
@ -329,9 +323,9 @@ fn run_dev(
|
||||
}),
|
||||
};
|
||||
|
||||
let _handle = write_options(&tauri_config.lock().unwrap(), cli_options)?;
|
||||
let _handle = write_options(tauri_config, cli_options)?;
|
||||
|
||||
inject_resources(config, &tauri_config.lock().unwrap())?;
|
||||
inject_resources(config, tauri_config)?;
|
||||
|
||||
if open {
|
||||
open_and_wait(config, &env)
|
||||
|
||||
@ -104,7 +104,6 @@ enum Commands {
|
||||
}
|
||||
|
||||
pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
|
||||
let dirs = crate::helpers::app_paths::resolve_dirs();
|
||||
let noise_level = NoiseLevel::from_occurrences(verbosity as u64);
|
||||
match cli.command {
|
||||
Commands::Init(options) => init_command(
|
||||
@ -113,7 +112,6 @@ pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
|
||||
false,
|
||||
options.skip_targets_install,
|
||||
options.config,
|
||||
&dirs,
|
||||
)?,
|
||||
Commands::Dev(options) => dev::command(options, noise_level)?,
|
||||
Commands::Build(options) => build::command(options, noise_level).map(|_| ())?,
|
||||
|
||||
@ -9,12 +9,12 @@ use cargo_mobile2::{
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::{configure_cargo, device_prompt, env};
|
||||
use crate::{
|
||||
error::Context,
|
||||
interface::{DevProcess, Interface, WatcherOptions},
|
||||
helpers::config::ConfigMetadata,
|
||||
interface::{DevProcess, WatcherOptions},
|
||||
mobile::{DevChild, TargetDevice},
|
||||
ConfigValue, Result,
|
||||
};
|
||||
@ -79,7 +79,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
};
|
||||
|
||||
let dirs = crate::helpers::app_paths::resolve_dirs();
|
||||
let cfg = crate::helpers::config::get_config(
|
||||
let mut tauri_config = crate::helpers::config::get_config(
|
||||
tauri_utils::platform::Target::Android,
|
||||
&options
|
||||
.config
|
||||
@ -88,7 +88,6 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
.collect::<Vec<_>>(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
let tauri_config = Mutex::new(cfg);
|
||||
let mut built_application = super::build::run(
|
||||
super::build::Options {
|
||||
debug: !options.release,
|
||||
@ -102,8 +101,9 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
features: options.features,
|
||||
config: options.config.clone(),
|
||||
split_per_abi: true,
|
||||
apk: Some(false),
|
||||
aab: Some(false),
|
||||
apk: false,
|
||||
aab: false,
|
||||
skip_bundle: false,
|
||||
open: options.open,
|
||||
ci: false,
|
||||
args: options.args,
|
||||
@ -125,7 +125,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
if let Some(device) = device {
|
||||
let config = built_application.config.clone();
|
||||
let release = options.release;
|
||||
let runner = move || {
|
||||
let runner = move |_tauri_config: &ConfigMetadata| {
|
||||
device
|
||||
.run(
|
||||
&config,
|
||||
@ -150,10 +150,10 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
};
|
||||
|
||||
if options.no_watch {
|
||||
runner()?;
|
||||
runner(&tauri_config)?;
|
||||
} else {
|
||||
built_application.interface.watch(
|
||||
&tauri_config,
|
||||
&mut tauri_config,
|
||||
WatcherOptions {
|
||||
config: options.config,
|
||||
additional_watch_folders: options.additional_watch_folders,
|
||||
|
||||
@ -6,7 +6,7 @@ use super::{get_app, Target};
|
||||
use crate::{
|
||||
helpers::app_paths::Dirs,
|
||||
helpers::{config::get_config as get_tauri_config, template::JsonMap},
|
||||
interface::{AppInterface, Interface},
|
||||
interface::AppInterface,
|
||||
ConfigValue, Result,
|
||||
};
|
||||
use cargo_mobile2::{
|
||||
@ -29,8 +29,8 @@ pub fn command(
|
||||
reinstall_deps: bool,
|
||||
skip_targets_install: bool,
|
||||
config: Vec<ConfigValue>,
|
||||
dirs: &Dirs,
|
||||
) -> Result<()> {
|
||||
let dirs = crate::helpers::app_paths::resolve_dirs();
|
||||
let wrapper = TextWrapper::default();
|
||||
|
||||
exec(
|
||||
@ -45,14 +45,14 @@ pub fn command(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
fn exec(
|
||||
target: Target,
|
||||
wrapper: &TextWrapper,
|
||||
#[allow(unused_variables)] non_interactive: bool,
|
||||
#[allow(unused_variables)] reinstall_deps: bool,
|
||||
skip_targets_install: bool,
|
||||
config: Vec<ConfigValue>,
|
||||
dirs: &Dirs,
|
||||
dirs: Dirs,
|
||||
) -> Result<App> {
|
||||
let tauri_config = get_tauri_config(
|
||||
target.platform_target(),
|
||||
|
||||
@ -16,7 +16,7 @@ use crate::{
|
||||
flock,
|
||||
plist::merge_plist,
|
||||
},
|
||||
interface::{AppInterface, Interface, Options as InterfaceOptions},
|
||||
interface::{AppInterface, Options as InterfaceOptions},
|
||||
mobile::{ios::ensure_ios_runtime_installed, write_options, CliOptions, TargetDevice},
|
||||
ConfigValue, Error, Result,
|
||||
};
|
||||
@ -167,7 +167,12 @@ pub struct BuiltApplication {
|
||||
options_handle: OptionsHandle,
|
||||
}
|
||||
|
||||
pub fn command(options: Options, noise_level: NoiseLevel, dirs: &Dirs) -> Result<BuiltApplication> {
|
||||
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplication> {
|
||||
let dirs = crate::helpers::app_paths::resolve_dirs();
|
||||
run(options, noise_level, &dirs)
|
||||
}
|
||||
|
||||
pub fn run(options: Options, noise_level: NoiseLevel, dirs: &Dirs) -> Result<BuiltApplication> {
|
||||
let mut build_options: BuildOptions = options.clone().into();
|
||||
build_options.target = Some(
|
||||
Target::all()
|
||||
@ -189,20 +194,17 @@ pub fn command(options: Options, noise_level: NoiseLevel, dirs: &Dirs) -> Result
|
||||
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
let (interface, mut config) = {
|
||||
let interface = AppInterface::new(&tauri_config, build_options.target.clone(), dirs.tauri)?;
|
||||
interface.build_options(&mut Vec::new(), &mut build_options.features, true);
|
||||
let interface = AppInterface::new(&tauri_config, build_options.target.clone(), dirs.tauri)?;
|
||||
interface.build_options(&mut Vec::new(), &mut build_options.features, true);
|
||||
|
||||
let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri);
|
||||
let (config, _metadata) = get_config(
|
||||
&app,
|
||||
&tauri_config,
|
||||
&build_options.features,
|
||||
&Default::default(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
(interface, config)
|
||||
};
|
||||
let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri);
|
||||
let (mut config, _) = get_config(
|
||||
&app,
|
||||
&tauri_config,
|
||||
&build_options.features,
|
||||
&Default::default(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
|
||||
set_current_dir(dirs.tauri).context("failed to set current directory")?;
|
||||
|
||||
@ -357,7 +359,7 @@ fn run_build(
|
||||
Profile::Release
|
||||
};
|
||||
|
||||
crate::build::setup(interface, &mut build_options, &tauri_config, true, dirs)?;
|
||||
crate::build::setup(interface, &mut build_options, &tauri_config, dirs, true)?;
|
||||
|
||||
let app_settings = interface.app_settings();
|
||||
let out_dir = app_settings.out_dir(
|
||||
|
||||
@ -15,7 +15,7 @@ use crate::{
|
||||
flock,
|
||||
plist::merge_plist,
|
||||
},
|
||||
interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions},
|
||||
interface::{AppInterface, MobileOptions, Options as InterfaceOptions},
|
||||
mobile::{
|
||||
ios::ensure_ios_runtime_installed, use_network_address_for_dev_url, write_options, CliOptions,
|
||||
DevChild, DevHost, DevProcess,
|
||||
@ -35,7 +35,6 @@ use cargo_mobile2::{
|
||||
};
|
||||
use url::Host;
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::{env::set_current_dir, net::Ipv4Addr, path::PathBuf};
|
||||
|
||||
const PHYSICAL_IPHONE_DEV_WARNING: &str = "To develop on physical phones you need the `--host` option (not required for Simulators). See the documentation for more information: https://v2.tauri.app/develop/#development-server";
|
||||
@ -189,20 +188,16 @@ fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<
|
||||
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
let (interface, config) = {
|
||||
let interface = AppInterface::new(&tauri_config, Some(target_triple), dirs.tauri)?;
|
||||
let interface = AppInterface::new(&tauri_config, Some(target_triple), dirs.tauri)?;
|
||||
|
||||
let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri);
|
||||
let (config, _metadata) = get_config(
|
||||
&app,
|
||||
&tauri_config,
|
||||
&dev_options.features,
|
||||
&Default::default(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
|
||||
(interface, config)
|
||||
};
|
||||
let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri);
|
||||
let (config, _) = get_config(
|
||||
&app,
|
||||
&tauri_config,
|
||||
&dev_options.features,
|
||||
&Default::default(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
|
||||
set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?;
|
||||
|
||||
@ -304,8 +299,6 @@ fn run_dev(
|
||||
|
||||
crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, &dirs)?;
|
||||
|
||||
let tauri_config = Mutex::new(tauri_config);
|
||||
|
||||
let app_settings = interface.app_settings();
|
||||
let out_dir = app_settings.out_dir(
|
||||
&InterfaceOptions {
|
||||
@ -321,7 +314,7 @@ fn run_dev(
|
||||
|
||||
let open = options.open;
|
||||
interface.mobile_dev(
|
||||
&tauri_config,
|
||||
&mut tauri_config,
|
||||
MobileOptions {
|
||||
debug: true,
|
||||
features: options.features,
|
||||
@ -330,7 +323,7 @@ fn run_dev(
|
||||
no_watch: options.no_watch,
|
||||
additional_watch_folders: options.additional_watch_folders,
|
||||
},
|
||||
|options| {
|
||||
|options, tauri_config| {
|
||||
let cli_options = CliOptions {
|
||||
dev: true,
|
||||
features: options.features.clone(),
|
||||
@ -340,7 +333,7 @@ fn run_dev(
|
||||
config: dev_options.config.clone(),
|
||||
target_device: None,
|
||||
};
|
||||
let _handle = write_options(&tauri_config.lock().unwrap(), cli_options)?;
|
||||
let _handle = write_options(tauri_config, cli_options)?;
|
||||
|
||||
let open_xcode = || {
|
||||
if !set_host {
|
||||
|
||||
@ -102,7 +102,6 @@ enum Commands {
|
||||
|
||||
pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
|
||||
let noise_level = NoiseLevel::from_occurrences(verbosity as u64);
|
||||
let dirs = crate::helpers::app_paths::resolve_dirs();
|
||||
match cli.command {
|
||||
Commands::Init(options) => init_command(
|
||||
MobileTarget::Ios,
|
||||
@ -110,10 +109,9 @@ pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
|
||||
options.reinstall_deps,
|
||||
options.skip_targets_install,
|
||||
options.config,
|
||||
&dirs,
|
||||
)?,
|
||||
Commands::Dev(options) => dev::command(options, noise_level)?,
|
||||
Commands::Build(options) => build::command(options, noise_level, &dirs).map(|_| ())?,
|
||||
Commands::Build(options) => build::command(options, noise_level).map(|_| ())?,
|
||||
Commands::Run(options) => run::command(options, noise_level)?,
|
||||
Commands::XcodeScript(options) => xcode_script::command(options)?,
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use cargo_mobile2::opts::{NoiseLevel, Profile};
|
||||
use clap::{ArgAction, Parser};
|
||||
@ -11,8 +10,8 @@ use clap::{ArgAction, Parser};
|
||||
use super::{device_prompt, env};
|
||||
use crate::{
|
||||
error::Context,
|
||||
helpers::config::get_config as get_tauri_config,
|
||||
interface::{DevProcess, Interface, WatcherOptions},
|
||||
helpers::config::{get_config as get_tauri_config, ConfigMetadata},
|
||||
interface::{DevProcess, WatcherOptions},
|
||||
mobile::{DevChild, TargetDevice},
|
||||
ConfigValue, Result,
|
||||
};
|
||||
@ -77,7 +76,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
|
||||
let dirs = crate::helpers::app_paths::resolve_dirs();
|
||||
|
||||
let mut built_application = super::build::command(
|
||||
let mut built_application = super::build::run(
|
||||
super::build::Options {
|
||||
debug: !options.release,
|
||||
targets: Some(vec![]), /* skips IPA build since there's no target */
|
||||
@ -98,17 +97,16 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
&dirs,
|
||||
)?;
|
||||
|
||||
let cfg = get_tauri_config(
|
||||
let mut tauri_config = get_tauri_config(
|
||||
tauri_utils::platform::Target::Ios,
|
||||
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
let tauri_config = Mutex::new(cfg);
|
||||
|
||||
// options.open is handled by the build command
|
||||
// so all we need to do here is run the app on the selected device
|
||||
if let Some(device) = device {
|
||||
let runner = move || {
|
||||
let runner = move |_tauri_config: &ConfigMetadata| {
|
||||
device
|
||||
.run(
|
||||
&built_application.config,
|
||||
@ -126,10 +124,10 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
};
|
||||
|
||||
if options.no_watch {
|
||||
runner()?;
|
||||
runner(&tauri_config)?;
|
||||
} else {
|
||||
built_application.interface.watch(
|
||||
&tauri_config,
|
||||
&mut tauri_config,
|
||||
WatcherOptions {
|
||||
config: options.config,
|
||||
additional_watch_folders: options.additional_watch_folders,
|
||||
|
||||
@ -6,7 +6,7 @@ use super::{ensure_init, env, get_app, get_config, read_options, MobileTarget};
|
||||
use crate::{
|
||||
error::{Context, ErrorExt},
|
||||
helpers::config::{get_config as get_tauri_config, reload_config as reload_tauri_config},
|
||||
interface::{AppInterface, Interface, Options as InterfaceOptions},
|
||||
interface::{AppInterface, Options as InterfaceOptions},
|
||||
mobile::ios::LIB_OUTPUT_FILE_NAME,
|
||||
Error, Result,
|
||||
};
|
||||
@ -95,40 +95,33 @@ pub fn command(options: Options) -> Result<()> {
|
||||
let macos = macos_from_platform(&options.platform);
|
||||
|
||||
let mut tauri_config = get_tauri_config(tauri_utils::platform::Target::Ios, &[], dirs.tauri)?;
|
||||
let cli_options = {
|
||||
let cli_options = { read_options(&tauri_config) };
|
||||
if !cli_options.config.is_empty() {
|
||||
// reload config with merges from the ios dev|build script
|
||||
reload_tauri_config(
|
||||
&mut tauri_config,
|
||||
&cli_options
|
||||
.config
|
||||
.iter()
|
||||
.map(|conf| &conf.0)
|
||||
.collect::<Vec<_>>(),
|
||||
dirs.tauri,
|
||||
)?
|
||||
};
|
||||
|
||||
cli_options
|
||||
};
|
||||
|
||||
let (config, metadata) = {
|
||||
let cli_options = read_options(&tauri_config);
|
||||
let (config, metadata) = get_config(
|
||||
&get_app(
|
||||
MobileTarget::Ios,
|
||||
&tauri_config,
|
||||
&AppInterface::new(&tauri_config, None, dirs.tauri)?,
|
||||
dirs.tauri,
|
||||
),
|
||||
&tauri_config,
|
||||
&[],
|
||||
&cli_options,
|
||||
let cli_options = read_options(&tauri_config);
|
||||
if !cli_options.config.is_empty() {
|
||||
// reload config with merges from the ios dev|build script
|
||||
reload_tauri_config(
|
||||
&mut tauri_config,
|
||||
&cli_options
|
||||
.config
|
||||
.iter()
|
||||
.map(|conf| &conf.0)
|
||||
.collect::<Vec<_>>(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
(config, metadata)
|
||||
)?
|
||||
};
|
||||
|
||||
let (config, metadata) = get_config(
|
||||
&get_app(
|
||||
MobileTarget::Ios,
|
||||
&tauri_config,
|
||||
&AppInterface::new(&tauri_config, None, dirs.tauri)?,
|
||||
dirs.tauri,
|
||||
),
|
||||
&tauri_config,
|
||||
&[],
|
||||
&cli_options,
|
||||
dirs.tauri,
|
||||
)?;
|
||||
|
||||
ensure_init(
|
||||
&tauri_config,
|
||||
config.app(),
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
use crate::{
|
||||
error::{Context, ErrorExt},
|
||||
helpers::config::{reload_config, Config as TauriConfig, ConfigMetadata},
|
||||
interface::{AppInterface, AppSettings, DevProcess, Interface, Options as InterfaceOptions},
|
||||
interface::{AppInterface, AppSettings, DevProcess, Options as InterfaceOptions},
|
||||
ConfigValue, Error, Result,
|
||||
};
|
||||
use heck::ToSnekCase;
|
||||
@ -67,18 +67,9 @@ impl DevChild {
|
||||
|
||||
impl DevProcess for DevChild {
|
||||
fn kill(&self) -> std::io::Result<()> {
|
||||
self.manually_killed_process.store(true, Ordering::Relaxed);
|
||||
match self.child.kill() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
self.manually_killed_process.store(false, Ordering::Relaxed);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
|
||||
self.child.try_wait().map(|res| res.map(|o| o.status))
|
||||
self.child.kill()?;
|
||||
self.manually_killed_process.store(true, Ordering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait(&self) -> std::io::Result<ExitStatus> {
|
||||
@ -86,7 +77,7 @@ impl DevProcess for DevChild {
|
||||
}
|
||||
|
||||
fn manually_killed_process(&self) -> bool {
|
||||
self.manually_killed_process.load(Ordering::Relaxed)
|
||||
self.manually_killed_process.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
"dev": "wrangler dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wrangler": "^4.20.3"
|
||||
"wrangler": "^4.59.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -3176,18 +3176,20 @@ impl<'d> serde::Deserialize<'d> for PackageVersion {
|
||||
})?;
|
||||
Ok(PackageVersion(
|
||||
Version::from_str(version)
|
||||
.map_err(|_| DeError::custom("`package > version` must be a semver string"))?
|
||||
.map_err(|_| {
|
||||
DeError::custom("`tauri.conf.json > version` must be a semver string")
|
||||
})?
|
||||
.to_string(),
|
||||
))
|
||||
} else {
|
||||
Err(DeError::custom(
|
||||
"`package > version` value is not a path to a JSON object",
|
||||
"`tauri.conf.json > version` value is not a path to a JSON object",
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(PackageVersion(
|
||||
Version::from_str(value)
|
||||
.map_err(|_| DeError::custom("`package > version` must be a semver string"))?
|
||||
.map_err(|_| DeError::custom("`tauri.conf.json > version` must be a semver string"))?
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
@ -344,23 +344,20 @@ fn resource_dir_from<P: AsRef<std::path::Path>>(
|
||||
// Variable holding the type of bundle the executable is stored in. This is modified by binary
|
||||
// patching during build
|
||||
#[used]
|
||||
#[no_mangle]
|
||||
#[cfg_attr(not(target_vendor = "apple"), link_section = ".taubndl")]
|
||||
#[cfg_attr(target_vendor = "apple", link_section = "__DATA,taubndl")]
|
||||
// Marked as `mut` because it could get optimized away without it,
|
||||
// see https://github.com/tauri-apps/tauri/pull/13812
|
||||
static mut __TAURI_BUNDLE_TYPE: &str = "UNK";
|
||||
static mut __TAURI_BUNDLE_TYPE: &str = "__TAURI_BUNDLE_TYPE_VAR_UNK";
|
||||
|
||||
/// Get the type of the bundle current binary is packaged in.
|
||||
/// If the bundle type is unknown, it returns [`Option::None`].
|
||||
pub fn bundle_type() -> Option<BundleType> {
|
||||
unsafe {
|
||||
match __TAURI_BUNDLE_TYPE {
|
||||
"DEB" => Some(BundleType::Deb),
|
||||
"RPM" => Some(BundleType::Rpm),
|
||||
"APP" => Some(BundleType::AppImage),
|
||||
"MSI" => Some(BundleType::Msi),
|
||||
"NSS" => Some(BundleType::Nsis),
|
||||
"__TAURI_BUNDLE_TYPE_VAR_DEB" => Some(BundleType::Deb),
|
||||
"__TAURI_BUNDLE_TYPE_VAR_RPM" => Some(BundleType::Rpm),
|
||||
"__TAURI_BUNDLE_TYPE_VAR_APP" => Some(BundleType::AppImage),
|
||||
"__TAURI_BUNDLE_TYPE_VAR_MSI" => Some(BundleType::Msi),
|
||||
"__TAURI_BUNDLE_TYPE_VAR_NSS" => Some(BundleType::Nsis),
|
||||
_ => {
|
||||
if cfg!(target_os = "macos") {
|
||||
Some(BundleType::App)
|
||||
|
||||
@ -54,9 +54,9 @@ pub struct Channel<TSend = InvokeResponseBody> {
|
||||
#[cfg(feature = "specta")]
|
||||
const _: () = {
|
||||
#[derive(specta::Type)]
|
||||
#[specta(remote = super::Channel, rename = "TAURI_CHANNEL")]
|
||||
#[allow(dead_code)]
|
||||
struct Channel<TSend>(std::marker::PhantomData<TSend>);
|
||||
#[specta(remote = super::Channel)]
|
||||
#[allow(dead_code, non_camel_case_types)]
|
||||
struct TAURI_CHANNEL<TSend>(std::marker::PhantomData<TSend>);
|
||||
};
|
||||
|
||||
impl<TSend> Clone for Channel<TSend> {
|
||||
|
||||
@ -48,9 +48,29 @@ fn get_response(
|
||||
return resp.status(403).body(Vec::new().into()).map_err(Into::into);
|
||||
}
|
||||
|
||||
let (mut file, len, mime_type, read_bytes) = crate::async_runtime::safe_block_on(async move {
|
||||
let mut file = File::open(&path).await?;
|
||||
// Separate block for easier error handling
|
||||
let mut file = match crate::async_runtime::safe_block_on(File::open(path.clone())) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
if path.starts_with("/storage/emulated/0/Android/data/") {
|
||||
log::error!("Failed to open Android external storage file '{}': {}. This may be due to missing storage permissions.", path, e);
|
||||
}
|
||||
}
|
||||
return if e.kind() == std::io::ErrorKind::NotFound {
|
||||
log::error!("File does not exist at path: {}", path);
|
||||
return resp.status(404).body(Vec::new().into()).map_err(Into::into);
|
||||
} else if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
log::error!("Missing OS permission to access path \"{}\": {}", path, e);
|
||||
return resp.status(403).body(Vec::new().into()).map_err(Into::into);
|
||||
} else {
|
||||
Err(e.into())
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let (mut file, len, mime_type, read_bytes) = crate::async_runtime::safe_block_on(async move {
|
||||
// get file length
|
||||
let len = {
|
||||
let old_pos = file.stream_position().await?;
|
||||
|
||||
@ -243,6 +243,7 @@ pub fn assert_ipc_response<
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_doctest_main)]
|
||||
/// Executes the given IPC message and get the return value.
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
@ -2312,4 +2312,25 @@ mod tests {
|
||||
crate::test_utils::assert_send::<super::Webview>();
|
||||
crate::test_utils::assert_sync::<super::Webview>();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[test]
|
||||
fn test_webview_window_has_set_simple_fullscreen_method() {
|
||||
use crate::test::{mock_builder, mock_context, noop_assets};
|
||||
|
||||
// Create a mock app with proper context
|
||||
let app = mock_builder().build(mock_context(noop_assets())).unwrap();
|
||||
|
||||
// Get or create a webview window
|
||||
let webview_window =
|
||||
crate::WebviewWindowBuilder::new(&app, "test", crate::WebviewUrl::default())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// This should compile if set_simple_fullscreen exists
|
||||
let result = webview_window.set_simple_fullscreen(true);
|
||||
|
||||
// We expect this to work without panicking
|
||||
assert!(result.is_ok(), "set_simple_fullscreen should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
@ -2057,6 +2057,20 @@ impl<R: Runtime> WebviewWindow<R> {
|
||||
self.window.set_fullscreen(fullscreen)
|
||||
}
|
||||
|
||||
/// Toggles a fullscreen mode that doesn't require a new macOS space.
|
||||
/// Returns a boolean indicating whether the transition was successful (this won't work if the window was already in the native fullscreen).
|
||||
///
|
||||
/// This is how fullscreen used to work on macOS in versions before Lion.
|
||||
/// And allows the user to have a fullscreen window without using another space or taking control over the entire monitor.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** Uses native simple fullscreen mode.
|
||||
/// - **Other platforms:** Falls back to [`Self::set_fullscreen`].
|
||||
pub fn set_simple_fullscreen(&self, enable: bool) -> crate::Result<()> {
|
||||
self.window.set_simple_fullscreen(enable)
|
||||
}
|
||||
|
||||
/// Bring the window to front and focus.
|
||||
pub fn set_focus(&self) -> crate::Result<()> {
|
||||
self.window.set_focus()
|
||||
|
||||
@ -1968,25 +1968,27 @@ tauri::Builder::default()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Toggles a fullscreen mode that doesn’t require a new macOS space. Returns a boolean indicating whether the transition was successful (this won’t work if the window was already in the native fullscreen).
|
||||
/// Toggles a fullscreen mode that doesn't require a new macOS space.
|
||||
/// Returns a boolean indicating whether the transition was successful (this won't work if the window was already in the native fullscreen).
|
||||
///
|
||||
/// This is how fullscreen used to work on macOS in versions before Lion. And allows the user to have a fullscreen window without using another space or taking control over the entire monitor.
|
||||
#[cfg(target_os = "macos")]
|
||||
/// This is how fullscreen used to work on macOS in versions before Lion.
|
||||
/// And allows the user to have a fullscreen window without using another space or taking control over the entire monitor.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** Uses native simple fullscreen mode.
|
||||
/// - **Other platforms:** Falls back to [`Self::set_fullscreen`].
|
||||
pub fn set_simple_fullscreen(&self, enable: bool) -> crate::Result<()> {
|
||||
self
|
||||
.window
|
||||
.dispatcher
|
||||
.set_simple_fullscreen(enable)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// On macOS, Toggles a fullscreen mode that doesn’t require a new macOS space. Returns a boolean indicating whether the transition was successful (this won’t work if the window was already in the native fullscreen).
|
||||
/// This is how fullscreen used to work on macOS in versions before Lion. And allows the user to have a fullscreen window without using another space or taking control over the entire monitor.
|
||||
///
|
||||
/// On other platforms, this is the same as [`Window#method.set_fullscreen`].
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn set_simple_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
|
||||
self.set_fullscreen(fullscreen)
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
self
|
||||
.window
|
||||
.dispatcher
|
||||
.set_simple_fullscreen(enable)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
self.set_fullscreen(enable)
|
||||
}
|
||||
|
||||
/// Bring the window to front and focus.
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
"eslint-plugin-security": "3.0.1",
|
||||
"fast-glob": "3.3.3",
|
||||
"globals": "^17.0.0",
|
||||
"rollup": "4.55.1",
|
||||
"rollup": "4.55.2",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.34.1"
|
||||
|
||||
@ -20,7 +20,7 @@ if (globalThis.navigator?.userAgent?.includes('Deno')) {
|
||||
}
|
||||
// Even if started by a package manager, the binary will be NodeJS.
|
||||
// Some distribution still use "nodejs" as the binary name.
|
||||
else if (binStem.match(/(nodejs|node|bun)\-?([0-9]*)*$/g)) {
|
||||
else if (binStem.match(/(nodejs|node|bun|electron)\-?([0-9]*)*$/g)) {
|
||||
const managerStem = process.env.npm_execpath
|
||||
? path.parse(process.env.npm_execpath).name.toLowerCase()
|
||||
: null
|
||||
|
||||
1072
pnpm-lock.yaml
1072
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user