Compare commits

..

1 Commits

Author SHA1 Message Date
Lucas Nogueira
05e49099e1 chore: improve iOS local network permission error message 2026-01-14 08:34:39 -03:00
209 changed files with 5417 additions and 8727 deletions

View File

@@ -6,6 +6,4 @@ ignore = [
"RUSTSEC-2020-0095",
# proc-macro-error is unmaintained
"RUSTSEC-2024-0370",
# time crate can't be updated in the repo because of MSRV, users are unaffected
"RUSTSEC-2026-0009",
]

View File

@@ -1,6 +0,0 @@
---
'tauri': 'minor:feat'
'@tauri-apps/api': 'minor:feat'
---
Add Bring All to Front predefined menu item type

View File

@@ -1,5 +0,0 @@
---
"tauri-macos-sign": patch:enhance
---
Do not rely on system base64 CLI to decode certificates.

View File

@@ -0,0 +1,7 @@
---
"tauri-utils": patch:enhance
"tauri-build": patch:enhance
"tauri-cli": patch:enhance
---
Small code refactors for improved code readability. No user facing changes.

View File

@@ -1,6 +0,0 @@
---
"tauri-build": patch:enhance
"tauri": patch:enhance
---
Simplify async-sync code boundaries, no externally visible changes

View File

@@ -1,5 +0,0 @@
---
"tauri": minor:feat
---
Add `data-tauri-drag-region="deep"` so clicks on non-clickable children will drag as well. Can still opt out of drag on some regions using `data-tauri-drag-region="false"`

View File

@@ -0,0 +1,6 @@
---
"@tauri-apps/cli": patch:enhance
"tauri-cli": patch:enhance
---
Simplified internal representation of `features: Option<Vec<String>>` with `Vec<String>`, no user facing changes

View File

@@ -1,7 +0,0 @@
---
'tauri': 'minor:feat'
'tauri-runtime': 'minor:feat'
'tauri-runtime-wry': 'minor:feat'
---
Add `eval_with_callback` to the Tauri webview APIs and runtime dispatch layers.

View File

@@ -1,13 +0,0 @@
---
"tauri-bundler": minor:feat
"tauri-cli": minor:feat
"@tauri-apps/cli": minor:feat
"tauri-utils": minor:feat
---
Added uninstaller icon and uninstaller header image support for NSIS installer.
Notes:
- For `tauri-bundler` lib users, the `NsisSettings` now has 2 new fields `uninstaller_icon` and `uninstaller_header_image` which can be a breaking change
- When bundling with NSIS, users can add `uninstallerIcon` and `uninstallerHeaderImage` under `bundle > windows > nsis` to configure them.

View File

@@ -1,7 +0,0 @@
---
"tauri-bundler": patch:bug
"tauri-cli": patch:bug
"@tauri-apps/cli": patch:bug
---
Fix `build --bundles` to allow `nsis` arg in linux+macOS

View File

@@ -1,6 +0,0 @@
---
"tauri-cli": patch:bug
"@tauri-apps/cli": patch:bug
---
Fix iOS build failure when `Metal Toolchain` is installed by using explicit `$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain` path instead of `$(TOOLCHAIN_DIR)` for Swift library search paths.

View File

@@ -1,5 +0,0 @@
---
"tauri-bundler": patch:bug
---
Correct GitHub Release URL path for Linux i686 tooling.

View File

@@ -0,0 +1,5 @@
---
tauri-runtime-wry: patch:bug
---
On Linux, keep the WebContext alive to prevent zombie WebKit processes after repeatedly closing all windows and re-opening them.

View File

@@ -1,5 +0,0 @@
---
"tauri-bundler": minor:feat
---
Added support to Liquid Glass icons.

View File

@@ -1,8 +0,0 @@
---
"tauri": minor:feat
"tauri-runtime-wry": minor:feat
"tauri-runtime": minor:feat
"tauri-utils": minor:feat
---
Support creating multiple windows on Android (activity embedding) and iOS (scenes).

View File

@@ -1,6 +0,0 @@
---
"tauri": minor:changes
"tauri-runtime-wry": minor:changes
---
The new window handler passed to `on_new_window` no longer requires `Sync`, and runs on main thread on Windows, aligning with other platforms

View File

@@ -1,6 +0,0 @@
---
"tauri-cli": patch:enhance
"@tauri-apps/cli": patch:enhance
---
Show the context before prompting for updater signing key password

6
.changes/runtime-bsd.md Normal file
View File

@@ -0,0 +1,6 @@
---
tauri-runtime: patch:bug
tauri-runtime-wry: patch:bug
---
Fix compilation errors when targeting BSD.

View File

@@ -1,5 +0,0 @@
---
"tauri": patch:enhance
---
Implement retrieving inner PathBuf from SafePathBuf to ease using APIs that require an owned PathBuf

View File

@@ -0,0 +1,14 @@
---
"tauri-cli": patch:enhance
"@tauri-apps/cli": patch:enhance
---
Added new environment variables for `tauri signer sign` command, to align with existing environment variables used in `tauri build`, `tauri bundle` and `tauri signer generate`
- `TAURI_SIGNING_PRIVATE_KEY`
- `TAURI_SIGNING_PRIVATE_KEY_PATH`
- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`
The old environment variables are deprecated and will be removed in a future release.
- `TAURI_PRIVATE_KEY`
- `TAURI_PRIVATE_KEY_PATH`
- `TAURI_PRIVATE_KEY_PASSWORD`

View File

@@ -1,6 +0,0 @@
---
"tauri-utils": minor:deps
---
Add new `html-manipulation-2` and `build-2` feature flags that use `dom_query` instead of `kuchikiki` for HTML parsing / manipulation.
This allows downstream users to remove `kuchikiki` and its dependencies from their dependency tree.

View File

@@ -1,5 +0,0 @@
---
tauri: minor:feat
---
Added `dbus` feature flag (enabled by default) which is required for theme detection on Linux.

View File

@@ -1,5 +0,0 @@
---
tauri-utils: patch:deps
---
Changed `toml` crate version from `0.9` to `">=0.9, <=1"`

View File

@@ -0,0 +1,6 @@
---
tauri-cli: patch:bug
"@tauri-apps/cli": patch:bug
---
`tauri signer sign` doesn't work for files without an extension

View File

@@ -0,0 +1,5 @@
---
tauri: patch:bug
---
`WindowConfig::focus` is set to `false` in `WindowConfig::default()`

View File

@@ -1,12 +0,0 @@
---
"tauri-bundler": minor:feat
"tauri-cli": minor:feat
"@tauri-apps/cli": minor:feat
---
Added support for `minimumWebview2Version` option support for the MSI (Wix) installer, the old `bundle > windows > nsis > minimumWebview2Version` is now deprecated in favor of `bundle > windows > minimumWebview2Version`
Notes:
- For anyone relying on the `WVRTINSTALLED` `Property` tag in `main.wxs`, it is now renamed to `INSTALLED_WEBVIEW2_VERSION`
- For `tauri-bundler` lib users, the `WindowsSettings` now has a new field `minimum_webview2_version` which can be a breaking change

View File

@@ -5,7 +5,6 @@ Hi! We, the maintainers, are really excited that you are interested in contribut
- [Issue Reporting Guidelines](#issue-reporting-guidelines)
- [Pull Request Guidelines](#pull-request-guidelines)
- [Development Guide](#development-guide)
- [AI Tool Policy](#ai-tool-policy)
## Issue Reporting Guidelines
@@ -105,16 +104,6 @@ $ cargo +nightly doc --all-features --open
The JS API provides bindings between the developer's JS in the Webview and the built-in Tauri APIs, written in Rust. Its code is located in [/packages/api](https://github.com/tauri-apps/tauri/tree/dev/packages/api).
After making changes to the code, run `pnpm build` to build it. To test your changes, we recommend using the API example app, located in [/examples/api](https://github.com/tauri-apps/tauri/tree/dev/examples/api). It will automatically use your local copy of the JS API and provides a helpful UI to test the various commands.
## AI Tool Policy
It takes a lot of time to review a Pull Request while it's very easy to make a nonsensical but plausible looking one using AI tools.
It is unfair for other contributors and the reviewers to spend much of the time dealing with this, hence these rules:
1. Review and test all LLM-generated content before submitting, you're the one responsible for it, not the AI.
2. Don't use AI to respond to review comments (except for translations).
We will close the Pull Request with a `ai-slop` tag if you failed to do so.
## Financial Contribution
Tauri is an MIT-licensed open source project. Its ongoing development can be supported via [GitHub Sponsors](https://github.com/sponsors/tauri-apps) or [Open Collective](https://opencollective.com/tauri). We prefer GitHub Sponsors as donations made are doubled through the matching fund program.

View File

@@ -8,16 +8,7 @@ on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
pull_request:
paths:
- '.github/workflows/audit.yml'
- '**/Cargo.lock'
- '**/Cargo.toml'
- '**/package.json'
- '**/pnpm-lock.yaml'
push:
branches:
- dev
paths:
- '.github/workflows/audit.yml'
- '**/Cargo.lock'
@@ -35,7 +26,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: rust audit
uses: rustsec/audit-check@v2
uses: rustsec/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -61,7 +61,7 @@ jobs:
actions: write # required for workflow_dispatch
contents: write # required to create new releases
pull-requests: write # required to open version update pr
id-token: write # pnpm provenance / oidc token
id-token: write # pnpm provenance
outputs:
change: ${{ steps.covector.outputs.change }}
commandRan: ${{ steps.covector.outputs.commandRan }}
@@ -74,9 +74,10 @@ jobs:
with:
fetch-depth: 0
- run: npm i -g --force corepack
- uses: actions/setup-node@v6
- uses: actions/setup-node@v4
with:
node-version: 24
node-version: 20
registry-url: 'https://registry.npmjs.org'
- name: cargo login
run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
@@ -94,6 +95,7 @@ jobs:
uses: jbolda/covector/packages/action@covector-v0
id: covector
env:
NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }}
NPM_CONFIG_PROVENANCE: true
with:

View File

@@ -20,10 +20,6 @@ defaults:
run:
working-directory: packages/cli/
permissions:
contents: write # update release
id-token: write # oidc token
jobs:
build:
strategy:
@@ -120,7 +116,7 @@ jobs:
if: ${{ !matrix.settings.docker }}
with:
targets: ${{ matrix.settings.target }}
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1
with:
key: ${{ matrix.settings.target }}
if: ${{ matrix.settings.docker }}
@@ -370,13 +366,16 @@ jobs:
- test-linux-x64-gnu-binding
- test-linux-x64-musl-binding
#- test-linux-arm-bindings
permissions:
contents: write # update release
id-token: write # npm provenance
steps:
- uses: actions/checkout@v4
- run: npm i -g --force corepack
- name: Setup node
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: 24
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm i --frozen-lockfile --ignore-scripts
@@ -391,8 +390,10 @@ jobs:
shell: bash
- name: Publish
run: |
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
npm publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ''
NPM_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
RELEASE_ID: ${{ github.event.client_payload.releaseId || inputs.releaseId }}
NPM_CONFIG_PROVENANCE: true

View File

@@ -90,26 +90,14 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.platform.target }}
prefix-key: v3
save-if: ${{ matrix.features.key == 'all' }}
- name: test tauri-utils
if: ${{ !matrix.platform.cross }}
# Using --lib --bins --tests to skip doc tests
run: cargo ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --lib --bins --tests --manifest-path crates/tauri-utils/Cargo.toml
- name: test tauri-utils (using cross)
if: ${{ matrix.platform.cross }}
# Using --lib --bins --tests to skip doc tests
run: |
cargo install cross --git https://github.com/cross-rs/cross --rev 51f46f296253d8122c927c5bb933e3c4f27cc317 --locked
cross ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --lib --bins --tests --manifest-path crates/tauri-utils/Cargo.toml
- name: test tauri
- name: test
if: ${{ !matrix.platform.cross }}
run: cargo ${{ matrix.features.key == 'no-default' && 'check' || matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --manifest-path crates/tauri/Cargo.toml
- name: test tauri (using cross)
- name: test (using cross)
if: ${{ matrix.platform.cross }}
run: |
cargo install cross --git https://github.com/cross-rs/cross --rev 51f46f296253d8122c927c5bb933e3c4f27cc317 --locked

16
.taurignore Normal file
View File

@@ -0,0 +1,16 @@
.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

866
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +1,5 @@
# Changelog
## \[2.5.6]
### Dependencies
- Upgraded to `tauri-utils@2.8.3`
- Upgraded to `tauri-codegen@2.5.5`
## \[2.5.5]
### Dependencies
- Upgraded to `tauri-codegen@2.5.4`
## \[2.5.4]
### Enhancements
- [`2d28e3143`](https://www.github.com/tauri-apps/tauri/commit/2d28e3143ee3d97d7570ea03877aa00a0d6e47d0) ([#14632](https://www.github.com/tauri-apps/tauri/pull/14632) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Small code refactors for improved code readability. No user facing changes.
### Dependencies
- Upgraded to `tauri-utils@2.8.2`
- Upgraded to `tauri-codegen@2.5.3`
## \[2.5.3]
### Dependencies

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-build"
version = "2.5.6"
version = "2.5.3"
description = "build time code to pair with https://crates.io/crates/tauri"
exclude = ["CHANGELOG.md", "/target"]
readme = "README.md"
@@ -26,9 +26,9 @@ targets = [
[dependencies]
anyhow = "1"
quote = { version = "1", optional = true }
tauri-codegen = { version = "2.5.5", path = "../tauri-codegen", optional = true }
tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
"build-2",
tauri-codegen = { version = "2.5.2", path = "../tauri-codegen", optional = true }
tauri-utils = { version = "2.8.1", path = "../tauri-utils", features = [
"build",
"resources",
] }
cargo_toml = "0.22"
@@ -41,6 +41,7 @@ tauri-winres = "0.3"
semver = "1"
dirs = "6"
glob = "0.3"
toml = "0.9"
# Our code requires at least 0.8.21 so don't simplify this to 0.8
schemars = { version = "0.8.21", features = ["preserve_order"] }

View File

@@ -57,7 +57,7 @@ fn copy_binaries(
binaries: ResourcePaths,
target_triple: &str,
path: &Path,
package_name: Option<&str>,
package_name: Option<&String>,
) -> Result<()> {
for src in binaries {
let src = src?;
@@ -411,8 +411,7 @@ impl Attributes {
}
pub fn is_dev() -> bool {
env::var_os("DEP_TAURI_DEV")
.expect("missing `cargo:dev` instruction, please update tauri to latest")
env::var("DEP_TAURI_DEV").expect("missing `cargo:dev` instruction, please update tauri to latest")
== "true"
}
@@ -459,7 +458,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
println!("cargo:rerun-if-env-changed=TAURI_CONFIG");
let target_os = env::var_os("CARGO_CFG_TARGET_OS").unwrap();
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let mobile = target_os == "ios" || target_os == "android";
cfg_alias("desktop", !mobile);
cfg_alias("mobile", mobile);
@@ -504,7 +503,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
let cargo_toml_path = Path::new("Cargo.toml").canonicalize()?;
let mut manifest = Manifest::<cargo_toml::Value>::from_path_with_metadata(cargo_toml_path)?;
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
manifest::check(&config, &mut manifest)?;
@@ -530,7 +529,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
ResourcePaths::new(&external_binaries(paths, &target_triple, &target), true),
&target_triple,
target_dir,
manifest.package.as_ref().map(|p| p.name.as_ref()),
manifest.package.as_ref().map(|p| &p.name),
)?;
}
@@ -539,7 +538,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
.bundle
.resources
.clone()
.unwrap_or(BundleResources::List(Vec::new()));
.unwrap_or_else(|| BundleResources::List(Vec::new()));
if target_triple.contains("windows") {
if let Some(fixed_webview2_runtime_path) = match &config.bundle.windows.webview_install_mode {
WebviewInstallMode::FixedRuntime { path } => Some(path),
@@ -588,19 +587,21 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
use semver::Version;
use tauri_winres::{VersionInfo, WindowsResource};
fn find_icon<F: Fn(&&String) -> bool>(config: &Config, predicate: F, default: &str) -> PathBuf {
let icon_path = config
.bundle
.icon
.iter()
.find(|i| predicate(i))
.cloned()
.unwrap_or_else(|| default.to_string());
icon_path.into()
}
let window_icon_path = attributes
.windows_attributes
.window_icon_path
.unwrap_or_else(|| {
config
.bundle
.icon
.iter()
.find(|i| i.ends_with(".ico"))
.map(AsRef::as_ref)
.unwrap_or("icons/icon.ico")
.into()
});
.unwrap_or_else(|| find_icon(&config, |i| i.ends_with(".ico"), "icons/icon.ico"));
let mut res = WindowsResource::new();
@@ -683,7 +684,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
}
}
"msvc" => {
if env::var_os("STATIC_VCRUNTIME").is_some_and(|v| v == "true") {
if env::var("STATIC_VCRUNTIME").is_ok_and(|v| v == "true") {
static_vcruntime::build();
}
}

View File

@@ -23,7 +23,7 @@ struct AllowlistedDependency {
name: String,
alias: Option<String>,
kind: DependencyKind,
all_cli_managed_features: Vec<&'static str>,
all_cli_managed_features: Option<Vec<&'static str>>,
expected_features: Vec<String>,
}
@@ -33,7 +33,7 @@ pub fn check(config: &Config, manifest: &mut Manifest) -> Result<()> {
name: "tauri-build".into(),
alias: None,
kind: DependencyKind::Build,
all_cli_managed_features: vec!["isolation"],
all_cli_managed_features: Some(vec!["isolation"]),
expected_features: match config.app.security.pattern {
PatternKind::Isolation { .. } => vec!["isolation".to_string()],
_ => vec![],
@@ -43,10 +43,12 @@ pub fn check(config: &Config, manifest: &mut Manifest) -> Result<()> {
name: "tauri".into(),
alias: None,
kind: DependencyKind::Normal,
all_cli_managed_features: AppConfig::all_features()
.into_iter()
.filter(|f| f != &"tray-icon")
.collect(),
all_cli_managed_features: Some(
AppConfig::all_features()
.into_iter()
.filter(|f| f != &"tray-icon")
.collect(),
),
expected_features: config
.app
.features()
@@ -127,13 +129,23 @@ fn check_features(dependency: Dependency, metadata: &AllowlistedDependency) -> R
Dependency::Inherited(dep) => dep.features,
};
let diff = features_diff(
&features
.into_iter()
.filter(|f| metadata.all_cli_managed_features.contains(&f.as_str()))
.collect::<Vec<String>>(),
&metadata.expected_features,
);
let diff = if let Some(all_cli_managed_features) = &metadata.all_cli_managed_features {
features_diff(
&features
.into_iter()
.filter(|f| all_cli_managed_features.contains(&f.as_str()))
.collect::<Vec<String>>(),
&metadata.expected_features,
)
} else {
features_diff(
&features
.into_iter()
.filter(|f| f.starts_with("allow-"))
.collect::<Vec<String>>(),
&metadata.expected_features,
)
};
let mut error_message = String::new();
if !diff.remove.is_empty() {

View File

@@ -15,8 +15,7 @@ pub fn generate_gradle_files(project_dir: PathBuf) -> Result<()> {
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n".to_string();
let mut app_build_gradle = "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
val implementation by configurations
dependencies {
implementation(\"androidx.lifecycle:lifecycle-process:2.10.0\")"
dependencies {"
.to_string();
for (env, value) in std::env::vars_os() {

View File

@@ -1,40 +1,5 @@
# Changelog
## \[2.8.1]
### Bug Fixes
- [`6252432f0`](https://www.github.com/tauri-apps/tauri/commit/6252432f0757d896d7a61819bbff127efac5a156) ([#14945](https://www.github.com/tauri-apps/tauri/pull/14945) by [@veeceey](https://www.github.com/tauri-apps/tauri/../../veeceey)) Fix WIX installer registry search order so that the named `InstallDir` key takes priority over the NSIS default key, preventing install location from changing on updates.
### What's Changed
- [`7be58a1c6`](https://www.github.com/tauri-apps/tauri/commit/7be58a1c643a7ed6d0f484a7e1134022618eb2b2) ([#14894](https://www.github.com/tauri-apps/tauri/pull/14894) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Log patching bundle type information again
### Dependencies
- Upgraded to `tauri-utils@2.8.3`
## \[2.8.0]
### Enhancements
- [`c769f211f`](https://www.github.com/tauri-apps/tauri/commit/c769f211fcaa543884c9d0f87ebd2ee106c01382) ([#14824](https://www.github.com/tauri-apps/tauri/pull/14824) by [@Kf637](https://www.github.com/tauri-apps/tauri/../../Kf637)) feat(nsis): add Norwegian language support for installer.
### Bug Fixes
- [`7fca58230`](https://www.github.com/tauri-apps/tauri/commit/7fca58230f97c3e6834134419514a0c7dbbe784b) ([#14830](https://www.github.com/tauri-apps/tauri/pull/14830) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) 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
### What's Changed
- [`0575dd287`](https://www.github.com/tauri-apps/tauri/commit/0575dd287e021b61d2aedf64d62ae84a2c925fb4) ([#14521](https://www.github.com/tauri-apps/tauri/pull/14521) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) 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.
### Dependencies
- Upgraded to `tauri-utils@2.8.2`
- Upgraded to `tauri-macos-sign@2.3.3`
## \[2.7.5]
### Enhancements

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-bundler"
version = "2.8.1"
version = "2.7.5"
authors = [
"George Burton <burtonageo@gmail.com>",
"Tauri Programme within The Commons Conservancy",
@@ -15,7 +15,7 @@ rust-version = "1.77.2"
exclude = ["CHANGELOG.md", "/target", "rustfmt.toml"]
[dependencies]
tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
tauri-utils = { version = "2.8.1", path = "../tauri-utils", features = [
"resources",
] }
image = "0.25"
@@ -59,7 +59,7 @@ features = ["Win32_System_SystemInformation", "Win32_System_Diagnostics_Debug"]
[target."cfg(target_os = \"macos\")".dependencies]
icns = { package = "tauri-icns", version = "0.1" }
time = { version = "0.3", features = ["formatting"] }
tauri-macos-sign = { version = "2.3.3", path = "../tauri-macos-sign" }
tauri-macos-sign = { version = "2.3.2", path = "../tauri-macos-sign" }
[target."cfg(target_os = \"linux\")".dependencies]
heck = "0.5"

View File

@@ -4,7 +4,6 @@
// SPDX-License-Identifier: MIT
mod category;
mod kmp;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
@@ -16,63 +15,28 @@ mod windows;
use tauri_utils::{display_path, platform::Target as TargetPlatform};
const BUNDLE_VAR_TOKEN: &[u8] = b"__TAURI_BUNDLE_TYPE_VAR_UNK";
/// Patch a binary with bundle type information
fn patch_binary(binary: &PathBuf, package_type: &PackageType) -> crate::Result<()> {
#[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",
// NSIS installers can be built in linux using cargo-xwin
crate::PackageType::Nsis => b"__TAURI_BUNDLE_TYPE_VAR_NSS",
_ => {
return Err(crate::Error::InvalidPackageType(
package_type.short_name().to_owned(),
"Linux".to_owned(),
))
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)?;
}
};
#[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(),
))
PackageType::Nsis | PackageType::WindowsMsi => {
log::info!(
"Patching binary {:?} for type {}",
binary,
package_type.short_name()
);
windows::patch_binary(binary, package_type)?;
}
};
#[cfg(target_os = "macos")]
let bundle_type = match package_type {
// NSIS installers can be built in macOS using cargo-xwin
crate::PackageType::Nsis => b"__TAURI_BUNDLE_TYPE_VAR_NSS",
crate::PackageType::MacOsBundle | crate::PackageType::Dmg => {
// skip patching for macOS-native bundles
return Ok(());
}
_ => {
return Err(crate::Error::InvalidPackageType(
package_type.short_name().to_owned(),
"macOS".to_owned(),
))
}
};
log::info!(
"Patching {} with bundle type information: {}",
display_path(binary),
package_type.short_name()
);
let mut file_data = std::fs::read(binary).expect("Could not read binary file.");
let bundle_var_index =
kmp::index_of(BUNDLE_VAR_TOKEN, &file_data).ok_or(crate::Error::MissingBundleTypeVar)?;
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()))?;
_ => (),
}
Ok(())
}
@@ -128,17 +92,22 @@ 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);
// 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:
// 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:
// - 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
// 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 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)?;
}
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
@@ -152,7 +121,16 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
// 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 {
@@ -175,7 +153,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
#[cfg(target_os = "windows")]
PackageType::WindowsMsi => windows::msi::bundle_project(settings, false)?,
// don't restrict to windows as NSIS installers can be built in linux+macOS using cargo-xwin
// note: don't restrict to windows as NSIS installers can be built in linux using cargo-xwin
PackageType::Nsis => windows::nsis::bundle_project(settings, false)?,
#[cfg(target_os = "linux")]
@@ -194,14 +172,6 @@ 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() {

View File

@@ -1,59 +0,0 @@
// 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
// KnuthMorrisPratt algorithm
// based on https://github.com/howeih/rust_kmp
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
}
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
}

View File

@@ -1,7 +1,7 @@
#! /usr/bin/env bash
# GTK3 environment variables: https://docs.gtk.org/gtk3/running.html
# GTK4 environment variables: https://docs.gtk.org/gtk4/running.html
# GTK3 environment variables: https://developer.gnome.org/gtk3/stable/gtk-running.html
# GTK4 environment variables: https://developer.gnome.org/gtk4/stable/gtk-running.html
# abort on all errors
set -e

View File

@@ -1,282 +0,0 @@
// 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" { "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 = download("https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh")?;
write_and_make_executable(&gtk, 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)
}

View File

@@ -1,21 +1,287 @@
// 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,
};
use crate::Settings;
mod linuxdeploy;
/// 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>> {
linuxdeploy::bundle_project(settings)
// 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])
}
fn write_and_make_executable(path: &Path, data: Vec<u8>) -> std::io::Result<()> {
// 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(&gtk, 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<()> {
use std::os::unix::fs::PermissionsExt;
fs::write(path, data)?;

View File

@@ -7,3 +7,8 @@ pub mod appimage;
pub mod debian;
pub mod freedesktop;
pub mod rpm;
mod util;
#[cfg(target_os = "linux")]
pub use util::patch_binary;

View File

@@ -0,0 +1,59 @@
// 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
}

View File

@@ -23,7 +23,7 @@
// files into the `Contents` directory of the bundle.
use super::{
icon::{app_icon_name_from_assets_car, create_assets_car_file, create_icns_file},
icon::create_icns_file,
sign::{notarize, notarize_auth, notarize_without_stapling, sign, SignTarget},
};
use crate::{
@@ -76,19 +76,11 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
let bin_dir = bundle_directory.join("MacOS");
let mut sign_paths = Vec::new();
let bundle_icon_file =
create_icns_file(&resources_dir, settings).with_context(|| "Failed to create app icon")?;
let bundle_icon_file: Option<PathBuf> =
{ create_icns_file(&resources_dir, settings).with_context(|| "Failed to create app icon")? };
let assets_car_file = create_assets_car_file(&resources_dir, settings)
.with_context(|| "Failed to create app Assets.car")?;
create_info_plist(
&bundle_directory,
bundle_icon_file,
assets_car_file,
settings,
)
.with_context(|| "Failed to create Info.plist")?;
create_info_plist(&bundle_directory, bundle_icon_file, settings)
.with_context(|| "Failed to create Info.plist")?;
let framework_paths = copy_frameworks_to_bundle(&bundle_directory, settings)
.with_context(|| "Failed to bundle frameworks")?;
@@ -144,7 +136,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
if matches!(e, NotarizeAuthError::MissingTeamId) {
return Err(e.into());
} else {
log::warn!("skipping app notarization, {e}");
log::warn!("skipping app notarization, {}", e.to_string());
}
}
}
@@ -212,7 +204,6 @@ fn copy_custom_files_to_bundle(bundle_directory: &Path, settings: &Settings) ->
fn create_info_plist(
bundle_dir: &Path,
bundle_icon_file: Option<PathBuf>,
assets_car_file: Option<PathBuf>,
settings: &Settings,
) -> crate::Result<()> {
let mut plist = plist::Dictionary::new();
@@ -222,6 +213,17 @@ fn create_info_plist(
"CFBundleExecutable".into(),
settings.main_binary_name()?.into(),
);
if let Some(path) = bundle_icon_file {
plist.insert(
"CFBundleIconFile".into(),
path
.file_name()
.expect("No file name")
.to_string_lossy()
.into_owned()
.into(),
);
}
plist.insert(
"CFBundleIdentifier".into(),
settings.bundle_identifier().into(),
@@ -360,27 +362,6 @@ fn create_info_plist(
);
}
if let Some(path) = bundle_icon_file {
plist.insert(
"CFBundleIconFile".into(),
path
.file_name()
.expect("No file name")
.to_string_lossy()
.into_owned()
.into(),
);
}
if let Some(assets_car_file) = assets_car_file {
if let Some(icon_name) = app_icon_name_from_assets_car(&assets_car_file) {
// only set CFBundleIconName for the Assets.car, CFBundleIconFile is the fallback icns file
plist.insert("CFBundleIconName".into(), icon_name.clone().into());
} else {
log::warn!("Failed to get icon name from Assets.car file");
}
}
if let Some(protocols) = settings.deep_link_protocols() {
plist.insert(
"CFBundleURLTypes".into(),

View File

@@ -4,14 +4,13 @@
// SPDX-License-Identifier: MIT
use crate::bundle::Settings;
use crate::utils::{self, fs_utils, CommandExt};
use crate::utils::{self, fs_utils};
use std::{
cmp::min,
ffi::OsStr,
fs::{self, File},
io::{self, BufWriter},
path::{Path, PathBuf},
process::Command,
};
use image::GenericImageView;
@@ -64,11 +63,6 @@ pub fn create_icns_file(out_dir: &Path, settings: &Settings) -> crate::Result<Op
let mut images_to_resize: Vec<(image::DynamicImage, u32, u32)> = vec![];
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
if icon_path.extension().map_or(false, |ext| ext == "car") {
continue;
}
let icon = image::open(&icon_path)?;
let density = if utils::is_retina(&icon_path) { 2 } else { 1 };
let (w, h) = icon.dimensions();
@@ -119,206 +113,3 @@ fn make_icns_image(img: image::DynamicImage) -> io::Result<icns::Image> {
};
icns::Image::from_data(pixel_format, img.width(), img.height(), img.into_bytes())
}
/// Creates an Assets.car file from a .icon file if there are any in the settings.
/// Uses an existing Assets.car file if it exists in the settings.
/// Returns the path to the Assets.car file.
pub fn create_assets_car_file(
out_dir: &Path,
settings: &Settings,
) -> crate::Result<Option<PathBuf>> {
let Some(icons) = settings.icons() else {
return Ok(None);
};
// If one of the icon files is already a CAR file, just use that.
let mut icon_composer_icon_path = None;
for icon in icons {
let icon_path = Path::new(&icon).to_path_buf();
if icon_path.extension() == Some(OsStr::new("car")) {
let dest_path = out_dir.join("Assets.car");
fs_utils::copy_file(&icon_path, &dest_path)?;
return Ok(Some(dest_path));
}
if icon_path.extension() == Some(OsStr::new("icon")) {
icon_composer_icon_path.replace(icon_path);
}
}
let Some(icon_composer_icon_path) = icon_composer_icon_path else {
return Ok(None);
};
// Check actool version - must be >= 26
if let Some(version) = get_actool_version() {
// Parse the major version number (before the dot)
let major_version: Option<u32> = version.split('.').next().and_then(|s| s.parse().ok());
if let Some(major) = major_version {
if major < 26 {
log::error!("actool version is less than 26, skipping Assets.car file creation. Please update Xcode to 26 or above and try again.");
return Ok(None);
}
} else {
// If we can't parse the version, return None to be safe
log::error!("failed to parse actool version, skipping Assets.car file creation");
return Ok(None);
}
} else {
log::error!("failed to get actool version, skipping Assets.car file creation");
// If we can't get the version, return None to be safe
return Ok(None);
}
// Create a temporary directory for actool work
let temp_dir = tempfile::tempdir()
.map_err(|e| crate::Error::GenericError(format!("failed to create temp dir: {e}")))?;
let icon_dest_path = temp_dir.path().join("Icon.icon");
let output_path = temp_dir.path().join("out");
// Copy the input .icon directory to the temp directory
if icon_composer_icon_path.is_dir() {
fs_utils::copy_dir(&icon_composer_icon_path, &icon_dest_path)?;
} else {
return Err(crate::Error::GenericError(format!(
"{} must be a directory",
icon_composer_icon_path.display()
)));
}
// Create the output directory
fs::create_dir_all(&output_path)?;
// Run actool command
let mut cmd = Command::new("actool");
cmd.arg(&icon_dest_path);
cmd.arg("--compile");
cmd.arg(&output_path);
cmd.arg("--output-format");
cmd.arg("human-readable-text");
cmd.arg("--notices");
cmd.arg("--warnings");
cmd.arg("--output-partial-info-plist");
cmd.arg(output_path.join("assetcatalog_generated_info.plist"));
cmd.arg("--app-icon");
cmd.arg("Icon");
cmd.arg("--include-all-app-icons");
cmd.arg("--accent-color");
cmd.arg("AccentColor");
cmd.arg("--enable-on-demand-resources");
cmd.arg("NO");
cmd.arg("--development-region");
cmd.arg("en");
cmd.arg("--target-device");
cmd.arg("mac");
cmd.arg("--minimum-deployment-target");
cmd.arg("26.0");
cmd.arg("--platform");
cmd.arg("macosx");
cmd.output_ok()?;
let assets_car_path = output_path.join("Assets.car");
if !assets_car_path.exists() {
return Err(crate::Error::GenericError(
"actool did not generate Assets.car file".to_owned(),
));
}
// copy to out_dir
fs_utils::copy_file(&assets_car_path, &out_dir.join("Assets.car"))?;
Ok(Some(out_dir.join("Assets.car")))
}
#[derive(serde::Deserialize)]
struct AssetsCarInfo {
#[serde(rename = "AssetType", default)]
asset_type: String,
#[serde(rename = "Name", default)]
name: String,
}
pub fn app_icon_name_from_assets_car(assets_car_path: &Path) -> Option<String> {
let Ok(output) = Command::new("assetutil")
.arg("--info")
.arg(assets_car_path)
.output_ok()
.inspect_err(|e| log::error!("Failed to get app icon name from Assets.car file: {e}"))
else {
return None;
};
let output = String::from_utf8(output.stdout).ok()?;
let assets_car_info: Vec<AssetsCarInfo> = serde_json::from_str(&output)
.inspect_err(|e| log::error!("Failed to parse Assets.car file info: {e}"))
.ok()?;
assets_car_info
.iter()
.find(|info| info.asset_type == "Icon Image")
.map(|info| info.name.clone())
}
/// Returns the actool short bundle version by running `actool --version --output-format=human-readable-text`.
/// Returns `None` if the command fails or the output cannot be parsed.
pub fn get_actool_version() -> Option<String> {
let Ok(output) = Command::new("actool")
.arg("--version")
.arg("--output-format=human-readable-text")
.output_ok()
.inspect_err(|e| log::error!("Failed to get actool version: {e}"))
else {
return None;
};
let output = String::from_utf8(output.stdout).ok()?;
parse_actool_version(&output)
}
fn parse_actool_version(output: &str) -> Option<String> {
// The output format is:
// /* com.apple.actool.version */
// bundle-version: 24411
// short-bundle-version: 26.1
for line in output.lines() {
let line = line.trim();
if let Some(version) = line.strip_prefix("short-bundle-version:") {
return Some(version.trim().to_string());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_actool_version() {
let output = r#"/* com.apple.actool.version */
some other line
bundle-version: 24411
short-bundle-version: 26.1
another line
"#;
let version = parse_actool_version(output).expect("Failed to parse version");
assert_eq!(version, "26.1");
}
#[test]
fn test_parse_actool_version_missing_fields() {
let output = r#"/* com.apple.actool.version */
bundle-version: 24411
"#;
assert!(parse_actool_version(output).is_none());
}
#[test]
fn test_parse_actool_version_empty() {
assert!(parse_actool_version("").is_none());
}
}

View File

@@ -106,10 +106,7 @@ fn generate_icon_files(bundle_dir: &Path, settings: &Settings) -> crate::Result<
// Fall back to non-PNG files for any missing sizes.
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
if icon_path
.extension()
.map_or(false, |ext| ext == "png" || ext == "car")
{
if icon_path.extension() == Some(OsStr::new("png")) {
continue;
} else if icon_path.extension() == Some(OsStr::new("icns")) {
let icon_family = icns::IconFamily::read(File::open(&icon_path)?)?;

View File

@@ -21,7 +21,7 @@ pub fn keychain(identity: Option<&str>) -> crate::Result<Option<tauri_macos_sign
var_os("APPLE_CERTIFICATE"),
var_os("APPLE_CERTIFICATE_PASSWORD"),
) {
// import user certificate - useful for CI build
// import user certificate - useful for for CI build
let keychain =
tauri_macos_sign::Keychain::with_certificate(&certificate_encoded, &certificate_password)
.map_err(Box::new)?;

View File

@@ -126,7 +126,7 @@ const ALL_PACKAGE_TYPES: &[PackageType] = &[
PackageType::IosBundle,
#[cfg(target_os = "windows")]
PackageType::WindowsMsi,
// NSIS installers can be built on all platforms but it's hidden in the --help output on macOS/Linux.
#[cfg(target_os = "windows")]
PackageType::Nsis,
#[cfg(target_os = "macos")]
PackageType::MacOsBundle,
@@ -231,7 +231,7 @@ pub struct AppImageSettings {
pub struct RpmSettings {
/// The list of RPM dependencies your application relies on.
pub depends: Option<Vec<String>>,
/// the list of RPM dependencies your application recommends.
/// the list of of RPM dependencies your application recommends.
pub recommends: Option<Vec<String>>,
/// The list of RPM dependencies your application provides.
pub provides: Option<Vec<String>>,
@@ -470,13 +470,6 @@ pub struct NsisSettings {
pub sidebar_image: Option<PathBuf>,
/// The path to an icon file used as the installer icon.
pub installer_icon: Option<PathBuf>,
/// The path to an icon file used as the uninstaller icon.
pub uninstaller_icon: Option<PathBuf>,
/// The path to a bitmap file to display on the header of uninstallers pages.
/// Defaults to [`Self::header_image`]. If this is set but [`Self::header_image`] is not, a default image from NSIS will be applied to `header_image`
///
/// The recommended dimensions are 150px x 57px.
pub uninstaller_header_image: Option<PathBuf>,
/// Whether the installation will be for all users or just the current user.
pub install_mode: NSISInstallerMode,
/// A list of installer languages.
@@ -539,10 +532,6 @@ pub struct NsisSettings {
/// Try to ensure that the WebView2 version is equal to or newer than this version,
/// if the user's WebView2 is older than this version,
/// the installer will try to trigger a WebView2 update.
#[deprecated(
since = "2.8.0",
note = "Use `WindowsSettings::minimum_webview2_version` instead."
)]
pub minimum_webview2_version: Option<String>,
}
@@ -598,10 +587,6 @@ pub struct WindowsSettings {
/// if you are on another platform and want to cross-compile and sign you will
/// need to use another tool like `osslsigncode`.
pub sign_command: Option<CustomSignCommandSettings>,
/// Try to ensure that the WebView2 version is equal to or newer than this version,
/// if the user's WebView2 is older than this version,
/// the installer will try to trigger a WebView2 update.
pub minimum_webview2_version: Option<String>,
}
impl WindowsSettings {
@@ -627,7 +612,6 @@ mod _default {
webview_install_mode: Default::default(),
allow_downgrades: true,
sign_command: None,
minimum_webview2_version: None,
}
}
}
@@ -811,8 +795,6 @@ pub struct Settings {
local_tools_directory: Option<PathBuf>,
/// the bundle settings.
bundle_settings: BundleSettings,
/// Same as `bundle_settings.icon`, but without the .icon directory.
icon_files: Option<Vec<String>>,
/// the binaries to bundle.
binaries: Vec<BundleBinary>,
/// The target platform.
@@ -924,14 +906,6 @@ impl SettingsBuilder {
};
let target_platform = TargetPlatform::from_triple(&target);
let icon_files = self.bundle_settings.icon.as_ref().map(|paths| {
paths
.iter()
.filter(|p| !p.ends_with(".icon"))
.cloned()
.collect()
});
Ok(Settings {
log_level: self.log_level.unwrap_or(log::Level::Error),
package: self
@@ -951,7 +925,6 @@ impl SettingsBuilder {
.map(|bins| external_binaries(bins, &target, &target_platform)),
..self.bundle_settings
},
icon_files,
target_platform,
target,
no_sign: self.no_sign,
@@ -985,11 +958,6 @@ impl Settings {
&self.target_platform
}
/// Raw list of icons.
pub fn icons(&self) -> Option<&Vec<String>> {
self.bundle_settings.icon.as_ref()
}
/// Returns the architecture for the binary being bundled (e.g. "arm", "x86" or "x86_64").
pub fn binary_arch(&self) -> Arch {
if self.target.starts_with("x86_64") {
@@ -1124,7 +1092,7 @@ impl Settings {
/// Returns an iterator over the icon files to be used for this bundle.
pub fn icon_files(&self) -> ResourcePaths<'_> {
match self.icon_files {
match self.bundle_settings.icon {
Some(ref paths) => ResourcePaths::new(paths.as_slice(), false),
None => ResourcePaths::new(&[], false),
}

View File

@@ -14,3 +14,5 @@ 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;

View File

@@ -70,16 +70,12 @@
<Property Id="ARPURLUPDATEINFO" Value="{{homepage}}"/>
{{/if}}
<!-- NOTE: The order of RegistrySearch elements below matters. In WIX, when multiple
RegistrySearch elements are listed under a single Property, the LAST successful
match wins. We list the NSIS default-key search first and the MSI InstallDir
search second so that the MSI-specific path takes priority when both keys exist. -->
<Property Id="INSTALLDIR">
<!-- First attempt: Search for the default key value (this is how the nsis installer stores the path) -->
<RegistrySearch Id="PrevInstallDirNoName" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Type="raw" />
<!-- Second attempt: Search for "InstallDir" which takes priority if found (this is how the msi installer stores the path) -->
<!-- First attempt: Search for "InstallDir" -->
<RegistrySearch Id="PrevInstallDirWithName" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw" />
<!-- Second attempt: If the first fails, search for the default key value (this is how the nsis installer currently stores the path) -->
<RegistrySearch Id="PrevInstallDirNoName" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Type="raw" />
</Property>
<!-- launch app checkbox -->
@@ -283,62 +279,38 @@
{{#if install_webview}}
<!-- WebView2 -->
<Property Id="INSTALLED_WEBVIEW2_VERSION">
<RegistrySearch Id="Webview2VersionSystemx64" Root="HKLM" Key="SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" />
<RegistrySearch Id="Webview2VersionSystemx86" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" />
<RegistrySearch Id="Webview2VersionUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
<Property Id="WVRTINSTALLED">
<RegistrySearch Id="WVRTInstalledSystem" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no" />
<RegistrySearch Id="WVRTInstalledUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
</Property>
{{#if download_bootstrapper}}
<!-- Download webview bootstrapper mode -->
<CustomAction Id='DownloadAndInvokeBootstrapper' Directory="INSTALLDIR" Execute="deferred" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\{] [\[]Net.ServicePointManager[\]]::SecurityProtocol = [\[]Net.SecurityProtocolType[\]]::Tls12 [\}] catch [\{][\}]; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ({{webview_installer_args}} &apos;/install&apos;) -Wait' Return='check'/>
<InstallExecuteSequence>
<Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>
<![CDATA[NOT(REMOVE OR INSTALLED_WEBVIEW2_VERSION)]]>
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
</Custom>
</InstallExecuteSequence>
{{/if}}
{{#if webview2_bootstrapper_path}}
<!-- Embedded webview bootstrapper mode -->
{{#if webview2_bootstrapper_path}}
<Binary Id="MicrosoftEdgeWebview2Setup.exe" SourceFile="{{webview2_bootstrapper_path}}"/>
<CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
<InstallExecuteSequence>
<Custom Action='InvokeBootstrapper' Before='InstallFinalize'>
<![CDATA[NOT(REMOVE OR INSTALLED_WEBVIEW2_VERSION)]]>
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
</Custom>
</InstallExecuteSequence>
{{/if}}
{{#if webview2_installer_path}}
<!-- Embedded offline installer -->
{{#if webview2_installer_path}}
<Binary Id="MicrosoftEdgeWebView2RuntimeInstaller.exe" SourceFile="{{webview2_installer_path}}"/>
<CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
<InstallExecuteSequence>
<Custom Action='InvokeStandalone' Before='InstallFinalize'>
<![CDATA[NOT(REMOVE OR INSTALLED_WEBVIEW2_VERSION)]]>
</Custom>
</InstallExecuteSequence>
{{/if}}
{{#if minimum_webview2_version}}
<!-- Update WebView2 if minimum version requirement not met -->
<Property Id="MINIMUM_WEBVIEW2_VERSION" Value="{{minimum_webview2_version}}" />
<Property Id="EDGEUPDATE_PATH">
<RegistrySearch Id="EdgeUpdateLocalMachine64" Root="HKLM" Key="SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate" Name="path" Type="raw" />
<RegistrySearch Id="EdgeUpdateLocalMachine32" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate" Name="path" Type="raw"/>
<RegistrySearch Id="EdgeUpdateCurrentUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate" Name="path" Type="raw"/>
</Property>
<!-- Chromium updater docs: https://source.chromium.org/chromium/chromium/src/+/main:docs/updater/user_manual.md -->
<!-- Modified from "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft EdgeWebView\ModifyPath" -->
<CustomAction Id="UpdateWebView2ViaEdgeUpdate" Execute="deferred" Property="EDGEUPDATE_PATH" Return="check" Impersonate="no" ExeCommand="/install appguid={F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}&amp;needsadmin=true" />
<InstallExecuteSequence>
<Custom Action='UpdateWebView2ViaEdgeUpdate' Before='InstallFinalize'>
<![CDATA[
NOT REMOVE
AND INSTALLED_WEBVIEW2_VERSION
AND (INSTALLED_WEBVIEW2_VERSION < MINIMUM_WEBVIEW2_VERSION)
]]>
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
</Custom>
</InstallExecuteSequence>
{{/if}}

View File

@@ -532,13 +532,6 @@ pub fn build_wix_app_installer(
}
}
if let Some(minimum_webview2_version) = &settings.windows().minimum_webview2_version {
data.insert(
"minimum_webview2_version",
to_json(minimum_webview2_version),
);
}
if let Some(license) = settings.license_file() {
if license.ends_with(".rtf") {
data.insert("license", to_json(license));

View File

@@ -41,8 +41,6 @@ ${StrLoc}
!define INSTALLERICON "{{installer_icon}}"
!define SIDEBARIMAGE "{{sidebar_image}}"
!define HEADERIMAGE "{{header_image}}"
!define UNINSTALLERICON "{{uninstaller_icon}}"
!define UNINSTALLERHEADERIMAGE "{{uninstaller_header_image}}"
!define MAINBINARYNAME "{{main_binary_name}}"
!define MAINBINARYSRCPATH "{{main_binary_path}}"
!define BUNDLEID "{{bundle_id}}"
@@ -131,26 +129,10 @@ VIAddVersionKey "ProductVersion" "${VERSION}"
!define MUI_WELCOMEFINISHPAGE_BITMAP "${SIDEBARIMAGE}"
!endif
; Enable header images for installer and uninstaller pages when either image is configured.
!if "${HEADERIMAGE}" != ""
!define MUI_HEADERIMAGE
!else if "${UNINSTALLERHEADERIMAGE}" != ""
!define MUI_HEADERIMAGE
!endif
; Installer header image
!if "${HEADERIMAGE}" != ""
!define MUI_HEADERIMAGE_BITMAP "${HEADERIMAGE}"
!endif
; Uninstaller header image
!if "${UNINSTALLERHEADERIMAGE}" != ""
!define MUI_HEADERIMAGE_UNBITMAP "${UNINSTALLERHEADERIMAGE}"
!endif
; Uninstaller icon
!if "${UNINSTALLERICON}" != ""
!define MUI_UNICON "${UNINSTALLERICON}"
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "${HEADERIMAGE}"
!endif
; Define registry key to store installer language

View File

@@ -1,27 +0,0 @@
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"

View File

@@ -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.3/nsis_tauri_utils.dll";
const NSIS_TAURI_UTILS_SHA1: &str = "75197FEE3C6A814FE035788D1C34EAD39349B860";
"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";
#[cfg(target_os = "windows")]
const NSIS_REQUIRED_FILES: &[&str] = &[
@@ -354,20 +354,6 @@ fn build_nsis_app_installer(
);
}
if let Some(uninstaller_icon) = &nsis.uninstaller_icon {
data.insert(
"uninstaller_icon",
to_json(dunce::canonicalize(uninstaller_icon)?),
);
}
if let Some(uninstaller_header_image) = &nsis.uninstaller_header_image {
data.insert(
"uninstaller_header_image",
to_json(dunce::canonicalize(uninstaller_header_image)?),
);
}
if let Some(installer_hooks) = &nsis.installer_hooks {
let installer_hooks = dunce::canonicalize(installer_hooks)?;
data.insert("installer_hooks", to_json(installer_hooks));
@@ -376,12 +362,7 @@ fn build_nsis_app_installer(
if let Some(start_menu_folder) = &nsis.start_menu_folder {
data.insert("start_menu_folder", to_json(start_menu_folder));
}
#[allow(deprecated)]
if let Some(minimum_webview2_version) = nsis
.minimum_webview2_version
.as_ref()
.or(settings.windows().minimum_webview2_version.as_ref())
{
if let Some(minimum_webview2_version) = &nsis.minimum_webview2_version {
data.insert(
"minimum_webview2_version",
to_json(minimum_webview2_version),
@@ -886,7 +867,6 @@ 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))

View File

@@ -77,3 +77,75 @@ 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(())
}

View File

@@ -99,13 +99,19 @@ pub enum Error {
#[error("Wrong package type {0} for platform {1}")]
InvalidPackageType(String, String),
/// Bundle type symbol missing in binary
#[error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")]
#[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")
)]
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.

View File

@@ -1,64 +1,5 @@
# Changelog
## \[2.10.1]
### Bug Fixes
- [`35c35f27a`](https://www.github.com/tauri-apps/tauri/commit/35c35f27aedc430b602ec74059b271128c15ad36) ([#14931](https://www.github.com/tauri-apps/tauri/pull/14931) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Support comma-separated list of Cargo features on all commands.
- [`0d1cb83ba`](https://www.github.com/tauri-apps/tauri/commit/0d1cb83bab2aa482c7d73116893fd7ff6aa56283) ([#14932](https://www.github.com/tauri-apps/tauri/pull/14932) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix missing Cargo args when running mobile dev and build commands.
- [`33754ae5e`](https://www.github.com/tauri-apps/tauri/commit/33754ae5e3740d022483b6164511c5c001a3c24b) ([#15022](https://www.github.com/tauri-apps/tauri/pull/15022) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix updater signing private keys generated using `tauri signer generate` with empty password can't be used (The keys generated during tauri were broken between v2.9.3 and v2.10.0, you'll need to regenerate them)
### What's Changed
- [`7be58a1c6`](https://www.github.com/tauri-apps/tauri/commit/7be58a1c643a7ed6d0f484a7e1134022618eb2b2) ([#14894](https://www.github.com/tauri-apps/tauri/pull/14894) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Log patching bundle type information again
### Dependencies
- Upgraded to `tauri-utils@2.8.3`
- Upgraded to `tauri-bundler@2.8.1`
## \[2.10.0]
### Enhancements
- [`f82594410`](https://www.github.com/tauri-apps/tauri/commit/f82594410cd57d6f794f58d4afea0ed335aa796f) ([#13253](https://www.github.com/tauri-apps/tauri/pull/13253) by [@Armaldio](https://www.github.com/tauri-apps/tauri/../../Armaldio)) Allow electron to run the CLI directly
- [`2d28e3143`](https://www.github.com/tauri-apps/tauri/commit/2d28e3143ee3d97d7570ea03877aa00a0d6e47d0) ([#14632](https://www.github.com/tauri-apps/tauri/pull/14632) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Small code refactors for improved code readability. No user facing changes.
- [`a2abe2e6b`](https://www.github.com/tauri-apps/tauri/commit/a2abe2e6bcb9e1eed8484240dfdb76a5bc28ae58) ([#14607](https://www.github.com/tauri-apps/tauri/pull/14607) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Simplified internal representation of `features: Option<Vec<String>>` with `Vec<String>`, no user facing changes
- [`84b04c4a8`](https://www.github.com/tauri-apps/tauri/commit/84b04c4a8d3310b7a7091d10e36244bf94996e51) ([#14759](https://www.github.com/tauri-apps/tauri/pull/14759) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added new environment variables for `tauri signer sign` command, to align with existing environment variables used in `tauri build`, `tauri bundle` and `tauri signer generate`
- `TAURI_SIGNING_PRIVATE_KEY`
- `TAURI_SIGNING_PRIVATE_KEY_PATH`
- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`
The old environment variables are deprecated and will be removed in a future release.
- `TAURI_PRIVATE_KEY`
- `TAURI_PRIVATE_KEY_PATH`
- `TAURI_PRIVATE_KEY_PASSWORD`
### Bug Fixes
- [`62aa13a12`](https://www.github.com/tauri-apps/tauri/commit/62aa13a124ef46bb5ce9887a2a574dd35ef86d4f) ([#14629](https://www.github.com/tauri-apps/tauri/pull/14629) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `android build`'s `--aab` and `--apk` flags requiring a value to be provided.
- [`eccff9758`](https://www.github.com/tauri-apps/tauri/commit/eccff97588232055bd0cafd83e6ee03d11a501fb) ([#14779](https://www.github.com/tauri-apps/tauri/pull/14779) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix empty associated-domains entitlements when domains are not configured for deep links.
- [`ea31b07f1`](https://www.github.com/tauri-apps/tauri/commit/ea31b07f19e0aa467ed0f921f60575cfe09809c8) ([#14789](https://www.github.com/tauri-apps/tauri/pull/14789) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fixed the command description for `tauri inspect`
- [`7fca58230`](https://www.github.com/tauri-apps/tauri/commit/7fca58230f97c3e6834134419514a0c7dbbe784b) ([#14830](https://www.github.com/tauri-apps/tauri/pull/14830) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) 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
- [`53611c4d7`](https://www.github.com/tauri-apps/tauri/commit/53611c4d7bdaf89b9a5d7c46a9c4bf4e34216148) ([#14747](https://www.github.com/tauri-apps/tauri/pull/14747) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Only watch dependent workspace members when running `tauri dev` instead of watching on all members
- [`1b0e335d3`](https://www.github.com/tauri-apps/tauri/commit/1b0e335d3f3445948d6590f7e074275d97cd9859) ([#14713](https://www.github.com/tauri-apps/tauri/pull/14713) by [@wasuaje](https://www.github.com/tauri-apps/tauri/../../wasuaje)) `tauri signer sign` doesn't work for files without an extension
### What's Changed
- [`e3fdcb500`](https://www.github.com/tauri-apps/tauri/commit/e3fdcb5002b362b46cde2a1971e4e7f2a1161208) ([#14836](https://www.github.com/tauri-apps/tauri/pull/14836) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Continued refactors of tauri-cli, fix too weak atomics.
- [`0575dd287`](https://www.github.com/tauri-apps/tauri/commit/0575dd287e021b61d2aedf64d62ae84a2c925fb4) ([#14521](https://www.github.com/tauri-apps/tauri/pull/14521) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) 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.
- [`7f7d9aac2`](https://www.github.com/tauri-apps/tauri/commit/7f7d9aac214e22d9492490543f7a9bcae0a6659e) ([#14668](https://www.github.com/tauri-apps/tauri/pull/14668) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Refactored internal use of static on config and directory resolvings, no user facing changes, please report any regressions if you encounter any
### Dependencies
- Upgraded to `tauri-utils@2.8.2`
- Upgraded to `tauri-macos-sign@2.3.3`
- Upgraded to `tauri-bundler@2.8.0`
## \[2.9.6]
### What's Changed

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-cli"
version = "2.10.1"
version = "2.9.6"
authors = ["Tauri Programme within The Commons Conservancy"]
edition = "2021"
rust-version = "1.77.2"
@@ -36,7 +36,7 @@ name = "cargo-tauri"
path = "src/main.rs"
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies]
cargo-mobile2 = { version = "0.22.3", default-features = false }
cargo-mobile2 = { version = "0.21.1", default-features = false }
[dependencies]
jsonrpsee = { version = "0.24", features = ["server"] }
@@ -47,7 +47,7 @@ sublime_fuzzy = "0.7"
clap_complete = "4"
clap = { version = "4", features = ["derive", "env"] }
thiserror = "2"
tauri-bundler = { version = "2.8.1", default-features = false, path = "../tauri-bundler" }
tauri-bundler = { version = "2.7.5", default-features = false, path = "../tauri-bundler" }
colored = "2"
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
@@ -56,23 +56,21 @@ notify = "8"
notify-debouncer-full = "0.6"
shared_child = "1"
duct = "1.0"
toml_edit = { version = "0.25", features = ["serde"] }
toml_edit = { version = "0.24", features = ["serde"] }
json-patch = "3"
tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
tauri-utils = { version = "2.8.1", path = "../tauri-utils", features = [
"isolation",
"schema",
"config-json5",
"config-toml",
"html-manipulation-2",
"html-manipulation",
] }
toml = "1"
toml = "0.9"
jsonschema = { version = "0.33", default-features = false }
handlebars = "6"
include_dir = "0.7"
dirs = "6"
# 0.7.4 to 0.8.0 were broken, 0.9 pulls in getrandom 0.4 with a high MSRV
# see https://github.com/tauri-apps/tauri/pull/15022
minisign = "=0.7.3"
minisign = "0.8"
base64 = "0.22"
ureq = { version = "3", default-features = false, features = ["gzip"] }
os_info = "3"
@@ -89,6 +87,8 @@ env_logger = "0.11"
icns = { package = "tauri-icns", version = "0.1" }
image = { version = "0.25", default-features = false, features = ["ico"] }
axum = { version = "0.8", features = ["ws"] }
html5ever = "0.29"
kuchiki = { package = "kuchikiki", version = "=0.8.8-speedreader" }
tokio = { version = "1", features = ["macros", "sync"] }
common-path = "1"
serde-value = "0.7"
@@ -133,7 +133,7 @@ libc = "0.2"
[target."cfg(target_os = \"macos\")".dependencies]
plist = "1"
tauri-macos-sign = { version = "2.3.3", path = "../tauri-macos-sign" }
tauri-macos-sign = { version = "2.3.2", path = "../tauri-macos-sign" }
object = { version = "0.36", default-features = false, features = [
"macho",
"read_core",

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://schema.tauri.app/config/2.10.3",
"$id": "https://schema.tauri.app/config/2.9.5",
"title": "Config",
"description": "The Tauri configuration object.\n It is read from a file where you can define your frontend assets,\n configure the bundler and define a tray icon.\n\n The configuration file is generated by the\n [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in\n your Tauri application source directory (src-tauri).\n\n Once generated, you may modify it at will to customize your Tauri application.\n\n ## File Formats\n\n By default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\n Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\n The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\n The TOML file name is `Tauri.toml`.\n\n ## Platform-Specific Configuration\n\n In addition to the default configuration file, Tauri can\n read a platform-specific configuration from `tauri.linux.conf.json`,\n `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\n (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\n which gets merged with the main configuration object.\n\n ## Configuration Structure\n\n The configuration is composed of the following objects:\n\n - [`app`](#appconfig): The Tauri configuration\n - [`build`](#buildconfig): The build configuration\n - [`bundle`](#bundleconfig): The bundle configurations\n - [`plugins`](#pluginconfig): The plugins configuration\n\n Example tauri.config.json file:\n\n ```json\n {\n \"productName\": \"tauri-app\",\n \"version\": \"0.1.0\",\n \"build\": {\n \"beforeBuildCommand\": \"\",\n \"beforeDevCommand\": \"\",\n \"devUrl\": \"http://localhost:3000\",\n \"frontendDist\": \"../dist\"\n },\n \"app\": {\n \"security\": {\n \"csp\": null\n },\n \"windows\": [\n {\n \"fullscreen\": false,\n \"height\": 600,\n \"resizable\": true,\n \"title\": \"Tauri App\",\n \"width\": 800\n }\n ]\n },\n \"bundle\": {},\n \"plugins\": {}\n }\n ```",
"type": "object",
@@ -131,7 +131,6 @@
"allowDowngrades": true,
"certificateThumbprint": null,
"digestAlgorithm": null,
"minimumWebview2Version": null,
"nsis": null,
"signCommand": null,
"timestampUrl": null,
@@ -604,27 +603,6 @@
"$ref": "#/definitions/ScrollBarStyle"
}
]
},
"activityName": {
"description": "The name of the Android activity to create for this window.",
"type": [
"string",
"null"
]
},
"createdByActivityName": {
"description": "The name of the Android activity that is creating this webview window.\n\n This is important to determine which stack the activity will belong to.",
"type": [
"string",
"null"
]
},
"requestedBySceneIdentifier": {
"description": "Sets the identifier of the scene that is requesting the new scene,\n establishing a relationship between the two scenes.\n\n By default the system uses the foreground scene.",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
@@ -2226,7 +2204,6 @@
"allowDowngrades": true,
"certificateThumbprint": null,
"digestAlgorithm": null,
"minimumWebview2Version": null,
"nsis": null,
"signCommand": null,
"timestampUrl": null,
@@ -2669,13 +2646,6 @@
"default": true,
"type": "boolean"
},
"minimumWebview2Version": {
"description": "Try to ensure that the WebView2 version is equal to or newer than this version,\n if the user's WebView2 is older than this version,\n the installer will try to trigger a WebView2 update.",
"type": [
"string",
"null"
]
},
"wix": {
"description": "Configuration for the MSI generated with WiX.",
"anyOf": [
@@ -2996,20 +2966,6 @@
"null"
]
},
"uninstallerIcon": {
"description": "The path to an icon file used as the uninstaller icon.",
"type": [
"string",
"null"
]
},
"uninstallerHeaderImage": {
"description": "The path to a bitmap file to display on the header of uninstallers pages.\n Defaults to [`Self::header_image`]. If this is set but [`Self::header_image`] is not, a default image from NSIS will be applied to `header_image`\n\n The recommended dimensions are 150px x 57px.",
"type": [
"string",
"null"
]
},
"installMode": {
"description": "Whether the installation will be for all users or just the current user.",
"default": "currentUser",
@@ -3068,8 +3024,7 @@
]
},
"minimumWebview2Version": {
"description": "Deprecated: use [`WindowsConfig::minimum_webview2_version`] (`bundle > windows > minimumWebview2Version`) instead.\n\n Try to ensure that the WebView2 version is equal to or newer than this version,\n if the user's WebView2 is older than this version,\n the installer will try to trigger a WebView2 update.",
"deprecated": true,
"description": "Try to ensure that the WebView2 version is equal to or newer than this version,\n if the user's WebView2 is older than this version,\n the installer will try to trigger a WebView2 update.",
"type": [
"string",
"null"

View File

@@ -1,9 +1,9 @@
{
"cli.js": {
"version": "2.10.1",
"version": "2.9.6",
"node": ">= 10.0.0"
},
"tauri": "2.10.3",
"tauri-build": "2.5.6",
"tauri-plugin": "2.5.4"
"tauri": "2.9.5",
"tauri-build": "2.5.3",
"tauri-plugin": "2.5.2"
}

View File

@@ -7,7 +7,12 @@ use std::{collections::HashSet, path::PathBuf};
use clap::Parser;
use tauri_utils::acl::capability::{Capability, PermissionEntry};
use crate::{acl::FileFormat, error::ErrorExt, helpers::prompts, Result};
use crate::{
acl::FileFormat,
error::ErrorExt,
helpers::{app_paths::tauri_dir, prompts},
Result,
};
#[derive(Debug, Parser)]
#[clap(about = "Create a new permission file")]
@@ -32,7 +37,7 @@ pub struct Options {
}
pub fn command(options: Options) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
let identifier = match options.identifier {
Some(i) => i,
@@ -106,7 +111,8 @@ pub fn command(options: Options) -> Result<()> {
.canonicalize()
.fs_context("failed to canonicalize capability file path", o.clone())?,
None => {
let capabilities_dir = dirs.tauri.join("capabilities");
let dir = tauri_dir();
let capabilities_dir = dir.join("capabilities");
capabilities_dir.join(format!(
"{}.{}",
capability.identifier,

View File

@@ -6,6 +6,7 @@ use clap::Parser;
use crate::{
error::{Context, ErrorExt},
helpers::app_paths::tauri_dir,
Result,
};
use colored::Colorize;
@@ -24,10 +25,9 @@ pub struct Options {
}
pub fn command(options: Options) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
let acl_manifests_path = dirs
.tauri
let acl_manifests_path = tauri_dir()
.join("gen")
.join("schemas")
.join("acl-manifests.json");

View File

@@ -24,10 +24,10 @@ pub struct Options {
#[clap(long)]
description: Option<String>,
/// List of commands to allow
#[clap(short, long, value_delimiter = ',')]
#[clap(short, long, use_value_delimiter = true)]
allow: Option<Vec<String>>,
/// List of commands to deny
#[clap(short, long, value_delimiter = ',')]
#[clap(short, long, use_value_delimiter = true)]
deny: Option<Vec<String>>,
/// Output file format.
#[clap(long, default_value_t = FileFormat::Json)]

View File

@@ -10,7 +10,7 @@ use crate::{
acl,
error::ErrorExt,
helpers::{
app_paths::{resolve_frontend_dir, Dirs},
app_paths::{resolve_frontend_dir, tauri_dir},
cargo,
npm::PackageManager,
},
@@ -39,11 +39,11 @@ pub struct Options {
}
pub fn command(options: Options) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
run(options, &dirs)
crate::helpers::app_paths::resolve();
run(options)
}
pub fn run(options: Options, dirs: &Dirs) -> Result<()> {
pub fn run(options: Options) -> Result<()> {
let (plugin, version) = options
.plugin
.split_once('@')
@@ -71,6 +71,7 @@ pub fn run(options: Options, dirs: &Dirs) -> Result<()> {
}
let frontend_dir = resolve_frontend_dir();
let tauri_dir = tauri_dir();
let target_str = metadata
.desktop_only
@@ -89,7 +90,7 @@ pub fn run(options: Options, dirs: &Dirs) -> Result<()> {
branch: options.branch.as_deref(),
rev: options.rev.as_deref(),
tag: options.tag.as_deref(),
cwd: Some(dirs.tauri),
cwd: Some(tauri_dir),
target: target_str,
})?;
@@ -116,7 +117,7 @@ pub fn run(options: Options, dirs: &Dirs) -> Result<()> {
(None, None, None, None) => npm_name,
_ => crate::error::bail!("Only one of --tag, --rev and --branch can be specified"),
};
manager.install(&[npm_spec], dirs.tauri)?;
manager.install(&[npm_spec], tauri_dir)?;
}
let _ = acl::permission::add::command(acl::permission::add::Options {
@@ -142,10 +143,7 @@ pub fn run(options: Options, dirs: &Dirs) -> Result<()> {
let plugin_init = format!(".plugin(tauri_plugin_{plugin_snake_case}::{plugin_init_fn})");
let re = Regex::new(r"(tauri\s*::\s*Builder\s*::\s*default\(\))(\s*)").unwrap();
for file in [
dirs.tauri.join("src/main.rs"),
dirs.tauri.join("src/lib.rs"),
] {
for file in [tauri_dir.join("src/main.rs"), tauri_dir.join("src/lib.rs")] {
let contents =
std::fs::read_to_string(&file).fs_context("failed to read Rust entry point", file.clone())?;
@@ -168,7 +166,7 @@ pub fn run(options: Options, dirs: &Dirs) -> Result<()> {
log::info!("Running `cargo fmt`...");
let _ = Command::new("cargo")
.arg("fmt")
.current_dir(dirs.tauri)
.current_dir(tauri_dir)
.status();
}

View File

@@ -7,11 +7,11 @@ use crate::{
error::{Context, ErrorExt},
helpers::{
self,
app_paths::Dirs,
config::{get_config, ConfigMetadata, FrontendDist},
app_paths::{frontend_dir, tauri_dir},
config::{get as get_config, ConfigMetadata, FrontendDist},
},
info::plugins::check_mismatched_packages,
interface::{rust::get_cargo_target_dir, AppInterface},
interface::{rust::get_cargo_target_dir, AppInterface, Interface},
ConfigValue, Result,
};
use clap::{ArgAction, Parser};
@@ -39,7 +39,7 @@ pub struct Options {
#[clap(short, long)]
pub target: Option<String>,
/// Space or comma separated list of features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Vec<String>,
/// Space or comma separated list of bundles to package.
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
@@ -82,7 +82,7 @@ pub struct Options {
}
pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
if options.no_sign {
log::warn!("--no-sign flag detected: Signing will be skipped.");
@@ -99,37 +99,41 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
let config = get_config(
target,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
let mut interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;
let mut interface = AppInterface::new(
config.lock().unwrap().as_ref().unwrap(),
options.target.clone(),
)?;
setup(&interface, &mut options, &config, &dirs, false)?;
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
if let Some(minimum_system_version) = &config.bundle.macos.minimum_system_version {
setup(&interface, &mut options, config_, false)?;
if let Some(minimum_system_version) = &config_.bundle.macos.minimum_system_version {
std::env::set_var("MACOSX_DEPLOYMENT_TARGET", minimum_system_version);
}
let app_settings = interface.app_settings();
let interface_options = options.clone().into();
let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?;
let out_dir = app_settings.out_dir(&interface_options)?;
let bin_path = interface.build(interface_options, &dirs)?;
let bin_path = interface.build(interface_options)?;
log::info!(action = "Built"; "application at: {}", tauri_utils::display_path(bin_path));
log::info!(action ="Built"; "application at: {}", tauri_utils::display_path(bin_path));
let app_settings = interface.app_settings();
if !options.no_bundle && (config.bundle.active || options.bundles.is_some()) {
if !options.no_bundle && (config_.bundle.active || options.bundles.is_some()) {
crate::bundle::bundle(
&options.into(),
verbosity,
ci,
&interface,
&*app_settings,
&config,
&dirs,
config_,
&out_dir,
)?;
}
@@ -141,13 +145,14 @@ pub fn setup(
interface: &AppInterface,
options: &mut Options,
config: &ConfigMetadata,
dirs: &Dirs,
mobile: bool,
) -> Result<()> {
let tauri_path = tauri_dir();
// TODO: Maybe optimize this to run in parallel in the future
// see https://github.com/tauri-apps/tauri/pull/13993#discussion_r2280697117
log::info!("Looking up installed tauri packages to check mismatched versions...");
if let Err(error) = check_mismatched_packages(dirs.frontend, dirs.tauri) {
if let Err(error) = check_mismatched_packages(frontend_dir(), tauri_path) {
if options.ignore_version_mismatches {
log::error!("{error}");
} else {
@@ -155,7 +160,7 @@ pub fn setup(
}
}
set_current_dir(dirs.tauri).context("failed to set current directory")?;
set_current_dir(tauri_path).context("failed to set current directory")?;
let bundle_identifier_source = config
.find_bundle_identifier_overwriter()
@@ -186,13 +191,7 @@ pub fn setup(
}
if let Some(before_build) = config.build.before_build_command.clone() {
helpers::run_hook(
"beforeBuildCommand",
before_build,
interface,
options.debug,
dirs.frontend,
)?;
helpers::run_hook("beforeBuildCommand", before_build, interface, options.debug)?;
}
if let Some(FrontendDist::Directory(web_asset_path)) = &config.build.frontend_dist {
@@ -220,7 +219,7 @@ pub fn setup(
// Issue #13287 - Allow the use of target dir inside frontendDist/distDir
// https://github.com/tauri-apps/tauri/issues/13287
let target_path = get_cargo_target_dir(&options.args, dirs.tauri)?;
let target_path = get_cargo_target_dir(&options.args)?;
let mut out_folders = Vec::new();
if let Ok(web_asset_canonical) = dunce::canonicalize(web_asset_path) {
if let Ok(relative_path) = target_path.strip_prefix(&web_asset_canonical) {

View File

@@ -16,11 +16,11 @@ use crate::{
error::{Context, ErrorExt},
helpers::{
self,
app_paths::Dirs,
config::{get_config, ConfigMetadata},
app_paths::tauri_dir,
config::{get as get_config, ConfigMetadata},
updater_signature,
},
interface::{AppInterface, AppSettings},
interface::{AppInterface, AppSettings, Interface},
ConfigValue,
};
@@ -43,7 +43,7 @@ impl ValueEnum for BundleFormat {
}
fn to_possible_value(&self) -> Option<PossibleValue> {
let hide = (!cfg!(windows) && self.0 == PackageType::Nsis) || self.0 == PackageType::Updater;
let hide = self.0 == PackageType::Updater;
Some(PossibleValue::new(self.0.short_name()).hide(hide))
}
}
@@ -70,7 +70,7 @@ pub struct Options {
#[clap(short, long)]
pub config: Vec<ConfigValue>,
/// Space or comma separated list of features, should be the same features passed to `tauri build` if any.
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Vec<String>,
/// Target triple to build against.
///
@@ -118,7 +118,7 @@ impl From<crate::build::Options> for Options {
}
pub fn command(options: Options, verbosity: u8) -> crate::Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
let ci = options.ci;
@@ -131,21 +131,27 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> {
let config = get_config(
target,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
let interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;
let interface = AppInterface::new(
config.lock().unwrap().as_ref().unwrap(),
options.target.clone(),
)?;
std::env::set_current_dir(dirs.tauri).context("failed to set current directory")?;
let tauri_path = tauri_dir();
std::env::set_current_dir(tauri_path).context("failed to set current directory")?;
if let Some(minimum_system_version) = &config.bundle.macos.minimum_system_version {
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
if let Some(minimum_system_version) = &config_.bundle.macos.minimum_system_version {
std::env::set_var("MACOSX_DEPLOYMENT_TARGET", minimum_system_version);
}
let app_settings = interface.app_settings();
let interface_options = options.clone().into();
let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?;
let out_dir = app_settings.out_dir(&interface_options)?;
bundle(
&options,
@@ -153,8 +159,7 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> {
ci,
&interface,
&*app_settings,
&config,
&dirs,
config_,
&out_dir,
)
}
@@ -167,7 +172,6 @@ pub fn bundle<A: AppSettings>(
interface: &AppInterface,
app_settings: &A,
config: &ConfigMetadata,
dirs: &Dirs,
out_dir: &Path,
) -> crate::Result<()> {
let package_types: Vec<PackageType> = if let Some(bundles) = &options.bundles {
@@ -194,19 +198,12 @@ pub fn bundle<A: AppSettings>(
before_bundle,
interface,
options.debug,
dirs.frontend,
)?;
}
}
let mut settings = app_settings
.get_bundler_settings(
options.clone().into(),
config,
out_dir,
package_types,
dirs.tauri,
)
.get_bundler_settings(options.clone().into(), config, out_dir, package_types)
.with_context(|| "failed to build bundler settings")?;
settings.set_no_sign(options.no_sign);
@@ -287,9 +284,6 @@ fn sign_updaters(
} else {
private_key
};
if password.is_none() {
log::info!("Decrypting updater signing key, expect a prompt for password")
}
let secret_key =
updater_signature::secret_key(private_key, password).context("failed to decode secret key")?;
let public_key = updater_signature::pub_key(pubkey).context("failed to decode pubkey")?;

View File

@@ -5,12 +5,14 @@
use crate::{
error::{Context, ErrorExt},
helpers::{
app_paths::Dirs,
app_paths::{frontend_dir, tauri_dir},
command_env,
config::{get_config, reload_config, BeforeDevCommand, ConfigMetadata, FrontendDist},
config::{
get as get_config, reload as reload_config, BeforeDevCommand, ConfigHandle, FrontendDist,
},
},
info::plugins::check_mismatched_packages,
interface::{AppInterface, ExitReason},
interface::{AppInterface, ExitReason, Interface},
CommandExt, ConfigValue, Error, Result,
};
@@ -25,13 +27,13 @@ use std::{
process::{exit, Command, Stdio},
sync::{
atomic::{AtomicBool, Ordering},
OnceLock,
Arc, Mutex, OnceLock,
},
};
mod builtin_dev_server;
static BEFORE_DEV: OnceLock<SharedChild> = OnceLock::new();
static BEFORE_DEV: OnceLock<Mutex<Arc<SharedChild>>> = OnceLock::new();
static KILL_BEFORE_DEV_FLAG: AtomicBool = AtomicBool::new(false);
#[cfg(unix)]
@@ -54,7 +56,7 @@ pub struct Options {
#[clap(short, long)]
pub target: Option<String>,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Vec<String>,
/// Exit on panic
#[clap(short, long)]
@@ -97,57 +99,61 @@ pub struct Options {
}
pub fn command(options: Options) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
let r = command_internal(options, dirs);
let r = command_internal(options);
if r.is_err() {
kill_before_dev_process();
}
r
}
fn command_internal(mut options: Options, dirs: Dirs) -> Result<()> {
fn command_internal(mut options: Options) -> Result<()> {
let target = options
.target
.as_deref()
.map(Target::from_triple)
.unwrap_or_else(Target::current);
let mut config = get_config(
let config = get_config(
target,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
let mut interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;
let mut interface = AppInterface::new(
config.lock().unwrap().as_ref().unwrap(),
options.target.clone(),
)?;
setup(&interface, &mut options, &mut config, &dirs)?;
setup(&interface, &mut options, config)?;
let exit_on_panic = options.exit_on_panic;
let no_watch = options.no_watch;
interface.dev(
&mut config,
options.into(),
move |status, reason| on_app_exit(status, reason, exit_on_panic, no_watch),
&dirs,
)
interface.dev(options.into(), move |status, reason| {
on_app_exit(status, reason, exit_on_panic, no_watch)
})
}
pub fn setup(
interface: &AppInterface,
options: &mut Options,
config: &mut ConfigMetadata,
dirs: &Dirs,
) -> Result<()> {
pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHandle) -> Result<()> {
let tauri_path = tauri_dir();
std::thread::spawn(|| {
if let Err(error) = check_mismatched_packages(dirs.frontend, dirs.tauri) {
if let Err(error) = check_mismatched_packages(frontend_dir(), tauri_path) {
log::error!("{error}");
}
});
set_current_dir(dirs.tauri).context("failed to set current directory")?;
set_current_dir(tauri_path).context("failed to set current directory")?;
if let Some(before_dev) = config.build.before_dev_command.clone() {
if let Some(before_dev) = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.before_dev_command
.clone()
{
let (script, script_cwd, wait) = match before_dev {
BeforeDevCommand::Script(s) if s.is_empty() => (None, None, false),
BeforeDevCommand::Script(s) => (Some(s), None, false),
@@ -155,7 +161,7 @@ pub fn setup(
(Some(script), cwd.map(Into::into), wait)
}
};
let cwd = script_cwd.unwrap_or_else(|| dirs.frontend.to_owned());
let cwd = script_cwd.unwrap_or_else(|| frontend_dir().clone());
if let Some(before_dev) = script {
log::info!(action = "Running"; "BeforeDevCommand (`{}`)", before_dev);
let mut env = command_env(true);
@@ -205,18 +211,21 @@ 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::SeqCst)) {
if !(status.success() || KILL_BEFORE_DEV_FLAG.load(Ordering::Relaxed)) {
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);
@@ -226,14 +235,43 @@ pub fn setup(
}
if options.runner.is_none() {
options.runner = config.build.runner.clone();
options.runner = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.runner
.clone();
}
let mut cargo_features = config.build.features.clone().unwrap_or_default();
let mut cargo_features = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.features
.clone()
.unwrap_or_default();
cargo_features.extend(options.features.clone());
let mut dev_url = config.build.dev_url.clone();
let frontend_dist = config.build.frontend_dist.clone();
let mut dev_url = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.dev_url
.clone();
let frontend_dist = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.frontend_dist
.clone();
if !options.no_dev_server && dev_url.is_none() {
if let Some(FrontendDist::Directory(path)) = &frontend_dist {
if path.exists() {
@@ -241,7 +279,9 @@ pub fn setup(
.canonicalize()
.fs_context("failed to canonicalize path", path.to_path_buf())?;
let ip = options.host.unwrap_or_else(|| Ipv4Addr::LOCALHOST.into());
let ip = options
.host
.unwrap_or_else(|| Ipv4Addr::new(127, 0, 0, 1).into());
let server_url = builtin_dev_server::start(path, ip, options.port)
.context("failed to start builtin dev server")?;
@@ -254,11 +294,7 @@ pub fn setup(
}
})));
reload_config(
config,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
reload_config(&options.config.iter().map(|c| &c.0).collect::<Vec<_>>())?;
}
}
}
@@ -311,9 +347,16 @@ pub fn setup(
}
if options.additional_watch_folders.is_empty() {
options
.additional_watch_folders
.extend(config.build.additional_watch_folders.clone());
options.additional_watch_folders.extend(
config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.additional_watch_folders
.clone(),
);
}
Ok(())
@@ -331,10 +374,11 @@ 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() {
if KILL_BEFORE_DEV_FLAG.load(Ordering::SeqCst) {
let child = child.lock().unwrap();
if KILL_BEFORE_DEV_FLAG.load(Ordering::Relaxed) {
return;
}
KILL_BEFORE_DEV_FLAG.store(true, Ordering::SeqCst);
KILL_BEFORE_DEV_FLAG.store(true, Ordering::Relaxed);
#[cfg(windows)]
{
let powershell_path = std::env::var("SYSTEMROOT").map_or_else(

View File

@@ -7,6 +7,8 @@ use axum::{
http::{header, StatusCode, Uri},
response::{IntoResponse, Response},
};
use html5ever::{namespace_url, ns, LocalName, QualName};
use kuchiki::{traits::TendrilSink, NodeRef};
use std::{
net::{IpAddr, SocketAddr},
path::{Path, PathBuf},
@@ -126,14 +128,30 @@ async fn ws_handler(ws: WebSocketUpgrade, state: State<ServerState>) -> Response
}
fn inject_address(html_bytes: Vec<u8>, address: &SocketAddr) -> Vec<u8> {
let document = tauri_utils::html2::parse_doc(String::from_utf8_lossy(&html_bytes).into_owned());
fn with_html_head<F: FnOnce(&NodeRef)>(document: &mut NodeRef, f: F) {
if let Ok(ref node) = document.select_first("head") {
f(node.as_node())
} else {
let node = NodeRef::new_element(
QualName::new(None, ns!(html), LocalName::from("head")),
None,
);
f(&node);
document.prepend(node)
}
}
tauri_utils::html2::append_script_to_head(
&document,
&RELOAD_SCRIPT.replace("{{reload_url}}", &format!("ws://{address}/__tauri_cli")),
);
let mut document = kuchiki::parse_html()
.one(String::from_utf8_lossy(&html_bytes).into_owned())
.document_node;
with_html_head(&mut document, |head| {
let script = RELOAD_SCRIPT.replace("{{reload_url}}", &format!("ws://{address}/__tauri_cli"));
let script_el = NodeRef::new_element(QualName::new(None, ns!(html), "script".into()), None);
script_el.append(NodeRef::new_text(script));
head.prepend(script_el);
});
tauri_utils::html2::serialize_doc(&document)
tauri_utils::html::serialize_node(&document)
}
fn fs_read_scoped(path: PathBuf, scope: &Path) -> crate::Result<Vec<u8>> {

View File

@@ -23,11 +23,6 @@ const ENV_TAURI_APP_PATH: &str = "TAURI_APP_PATH";
// path to the frontend app directory, usually `<project>/`
const ENV_TAURI_FRONTEND_PATH: &str = "TAURI_FRONTEND_PATH";
pub struct Dirs {
pub tauri: &'static Path,
pub frontend: &'static Path,
}
static FRONTEND_DIR: OnceLock<PathBuf> = OnceLock::new();
static TAURI_DIR: OnceLock<PathBuf> = OnceLock::new();
@@ -127,8 +122,8 @@ pub fn resolve_tauri_dir() -> Option<PathBuf> {
})
}
pub fn resolve_dirs() -> Dirs {
let tauri = TAURI_DIR.get_or_init(|| resolve_tauri_dir().unwrap_or_else(|| {
pub fn resolve() {
TAURI_DIR.set(resolve_tauri_dir().unwrap_or_else(|| {
let env_var_name = env_tauri_app_path().is_some().then(|| format!("`{ENV_TAURI_APP_PATH}`"));
panic!("Couldn't recognize the {} folder as a Tauri project. It must contain a `{}`, `{}` or `{}` file in any subfolder.",
env_var_name.as_deref().unwrap_or("current"),
@@ -136,11 +131,16 @@ pub fn resolve_dirs() -> Dirs {
ConfigFormat::Json5.into_file_name(),
ConfigFormat::Toml.into_file_name()
)
}));
let frontend = FRONTEND_DIR.get_or_init(|| {
resolve_frontend_dir().unwrap_or_else(|| tauri.parent().unwrap().to_path_buf())
});
Dirs { tauri, frontend }
})).expect("tauri dir already resolved");
FRONTEND_DIR
.set(resolve_frontend_dir().unwrap_or_else(|| tauri_dir().parent().unwrap().to_path_buf()))
.expect("app dir already resolved");
}
pub fn tauri_dir() -> &'static PathBuf {
TAURI_DIR
.get()
.expect("app paths not initialized, this is a Tauri CLI bug")
}
pub fn resolve_frontend_dir() -> Option<PathBuf> {
@@ -165,3 +165,9 @@ pub fn resolve_frontend_dir() -> Option<PathBuf> {
})
.map(|p| p.parent().unwrap().to_path_buf())
}
pub fn frontend_dir() -> &'static PathBuf {
FRONTEND_DIR
.get()
.expect("app paths not initialized, this is a Tauri CLI bug")
}

View File

@@ -56,7 +56,7 @@ pub fn cargo_manifest_and_lock(tauri_dir: &Path) -> (Option<CargoManifest>, Opti
.ok()
.and_then(|manifest_contents| toml::from_str(&manifest_contents).ok());
let lock: Option<CargoLock> = get_workspace_dir(tauri_dir)
let lock: Option<CargoLock> = get_workspace_dir()
.ok()
.and_then(|p| fs::read_to_string(p.join("Cargo.lock")).ok())
.and_then(|s| toml::from_str(&s).ok());

View File

@@ -13,9 +13,8 @@ use std::{
collections::HashMap,
env::{current_dir, set_current_dir, set_var},
ffi::{OsStr, OsString},
path::Path,
process::exit,
sync::OnceLock,
sync::{Mutex, OnceLock},
};
use crate::error::Context;
@@ -55,7 +54,8 @@ impl ConfigMetadata {
for (ext, config) in &self.extensions {
if let Some(identifier) = config
.as_object()
.and_then(|bundle_config| bundle_config.get("identifier")?.as_str())
.and_then(|bundle_config| bundle_config.get("identifier"))
.and_then(|id| id.as_str())
{
if identifier == self.inner.identifier {
return Some(ext.clone());
@@ -66,11 +66,14 @@ impl ConfigMetadata {
}
}
pub type ConfigHandle = &'static Mutex<Option<ConfigMetadata>>;
pub fn wix_settings(config: WixConfig) -> tauri_bundler::WixSettings {
tauri_bundler::WixSettings {
version: config.version,
upgrade_code: config.upgrade_code,
fips_compliant: std::env::var_os("TAURI_BUNDLER_WIX_FIPS_COMPLIANT")
fips_compliant: std::env::var("TAURI_BUNDLER_WIX_FIPS_COMPLIANT")
.ok()
.map(|v| v == "true")
.unwrap_or(config.fips_compliant),
language: tauri_bundler::WixLanguage(match config.language {
@@ -110,8 +113,6 @@ pub fn nsis_settings(config: NsisConfig) -> tauri_bundler::NsisSettings {
header_image: config.header_image,
sidebar_image: config.sidebar_image,
installer_icon: config.installer_icon,
uninstaller_icon: config.uninstaller_icon,
uninstaller_header_image: config.uninstaller_header_image,
install_mode: config.install_mode,
languages: config.languages,
custom_language_files: config.custom_language_files,
@@ -119,7 +120,6 @@ pub fn nsis_settings(config: NsisConfig) -> tauri_bundler::NsisSettings {
compression: config.compression,
start_menu_folder: config.start_menu_folder,
installer_hooks: config.installer_hooks,
#[allow(deprecated)]
minimum_webview2_version: config.minimum_webview2_version,
}
}
@@ -141,6 +141,11 @@ pub fn custom_sign_settings(
}
}
fn config_handle() -> ConfigHandle {
static CONFIG_HANDLE: Mutex<Option<ConfigMetadata>> = Mutex::new(None);
&CONFIG_HANDLE
}
fn config_schema_validator() -> &'static jsonschema::Validator {
// TODO: Switch to `LazyLock` when we bump MSRV to above 1.80
static CONFIG_SCHEMA_VALIDATOR: OnceLock<jsonschema::Validator> = OnceLock::new();
@@ -151,12 +156,17 @@ fn config_schema_validator() -> &'static jsonschema::Validator {
})
}
fn load_config(
/// Gets the static parsed config from `tauri.conf.json`.
fn get_internal(
merge_configs: &[&serde_json::Value],
reload: bool,
target: Target,
tauri_dir: &Path,
) -> crate::Result<ConfigMetadata> {
) -> crate::Result<ConfigHandle> {
if !reload && config_handle().lock().unwrap().is_some() {
return Ok(config_handle());
}
let tauri_dir = super::app_paths::tauri_dir();
let (mut config, config_path) =
tauri_utils::config::parse::parse_value(target, tauri_dir.join("tauri.conf.json"))
.context("failed to parse config")?;
@@ -165,7 +175,8 @@ fn load_config(
let original_identifier = config
.as_object()
.and_then(|config| config.get("identifier")?.as_str())
.and_then(|config| config.get("identifier"))
.and_then(|id| id.as_str())
.map(ToString::to_string);
if let Some((platform_config, config_path)) =
@@ -229,54 +240,59 @@ fn load_config(
std::env::set_var(REMOVE_UNUSED_COMMANDS_ENV_VAR, tauri_dir);
}
Ok(ConfigMetadata {
*config_handle().lock().unwrap() = Some(ConfigMetadata {
target,
original_identifier,
inner: config,
extensions,
})
});
Ok(config_handle())
}
pub fn get_config(
target: Target,
merge_configs: &[&serde_json::Value],
tauri_dir: &Path,
) -> crate::Result<ConfigMetadata> {
load_config(merge_configs, false, target, tauri_dir)
pub fn get(target: Target, merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
get_internal(merge_configs, false, target)
}
pub fn reload_config(
config: &mut ConfigMetadata,
merge_configs: &[&serde_json::Value],
tauri_dir: &Path,
) -> crate::Result<()> {
let target = config.target;
*config = load_config(merge_configs, true, target, tauri_dir)?;
Ok(())
pub fn reload(merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
let target = config_handle()
.lock()
.unwrap()
.as_ref()
.map(|conf| conf.target);
if let Some(target) = target {
get_internal(merge_configs, true, target)
} else {
crate::error::bail!("config not loaded");
}
}
/// merges the loaded config with the given value
pub fn merge_config_with(
config: &mut ConfigMetadata,
merge_configs: &[&serde_json::Value],
) -> crate::Result<()> {
pub fn merge_with(merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
let handle = config_handle();
if merge_configs.is_empty() {
return Ok(());
return Ok(handle);
}
let mut merge_config = serde_json::Value::Object(Default::default());
for conf in merge_configs {
merge_patches(&mut merge_config, conf);
if let Some(config_metadata) = &mut *handle.lock().unwrap() {
let mut merge_config = serde_json::Value::Object(Default::default());
for conf in merge_configs {
merge_patches(&mut merge_config, conf);
}
let merge_config_str = serde_json::to_string(&merge_config).unwrap();
set_var("TAURI_CONFIG", merge_config_str);
let mut value =
serde_json::to_value(config_metadata.inner.clone()).context("failed to serialize config")?;
merge(&mut value, &merge_config);
config_metadata.inner = serde_json::from_value(value).context("failed to parse config")?;
Ok(handle)
} else {
crate::error::bail!("config not loaded");
}
let merge_config_str = serde_json::to_string(&merge_config).unwrap();
set_var("TAURI_CONFIG", merge_config_str);
let mut value =
serde_json::to_value(config.inner.clone()).context("failed to serialize config")?;
merge(&mut value, &merge_config);
config.inner = serde_json::from_value(value).context("failed to parse config")?;
Ok(())
}
/// Same as [`json_patch::merge`] but doesn't delete the key when the patch's value is `null`

View File

@@ -30,7 +30,12 @@ use tauri_utils::config::HookCommand;
#[cfg(not(target_os = "windows"))]
use crate::Error;
use crate::{interface::AppInterface, CommandExt};
use crate::{
interface::{AppInterface, Interface},
CommandExt,
};
use self::app_paths::frontend_dir;
pub fn command_env(debug: bool) -> HashMap<&'static str, String> {
let mut map = HashMap::new();
@@ -73,14 +78,13 @@ pub fn run_hook(
hook: HookCommand,
interface: &AppInterface,
debug: bool,
frontend_dir: &Path,
) -> crate::Result<()> {
let (script, script_cwd) = match hook {
HookCommand::Script(s) if s.is_empty() => (None, None),
HookCommand::Script(s) => (Some(s), None),
HookCommand::ScriptWithOptions { script, cwd } => (Some(script), cwd.map(Into::into)),
};
let cwd = script_cwd.unwrap_or_else(|| frontend_dir.to_owned());
let cwd = script_cwd.unwrap_or_else(|| frontend_dir().clone());
if let Some(script) = script {
log::info!(action = "Running"; "{} `{}`", name, script);

View File

@@ -158,8 +158,6 @@ where
}
/// Gets the updater secret key from the given private key and password.
///
/// If `password` is `None`, a password is going to be prompted interactively.
pub fn secret_key<S: AsRef<[u8]>>(
private_key: S,
password: Option<String>,
@@ -206,30 +204,16 @@ where
#[cfg(test)]
mod tests {
use super::*;
// This was encrypted with an empty string
const PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5dkpDN09RZm5GeVAzc2RuYlNzWVVJelJRQnNIV2JUcGVXZUplWXZXYXpqUUFBQkFBQUFBQUFBQUFBQUlBQUFBQTZrN2RnWGh5dURxSzZiL1ZQSDdNcktiaHRxczQwMXdQelRHbjRNcGVlY1BLMTBxR2dpa3I3dDE1UTVDRDE4MXR4WlQwa1BQaXdxKy9UU2J2QmVSNXhOQWFDeG1GSVllbUNpTGJQRkhhTnROR3I5RmdUZi90OGtvaGhJS1ZTcjdZU0NyYzhQWlQ5cGM9Cg==";
// minisign >=0.7.4,<0.8.0 couldn't handle empty passwords if the private key is encrypted with an empty string.
// minisign >=0.7.4,<0.8.0 couldn't handle empty passwords.
#[test]
fn empty_password_is_valid() {
let path = std::env::temp_dir().join("minisign-password-text.txt");
std::fs::write(&path, b"TAURI").expect("failed to write test file");
let secret_key =
secret_key(PRIVATE_KEY, Some("".into())).expect("failed to resolve secret key");
sign_file(&secret_key, &path).expect("failed to sign file");
}
// This tests the newly generated keys with empty string password works
// minisign >=0.7.4,<=0.8.0 generate keys unencrypted if the password is empty but is marked encrypted hence unusable
#[test]
fn generate_empty_password_keys_and_use() {
let KeyPair { pk, sk } = generate_key(Some("".to_owned())).unwrap();
let pk = pub_key(pk).unwrap();
let sk = secret_key(sk, Some("".into())).unwrap();
let data = b"TAURI".as_slice();
sign(Some(&pk), &sk, data, None, None).expect("failed to sign file");
super::secret_key(PRIVATE_KEY, Some("".into())).expect("failed to resolve secret key");
super::sign_file(&secret_key, &path).expect("failed to sign file");
}
}

View File

@@ -4,6 +4,7 @@
use crate::{
error::{Context, Error, ErrorExt},
helpers::app_paths::tauri_dir,
Result,
};
@@ -94,7 +95,7 @@ pub struct Options {
output: Option<PathBuf>,
/// Custom PNG icon sizes to generate. When set, the default icons are not generated.
#[clap(short, long, value_delimiter = ',')]
#[clap(short, long, use_value_delimiter = true)]
png: Option<Vec<u32>>,
/// The background color of the iOS icon - string as defined in the W3C's CSS Color Module Level 4 <https://www.w3.org/TR/css-color-4/>.
@@ -236,8 +237,8 @@ fn parse_bg_color(bg_color_string: &String) -> Result<Rgba<u8>> {
pub fn command(options: Options) -> Result<()> {
let input = options.input;
let out_dir = options.output.unwrap_or_else(|| {
let dirs = crate::helpers::app_paths::resolve_dirs();
dirs.tauri.join("icons")
crate::helpers::app_paths::resolve();
tauri_dir().join("icons")
});
let png_icon_sizes = options.png.unwrap_or_default();

View File

@@ -3,46 +3,56 @@
// SPDX-License-Identifier: MIT
use super::SectionItem;
use crate::helpers::config::ConfigMetadata;
use crate::helpers::framework;
use std::{fs::read_to_string, path::PathBuf};
use std::{
fs::read_to_string,
path::{Path, PathBuf},
};
use tauri_utils::platform::Target;
pub fn items(config: &ConfigMetadata, frontend_dir: Option<&PathBuf>) -> Vec<SectionItem> {
pub fn items(frontend_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<SectionItem> {
let mut items = Vec::new();
let bundle_or_build = if config.bundle.active {
"bundle"
} else {
"build"
};
items.push(SectionItem::new().description(format!("build-type: {bundle_or_build}")));
if tauri_dir.is_some() {
if let Ok(config) = crate::helpers::config::get(Target::current(), &[]) {
let config_guard = config.lock().unwrap();
let config = config_guard.as_ref().unwrap();
let csp = config
.app
.security
.csp
.clone()
.map(|c| c.to_string())
.unwrap_or_else(|| "unset".to_string());
items.push(SectionItem::new().description(format!("CSP: {csp}")));
let bundle_or_build = if config.bundle.active {
"bundle"
} else {
"build"
};
items.push(SectionItem::new().description(format!("build-type: {bundle_or_build}")));
if let Some(frontend_dist) = &config.build.frontend_dist {
items.push(SectionItem::new().description(format!("frontendDist: {frontend_dist}")));
}
let csp = config
.app
.security
.csp
.clone()
.map(|c| c.to_string())
.unwrap_or_else(|| "unset".to_string());
items.push(SectionItem::new().description(format!("CSP: {csp}")));
if let Some(dev_url) = &config.build.dev_url {
items.push(SectionItem::new().description(format!("devUrl: {dev_url}")));
}
if let Some(frontend_dir) = frontend_dir {
if let Ok(package_json) = read_to_string(frontend_dir.join("package.json")) {
let (framework, bundler) = framework::infer_from_package_json(&package_json);
if let Some(framework) = framework {
items.push(SectionItem::new().description(format!("framework: {framework}")));
if let Some(frontend_dist) = &config.build.frontend_dist {
items.push(SectionItem::new().description(format!("frontendDist: {frontend_dist}")));
}
if let Some(bundler) = bundler {
items.push(SectionItem::new().description(format!("bundler: {bundler}")));
if let Some(dev_url) = &config.build.dev_url {
items.push(SectionItem::new().description(format!("devUrl: {dev_url}")));
}
if let Some(frontend_dir) = frontend_dir {
if let Ok(package_json) = read_to_string(frontend_dir.join("package.json")) {
let (framework, bundler) = framework::infer_from_package_json(&package_json);
if let Some(framework) = framework {
items.push(SectionItem::new().description(format!("framework: {framework}")));
}
if let Some(bundler) = bundler {
items.push(SectionItem::new().description(format!("bundler: {bundler}")));
}
}
}
}
}

View File

@@ -12,7 +12,6 @@ use colored::{ColoredString, Colorize};
use dialoguer::{theme::ColorfulTheme, Confirm};
use serde::Deserialize;
use std::fmt::{self, Display, Formatter};
use tauri_utils::platform::Target;
mod app;
mod env_nodejs;
@@ -266,6 +265,11 @@ pub fn command(options: Options) -> Result<()> {
let frontend_dir = resolve_frontend_dir();
let tauri_dir = resolve_tauri_dir();
if tauri_dir.is_some() {
// safe to initialize
crate::helpers::app_paths::resolve();
}
let package_manager = frontend_dir
.as_ref()
.map(packages_nodejs::package_manager)
@@ -309,11 +313,9 @@ pub fn command(options: Options) -> Result<()> {
interactive,
items: Vec::new(),
};
if let Some(tauri_dir) = &tauri_dir {
if let Ok(config) = crate::helpers::config::get_config(Target::current(), &[], tauri_dir) {
app.items.extend(app::items(&config, frontend_dir.as_ref()));
};
}
app
.items
.extend(app::items(frontend_dir.as_ref(), tauri_dir.as_deref()));
environment.display();

View File

@@ -111,27 +111,33 @@ pub fn items(
) -> Vec<SectionItem> {
let mut items = Vec::new();
if let Some(tauri_dir) = tauri_dir {
let (manifest, lock) = cargo_manifest_and_lock(tauri_dir);
if tauri_dir.is_some() || frontend_dir.is_some() {
if let Some(tauri_dir) = tauri_dir {
let (manifest, lock) = cargo_manifest_and_lock(tauri_dir);
for p in helpers::plugins::known_plugins().keys() {
let dep = format!("tauri-plugin-{p}");
let crate_version = crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), &dep);
if !crate_version.has_version() {
continue;
for p in helpers::plugins::known_plugins().keys() {
let dep = format!("tauri-plugin-{p}");
let crate_version = crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), &dep);
if !crate_version.has_version() {
continue;
}
let item = packages_rust::rust_section_item(&dep, crate_version);
items.push(item);
let Some(frontend_dir) = frontend_dir else {
continue;
};
let package = format!("@tauri-apps/plugin-{p}");
let item = packages_nodejs::nodejs_section_item(
package,
None,
frontend_dir.clone(),
package_manager,
);
items.push(item);
}
let item = packages_rust::rust_section_item(&dep, crate_version);
items.push(item);
let Some(frontend_dir) = frontend_dir else {
continue;
};
let package = format!("@tauri-apps/plugin-{p}");
let item =
packages_nodejs::nodejs_section_item(package, None, frontend_dir.clone(), package_manager);
items.push(item);
}
}

View File

@@ -1,15 +1,14 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::path::Path;
use crate::Result;
use clap::{Parser, Subcommand};
use crate::interface::{AppInterface, AppSettings};
use crate::interface::{AppInterface, AppSettings, Interface};
#[derive(Debug, Parser)]
#[clap(about = "Inspect values used by Tauri")]
#[clap(about = "Manage or create permissions for your app or plugin")]
pub struct Cli {
#[clap(subcommand)]
command: Commands,
@@ -22,34 +21,36 @@ enum Commands {
}
pub fn command(cli: Cli) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
match cli.command {
Commands::WixUpgradeCode => wix_upgrade_code(dirs.tauri),
Commands::WixUpgradeCode => wix_upgrade_code(),
}
}
// NOTE: if this is ever changed, make sure to also update Wix upgrade code generation in tauri-bundler
fn wix_upgrade_code(tauri_dir: &Path) -> Result<()> {
let target = tauri_utils::platform::Target::Windows;
let config = crate::helpers::config::get_config(target, &[], tauri_dir)?;
fn wix_upgrade_code() -> Result<()> {
crate::helpers::app_paths::resolve();
let interface = AppInterface::new(&config, None, tauri_dir)?;
let target = tauri_utils::platform::Target::Windows;
let config = crate::helpers::config::get(target, &[])?;
let interface = AppInterface::new(config.lock().unwrap().as_ref().unwrap(), None)?;
let product_name = interface.app_settings().get_package_settings().product_name;
let upgrade_code = uuid::Uuid::new_v5(
&uuid::Uuid::NAMESPACE_DNS,
format!("{product_name}.exe.app.x64").as_bytes(),
);
)
.to_string();
log::info!("Default WiX Upgrade Code, derived from {product_name}: {upgrade_code}");
if let Some(code) = config
.bundle
.windows
.wix
.as_ref()
.and_then(|wix| wix.upgrade_code)
{
if let Some(code) = config.lock().unwrap().as_ref().and_then(|c| {
c.bundle
.windows
.wix
.as_ref()
.and_then(|wix| wix.upgrade_code)
}) {
log::info!("Application Upgrade Code override: {code}");
}

View File

@@ -5,8 +5,10 @@
pub mod rust;
use std::{
collections::HashMap,
path::{Path, PathBuf},
process::ExitStatus,
sync::Arc,
};
use crate::{error::Context, helpers::config::Config};
@@ -16,6 +18,7 @@ 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)]
@@ -29,14 +32,9 @@ pub trait AppSettings {
options: &Options,
config: &Config,
features: &[String],
tauri_dir: &Path,
) -> crate::Result<tauri_bundler::BundleSettings>;
fn app_binary_path(&self, options: &Options, tauri_dir: &Path) -> crate::Result<PathBuf>;
fn get_binaries(
&self,
options: &Options,
tauri_dir: &Path,
) -> crate::Result<Vec<tauri_bundler::BundleBinary>>;
fn app_binary_path(&self, options: &Options) -> crate::Result<PathBuf>;
fn get_binaries(&self, options: &Options) -> crate::Result<Vec<tauri_bundler::BundleBinary>>;
fn app_name(&self) -> Option<String>;
fn lib_name(&self) -> Option<String>;
@@ -46,7 +44,6 @@ pub trait AppSettings {
config: &Config,
out_dir: &Path,
package_types: Vec<PackageType>,
tauri_dir: &Path,
) -> crate::Result<Settings> {
let no_default_features = options.args.contains(&"--no-default-features".into());
let mut enabled_features = options.features.clone();
@@ -60,7 +57,7 @@ pub trait AppSettings {
tauri_utils::platform::target_triple().context("failed to get target triple")?
};
let mut bins = self.get_binaries(&options, tauri_dir)?;
let mut bins = self.get_binaries(&options)?;
if let Some(main_binary_name) = &config.main_binary_name {
let main = bins.iter_mut().find(|b| b.main()).context("no main bin?")?;
main.set_name(main_binary_name.to_owned());
@@ -68,7 +65,7 @@ pub trait AppSettings {
let mut settings_builder = SettingsBuilder::new()
.package_settings(self.get_package_settings())
.bundle_settings(self.get_bundle_settings(&options, config, &enabled_features, tauri_dir)?)
.bundle_settings(self.get_bundle_settings(&options, config, &enabled_features)?)
.binaries(bins)
.project_out_directory(out_dir)
.target(target)
@@ -76,7 +73,7 @@ pub trait AppSettings {
if config.bundle.use_local_tools_dir {
settings_builder = settings_builder.local_tools_directory(
rust::get_cargo_metadata(tauri_dir)
rust::get_cargo_metadata()
.context("failed to get cargo metadata")?
.target_directory,
)
@@ -98,3 +95,27 @@ pub enum ExitReason {
/// Regular exit.
NormalExit,
}
pub trait Interface: Sized {
type AppSettings: AppSettings;
fn new(config: &Config, target: Option<String>) -> crate::Result<Self>;
fn app_settings(&self) -> Arc<Self::AppSettings>;
fn env(&self) -> HashMap<&str, String>;
fn build(&mut self, options: Options) -> crate::Result<PathBuf>;
fn dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
&mut self,
options: Options,
on_exit: F,
) -> crate::Result<()>;
fn mobile_dev<R: Fn(MobileOptions) -> crate::Result<Box<dyn DevProcess + Send>>>(
&mut self,
options: MobileOptions,
runner: R,
) -> crate::Result<()>;
fn watch<R: Fn() -> crate::Result<Box<dyn DevProcess + Send>>>(
&mut self,
options: WatcherOptions,
runner: R,
) -> crate::Result<()>;
}

View File

@@ -7,7 +7,6 @@ use std::{
ffi::OsStr,
fs::FileType,
io::{BufRead, Write},
iter::once,
path::{Path, PathBuf},
process::Command,
str::FromStr,
@@ -16,6 +15,7 @@ use std::{
};
use dunce::canonicalize;
use glob::glob;
use ignore::gitignore::{Gitignore, GitignoreBuilder};
use notify::RecursiveMode;
use notify_debouncer_full::new_debouncer;
@@ -27,12 +27,12 @@ use tauri_bundler::{
};
use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol, RunnerConfig, Updater};
use super::{AppSettings, DevProcess, ExitReason};
use super::{AppSettings, DevProcess, ExitReason, Interface};
use crate::{
error::{bail, Context, Error, ErrorExt},
error::{Context, Error, ErrorExt},
helpers::{
app_paths::Dirs,
config::{nsis_settings, reload_config, wix_settings, BundleResources, Config, ConfigMetadata},
app_paths::{frontend_dir, tauri_dir},
config::{nsis_settings, reload as reload_config, wix_settings, BundleResources, Config},
},
ConfigValue,
};
@@ -134,8 +134,10 @@ pub struct Rust {
main_binary_name: Option<String>,
}
impl Rust {
pub fn new(config: &Config, target: Option<String>, tauri_dir: &Path) -> crate::Result<Self> {
impl Interface for Rust {
type AppSettings = RustAppSettings;
fn new(config: &Config, target: Option<String>) -> crate::Result<Self> {
let manifest = {
let (tx, rx) = sync_channel(1);
let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| {
@@ -144,11 +146,15 @@ impl Rust {
}
})
.unwrap();
let manifest_path = tauri_dir.join("Cargo.toml");
watcher
.watch(&manifest_path, RecursiveMode::NonRecursive)
.with_context(|| format!("failed to watch {}", manifest_path.display()))?;
let (manifest, modified) = rewrite_manifest(config, tauri_dir)?;
.watch(tauri_dir().join("Cargo.toml"), RecursiveMode::NonRecursive)
.with_context(|| {
format!(
"failed to watch {}",
tauri_dir().join("Cargo.toml").display()
)
})?;
let (manifest, modified) = rewrite_manifest(config)?;
if modified {
// Wait for the modified event so we don't trigger a re-build later on
let _ = rx.recv_timeout(Duration::from_secs(2));
@@ -166,7 +172,7 @@ impl Rust {
);
}
let app_settings = RustAppSettings::new(config, manifest, target, tauri_dir)?;
let app_settings = RustAppSettings::new(config, manifest, target)?;
Ok(Self {
app_settings: Arc::new(app_settings),
@@ -176,27 +182,24 @@ impl Rust {
})
}
pub fn app_settings(&self) -> Arc<RustAppSettings> {
fn app_settings(&self) -> Arc<Self::AppSettings> {
self.app_settings.clone()
}
pub fn build(&mut self, options: Options, dirs: &Dirs) -> crate::Result<PathBuf> {
fn build(&mut self, options: Options) -> crate::Result<PathBuf> {
desktop::build(
options,
&self.app_settings,
&mut self.available_targets,
self.config_features.clone(),
self.main_binary_name.as_deref(),
dirs.tauri,
)
}
pub fn dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
fn dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
&mut self,
config: &mut ConfigMetadata,
mut options: Options,
on_exit: F,
dirs: &Dirs,
) -> crate::Result<()> {
let on_exit = Arc::new(on_exit);
@@ -211,7 +214,7 @@ impl 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();
})?;
@@ -220,31 +223,20 @@ impl Rust {
Ok(())
} else {
let merge_configs = options.config.iter().map(|c| &c.0).collect::<Vec<_>>();
self.run_dev_watcher(
config,
&options.additional_watch_folders,
&merge_configs,
|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,
)
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(&options.additional_watch_folders, &merge_configs, run)
}
}
pub fn mobile_dev<
R: Fn(MobileOptions, &ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>,
>(
fn mobile_dev<R: Fn(MobileOptions) -> crate::Result<Box<dyn DevProcess + Send>>>(
&mut self,
config: &mut ConfigMetadata,
mut options: MobileOptions,
runner: R,
dirs: &Dirs,
) -> crate::Result<()> {
let mut run_args = Vec::new();
dev_options(
@@ -256,39 +248,30 @@ impl Rust {
);
if options.no_watch {
runner(options, config)?;
runner(options)?;
Ok(())
} else {
self.watch(
config,
WatcherOptions {
config: options.config.clone(),
additional_watch_folders: options.additional_watch_folders.clone(),
},
move |config| runner(options.clone(), config),
dirs,
move || runner(options.clone()),
)
}
}
pub fn watch<R: Fn(&ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>>(
fn watch<R: Fn() -> crate::Result<Box<dyn DevProcess + Send>>>(
&mut self,
config: &mut ConfigMetadata,
options: WatcherOptions,
runner: R,
dirs: &Dirs,
) -> crate::Result<()> {
let merge_configs = options.config.iter().map(|c| &c.0).collect::<Vec<_>>();
self.run_dev_watcher(
config,
&options.additional_watch_folders,
&merge_configs,
|_rust: &mut Rust, config| runner(config),
dirs,
)
let run = Arc::new(|_rust: &mut Rust| runner());
self.run_dev_watcher(&options.additional_watch_folders, &merge_configs, run)
}
pub fn env(&self) -> HashMap<&str, String> {
fn env(&self) -> HashMap<&str, String> {
let mut env = HashMap::new();
env.insert(
"TAURI_ENV_TARGET_TRIPLE",
@@ -364,7 +347,7 @@ fn build_ignore_matcher(dir: &Path) -> IgnoreMatcher {
ignore_builder.add(path);
if let Some(ignore_file) = std::env::var_os("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
if let Ok(ignore_file) = std::env::var("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
ignore_builder.add(dir.join(ignore_file));
}
@@ -396,7 +379,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 Some(ignore_file) = std::env::var_os("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
if let Ok(ignore_file) = std::env::var("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
builder.add_ignore(ignore_file);
}
builder.require_git(false).ignore(false).max_depth(Some(1));
@@ -426,7 +409,7 @@ fn dev_options(
}
*args = dev_args;
if mobile && !args.contains(&"--lib".into()) {
if mobile {
args.push("--lib".into());
}
@@ -450,21 +433,29 @@ fn dev_options(
}
}
fn get_watch_folders(
additional_watch_folders: &[PathBuf],
tauri_dir: &Path,
) -> crate::Result<Vec<PathBuf>> {
// We always want to watch the main tauri folder.
let mut watch_folders = vec![tauri_dir.to_path_buf()];
// 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)
}
watch_folders.extend(get_in_workspace_dependency_paths(tauri_dir)?);
fn get_watch_folders(additional_watch_folders: &[PathBuf]) -> crate::Result<Vec<PathBuf>> {
let tauri_path = tauri_dir();
let workspace_path = get_workspace_dir()?;
// We always want to watch the main tauri folder.
let mut watch_folders = vec![tauri_path.to_path_buf()];
// 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() {
dir.to_owned()
} else {
tauri_dir.join(dir)
tauri_path.join(dir)
};
let canonicalized = canonicalize(&path).ok();
@@ -477,6 +468,30 @@ 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)
}
@@ -484,9 +499,7 @@ impl Rust {
pub fn build_options(&self, args: &mut Vec<String>, features: &mut Vec<String>, mobile: bool) {
features.push("tauri/custom-protocol".into());
if mobile {
if !args.contains(&"--lib".into()) {
args.push("--lib".into());
}
args.push("--lib".into());
} else {
args.push("--bins".into());
}
@@ -495,9 +508,9 @@ impl Rust {
fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
&mut self,
options: Options,
run_args: &[String],
run_args: Vec<String>,
on_exit: F,
) -> crate::Result<desktop::DevChild> {
) -> crate::Result<Box<dyn DevProcess + Send>> {
desktop::run_dev(
options,
run_args,
@@ -505,30 +518,25 @@ 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, &ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>,
>(
fn run_dev_watcher<F: Fn(&mut Rust) -> crate::Result<Box<dyn DevProcess + Send>>>(
&mut self,
config: &mut ConfigMetadata,
additional_watch_folders: &[PathBuf],
merge_configs: &[&serde_json::Value],
run: F,
dirs: &Dirs,
run: Arc<F>,
) -> crate::Result<()> {
let mut child = run(self, config)?;
let child = run(self)?;
let process = Arc::new(Mutex::new(child));
let (tx, rx) = sync_channel(1);
let frontend_path = frontend_dir();
let watch_folders = get_watch_folders(additional_watch_folders, dirs.tauri)?;
let watch_folders = get_watch_folders(additional_watch_folders)?;
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 common_ancestor = common_path::common_path_all(watch_folders.iter().map(Path::new))
.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| {
@@ -556,47 +564,49 @@ impl Rust {
}
}
while let Ok(events) = rx.recv() {
let paths: Vec<PathBuf> = events
.into_iter()
.filter(|event| !event.kind.is_access())
.flat_map(|event| event.event.paths)
.filter(|path| !ignore_matcher.is_ignore(path, path.is_dir()))
.collect();
loop {
if let Ok(events) = rx.recv() {
for event in events {
if event.kind.is_access() {
continue;
}
let config_file_changed = paths
.iter()
.any(|path| is_configuration_file(self.app_settings.target_platform, path));
if config_file_changed && reload_config(config, merge_configs, dirs.tauri).is_ok() {
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
// and it will trigger the watcher again
continue;
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) {
if let Ok(config) = reload_config(merge_configs) {
let (manifest, modified) =
rewrite_manifest(config.lock().unwrap().as_ref().unwrap())?;
if modified {
*self.app_settings.manifest.lock().unwrap() = manifest;
// no need to run the watcher logic, the manifest was modified
// and it will trigger the watcher again
continue;
}
}
}
log::info!(
"File {} changed. Rebuilding application...",
display_path(event_path.strip_prefix(frontend_path).unwrap_or(event_path))
);
let mut p = process.lock().unwrap();
p.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 Some(first_changed_path) = paths.first() else {
continue;
};
log::info!(
"File {} changed. Rebuilding application...",
display_path(
first_changed_path
.strip_prefix(dirs.frontend)
.unwrap_or(first_changed_path)
)
);
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)
let _ = child.wait();
child = run(self, config)?;
}
bail!("File watcher exited unexpectedly")
}
}
@@ -662,7 +672,7 @@ pub struct TomlWorkspaceField {
#[derive(Clone, Debug, Deserialize)]
struct WorkspaceSettings {
/// the workspace members.
// members: Option<Vec<String>>,
members: Option<Vec<String>>,
package: Option<WorkspacePackageSettings>,
}
@@ -749,7 +759,6 @@ pub struct RustAppSettings {
cargo_config: CargoConfig,
target_triple: String,
target_platform: TargetPlatform,
workspace_dir: PathBuf,
}
#[derive(Deserialize)]
@@ -828,7 +837,6 @@ impl AppSettings for RustAppSettings {
options: &Options,
config: &Config,
features: &[String],
tauri_dir: &Path,
) -> crate::Result<BundleSettings> {
let arch64bits = self.target_triple.starts_with("x86_64")
|| self.target_triple.starts_with("aarch64")
@@ -859,7 +867,6 @@ impl AppSettings for RustAppSettings {
self,
features,
config,
tauri_dir,
config.bundle.clone(),
updater_settings,
arch64bits,
@@ -904,8 +911,8 @@ impl AppSettings for RustAppSettings {
Ok(settings)
}
fn app_binary_path(&self, options: &Options, tauri_dir: &Path) -> crate::Result<PathBuf> {
let binaries = self.get_binaries(options, tauri_dir)?;
fn app_binary_path(&self, options: &Options) -> crate::Result<PathBuf> {
let binaries = self.get_binaries(options)?;
let bin_name = binaries
.iter()
.find(|x| x.main())
@@ -913,7 +920,7 @@ impl AppSettings for RustAppSettings {
.name();
let out_dir = self
.out_dir(options, tauri_dir)
.out_dir(options)
.context("failed to get project out directory")?;
let mut path = out_dir.join(bin_name);
@@ -931,7 +938,7 @@ impl AppSettings for RustAppSettings {
Ok(path)
}
fn get_binaries(&self, options: &Options, tauri_dir: &Path) -> crate::Result<Vec<BundleBinary>> {
fn get_binaries(&self, options: &Options) -> crate::Result<Vec<BundleBinary>> {
let mut binaries = Vec::new();
if let Some(bins) = &self.cargo_settings.bin {
@@ -960,6 +967,8 @@ impl AppSettings for RustAppSettings {
}
}
let tauri_dir = tauri_dir();
let mut binaries_paths = std::fs::read_dir(tauri_dir.join("src/bin"))
.map(|dir| {
dir
@@ -1051,12 +1060,8 @@ impl AppSettings for RustAppSettings {
}
impl RustAppSettings {
pub fn new(
config: &Config,
manifest: Manifest,
target: Option<String>,
tauri_dir: &Path,
) -> crate::Result<Self> {
pub fn new(config: &Config, manifest: Manifest, target: Option<String>) -> crate::Result<Self> {
let tauri_dir = tauri_dir();
let cargo_settings = CargoSettings::load(tauri_dir).context("failed to load Cargo settings")?;
let cargo_package_settings = match &cargo_settings.package {
Some(package_info) => package_info.clone(),
@@ -1067,8 +1072,7 @@ impl RustAppSettings {
}
};
let workspace_dir = get_workspace_dir(tauri_dir)?;
let ws_package_settings = CargoSettings::load(&workspace_dir)
let ws_package_settings = CargoSettings::load(&get_workspace_dir()?)
.context("failed to load Cargo settings from workspace root")?
.workspace
.and_then(|v| v.package);
@@ -1163,7 +1167,6 @@ impl RustAppSettings {
cargo_config,
target_triple,
target_platform,
workspace_dir,
})
}
@@ -1174,8 +1177,8 @@ impl RustAppSettings {
.or_else(|| self.cargo_config.build().target())
}
pub fn out_dir(&self, options: &Options, tauri_dir: &Path) -> crate::Result<PathBuf> {
get_target_dir(self.target(options), options, tauri_dir)
pub fn out_dir(&self, options: &Options) -> crate::Result<PathBuf> {
get_target_dir(self.target(options), options)
}
}
@@ -1183,29 +1186,12 @@ 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> {
pub(crate) fn get_cargo_metadata() -> crate::Result<CargoMetadata> {
let output = Command::new("cargo")
.args(["metadata", "--no-deps", "--format-version", "1"])
.current_dir(tauri_dir)
.current_dir(tauri_dir())
.output()
.map_err(|error| Error::CommandFailed {
command: "cargo metadata --no-deps --format-version 1".to_string(),
@@ -1222,66 +1208,16 @@ 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.
pub(crate) fn get_cargo_target_dir(args: &[String], tauri_dir: &Path) -> crate::Result<PathBuf> {
pub(crate) fn get_cargo_target_dir(args: &[String]) -> crate::Result<PathBuf> {
let path = if let Some(target) = get_cargo_option(args, "--target-dir") {
std::env::current_dir()
.context("failed to get current directory")?
.join(target)
} else {
get_cargo_metadata(tauri_dir)
get_cargo_metadata()
.context("failed to run 'cargo metadata' command to get target directory")?
.target_directory
};
@@ -1291,12 +1227,8 @@ pub(crate) fn get_cargo_target_dir(args: &[String], tauri_dir: &Path) -> crate::
/// This function determines the 'target' directory and suffixes it with the profile
/// to determine where the compiled binary will be located.
fn get_target_dir(
triple: Option<&str>,
options: &Options,
tauri_dir: &Path,
) -> crate::Result<PathBuf> {
let mut path = get_cargo_target_dir(&options.args, tauri_dir)?;
fn get_target_dir(triple: Option<&str>, options: &Options) -> crate::Result<PathBuf> {
let mut path = get_cargo_target_dir(&options.args)?;
if let Some(triple) = triple {
path.push(triple);
@@ -1321,9 +1253,9 @@ fn get_cargo_option<'a>(args: &'a [String], option: &'a str) -> Option<&'a str>
}
/// Executes `cargo metadata` to get the workspace directory.
pub fn get_workspace_dir(tauri_dir: &Path) -> crate::Result<PathBuf> {
pub fn get_workspace_dir() -> crate::Result<PathBuf> {
Ok(
get_cargo_metadata(tauri_dir)
get_cargo_metadata()
.context("failed to run 'cargo metadata' command to get workspace directory")?
.workspace_root,
)
@@ -1349,7 +1281,6 @@ fn tauri_config_to_bundle_settings(
settings: &RustAppSettings,
features: &[String],
tauri_config: &Config,
tauri_dir: &Path,
config: crate::helpers::config::BundleConfig,
updater_config: Option<UpdaterSettings>,
arch64bits: bool,
@@ -1381,7 +1312,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_os("TAURI_LINUX_AYATANA_APPINDICATOR")
let (tray_kind, path) = std::env::var("TAURI_LINUX_AYATANA_APPINDICATOR")
.map(|ayatana| {
if ayatana == "true" || ayatana == "1" {
(
@@ -1403,7 +1334,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());
@@ -1494,16 +1425,14 @@ fn tauri_config_to_bundle_settings(
.map(tauri_bundler::bundle::Entitlements::Path)
} else {
let mut app_links_entitlements = plist::Dictionary::new();
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(),
);
}
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(),
@@ -1633,7 +1562,7 @@ fn tauri_config_to_bundle_settings(
info_plist: {
let mut src_plists = vec![];
let path = tauri_dir.join("Info.plist");
let path = tauri_dir().join("Info.plist");
if path.exists() {
src_plists.push(path.into());
}
@@ -1657,7 +1586,6 @@ fn tauri_config_to_bundle_settings(
webview_install_mode: config.windows.webview_install_mode,
allow_downgrades: config.windows.allow_downgrades,
sign_command: config.windows.sign_command.map(custom_sign_settings),
minimum_webview2_version: config.windows.minimum_webview2_version,
},
license: config.license.or_else(|| {
settings
@@ -1676,7 +1604,7 @@ fn tauri_config_to_bundle_settings(
.unwrap()
})
}),
license_file: config.license_file.map(|l| tauri_dir.join(l)),
license_file: config.license_file.map(|l| tauri_dir().join(l)),
updater: updater_config,
..Default::default()
})
@@ -1813,7 +1741,7 @@ mod tests {
#[test]
fn parse_target_dir_from_opts() {
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
let current_dir = std::env::current_dir().unwrap();
let options = Options {
@@ -1830,11 +1758,11 @@ mod tests {
};
assert_eq!(
get_target_dir(None, &options, dirs.tauri).unwrap(),
get_target_dir(None, &options).unwrap(),
current_dir.join("path/to/some/dir/release")
);
assert_eq!(
get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri).unwrap(),
get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(),
current_dir
.join("path/to/some/dir")
.join("x86_64-pc-windows-msvc")
@@ -1853,27 +1781,23 @@ mod tests {
};
#[cfg(windows)]
assert!(
get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri)
.unwrap()
.ends_with("x86_64-pc-windows-msvc\\release")
);
assert!(get_target_dir(Some("x86_64-pc-windows-msvc"), &options)
.unwrap()
.ends_with("x86_64-pc-windows-msvc\\release"));
#[cfg(not(windows))]
assert!(
get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri)
.unwrap()
.ends_with("x86_64-pc-windows-msvc/release")
);
assert!(get_target_dir(Some("x86_64-pc-windows-msvc"), &options)
.unwrap()
.ends_with("x86_64-pc-windows-msvc/release"));
#[cfg(windows)]
{
std::env::set_var("CARGO_TARGET_DIR", "D:\\path\\to\\env\\dir");
assert_eq!(
get_target_dir(None, &options, dirs.tauri).unwrap(),
get_target_dir(None, &options).unwrap(),
PathBuf::from("D:\\path\\to\\env\\dir\\release")
);
assert_eq!(
get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri).unwrap(),
get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(),
PathBuf::from("D:\\path\\to\\env\\dir\\x86_64-pc-windows-msvc\\release")
);
}
@@ -1882,11 +1806,11 @@ mod tests {
{
std::env::set_var("CARGO_TARGET_DIR", "/path/to/env/dir");
assert_eq!(
get_target_dir(None, &options, dirs.tauri).unwrap(),
get_target_dir(None, &options).unwrap(),
PathBuf::from("/path/to/env/dir/release")
);
assert_eq!(
get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri).unwrap(),
get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(),
PathBuf::from("/path/to/env/dir/x86_64-pc-windows-msvc/release")
);
}

View File

@@ -12,7 +12,7 @@ use shared_child::SharedChild;
use std::{
fs,
io::{BufReader, ErrorKind, Write},
path::{Path, PathBuf},
path::PathBuf,
process::{Command, ExitStatus, Stdio},
sync::{
atomic::{AtomicBool, Ordering},
@@ -29,26 +29,30 @@ pub struct DevChild {
impl DevProcess for DevChild {
fn kill(&self) -> std::io::Result<()> {
self.dev_child.kill()?;
self.manually_killed_app.store(true, Ordering::SeqCst);
self.manually_killed_app.store(true, Ordering::Relaxed);
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::SeqCst)
self.manually_killed_app.load(Ordering::Relaxed)
}
}
pub fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
options: Options,
run_args: &[String],
run_args: Vec<String>,
available_targets: &mut Option<Vec<RustupTarget>>,
config_features: Vec<String>,
on_exit: F,
) -> crate::Result<DevChild> {
) -> crate::Result<impl DevProcess> {
let mut dev_cmd = cargo_command(true, options, available_targets, config_features)?;
let runner = dev_cmd.get_program().to_string_lossy().into_owned();
@@ -133,7 +137,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::SeqCst) {
} else if manually_killed_app_.load(Ordering::Relaxed) {
ExitReason::TriggeredKill
} else {
ExitReason::NormalExit
@@ -154,12 +158,11 @@ pub fn build(
available_targets: &mut Option<Vec<RustupTarget>>,
config_features: Vec<String>,
main_binary_name: Option<&str>,
tauri_dir: &Path,
) -> crate::Result<PathBuf> {
let out_dir = app_settings.out_dir(&options, tauri_dir)?;
let bin_path = app_settings.app_binary_path(&options, tauri_dir)?;
let out_dir = app_settings.out_dir(&options)?;
let bin_path = app_settings.app_binary_path(&options)?;
if !std::env::var_os("STATIC_VCRUNTIME").is_some_and(|v| v == "false") {
if !std::env::var("STATIC_VCRUNTIME").is_ok_and(|v| v == "false") {
std::env::set_var("STATIC_VCRUNTIME", "true");
}
@@ -179,7 +182,7 @@ pub fn build(
options.target.replace(triple.into());
let triple_out_dir = app_settings
.out_dir(&options, tauri_dir)
.out_dir(&options)
.with_context(|| format!("failed to get {triple} out dir"))?;
build_production_app(options, available_targets, config_features.clone())

View File

@@ -4,7 +4,10 @@
use crate::{
error::{Context, ErrorExt},
helpers::config::{Config, PatternKind},
helpers::{
app_paths::tauri_dir,
config::{Config, PatternKind},
},
};
use itertools::Itertools;
@@ -269,8 +272,8 @@ fn inject_features(
Ok(persist)
}
pub fn rewrite_manifest(config: &Config, tauri_dir: &Path) -> crate::Result<(Manifest, bool)> {
let manifest_path = tauri_dir.join("Cargo.toml");
pub fn rewrite_manifest(config: &Config) -> crate::Result<(Manifest, bool)> {
let manifest_path = tauri_dir().join("Cargo.toml");
let (mut manifest, original_manifest_str) = read_manifest(&manifest_path)?;
let mut dependencies = Vec::new();
@@ -351,7 +354,10 @@ mod tests {
} else {
None
};
if let Some(f) = item_table.and_then(|t| t.get("features")?.as_array().cloned()) {
if let Some(f) = item_table
.and_then(|t| t.get("features").cloned())
.and_then(|f| f.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) {

View File

@@ -2,31 +2,35 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{error::Context, helpers::app_paths::Dirs, Result};
use crate::{
error::Context,
helpers::app_paths::{frontend_dir, tauri_dir},
Result,
};
mod config;
mod frontend;
mod manifest;
pub fn run(dirs: &Dirs) -> Result<()> {
let mut migrated = config::migrate(dirs.tauri).context("Could not migrate config")?;
manifest::migrate(dirs.tauri).context("Could not migrate manifest")?;
let plugins = frontend::migrate(dirs.frontend)?;
pub fn run() -> Result<()> {
let tauri_dir = tauri_dir();
let frontend_dir = frontend_dir();
let mut migrated = config::migrate(tauri_dir).context("Could not migrate config")?;
manifest::migrate(tauri_dir).context("Could not migrate manifest")?;
let plugins = frontend::migrate(frontend_dir)?;
migrated.plugins.extend(plugins);
// Add plugins
for plugin in migrated.plugins {
crate::add::run(
crate::add::Options {
plugin: plugin.clone(),
branch: None,
tag: None,
rev: None,
no_fmt: false,
},
dirs,
)
crate::add::run(crate::add::Options {
plugin: plugin.clone(),
branch: None,
tag: None,
rev: None,
no_fmt: false,
})
.with_context(|| format!("Could not migrate plugin '{plugin}'"))?;
}

View File

@@ -4,7 +4,10 @@
use crate::{
error::{Context, ErrorExt},
helpers::{app_paths::Dirs, npm::PackageManager},
helpers::{
app_paths::{frontend_dir, tauri_dir},
npm::PackageManager,
},
interface::rust::manifest::{read_manifest, serialize_manifest},
Result,
};
@@ -13,14 +16,17 @@ use std::{fs::read_to_string, path::Path};
use toml_edit::{DocumentMut, Item, Table, TableLike, Value};
pub fn run(dirs: &Dirs) -> Result<()> {
let manifest_path = dirs.tauri.join("Cargo.toml");
pub fn run() -> Result<()> {
let frontend_dir = frontend_dir();
let tauri_dir = tauri_dir();
let manifest_path = tauri_dir.join("Cargo.toml");
let (mut manifest, _) = read_manifest(&manifest_path)?;
migrate_manifest(&mut manifest)?;
migrate_permissions(dirs.tauri)?;
migrate_permissions(tauri_dir)?;
migrate_npm_dependencies(dirs.frontend)?;
migrate_npm_dependencies(frontend_dir)?;
std::fs::write(&manifest_path, serialize_manifest(&manifest))
.fs_context("failed to rewrite Cargo manifest", &manifest_path)?;

View File

@@ -4,7 +4,10 @@
use crate::{
error::{bail, Context, ErrorExt},
helpers::cargo_manifest::{crate_version, CargoLock, CargoManifest},
helpers::{
app_paths::tauri_dir,
cargo_manifest::{crate_version, CargoLock, CargoManifest},
},
interface::rust::get_workspace_dir,
Result,
};
@@ -14,20 +17,22 @@ use std::{fs::read_to_string, str::FromStr};
mod migrations;
pub fn command() -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
let manifest_contents = read_to_string(dirs.tauri.join("Cargo.toml")).fs_context(
let tauri_dir = tauri_dir();
let manifest_contents = read_to_string(tauri_dir.join("Cargo.toml")).fs_context(
"failed to read Cargo manifest",
dirs.tauri.join("Cargo.toml"),
tauri_dir.join("Cargo.toml"),
)?;
let manifest = toml::from_str::<CargoManifest>(&manifest_contents).with_context(|| {
format!(
"failed to parse Cargo manifest {}",
dirs.tauri.join("Cargo.toml").display()
tauri_dir.join("Cargo.toml").display()
)
})?;
let workspace_dir = get_workspace_dir(dirs.tauri)?;
let workspace_dir = get_workspace_dir()?;
let lock_path = workspace_dir.join("Cargo.lock");
let lock = if lock_path.exists() {
let lockfile_contents =
@@ -39,19 +44,19 @@ pub fn command() -> Result<()> {
None
};
let tauri_version = crate_version(dirs.tauri, Some(&manifest), lock.as_ref(), "tauri")
let tauri_version = crate_version(tauri_dir, Some(&manifest), lock.as_ref(), "tauri")
.version
.context("failed to get tauri version")?;
let tauri_version = semver::Version::from_str(&tauri_version)
.with_context(|| format!("failed to parse tauri version {tauri_version}"))?;
if tauri_version.major == 1 {
migrations::v1::run(&dirs).context("failed to migrate from v1")?;
migrations::v1::run().context("failed to migrate from v1")?;
} else if tauri_version.major == 2 {
if let Some((pre, _number)) = tauri_version.pre.as_str().split_once('.') {
match pre {
"beta" => {
migrations::v2_beta::run(&dirs).context("failed to migrate from v2 beta")?;
migrations::v2_beta::run().context("failed to migrate from v2 beta")?;
}
"alpha" => {
bail!(

View File

@@ -5,8 +5,8 @@
use super::{detect_target_ok, 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,
helpers::config::{get as get_tauri_config, reload as reload_tauri_config},
interface::{AppInterface, Interface},
mobile::CliOptions,
Error, Result,
};
@@ -38,7 +38,7 @@ pub struct Options {
}
pub fn command(options: Options) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
let profile = if options.release {
Profile::Release
@@ -46,33 +46,45 @@ pub fn command(options: Options) -> Result<()> {
Profile::Debug
};
let mut tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[], dirs.tauri)?;
let cli_options = read_options(&tauri_config);
let (tauri_config, cli_options) = {
let tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[])?;
let cli_options = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
read_options(tauri_config_)
};
if !cli_options.config.is_empty() {
// reload config with merges from the android dev|build script
reload_tauri_config(
&mut tauri_config,
&cli_options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
dirs.tauri,
)?
let tauri_config = if cli_options.config.is_empty() {
tauri_config
} else {
// reload config with merges from the android dev|build script
reload_tauri_config(
&cli_options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
)?
};
(tauri_config, cli_options)
};
let (config, metadata) = get_config(
&get_app(
MobileTarget::Android,
&tauri_config,
&AppInterface::new(&tauri_config, None, dirs.tauri)?,
dirs.tauri,
),
&tauri_config,
&[],
&cli_options,
);
let (config, metadata) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let (config, metadata) = get_config(
&get_app(
MobileTarget::Android,
tauri_config_,
&AppInterface::new(tauri_config_, None)?,
),
tauri_config_,
&[],
&cli_options,
);
(config, metadata)
};
ensure_init(
&tauri_config,
@@ -83,8 +95,7 @@ pub fn command(options: Options) -> Result<()> {
)?;
if !cli_options.config.is_empty() {
crate::helpers::config::merge_config_with(
&mut tauri_config,
crate::helpers::config::merge_with(
&cli_options
.config
.iter()
@@ -96,7 +107,16 @@ pub fn command(options: Options) -> Result<()> {
let env = env(std::env::var("CI").is_ok())?;
if cli_options.dev {
if let Some(url) = &tauri_config.build.dev_url {
let dev_url = tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.dev_url
.clone();
if let Some(url) = dev_url {
let localhost = match url.host() {
Some(url::Host::Domain(d)) => d == "localhost",
Some(url::Host::Ipv4(i)) => i == std::net::Ipv4Addr::LOCALHOST,

View File

@@ -10,11 +10,11 @@ use crate::{
build::Options as BuildOptions,
error::Context,
helpers::{
app_paths::Dirs,
config::{get_config as get_tauri_config, ConfigMetadata},
app_paths::tauri_dir,
config::{get as get_tauri_config, ConfigHandle},
flock,
},
interface::{AppInterface, Options as InterfaceOptions},
interface::{AppInterface, Interface, Options as InterfaceOptions},
mobile::{android::generate_tauri_properties, write_options, CliOptions, TargetDevice},
ConfigValue, Error, Result,
};
@@ -27,7 +27,6 @@ use cargo_mobile2::{
};
use std::env::set_current_dir;
use std::path::Path;
#[derive(Debug, Clone, Parser)]
#[clap(
@@ -48,7 +47,7 @@ pub struct Options {
)]
pub targets: Option<Vec<String>>,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Vec<String>,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
@@ -64,12 +63,10 @@ pub struct Options {
pub split_per_abi: bool,
/// Build APKs.
#[clap(long)]
pub apk: bool,
pub apk: Option<bool>,
/// Build AABs.
#[clap(long)]
pub aab: bool,
#[clap(skip)]
pub skip_bundle: bool,
pub aab: Option<bool>,
/// Open Android Studio
#[clap(short, long)]
pub open: bool,
@@ -119,25 +116,8 @@ pub struct BuiltApplication {
}
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplication> {
let dirs = crate::helpers::app_paths::resolve_dirs();
let tauri_config = get_tauri_config(
tauri_utils::platform::Target::Android,
&options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
dirs.tauri,
)?;
run(options, noise_level, &dirs, &tauri_config)
}
crate::helpers::app_paths::resolve();
pub fn run(
options: Options,
noise_level: NoiseLevel,
dirs: &Dirs,
tauri_config: &ConfigMetadata,
) -> Result<BuiltApplication> {
delete_codegen_vars();
let mut build_options: BuildOptions = options.clone().into();
@@ -153,24 +133,30 @@ pub fn run(
.unwrap();
build_options.target = Some(first_target.triple.into());
let interface = AppInterface::new(tauri_config, build_options.target.clone(), dirs.tauri)?;
interface.build_options(&mut build_options.args, &mut build_options.features, true);
let tauri_config = get_tauri_config(
tauri_utils::platform::Target::Android,
&options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
)?;
let (interface, config, metadata) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let app = get_app(MobileTarget::Android, tauri_config, &interface, dirs.tauri);
let (config, metadata) = get_config(
&app,
tauri_config,
&build_options.features,
&CliOptions {
dev: false,
features: build_options.features.clone(),
args: build_options.args.clone(),
noise_level,
vars: Default::default(),
config: build_options.config.clone(),
target_device: None,
},
);
let interface = AppInterface::new(tauri_config_, build_options.target.clone())?;
interface.build_options(&mut Vec::new(), &mut build_options.features, true);
let app = get_app(MobileTarget::Android, tauri_config_, &interface);
let (config, metadata) = get_config(
&app,
tauri_config_,
&build_options.features,
&Default::default(),
);
(interface, config, metadata)
};
let profile = if options.debug {
Profile::Debug
@@ -178,10 +164,11 @@ pub fn run(
Profile::Release
};
set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?;
let tauri_path = tauri_dir();
set_current_dir(tauri_path).context("failed to set current directory to Tauri directory")?;
ensure_init(
tauri_config,
&tauri_config,
config.app(),
config.project_dir(),
MobileTarget::Android,
@@ -191,9 +178,18 @@ pub fn run(
let mut env = env(options.ci)?;
configure_cargo(&mut env, &config)?;
generate_tauri_properties(&config, tauri_config, false)?;
generate_tauri_properties(
&config,
tauri_config.lock().unwrap().as_ref().unwrap(),
false,
)?;
crate::build::setup(&interface, &mut build_options, tauri_config, dirs, true)?;
{
let config_guard = tauri_config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
crate::build::setup(&interface, &mut build_options, config_, true)?;
}
let installed_targets =
crate::interface::rust::installation::installed_targets().unwrap_or_default();
@@ -223,7 +219,6 @@ pub fn run(
&config,
&mut env,
noise_level,
dirs.tauri,
)?;
if open {
@@ -242,17 +237,16 @@ fn run_build(
interface: &AppInterface,
mut options: Options,
build_options: BuildOptions,
tauri_config: &ConfigMetadata,
tauri_config: ConfigHandle,
profile: Profile,
config: &AndroidConfig,
env: &mut Env,
noise_level: NoiseLevel,
tauri_dir: &Path,
) -> Result<OptionsHandle> {
if !(options.skip_bundle || options.apk || options.aab) {
if !(options.apk.is_some() || options.aab.is_some()) {
// if the user didn't specify the format to build, we'll do both
options.apk = true;
options.aab = true;
options.apk = Some(true);
options.aab = Some(true);
}
let interface_options = InterfaceOptions {
@@ -263,7 +257,7 @@ fn run_build(
};
let app_settings = interface.app_settings();
let out_dir = app_settings.out_dir(&interface_options, tauri_dir)?;
let out_dir = app_settings.out_dir(&interface_options)?;
let _lock = flock::open_rw(out_dir.join("lock").with_extension("android"), "Android")?;
let cli_options = CliOptions {
@@ -275,11 +269,11 @@ fn run_build(
config: build_options.config,
target_device: options.target_device.clone(),
};
let handle = write_options(tauri_config, cli_options)?;
let handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?;
inject_resources(config, tauri_config)?;
inject_resources(config, tauri_config.lock().unwrap().as_ref().unwrap())?;
let apk_outputs = if options.apk {
let apk_outputs = if options.apk.unwrap_or_default() {
apk::build(
config,
env,
@@ -293,7 +287,7 @@ fn run_build(
Vec::new()
};
let aab_outputs = if options.aab {
let aab_outputs = if options.aab.unwrap_or_default() {
aab::build(
config,
env,

View File

@@ -10,11 +10,11 @@ use crate::{
dev::Options as DevOptions,
error::{Context, ErrorExt},
helpers::{
app_paths::Dirs,
config::{get_config as get_tauri_config, ConfigMetadata},
app_paths::tauri_dir,
config::{get as get_tauri_config, ConfigHandle},
flock,
},
interface::{AppInterface, MobileOptions, Options as InterfaceOptions},
interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions},
mobile::{
android::generate_tauri_properties, use_network_address_for_dev_url, write_options, CliOptions,
DevChild, DevHost, DevProcess, TargetDevice,
@@ -44,7 +44,7 @@ use std::{env::set_current_dir, net::Ipv4Addr, path::PathBuf};
)]
pub struct Options {
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Vec<String>,
/// Exit on panic
#[clap(short, long)]
@@ -131,16 +131,16 @@ impl From<Options> for DevOptions {
}
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
let result = run_command(options, noise_level, dirs);
let result = run_command(options, noise_level);
if result.is_err() {
crate::dev::kill_before_dev_process();
}
result
}
fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<()> {
fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
delete_codegen_vars();
// setup env additions before calling env()
if let Some(root_certificate_path) = &options.root_certificate_path {
@@ -160,7 +160,6 @@ fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
dirs.tauri,
)?;
let env = env(false)?;
@@ -182,27 +181,25 @@ fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<
.map(|d| d.target().triple.to_string())
.unwrap_or_else(|| Target::all().values().next().unwrap().triple.into());
dev_options.target = Some(target_triple);
dev_options.args.push("--lib".into());
let interface = AppInterface::new(&tauri_config, dev_options.target.clone(), dirs.tauri)?;
let (interface, config, metadata) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let app = get_app(MobileTarget::Android, &tauri_config, &interface, dirs.tauri);
let (config, metadata) = get_config(
&app,
&tauri_config,
dev_options.features.as_ref(),
&CliOptions {
dev: true,
features: dev_options.features.clone(),
args: dev_options.args.clone(),
noise_level,
vars: Default::default(),
config: dev_options.config.clone(),
target_device: None,
},
);
let interface = AppInterface::new(tauri_config_, dev_options.target.clone())?;
set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?;
let app = get_app(MobileTarget::Android, tauri_config_, &interface);
let (config, metadata) = get_config(
&app,
tauri_config_,
dev_options.features.as_ref(),
&Default::default(),
);
(interface, config, metadata)
};
let tauri_path = tauri_dir();
set_current_dir(tauri_path).context("failed to set current directory to Tauri directory")?;
ensure_init(
&tauri_config,
@@ -221,7 +218,6 @@ fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<
&config,
&metadata,
noise_level,
&dirs,
)
}
@@ -230,13 +226,12 @@ fn run_dev(
mut interface: AppInterface,
options: Options,
mut dev_options: DevOptions,
mut tauri_config: ConfigMetadata,
tauri_config: ConfigHandle,
device: Option<Device>,
mut env: Env,
config: &AndroidConfig,
metadata: &AndroidMetadata,
noise_level: NoiseLevel,
dirs: &Dirs,
) -> Result<()> {
// when --host is provided or running on a physical device or resolving 0.0.0.0 we must use the network IP
if options.host.0.is_some()
@@ -244,22 +239,25 @@ fn run_dev(
.as_ref()
.map(|device| !device.serial_no().starts_with("emulator"))
.unwrap_or(false)
|| tauri_config.build.dev_url.as_ref().is_some_and(|url| {
matches!(
url.host(),
Some(Host::Ipv4(i)) if i == Ipv4Addr::UNSPECIFIED
)
})
|| tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.dev_url
.as_ref()
.is_some_and(|url| {
matches!(
url.host(),
Some(Host::Ipv4(i)) if i == Ipv4Addr::UNSPECIFIED
)
})
{
use_network_address_for_dev_url(
&mut tauri_config,
&mut dev_options,
options.force_ip_prompt,
dirs.tauri,
)?;
use_network_address_for_dev_url(&tauri_config, &mut dev_options, options.force_ip_prompt)?;
}
crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, dirs)?;
crate::dev::setup(&interface, &mut dev_options, tauri_config)?;
let interface_options = InterfaceOptions {
debug: !dev_options.release_mode,
@@ -268,12 +266,12 @@ fn run_dev(
};
let app_settings = interface.app_settings();
let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?;
let out_dir = app_settings.out_dir(&interface_options)?;
let _lock = flock::open_rw(out_dir.join("lock").with_extension("android"), "Android")?;
configure_cargo(&mut env, config)?;
generate_tauri_properties(config, &tauri_config, true)?;
generate_tauri_properties(config, tauri_config.lock().unwrap().as_ref().unwrap(), true)?;
let installed_targets =
crate::interface::rust::installation::installed_targets().unwrap_or_default();
@@ -309,7 +307,6 @@ fn run_dev(
let open = options.open;
interface.mobile_dev(
&mut tauri_config,
MobileOptions {
debug: !options.release_mode,
features: options.features,
@@ -318,7 +315,7 @@ fn run_dev(
no_watch: options.no_watch,
additional_watch_folders: options.additional_watch_folders,
},
|options, tauri_config| {
|options| {
let cli_options = CliOptions {
dev: true,
features: options.features.clone(),
@@ -332,9 +329,9 @@ fn run_dev(
}),
};
let _handle = write_options(tauri_config, cli_options)?;
let _handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?;
inject_resources(config, tauri_config)?;
inject_resources(config, tauri_config.lock().unwrap().as_ref().unwrap())?;
if open {
open_and_wait(config, &env)
@@ -350,7 +347,6 @@ fn run_dev(
open_and_wait(config, &env)
}
},
dirs,
)
}

View File

@@ -106,13 +106,16 @@ enum Commands {
pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
let noise_level = NoiseLevel::from_occurrences(verbosity as u64);
match cli.command {
Commands::Init(options) => init_command(
MobileTarget::Android,
options.ci,
false,
options.skip_targets_install,
options.config,
)?,
Commands::Init(options) => {
crate::helpers::app_paths::resolve();
init_command(
MobileTarget::Android,
options.ci,
false,
options.skip_targets_install,
options.config,
)?
}
Commands::Dev(options) => dev::command(options, noise_level)?,
Commands::Build(options) => build::command(options, noise_level).map(|_| ())?,
Commands::Run(options) => run::command(options, noise_level)?,

View File

@@ -13,8 +13,7 @@ use std::path::PathBuf;
use super::{configure_cargo, device_prompt, env};
use crate::{
error::Context,
helpers::config::ConfigMetadata,
interface::{DevProcess, WatcherOptions},
interface::{DevProcess, Interface, WatcherOptions},
mobile::{DevChild, TargetDevice},
ConfigValue, Result,
};
@@ -29,7 +28,7 @@ pub struct Options {
#[clap(short, long)]
pub release: bool,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Vec<String>,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
@@ -78,17 +77,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
}
};
let dirs = crate::helpers::app_paths::resolve_dirs();
let mut tauri_config = crate::helpers::config::get_config(
tauri_utils::platform::Target::Android,
&options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
dirs.tauri,
)?;
let mut built_application = super::build::run(
let mut built_application = super::build::command(
super::build::Options {
debug: !options.release,
targets: device.as_ref().map(|d| {
@@ -101,9 +90,8 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
features: options.features,
config: options.config.clone(),
split_per_abi: true,
apk: false,
aab: false,
skip_bundle: false,
apk: Some(false),
aab: Some(false),
open: options.open,
ci: false,
args: options.args,
@@ -114,8 +102,6 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
}),
},
noise_level,
&dirs,
&tauri_config,
)?;
configure_cargo(&mut env, &built_application.config)?;
@@ -125,7 +111,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 |_tauri_config: &ConfigMetadata| {
let runner = move || {
device
.run(
&config,
@@ -150,16 +136,14 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
};
if options.no_watch {
runner(&tauri_config)?;
runner()?;
} else {
built_application.interface.watch(
&mut tauri_config,
WatcherOptions {
config: options.config,
additional_watch_folders: options.additional_watch_folders,
},
runner,
&dirs,
)?;
}
}

View File

@@ -4,9 +4,8 @@
use super::{get_app, Target};
use crate::{
helpers::app_paths::Dirs,
helpers::{config::get_config as get_tauri_config, template::JsonMap},
interface::AppInterface,
helpers::{config::get as get_tauri_config, template::JsonMap},
interface::{AppInterface, Interface},
ConfigValue, Result,
};
use cargo_mobile2::{
@@ -30,7 +29,6 @@ pub fn command(
skip_targets_install: bool,
config: Vec<ConfigValue>,
) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
let wrapper = TextWrapper::default();
exec(
@@ -40,31 +38,30 @@ pub fn command(
reinstall_deps,
skip_targets_install,
config,
dirs,
)?;
Ok(())
}
fn exec(
pub 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,
) -> Result<App> {
let tauri_config = get_tauri_config(
target.platform_target(),
&config.iter().map(|conf| &conf.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let app = get_app(
target,
&tauri_config,
&AppInterface::new(&tauri_config, None, dirs.tauri)?,
dirs.tauri,
tauri_config_,
&AppInterface::new(tauri_config_, None)?,
);
let (handlebars, mut map) = handlebars(&app);
@@ -138,7 +135,7 @@ fn exec(
Target::Android => {
let _env = super::android::env(non_interactive)?;
let (config, metadata) =
super::android::get_config(&app, &tauri_config, &[], &Default::default());
super::android::get_config(&app, tauri_config_, &[], &Default::default());
map.insert("android", &config);
super::android::project::gen(
&config,
@@ -153,10 +150,10 @@ fn exec(
// Generate Xcode project
Target::Ios => {
let (config, metadata) =
super::ios::get_config(&app, &tauri_config, &[], &Default::default(), dirs.tauri)?;
super::ios::get_config(&app, tauri_config_, &[], &Default::default())?;
map.insert("apple", &config);
super::ios::project::gen(
&tauri_config,
tauri_config_,
&config,
&metadata,
(handlebars, map),

View File

@@ -11,12 +11,12 @@ use crate::{
build::Options as BuildOptions,
error::{Context, ErrorExt},
helpers::{
app_paths::Dirs,
config::{get_config as get_tauri_config, ConfigMetadata},
app_paths::tauri_dir,
config::{get as get_tauri_config, ConfigHandle},
flock,
plist::merge_plist,
},
interface::{AppInterface, Options as InterfaceOptions},
interface::{AppInterface, Interface, Options as InterfaceOptions},
mobile::{ios::ensure_ios_runtime_installed, write_options, CliOptions, TargetDevice},
ConfigValue, Error, Result,
};
@@ -59,7 +59,7 @@ pub struct Options {
)]
pub targets: Option<Vec<String>>,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Vec<String>,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
@@ -168,11 +168,8 @@ pub struct BuiltApplication {
}
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplication> {
let dirs = crate::helpers::app_paths::resolve_dirs();
run(options, noise_level, &dirs)
}
crate::helpers::app_paths::resolve();
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()
@@ -192,29 +189,26 @@ pub fn run(options: Options, noise_level: NoiseLevel, dirs: &Dirs) -> Result<Bui
let tauri_config = get_tauri_config(
tauri_utils::platform::Target::Ios,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
let interface = AppInterface::new(&tauri_config, build_options.target.clone(), dirs.tauri)?;
interface.build_options(&mut build_options.args, &mut build_options.features, true);
let (interface, mut config) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri);
let (mut config, _) = get_config(
&app,
&tauri_config,
&build_options.features,
&CliOptions {
dev: false,
features: build_options.features.clone(),
args: build_options.args.clone(),
noise_level,
vars: Default::default(),
config: build_options.config.clone(),
target_device: None,
},
dirs.tauri,
)?;
let interface = AppInterface::new(tauri_config_, build_options.target.clone())?;
interface.build_options(&mut Vec::new(), &mut build_options.features, true);
set_current_dir(dirs.tauri).context("failed to set current directory")?;
let app = get_app(MobileTarget::Ios, tauri_config_, &interface);
let (config, _metadata) = get_config(
&app,
tauri_config_,
&build_options.features,
&Default::default(),
)?;
(interface, config)
};
let tauri_path = tauri_dir();
set_current_dir(tauri_path).context("failed to set current directory")?;
ensure_init(
&tauri_config,
@@ -223,7 +217,7 @@ pub fn run(options: Options, noise_level: NoiseLevel, dirs: &Dirs) -> Result<Bui
MobileTarget::Ios,
options.ci,
)?;
inject_resources(&config, &tauri_config)?;
inject_resources(&config, tauri_config.lock().unwrap().as_ref().unwrap())?;
let mut plist = plist::Dictionary::new();
plist.insert(
@@ -237,13 +231,21 @@ pub fn run(options: Options, noise_level: NoiseLevel, dirs: &Dirs) -> Result<Bui
.join("Info.plist");
let mut src_plists = vec![info_plist_path.clone().into()];
src_plists.push(plist::Value::Dictionary(plist).into());
if dirs.tauri.join("Info.plist").exists() {
src_plists.push(dirs.tauri.join("Info.plist").into());
if tauri_path.join("Info.plist").exists() {
src_plists.push(tauri_path.join("Info.plist").into());
}
if dirs.tauri.join("Info.ios.plist").exists() {
src_plists.push(dirs.tauri.join("Info.ios.plist").into());
if tauri_path.join("Info.ios.plist").exists() {
src_plists.push(tauri_path.join("Info.ios.plist").into());
}
if let Some(info_plist) = &tauri_config.bundle.ios.info_plist {
if let Some(info_plist) = &tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.bundle
.ios
.info_plist
{
src_plists.push(info_plist.clone().into());
}
let merged_info_plist = merge_plist(src_plists)?;
@@ -336,7 +338,6 @@ pub fn run(options: Options, noise_level: NoiseLevel, dirs: &Dirs) -> Result<Bui
&mut config,
&mut env,
noise_level,
dirs,
)?;
if open {
@@ -355,11 +356,10 @@ fn run_build(
interface: &AppInterface,
options: Options,
mut build_options: BuildOptions,
tauri_config: ConfigMetadata,
tauri_config: ConfigHandle,
config: &mut AppleConfig,
env: &mut Env,
noise_level: NoiseLevel,
dirs: &Dirs,
) -> Result<OptionsHandle> {
let profile = if options.debug {
Profile::Debug
@@ -367,18 +367,20 @@ fn run_build(
Profile::Release
};
crate::build::setup(interface, &mut build_options, &tauri_config, dirs, true)?;
crate::build::setup(
interface,
&mut build_options,
tauri_config.lock().unwrap().as_ref().unwrap(),
true,
)?;
let app_settings = interface.app_settings();
let out_dir = app_settings.out_dir(
&InterfaceOptions {
debug: build_options.debug,
target: build_options.target.clone(),
args: build_options.args.clone(),
..Default::default()
},
dirs.tauri,
)?;
let out_dir = app_settings.out_dir(&InterfaceOptions {
debug: build_options.debug,
target: build_options.target.clone(),
args: build_options.args.clone(),
..Default::default()
})?;
let _lock = flock::open_rw(out_dir.join("lock").with_extension("ios"), "iOS")?;
let cli_options = CliOptions {
@@ -390,7 +392,7 @@ fn run_build(
config: build_options.config.clone(),
target_device: options.target_device.clone(),
};
let handle = write_options(&tauri_config, cli_options)?;
let handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?;
if options.open {
return Ok(handle);

View File

@@ -10,12 +10,12 @@ use crate::{
dev::Options as DevOptions,
error::{Context, ErrorExt},
helpers::{
app_paths::Dirs,
config::{get_config as get_tauri_config, ConfigMetadata},
app_paths::tauri_dir,
config::{get as get_tauri_config, ConfigHandle},
flock,
plist::merge_plist,
},
interface::{AppInterface, MobileOptions, Options as InterfaceOptions},
interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions},
mobile::{
ios::ensure_ios_runtime_installed, use_network_address_for_dev_url, write_options, CliOptions,
DevChild, DevHost, DevProcess,
@@ -53,7 +53,7 @@ environment variable to determine whether the public network should be used or n
)]
pub struct Options {
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Vec<String>,
/// Exit on panic
#[clap(short, long)]
@@ -138,16 +138,16 @@ impl From<Options> for DevOptions {
}
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
let result = run_command(options, noise_level, dirs);
let result = run_command(options, noise_level);
if result.is_err() {
crate::dev::kill_before_dev_process();
}
result
}
fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<()> {
fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
// setup env additions before calling env()
if let Some(root_certificate_path) = &options.root_certificate_path {
std::env::set_var(
@@ -182,33 +182,30 @@ fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<
.map(|d| d.target().triple.to_string())
.unwrap_or_else(|| "aarch64-apple-ios".into());
dev_options.target = Some(target_triple.clone());
dev_options.args.push("--lib".into());
let tauri_config = get_tauri_config(
tauri_utils::platform::Target::Ios,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
let interface = AppInterface::new(&tauri_config, Some(target_triple), dirs.tauri)?;
let (interface, config) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri);
let (config, _) = get_config(
&app,
&tauri_config,
&dev_options.features,
&CliOptions {
dev: true,
features: dev_options.features.clone(),
args: dev_options.args.clone(),
noise_level,
vars: Default::default(),
config: dev_options.config.clone(),
target_device: None,
},
dirs.tauri,
)?;
let interface = AppInterface::new(tauri_config_, Some(target_triple))?;
set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?;
let app = get_app(MobileTarget::Ios, tauri_config_, &interface);
let (config, _metadata) = get_config(
&app,
tauri_config_,
&dev_options.features,
&Default::default(),
)?;
(interface, config)
};
let tauri_path = tauri_dir();
set_current_dir(tauri_path).context("failed to set current directory to Tauri directory")?;
ensure_init(
&tauri_config,
@@ -217,20 +214,28 @@ fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<
MobileTarget::Ios,
false,
)?;
inject_resources(&config, &tauri_config)?;
inject_resources(&config, tauri_config.lock().unwrap().as_ref().unwrap())?;
let info_plist_path = config
.project_dir()
.join(config.scheme())
.join("Info.plist");
let mut src_plists = vec![info_plist_path.clone().into()];
if dirs.tauri.join("Info.plist").exists() {
src_plists.push(dirs.tauri.join("Info.plist").into());
if tauri_path.join("Info.plist").exists() {
src_plists.push(tauri_path.join("Info.plist").into());
}
if dirs.tauri.join("Info.ios.plist").exists() {
src_plists.push(dirs.tauri.join("Info.ios.plist").into());
if tauri_path.join("Info.ios.plist").exists() {
src_plists.push(tauri_path.join("Info.ios.plist").into());
}
if let Some(info_plist) = &tauri_config.bundle.ios.info_plist {
if let Some(info_plist) = &tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.bundle
.ios
.info_plist
{
src_plists.push(info_plist.clone().into());
}
let merged_info_plist = merge_plist(src_plists)?;
@@ -269,7 +274,6 @@ fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<
env,
&config,
noise_level,
&dirs,
)
}
@@ -278,12 +282,11 @@ fn run_dev(
mut interface: AppInterface,
options: Options,
mut dev_options: DevOptions,
mut tauri_config: ConfigMetadata,
tauri_config: ConfigHandle,
device: Option<Device>,
env: Env,
config: &AppleConfig,
noise_level: NoiseLevel,
dirs: &Dirs,
) -> Result<()> {
// when --host is provided or running on a physical device or resolving 0.0.0.0 we must use the network IP
if options.host.0.is_some()
@@ -291,39 +294,38 @@ fn run_dev(
.as_ref()
.map(|device| !matches!(device.kind(), DeviceKind::Simulator))
.unwrap_or(false)
|| tauri_config.build.dev_url.as_ref().is_some_and(|url| {
matches!(
|| tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.dev_url
.as_ref()
.is_some_and(|url| {
matches!(
url.host(),
Some(Host::Ipv4(i)) if i == Ipv4Addr::UNSPECIFIED
)
})
)
})
{
use_network_address_for_dev_url(
&mut tauri_config,
&mut dev_options,
options.force_ip_prompt,
dirs.tauri,
)?;
use_network_address_for_dev_url(&tauri_config, &mut dev_options, options.force_ip_prompt)?;
}
crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, dirs)?;
crate::dev::setup(&interface, &mut dev_options, tauri_config.clone())?;
let app_settings = interface.app_settings();
let out_dir = app_settings.out_dir(
&InterfaceOptions {
debug: !dev_options.release_mode,
target: dev_options.target.clone(),
..Default::default()
},
dirs.tauri,
)?;
let out_dir = app_settings.out_dir(&InterfaceOptions {
debug: !dev_options.release_mode,
target: dev_options.target.clone(),
..Default::default()
})?;
let _lock = flock::open_rw(out_dir.join("lock").with_extension("ios"), "iOS")?;
let set_host = options.host.0.is_some();
let open = options.open;
interface.mobile_dev(
&mut tauri_config,
MobileOptions {
debug: true,
features: options.features,
@@ -332,7 +334,7 @@ fn run_dev(
no_watch: options.no_watch,
additional_watch_folders: options.additional_watch_folders,
},
|options, tauri_config| {
|options| {
let cli_options = CliOptions {
dev: true,
features: options.features.clone(),
@@ -342,7 +344,7 @@ fn run_dev(
config: dev_options.config.clone(),
target_device: None,
};
let _handle = write_options(tauri_config, cli_options)?;
let _handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?;
let open_xcode = || {
if !set_host {
@@ -369,7 +371,6 @@ fn run_dev(
open_xcode()
}
},
dirs,
)
}

View File

@@ -30,7 +30,8 @@ use super::{
use crate::{
error::{Context, ErrorExt},
helpers::{
config::{BundleResources, Config as TauriConfig, ConfigMetadata},
app_paths::tauri_dir,
config::{BundleResources, Config as TauriConfig, ConfigHandle},
pbxproj, strip_semver_prerelease_tag,
},
ConfigValue, Error, Result,
@@ -103,13 +104,16 @@ enum Commands {
pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
let noise_level = NoiseLevel::from_occurrences(verbosity as u64);
match cli.command {
Commands::Init(options) => init_command(
MobileTarget::Ios,
options.ci,
options.reinstall_deps,
options.skip_targets_install,
options.config,
)?,
Commands::Init(options) => {
crate::helpers::app_paths::resolve();
init_command(
MobileTarget::Ios,
options.ci,
options.reinstall_deps,
options.skip_targets_install,
options.config,
)?
}
Commands::Dev(options) => dev::command(options, noise_level)?,
Commands::Build(options) => build::command(options, noise_level).map(|_| ())?,
Commands::Run(options) => run::command(options, noise_level)?,
@@ -124,7 +128,6 @@ pub fn get_config(
tauri_config: &TauriConfig,
features: &[String],
cli_options: &CliOptions,
tauri_dir: &Path,
) -> Result<(AppleConfig, AppleMetadata)> {
let mut ios_options = cli_options.clone();
ios_options.features.extend_from_slice(features);
@@ -233,6 +236,8 @@ pub fn get_config(
let config = AppleConfig::from_raw(app.clone(), Some(raw))
.context("failed to create Apple configuration")?;
let tauri_dir = tauri_dir();
let mut vendor_frameworks = Vec::new();
let mut frameworks = Vec::new();
for framework in tauri_config
@@ -267,7 +272,11 @@ pub fn get_config(
supported: true,
ios: ApplePlatform {
cargo_args: Some(ios_options.args),
features: Some(ios_options.features),
features: if ios_options.features.is_empty() {
None
} else {
Some(ios_options.features)
},
frameworks: Some(frameworks),
vendor_frameworks: Some(vendor_frameworks),
..Default::default()
@@ -544,14 +553,26 @@ pub fn load_pbxproj(config: &AppleConfig) -> Result<pbxproj::Pbxproj> {
pub fn synchronize_project_config(
config: &AppleConfig,
tauri_config: &ConfigMetadata,
tauri_config: &ConfigHandle,
pbxproj: &mut pbxproj::Pbxproj,
export_options_plist: &mut plist::Dictionary,
project_config: &ProjectConfig,
debug: bool,
) -> Result<()> {
let identifier = tauri_config.identifier.clone();
let product_name = tauri_config.product_name.clone();
let identifier = tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.identifier
.clone();
let product_name = tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.product_name
.clone();
let manual_signing = project_config.code_sign_identity.is_some()
|| project_config.provisioning_profile_uuid.is_some();

View File

@@ -10,8 +10,7 @@ use clap::{ArgAction, Parser};
use super::{device_prompt, env};
use crate::{
error::Context,
helpers::config::{get_config as get_tauri_config, ConfigMetadata},
interface::{DevProcess, WatcherOptions},
interface::{DevProcess, Interface, WatcherOptions},
mobile::{DevChild, TargetDevice},
ConfigValue, Result,
};
@@ -26,7 +25,7 @@ pub struct Options {
#[clap(short, long)]
pub release: bool,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Vec<String>,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
@@ -74,9 +73,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
}
};
let dirs = crate::helpers::app_paths::resolve_dirs();
let mut built_application = super::build::run(
let mut built_application = super::build::command(
super::build::Options {
debug: !options.release,
targets: Some(vec![]), /* skips IPA build since there's no target */
@@ -94,19 +91,12 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
}),
},
noise_level,
&dirs,
)?;
let mut tauri_config = get_tauri_config(
tauri_utils::platform::Target::Ios,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
// 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 |_tauri_config: &ConfigMetadata| {
let runner = move || {
device
.run(
&built_application.config,
@@ -124,16 +114,14 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
};
if options.no_watch {
runner(&tauri_config)?;
runner()?;
} else {
built_application.interface.watch(
&mut tauri_config,
WatcherOptions {
config: options.config,
additional_watch_folders: options.additional_watch_folders,
},
runner,
&dirs,
)?;
}
}

View File

@@ -5,8 +5,8 @@
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, Options as InterfaceOptions},
helpers::config::{get as get_tauri_config, reload as reload_tauri_config},
interface::{AppInterface, Interface, Options as InterfaceOptions},
mobile::ios::LIB_OUTPUT_FILE_NAME,
Error, Result,
};
@@ -89,39 +89,50 @@ pub fn command(options: Options) -> Result<()> {
.unwrap();
}
let dirs = crate::helpers::app_paths::resolve_dirs();
crate::helpers::app_paths::resolve();
let profile = profile_from_configuration(&options.configuration);
let macos = macos_from_platform(&options.platform);
let mut tauri_config = get_tauri_config(tauri_utils::platform::Target::Ios, &[], dirs.tauri)?;
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,
)?
let (tauri_config, cli_options) = {
let tauri_config = get_tauri_config(tauri_utils::platform::Target::Ios, &[])?;
let cli_options = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
read_options(tauri_config_)
};
let tauri_config = if cli_options.config.is_empty() {
tauri_config
} else {
// reload config with merges from the ios dev|build script
reload_tauri_config(
&cli_options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
)?
};
(tauri_config, cli_options)
};
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,
)?;
let (config, metadata) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let cli_options = read_options(tauri_config_);
let (config, metadata) = get_config(
&get_app(
MobileTarget::Ios,
tauri_config_,
&AppInterface::new(tauri_config_, None)?,
),
tauri_config_,
&[],
&cli_options,
)?;
(config, metadata)
};
ensure_init(
&tauri_config,
config.app(),
@@ -131,8 +142,7 @@ pub fn command(options: Options) -> Result<()> {
)?;
if !cli_options.config.is_empty() {
crate::helpers::config::merge_config_with(
&mut tauri_config,
crate::helpers::config::merge_with(
&cli_options
.config
.iter()
@@ -226,7 +236,10 @@ pub fn command(options: Options) -> Result<()> {
}
};
let interface = AppInterface::new(&tauri_config, Some(rust_triple.into()), dirs.tauri)?;
let interface = AppInterface::new(
tauri_config.lock().unwrap().as_ref().unwrap(),
Some(rust_triple.into()),
)?;
let cflags = format!("CFLAGS_{env_triple}");
let cxxflags = format!("CFLAGS_{env_triple}");
@@ -267,14 +280,11 @@ pub fn command(options: Options) -> Result<()> {
)
.context("failed to compile iOS app")?;
let out_dir = interface.app_settings().out_dir(
&InterfaceOptions {
debug: matches!(profile, Profile::Debug),
target: Some(rust_triple.into()),
..Default::default()
},
dirs.tauri,
)?;
let out_dir = interface.app_settings().out_dir(&InterfaceOptions {
debug: matches!(profile, Profile::Debug),
target: Some(rust_triple.into()),
..Default::default()
})?;
let lib_path = out_dir.join(format!("lib{}.a", config.app().lib_name()));
if !lib_path.exists() {

Some files were not shown because too many files have changed in this diff Show More