Compare commits

...

120 Commits

Author SHA1 Message Date
renovate[bot]
7d01aa0417
chore(deps): update dependency rollup to v4.57.1 (#14868)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-05 23:16:42 +08:00
Tony
7be58a1c64
chore(bundler): bring back binary patching log (#14894)
* chore(bundler): bring back binary patching log

* Fix change tag
2026-02-05 17:59:16 +08:00
dependabot[bot]
06374a902a
chore(deps): bump bytes from 1.9.0 to 1.11.1 (#14890)
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.9.0 to 1.11.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.9.0...v1.11.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.11.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-04 23:06:29 +08:00
github-actions[bot]
c37368f339
apply version updates (#14884)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-03 11:54:01 -03:00
goosewobbler
06f911aaff
fix: don't inherit stdout from parent (#14871) 2026-02-03 11:20:42 -03:00
Lucas Fernandes Nogueira
eb5d88427a
fix(codegen): Context generation with custom assets (#14883)
when custom assets are provided (`tauri::generate_context!(assets = my_assets)`) we can't use the fn inner logic directly and capture variables - we must pass them as arguments
2026-02-03 11:01:12 -03:00
FabianLars
540c5b4e59
chore(deps): update wrangler for undici update 2026-02-03 00:56:28 +01:00
FabianLars
5dbb37bab1
chore(api.js): Re-release 2.10.0 as 2.10.1 to fix npm package 2026-02-03 00:32:07 +01:00
FabianLars
19ded696de
apply version updates 2026-02-02 23:05:28 +01:00
Fabian-Lars
08558b8ba4
chore(bundler): update gtk3 docs links in code comments (#14872) 2026-02-02 20:15:28 +01:00
Fabian-Lars
ce8fddb464
chore(deps): unlock webkit2gtk patch version (#14873) 2026-02-02 20:15:02 +01:00
Lucas Fernandes Nogueira
517b81e970
chore(api): release 2.10 (#14876)
api, CLI and tauri crates must be in sync
2026-02-02 16:09:22 -03:00
Lucas Fernandes Nogueira
cd68b03ee5
feat(ci): use trusted publishers for NPM publishing (#14874)
* feat(ci): use trusted publishers for NPM publishing

* bump npm version

* update npm

* use empty NODE_AUTH_TOKEN

* entire workflow permissions
2026-02-02 16:09:01 -03:00
github-actions[bot]
8d67af37b6
apply version updates (#14639)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-02 09:15:37 -03:00
Tunglies
9f0306fbcc
refactor: rewrite some &String to &str (#14857)
* perf(tauri-build): refactor find_icon to use &str to remove unnecessary clones

* refactor(perf-tauri-build): remove obsolete changelog for find_icon refactor

* refactor(tauri-build): inline find_icon logic to simplify window icon path retrieval

* refactor(context): update find_icon predicate to use &str

* refactor(context): simplify predicate in find_icon to accept &&String

* Update crates/tauri-build/src/lib.rs

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

---------

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
2026-01-31 21:48:00 +08:00
renovate[bot]
f7c083cd41
chore(deps): update dependency rollup to v4.57.0 (#14820) 2026-01-31 20:32:55 +08:00
sftse
32576120fd
Fix busy loop (#14839)
* refactor(tauri-cli):  remove unneeded Arc<Mutex>

* fix(tauri-cli): remove busy-looping
2026-01-29 11:13:03 +08:00
sftse
e3fdcb5002
refactor tauri-cli (#14836)
* refactor(tauri-cli): use OsString where possible

* refactor(tauri-cli): remove needless scoping blocks

* refactor(tauri-cli): make return type concrete

* refactor(tauri-cli): use ?

* refactor(tauri-cli): coerce later to trait object

* refactor(tauri-cli): remove clone

* refactor(tauri-cli): make better use of static OnceLock

* fix(tauri-cli): upgrade atomics to SeqCst

* Add change file

* Update .changes/change-pr-14836.md

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
2026-01-29 10:39:00 +08:00
sftse
d453e2e06a
refactor(tauri-cli): remove trait implemented only once (#14840) 2026-01-29 10:35:55 +08:00
Fabian-Lars
20b99f9281
refactor: split appimage bundler in multiple files to support new backends (#14841) 2026-01-28 20:27:51 +01:00
sftse
3a4e165b6f
Less statics fixup (#14833)
* fix(tauri-cli): be more conservative to preserve behavior (#14804)

* refactor(tauri-cli): move app path initialization into commands
2026-01-27 16:33:11 +08:00
Fabian-Lars
efc4c26ebc
chore: fix clippy lints (#14834) 2026-01-26 17:13:08 +01:00
Tony
7fca58230f
chore(deps): update nsis_tauri_utils to 0.5.3 (#14830) 2026-01-26 17:55:27 +08:00
Kf637
c769f211fc
feat(nsis): add Norwegian language support for installer (#14824)
* feat(nsis): add Norwegian language support for installer

* feat(nsis): add Norwegian language support for installer strings

* Add change file
2026-01-25 16:30:50 +08:00
Oscar Beaumont
4d5d78daf6
fix(specta): don't use #[specta(rename = ...)] with tauri::ipc::Channel (#14812) 2026-01-24 11:00:40 +01:00
renovate[bot]
4794a6ba22
chore(deps): update dependency rollup to v4.55.2 (#14808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 14:09:19 +01:00
dependabot[bot]
09a4e7f55a
chore(deps-dev): bump wrangler from 4.20.3 to 4.59.1 (#14806)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-22 14:06:29 +01:00
Fabian-Lars
c862a0bd8c
fix(core): update error wording for invalid version field (#14800)
* fix(core): update error wording for invalid version field

fixes #14799

* fmt
2026-01-21 10:42:15 +08:00
Quentin Goinaud
f82594410c
feat(cli): allow electron to start tauri (#13253)
Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app>
2026-01-20 22:19:05 +01:00
Ishita Singh
853ed4642f
fix(android): improve error handling for external storage file access (#14442)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2026-01-20 21:52:11 +01:00
Tony
53611c4d7b
fix(cli): only watch dependent workspace members (#14747)
* fix(cli): only watch dependent workspace members

* Use manifest path instead of `workspace_default_members`

* Add change file

* Merge remote-tracking branch 'upstream/dev' into only-watch-dependencies

* `bug` not `fix`

* Merge branch 'dev' into only-watch-dependencies

* Remove `CargoMetadataExpended`

* Merge remote-tracking branch 'upstream/dev' into only-watch-dependencies

* Remove top level `.taurignore`

* Load ignore files from workspace root
2026-01-20 17:52:34 +08:00
Lucas Fernandes Nogueira
62aa13a124
fix(cli): Android build --apk and --aab flags requiring a value (#14629)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2026-01-19 20:32:43 +01:00
Luke
e919a760ed
feat(webview-window): add set_simple_fullscreen to WebviewWindow (#14619)
* feat(webview): add set_simple_fullscreen to WebviewWindow

* add changes

* Combine per platform fn to one

---------

Co-authored-by: Tony <legendmastertony@gmail.com>
2026-01-19 11:38:37 +08:00
kandrelczyk
0575dd287e
fix(bundler): patch bundle type via string replacement (#14521)
Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
Co-authored-by: FabianLars <github@fabianlars.de>
2026-01-18 23:51:02 +01:00
Lucas Fernandes Nogueira
eccff97588
fix(cli): possibly empty associated-domains entitlement (#14779) 2026-01-18 19:01:36 +01:00
Tony
08e35fcda0
refactor(cli): remove mutex on config (#14791)
* refactor(cli): remove mutex on config

* Fix ios

* Clippy

* Fix ios

* Unused import

* Fix ios closure and clippy

* Import `ConfigMetadata`

* Remove life time tags
2026-01-18 21:09:36 +08:00
Tony
10a8066db3
refactor(cli): reorder a few parameters (#14792) 2026-01-18 20:57:36 +08:00
Tony
ea31b07f19
fix(cli): inspect's description (#14789) 2026-01-17 19:23:12 +02:00
sftse
7f7d9aac21
Less statics (#14668)
* refactor(tauri-cli): introduce replacement functions

* refactor(tauri-cli): apply replacement to remove.rs

* refactor(tauri-cli): apply replacement to icon.rs

* refactor(tauri-cli): apply replacement to bundle.rs

* refactor(tauri-cli): apply replacement to build.rs

* refactor(tauri-cli): apply replacement to add.rs

* refactor(tauri-cli): apply replacement to dev.rs

* refactor(tauri-cli): less controlflow

* refactor(tauri-cli): split config loading from locking static

* refactor(tauri-cli): remove duplicate checks covered by if let Some(tauri_dir) = tauri_dir

tauri_dir.is_some() must be true, otherwise the entire block is not run, so the frontend_dir check
is irrelevant

* fmt

* refactor(tauri-cli): apply replacement to inspect.rs

* refactor(tauri-cli): dont use statics for config

* refactor(tauri-cli): finish threading directory paths through functions

* fix: clippy

* fixup CI

* refactor(tauri-cli): dont need mutex

* refactor(tauri-cli): rescope mutex use to minimal necessary

* fix CI, reduce mutex use

* fixup PR #14607

* fix: clippy

* refactor(tauri-cli): remove ConfigHandle

* refactor(tauri-cli): remove more unwraps and panicing functions

* refactor(tauri-cli): less mutexes

* refactor(tauri-cli): undo mistaken change, address review comment

* Split android build to 2 functions

* Pass in dirs to migration v1 like the v2 beta

* Add change file

---------

Co-authored-by: Tony <legendmastertony@gmail.com>
2026-01-17 23:52:42 +08:00
yy
7873c4a1c6
docs: fix typos in comments (#14787) 2026-01-17 12:30:14 +01:00
Fabian-Lars
123d63a0c1
chore: change webkit2gtk bump to minor 2026-01-15 17:00:58 +01:00
Fabian-Lars
75057c7c08
chore(deps): update wry to 0.54 and webkit2gtk-rs to 2.0.2 (#14778) 2026-01-15 14:48:45 +01:00
sftse
268bb339f0
build(tauri-macos-sign): remove once-cell-regex (#14766)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2026-01-15 13:14:30 +01:00
renovate[bot]
07788af13f
chore(deps): update rust crate signal-hook-tokio to 0.4 (#14729)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FabianLars <github@fabianlars.de>
2026-01-15 12:53:18 +01:00
renovate[bot]
9a53c84ec0
chore(deps): update dependency globals to v17 (#14730)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 12:28:04 +01:00
renovate[bot]
137576e8a4
chore(deps): update dependency rollup to v4.55.1 (#14746)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 11:51:21 +01:00
Wuelfhis Asuaje
1b0e335d3f
Fix: Updater signer failed signing file without extension (#14713)
* fixing bug where updater signer failed signing file withou extension

* removing  unnecessary unwrap()

* Adding change file. Removing commnent. Adding TODO.

* Apply suggestions from code review

---------

Co-authored-by: Wuelfhis Asuaje <wasuaje@shorecg.com>
Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
2026-01-14 09:09:45 +08:00
Amr Bashir
84b04c4a8d
fix: fix leftover inconsistent env var in tauri signer sign command (#14759) 2026-01-11 20:37:52 +02:00
Tony
897529d7a2
fix: map rustls-tls to reqwest/rustls-no-provider (#14726)
Co-authored-by: FabianLars <github@fabianlars.de>
2026-01-08 15:14:51 +01:00
dependabot[bot]
3d102e0c13
chore(deps): bump rsa from 0.9.7 to 0.9.10 (#14738)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-06 19:26:47 +01:00
Fabian-Lars
fea4d02403
chore(deps): update rkyv, closes #14734 (#14736) 2026-01-06 11:16:00 +01:00
Tony
a03219ca19
refactor(cli): disable jsonschema resolving external resources (#14725)
* refactor(cli): disable jsonschema resolving external resources

* Move `CONFIG_SCHEMA_VALIDATOR` to fn

* Format

* Update ureq to fix compile on linux

* New clippy warnings
2026-01-03 19:30:42 +08:00
renovate[bot]
b75ea5bead
chore(deps): update rust crate reqwest to 0.13 (#14724)
* chore(deps): update rust crate reqwest to 0.13

* Fix feature name

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Tony <legendmastertony@gmail.com>
2026-01-03 12:34:59 +08:00
Fabian-Lars
dcd1a65889
chore: fix tests (#14720)
* chore: fix tests

* windows
2026-01-02 16:02:23 +01:00
Camilla F
9b242e40c8
fix: BSD support in tauri-runtime (#14700)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-12-29 17:29:07 +01:00
Bruno Verachten
1dbf6fd067
feat(cli): add RISC-V 64-bit pre-built binary support (#14685)
* feat(cli): add RISC-V 64-bit pre-built binary support

Add riscv64gc-unknown-linux-gnu target to the tauri-cli release workflow,
enabling pre-built binaries for RISC-V 64-bit Linux systems.

This eliminates the multi-hour QEMU compilation time that currently blocks
RISC-V adoption of Tauri apps. Native compilation on RISC-V hardware
takes ~63 minutes.

Changes:
- Add RISC-V entry to build matrix with self-hosted runner support
- Support custom `runs_on` field for matrix entries (falls back to `os`)
- Skip dtolnay/rust-toolchain and rust-cache for self-hosted runners
- Source ~/.cargo/env for self-hosted runners where Rust is pre-installed

Tested on:
- Hardware: Banana Pi F3 (RISC-V64, 16GB RAM)
- OS: Debian Trixie (required for WebKit2GTK RISC-V support)
- Build time: 1h 2m 28s
- Binary: ELF 64-bit RISC-V, 16MB stripped

* feat(cli): use cross for RISC-V cross-compilation

Switch from self-hosted runners to cross-rs for building RISC-V binaries.
This approach is simpler and doesn't require maintaining self-hosted infrastructure.

Local testing confirms cross builds a valid RISC-V binary in ~4 minutes.

* refactor(cli): address review feedback for RISC-V workflow

- Skip Rust toolchain and cache setup for cross builds (unnecessary)
- Pin cross version to 0.2.5 for reproducibility
- Fix Linux dependencies condition to match ubuntu-* variants
2025-12-29 10:21:49 -03:00
Tony
8a43e4f9d9
refactor: use u64 instead of usize for nonce gen (#14708) 2025-12-29 08:43:09 -03:00
sftse
a2abe2e6bc
refactor(cli): simplify features: Option<Vec<String>> to Vec<String> (#14607)
* refactor: use empty vector for features instead of None

* refactor: reorder

* add change file

* comment: highlight places where serialization is used

* refactor: simplify serialization

* Update .changes/empty-vec-instead-of-none.md

* Update crates/tauri-cli/src/mobile/ios/mod.rs

---------

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
2025-12-29 13:54:51 +08:00
Tony
51f0fcb69c
docs: pixel units (#14702) 2025-12-28 10:40:03 +08:00
Tony
0650852d14
docs: things related to WebviewUrl (#14692)
* Typos

* Rename to `handler`/`protocol_handler`

* Fix the `AssetResolver::get` fallback docs

* Refactor and update the docs for `get_url`

* Rename the remaining ones to `get_app_url`

* Apply suggestions from code review

Co-authored-by: Fabian-Lars <github@fabianlars.de>

* Generate schema
2025-12-25 20:05:02 +08:00
Kushal Meghani
c1d82eb3a3
fix(linux): reuse WebContext to prevent WebKitNetworkProcess leak (#14628)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-12-25 13:03:15 +01:00
renovate[bot]
51a0d6d66d
chore(deps): update dependency rollup to v4.54.0 (#14688)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-23 22:49:57 +08:00
renovate[bot]
7f48ee9068
chore(deps): update rust crate toml_edit to 0.24 (#14683)
* chore(deps): update rust crate toml_edit to 0.24

* Downgrade indexmap to 2.11.4 for MSRV

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Tony <legendmastertony@gmail.com>
2025-12-21 22:04:33 +08:00
renovate[bot]
e290642fb4
chore(deps): update dependency rollup to v4.53.5 (#14676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 17:15:17 +08:00
renovate[bot]
b79386010d
chore(deps): update dependency rollup to v4.53.4 (#14670)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-18 22:49:52 +08:00
Tony
ff5d76ca21
fix: default WindowConfig::focus to false in Default::default (#14653) 2025-12-14 16:21:44 +08:00
sftse
2d28e3143e
Cleanups (#14632)
* refactor(tauri-utils): current_dest and current_pattern always change in-sync, group them to one Option

* refactor(tauri-utils): pass path as explicit argument instead of implicitly through self

* refactor(tauri-utils): remove struct field that is never set to Some

* refactor(tauri-cli): use OsString, OsStr where possible

* refactor(tauri-cli): Deref Arc early

* refactor(tauri-cli): lock config before passing to build::setup()

* refactor(tauri-build, tauri-utils): bettern pattern matching and borrowing

* refactor(tauri-cli): dont need Arc if already have static

* fix(tauri-cli): race condition initializing static flag, remove unnecessary OnceLock

* refactor(tauri-cli): use expect

* refactor(tauri-cli): remove unnecessary OnceLock

* refactor(tauri-cli): better use of dunce api

* refactor(tauri-cli): rename
2025-12-09 21:38:14 +08:00
renovate[bot]
18c69df8c7
chore(deps): update worker-rs crates to 0.7 (#14638)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FabianLars <github@fabianlars.de>
2025-12-09 13:42:27 +01:00
github-actions[bot]
f2e0405dc2
apply version updates (#14592)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-09 12:41:05 +01:00
FabianLars
54e8d93db1
ci(renovate): group worker-rs updates 2025-12-09 12:11:53 +01:00
Tony
251203b896
fix(linux): work area returns logical rect (#14637) 2025-12-09 18:05:12 +08:00
Tony
91becd9e4f
fix(nsis): plugins not signed (#14627) 2025-12-08 20:13:52 +08:00
Tony
018b4db22e
fix(bundler): skip signing for nsis uninstaller on --no-sign (#14625) 2025-12-08 20:13:43 +08:00
Tony
731dd5bfdc
docs: remove $APP and $LOG from FsScope (#14623) 2025-12-07 23:09:11 +08:00
Tony
7b1b3514df
changes(cli): log npm package version parse in debug level (#14621) 2025-12-07 21:06:27 +08:00
sftse
546b296405
fix(tauri-bundler): add a bit more context to error message (#14606) 2025-12-05 11:56:34 +08:00
Tunglies
514cf21e14
chore(deps): update num-bigint-dig to version 0.8.6 (#14591)
* chore(deps): update num-bigint-dig to version 0.8.6

* Update .changes/bump-version-num-bigint-dig.md

---------

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
2025-12-02 10:20:35 +08:00
renovate[bot]
60174527c0
chore(deps): update rust crate ico to 0.5 (#14589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 10:19:09 +08:00
chfaft
4176f93ae4
feat(bundler): consider extensions defined in main.wxs. (#14570)
* feat(bundler): consider extensions defined in main.wxs.

* chore(bundler): apply nitpick and add a change file.

* Update crates/tauri-bundler/src/bundle/windows/msi/mod.rs

chore(bundler): avoid clone and use reference.

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* Update .changes/support-template-extensions.md

chore(bundler): reclassify changes.

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

---------

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
2025-12-02 09:58:35 +08:00
github-actions[bot]
4408f72af6
apply version updates (#14467)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-30 11:22:38 +01:00
Fabian-Lars
1496145f82
fix(bundler): typo in 32bit arch (#14585)
* fix(bundler): typo in 32bit arch

* changefile
2025-11-30 07:49:33 +01:00
hrzlgnm
f022b2d1ae
fix(cli): Skip signing bundles entirely if --no-sign is requested (#14582)
Closes #14581
2025-11-30 11:45:43 +08:00
Fabian-Lars
1573c72402
fix: remove \\r from schema files on windows (#14561) 2025-11-26 11:22:45 +01:00
renovate[bot]
dd7e59a495
chore(deps): update dependency rollup to v4.53.3 (#14519)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 13:25:01 +08:00
Fabian-Lars
2d2a1be429
docs(cli): fix formatting of paths 2025-11-20 15:02:46 +01:00
Fabian-Lars
afdd288eab
chore(deps): update js-yaml (#14498) 2025-11-19 11:53:13 +01:00
Fabian-Lars
79a7d9ec01
fix(cli): change Cargo.toml version check to debug log (#14468) 2025-11-18 16:08:17 +01:00
Tony
f855caf8a3
fix(cli): mismatched versions check for pnpm (#14481) 2025-11-18 18:16:29 +08:00
Tunglies
ee3cc4a91b
perf: remove needless clones in various files for improved performance (#14475)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-11-17 15:27:49 +01:00
Tony
b5ef603d84
chore(deps): update NSIS to 3.11 (#14478) 2025-11-16 21:56:05 +08:00
Tunglies
ce98d87ce0
refactor: remove needless collect (#14474)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-11-16 12:49:20 +01:00
Aleksey Ponomarev
ad1dec2e24
fix(core): properly handle async errors in addPluginListener (#14464)
* fix(core): properly handle async errors in addPluginListener

The previous implementation used .then() after invoke() without await, which prevented the catch block from handling rejected promises. Now using await to properly catch errors and allow fallback to camelCase registerListener method.

* Change file and generate `bundle.global.js`

---------

Co-authored-by: Tony <legendmastertony@gmail.com>
2025-11-15 12:07:51 +08:00
renovate[bot]
beffcd880f
chore(deps): update dependency rollup to v4.53.2 (#14459)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 10:23:57 +08:00
github-actions[bot]
956031d73d
apply version updates (#14458)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-13 09:14:14 -03:00
Lucas Fernandes Nogueira
4b00130b86
refactor(core): improve iOS log messages from stdout/stderr (#14385)
* refactor(core): improve iOS log messages from stdout/stderr

move the stdout/stderr forward logic to Swift so it does not consume a Rust thread and never deadlocks on the simulator

I had to work on this because i'm facing #12172 again on latest Xcode (26.1)

* patch
2025-11-13 08:18:50 -03:00
Tony
8e3bd63db9
perf(codegen): wrap generated context in a fn (#14457)
* perf(codegen): wrap generated context in a fn

* Add comment about the reasoning
2025-11-13 15:24:10 +08:00
renovate[bot]
cfe47871a5
chore(deps): update dependency rollup to v4.53.1 (#14444)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 22:05:17 +08:00
Fabian-Lars
236f55b7aa
docs: enable dynamic-acl feature on docs.rs (#14452) 2025-11-12 10:21:18 +01:00
github-actions[bot]
9bb7e79e97
apply version updates (#14425)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-09 12:48:27 +01:00
FabianLars
d566679a99
ci: don't re-generate lockfile on prepublish 2025-11-09 12:08:57 +01:00
Kushal Meghani
3899d456d4
Address review comments (#14426)
* Address review comments

* Revert comments in `impl FromStr for ConfigValue`
2025-11-07 09:38:05 +08:00
Tony
b586ecf1f4
fix(cli): demultiply tiny skia pixels (#14416)
* fix(cli): demultiply tiny skia pixels

* Pull resize out to a function `resize_image`

* Move comments as well

* Use cow for older rust versions
2025-11-06 10:12:10 +08:00
Fabian-Lars
dd70d213cd
chore(deps): update minisign to 0.8 (#14415) 2025-11-05 14:58:54 +01:00
Kushal Meghani
d06a1994e9
refactor: improve cli code readability (#14333) 2025-11-05 13:48:32 +01:00
github-actions[bot]
b446a858de
apply version updates (#14409)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-04 17:00:19 +01:00
renovate[bot]
85ba5315c2
chore(deps): update dependency @types/node to v24 (#14376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 11:27:01 +01:00
Adam
779612ac84
fix(cli): respect required-features field from Cargo.toml (#14379)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-11-04 11:16:01 +01:00
Fabian-Lars
22edc65aad
fix(bundler/cli): set user-agent when fetching build tools (#14408) 2025-11-04 10:53:44 +01:00
Tony
9a19226369
fix(nsis): uninstall fails when manually close app on kill app dialog (#14410) 2025-11-04 17:18:21 +08:00
Chase Knowlden
fd8c30b4f1
fix: premultiply alpha before resizing (fix #14351) (#14353)
* fix: Premultiply alpha before resizing

* feat: Use rayon for process speedup

* Fix change tag

* `cargo fmt`

* Document reasoning & use imageops::resize directly

---------

Co-authored-by: Tony <legendmastertony@gmail.com>
2025-11-04 11:16:11 +08:00
renovate[bot]
18464d9481
chore(deps): update dependency vitest to v4 (#14361)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 17:21:40 +08:00
Tony
b80f9deb5f
chore: fix new clippy warnings (derive default) (#14395)
* chore: fix new clippy warnings (derive default)

* Fix left over `#[cfg(feature = "isolation")]`
2025-10-31 21:12:41 +08:00
Sebastian Neubauer
1afa9df6d5
fix(tauri-utils): Use write_if_changed more (#13621)
Replace `fs::write` with `write_if_changed` in two places. This can
prevent unnecessary rebuilds. (I didn’t encounter any, but this should
be ok nonetheless.)
2025-10-31 09:19:35 +08:00
Fabian-Lars
75a1fec705
ci: don't cache pnpm files in version-or-publish workflow (#14392) 2025-10-30 10:25:12 +01:00
github-actions[bot]
100dc94c48
apply version updates (#14378)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-29 15:15:51 +01:00
Fabian-Lars
7f710b8f3b
fix(bundler): inline linuxdeploy plugin scripts (#14390) 2025-10-29 14:50:33 +01:00
Braden Wong
bda1d22369
docs(webviewWindow): fix incorrect import in JSDoc example (#14388)
The getByLabel method is a static method on WebviewWindow, not Webview.
Updated the JSDoc example to import and use the correct class name.
2025-10-29 15:34:16 +08:00
Tony
28b9e7c7b8
fix: throw on custom protocol IPC fails (#14377) 2025-10-28 18:07:50 +08:00
renovate[bot]
3056d44d96
chore(deps): update dependency @rollup/plugin-typescript to v12.3.0 (#14364)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 15:57:47 +08:00
kandrelczyk
fc017ee257
add info to error message (fix 14186) (#14368)
* add info to error message

* changes file and  linux only warning

Signed-off-by: Krzysztof Andrelczyk <cristof@curiana.net>

* Update change file

---------

Signed-off-by: Krzysztof Andrelczyk <cristof@curiana.net>
Co-authored-by: Tony <legendmastertony@gmail.com>
2025-10-28 15:03:48 +08:00
170 changed files with 4739 additions and 3537 deletions

View File

@ -27,12 +27,6 @@
"dryRunCommand": true,
"pipe": true
},
{
"command": "cargo generate-lockfile",
"dryRunCommand": true,
"runFromRoot": true,
"pipe": true
},
{
"command": "cargo audit ${ process.env.CARGO_AUDIT_OPTIONS || '' }",
"dryRunCommand": true,

View File

@ -0,0 +1,7 @@
---
"tauri-bundler": patch:changes
"tauri-cli": patch:changes
"@tauri-apps/cli": patch:changes
---
Log patching bundle type information again

View File

@ -33,11 +33,9 @@ Hi! We, the maintainers, are really excited that you are interested in contribut
- It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging.
- If adding new feature:
- Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it greenlighted before working on it.
- If fixing a bug:
- If you are resolving a special issue, add `(fix: #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`.
- Provide detailed description of the bug in the PR, or link to an issue that does.

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
id-token: write # pnpm provenance / oidc token
outputs:
change: ${{ steps.covector.outputs.change }}
commandRan: ${{ steps.covector.outputs.commandRan }}
@ -74,11 +74,9 @@ jobs:
with:
fetch-depth: 0
- run: npm i -g --force corepack
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
node-version: 24
- name: cargo login
run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
@ -96,7 +94,6 @@ 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,6 +20,10 @@ defaults:
run:
working-directory: packages/cli/
permissions:
contents: write # update release
id-token: write # oidc token
jobs:
build:
strategy:
@ -366,16 +370,13 @@ 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@v4
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 24
cache: 'pnpm'
- name: Install dependencies
run: pnpm i --frozen-lockfile --ignore-scripts
@ -390,10 +391,8 @@ jobs:
shell: bash
- name: Publish
run: |
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
npm publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
NODE_AUTH_TOKEN: ''
RELEASE_ID: ${{ github.event.client_payload.releaseId || inputs.releaseId }}
NPM_CONFIG_PROVENANCE: true

View File

@ -38,35 +38,62 @@ jobs:
rust_target: aarch64-pc-windows-msvc
ext: '.exe'
args: ''
- os: ubuntu-22.04
rust_target: riscv64gc-unknown-linux-gnu
ext: ''
args: ''
cross: true
steps:
- uses: actions/checkout@v4
- name: 'Setup Rust'
if: ${{ !matrix.config.cross }}
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.config.rust_target }}
- uses: Swatinem/rust-cache@v2
if: ${{ !matrix.config.cross }}
with:
key: ${{ matrix.config.rust_target }}
- name: install Linux dependencies
if: matrix.config.os == 'ubuntu-latest'
if: ${{ !matrix.config.cross && startsWith(matrix.config.os, 'ubuntu') }}
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev
- name: Install cross
if: ${{ matrix.config.cross }}
uses: taiki-e/install-action@v2
with:
tool: cross@0.2.5
- name: Build CLI
if: ${{ !matrix.config.cross }}
run: cargo build --manifest-path ./crates/tauri-cli/Cargo.toml --profile release-size-optimized ${{ matrix.config.args }}
- name: Build CLI (cross)
if: ${{ matrix.config.cross }}
run: cross build --manifest-path ./crates/tauri-cli/Cargo.toml --target ${{ matrix.config.rust_target }} --profile release-size-optimized ${{ matrix.config.args }}
- name: Upload CLI
if: ${{ !matrix.config.cross }}
uses: actions/upload-artifact@v4
with:
name: cargo-tauri-${{ matrix.config.rust_target }}${{ matrix.config.ext }}
path: target/release-size-optimized/cargo-tauri${{ matrix.config.ext }}
if-no-files-found: error
- name: Upload CLI (cross)
if: ${{ matrix.config.cross }}
uses: actions/upload-artifact@v4
with:
name: cargo-tauri-${{ matrix.config.rust_target }}${{ matrix.config.ext }}
path: target/${{ matrix.config.rust_target }}/release-size-optimized/cargo-tauri${{ matrix.config.ext }}
if-no-files-found: error
upload:
needs: build
runs-on: ubuntu-latest

View File

@ -1,16 +0,0 @@
.changes
.devcontainer
.docker
.github
.scripts
.vscode
audits
bench
packages/api
packages/cli
crates/tauri-cli
crates/tauri-bundler
crates/tauri-driver
crates/tauri-macos-sign
crates/tauri-schema-generator
crates/tests

680
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,35 @@
# Changelog
## \[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
- Upgraded to `tauri-utils@2.8.1`
- Upgraded to `tauri-codegen@2.5.2`
## \[2.5.2]
### Dependencies
- Upgraded to `tauri-codegen@2.5.1`
## \[2.5.1]
### Bug Fixes

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-build"
version = "2.5.1"
version = "2.5.5"
description = "build time code to pair with https://crates.io/crates/tauri"
exclude = ["CHANGELOG.md", "/target"]
readme = "README.md"
@ -26,8 +26,8 @@ targets = [
[dependencies]
anyhow = "1"
quote = { version = "1", optional = true }
tauri-codegen = { version = "2.5.0", path = "../tauri-codegen", optional = true }
tauri-utils = { version = "2.8.0", path = "../tauri-utils", features = [
tauri-codegen = { version = "2.5.4", path = "../tauri-codegen", optional = true }
tauri-utils = { version = "2.8.2", path = "../tauri-utils", features = [
"build",
"resources",
] }

View File

@ -57,7 +57,7 @@ fn copy_binaries(
binaries: ResourcePaths,
target_triple: &str,
path: &Path,
package_name: Option<&String>,
package_name: Option<&str>,
) -> Result<()> {
for src in binaries {
let src = src?;
@ -165,21 +165,21 @@ fn copy_frameworks(dest_dir: &Path, frameworks: &[String]) -> Result<()> {
.with_context(|| format!("Failed to create frameworks output directory at {dest_dir:?}"))?;
for framework in frameworks.iter() {
if framework.ends_with(".framework") {
let src_path = PathBuf::from(framework);
let src_path = Path::new(framework);
let src_name = src_path
.file_name()
.expect("Couldn't get framework filename");
let dest_path = dest_dir.join(src_name);
copy_dir(&src_path, &dest_path)?;
copy_dir(src_path, &dest_path)?;
continue;
} else if framework.ends_with(".dylib") {
let src_path = PathBuf::from(framework);
let src_path = Path::new(framework);
if !src_path.exists() {
return Err(anyhow::anyhow!("Library not found: {}", framework));
}
let src_name = src_path.file_name().expect("Couldn't get library filename");
let dest_path = dest_dir.join(src_name);
copy_file(&src_path, &dest_path)?;
copy_file(src_path, &dest_path)?;
continue;
} else if framework.contains('/') {
return Err(anyhow::anyhow!(
@ -192,12 +192,8 @@ fn copy_frameworks(dest_dir: &Path, frameworks: &[String]) -> Result<()> {
continue;
}
}
if copy_framework_from(&PathBuf::from("/Library/Frameworks/"), framework, dest_dir)?
|| copy_framework_from(
&PathBuf::from("/Network/Library/Frameworks/"),
framework,
dest_dir,
)?
if copy_framework_from("/Library/Frameworks/".as_ref(), framework, dest_dir)?
|| copy_framework_from("/Network/Library/Frameworks/".as_ref(), framework, dest_dir)?
{
continue;
}
@ -533,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),
manifest.package.as_ref().map(|p| p.name.as_ref()),
)?;
}
@ -591,21 +587,19 @@ 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(|| find_icon(&config, |i| i.ends_with(".ico"), "icons/icon.ico"));
.unwrap_or_else(|| {
config
.bundle
.icon
.iter()
.find(|i| i.ends_with(".ico"))
.map(AsRef::as_ref)
.unwrap_or("icons/icon.ico")
.into()
});
let mut res = WindowsResource::new();

View File

@ -1,5 +1,75 @@
# Changelog
## \[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
- [`4176f93ae`](https://www.github.com/tauri-apps/tauri/commit/4176f93ae43ef66714c4934feb3df19df3a3e28a) ([#14570](https://www.github.com/tauri-apps/tauri/pull/14570) by [@chfaft](https://www.github.com/tauri-apps/tauri/../../chfaft)) Consider extensions that are defined in the wxs template.
### Bug Fixes
- [`018b4db22`](https://www.github.com/tauri-apps/tauri/commit/018b4db22e167fa67b37b0933e192a0f3556d3e5) ([#14625](https://www.github.com/tauri-apps/tauri/pull/14625) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Skip signing for NSIS uninstaller when using `--no-sign` flag
- [`91becd9e4`](https://www.github.com/tauri-apps/tauri/commit/91becd9e4fa2db089ddc6b21dadc06133e939e08) ([#14627](https://www.github.com/tauri-apps/tauri/pull/14627) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix NSIS plugins not being signed due to wrong path handlings
### Dependencies
- Upgraded to `tauri-macos-sign@2.3.2`
## \[2.7.4]
### Bug Fixes
- [`1496145f8`](https://www.github.com/tauri-apps/tauri/commit/1496145f8222649efeff22b819a96208670bbea1) ([#14585](https://www.github.com/tauri-apps/tauri/pull/14585) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the AppImage bundler to fail with 404 errors for 32-bit builds.
### Performance Improvements
- [`ce98d87ce`](https://www.github.com/tauri-apps/tauri/commit/ce98d87ce0aaa907285852eb80691197424e03c3) ([#14474](https://www.github.com/tauri-apps/tauri/pull/14474) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) refactor: remove needless collect. No user facing changes.
- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.
### Dependencies
- Upgraded to `tauri-macos-sign@2.3.1`
- Upgraded to `tauri-utils@2.8.1`
- [`b5ef603d8`](https://www.github.com/tauri-apps/tauri/commit/b5ef603d84bd8044625e50dcfdabb099b2e9fdd9) ([#14478](https://www.github.com/tauri-apps/tauri/pull/14478) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Updated NSIS from 3.8 to 3.11
## \[2.7.3]
### Enhancements
- [`22edc65aa`](https://www.github.com/tauri-apps/tauri/commit/22edc65aad0b3e45515008e8e0866112da70c8a1) ([#14408](https://www.github.com/tauri-apps/tauri/pull/14408) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Set user-agent in bundler and cli http requests when fetching build tools.
### Bug Fixes
- [`9a1922636`](https://www.github.com/tauri-apps/tauri/commit/9a192263693d71123a9953e2a6ee60fad07500b4) ([#14410](https://www.github.com/tauri-apps/tauri/pull/14410) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix uninstall fails if you close the app manually during the 'Click Ok to kill it' dialog
## \[2.7.2]
### Enhancements
- [`7f710b8f3`](https://www.github.com/tauri-apps/tauri/commit/7f710b8f3b509ed327d76761926511cf56e66b2d) ([#14390](https://www.github.com/tauri-apps/tauri/pull/14390) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Inline linuxdeploy plugins which were previously downloaded from `https://raw.githubusercontent.com` which lately blocks many users with a 429 error.
- [`fc017ee25`](https://www.github.com/tauri-apps/tauri/commit/fc017ee2577f48615367ea519386d3f37837e2c1) ([#14368](https://www.github.com/tauri-apps/tauri/pull/14368) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Mention symbol stripping on Linux in binary patch failed warning message
## \[2.7.1]
### Dependencies

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-bundler"
version = "2.7.1"
version = "2.8.0"
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.0", path = "../tauri-utils", features = [
tauri-utils = { version = "2.8.2", 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.0", path = "../tauri-macos-sign" }
tauri-macos-sign = { version = "2.3.3", path = "../tauri-macos-sign" }
[target."cfg(target_os = \"linux\")".dependencies]
heck = "0.5"

View File

@ -4,6 +4,8 @@
// SPDX-License-Identifier: MIT
mod category;
#[cfg(any(target_os = "linux", target_os = "windows"))]
mod kmp;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
@ -15,28 +17,49 @@ mod windows;
use tauri_utils::{display_path, platform::Target as TargetPlatform};
#[cfg(any(target_os = "linux", target_os = "windows"))]
const BUNDLE_VAR_TOKEN: &[u8] = b"__TAURI_BUNDLE_TYPE_VAR_UNK";
/// Patch a binary with bundle type information
#[cfg(any(target_os = "linux", target_os = "windows"))]
fn patch_binary(binary: &PathBuf, package_type: &PackageType) -> crate::Result<()> {
match package_type {
#[cfg(target_os = "linux")]
PackageType::AppImage | PackageType::Deb | PackageType::Rpm => {
log::info!(
"Patching binary {:?} for type {}",
binary,
package_type.short_name()
);
linux::patch_binary(binary, package_type)?;
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)?;
#[cfg(target_os = "linux")]
let bundle_type = match package_type {
crate::PackageType::Deb => b"__TAURI_BUNDLE_TYPE_VAR_DEB",
crate::PackageType::Rpm => b"__TAURI_BUNDLE_TYPE_VAR_RPM",
crate::PackageType::AppImage => b"__TAURI_BUNDLE_TYPE_VAR_APP",
_ => {
return Err(crate::Error::InvalidPackageType(
package_type.short_name().to_owned(),
"Linux".to_owned(),
))
}
PackageType::Nsis | PackageType::WindowsMsi => {
log::info!(
"Patching binary {:?} for type {}",
binary,
package_type.short_name()
);
windows::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(),
))
}
_ => (),
}
};
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(())
}
@ -92,22 +115,17 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
.expect("Main binary missing in settings");
let main_binary_path = settings.binary_path(main_binary);
// When packaging multiple binary types, we make a copy of the unsigned main_binary so that we can
// restore it after each package_type step. This avoids two issues:
// We make a copy of the unsigned main_binary so that we can restore it after each package_type step.
// This allows us to patch the binary correctly and avoids two issues:
// - modifying a signed binary without updating its PE checksum can break signature verification
// - codesigning tools should handle calculating+updating this, we just need to ensure
// (re)signing is performed after every `patch_binary()` operation
// - signing an already-signed binary can result in multiple signatures, causing verification errors
let main_binary_reset_required = matches!(target_os, TargetPlatform::Windows)
&& settings.windows().can_sign()
&& package_types.len() > 1;
let mut unsigned_main_binary_copy = tempfile::tempfile()?;
if main_binary_reset_required {
let mut unsigned_main_binary = std::fs::File::open(&main_binary_path)?;
std::io::copy(&mut unsigned_main_binary, &mut unsigned_main_binary_copy)?;
}
// TODO: change this to work on a copy while preserving the main binary unchanged
let mut main_binary_copy = tempfile::tempfile()?;
let mut main_binary_orignal = std::fs::File::open(&main_binary_path)?;
std::io::copy(&mut main_binary_orignal, &mut main_binary_copy)?;
let mut main_binary_signed = false;
let mut bundles = Vec::<Bundle>::new();
for package_type in &package_types {
// bundle was already built! e.g. DMG already built .app
@ -115,22 +133,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
continue;
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
if let Err(e) = patch_binary(&main_binary_path, package_type) {
log::warn!("Failed to add bundler type to the binary: {e}. Updater plugin may not be able to update this package. This shouldn't normally happen, please report it to https://github.com/tauri-apps/tauri/issues");
}
// sign main binary for every package type after patch
if matches!(target_os, TargetPlatform::Windows) && settings.windows().can_sign() {
if main_binary_signed && main_binary_reset_required {
let mut signed_main_binary = std::fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&main_binary_path)?;
unsigned_main_binary_copy.seek(SeekFrom::Start(0))?;
std::io::copy(&mut unsigned_main_binary_copy, &mut signed_main_binary)?;
}
windows::sign::try_sign(&main_binary_path, settings)?;
main_binary_signed = true;
}
let bundle_paths = match package_type {
@ -172,6 +182,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
package_type: package_type.to_owned(),
bundle_paths,
});
// Restore unsigned and unpatched binary
let mut modified_main_binary = std::fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&main_binary_path)?;
main_binary_copy.seek(SeekFrom::Start(0))?;
std::io::copy(&mut main_binary_copy, &mut modified_main_binary)?;
}
if let Some(updater) = settings.updater() {
@ -241,11 +259,10 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
return Ok(bundles);
}
let bundles_wo_updater = bundles
let finished_bundles = bundles
.iter()
.filter(|b| b.package_type != PackageType::Updater)
.collect::<Vec<_>>();
let finished_bundles = bundles_wo_updater.len();
.count();
let pluralised = if finished_bundles == 1 {
"bundle"
} else {
@ -274,7 +291,7 @@ fn sign_binaries_if_needed(settings: &Settings, target_os: &TargetPlatform) -> c
if matches!(target_os, TargetPlatform::Windows) {
if settings.windows().can_sign() {
if settings.no_sign() {
log::info!("Skipping binary signing due to --no-sign flag.");
log::warn!("Skipping binary signing due to --no-sign flag.");
return Ok(());
}

View File

@ -0,0 +1,61 @@
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
// KnuthMorrisPratt algorithm
// based on https://github.com/howeih/rust_kmp
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub fn index_of(pattern: &[u8], target: &[u8]) -> Option<usize> {
let failure_function = find_failure_function(pattern);
let mut t_i: usize = 0;
let mut p_i: usize = 0;
let target_len = target.len();
let mut result_idx = None;
let pattern_len = pattern.len();
while (t_i < target_len) && (p_i < pattern_len) {
if target[t_i] == pattern[p_i] {
if result_idx.is_none() {
result_idx.replace(t_i);
}
t_i += 1;
p_i += 1;
if p_i >= pattern_len {
return result_idx;
}
} else {
if p_i == 0 {
p_i = 0;
t_i += 1;
} else {
p_i = failure_function[p_i - 1];
}
result_idx = None;
}
}
None
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
fn find_failure_function(pattern: &[u8]) -> Vec<usize> {
let mut i = 1;
let mut j = 0;
let pattern_length = pattern.len();
let end_i = pattern_length - 1;
let mut failure_function = vec![0usize; pattern_length];
while i <= end_i {
if pattern[i] == pattern[j] {
failure_function[i] = j + 1;
i += 1;
j += 1;
} else if j == 0 {
failure_function[i] = 0;
i += 1;
} else {
j = failure_function[j - 1];
}
}
failure_function
}

View File

@ -0,0 +1,165 @@
#! /bin/bash
# abort on all errors
set -e
if [ "$DEBUG" != "" ]; then
set -x
fi
script=$(readlink -f "$0")
show_usage() {
echo "Usage: $script --appdir <path to AppDir>"
echo
echo "Bundles GStreamer plugins into an AppDir"
echo
echo "Required variables:"
echo " LINUXDEPLOY=\".../linuxdeploy\" path to linuxdeploy (e.g., AppImage); set automatically when plugin is run directly by linuxdeploy"
echo
echo "Optional variables:"
echo " GSTREAMER_INCLUDE_BAD_PLUGINS=\"1\" (default: disabled; set to empty string or unset to disable)"
echo " GSTREAMER_PLUGINS_DIR=\"...\" (directory containing GStreamer plugins; default: guessed based on main distro architecture)"
echo " GSTREAMER_HELPERS_DIR=\"...\" (directory containing GStreamer helper tools like gst-plugin-scanner; default: guessed based on main distro architecture)"
echo " GSTREAMER_VERSION=\"1.0\" (default: 1.0)"
}
while [ "$1" != "" ]; do
case "$1" in
--plugin-api-version)
echo "0"
exit 0
;;
--appdir)
APPDIR="$2"
shift
shift
;;
--help)
show_usage
exit 0
;;
*)
echo "Invalid argument: $1"
echo
show_usage
exit 1
;;
esac
done
if [ "$APPDIR" == "" ]; then
show_usage
exit 1
fi
if ! which patchelf &>/dev/null && ! type patchelf &>/dev/null; then
echo "Error: patchelf not found"
echo
show_usage
exit 2
fi
if [[ "$LINUXDEPLOY" == "" ]]; then
echo "Error: \$LINUXDEPLOY not set"
echo
show_usage
exit 3
fi
mkdir -p "$APPDIR"
export GSTREAMER_VERSION="${GSTREAMER_VERSION:-1.0}"
plugins_target_dir="$APPDIR"/usr/lib/gstreamer-"$GSTREAMER_VERSION"
helpers_target_dir="$APPDIR"/usr/lib/gstreamer"$GSTREAMER_VERSION"/gstreamer-"$GSTREAMER_VERSION"
if [ "$GSTREAMER_PLUGINS_DIR" != "" ]; then
plugins_dir="${GSTREAMER_PLUGINS_DIR}"
elif [ -d /usr/lib/"$(uname -m)"-linux-gnu/gstreamer-"$GSTREAMER_VERSION" ]; then
plugins_dir=/usr/lib/$(uname -m)-linux-gnu/gstreamer-"$GSTREAMER_VERSION"
else
plugins_dir=/usr/lib/gstreamer-"$GSTREAMER_VERSION"
fi
if [ "$GSTREAMER_HELPERS_DIR" != "" ]; then
helpers_dir="${GSTREAMER_HELPERS_DIR}"
else
helpers_dir=/usr/lib/$(uname -m)-linux-gnu/gstreamer"$GSTREAMER_VERSION"/gstreamer-"$GSTREAMER_VERSION"
fi
if [ ! -d "$plugins_dir" ]; then
echo "Error: could not find plugins directory: $plugins_dir"
exit 1
fi
mkdir -p "$plugins_target_dir"
echo "Copying plugins into $plugins_target_dir"
for i in "$plugins_dir"/*; do
[ -d "$i" ] && continue
[ ! -f "$i" ] && echo "File does not exist: $i" && continue
echo "Copying plugin: $i"
cp "$i" "$plugins_target_dir"
done
"$LINUXDEPLOY" --appdir "$APPDIR"
for i in "$plugins_target_dir"/*; do
[ -d "$i" ] && continue
[ ! -f "$i" ] && echo "File does not exist: $i" && continue
(file "$i" | grep -v ELF --silent) && echo "Ignoring non ELF file: $i" && continue
echo "Manually setting rpath for $i"
patchelf --set-rpath '$ORIGIN/..:$ORIGIN' "$i"
done
mkdir -p "$helpers_target_dir"
echo "Copying helpers in $helpers_target_dir"
for i in "$helpers_dir"/*; do
[ -d "$i" ] && continue
[ ! -f "$i" ] && echo "File does not exist: $i" && continue
echo "Copying helper: $i"
cp "$i" "$helpers_target_dir"
done
for i in "$helpers_target_dir"/*; do
[ -d "$i" ] && continue
[ ! -f "$i" ] && echo "File does not exist: $i" && continue
(file "$i" | grep -v ELF --silent) && echo "Ignoring non ELF file: $i" && continue
echo "Manually setting rpath for $i"
patchelf --set-rpath '$ORIGIN/../..' "$i"
done
echo "Installing AppRun hook"
mkdir -p "$APPDIR"/apprun-hooks
if [ "$GSTREAMER_VERSION" == "1.0" ]; then
cat > "$APPDIR"/apprun-hooks/linuxdeploy-plugin-gstreamer.sh <<\EOF
#! /bin/bash
export GST_REGISTRY_REUSE_PLUGIN_SCANNER="no"
export GST_PLUGIN_SYSTEM_PATH_1_0="${APPDIR}/usr/lib/gstreamer-1.0"
export GST_PLUGIN_PATH_1_0="${APPDIR}/usr/lib/gstreamer-1.0"
export GST_PLUGIN_SCANNER_1_0="${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner"
export GST_PTP_HELPER_1_0="${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-ptp-helper"
EOF
elif [ "$GSTREAMER_VERSION" == "0.10" ]; then
cat > "$APPDIR"/apprun-hooks/linuxdeploy-plugin-gstreamer.sh <<\EOF
#! /bin/bash
export GST_REGISTRY_REUSE_PLUGIN_SCANNER="no"
export GST_PLUGIN_SYSTEM_PATH_0_10="${APPDIR}/usr/lib/gstreamer-1.0"
export GST_PLUGIN_SCANNER_0_10="${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner"
export GST_PTP_HELPER_0_10="${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-ptp-helper"
EOF
else
echo "Warning: unknown GStreamer version: $GSTREAMER_VERSION, cannot install AppRun hook"
fi

View File

@ -0,0 +1,327 @@
#! /usr/bin/env bash
# GTK3 environment variables: https://docs.gtk.org/gtk3/running.html
# GTK4 environment variables: https://docs.gtk.org/gtk4/running.html
# abort on all errors
set -e
if [ "$DEBUG" != "" ]; then
set -x
verbose="--verbose"
fi
script=$(readlink -f "$0")
show_usage() {
echo "Usage: $script --appdir <path to AppDir>"
echo
echo "Bundles resources for applications that use GTK into an AppDir"
echo
echo "Required variables:"
echo " LINUXDEPLOY=\".../linuxdeploy\" path to linuxdeploy (e.g., AppImage); set automatically when plugin is run directly by linuxdeploy"
#echo
#echo "Optional variables:"
#echo " DEPLOY_GTK_VERSION (major version of GTK to deploy, e.g. '2', '3' or '4'; auto-detect by default)"
}
variable_is_true() {
local var="$1"
if [ -n "$var" ] && { [ "$var" == "true" ] || [ "$var" -gt 0 ]; } 2> /dev/null; then
return 0 # true
else
return 1 # false
fi
}
get_pkgconf_variable() {
local variable="$1"
local library="$2"
local default_path="$3"
path="$("$PKG_CONFIG" --variable="$variable" "$library")"
if [ -n "$path" ]; then
echo "$path"
elif [ -n "$default_path" ]; then
echo "$default_path"
else
echo "$0: there is no '$variable' variable for '$library' library." > /dev/stderr
echo "Please check the '$library.pc' file is present in \$PKG_CONFIG_PATH (you may need to install the appropriate -dev/-devel package)." > /dev/stderr
exit 1
fi
}
copy_tree() {
local src=("${@:1:$#-1}")
local dst="${*:$#}"
for elem in "${src[@]}"; do
mkdir -p "${dst::-1}$elem"
cp "$elem" --archive --parents --target-directory="$dst" $verbose
done
}
search_tool() {
local tool="$1"
local directory="$2"
if command -v "$tool"; then
return 0
fi
PATH_ARRAY=(
"/usr/lib/$(uname -m)-linux-gnu/$directory/$tool"
"/usr/lib/$directory/$tool"
"/usr/bin/$tool"
"/usr/bin/$tool-64"
"/usr/bin/$tool-32"
)
for path in "${PATH_ARRAY[@]}"; do
if [ -x "$path" ]; then
echo "$path"
return 0
fi
done
}
#DEPLOY_GTK_VERSION="${DEPLOY_GTK_VERSION:-0}" # When not set by user, this variable use the integer '0' as a sentinel value
DEPLOY_GTK_VERSION=3 # Force GTK3 for tauri apps
APPDIR=
while [ "$1" != "" ]; do
case "$1" in
--plugin-api-version)
echo "0"
exit 0
;;
--appdir)
APPDIR="$2"
shift
shift
;;
--help)
show_usage
exit 0
;;
*)
echo "Invalid argument: $1"
echo
show_usage
exit 1
;;
esac
done
if [ "$APPDIR" == "" ]; then
show_usage
exit 1
fi
mkdir -p "$APPDIR"
# make lib64 writable again.
chmod +w "$APPDIR"/usr/lib64 || true
if command -v pkgconf > /dev/null; then
PKG_CONFIG="pkgconf"
elif command -v pkg-config > /dev/null; then
PKG_CONFIG="pkg-config"
else
echo "$0: pkg-config/pkgconf not found in PATH, aborting"
exit 1
fi
if ! command -v find &>/dev/null && ! type find &>/dev/null; then
echo -e "$0: find not found.\nInstall findutils then re-run the plugin."
exit 1
fi
if [ -z "$LINUXDEPLOY" ]; then
echo -e "$0: LINUXDEPLOY environment variable is not set.\nDownload a suitable linuxdeploy AppImage, set the environment variable and re-run the plugin."
exit 1
fi
gtk_versions=0 # Count major versions of GTK when auto-detect GTK version
if [ "$DEPLOY_GTK_VERSION" -eq 0 ]; then
echo "Determining which GTK version to deploy"
while IFS= read -r -d '' file; do
if [ "$DEPLOY_GTK_VERSION" -ne 2 ] && ldd "$file" | grep -q "libgtk-x11-2.0.so"; then
DEPLOY_GTK_VERSION=2
gtk_versions="$((gtk_versions+1))"
fi
if [ "$DEPLOY_GTK_VERSION" -ne 3 ] && ldd "$file" | grep -q "libgtk-3.so"; then
DEPLOY_GTK_VERSION=3
gtk_versions="$((gtk_versions+1))"
fi
if [ "$DEPLOY_GTK_VERSION" -ne 4 ] && ldd "$file" | grep -q "libgtk-4.so"; then
DEPLOY_GTK_VERSION=4
gtk_versions="$((gtk_versions+1))"
fi
done < <(find "$APPDIR/usr/bin" -executable -type f -print0)
fi
if [ "$gtk_versions" -gt 1 ]; then
echo "$0: can not deploy multiple GTK versions at the same time."
echo "Please set DEPLOY_GTK_VERSION to {2, 3, 4}."
exit 1
elif [ "$DEPLOY_GTK_VERSION" -eq 0 ]; then
echo "$0: failed to auto-detect GTK version."
echo "Please set DEPLOY_GTK_VERSION to {2, 3, 4}."
exit 1
fi
echo "Installing AppRun hook"
HOOKSDIR="$APPDIR/apprun-hooks"
HOOKFILE="$HOOKSDIR/linuxdeploy-plugin-gtk.sh"
mkdir -p "$HOOKSDIR"
cat > "$HOOKFILE" <<\EOF
#! /usr/bin/env bash
gsettings get org.gnome.desktop.interface gtk-theme 2> /dev/null | grep -qi "dark" && GTK_THEME_VARIANT="dark" || GTK_THEME_VARIANT="light"
APPIMAGE_GTK_THEME="${APPIMAGE_GTK_THEME:-"Adwaita:$GTK_THEME_VARIANT"}" # Allow user to override theme (discouraged)
export APPDIR="${APPDIR:-"$(dirname "$(realpath "$0")")"}" # Workaround to run extracted AppImage
export GTK_DATA_PREFIX="$APPDIR"
export GTK_THEME="$APPIMAGE_GTK_THEME" # Custom themes are broken
export GDK_BACKEND=x11 # Crash with Wayland backend on Wayland - We tested it without it and ended up with this: https://github.com/tauri-apps/tauri/issues/8541
export XDG_DATA_DIRS="$APPDIR/usr/share:/usr/share:$XDG_DATA_DIRS" # g_get_system_data_dirs() from GLib
EOF
echo "Installing GLib schemas"
# Note: schemasdir is undefined on Ubuntu 16.04
glib_schemasdir="$(get_pkgconf_variable "schemasdir" "gio-2.0" "/usr/share/glib-2.0/schemas")"
copy_tree "$glib_schemasdir" "$APPDIR/"
glib-compile-schemas "$APPDIR/$glib_schemasdir"
cat >> "$HOOKFILE" <<EOF
export GSETTINGS_SCHEMA_DIR="\$APPDIR/$glib_schemasdir"
EOF
case "$DEPLOY_GTK_VERSION" in
2)
# https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/pull/20#issuecomment-826354261
echo "WARNING: Gtk+2 applications are not fully supported by this plugin"
;;
3)
echo "Installing GTK 3.0 modules"
gtk3_exec_prefix="$(get_pkgconf_variable "exec_prefix" "gtk+-3.0")"
gtk3_libdir="$(get_pkgconf_variable "libdir" "gtk+-3.0")/gtk-3.0"
#gtk3_path="$gtk3_libdir/modules" export GTK_PATH="\$APPDIR/$gtk3_path"
gtk3_immodulesdir="$gtk3_libdir/$(get_pkgconf_variable "gtk_binary_version" "gtk+-3.0")/immodules"
gtk3_printbackendsdir="$gtk3_libdir/$(get_pkgconf_variable "gtk_binary_version" "gtk+-3.0")/printbackends"
gtk3_immodules_cache_file="$(dirname "$gtk3_immodulesdir")/immodules.cache"
gtk3_immodules_query="$(search_tool "gtk-query-immodules-3.0" "libgtk-3-0")"
copy_tree "$gtk3_libdir" "$APPDIR/"
cat >> "$HOOKFILE" <<EOF
export GTK_EXE_PREFIX="\$APPDIR/$gtk3_exec_prefix"
export GTK_PATH="\$APPDIR/$gtk3_libdir:/usr/lib64/gtk-3.0:/usr/lib/x86_64-linux-gnu/gtk-3.0"
export GTK_IM_MODULE_FILE="\$APPDIR/$gtk3_immodules_cache_file"
EOF
if [ -x "$gtk3_immodules_query" ]; then
echo "Updating immodules cache in $APPDIR/$gtk3_immodules_cache_file"
"$gtk3_immodules_query" > "$APPDIR/$gtk3_immodules_cache_file"
else
echo "WARNING: gtk-query-immodules-3.0 not found"
fi
if [ ! -f "$APPDIR/$gtk3_immodules_cache_file" ]; then
echo "WARNING: immodules.cache file is missing"
fi
sed -i "s|$gtk3_libdir/3.0.0/immodules/||g" "$APPDIR/$gtk3_immodules_cache_file"
;;
4)
echo "Installing GTK 4.0 modules"
gtk4_exec_prefix="$(get_pkgconf_variable "exec_prefix" "gtk4" "/usr")"
gtk4_libdir="$(get_pkgconf_variable "libdir" "gtk4")/gtk-4.0"
gtk4_path="$gtk4_libdir/modules"
copy_tree "$gtk4_libdir" "$APPDIR/"
cat >> "$HOOKFILE" <<EOF
export GTK_EXE_PREFIX="\$APPDIR/$gtk4_exec_prefix"
export GTK_PATH="\$APPDIR/$gtk4_path"
EOF
;;
*)
echo "$0: '$DEPLOY_GTK_VERSION' is not a valid GTK major version."
echo "Please set DEPLOY_GTK_VERSION to {2, 3, 4}."
exit 1
esac
echo "Installing GDK PixBufs"
gdk_libdir="$(get_pkgconf_variable "libdir" "gdk-pixbuf-2.0")"
gdk_pixbuf_binarydir="$(get_pkgconf_variable "gdk_pixbuf_binarydir" "gdk-pixbuf-2.0")"
gdk_pixbuf_cache_file="$(get_pkgconf_variable "gdk_pixbuf_cache_file" "gdk-pixbuf-2.0")"
gdk_pixbuf_moduledir="$(get_pkgconf_variable "gdk_pixbuf_moduledir" "gdk-pixbuf-2.0")"
# Note: gdk_pixbuf_query_loaders variable is not defined on some systems
gdk_pixbuf_query="$(search_tool "gdk-pixbuf-query-loaders" "gdk-pixbuf-2.0")"
copy_tree "$gdk_pixbuf_binarydir" "$APPDIR/"
cat >> "$HOOKFILE" <<EOF
export GDK_PIXBUF_MODULE_FILE="\$APPDIR/$gdk_pixbuf_cache_file"
EOF
if [ -x "$gdk_pixbuf_query" ]; then
echo "Updating pixbuf cache in $APPDIR/$gdk_pixbuf_cache_file"
"$gdk_pixbuf_query" > "$APPDIR/$gdk_pixbuf_cache_file"
else
echo "WARNING: gdk-pixbuf-query-loaders not found"
fi
if [ ! -f "$APPDIR/$gdk_pixbuf_cache_file" ]; then
echo "WARNING: loaders.cache file is missing"
fi
sed -i "s|$gdk_pixbuf_moduledir/||g" "$APPDIR/$gdk_pixbuf_cache_file"
echo "Copying more libraries"
gobject_libdir="$(get_pkgconf_variable "libdir" "gobject-2.0")"
gio_libdir="$(get_pkgconf_variable "libdir" "gio-2.0")"
librsvg_libdir="$(get_pkgconf_variable "libdir" "librsvg-2.0")"
pango_libdir="$(get_pkgconf_variable "libdir" "pango")"
pangocairo_libdir="$(get_pkgconf_variable "libdir" "pangocairo")"
pangoft2_libdir="$(get_pkgconf_variable "libdir" "pangoft2")"
FIND_ARRAY=(
"$gdk_libdir" "libgdk_pixbuf-*.so*"
"$gobject_libdir" "libgobject-*.so*"
"$gio_libdir" "libgio-*.so*"
"$librsvg_libdir" "librsvg-*.so*"
"$pango_libdir" "libpango-*.so*"
"$pangocairo_libdir" "libpangocairo-*.so*"
"$pangoft2_libdir" "libpangoft2-*.so*"
)
LIBRARIES=()
for (( i=0; i<${#FIND_ARRAY[@]}; i+=2 )); do
directory=${FIND_ARRAY[i]}
library=${FIND_ARRAY[i+1]}
while IFS= read -r -d '' file; do
LIBRARIES+=( "--library=$file" )
done < <(find "$directory" \( -type l -o -type f \) -name "$library" -print0)
done
env LINUXDEPLOY_PLUGIN_MODE=1 "$LINUXDEPLOY" --appdir="$APPDIR" "${LIBRARIES[@]}"
# Create symbolic links as a workaround
# Details: https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/issues/24#issuecomment-1030026529
echo "Manually setting rpath for GTK modules"
PATCH_ARRAY=(
"$gtk3_immodulesdir"
"$gtk3_printbackendsdir"
"$gdk_pixbuf_moduledir"
)
for directory in "${PATCH_ARRAY[@]}"; do
while IFS= read -r -d '' file; do
ln $verbose -s "${file/\/usr\/lib\//}" "$APPDIR/usr/lib"
done < <(find "$directory" -name '*.so' -print0)
done
# set write permission on lib64 again to make it deletable.
chmod +w "$APPDIR"/usr/lib64 || true
# We have to copy the files first to not get permission errors when we assign gio_extras_dir
find /usr/lib* -name libgiognutls.so -exec mkdir -p "$APPDIR"/"$(dirname '{}')" \; -exec cp --parents '{}' "$APPDIR/" \; || true
# related files that we seemingly don't need:
# libgiolibproxy.so - libgiognomeproxy.so - glib-pacrunner
gio_extras_dir=$(find "$APPDIR"/usr/lib* -name libgiognutls.so -exec dirname '{}' \; 2>/dev/null)
cat >> "$HOOKFILE" <<EOF
export GIO_EXTRA_MODULES="\$APPDIR/${gio_extras_dir#"$APPDIR"/}"
EOF
#binary patch absolute paths in libwebkit files
find "$APPDIR"/usr/lib* -name 'libwebkit*' -exec sed -i -e "s|/usr|././|g" '{}' \;

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::debian;
use super::{super::debian, write_and_make_executable};
use crate::{
bundle::settings::Arch,
error::{Context, ErrorExt},
@ -280,12 +280,3 @@ fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result<
Ok(linuxdeploy)
}
fn write_and_make_executable(path: &Path, data: Vec<u8>) -> std::io::Result<()> {
use std::os::unix::fs::PermissionsExt;
fs::write(path, data)?;
fs::set_permissions(path, fs::Permissions::from_mode(0o770))?;
Ok(())
}

View File

@ -0,0 +1,25 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
fs,
path::{Path, PathBuf},
};
use crate::Settings;
mod linuxdeploy;
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
linuxdeploy::bundle_project(settings)
}
fn write_and_make_executable(path: &Path, data: Vec<u8>) -> std::io::Result<()> {
use std::os::unix::fs::PermissionsExt;
fs::write(path, data)?;
fs::set_permissions(path, fs::Permissions::from_mode(0o770))?;
Ok(())
}

View File

@ -119,8 +119,9 @@ pub fn generate_data(
for bin in settings.binaries() {
let bin_path = settings.binary_path(bin);
fs_utils::copy_file(&bin_path, &bin_dir.join(bin.name()))
.with_context(|| format!("Failed to copy binary from {bin_path:?}"))?;
let trgt = bin_dir.join(bin.name());
fs_utils::copy_file(&bin_path, &trgt)
.with_context(|| format!("Failed to copy binary from {bin_path:?} to {trgt:?}"))?;
}
copy_resource_files(settings, &data_dir).with_context(|| "Failed to copy resource files")?;

View File

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

View File

@ -1,59 +0,0 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
/// Change value of __TAURI_BUNDLE_TYPE static variable to mark which package type it was bundled in
#[cfg(target_os = "linux")]
pub fn patch_binary(
binary_path: &std::path::PathBuf,
package_type: &crate::PackageType,
) -> crate::Result<()> {
let mut file_data = std::fs::read(binary_path).expect("Could not read binary file.");
let elf = match goblin::Object::parse(&file_data)? {
goblin::Object::Elf(elf) => elf,
_ => return Err(crate::Error::GenericError("Not an ELF file".to_owned())),
};
let offset = find_bundle_type_symbol(elf).ok_or(crate::Error::MissingBundleTypeVar)?;
let offset = offset as usize;
if offset + 3 <= file_data.len() {
let chars = &mut file_data[offset..offset + 3];
match package_type {
crate::PackageType::Deb => chars.copy_from_slice(b"DEB"),
crate::PackageType::Rpm => chars.copy_from_slice(b"RPM"),
crate::PackageType::AppImage => chars.copy_from_slice(b"APP"),
_ => {
return Err(crate::Error::InvalidPackageType(
package_type.short_name().to_owned(),
"linux".to_owned(),
))
}
}
std::fs::write(binary_path, &file_data)
.map_err(|error| crate::Error::BinaryWriteError(error.to_string()))?;
} else {
return Err(crate::Error::BinaryOffsetOutOfRange);
}
Ok(())
}
/// Find address of a symbol in relocations table
#[cfg(target_os = "linux")]
fn find_bundle_type_symbol(elf: goblin::elf::Elf<'_>) -> Option<i64> {
for sym in elf.syms.iter() {
if let Some(name) = elf.strtab.get_at(sym.st_name) {
if name == "__TAURI_BUNDLE_TYPE" {
for reloc in elf.dynrelas.iter() {
if reloc.r_offset == sym.st_value {
return Some(reloc.r_addend.unwrap());
}
}
}
}
}
None
}

View File

@ -65,16 +65,12 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
log::info!(action = "Bundling"; "{} ({})", app_product_name, app_bundle_path.display());
if app_bundle_path.exists() {
fs::remove_dir_all(&app_bundle_path).fs_context(
"failed to remove old app bundle",
app_bundle_path.to_path_buf(),
)?;
fs::remove_dir_all(&app_bundle_path)
.fs_context("failed to remove old app bundle", &app_bundle_path)?;
}
let bundle_directory = app_bundle_path.join("Contents");
fs::create_dir_all(&bundle_directory).fs_context(
"failed to create bundle directory",
bundle_directory.to_path_buf(),
)?;
fs::create_dir_all(&bundle_directory)
.fs_context("failed to create bundle directory", &bundle_directory)?;
let resources_dir = bundle_directory.join("Resources");
let bin_dir = bundle_directory.join("MacOS");
@ -459,20 +455,12 @@ fn copy_frameworks_to_bundle(
) -> crate::Result<Vec<SignTarget>> {
let mut paths = Vec::new();
let frameworks = settings
.macos()
.frameworks
.as_ref()
.cloned()
.unwrap_or_default();
let frameworks = settings.macos().frameworks.clone().unwrap_or_default();
if frameworks.is_empty() {
return Ok(paths);
}
let dest_dir = bundle_directory.join("Frameworks");
fs::create_dir_all(&dest_dir).fs_context(
"failed to create Frameworks directory",
dest_dir.to_path_buf(),
)?;
fs::create_dir_all(&dest_dir).fs_context("failed to create Frameworks directory", &dest_dir)?;
for framework in frameworks.iter() {
if framework.ends_with(".framework") {
let src_path = PathBuf::from(framework);

View File

@ -44,15 +44,11 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
log::info!(action = "Bundling"; "{} ({})", app_product_name, app_bundle_path.display());
if app_bundle_path.exists() {
fs::remove_dir_all(&app_bundle_path).fs_context(
"failed to remove old app bundle",
app_bundle_path.to_path_buf(),
)?;
fs::remove_dir_all(&app_bundle_path)
.fs_context("failed to remove old app bundle", &app_bundle_path)?;
}
fs::create_dir_all(&app_bundle_path).fs_context(
"failed to create bundle directory",
app_bundle_path.to_path_buf(),
)?;
fs::create_dir_all(&app_bundle_path)
.fs_context("failed to create bundle directory", &app_bundle_path)?;
for src in settings.resource_files() {
let src = src?;

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 for CI build
// import user certificate - useful for CI build
let keychain =
tauri_macos_sign::Keychain::with_certificate(&certificate_encoded, &certificate_password)
.map_err(Box::new)?;

View File

@ -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 of RPM dependencies your application recommends.
/// the list of RPM dependencies your application recommends.
pub recommends: Option<Vec<String>>,
/// The list of RPM dependencies your application provides.
pub provides: Option<Vec<String>>,

View File

@ -14,5 +14,3 @@ pub use util::{
NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME, WIX_OUTPUT_FOLDER_NAME,
WIX_UPDATER_OUTPUT_FOLDER_NAME,
};
pub use util::patch_binary;

View File

@ -753,26 +753,28 @@ pub fn build_wix_app_installer(
}
let main_wxs_path = output_path.join("main.wxs");
fs::write(main_wxs_path, handlebars.render("main.wxs", &data)?)?;
fs::write(&main_wxs_path, handlebars.render("main.wxs", &data)?)?;
let mut candle_inputs = vec![("main.wxs".into(), Vec::new())];
let mut candle_inputs = vec![];
let current_dir = std::env::current_dir()?;
let extension_regex = Regex::new("\"http://schemas.microsoft.com/wix/(\\w+)\"")?;
for fragment_path in fragment_paths {
let fragment_path = current_dir.join(fragment_path);
let fragment_content = fs::read_to_string(&fragment_path)?;
let fragment_handlebars = Handlebars::new();
let fragment = fragment_handlebars.render_template(&fragment_content, &data)?;
let input_paths =
std::iter::once(main_wxs_path).chain(fragment_paths.iter().map(|p| current_dir.join(p)));
for input_path in input_paths {
let input_content = fs::read_to_string(&input_path)?;
let input_handlebars = Handlebars::new();
let input = input_handlebars.render_template(&input_content, &data)?;
let mut extensions = Vec::new();
for cap in extension_regex.captures_iter(&fragment) {
for cap in extension_regex.captures_iter(&input) {
let path = wix_toolset_path.join(format!("Wix{}.dll", &cap[1]));
if settings.windows().can_sign() {
try_sign(&path, settings)?;
}
extensions.push(path);
}
candle_inputs.push((fragment_path, extensions));
candle_inputs.push((input_path, extensions));
}
let mut fragment_extensions = HashSet::new();

View File

@ -0,0 +1,27 @@
LangString addOrReinstall ${LANG_NORWEGIAN} "Legg til/reinstaller komponenter"
LangString alreadyInstalled ${LANG_NORWEGIAN} "Allerede installert"
LangString alreadyInstalledLong ${LANG_NORWEGIAN} "${PRODUCTNAME} ${VERSION} er allerede installert. Velg operasjonen du vil utføre og klikk Neste for å fortsette."
LangString appRunning ${LANG_NORWEGIAN} "{{product_name}} kjører! Lukk den først og prøv igjen."
LangString appRunningOkKill ${LANG_NORWEGIAN} "{{product_name}} kjører!$\nKlikk OK for å avslutte den"
LangString chooseMaintenanceOption ${LANG_NORWEGIAN} "Velg vedlikeholdsoperasjonen som skal utføres."
LangString choowHowToInstall ${LANG_NORWEGIAN} "Velg hvordan du vil installere ${PRODUCTNAME}."
LangString createDesktop ${LANG_NORWEGIAN} "Opprett skrivebordssnarvei"
LangString dontUninstall ${LANG_NORWEGIAN} "Ikke avinstaller"
LangString dontUninstallDowngrade ${LANG_NORWEGIAN} "Ikke avinstaller (nedgradering uten avinstallasjon er deaktivert for denne installasjonen)"
LangString failedToKillApp ${LANG_NORWEGIAN} "Kunne ikke avslutte {{product_name}}. Lukk den først og prøv igjen"
LangString installingWebview2 ${LANG_NORWEGIAN} "Installerer WebView2..."
LangString newerVersionInstalled ${LANG_NORWEGIAN} "En nyere versjon av ${PRODUCTNAME} er allerede installert! Det anbefales ikke at du installerer en eldre versjon. Hvis du virkelig vil installere denne eldre versjonen, er det bedre å avinstallere den nåværende versjonen først. Velg operasjonen du vil utføre og klikk Neste for å fortsette."
LangString older ${LANG_NORWEGIAN} "eldre"
LangString olderOrUnknownVersionInstalled ${LANG_NORWEGIAN} "En $R4-versjon av ${PRODUCTNAME} er installert på systemet ditt. Det anbefales at du avinstallerer den nåværende versjonen før installasjon. Velg operasjonen du vil utføre og klikk Neste for å fortsette."
LangString silentDowngrades ${LANG_NORWEGIAN} "Nedgraderinger er deaktivert for denne installasjonen. Kan ikke fortsette med stille installasjon; bruk den grafiske installasjonen i stedet.$\n"
LangString unableToUninstall ${LANG_NORWEGIAN} "Kunne ikke avinstallere!"
LangString uninstallApp ${LANG_NORWEGIAN} "Avinstaller ${PRODUCTNAME}"
LangString uninstallBeforeInstalling ${LANG_NORWEGIAN} "Avinstaller før installasjon"
LangString unknown ${LANG_NORWEGIAN} "ukjent"
LangString webview2AbortError ${LANG_NORWEGIAN} "Kunne ikke installere WebView2! Appen kan ikke kjøre uten den. Prøv å starte installasjonen på nytt."
LangString webview2DownloadError ${LANG_NORWEGIAN} "Feil: Nedlasting av WebView2 mislyktes - $0"
LangString webview2DownloadSuccess ${LANG_NORWEGIAN} "WebView2-bootstrapper lastet ned"
LangString webview2Downloading ${LANG_NORWEGIAN} "Laster ned WebView2-bootstrapper..."
LangString webview2InstallError ${LANG_NORWEGIAN} "Feil: Installering av WebView2 mislyktes med avslutningskode $1"
LangString webview2InstallSuccess ${LANG_NORWEGIAN} "WebView2 ble installert"
LangString deleteAppData ${LANG_NORWEGIAN} "Slett programdata"

View File

@ -36,12 +36,12 @@ use std::{
// URLS for the NSIS toolchain.
#[cfg(target_os = "windows")]
const NSIS_URL: &str =
"https://github.com/tauri-apps/binary-releases/releases/download/nsis-3/nsis-3.zip";
"https://github.com/tauri-apps/binary-releases/releases/download/nsis-3.11/nsis-3.11.zip";
#[cfg(target_os = "windows")]
const NSIS_SHA1: &str = "057e83c7d82462ec394af76c87d06733605543d4";
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.1/nsis_tauri_utils.dll";
const NSIS_TAURI_UTILS_SHA1: &str = "B053B2E5FDB97257954C8F935D80964F056520AE";
"https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.5.3/nsis_tauri_utils.dll";
const NSIS_TAURI_UTILS_SHA1: &str = "75197FEE3C6A814FE035788D1C34EAD39349B860";
#[cfg(target_os = "windows")]
const NSIS_REQUIRED_FILES: &[&str] = &[
@ -55,6 +55,9 @@ const NSIS_REQUIRED_FILES: &[&str] = &[
"Include/x64.nsh",
"Include/nsDialogs.nsh",
"Include/WinMessages.nsh",
"Include/Win/COM.nsh",
"Include/Win/Propkey.nsh",
"Include/Win/RestartManager.nsh",
];
const NSIS_PLUGIN_FILES: &[&str] = &[
"NSISdl.dll",
@ -125,7 +128,7 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, _tauri_tools_path: &Path) -> c
let data = download_and_verify(NSIS_URL, NSIS_SHA1, HashAlgorithm::Sha1)?;
log::info!("extracting NSIS");
crate::utils::http_utils::extract_zip(&data, _tauri_tools_path)?;
fs::rename(_tauri_tools_path.join("nsis-3.08"), nsis_toolset_path)?;
fs::rename(_tauri_tools_path.join("nsis-3.11"), nsis_toolset_path)?;
}
// download additional plugins
@ -295,8 +298,12 @@ fn build_nsis_app_installer(
data.insert("copyright", to_json(settings.copyright_string()));
if settings.windows().can_sign() {
let sign_cmd = format!("{:?}", sign_command("%1", &settings.sign_params())?);
data.insert("uninstaller_sign_cmd", to_json(sign_cmd));
if settings.no_sign() {
log::warn!("Skipping signing for NSIS uninstaller due to --no-sign flag.");
} else {
let sign_cmd = format!("{:?}", sign_command("%1", &settings.sign_params())?);
data.insert("uninstaller_sign_cmd", to_json(sign_cmd));
}
}
let version = settings.version_string();
@ -614,13 +621,16 @@ fn build_nsis_app_installer(
fs::create_dir_all(nsis_installer_path.parent().unwrap())?;
if settings.windows().can_sign() {
log::info!("Signing NSIS plugins");
for dll in NSIS_PLUGIN_FILES {
let path = additional_plugins_path.join(dll);
if path.exists() {
try_sign(&path, settings)?;
} else {
log::warn!("Could not find {}, skipping signing", path.display());
if let Some(plugin_copy_path) = &maybe_plugin_copy_path {
let plugin_copy_path = plugin_copy_path.join("x86-unicode");
log::info!("Signing NSIS plugins");
for dll in NSIS_PLUGIN_FILES {
let path = plugin_copy_path.join(dll);
if path.exists() {
try_sign(&path, settings)?;
} else {
log::warn!("Could not find {}, skipping signing", path.display());
}
}
}
}
@ -857,6 +867,7 @@ fn get_lang_data(lang: &str) -> Option<(String, &[u8])> {
"swedish" => include_bytes!("./languages/Swedish.nsh"),
"portuguese" => include_bytes!("./languages/Portuguese.nsh"),
"ukrainian" => include_bytes!("./languages/Ukrainian.nsh"),
"norwegian" => include_bytes!("./languages/Norwegian.nsh"),
_ => return None,
};
Some((path, content))

View File

@ -48,6 +48,7 @@
Pop $R0
Sleep 500
${If} $R0 = 0
${OrIf} $R0 = 2
Goto app_check_done_${UniqueID}
${Else}
IfSilent silent_${UniqueID} ui_${UniqueID}

View File

@ -266,8 +266,7 @@ pub fn try_sign<P: AsRef<Path>>(file_path: P, settings: &Settings) -> crate::Res
pub fn should_sign(file_path: &Path) -> crate::Result<bool> {
let is_binary = file_path
.extension()
.and_then(|extension| extension.to_str())
.is_some_and(|extension| matches!(extension, "exe" | "dll"));
.is_some_and(|ext| ext == "exe" || ext == "dll");
if !is_binary {
return Ok(false);
}

View File

@ -77,75 +77,3 @@ pub fn os_bitness<'a>() -> Option<&'a str> {
_ => None,
}
}
pub fn patch_binary(binary_path: &PathBuf, package_type: &crate::PackageType) -> crate::Result<()> {
let mut file_data = std::fs::read(binary_path)?;
let pe = match goblin::Object::parse(&file_data)? {
goblin::Object::PE(pe) => pe,
_ => {
return Err(crate::Error::BinaryParseError(
std::io::Error::new(std::io::ErrorKind::InvalidInput, "binary is not a PE file").into(),
));
}
};
let tauri_bundle_section = pe
.sections
.iter()
.find(|s| s.name().unwrap_or_default() == ".taubndl")
.ok_or(crate::Error::MissingBundleTypeVar)?;
let data_offset = tauri_bundle_section.pointer_to_raw_data as usize;
let pointer_size = if pe.is_64 { 8 } else { 4 };
let ptr_bytes = file_data
.get(data_offset..data_offset + pointer_size)
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
// `try_into` is safe to `unwrap` here because we have already checked the slice's size through `get`
let ptr_value = if pe.is_64 {
u64::from_le_bytes(ptr_bytes.try_into().unwrap())
} else {
u32::from_le_bytes(ptr_bytes.try_into().unwrap()).into()
};
let rdata_section = pe
.sections
.iter()
.find(|s| s.name().unwrap_or_default() == ".rdata")
.ok_or_else(|| {
crate::Error::BinaryParseError(
std::io::Error::new(std::io::ErrorKind::InvalidInput, ".rdata section not found").into(),
)
})?;
let rva = ptr_value.checked_sub(pe.image_base as u64).ok_or_else(|| {
crate::Error::BinaryParseError(
std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid RVA offset").into(),
)
})?;
// see "Relative virtual address (RVA)" for explanation of offset arithmetic here:
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#general-concepts
let file_offset = rdata_section.pointer_to_raw_data as usize
+ (rva as usize).saturating_sub(rdata_section.virtual_address as usize);
// Overwrite the string at that offset
let string_bytes = file_data
.get_mut(file_offset..file_offset + 3)
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
match package_type {
crate::PackageType::Nsis => string_bytes.copy_from_slice(b"NSS"),
crate::PackageType::WindowsMsi => string_bytes.copy_from_slice(b"MSI"),
_ => {
return Err(crate::Error::InvalidPackageType(
package_type.short_name().to_owned(),
"windows".to_owned(),
));
}
}
std::fs::write(binary_path, &file_data)
.map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?;
Ok(())
}

View File

@ -105,6 +105,7 @@ pub enum Error {
#[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

@ -14,6 +14,8 @@ use sha2::Digest;
use url::Url;
use zip::ZipArchive;
const BUNDLER_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
fn generate_github_mirror_url_from_template(github_url: &str) -> Option<String> {
std::env::var("TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE")
.ok()
@ -47,7 +49,15 @@ fn generate_github_alternative_url(url: &str) -> Option<(ureq::Agent, String)> {
generate_github_mirror_url_from_template(url)
.or_else(|| generate_github_mirror_url_from_base(url))
.map(|alt_url| (ureq::agent(), alt_url))
.map(|alt_url| {
(
ureq::Agent::config_builder()
.user_agent(BUNDLER_USER_AGENT)
.build()
.into(),
alt_url,
)
})
}
fn create_agent_and_url(url: &str) -> (ureq::Agent, String) {
@ -55,22 +65,21 @@ fn create_agent_and_url(url: &str) -> (ureq::Agent, String) {
}
pub(crate) fn base_ureq_agent() -> ureq::Agent {
#[allow(unused_mut)]
let mut config_builder = ureq::Agent::config_builder()
.user_agent(BUNDLER_USER_AGENT)
.proxy(ureq::Proxy::try_from_env());
#[cfg(feature = "platform-certs")]
let agent: ureq::Agent = ureq::Agent::config_builder()
.tls_config(
{
config_builder = config_builder.tls_config(
ureq::tls::TlsConfig::builder()
.root_certs(ureq::tls::RootCerts::PlatformVerifier)
.build(),
)
.proxy(ureq::Proxy::try_from_env())
.build()
.into();
#[cfg(not(feature = "platform-certs"))]
let agent: ureq::Agent = ureq::Agent::config_builder()
.proxy(ureq::Proxy::try_from_env())
.build()
.into();
agent
);
}
config_builder.build().into()
}
#[allow(dead_code)]

View File

@ -1,5 +1,104 @@
# Changelog
## \[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
- [`7b1b3514d`](https://www.github.com/tauri-apps/tauri/commit/7b1b3514df771e6e9859b9f54fa4df332433948e) ([#14621](https://www.github.com/tauri-apps/tauri/pull/14621) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Errors like `Error Failed to parse version 2 for for NPM package tauri` when there was no `package-lock.json` file present yet or when using ones like `link:./tauri` are now only logged in `--verbose` mode.
### Dependencies
- Upgraded to `tauri-macos-sign@2.3.2`
- Upgraded to `tauri-bundler@2.7.5`
## \[2.9.5]
### Bug Fixes
- [`f022b2d1a`](https://www.github.com/tauri-apps/tauri/commit/f022b2d1ae57612e39c75782926f2f341d9034a8) ([#14582](https://www.github.com/tauri-apps/tauri/pull/14582) by [@hrzlgnm](https://www.github.com/tauri-apps/tauri/../../hrzlgnm)) Fixed an issue that caused the cli to error out with missing private key, in case the option `--no-sign` was requested and the `tauri.config` has signing key set and the plugin `tauri-plugin-updater` is used.
- [`f855caf8a`](https://www.github.com/tauri-apps/tauri/commit/f855caf8a3830aa5dd6d0b039312866a5d9c3606) ([#14481](https://www.github.com/tauri-apps/tauri/pull/14481) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fixed the mismatched tauri package versions check didn't work for pnpm
- [`79a7d9ec0`](https://www.github.com/tauri-apps/tauri/commit/79a7d9ec01be1a371b8e923848140fea75e9caed) ([#14468](https://www.github.com/tauri-apps/tauri/pull/14468) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the cli to print errors like `Error Failed to parse version 2 for crate tauri` when there was no `Cargo.lock` file present yet. This will still be logged in `--verbose` mode.
### Performance Improvements
- [`ce98d87ce`](https://www.github.com/tauri-apps/tauri/commit/ce98d87ce0aaa907285852eb80691197424e03c3) ([#14474](https://www.github.com/tauri-apps/tauri/pull/14474) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) refactor: remove needless collect. No user facing changes.
- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.
### Dependencies
- Upgraded to `tauri-bundler@2.7.4`
- Upgraded to `tauri-macos-sign@2.3.1`
- Upgraded to `tauri-utils@2.8.1`
## \[2.9.4]
### Bug Fixes
- [`b586ecf1f`](https://www.github.com/tauri-apps/tauri/commit/b586ecf1f4b3b087f9aa6c4668c2c18b1b7925f4) ([#14416](https://www.github.com/tauri-apps/tauri/pull/14416) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons for svg images.
## \[2.9.3]
### Enhancements
- [`22edc65aa`](https://www.github.com/tauri-apps/tauri/commit/22edc65aad0b3e45515008e8e0866112da70c8a1) ([#14408](https://www.github.com/tauri-apps/tauri/pull/14408) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Set user-agent in bundler and cli http requests when fetching build tools.
- [`779612ac8`](https://www.github.com/tauri-apps/tauri/commit/779612ac8425a787626da4cefdb9eaf7d63bea18) ([#14379](https://www.github.com/tauri-apps/tauri/pull/14379) by [@moubctez](https://www.github.com/tauri-apps/tauri/../../moubctez)) Properly read the `required-features` field of binaries in Cargo.toml to prevent bundling issues when the features weren't enabled.
### Bug Fixes
- [`fd8c30b4f`](https://www.github.com/tauri-apps/tauri/commit/fd8c30b4f1bca8dd7165c5c0ebe7fbfd17662153) ([#14353](https://www.github.com/tauri-apps/tauri/pull/14353) by [@ChaseKnowlden](https://www.github.com/tauri-apps/tauri/../../ChaseKnowlden)) Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons.
### Dependencies
- Upgraded to `tauri-bundler@2.7.3`
## \[2.9.2]
### Dependencies
- Upgraded to `tauri-bundler@2.7.2`
## \[2.9.1]
### Dependencies

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-cli"
version = "2.9.1"
version = "2.10.0"
authors = ["Tauri Programme within The Commons Conservancy"]
edition = "2021"
rust-version = "1.77.2"
@ -47,7 +47,7 @@ sublime_fuzzy = "0.7"
clap_complete = "4"
clap = { version = "4", features = ["derive", "env"] }
thiserror = "2"
tauri-bundler = { version = "2.7.1", default-features = false, path = "../tauri-bundler" }
tauri-bundler = { version = "2.8.0", default-features = false, path = "../tauri-bundler" }
colored = "2"
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
@ -56,9 +56,9 @@ notify = "8"
notify-debouncer-full = "0.6"
shared_child = "1"
duct = "1.0"
toml_edit = { version = "0.23", features = ["serde"] }
toml_edit = { version = "0.24", features = ["serde"] }
json-patch = "3"
tauri-utils = { version = "2.8.0", path = "../tauri-utils", features = [
tauri-utils = { version = "2.8.2", path = "../tauri-utils", features = [
"isolation",
"schema",
"config-json5",
@ -66,11 +66,11 @@ tauri-utils = { version = "2.8.0", path = "../tauri-utils", features = [
"html-manipulation",
] }
toml = "0.9"
jsonschema = "0.33"
jsonschema = { version = "0.33", default-features = false }
handlebars = "6"
include_dir = "0.7"
dirs = "6"
minisign = "=0.7.3"
minisign = "0.8"
base64 = "0.22"
ureq = { version = "3", default-features = false, features = ["gzip"] }
os_info = "3"
@ -113,6 +113,7 @@ uuid = { version = "1", features = ["v5"] }
rand = "0.9"
zip = { version = "4", default-features = false, features = ["deflate"] }
which = "8"
rayon = "1.10"
[dev-dependencies]
insta = "1"
@ -132,7 +133,7 @@ libc = "0.2"
[target."cfg(target_os = \"macos\")".dependencies]
plist = "1"
tauri-macos-sign = { version = "2.3.0", path = "../tauri-macos-sign" }
tauri-macos-sign = { version = "2.3.3", path = "../tauri-macos-sign" }
object = { version = "0.36", default-features = false, features = [
"macho",
"read_core",

View File

@ -33,7 +33,7 @@ These environment variables are inputs to the CLI which may have an equivalent C
- See [creating API keys](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) for more information.
- `API_PRIVATE_KEYS_DIR` — Specify the directory where your AuthKey file is located. See `APPLE_API_KEY`.
- `APPLE_API_ISSUER` — Issuer ID. Required if `APPLE_API_KEY` is specified.
- `APPLE_API_KEY_PATH` - path to the API key `.p8` file. If not specified, for macOS apps the bundler searches the following directories in sequence for a private key file with the name of 'AuthKey\_<api_key>.p8': './private_keys', '~/private_keys', '~/.private_keys', and '~/.appstoreconnect/private_keys'. **For iOS this variable is required**.
- `APPLE_API_KEY_PATH` - path to the API key `.p8` file. If not specified, for macOS apps the bundler searches the following directories in sequence for a private key file with the name of `AuthKey\_<api_key>.p8`: `./private_keys`, `~/private_keys`, `~/.private_keys`, and `~/.appstoreconnect/private_keys`. **For iOS this variable is required**.
- `APPLE_SIGNING_IDENTITY` — The identity used to code sign. Overwrites `tauri.conf.json > bundle > macOS > signingIdentity`. If neither are set, it is inferred from `APPLE_CERTIFICATE` when provided.
- `APPLE_PROVIDER_SHORT_NAME` — If your Apple ID is connected to multiple teams, you have to specify the provider short name of the team you want to use to notarize your app. Overwrites `tauri.conf.json > bundle > macOS > providerShortName`.
- `APPLE_DEVELOPMENT_TEAM` — The team ID used to code sign on iOS. Overwrites `tauri.conf.json > bundle > iOS > developmentTeam`. Can be found in https://developer.apple.com/account#MembershipDetailsCard.

View File

@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://schema.tauri.app/config/2.9.1",
"$id": "https://schema.tauri.app/config/2.10.2",
"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",
@ -165,7 +165,7 @@
"type": "object",
"properties": {
"windows": {
"description": "The app windows configuration.\n\n ## Example:\n\n To create a window at app startup\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n If not specified, the window's label (its identifier) defaults to \"main\",\n you can use this label to get the window through\n `app.get_webview_window` in Rust or `WebviewWindow.getByLabel` in JavaScript\n\n When working with multiple windows, each window will need an unique label\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"label\": \"main\", \"width\": 800, \"height\": 600 },\n { \"label\": \"secondary\", \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n You can also set `create` to false and use this config through the Rust APIs\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"create\": false, \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n and use it like this\n\n ```rust\n tauri::Builder::default()\n .setup(|app| {\n tauri::WebviewWindowBuilder::from_config(app.handle(), app.config().app.windows[0])?.build()?;\n Ok(())\n });\n ```",
"description": "The app windows configuration.\n\n ## Example:\n\n To create a window at app startup\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n If not specified, the window's label (its identifier) defaults to \"main\",\n you can use this label to get the window through\n `app.get_webview_window` in Rust or `WebviewWindow.getByLabel` in JavaScript\n\n When working with multiple windows, each window will need an unique label\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"label\": \"main\", \"width\": 800, \"height\": 600 },\n { \"label\": \"secondary\", \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n You can also set `create` to false and use this config through the Rust APIs\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"create\": false, \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n and use it like this\n\n ```rust\n tauri::Builder::default()\n .setup(|app| {\n tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?;\n Ok(())\n });\n ```",
"default": [],
"type": "array",
"items": {
@ -231,7 +231,7 @@
"type": "string"
},
"create": {
"description": "Whether Tauri should create this window at app startup or not.\n\n When this is set to `false` you must manually grab the config object via `app.config().app.windows`\n and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).\n\n ## Example:\n\n ```rust\n tauri::Builder::default()\n .setup(|app| {\n tauri::WebviewWindowBuilder::from_config(app.handle(), app.config().app.windows[0])?.build()?;\n Ok(())\n });\n ```",
"description": "Whether Tauri should create this window at app startup or not.\n\n When this is set to `false` you must manually grab the config object via `app.config().app.windows`\n and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).\n\n ## Example:\n\n ```rust\n tauri::Builder::default()\n .setup(|app| {\n tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?;\n Ok(())\n });\n ```",
"default": true,
"type": "boolean"
},
@ -262,7 +262,7 @@
"type": "boolean"
},
"x": {
"description": "The horizontal position of the window's top left corner",
"description": "The horizontal position of the window's top left corner in logical pixels",
"type": [
"number",
"null"
@ -270,7 +270,7 @@
"format": "double"
},
"y": {
"description": "The vertical position of the window's top left corner",
"description": "The vertical position of the window's top left corner in logical pixels",
"type": [
"number",
"null"
@ -278,19 +278,19 @@
"format": "double"
},
"width": {
"description": "The window width.",
"description": "The window width in logical pixels.",
"default": 800.0,
"type": "number",
"format": "double"
},
"height": {
"description": "The window height.",
"description": "The window height in logical pixels.",
"default": 600.0,
"type": "number",
"format": "double"
},
"minWidth": {
"description": "The min window width.",
"description": "The min window width in logical pixels.",
"type": [
"number",
"null"
@ -298,7 +298,7 @@
"format": "double"
},
"minHeight": {
"description": "The min window height.",
"description": "The min window height in logical pixels.",
"type": [
"number",
"null"
@ -306,7 +306,7 @@
"format": "double"
},
"maxWidth": {
"description": "The max window width.",
"description": "The max window width in logical pixels.",
"type": [
"number",
"null"
@ -314,7 +314,7 @@
"format": "double"
},
"maxHeight": {
"description": "The max window height.",
"description": "The max window height in logical pixels.",
"type": [
"number",
"null"
@ -652,13 +652,13 @@
],
"properties": {
"width": {
"description": "Horizontal margin in physical unit",
"description": "Horizontal margin in physical pixels",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"height": {
"description": "Vertical margin in physical unit",
"description": "Vertical margin in physical pixels",
"type": "integer",
"format": "uint32",
"minimum": 0.0
@ -1296,7 +1296,7 @@
"additionalProperties": false
},
"FsScope": {
"description": "Protocol scope definition.\n It is a list of glob patterns that restrict the API access from the webview.\n\n Each pattern can start with a variable that resolves to a system base directory.\n The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,\n `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,\n `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,\n `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"description": "Protocol scope definition.\n It is a list of glob patterns that restrict the API access from the webview.\n\n Each pattern can start with a variable that resolves to a system base directory.\n The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,\n `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,\n `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$TEMP`,\n `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"anyOf": [
{
"description": "A list of paths that are allowed by this scope.",
@ -1991,7 +1991,7 @@
"description": "Defines the URL or assets to embed in the application.",
"anyOf": [
{
"description": "An external URL that should be used as the default application URL.",
"description": "An external URL that should be used as the default application URL. No assets are embedded in the app in this case.",
"type": "string",
"format": "uri"
},
@ -2000,7 +2000,7 @@
"type": "string"
},
{
"description": "An array of files to embed on the app.",
"description": "An array of files to embed in the app.",
"type": "array",
"items": {
"type": "string"
@ -2792,7 +2792,7 @@
"type": "object",
"properties": {
"version": {
"description": "MSI installer version in the format `major.minor.patch.build` (build is optional).\n\n Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.\n\n The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n The third and foruth fields have a maximum value of 65,535.\n\n See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.",
"description": "MSI installer version in the format `major.minor.patch.build` (build is optional).\n\n Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.\n\n The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n The third and fourth fields have a maximum value of 65,535.\n\n See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.",
"type": [
"string",
"null"
@ -3096,7 +3096,7 @@
"description": "Custom Signing Command configuration.",
"anyOf": [
{
"description": "A string notation of the script to execute.\n\n \"%1\" will be replaced with the path to the binary to be signed.\n\n This is a simpler notation for the command.\n Tauri will split the string with `' '` and use the first element as the command name and the rest as arguments.\n\n If you need to use whitespace in the command or arguments, use the object notation [`Self::ScriptWithOptions`].",
"description": "A string notation of the script to execute.\n\n \"%1\" will be replaced with the path to the binary to be signed.\n\n This is a simpler notation for the command.\n Tauri will split the string with `' '` and use the first element as the command name and the rest as arguments.\n\n If you need to use whitespace in the command or arguments, use the object notation [`Self::CommandWithOptions`].",
"type": "string"
},
{

View File

@ -1,9 +1,9 @@
{
"cli.js": {
"version": "2.9.1",
"version": "2.10.0",
"node": ">= 10.0.0"
},
"tauri": "2.9.1",
"tauri-build": "2.5.1",
"tauri-plugin": "2.5.1"
"tauri": "2.10.2",
"tauri-build": "2.5.5",
"tauri-plugin": "2.5.3"
}

View File

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

View File

@ -6,7 +6,6 @@ use clap::Parser;
use crate::{
error::{Context, ErrorExt},
helpers::app_paths::tauri_dir,
Result,
};
use colored::Colorize;
@ -25,16 +24,17 @@ pub struct Options {
}
pub fn command(options: Options) -> Result<()> {
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
let acl_manifests_path = tauri_dir()
let acl_manifests_path = dirs
.tauri
.join("gen")
.join("schemas")
.join("acl-manifests.json");
if acl_manifests_path.exists() {
let plugin_manifest_json = read_to_string(&acl_manifests_path)
.fs_context("failed to read plugin manifest", acl_manifests_path.clone())?;
.fs_context("failed to read plugin manifest", acl_manifests_path)?;
let acl = serde_json::from_str::<BTreeMap<String, Manifest>>(&plugin_manifest_json)
.context("failed to parse plugin manifest as JSON")?;

View File

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

View File

@ -7,11 +7,11 @@ use crate::{
error::{Context, ErrorExt},
helpers::{
self,
app_paths::{frontend_dir, tauri_dir},
config::{get as get_config, ConfigHandle, FrontendDist},
app_paths::Dirs,
config::{get_config, ConfigMetadata, FrontendDist},
},
info::plugins::check_mismatched_packages,
interface::{rust::get_cargo_target_dir, AppInterface, Interface},
interface::{rust::get_cargo_target_dir, AppInterface},
ConfigValue, Result,
};
use clap::{ArgAction, Parser};
@ -40,7 +40,7 @@ pub struct Options {
pub target: Option<String>,
/// Space or comma separated list of features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
pub features: Vec<String>,
/// Space or comma separated list of bundles to package.
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
pub bundles: Option<Vec<BundleFormat>>,
@ -82,7 +82,7 @@ pub struct Options {
}
pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
if options.no_sign {
log::warn!("--no-sign flag detected: Signing will be skipped.");
@ -99,41 +99,37 @@ 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.lock().unwrap().as_ref().unwrap(),
options.target.clone(),
)?;
let mut interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;
setup(&interface, &mut options, config.clone(), false)?;
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 {
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)?;
let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?;
let bin_path = interface.build(interface_options)?;
let bin_path = interface.build(interface_options, &dirs)?;
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_,
&*app_settings,
&config,
&dirs,
&out_dir,
)?;
}
@ -144,15 +140,14 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
pub fn setup(
interface: &AppInterface,
options: &mut Options,
config: ConfigHandle,
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(frontend_dir(), tauri_path) {
if let Err(error) = check_mismatched_packages(dirs.frontend, dirs.tauri) {
if options.ignore_version_mismatches {
log::error!("{error}");
} else {
@ -160,46 +155,47 @@ pub fn setup(
}
}
set_current_dir(tauri_path).context("failed to set current directory")?;
set_current_dir(dirs.tauri).context("failed to set current directory")?;
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
let bundle_identifier_source = config_
let bundle_identifier_source = config
.find_bundle_identifier_overwriter()
.unwrap_or_else(|| "tauri.conf.json".into());
if config_.identifier == "com.tauri.dev" {
if config.identifier == "com.tauri.dev" {
crate::error::bail!(
"You must change the bundle identifier in `{bundle_identifier_source} identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.",
);
}
if config_
if config
.identifier
.chars()
.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))
{
crate::error::bail!(
"The bundle identifier \"{}\" set in `{} identifier`. The bundle identifier string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.).",
config_.identifier,
bundle_identifier_source
"The bundle identifier \"{}\" set in `{bundle_identifier_source:?} identifier`. The bundle identifier string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.).",
config.identifier,
);
}
if config_.identifier.ends_with(".app") {
if config.identifier.ends_with(".app") {
log::warn!(
"The bundle identifier \"{}\" set in `{} identifier` ends with `.app`. This is not recommended because it conflicts with the application bundle extension on macOS.",
config_.identifier,
bundle_identifier_source
"The bundle identifier \"{}\" set in `{bundle_identifier_source:?} identifier` ends with `.app`. This is not recommended because it conflicts with the application bundle extension on macOS.",
config.identifier,
);
}
if let Some(before_build) = config_.build.before_build_command.clone() {
helpers::run_hook("beforeBuildCommand", before_build, interface, options.debug)?;
if let Some(before_build) = config.build.before_build_command.clone() {
helpers::run_hook(
"beforeBuildCommand",
before_build,
interface,
options.debug,
dirs.frontend,
)?;
}
if let Some(FrontendDist::Directory(web_asset_path)) = &config_.build.frontend_dist {
if let Some(FrontendDist::Directory(web_asset_path)) = &config.build.frontend_dist {
if !web_asset_path.exists() {
let absolute_path = web_asset_path
.parent()
@ -224,7 +220,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)?;
let target_path = get_cargo_target_dir(&options.args, dirs.tauri)?;
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) {
@ -252,13 +248,12 @@ pub fn setup(
}
if options.runner.is_none() {
options.runner = config_.build.runner.clone();
options.runner = config.build.runner.clone();
}
options
.features
.get_or_insert(Vec::new())
.extend(config_.build.features.clone().unwrap_or_default());
.extend_from_slice(config.build.features.as_deref().unwrap_or_default());
interface.build_options(&mut options.args, &mut options.features, mobile);
Ok(())

View File

@ -16,11 +16,11 @@ use crate::{
error::{Context, ErrorExt},
helpers::{
self,
app_paths::tauri_dir,
config::{get as get_config, ConfigMetadata},
app_paths::Dirs,
config::{get_config, ConfigMetadata},
updater_signature,
},
interface::{AppInterface, AppSettings, Interface},
interface::{AppInterface, AppSettings},
ConfigValue,
};
@ -71,7 +71,7 @@ pub struct Options {
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..))]
pub features: Option<Vec<String>>,
pub features: Vec<String>,
/// Target triple to build against.
///
/// It must be one of the values outputted by `$rustc --print target-list` or `universal-apple-darwin` for an universal macOS application.
@ -118,7 +118,7 @@ impl From<crate::build::Options> for Options {
}
pub fn command(options: Options, verbosity: u8) -> crate::Result<()> {
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
let ci = options.ci;
@ -131,35 +131,30 @@ 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.lock().unwrap().as_ref().unwrap(),
options.target.clone(),
)?;
let interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;
let tauri_path = tauri_dir();
std::env::set_current_dir(tauri_path).context("failed to set current directory")?;
std::env::set_current_dir(dirs.tauri).context("failed to set current directory")?;
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 {
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)?;
let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?;
bundle(
&options,
verbosity,
ci,
&interface,
&app_settings,
config_,
&*app_settings,
&config,
&dirs,
&out_dir,
)
}
@ -170,8 +165,9 @@ pub fn bundle<A: AppSettings>(
verbosity: u8,
ci: bool,
interface: &AppInterface,
app_settings: &std::sync::Arc<A>,
app_settings: &A,
config: &ConfigMetadata,
dirs: &Dirs,
out_dir: &Path,
) -> crate::Result<()> {
let package_types: Vec<PackageType> = if let Some(bundles) = &options.bundles {
@ -198,12 +194,19 @@ 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)
.get_bundler_settings(
options.clone().into(),
config,
out_dir,
package_types,
dirs.tauri,
)
.with_context(|| "failed to build bundler settings")?;
settings.set_no_sign(options.no_sign);
@ -249,6 +252,11 @@ fn sign_updaters(
return Ok(());
}
if settings.no_sign() {
log::warn!("Updater signing is skipped due to --no-sign flag.");
return Ok(());
}
// get the public key
let pubkey = &update_settings.pubkey;
// check if pubkey points to a file...

View File

@ -5,14 +5,12 @@
use crate::{
error::{Context, ErrorExt},
helpers::{
app_paths::{frontend_dir, tauri_dir},
app_paths::Dirs,
command_env,
config::{
get as get_config, reload as reload_config, BeforeDevCommand, ConfigHandle, FrontendDist,
},
config::{get_config, reload_config, BeforeDevCommand, ConfigMetadata, FrontendDist},
},
info::plugins::check_mismatched_packages,
interface::{AppInterface, ExitReason, Interface},
interface::{AppInterface, ExitReason},
CommandExt, ConfigValue, Error, Result,
};
@ -27,14 +25,14 @@ use std::{
process::{exit, Command, Stdio},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex, OnceLock,
OnceLock,
},
};
mod builtin_dev_server;
static BEFORE_DEV: OnceLock<Mutex<Arc<SharedChild>>> = OnceLock::new();
static KILL_BEFORE_DEV_FLAG: OnceLock<AtomicBool> = OnceLock::new();
static BEFORE_DEV: OnceLock<SharedChild> = OnceLock::new();
static KILL_BEFORE_DEV_FLAG: AtomicBool = AtomicBool::new(false);
#[cfg(unix)]
const KILL_CHILDREN_SCRIPT: &[u8] = include_bytes!("../scripts/kill-children.sh");
@ -57,7 +55,7 @@ pub struct Options {
pub target: Option<String>,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
pub features: Vec<String>,
/// Exit on panic
#[clap(short, long)]
pub exit_on_panic: bool,
@ -99,61 +97,57 @@ pub struct Options {
}
pub fn command(options: Options) -> Result<()> {
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
let r = command_internal(options);
let r = command_internal(options, dirs);
if r.is_err() {
kill_before_dev_process();
}
r
}
fn command_internal(mut options: Options) -> Result<()> {
fn command_internal(mut options: Options, dirs: Dirs) -> Result<()> {
let target = options
.target
.as_deref()
.map(Target::from_triple)
.unwrap_or_else(Target::current);
let config = get_config(
let mut config = get_config(
target,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
let mut interface = AppInterface::new(
config.lock().unwrap().as_ref().unwrap(),
options.target.clone(),
)?;
let mut interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;
setup(&interface, &mut options, config)?;
setup(&interface, &mut options, &mut config, &dirs)?;
let exit_on_panic = options.exit_on_panic;
let no_watch = options.no_watch;
interface.dev(options.into(), move |status, reason| {
on_app_exit(status, reason, exit_on_panic, no_watch)
})
interface.dev(
&mut config,
options.into(),
move |status, reason| on_app_exit(status, reason, exit_on_panic, no_watch),
&dirs,
)
}
pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHandle) -> Result<()> {
let tauri_path = tauri_dir();
pub fn setup(
interface: &AppInterface,
options: &mut Options,
config: &mut ConfigMetadata,
dirs: &Dirs,
) -> Result<()> {
std::thread::spawn(|| {
if let Err(error) = check_mismatched_packages(frontend_dir(), tauri_path) {
if let Err(error) = check_mismatched_packages(dirs.frontend, dirs.tauri) {
log::error!("{error}");
}
});
set_current_dir(tauri_path).context("failed to set current directory")?;
set_current_dir(dirs.tauri).context("failed to set current directory")?;
if let Some(before_dev) = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.before_dev_command
.clone()
{
if let Some(before_dev) = config.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),
@ -161,7 +155,7 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
(Some(script), cwd.map(Into::into), wait)
}
};
let cwd = script_cwd.unwrap_or_else(|| frontend_dir().clone());
let cwd = script_cwd.unwrap_or_else(|| dirs.frontend.to_owned());
if let Some(before_dev) = script {
log::info!(action = "Running"; "BeforeDevCommand (`{}`)", before_dev);
let mut env = command_env(true);
@ -211,22 +205,18 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
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.get().unwrap().load(Ordering::Relaxed)) {
if !(status.success() || KILL_BEFORE_DEV_FLAG.load(Ordering::SeqCst)) {
log::error!("The \"beforeDevCommand\" terminated with a non-zero status code.");
exit(status.code().unwrap_or(1));
}
});
BEFORE_DEV.set(Mutex::new(child)).unwrap();
KILL_BEFORE_DEV_FLAG.set(AtomicBool::default()).unwrap();
let _ = ctrlc::set_handler(move || {
kill_before_dev_process();
exit(130);
@ -236,45 +226,14 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
}
if options.runner.is_none() {
options.runner = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.runner
.clone();
options.runner = config.build.runner.clone();
}
let mut cargo_features = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.features
.clone()
.unwrap_or_default();
if let Some(features) = &options.features {
cargo_features.extend(features.clone());
}
let mut cargo_features = config.build.features.clone().unwrap_or_default();
cargo_features.extend(options.features.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();
let mut dev_url = config.build.dev_url.clone();
let frontend_dist = config.build.frontend_dist.clone();
if !options.no_dev_server && dev_url.is_none() {
if let Some(FrontendDist::Directory(path)) = &frontend_dist {
if path.exists() {
@ -297,19 +256,21 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
}
})));
reload_config(&options.config.iter().map(|c| &c.0).collect::<Vec<_>>())?;
reload_config(
config,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
}
}
}
if !options.no_dev_server_wait {
if let Some(url) = dev_url {
let host = url
.host()
.unwrap_or_else(|| panic!("No host name in the URL"));
let host = url.host().expect("No host name in the URL");
let port = url
.port_or_known_default()
.unwrap_or_else(|| panic!("No port number in the URL"));
.expect("No port number in the URL");
let addrs;
let addr;
let addrs = match host {
@ -352,16 +313,9 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
}
if options.additional_watch_folders.is_empty() {
options.additional_watch_folders.extend(
config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.additional_watch_folders
.clone(),
);
options
.additional_watch_folders
.extend(config.build.additional_watch_folders.clone());
}
Ok(())
@ -379,12 +333,10 @@ pub fn on_app_exit(code: Option<i32>, reason: ExitReason, exit_on_panic: bool, n
pub fn kill_before_dev_process() {
if let Some(child) = BEFORE_DEV.get() {
let child = child.lock().unwrap();
let kill_before_dev_flag = KILL_BEFORE_DEV_FLAG.get().unwrap();
if kill_before_dev_flag.load(Ordering::Relaxed) {
if KILL_BEFORE_DEV_FLAG.load(Ordering::SeqCst) {
return;
}
kill_before_dev_flag.store(true, Ordering::Relaxed);
KILL_BEFORE_DEV_FLAG.store(true, Ordering::SeqCst);
#[cfg(windows)]
{
let powershell_path = std::env::var("SYSTEMROOT").map_or_else(

View File

@ -155,9 +155,9 @@ fn inject_address(html_bytes: Vec<u8>, address: &SocketAddr) -> Vec<u8> {
}
fn fs_read_scoped(path: PathBuf, scope: &Path) -> crate::Result<Vec<u8>> {
let path = dunce::canonicalize(&path).fs_context("failed to canonicalize path", path.clone())?;
let path = dunce::canonicalize(&path).fs_context("failed to canonicalize path", path)?;
if path.starts_with(scope) {
std::fs::read(&path).fs_context("failed to read file", path.clone())
std::fs::read(&path).fs_context("failed to read file", &path)
} else {
crate::error::bail!("forbidden path")
}

View File

@ -23,6 +23,11 @@ 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();
@ -75,21 +80,13 @@ fn lookup<F: Fn(&PathBuf) -> bool>(dir: &Path, checker: F) -> Option<PathBuf> {
}
fn env_tauri_app_path() -> Option<PathBuf> {
std::env::var(ENV_TAURI_APP_PATH)
.map(PathBuf::from)
.ok()?
.canonicalize()
.ok()
.map(|p| dunce::simplified(&p).to_path_buf())
let p = PathBuf::from(std::env::var_os(ENV_TAURI_APP_PATH)?);
dunce::canonicalize(p).ok()
}
fn env_tauri_frontend_path() -> Option<PathBuf> {
std::env::var(ENV_TAURI_FRONTEND_PATH)
.map(PathBuf::from)
.ok()?
.canonicalize()
.ok()
.map(|p| dunce::simplified(&p).to_path_buf())
let p = PathBuf::from(std::env::var_os(ENV_TAURI_FRONTEND_PATH)?);
dunce::canonicalize(p).ok()
}
pub fn resolve_tauri_dir() -> Option<PathBuf> {
@ -130,8 +127,8 @@ pub fn resolve_tauri_dir() -> Option<PathBuf> {
})
}
pub fn resolve() {
TAURI_DIR.set(resolve_tauri_dir().unwrap_or_else(|| {
pub fn resolve_dirs() -> Dirs {
let tauri = TAURI_DIR.get_or_init(|| 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"),
@ -139,16 +136,11 @@ pub fn resolve() {
ConfigFormat::Json5.into_file_name(),
ConfigFormat::Toml.into_file_name()
)
})).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")
}));
let frontend = FRONTEND_DIR.get_or_init(|| {
resolve_frontend_dir().unwrap_or_else(|| tauri.parent().unwrap().to_path_buf())
});
Dirs { tauri, frontend }
}
pub fn resolve_frontend_dir() -> Option<PathBuf> {
@ -173,9 +165,3 @@ 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()
let lock: Option<CargoLock> = get_workspace_dir(tauri_dir)
.ok()
.and_then(|p| fs::read_to_string(p.join("Cargo.lock")).ok())
.and_then(|s| toml::from_str(&s).ok());

View File

@ -12,9 +12,10 @@ pub use tauri_utils::{config::*, platform::Target};
use std::{
collections::HashMap,
env::{current_dir, set_current_dir, set_var},
ffi::OsStr,
ffi::{OsStr, OsString},
path::Path,
process::exit,
sync::{Arc, Mutex, OnceLock},
sync::OnceLock,
};
use crate::error::Context;
@ -30,7 +31,7 @@ pub struct ConfigMetadata {
inner: Config,
/// The config extensions (platform-specific config files or the config CLI argument).
/// Maps the extension name to its value.
extensions: HashMap<String, JsonValue>,
extensions: HashMap<OsString, JsonValue>,
}
impl std::ops::Deref for ConfigMetadata {
@ -50,12 +51,11 @@ impl ConfigMetadata {
}
/// Checks which config is overwriting the bundle identifier.
pub fn find_bundle_identifier_overwriter(&self) -> Option<String> {
pub fn find_bundle_identifier_overwriter(&self) -> Option<OsString> {
for (ext, config) in &self.extensions {
if let Some(identifier) = config
.as_object()
.and_then(|bundle_config| bundle_config.get("identifier"))
.and_then(|id| id.as_str())
.and_then(|bundle_config| bundle_config.get("identifier")?.as_str())
{
if identifier == self.inner.identifier {
return Some(ext.clone());
@ -66,14 +66,11 @@ impl ConfigMetadata {
}
}
pub type ConfigHandle = Arc<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("TAURI_BUNDLER_WIX_FIPS_COMPLIANT")
.ok()
fips_compliant: std::env::var_os("TAURI_BUNDLER_WIX_FIPS_COMPLIANT")
.map(|v| v == "true")
.unwrap_or(config.fips_compliant),
language: tauri_bundler::WixLanguage(match config.language {
@ -141,32 +138,31 @@ pub fn custom_sign_settings(
}
}
fn config_handle() -> &'static ConfigHandle {
static CONFIG_HANDLE: OnceLock<ConfigHandle> = OnceLock::new();
CONFIG_HANDLE.get_or_init(Default::default)
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();
CONFIG_SCHEMA_VALIDATOR.get_or_init(|| {
let schema: JsonValue = serde_json::from_str(include_str!("../../config.schema.json"))
.expect("Failed to parse config schema bundled in the tauri-cli");
jsonschema::validator_for(&schema).expect("Config schema bundled in the tauri-cli is invalid")
})
}
/// Gets the static parsed config from `tauri.conf.json`.
fn get_internal(
fn load_config(
merge_configs: &[&serde_json::Value],
reload: bool,
target: Target,
) -> crate::Result<ConfigHandle> {
if !reload && config_handle().lock().unwrap().is_some() {
return Ok(config_handle().clone());
}
let tauri_dir = super::app_paths::tauri_dir();
tauri_dir: &Path,
) -> crate::Result<ConfigMetadata> {
let (mut config, config_path) =
tauri_utils::config::parse::parse_value(target, tauri_dir.join("tauri.conf.json"))
.context("failed to parse config")?;
let config_file_name = config_path.file_name().unwrap().to_string_lossy();
let config_file_name = config_path.file_name().unwrap();
let mut extensions = HashMap::new();
let original_identifier = config
.as_object()
.and_then(|config| config.get("identifier"))
.and_then(|id| id.as_str())
.and_then(|config| config.get("identifier")?.as_str())
.map(ToString::to_string);
if let Some((platform_config, config_path)) =
@ -174,10 +170,7 @@ fn get_internal(
.context("failed to parse platform config")?
{
merge(&mut config, &platform_config);
extensions.insert(
config_path.file_name().unwrap().to_str().unwrap().into(),
platform_config,
);
extensions.insert(config_path.file_name().unwrap().into(), platform_config);
}
if !merge_configs.is_empty() {
@ -195,17 +188,14 @@ fn get_internal(
if config_path.extension() == Some(OsStr::new("json"))
|| config_path.extension() == Some(OsStr::new("json5"))
{
let schema: JsonValue = serde_json::from_str(include_str!("../../config.schema.json"))
.context("failed to parse config schema")?;
let validator = jsonschema::validator_for(&schema).expect("Invalid schema");
let mut errors = validator.iter_errors(&config).peekable();
let mut errors = config_schema_validator().iter_errors(&config).peekable();
if errors.peek().is_some() {
for error in errors {
let path = error.instance_path.into_iter().join(" > ");
if path.is_empty() {
log::error!("`{}` error: {}", config_file_name, error);
log::error!("`{config_file_name:?}` error: {error}");
} else {
log::error!("`{}` error on `{}`: {}", config_file_name, path, error);
log::error!("`{config_file_name:?}` error on `{path}`: {error}");
}
}
if !reload {
@ -236,59 +226,54 @@ fn get_internal(
std::env::set_var(REMOVE_UNUSED_COMMANDS_ENV_VAR, tauri_dir);
}
*config_handle().lock().unwrap() = Some(ConfigMetadata {
Ok(ConfigMetadata {
target,
original_identifier,
inner: config,
extensions,
});
Ok(config_handle().clone())
})
}
pub fn get(target: Target, merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
get_internal(merge_configs, false, target)
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 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");
}
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(())
}
/// merges the loaded config with the given value
pub fn merge_with(merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
let handle = config_handle();
pub fn merge_config_with(
config: &mut ConfigMetadata,
merge_configs: &[&serde_json::Value],
) -> crate::Result<()> {
if merge_configs.is_empty() {
return Ok(handle.clone());
return Ok(());
}
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.clone())
} else {
crate::error::bail!("config not loaded");
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.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

@ -2,23 +2,25 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use ureq::{http::Response, Body};
use ureq::{http::Response, Agent, Body};
const CLI_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
pub fn get(url: &str) -> Result<Response<Body>, ureq::Error> {
#[allow(unused_mut)]
let mut config_builder = ureq::Agent::config_builder()
.user_agent(CLI_USER_AGENT)
.proxy(ureq::Proxy::try_from_env());
#[cfg(feature = "platform-certs")]
{
let agent = ureq::Agent::config_builder()
.tls_config(
ureq::tls::TlsConfig::builder()
.root_certs(ureq::tls::RootCerts::PlatformVerifier)
.build(),
)
.build()
.new_agent();
agent.get(url).call()
}
#[cfg(not(feature = "platform-certs"))]
{
ureq::get(url).call()
config_builder = config_builder.tls_config(
ureq::tls::TlsConfig::builder()
.root_certs(ureq::tls::RootCerts::PlatformVerifier)
.build(),
);
}
let agent: Agent = config_builder.build().into();
agent.get(url).call()
}

View File

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

View File

@ -332,13 +332,20 @@ impl PackageManager {
version: String,
}
let json: ListOutput = serde_json::from_str(&stdout).context("failed to parse npm list")?;
let json = if matches!(self, PackageManager::Pnpm) {
serde_json::from_str::<Vec<ListOutput>>(&stdout)
.ok()
.and_then(|out| out.into_iter().next())
.context("failed to parse pnpm list")?
} else {
serde_json::from_str::<ListOutput>(&stdout).context("failed to parse npm list")?
};
for (package, dependency) in json.dependencies.into_iter().chain(json.dev_dependencies) {
let version = dependency.version;
if let Ok(version) = semver::Version::parse(&version) {
versions.insert(package, version);
} else {
log::error!("Failed to parse version `{version}` for NPM package `{package}`");
log::debug!("Failed to parse version `{version}` for NPM package `{package}`");
}
}
Ok(versions)
@ -390,7 +397,7 @@ fn yarn_package_versions(
if let Ok(version) = semver::Version::parse(version) {
versions.insert(name.to_owned(), version);
} else {
log::error!("Failed to parse version `{version}` for NPM package `{name}`");
log::debug!("Failed to parse version `{version}` for NPM package `{name}`");
}
}
return Ok(versions);
@ -443,7 +450,7 @@ fn yarn_berry_package_versions(
if let Ok(version) = semver::Version::parse(&version) {
versions.insert(name.to_owned(), version);
} else {
log::error!("Failed to parse version `{version}` for NPM package `{name}`");
log::debug!("Failed to parse version `{version}` for NPM package `{name}`");
}
}
}

View File

@ -120,9 +120,14 @@ where
{
let bin_path = bin_path.as_ref();
// We need to append .sig at the end it's where the signature will be stored
let mut extension = bin_path.extension().unwrap().to_os_string();
extension.push(".sig");
let signature_path = bin_path.with_extension(extension);
// TODO: use with_added_extension when we bump MSRV to > 1.91'
let signature_path = if let Some(ext) = bin_path.extension() {
let mut extension = ext.to_os_string();
extension.push(".sig");
bin_path.with_extension(extension)
} else {
bin_path.with_extension("sig")
};
let trusted_comment = format!(
"timestamp:{}\tfile:{}",
@ -146,10 +151,8 @@ where
std::fs::write(&signature_path, encoded_signature.as_bytes())
.fs_context("failed to write signature file", signature_path.clone())?;
Ok((
fs::canonicalize(&signature_path).fs_context(
"failed to canonicalize signature file",
signature_path.clone(),
)?,
fs::canonicalize(&signature_path)
.fs_context("failed to canonicalize signature file", &signature_path)?,
signature_box,
))
}
@ -203,7 +206,7 @@ where
mod tests {
const PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5dkpDN09RZm5GeVAzc2RuYlNzWVVJelJRQnNIV2JUcGVXZUplWXZXYXpqUUFBQkFBQUFBQUFBQUFBQUlBQUFBQTZrN2RnWGh5dURxSzZiL1ZQSDdNcktiaHRxczQwMXdQelRHbjRNcGVlY1BLMTBxR2dpa3I3dDE1UTVDRDE4MXR4WlQwa1BQaXdxKy9UU2J2QmVSNXhOQWFDeG1GSVllbUNpTGJQRkhhTnROR3I5RmdUZi90OGtvaGhJS1ZTcjdZU0NyYzhQWlQ5cGM9Cg==";
// we use minisign=0.7.3 to prevent a breaking change
// 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");

View File

@ -4,11 +4,11 @@
use crate::{
error::{Context, Error, ErrorExt},
helpers::app_paths::tauri_dir,
Result,
};
use std::{
borrow::Cow,
collections::HashMap,
fs::{create_dir_all, File},
io::{BufWriter, Write},
@ -25,8 +25,9 @@ use image::{
png::{CompressionType, FilterType as PngFilterType, PngEncoder},
},
imageops::FilterType,
open, DynamicImage, ExtendedColorType, GenericImageView, ImageBuffer, ImageEncoder, Rgba,
open, DynamicImage, ExtendedColorType, GenericImageView, ImageBuffer, ImageEncoder, Pixel, Rgba,
};
use rayon::iter::ParallelIterator;
use resvg::{tiny_skia, usvg};
use serde::Deserialize;
@ -123,7 +124,7 @@ impl Source {
}
}
fn resize_exact(&self, size: u32) -> Result<DynamicImage> {
fn resize_exact(&self, size: u32) -> DynamicImage {
match self {
Self::Svg(svg) => {
let mut pixmap = tiny_skia::Pixmap::new(size, size).unwrap();
@ -133,14 +134,49 @@ impl Source {
tiny_skia::Transform::from_scale(scale, scale),
&mut pixmap.as_mut(),
);
let img_buffer = ImageBuffer::from_raw(size, size, pixmap.take()).unwrap();
Ok(DynamicImage::ImageRgba8(img_buffer))
// Switch to use `Pixmap::take_demultiplied` in the future when it's published
// https://github.com/linebender/tiny-skia/blob/624257c0feb394bf6c4d0d688f8ea8030aae320f/src/pixmap.rs#L266
let img_buffer = ImageBuffer::from_par_fn(size, size, |x, y| {
let pixel = pixmap.pixel(x, y).unwrap().demultiply();
Rgba([pixel.red(), pixel.green(), pixel.blue(), pixel.alpha()])
});
DynamicImage::ImageRgba8(img_buffer)
}
Self::DynamicImage(image) => {
// image.resize_exact(size, size, FilterType::Lanczos3)
resize_image(image, size, size)
}
Self::DynamicImage(i) => Ok(i.resize_exact(size, size, FilterType::Lanczos3)),
}
}
}
// `image` does not use premultiplied alpha in resize, so we do it manually here,
// see https://github.com/image-rs/image/issues/1655
fn resize_image(image: &DynamicImage, new_width: u32, new_height: u32) -> DynamicImage {
// Premultiply alpha
let premultiplied_image = ImageBuffer::from_par_fn(image.width(), image.height(), |x, y| {
let mut pixel = image.get_pixel(x, y);
let alpha = pixel.0[3] as f32 / u8::MAX as f32;
pixel.apply_without_alpha(|channel_value| (channel_value as f32 * alpha) as u8);
pixel
});
let mut resized = image::imageops::resize(
&premultiplied_image,
new_width,
new_height,
FilterType::Lanczos3,
);
// Demultiply alpha
resized.par_pixels_mut().for_each(|pixel| {
let alpha = pixel.0[3] as f32 / u8::MAX as f32;
pixel.apply_without_alpha(|channel_value| (channel_value as f32 / alpha) as u8);
});
DynamicImage::ImageRgba8(resized)
}
fn read_source(path: PathBuf) -> Result<Source> {
if let Some(extension) = path.extension() {
if extension == "svg" {
@ -157,7 +193,7 @@ fn read_source(path: PathBuf) -> Result<Source> {
..Default::default()
};
let svg_data = std::fs::read(&path).unwrap();
let svg_data = std::fs::read(&path).fs_context("Failed to read source icon", &path)?;
usvg::Tree::from_data(&svg_data, &opt).unwrap()
};
@ -200,8 +236,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(|| {
crate::helpers::app_paths::resolve();
tauri_dir().join("icons")
let dirs = crate::helpers::app_paths::resolve_dirs();
dirs.tauri.join("icons")
});
let png_icon_sizes = options.png.unwrap_or_default();
@ -243,19 +279,15 @@ pub fn command(options: Options) -> Result<()> {
android(&source, &input, manifest, &bg_color_string, &out_dir)
.context("Failed to generate android icons")?;
} else {
for target in png_icon_sizes
.into_iter()
.map(|size| {
let name = format!("{size}x{size}.png");
let out_path = out_dir.join(&name);
PngEntry {
name,
out_path,
size,
}
})
.collect::<Vec<PngEntry>>()
{
for target in png_icon_sizes.into_iter().map(|size| {
let name = format!("{size}x{size}.png");
let out_path = out_dir.join(&name);
PngEntry {
name,
out_path,
size,
}
}) {
log::info!(action = "PNG"; "Creating {}", target.name);
resize_and_save_png(&source, target.size, &target.out_path, None, None)?;
}
@ -303,7 +335,7 @@ fn icns(source: &Source, out_dir: &Path) -> Result<()> {
let size = entry.size;
let mut buf = Vec::new();
let image = source.resize_exact(size)?;
let image = source.resize_exact(size);
write_png(image.as_bytes(), &mut buf, size).context("failed to write output file")?;
@ -338,7 +370,7 @@ fn ico(source: &Source, out_dir: &Path) -> Result<()> {
let mut frames = Vec::new();
for size in [32, 16, 24, 48, 64, 256] {
let image = source.resize_exact(size)?;
let image = source.resize_exact(size);
// Only the 256px layer can be compressed according to the ico specs.
if size == 256 {
@ -769,7 +801,7 @@ fn resize_png(
bg: Option<Background>,
scale_percent: Option<f32>,
) -> Result<DynamicImage> {
let mut image = source.resize_exact(size)?;
let mut image = source.resize_exact(size);
match bg {
Some(Background::Color(bg_color)) => {
@ -783,7 +815,7 @@ fn resize_png(
image = bg_img.into();
}
Some(Background::Image(bg_source)) => {
let mut bg = bg_source.resize_exact(size)?;
let mut bg = bg_source.resize_exact(size);
let fg = scale_percent
.map(|scale| resize_asset(&image, size, scale))
@ -863,9 +895,10 @@ fn content_bounds(img: &DynamicImage) -> Option<(u32, u32, u32, u32)> {
fn resize_asset(img: &DynamicImage, target_size: u32, scale_percent: f32) -> DynamicImage {
let cropped = if let Some((x, y, cw, ch)) = content_bounds(img) {
img.crop_imm(x, y, cw, ch)
// TODO: Use `&` here instead when we raise MSRV to above 1.79
Cow::Owned(img.crop_imm(x, y, cw, ch))
} else {
img.clone()
Cow::Borrowed(img)
};
let (cw, ch) = cropped.dimensions();
@ -875,7 +908,7 @@ fn resize_asset(img: &DynamicImage, target_size: u32, scale_percent: f32) -> Dyn
let new_w = (cw as f32 * scale).round() as u32;
let new_h = (ch as f32 * scale).round() as u32;
let resized = image::imageops::resize(&cropped, new_w, new_h, image::imageops::Lanczos3);
let resized = resize_image(&cropped, new_w, new_h);
// Place on transparent square canvas
let mut canvas = ImageBuffer::from_pixel(target_size, target_size, Rgba([0, 0, 0, 0]));

View File

@ -3,56 +3,46 @@
// SPDX-License-Identifier: MIT
use super::SectionItem;
use crate::helpers::config::ConfigMetadata;
use crate::helpers::framework;
use std::{
fs::read_to_string,
path::{Path, PathBuf},
};
use tauri_utils::platform::Target;
use std::{fs::read_to_string, path::PathBuf};
pub fn items(frontend_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<SectionItem> {
pub fn items(config: &ConfigMetadata, frontend_dir: Option<&PathBuf>) -> Vec<SectionItem> {
let mut items = Vec::new();
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 bundle_or_build = if config.bundle.active {
"bundle"
} else {
"build"
};
items.push(SectionItem::new().description(format!("build-type: {bundle_or_build}")));
let bundle_or_build = if config.bundle.active {
"bundle"
} else {
"build"
};
items.push(SectionItem::new().description(format!("build-type: {bundle_or_build}")));
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 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(frontend_dist) = &config.build.frontend_dist {
items.push(SectionItem::new().description(format!("frontendDist: {frontend_dist}")));
}
if let Some(frontend_dist) = &config.build.frontend_dist {
items.push(SectionItem::new().description(format!("frontendDist: {frontend_dist}")));
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(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}")));
}
}
if let Some(bundler) = bundler {
items.push(SectionItem::new().description(format!("bundler: {bundler}")));
}
}
}

View File

@ -12,6 +12,7 @@ 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;
@ -265,11 +266,6 @@ 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)
@ -313,9 +309,11 @@ pub fn command(options: Options) -> Result<()> {
interactive,
items: Vec::new(),
};
app
.items
.extend(app::items(frontend_dir.as_ref(), tauri_dir.as_deref()));
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()));
};
}
environment.display();

View File

@ -72,7 +72,10 @@ pub fn installed_tauri_packages(
crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), crate_name).version?;
let crate_version = semver::Version::parse(&crate_version)
.inspect_err(|_| {
log::error!("Failed to parse version `{crate_version}` for crate `{crate_name}`");
// On first run there's no lockfile yet so we get the version requirement from Cargo.toml.
// In our templates that's `2` which is not a valid semver version but a version requirement.
// log::error confused users so we use log::debug to still be able to see this error if needed.
log::debug!("Failed to parse version `{crate_version}` for crate `{crate_name}`");
})
.ok()?;
Some((crate_name.clone(), crate_version))
@ -108,33 +111,27 @@ pub fn items(
) -> Vec<SectionItem> {
let mut items = Vec::new();
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);
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;
}
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);
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);
}
}

View File

@ -78,8 +78,8 @@ impl Options {
let package_json_path = PathBuf::from(&self.directory).join("package.json");
let init_defaults = if package_json_path.exists() {
let package_json_text = read_to_string(&package_json_path)
.fs_context("failed to read", package_json_path.clone())?;
let package_json_text =
read_to_string(&package_json_path).fs_context("failed to read", &package_json_path)?;
let package_json: crate::PackageJson =
serde_json::from_str(&package_json_text).context("failed to parse JSON")?;
let (framework, _) = infer_framework(&package_json_text);

View File

@ -1,14 +1,15 @@
// 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, Interface};
use crate::interface::{AppInterface, AppSettings};
#[derive(Debug, Parser)]
#[clap(about = "Manage or create permissions for your app or plugin")]
#[clap(about = "Inspect values used by Tauri")]
pub struct Cli {
#[clap(subcommand)]
command: Commands,
@ -21,36 +22,34 @@ enum Commands {
}
pub fn command(cli: Cli) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
match cli.command {
Commands::WixUpgradeCode => wix_upgrade_code(),
Commands::WixUpgradeCode => wix_upgrade_code(dirs.tauri),
}
}
// NOTE: if this is ever changed, make sure to also update Wix upgrade code generation in tauri-bundler
fn wix_upgrade_code() -> Result<()> {
crate::helpers::app_paths::resolve();
fn wix_upgrade_code(tauri_dir: &Path) -> Result<()> {
let target = tauri_utils::platform::Target::Windows;
let config = crate::helpers::config::get(target, &[])?;
let config = crate::helpers::config::get_config(target, &[], tauri_dir)?;
let interface = AppInterface::new(config.lock().unwrap().as_ref().unwrap(), None)?;
let interface = AppInterface::new(&config, None, tauri_dir)?;
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.lock().unwrap().as_ref().and_then(|c| {
c.bundle
.windows
.wix
.as_ref()
.and_then(|wix| wix.upgrade_code)
}) {
if let Some(code) = config
.bundle
.windows
.wix
.as_ref()
.and_then(|wix| wix.upgrade_code)
{
log::info!("Application Upgrade Code override: {code}");
}

View File

@ -5,10 +5,8 @@
pub mod rust;
use std::{
collections::HashMap,
path::{Path, PathBuf},
process::ExitStatus,
sync::Arc,
};
use crate::{error::Context, helpers::config::Config};
@ -18,7 +16,6 @@ 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)]
@ -32,9 +29,14 @@ pub trait AppSettings {
options: &Options,
config: &Config,
features: &[String],
tauri_dir: &Path,
) -> crate::Result<tauri_bundler::BundleSettings>;
fn app_binary_path(&self, options: &Options) -> crate::Result<PathBuf>;
fn get_binaries(&self) -> crate::Result<Vec<tauri_bundler::BundleBinary>>;
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_name(&self) -> Option<String>;
fn lib_name(&self) -> Option<String>;
@ -44,9 +46,10 @@ 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().unwrap_or_default();
let mut enabled_features = options.features.clone();
if !no_default_features {
enabled_features.push("default".into());
}
@ -57,7 +60,7 @@ pub trait AppSettings {
tauri_utils::platform::target_triple().context("failed to get target triple")?
};
let mut bins = self.get_binaries()?;
let mut bins = self.get_binaries(&options, tauri_dir)?;
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());
@ -65,7 +68,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)?)
.bundle_settings(self.get_bundle_settings(&options, config, &enabled_features, tauri_dir)?)
.binaries(bins)
.project_out_directory(out_dir)
.target(target)
@ -73,7 +76,7 @@ pub trait AppSettings {
if config.bundle.use_local_tools_dir {
settings_builder = settings_builder.local_tools_directory(
rust::get_cargo_metadata()
rust::get_cargo_metadata(tauri_dir)
.context("failed to get cargo metadata")?
.target_directory,
)
@ -95,27 +98,3 @@ 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,6 +7,7 @@ use std::{
ffi::OsStr,
fs::FileType,
io::{BufRead, Write},
iter::once,
path::{Path, PathBuf},
process::Command,
str::FromStr,
@ -15,7 +16,6 @@ use std::{
};
use dunce::canonicalize;
use glob::glob;
use ignore::gitignore::{Gitignore, GitignoreBuilder};
use notify::RecursiveMode;
use notify_debouncer_full::new_debouncer;
@ -27,12 +27,12 @@ use tauri_bundler::{
};
use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol, RunnerConfig, Updater};
use super::{AppSettings, DevProcess, ExitReason, Interface};
use super::{AppSettings, DevProcess, ExitReason};
use crate::{
error::{Context, Error, ErrorExt},
helpers::{
app_paths::{frontend_dir, tauri_dir},
config::{nsis_settings, reload as reload_config, wix_settings, BundleResources, Config},
app_paths::Dirs,
config::{nsis_settings, reload_config, wix_settings, BundleResources, Config, ConfigMetadata},
},
ConfigValue,
};
@ -51,7 +51,7 @@ pub struct Options {
pub runner: Option<RunnerConfig>,
pub debug: bool,
pub target: Option<String>,
pub features: Option<Vec<String>>,
pub features: Vec<String>,
pub args: Vec<String>,
pub config: Vec<ConfigValue>,
pub no_watch: bool,
@ -108,7 +108,7 @@ impl From<crate::dev::Options> for Options {
#[derive(Debug, Clone)]
pub struct MobileOptions {
pub debug: bool,
pub features: Option<Vec<String>>,
pub features: Vec<String>,
pub args: Vec<String>,
pub config: Vec<ConfigValue>,
pub no_watch: bool,
@ -134,10 +134,8 @@ pub struct Rust {
main_binary_name: Option<String>,
}
impl Interface for Rust {
type AppSettings = RustAppSettings;
fn new(config: &Config, target: Option<String>) -> crate::Result<Self> {
impl Rust {
pub fn new(config: &Config, target: Option<String>, tauri_dir: &Path) -> crate::Result<Self> {
let manifest = {
let (tx, rx) = sync_channel(1);
let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| {
@ -147,14 +145,9 @@ impl Interface for Rust {
})
.unwrap();
watcher
.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)?;
.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, tauri_dir)?;
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));
@ -172,7 +165,7 @@ impl Interface for Rust {
);
}
let app_settings = RustAppSettings::new(config, manifest, target)?;
let app_settings = RustAppSettings::new(config, manifest, target, tauri_dir)?;
Ok(Self {
app_settings: Arc::new(app_settings),
@ -182,24 +175,27 @@ impl Interface for Rust {
})
}
fn app_settings(&self) -> Arc<Self::AppSettings> {
pub fn app_settings(&self) -> Arc<RustAppSettings> {
self.app_settings.clone()
}
fn build(&mut self, options: Options) -> crate::Result<PathBuf> {
pub fn build(&mut self, options: Options, dirs: &Dirs) -> 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,
)
}
fn dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
pub 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);
@ -214,7 +210,7 @@ impl Interface for Rust {
if options.no_watch {
let (tx, rx) = sync_channel(1);
self.run_dev(options, run_args, move |status, reason| {
self.run_dev(options, &run_args, move |status, reason| {
on_exit(status, reason);
tx.send(()).unwrap();
})?;
@ -223,20 +219,31 @@ impl Interface for Rust {
Ok(())
} else {
let merge_configs = options.config.iter().map(|c| &c.0).collect::<Vec<_>>();
let run = Arc::new(|rust: &mut Rust| {
let on_exit = on_exit.clone();
rust.run_dev(options.clone(), run_args.clone(), move |status, reason| {
on_exit(status, reason)
})
});
self.run_dev_watcher(&options.additional_watch_folders, &merge_configs, run)
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,
)
}
}
fn mobile_dev<R: Fn(MobileOptions) -> crate::Result<Box<dyn DevProcess + Send>>>(
pub fn mobile_dev<
R: Fn(MobileOptions, &ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>,
>(
&mut self,
config: &mut ConfigMetadata,
mut options: MobileOptions,
runner: R,
dirs: &Dirs,
) -> crate::Result<()> {
let mut run_args = Vec::new();
dev_options(
@ -248,30 +255,39 @@ impl Interface for Rust {
);
if options.no_watch {
runner(options)?;
runner(options, config)?;
Ok(())
} else {
self.watch(
config,
WatcherOptions {
config: options.config.clone(),
additional_watch_folders: options.additional_watch_folders.clone(),
},
move || runner(options.clone()),
move |config| runner(options.clone(), config),
dirs,
)
}
}
fn watch<R: Fn() -> crate::Result<Box<dyn DevProcess + Send>>>(
pub fn watch<R: Fn(&ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>>(
&mut self,
config: &mut ConfigMetadata,
options: WatcherOptions,
runner: R,
dirs: &Dirs,
) -> crate::Result<()> {
let merge_configs = options.config.iter().map(|c| &c.0).collect::<Vec<_>>();
let run = Arc::new(|_rust: &mut Rust| runner());
self.run_dev_watcher(&options.additional_watch_folders, &merge_configs, run)
self.run_dev_watcher(
config,
&options.additional_watch_folders,
&merge_configs,
|_rust: &mut Rust, config| runner(config),
dirs,
)
}
fn env(&self) -> HashMap<&str, String> {
pub fn env(&self) -> HashMap<&str, String> {
let mut env = HashMap::new();
env.insert(
"TAURI_ENV_TARGET_TRIPLE",
@ -347,7 +363,7 @@ fn build_ignore_matcher(dir: &Path) -> IgnoreMatcher {
ignore_builder.add(path);
if let Ok(ignore_file) = std::env::var("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
if let Some(ignore_file) = std::env::var_os("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
ignore_builder.add(dir.join(ignore_file));
}
@ -379,7 +395,7 @@ fn lookup<F: FnMut(FileType, PathBuf)>(dir: &Path, mut f: F) {
let mut builder = ignore::WalkBuilder::new(dir);
builder.add_custom_ignore_filename(".taurignore");
let _ = builder.add_ignore(default_gitignore);
if let Ok(ignore_file) = std::env::var("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
if let Some(ignore_file) = std::env::var_os("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
builder.add_ignore(ignore_file);
}
builder.require_git(false).ignore(false).max_depth(Some(1));
@ -393,7 +409,7 @@ fn dev_options(
mobile: bool,
args: &mut Vec<String>,
run_args: &mut Vec<String>,
features: &mut Option<Vec<String>>,
features: &mut Vec<String>,
app_settings: &RustAppSettings,
) {
let mut dev_args = Vec::new();
@ -429,35 +445,25 @@ fn dev_options(
})
.collect();
args.push("--no-default-features".into());
if !enable_features.is_empty() {
features.get_or_insert(Vec::new()).extend(enable_features);
}
features.extend(enable_features);
}
}
// Copied from https://github.com/rust-lang/cargo/blob/69255bb10de7f74511b5cef900a9d102247b6029/src/cargo/core/workspace.rs#L665
fn expand_member_path(path: &Path) -> crate::Result<Vec<PathBuf>> {
let path = path.to_str().context("path is not UTF-8 compatible")?;
let res = glob(path).with_context(|| format!("failed to expand glob pattern for {path}"))?;
let res = res
.map(|p| p.with_context(|| format!("failed to expand glob pattern for {path}")))
.collect::<Result<Vec<_>, _>>()?;
Ok(res)
}
fn get_watch_folders(additional_watch_folders: &[PathBuf]) -> crate::Result<Vec<PathBuf>> {
let tauri_path = tauri_dir();
let workspace_path = get_workspace_dir()?;
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_path.to_path_buf()];
let mut watch_folders = vec![tauri_dir.to_path_buf()];
watch_folders.extend(get_in_workspace_dependency_paths(tauri_dir)?);
// Add the additional watch folders, resolving the path from the tauri path if it is relative
watch_folders.extend(additional_watch_folders.iter().filter_map(|dir| {
let path = if dir.is_absolute() {
dir.to_owned()
} else {
tauri_path.join(dir)
tauri_dir.join(dir)
};
let canonicalized = canonicalize(&path).ok();
@ -470,43 +476,12 @@ fn get_watch_folders(additional_watch_folders: &[PathBuf]) -> crate::Result<Vec<
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.to_string());
watch_folders.push(p);
}
};
}
}
Ok(watch_folders)
}
impl Rust {
pub fn build_options(
&self,
args: &mut Vec<String>,
features: &mut Option<Vec<String>>,
mobile: bool,
) {
features
.get_or_insert(Vec::new())
.push("tauri/custom-protocol".into());
pub fn build_options(&self, args: &mut Vec<String>, features: &mut Vec<String>, mobile: bool) {
features.push("tauri/custom-protocol".into());
if mobile {
args.push("--lib".into());
} else {
@ -517,9 +492,9 @@ impl Rust {
fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
&mut self,
options: Options,
run_args: Vec<String>,
run_args: &[String],
on_exit: F,
) -> crate::Result<Box<dyn DevProcess + Send>> {
) -> crate::Result<desktop::DevChild> {
desktop::run_dev(
options,
run_args,
@ -527,25 +502,30 @@ impl Rust {
self.config_features.clone(),
on_exit,
)
.map(|c| Box::new(c) as Box<dyn DevProcess + Send>)
}
fn run_dev_watcher<F: Fn(&mut Rust) -> crate::Result<Box<dyn DevProcess + Send>>>(
fn run_dev_watcher<
F: Fn(&mut Rust, &ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>,
>(
&mut self,
config: &mut ConfigMetadata,
additional_watch_folders: &[PathBuf],
merge_configs: &[&serde_json::Value],
run: Arc<F>,
run: F,
dirs: &Dirs,
) -> crate::Result<()> {
let child = run(self)?;
let process = Arc::new(Mutex::new(child));
let mut child = run(self, config)?;
let (tx, rx) = sync_channel(1);
let frontend_path = frontend_dir();
let watch_folders = get_watch_folders(additional_watch_folders)?;
let watch_folders = get_watch_folders(additional_watch_folders, dirs.tauri)?;
let common_ancestor = common_path::common_path_all(watch_folders.iter().map(Path::new))
.expect("watch_folders should not be empty");
let common_ancestor = common_path::common_path_all(
watch_folders
.iter()
.map(Path::new)
.chain(once(self.app_settings.workspace_dir.as_path())),
)
.expect("watch_folders should not be empty");
let ignore_matcher = build_ignore_matcher(&common_ancestor);
let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| {
@ -582,35 +562,29 @@ impl Rust {
if let Some(event_path) = event.paths.first() {
if !ignore_matcher.is_ignore(event_path, event_path.is_dir()) {
if is_configuration_file(self.app_settings.target_platform, event_path) {
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;
}
if is_configuration_file(self.app_settings.target_platform, event_path)
&& 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;
}
}
log::info!(
"File {} changed. Rebuilding application...",
display_path(event_path.strip_prefix(frontend_path).unwrap_or(event_path))
display_path(event_path.strip_prefix(dirs.frontend).unwrap_or(event_path))
);
let mut p = process.lock().unwrap();
p.kill().context("failed to kill app process")?;
child.kill().context("failed to kill app process")?;
// wait for the process to exit
// note that on mobile, kill() already waits for the process to exit (duct implementation)
loop {
if !matches!(p.try_wait(), Ok(None)) {
break;
}
}
*p = run(self)?;
let _ = child.wait();
child = run(self, config)?;
}
}
}
@ -681,7 +655,7 @@ pub struct TomlWorkspaceField {
#[derive(Clone, Debug, Deserialize)]
struct WorkspaceSettings {
/// the workspace members.
members: Option<Vec<String>>,
// members: Option<Vec<String>>,
package: Option<WorkspacePackageSettings>,
}
@ -695,11 +669,13 @@ struct WorkspacePackageSettings {
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct BinarySettings {
name: String,
/// This is from nightly: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#different-binary-name
filename: Option<String>,
path: Option<String>,
required_features: Option<Vec<String>>,
}
impl BinarySettings {
@ -766,6 +742,7 @@ pub struct RustAppSettings {
cargo_config: CargoConfig,
target_triple: String,
target_platform: TargetPlatform,
workspace_dir: PathBuf,
}
#[derive(Deserialize)]
@ -785,7 +762,7 @@ pub struct UpdaterConfig {
}
/// Install modes for the Windows update.
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub enum WindowsUpdateInstallMode {
/// Specifies there's a basic UI during the installation process, including a final dialog box at the end.
BasicUi,
@ -793,17 +770,12 @@ pub enum WindowsUpdateInstallMode {
/// Requires admin privileges if the installer does.
Quiet,
/// Specifies unattended mode, which means the installation only shows a progress bar.
#[default]
Passive,
// to add more modes, we need to check if the updater relaunch makes sense
// i.e. for a full UI mode, the user can also mark the installer to start the app
}
impl Default for WindowsUpdateInstallMode {
fn default() -> Self {
Self::Passive
}
}
impl<'de> Deserialize<'de> for WindowsUpdateInstallMode {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
@ -849,6 +821,7 @@ 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")
@ -879,6 +852,7 @@ impl AppSettings for RustAppSettings {
self,
features,
config,
tauri_dir,
config.bundle.clone(),
updater_settings,
arch64bits,
@ -923,8 +897,8 @@ impl AppSettings for RustAppSettings {
Ok(settings)
}
fn app_binary_path(&self, options: &Options) -> crate::Result<PathBuf> {
let binaries = self.get_binaries()?;
fn app_binary_path(&self, options: &Options, tauri_dir: &Path) -> crate::Result<PathBuf> {
let binaries = self.get_binaries(options, tauri_dir)?;
let bin_name = binaries
.iter()
.find(|x| x.main())
@ -932,7 +906,7 @@ impl AppSettings for RustAppSettings {
.name();
let out_dir = self
.out_dir(options)
.out_dir(options, tauri_dir)
.context("failed to get project out directory")?;
let mut path = out_dir.join(bin_name);
@ -950,8 +924,8 @@ impl AppSettings for RustAppSettings {
Ok(path)
}
fn get_binaries(&self) -> crate::Result<Vec<BundleBinary>> {
let mut binaries: Vec<BundleBinary> = vec![];
fn get_binaries(&self, options: &Options, tauri_dir: &Path) -> crate::Result<Vec<BundleBinary>> {
let mut binaries = Vec::new();
if let Some(bins) = &self.cargo_settings.bin {
let default_run = self
@ -960,6 +934,15 @@ impl AppSettings for RustAppSettings {
.clone()
.unwrap_or_default();
for bin in bins {
if let Some(req_features) = &bin.required_features {
// Check if all required features are enabled.
if !req_features
.iter()
.all(|feat| options.features.contains(feat))
{
continue;
}
}
let file_name = bin.file_name();
let is_main = file_name == self.cargo_package_settings.name || file_name == default_run;
binaries.push(BundleBinary::with_path(
@ -970,8 +953,6 @@ 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
@ -1063,8 +1044,12 @@ impl AppSettings for RustAppSettings {
}
impl RustAppSettings {
pub fn new(config: &Config, manifest: Manifest, target: Option<String>) -> crate::Result<Self> {
let tauri_dir = tauri_dir();
pub fn new(
config: &Config,
manifest: Manifest,
target: Option<String>,
tauri_dir: &Path,
) -> crate::Result<Self> {
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(),
@ -1075,7 +1060,8 @@ impl RustAppSettings {
}
};
let ws_package_settings = CargoSettings::load(&get_workspace_dir()?)
let workspace_dir = get_workspace_dir(tauri_dir)?;
let ws_package_settings = CargoSettings::load(&workspace_dir)
.context("failed to load Cargo settings from workspace root")?
.workspace
.and_then(|v| v.package);
@ -1170,6 +1156,7 @@ impl RustAppSettings {
cargo_config,
target_triple,
target_platform,
workspace_dir,
})
}
@ -1180,8 +1167,8 @@ impl RustAppSettings {
.or_else(|| self.cargo_config.build().target())
}
pub fn out_dir(&self, options: &Options) -> crate::Result<PathBuf> {
get_target_dir(self.target(options), options)
pub fn out_dir(&self, options: &Options, tauri_dir: &Path) -> crate::Result<PathBuf> {
get_target_dir(self.target(options), options, tauri_dir)
}
}
@ -1189,12 +1176,29 @@ impl RustAppSettings {
pub(crate) struct CargoMetadata {
pub(crate) target_directory: PathBuf,
pub(crate) workspace_root: PathBuf,
workspace_members: Vec<String>,
packages: Vec<Package>,
}
pub(crate) fn get_cargo_metadata() -> crate::Result<CargoMetadata> {
#[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> {
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(),
@ -1211,16 +1215,66 @@ pub(crate) fn get_cargo_metadata() -> crate::Result<CargoMetadata> {
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]) -> crate::Result<PathBuf> {
pub(crate) fn get_cargo_target_dir(args: &[String], tauri_dir: &Path) -> 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()
get_cargo_metadata(tauri_dir)
.context("failed to run 'cargo metadata' command to get target directory")?
.target_directory
};
@ -1230,8 +1284,12 @@ pub(crate) fn get_cargo_target_dir(args: &[String]) -> crate::Result<PathBuf> {
/// 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) -> crate::Result<PathBuf> {
let mut path = get_cargo_target_dir(&options.args)?;
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)?;
if let Some(triple) = triple {
path.push(triple);
@ -1256,9 +1314,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() -> crate::Result<PathBuf> {
pub fn get_workspace_dir(tauri_dir: &Path) -> crate::Result<PathBuf> {
Ok(
get_cargo_metadata()
get_cargo_metadata(tauri_dir)
.context("failed to run 'cargo metadata' command to get workspace directory")?
.workspace_root,
)
@ -1284,6 +1342,7 @@ 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,
@ -1315,7 +1374,7 @@ fn tauri_config_to_bundle_settings(
if enabled_features.contains(&"tray-icon".into())
|| enabled_features.contains(&"tauri/tray-icon".into())
{
let (tray_kind, path) = std::env::var("TAURI_LINUX_AYATANA_APPINDICATOR")
let (tray_kind, path) = std::env::var_os("TAURI_LINUX_AYATANA_APPINDICATOR")
.map(|ayatana| {
if ayatana == "true" || ayatana == "1" {
(
@ -1337,7 +1396,7 @@ fn tauri_config_to_bundle_settings(
)
}
})
.unwrap_or_else(|_| pkgconfig_utils::get_appindicator_library_path());
.unwrap_or_else(pkgconfig_utils::get_appindicator_library_path);
match tray_kind {
pkgconfig_utils::TrayKind::Ayatana => {
depends_deb.push("libayatana-appindicator3-1".into());
@ -1428,14 +1487,16 @@ fn tauri_config_to_bundle_settings(
.map(tauri_bundler::bundle::Entitlements::Path)
} else {
let mut app_links_entitlements = plist::Dictionary::new();
app_links_entitlements.insert(
"com.apple.developer.associated-domains".to_string(),
domains
.into_iter()
.map(|domain| format!("applinks:{domain}").into())
.collect::<Vec<_>>()
.into(),
);
if !domains.is_empty() {
app_links_entitlements.insert(
"com.apple.developer.associated-domains".to_string(),
domains
.into_iter()
.map(|domain| format!("applinks:{domain}").into())
.collect::<Vec<_>>()
.into(),
);
}
let entitlements = if let Some(user_provided_entitlements) = config.macos.entitlements {
crate::helpers::plist::merge_plist(vec![
PathBuf::from(user_provided_entitlements).into(),
@ -1565,7 +1626,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());
}
@ -1607,7 +1668,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()
})
@ -1664,7 +1725,7 @@ mod tests {
#[test]
fn parse_cargo_option() {
let args = vec![
let args = [
"build".into(),
"--".into(),
"--profile".into(),
@ -1744,7 +1805,7 @@ mod tests {
#[test]
fn parse_target_dir_from_opts() {
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
let current_dir = std::env::current_dir().unwrap();
let options = Options {
@ -1761,11 +1822,11 @@ mod tests {
};
assert_eq!(
get_target_dir(None, &options).unwrap(),
get_target_dir(None, &options, dirs.tauri).unwrap(),
current_dir.join("path/to/some/dir/release")
);
assert_eq!(
get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(),
get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri).unwrap(),
current_dir
.join("path/to/some/dir")
.join("x86_64-pc-windows-msvc")
@ -1784,23 +1845,27 @@ mod tests {
};
#[cfg(windows)]
assert!(get_target_dir(Some("x86_64-pc-windows-msvc"), &options)
.unwrap()
.ends_with("x86_64-pc-windows-msvc\\release"));
assert!(
get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri)
.unwrap()
.ends_with("x86_64-pc-windows-msvc\\release")
);
#[cfg(not(windows))]
assert!(get_target_dir(Some("x86_64-pc-windows-msvc"), &options)
.unwrap()
.ends_with("x86_64-pc-windows-msvc/release"));
assert!(
get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri)
.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).unwrap(),
get_target_dir(None, &options, dirs.tauri).unwrap(),
PathBuf::from("D:\\path\\to\\env\\dir\\release")
);
assert_eq!(
get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(),
get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri).unwrap(),
PathBuf::from("D:\\path\\to\\env\\dir\\x86_64-pc-windows-msvc\\release")
);
}
@ -1809,11 +1874,11 @@ mod tests {
{
std::env::set_var("CARGO_TARGET_DIR", "/path/to/env/dir");
assert_eq!(
get_target_dir(None, &options).unwrap(),
get_target_dir(None, &options, dirs.tauri).unwrap(),
PathBuf::from("/path/to/env/dir/release")
);
assert_eq!(
get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(),
get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri).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::PathBuf,
path::{Path, PathBuf},
process::{Command, ExitStatus, Stdio},
sync::{
atomic::{AtomicBool, Ordering},
@ -29,30 +29,26 @@ pub struct DevChild {
impl DevProcess for DevChild {
fn kill(&self) -> std::io::Result<()> {
self.dev_child.kill()?;
self.manually_killed_app.store(true, Ordering::Relaxed);
self.manually_killed_app.store(true, Ordering::SeqCst);
Ok(())
}
fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
self.dev_child.try_wait()
}
fn wait(&self) -> std::io::Result<ExitStatus> {
self.dev_child.wait()
}
fn manually_killed_process(&self) -> bool {
self.manually_killed_app.load(Ordering::Relaxed)
self.manually_killed_app.load(Ordering::SeqCst)
}
}
pub fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
options: Options,
run_args: Vec<String>,
run_args: &[String],
available_targets: &mut Option<Vec<RustupTarget>>,
config_features: Vec<String>,
on_exit: F,
) -> crate::Result<impl DevProcess> {
) -> crate::Result<DevChild> {
let mut dev_cmd = cargo_command(true, options, available_targets, config_features)?;
let runner = dev_cmd.get_program().to_string_lossy().into_owned();
@ -137,7 +133,7 @@ pub fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
status.code(),
if status.code() == Some(101) && is_cargo_compile_error {
ExitReason::CompilationFailed
} else if manually_killed_app_.load(Ordering::Relaxed) {
} else if manually_killed_app_.load(Ordering::SeqCst) {
ExitReason::TriggeredKill
} else {
ExitReason::NormalExit
@ -158,11 +154,12 @@ 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)?;
let bin_path = app_settings.app_binary_path(&options)?;
let out_dir = app_settings.out_dir(&options, tauri_dir)?;
let bin_path = app_settings.app_binary_path(&options, tauri_dir)?;
if !std::env::var("STATIC_VCRUNTIME").is_ok_and(|v| v == "false") {
if !std::env::var_os("STATIC_VCRUNTIME").is_some_and(|v| v == "false") {
std::env::set_var("STATIC_VCRUNTIME", "true");
}
@ -182,7 +179,7 @@ pub fn build(
options.target.replace(triple.into());
let triple_out_dir = app_settings
.out_dir(&options)
.out_dir(&options, tauri_dir)
.with_context(|| format!("failed to get {triple} out dir"))?;
build_production_app(options, available_targets, config_features.clone())
@ -262,9 +259,7 @@ fn cargo_command(
build_cmd.args(&options.args);
let mut features = config_features;
if let Some(f) = options.features {
features.extend(f);
}
features.extend(options.features);
if !features.is_empty() {
build_cmd.arg("--features");
build_cmd.arg(features.join(","));
@ -335,7 +330,7 @@ fn rename_app(
""
};
let new_path = bin_path.with_file_name(format!("{main_binary_name}{extension}"));
fs::rename(&bin_path, &new_path).fs_context("failed to rename app binary", bin_path.clone())?;
fs::rename(&bin_path, &new_path).fs_context("failed to rename app binary", bin_path)?;
Ok(new_path)
} else {
Ok(bin_path)

View File

@ -4,10 +4,7 @@
use crate::{
error::{Context, ErrorExt},
helpers::{
app_paths::tauri_dir,
config::{Config, PatternKind},
},
helpers::config::{Config, PatternKind},
};
use itertools::Itertools;
@ -272,8 +269,8 @@ fn inject_features(
Ok(persist)
}
pub fn rewrite_manifest(config: &Config) -> crate::Result<(Manifest, bool)> {
let manifest_path = tauri_dir().join("Cargo.toml");
pub fn rewrite_manifest(config: &Config, tauri_dir: &Path) -> 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();
@ -314,7 +311,7 @@ pub fn rewrite_manifest(config: &Config) -> crate::Result<(Manifest, bool)> {
if persist && original_manifest_str != new_manifest_str {
std::fs::write(&manifest_path, new_manifest_str)
.fs_context("failed to rewrite Cargo manifest", manifest_path.clone())?;
.fs_context("failed to rewrite Cargo manifest", &manifest_path)?;
Ok((
Manifest {
inner: manifest,
@ -354,10 +351,7 @@ mod tests {
} else {
None
};
if let Some(f) = item_table
.and_then(|t| t.get("features").cloned())
.and_then(|f| f.as_array().cloned())
{
if let Some(f) = item_table.and_then(|t| t.get("features")?.as_array().cloned()) {
for feature in f.iter() {
let feature = feature.as_str().expect("feature is not a string");
if !dep.all_cli_managed_features.contains(&feature) {

View File

@ -65,17 +65,14 @@ impl FromStr for ConfigValue {
let path = PathBuf::from(config);
let raw =
read_to_string(&path).fs_context("failed to read configuration file", path.clone())?;
match path.extension() {
Some(ext) if ext == "toml" => {
Ok(Self(::toml::from_str(&raw).with_context(|| {
format!("failed to parse config at {} as TOML", path.display())
})?))
}
Some(ext) if ext == "json5" => {
Ok(Self(::json5::from_str(&raw).with_context(|| {
format!("failed to parse config at {} as JSON5", path.display())
})?))
}
match path.extension().and_then(|ext| ext.to_str()) {
Some("toml") => Ok(Self(::toml::from_str(&raw).with_context(|| {
format!("failed to parse config at {} as TOML", path.display())
})?)),
Some("json5") => Ok(Self(::json5::from_str(&raw).with_context(|| {
format!("failed to parse config at {} as JSON5", path.display())
})?)),
// treat all other extensions as json
_ => Ok(Self(
// from tauri-utils/src/config/parse.rs:
@ -178,6 +175,13 @@ fn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error {
err.format(&mut app)
}
fn get_verbosity(cli_verbose: u8) -> u8 {
std::env::var("TAURI_CLI_VERBOSITY")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(cli_verbose)
}
/// Run the Tauri CLI with the passed arguments, exiting if an error occurs.
///
/// The passed arguments should have the binary argument(s) stripped out before being passed.
@ -221,16 +225,12 @@ where
Ok(s) => s,
Err(e) => e.exit(),
};
let verbosity_number = std::env::var("TAURI_CLI_VERBOSITY")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(cli.verbose);
// set the verbosity level so subsequent CLI calls (xcode-script, android-studio-script) refer to it
let verbosity_number = get_verbosity(cli.verbose);
std::env::set_var("TAURI_CLI_VERBOSITY", verbosity_number.to_string());
let mut builder = Builder::from_default_env();
let init_res = builder
if let Err(err) = builder
.format_indent(Some(12))
.filter(None, verbosity_level(verbosity_number).to_level_filter())
// golbin spams an insane amount of really technical logs on the debug level so we're reducing one level
@ -250,7 +250,6 @@ where
is_command_output = action == "stdout" || action == "stderr";
if !is_command_output {
let style = Style::new().fg_color(Some(AnsiColor::Green.into())).bold();
write!(f, "{style}{action:>12}{style:#} ")?;
}
} else {
@ -264,15 +263,13 @@ where
if !is_command_output && log::log_enabled!(Level::Debug) {
let style = Style::new().fg_color(Some(AnsiColor::Black.into()));
write!(f, "[{style}{}{style:#}] ", record.target())?;
}
writeln!(f, "{}", record.args())
})
.try_init();
if let Err(err) = init_res {
.try_init()
{
eprintln!("Failed to attach logger: {err}");
}
@ -305,7 +302,7 @@ fn verbosity_level(num: u8) -> Level {
match num {
0 => Level::Info,
1 => Level::Debug,
2.. => Level::Trace,
_ => Level::Trace,
}
}
@ -332,9 +329,15 @@ impl CommandExt for Command {
self.stdin(os_pipe::dup_stdin()?);
self.stdout(os_pipe::dup_stdout()?);
self.stderr(os_pipe::dup_stderr()?);
let program = self.get_program().to_string_lossy().into_owned();
log::debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
let program = self.get_program().to_string_lossy().into_owned();
let args = self
.get_args()
.map(|a| a.to_string_lossy())
.collect::<Vec<_>>()
.join(" ");
log::debug!(action = "Running"; "Command `{program} {args}`");
self.status()
}
@ -342,8 +345,9 @@ impl CommandExt for Command {
let program = self.get_program().to_string_lossy().into_owned();
let args = self
.get_args()
.map(|arg| arg.to_string_lossy())
.fold(String::new(), |acc, arg| format!("{acc} {arg}"));
.map(|a| a.to_string_lossy())
.collect::<Vec<_>>()
.join(" ");
let cmdline = format!("{program} {args}");
log::debug!(action = "Running"; "Command `{cmdline}`");
@ -359,16 +363,17 @@ impl CommandExt for Command {
let stdout_lines_ = stdout_lines.clone();
std::thread::spawn(move || {
let mut line = String::new();
let mut lines = stdout_lines_.lock().unwrap();
loop {
line.clear();
match stdout.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stdout"; "{}", line.trim_end());
lines.extend(line.as_bytes().to_vec());
if let Ok(mut lines) = stdout_lines_.lock() {
loop {
line.clear();
match stdout.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stdout"; "{}", line.trim_end());
lines.extend(line.as_bytes());
}
Err(_) => (),
}
Err(_) => (),
}
}
});
@ -378,16 +383,17 @@ impl CommandExt for Command {
let stderr_lines_ = stderr_lines.clone();
std::thread::spawn(move || {
let mut line = String::new();
let mut lines = stderr_lines_.lock().unwrap();
loop {
line.clear();
match stderr.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stderr"; "{}", line.trim_end());
lines.extend(line.as_bytes().to_vec());
if let Ok(mut lines) = stderr_lines_.lock() {
loop {
line.clear();
match stderr.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stderr"; "{}", line.trim_end());
lines.extend(line.as_bytes());
}
Err(_) => (),
}
Err(_) => (),
}
}
});
@ -423,4 +429,10 @@ mod tests {
fn verify_cli() {
Cli::command().debug_assert();
}
#[test]
fn help_output_includes_build() {
let help = Cli::command().render_help().to_string();
assert!(help.contains("Build"));
}
}

View File

@ -21,7 +21,7 @@ pub fn migrate(tauri_dir: &Path) -> Result<()> {
migrate_manifest(&mut manifest)?;
std::fs::write(&manifest_path, serialize_manifest(&manifest))
.fs_context("failed to rewrite Cargo manifest", manifest_path.clone())?;
.fs_context("failed to rewrite Cargo manifest", &manifest_path)?;
Ok(())
}

View File

@ -2,35 +2,31 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{
error::Context,
helpers::app_paths::{frontend_dir, tauri_dir},
Result,
};
use crate::{error::Context, helpers::app_paths::Dirs, Result};
mod config;
mod frontend;
mod manifest;
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)?;
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)?;
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,
})
crate::add::run(
crate::add::Options {
plugin: plugin.clone(),
branch: None,
tag: None,
rev: None,
no_fmt: false,
},
dirs,
)
.with_context(|| format!("Could not migrate plugin '{plugin}'"))?;
}

View File

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

View File

@ -4,10 +4,7 @@
use crate::{
error::{bail, Context, ErrorExt},
helpers::{
app_paths::tauri_dir,
cargo_manifest::{crate_version, CargoLock, CargoManifest},
},
helpers::cargo_manifest::{crate_version, CargoLock, CargoManifest},
interface::rust::get_workspace_dir,
Result,
};
@ -17,22 +14,20 @@ use std::{fs::read_to_string, str::FromStr};
mod migrations;
pub fn command() -> Result<()> {
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
let tauri_dir = tauri_dir();
let manifest_contents = read_to_string(tauri_dir.join("Cargo.toml")).fs_context(
let manifest_contents = read_to_string(dirs.tauri.join("Cargo.toml")).fs_context(
"failed to read Cargo manifest",
tauri_dir.join("Cargo.toml"),
dirs.tauri.join("Cargo.toml"),
)?;
let manifest = toml::from_str::<CargoManifest>(&manifest_contents).with_context(|| {
format!(
"failed to parse Cargo manifest {}",
tauri_dir.join("Cargo.toml").display()
dirs.tauri.join("Cargo.toml").display()
)
})?;
let workspace_dir = get_workspace_dir()?;
let workspace_dir = get_workspace_dir(dirs.tauri)?;
let lock_path = workspace_dir.join("Cargo.lock");
let lock = if lock_path.exists() {
let lockfile_contents =
@ -44,19 +39,19 @@ pub fn command() -> Result<()> {
None
};
let tauri_version = crate_version(tauri_dir, Some(&manifest), lock.as_ref(), "tauri")
let tauri_version = crate_version(dirs.tauri, 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().context("failed to migrate from v1")?;
migrations::v1::run(&dirs).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().context("failed to migrate from v2 beta")?;
migrations::v2_beta::run(&dirs).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 as get_tauri_config, reload as reload_tauri_config},
interface::{AppInterface, Interface},
helpers::config::{get_config as get_tauri_config, reload_config as reload_tauri_config},
interface::AppInterface,
mobile::CliOptions,
Error, Result,
};
@ -38,7 +38,7 @@ pub struct Options {
}
pub fn command(options: Options) -> Result<()> {
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
let profile = if options.release {
Profile::Release
@ -46,45 +46,33 @@ pub fn command(options: Options) -> Result<()> {
Profile::Debug
};
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_)
};
let mut tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[], dirs.tauri)?;
let cli_options = read_options(&tauri_config);
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)
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 (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_,
None,
&cli_options,
);
(config, metadata)
};
let (config, metadata) = get_config(
&get_app(
MobileTarget::Android,
&tauri_config,
&AppInterface::new(&tauri_config, None, dirs.tauri)?,
dirs.tauri,
),
&tauri_config,
&[],
&cli_options,
);
ensure_init(
&tauri_config,
@ -95,7 +83,8 @@ pub fn command(options: Options) -> Result<()> {
)?;
if !cli_options.config.is_empty() {
crate::helpers::config::merge_with(
crate::helpers::config::merge_config_with(
&mut tauri_config,
&cli_options
.config
.iter()
@ -107,16 +96,7 @@ pub fn command(options: Options) -> Result<()> {
let env = env(std::env::var("CI").is_ok())?;
if cli_options.dev {
let dev_url = tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.dev_url
.clone();
if let Some(url) = dev_url {
if let Some(url) = &tauri_config.build.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::tauri_dir,
config::{get as get_tauri_config, ConfigHandle},
app_paths::Dirs,
config::{get_config as get_tauri_config, ConfigMetadata},
flock,
},
interface::{AppInterface, Interface, Options as InterfaceOptions},
interface::{AppInterface, Options as InterfaceOptions},
mobile::{android::generate_tauri_properties, write_options, CliOptions, TargetDevice},
ConfigValue, Error, Result,
};
@ -27,6 +27,7 @@ use cargo_mobile2::{
};
use std::env::set_current_dir;
use std::path::Path;
#[derive(Debug, Clone, Parser)]
#[clap(
@ -48,7 +49,7 @@ pub struct Options {
pub targets: Option<Vec<String>>,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
pub features: Vec<String>,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
@ -63,10 +64,12 @@ pub struct Options {
pub split_per_abi: bool,
/// Build APKs.
#[clap(long)]
pub apk: Option<bool>,
pub apk: bool,
/// Build AABs.
#[clap(long)]
pub aab: Option<bool>,
pub aab: bool,
#[clap(skip)]
pub skip_bundle: bool,
/// Open Android Studio
#[clap(short, long)]
pub open: bool,
@ -116,8 +119,25 @@ pub struct BuiltApplication {
}
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplication> {
crate::helpers::app_paths::resolve();
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)
}
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();
@ -133,30 +153,16 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
.unwrap();
build_options.target = Some(first_target.triple.into());
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 interface = AppInterface::new(tauri_config, build_options.target.clone(), dirs.tauri)?;
interface.build_options(&mut Vec::new(), &mut build_options.features, true);
let interface = AppInterface::new(tauri_config_, build_options.target.clone())?;
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.as_ref(),
&Default::default(),
);
(interface, config, metadata)
};
let app = get_app(MobileTarget::Android, tauri_config, &interface, dirs.tauri);
let (config, metadata) = get_config(
&app,
tauri_config,
&build_options.features,
&Default::default(),
);
let profile = if options.debug {
Profile::Debug
@ -164,11 +170,10 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
Profile::Release
};
let tauri_path = tauri_dir();
set_current_dir(tauri_path).context("failed to set current directory to Tauri directory")?;
set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?;
ensure_init(
&tauri_config,
tauri_config,
config.app(),
config.project_dir(),
MobileTarget::Android,
@ -178,13 +183,9 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
let mut env = env(options.ci)?;
configure_cargo(&mut env, &config)?;
generate_tauri_properties(
&config,
tauri_config.lock().unwrap().as_ref().unwrap(),
false,
)?;
generate_tauri_properties(&config, tauri_config, false)?;
crate::build::setup(&interface, &mut build_options, tauri_config.clone(), true)?;
crate::build::setup(&interface, &mut build_options, tauri_config, dirs, true)?;
let installed_targets =
crate::interface::rust::installation::installed_targets().unwrap_or_default();
@ -214,6 +215,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
&config,
&mut env,
noise_level,
dirs.tauri,
)?;
if open {
@ -232,16 +234,17 @@ fn run_build(
interface: &AppInterface,
mut options: Options,
build_options: BuildOptions,
tauri_config: ConfigHandle,
tauri_config: &ConfigMetadata,
profile: Profile,
config: &AndroidConfig,
env: &mut Env,
noise_level: NoiseLevel,
tauri_dir: &Path,
) -> Result<OptionsHandle> {
if !(options.apk.is_some() || options.aab.is_some()) {
if !(options.skip_bundle || options.apk || options.aab) {
// if the user didn't specify the format to build, we'll do both
options.apk = Some(true);
options.aab = Some(true);
options.apk = true;
options.aab = true;
}
let interface_options = InterfaceOptions {
@ -252,7 +255,7 @@ fn run_build(
};
let app_settings = interface.app_settings();
let out_dir = app_settings.out_dir(&interface_options)?;
let out_dir = app_settings.out_dir(&interface_options, tauri_dir)?;
let _lock = flock::open_rw(out_dir.join("lock").with_extension("android"), "Android")?;
let cli_options = CliOptions {
@ -264,11 +267,11 @@ fn run_build(
config: build_options.config,
target_device: options.target_device.clone(),
};
let handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?;
let handle = write_options(tauri_config, cli_options)?;
inject_resources(config, tauri_config.lock().unwrap().as_ref().unwrap())?;
inject_resources(config, tauri_config)?;
let apk_outputs = if options.apk.unwrap_or_default() {
let apk_outputs = if options.apk {
apk::build(
config,
env,
@ -282,7 +285,7 @@ fn run_build(
Vec::new()
};
let aab_outputs = if options.aab.unwrap_or_default() {
let aab_outputs = if options.aab {
aab::build(
config,
env,

View File

@ -10,11 +10,11 @@ use crate::{
dev::Options as DevOptions,
error::{Context, ErrorExt},
helpers::{
app_paths::tauri_dir,
config::{get as get_tauri_config, ConfigHandle},
app_paths::Dirs,
config::{get_config as get_tauri_config, ConfigMetadata},
flock,
},
interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions},
interface::{AppInterface, MobileOptions, Options as InterfaceOptions},
mobile::{
android::generate_tauri_properties, use_network_address_for_dev_url, write_options, CliOptions,
DevChild, DevHost, DevProcess, TargetDevice,
@ -45,7 +45,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..))]
pub features: Option<Vec<String>>,
pub features: Vec<String>,
/// Exit on panic
#[clap(short, long)]
exit_on_panic: bool,
@ -131,16 +131,16 @@ impl From<Options> for DevOptions {
}
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
let result = run_command(options, noise_level);
let result = run_command(options, noise_level, dirs);
if result.is_err() {
crate::dev::kill_before_dev_process();
}
result
}
fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<()> {
delete_codegen_vars();
// setup env additions before calling env()
if let Some(root_certificate_path) = &options.root_certificate_path {
@ -160,6 +160,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
dirs.tauri,
)?;
let env = env(false)?;
@ -182,24 +183,17 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
.unwrap_or_else(|| Target::all().values().next().unwrap().triple.into());
dev_options.target = Some(target_triple);
let (interface, config, metadata) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let interface = AppInterface::new(&tauri_config, dev_options.target.clone(), dirs.tauri)?;
let interface = AppInterface::new(tauri_config_, dev_options.target.clone())?;
let app = get_app(MobileTarget::Android, &tauri_config, &interface, dirs.tauri);
let (config, metadata) = get_config(
&app,
&tauri_config,
dev_options.features.as_ref(),
&Default::default(),
);
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")?;
set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?;
ensure_init(
&tauri_config,
@ -218,6 +212,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
&config,
&metadata,
noise_level,
&dirs,
)
}
@ -226,12 +221,13 @@ fn run_dev(
mut interface: AppInterface,
options: Options,
mut dev_options: DevOptions,
tauri_config: ConfigHandle,
mut tauri_config: ConfigMetadata,
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()
@ -239,25 +235,22 @@ fn run_dev(
.as_ref()
.map(|device| !device.serial_no().starts_with("emulator"))
.unwrap_or(false)
|| 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
)
})
|| tauri_config.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(&tauri_config, &mut dev_options, options.force_ip_prompt)?;
use_network_address_for_dev_url(
&mut tauri_config,
&mut dev_options,
options.force_ip_prompt,
dirs.tauri,
)?;
}
crate::dev::setup(&interface, &mut dev_options, tauri_config.clone())?;
crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, dirs)?;
let interface_options = InterfaceOptions {
debug: !dev_options.release_mode,
@ -266,12 +259,12 @@ fn run_dev(
};
let app_settings = interface.app_settings();
let out_dir = app_settings.out_dir(&interface_options)?;
let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?;
let _lock = flock::open_rw(out_dir.join("lock").with_extension("android"), "Android")?;
configure_cargo(&mut env, config)?;
generate_tauri_properties(config, tauri_config.lock().unwrap().as_ref().unwrap(), true)?;
generate_tauri_properties(config, &tauri_config, true)?;
let installed_targets =
crate::interface::rust::installation::installed_targets().unwrap_or_default();
@ -307,6 +300,7 @@ fn run_dev(
let open = options.open;
interface.mobile_dev(
&mut tauri_config,
MobileOptions {
debug: !options.release_mode,
features: options.features,
@ -315,7 +309,7 @@ fn run_dev(
no_watch: options.no_watch,
additional_watch_folders: options.additional_watch_folders,
},
|options| {
|options, tauri_config| {
let cli_options = CliOptions {
dev: true,
features: options.features.clone(),
@ -329,9 +323,9 @@ fn run_dev(
}),
};
let _handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?;
let _handle = write_options(tauri_config, cli_options)?;
inject_resources(config, tauri_config.lock().unwrap().as_ref().unwrap())?;
inject_resources(config, tauri_config)?;
if open {
open_and_wait(config, &env)
@ -347,6 +341,7 @@ fn run_dev(
open_and_wait(config, &env)
}
},
dirs,
)
}

View File

@ -106,16 +106,13 @@ 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) => {
crate::helpers::app_paths::resolve();
init_command(
MobileTarget::Android,
options.ci,
false,
options.skip_targets_install,
options.config,
)?
}
Commands::Init(options) => 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)?,
@ -128,19 +125,14 @@ pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
pub fn get_config(
app: &App,
config: &TauriConfig,
features: Option<&Vec<String>>,
features: &[String],
cli_options: &CliOptions,
) -> (AndroidConfig, AndroidMetadata) {
let mut android_options = cli_options.clone();
if let Some(features) = features {
android_options
.features
.get_or_insert(Vec::new())
.extend_from_slice(features);
}
android_options.features.extend_from_slice(features);
let raw = RawAndroidConfig {
features: android_options.features.clone(),
features: Some(android_options.features.clone()),
logcat_filter_specs: vec![
"RustStdoutStderr".into(),
format!(
@ -161,7 +153,7 @@ pub fn get_config(
let metadata = AndroidMetadata {
supported: true,
cargo_args: Some(android_options.args),
features: android_options.features,
features: Some(android_options.features),
..Default::default()
};
@ -257,8 +249,8 @@ fn ensure_java() -> Result<()> {
fn ensure_sdk(non_interactive: bool) -> Result<()> {
let android_home = std::env::var_os("ANDROID_HOME")
.map(PathBuf::from)
.or_else(|| std::env::var_os("ANDROID_SDK_ROOT").map(PathBuf::from));
.or_else(|| std::env::var_os("ANDROID_SDK_ROOT"))
.map(PathBuf::from);
if !android_home.as_ref().is_some_and(|v| v.exists()) {
log::info!(
"ANDROID_HOME {}, trying to locate Android SDK...",
@ -354,8 +346,8 @@ fn ensure_sdk(non_interactive: bool) -> Result<()> {
fn ensure_ndk(non_interactive: bool) -> Result<()> {
// re-evaluate ANDROID_HOME
let android_home = std::env::var_os("ANDROID_HOME")
.or_else(|| std::env::var_os("ANDROID_SDK_ROOT"))
.map(PathBuf::from)
.or_else(|| std::env::var_os("ANDROID_SDK_ROOT").map(PathBuf::from))
.context("Failed to locate Android SDK")?;
let mut installed_ndks = read_dir(android_home.join("ndk"))
.map(|dir| {

View File

@ -13,7 +13,8 @@ use std::path::PathBuf;
use super::{configure_cargo, device_prompt, env};
use crate::{
error::Context,
interface::{DevProcess, Interface, WatcherOptions},
helpers::config::ConfigMetadata,
interface::{DevProcess, WatcherOptions},
mobile::{DevChild, TargetDevice},
ConfigValue, Result,
};
@ -29,7 +30,7 @@ pub struct Options {
pub release: bool,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
pub features: Vec<String>,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
@ -77,7 +78,17 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
}
};
let mut built_application = super::build::command(
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(
super::build::Options {
debug: !options.release,
targets: device.as_ref().map(|d| {
@ -90,8 +101,9 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
features: options.features,
config: options.config.clone(),
split_per_abi: true,
apk: Some(false),
aab: Some(false),
apk: false,
aab: false,
skip_bundle: false,
open: options.open,
ci: false,
args: options.args,
@ -102,6 +114,8 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
}),
},
noise_level,
&dirs,
&tauri_config,
)?;
configure_cargo(&mut env, &built_application.config)?;
@ -111,7 +125,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
if let Some(device) = device {
let config = built_application.config.clone();
let release = options.release;
let runner = move || {
let runner = move |_tauri_config: &ConfigMetadata| {
device
.run(
&config,
@ -136,14 +150,16 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
};
if options.no_watch {
runner()?;
runner(&tauri_config)?;
} else {
built_application.interface.watch(
&mut tauri_config,
WatcherOptions {
config: options.config,
additional_watch_folders: options.additional_watch_folders,
},
runner,
&dirs,
)?;
}
}

View File

@ -4,8 +4,9 @@
use super::{get_app, Target};
use crate::{
helpers::{config::get as get_tauri_config, template::JsonMap},
interface::{AppInterface, Interface},
helpers::app_paths::Dirs,
helpers::{config::get_config as get_tauri_config, template::JsonMap},
interface::AppInterface,
ConfigValue, Result,
};
use cargo_mobile2::{
@ -29,6 +30,7 @@ pub fn command(
skip_targets_install: bool,
config: Vec<ConfigValue>,
) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
let wrapper = TextWrapper::default();
exec(
@ -38,30 +40,31 @@ pub fn command(
reinstall_deps,
skip_targets_install,
config,
dirs,
)?;
Ok(())
}
pub fn exec(
fn exec(
target: Target,
wrapper: &TextWrapper,
#[allow(unused_variables)] non_interactive: bool,
#[allow(unused_variables)] reinstall_deps: bool,
skip_targets_install: bool,
config: Vec<ConfigValue>,
dirs: Dirs,
) -> 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)?,
&tauri_config,
&AppInterface::new(&tauri_config, None, dirs.tauri)?,
dirs.tauri,
);
let (handlebars, mut map) = handlebars(&app);
@ -135,7 +138,7 @@ pub fn exec(
Target::Android => {
let _env = super::android::env(non_interactive)?;
let (config, metadata) =
super::android::get_config(&app, tauri_config_, None, &Default::default());
super::android::get_config(&app, &tauri_config, &[], &Default::default());
map.insert("android", &config);
super::android::project::gen(
&config,
@ -150,10 +153,10 @@ pub fn exec(
// Generate Xcode project
Target::Ios => {
let (config, metadata) =
super::ios::get_config(&app, tauri_config_, None, &Default::default())?;
super::ios::get_config(&app, &tauri_config, &[], &Default::default(), dirs.tauri)?;
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::tauri_dir,
config::{get as get_tauri_config, ConfigHandle},
app_paths::Dirs,
config::{get_config as get_tauri_config, ConfigMetadata},
flock,
plist::merge_plist,
},
interface::{AppInterface, Interface, Options as InterfaceOptions},
interface::{AppInterface, Options as InterfaceOptions},
mobile::{ios::ensure_ios_runtime_installed, write_options, CliOptions, TargetDevice},
ConfigValue, Error, Result,
};
@ -60,7 +60,7 @@ pub struct Options {
pub targets: Option<Vec<String>>,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
pub features: Vec<String>,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
@ -168,8 +168,11 @@ pub struct BuiltApplication {
}
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplication> {
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
run(options, noise_level, &dirs)
}
pub fn run(options: Options, noise_level: NoiseLevel, dirs: &Dirs) -> Result<BuiltApplication> {
let mut build_options: BuildOptions = options.clone().into();
build_options.target = Some(
Target::all()
@ -189,26 +192,21 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
let tauri_config = get_tauri_config(
tauri_utils::platform::Target::Ios,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
let (interface, mut config) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let interface = AppInterface::new(&tauri_config, build_options.target.clone(), dirs.tauri)?;
interface.build_options(&mut Vec::new(), &mut build_options.features, true);
let interface = AppInterface::new(tauri_config_, build_options.target.clone())?;
interface.build_options(&mut Vec::new(), &mut build_options.features, true);
let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri);
let (mut config, _) = get_config(
&app,
&tauri_config,
&build_options.features,
&Default::default(),
dirs.tauri,
)?;
let app = get_app(MobileTarget::Ios, tauri_config_, &interface);
let (config, _metadata) = get_config(
&app,
tauri_config_,
build_options.features.as_ref(),
&Default::default(),
)?;
(interface, config)
};
let tauri_path = tauri_dir();
set_current_dir(tauri_path).context("failed to set current directory")?;
set_current_dir(dirs.tauri).context("failed to set current directory")?;
ensure_init(
&tauri_config,
@ -217,7 +215,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
MobileTarget::Ios,
options.ci,
)?;
inject_resources(&config, tauri_config.lock().unwrap().as_ref().unwrap())?;
inject_resources(&config, &tauri_config)?;
let mut plist = plist::Dictionary::new();
plist.insert(
@ -231,21 +229,13 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
.join("Info.plist");
let mut src_plists = vec![info_plist_path.clone().into()];
src_plists.push(plist::Value::Dictionary(plist).into());
if tauri_path.join("Info.plist").exists() {
src_plists.push(tauri_path.join("Info.plist").into());
if dirs.tauri.join("Info.plist").exists() {
src_plists.push(dirs.tauri.join("Info.plist").into());
}
if tauri_path.join("Info.ios.plist").exists() {
src_plists.push(tauri_path.join("Info.ios.plist").into());
if dirs.tauri.join("Info.ios.plist").exists() {
src_plists.push(dirs.tauri.join("Info.ios.plist").into());
}
if let Some(info_plist) = &tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.bundle
.ios
.info_plist
{
if let Some(info_plist) = &tauri_config.bundle.ios.info_plist {
src_plists.push(info_plist.clone().into());
}
let merged_info_plist = merge_plist(src_plists)?;
@ -311,7 +301,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
tempfile::NamedTempFile::new().context("failed to create temporary file")?;
let merged_plist = merge_plist(vec![
export_options_plist_path.clone().into(),
export_options_plist_path.into(),
plist::Value::from(export_options_plist).into(),
])?;
merged_plist
@ -338,6 +328,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
&mut config,
&mut env,
noise_level,
&dirs,
)?;
if open {
@ -356,10 +347,11 @@ fn run_build(
interface: &AppInterface,
options: Options,
mut build_options: BuildOptions,
tauri_config: ConfigHandle,
tauri_config: ConfigMetadata,
config: &mut AppleConfig,
env: &mut Env,
noise_level: NoiseLevel,
dirs: &Dirs,
) -> Result<OptionsHandle> {
let profile = if options.debug {
Profile::Debug
@ -367,15 +359,18 @@ fn run_build(
Profile::Release
};
crate::build::setup(interface, &mut build_options, tauri_config.clone(), true)?;
crate::build::setup(interface, &mut build_options, &tauri_config, dirs, 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()
})?;
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 _lock = flock::open_rw(out_dir.join("lock").with_extension("ios"), "iOS")?;
let cli_options = CliOptions {
@ -387,7 +382,7 @@ fn run_build(
config: build_options.config.clone(),
target_device: options.target_device.clone(),
};
let handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?;
let handle = write_options(&tauri_config, 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::tauri_dir,
config::{get as get_tauri_config, ConfigHandle},
app_paths::Dirs,
config::{get_config as get_tauri_config, ConfigMetadata},
flock,
plist::merge_plist,
},
interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions},
interface::{AppInterface, MobileOptions, Options as InterfaceOptions},
mobile::{
ios::ensure_ios_runtime_installed, use_network_address_for_dev_url, write_options, CliOptions,
DevChild, DevHost, DevProcess,
@ -54,7 +54,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..))]
pub features: Option<Vec<String>>,
pub features: Vec<String>,
/// Exit on panic
#[clap(short, long)]
exit_on_panic: bool,
@ -138,16 +138,16 @@ impl From<Options> for DevOptions {
}
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
let result = run_command(options, noise_level);
let result = run_command(options, noise_level, dirs);
if result.is_err() {
crate::dev::kill_before_dev_process();
}
result
}
fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<()> {
// setup env additions before calling env()
if let Some(root_certificate_path) = &options.root_certificate_path {
std::env::set_var(
@ -186,26 +186,20 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
let tauri_config = get_tauri_config(
tauri_utils::platform::Target::Ios,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
dirs.tauri,
)?;
let (interface, config) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let interface = AppInterface::new(&tauri_config, Some(target_triple), dirs.tauri)?;
let interface = AppInterface::new(tauri_config_, Some(target_triple))?;
let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri);
let (config, _) = get_config(
&app,
&tauri_config,
&dev_options.features,
&Default::default(),
dirs.tauri,
)?;
let app = get_app(MobileTarget::Ios, tauri_config_, &interface);
let (config, _metadata) = get_config(
&app,
tauri_config_,
dev_options.features.as_ref(),
&Default::default(),
)?;
(interface, config)
};
let tauri_path = tauri_dir();
set_current_dir(tauri_path).context("failed to set current directory to Tauri directory")?;
set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?;
ensure_init(
&tauri_config,
@ -214,28 +208,20 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
MobileTarget::Ios,
false,
)?;
inject_resources(&config, tauri_config.lock().unwrap().as_ref().unwrap())?;
inject_resources(&config, &tauri_config)?;
let info_plist_path = config
.project_dir()
.join(config.scheme())
.join("Info.plist");
let mut src_plists = vec![info_plist_path.clone().into()];
if tauri_path.join("Info.plist").exists() {
src_plists.push(tauri_path.join("Info.plist").into());
if dirs.tauri.join("Info.plist").exists() {
src_plists.push(dirs.tauri.join("Info.plist").into());
}
if tauri_path.join("Info.ios.plist").exists() {
src_plists.push(tauri_path.join("Info.ios.plist").into());
if dirs.tauri.join("Info.ios.plist").exists() {
src_plists.push(dirs.tauri.join("Info.ios.plist").into());
}
if let Some(info_plist) = &tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.bundle
.ios
.info_plist
{
if let Some(info_plist) = &tauri_config.bundle.ios.info_plist {
src_plists.push(info_plist.clone().into());
}
let merged_info_plist = merge_plist(src_plists)?;
@ -274,6 +260,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
env,
&config,
noise_level,
&dirs,
)
}
@ -282,11 +269,12 @@ fn run_dev(
mut interface: AppInterface,
options: Options,
mut dev_options: DevOptions,
tauri_config: ConfigHandle,
mut tauri_config: ConfigMetadata,
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()
@ -294,38 +282,39 @@ fn run_dev(
.as_ref()
.map(|device| !matches!(device.kind(), DeviceKind::Simulator))
.unwrap_or(false)
|| tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.dev_url
.as_ref()
.is_some_and(|url| {
matches!(
|| tauri_config.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(&tauri_config, &mut dev_options, options.force_ip_prompt)?;
use_network_address_for_dev_url(
&mut tauri_config,
&mut dev_options,
options.force_ip_prompt,
dirs.tauri,
)?;
}
crate::dev::setup(&interface, &mut dev_options, tauri_config.clone())?;
crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, &dirs)?;
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()
})?;
let out_dir = app_settings.out_dir(
&InterfaceOptions {
debug: !dev_options.release_mode,
target: dev_options.target.clone(),
..Default::default()
},
dirs.tauri,
)?;
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,
@ -334,7 +323,7 @@ fn run_dev(
no_watch: options.no_watch,
additional_watch_folders: options.additional_watch_folders,
},
|options| {
|options, tauri_config| {
let cli_options = CliOptions {
dev: true,
features: options.features.clone(),
@ -344,7 +333,7 @@ fn run_dev(
config: dev_options.config.clone(),
target_device: None,
};
let _handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?;
let _handle = write_options(tauri_config, cli_options)?;
let open_xcode = || {
if !set_host {
@ -371,6 +360,7 @@ fn run_dev(
open_xcode()
}
},
&dirs,
)
}

View File

@ -30,8 +30,7 @@ use super::{
use crate::{
error::{Context, ErrorExt},
helpers::{
app_paths::tauri_dir,
config::{BundleResources, Config as TauriConfig, ConfigHandle},
config::{BundleResources, Config as TauriConfig, ConfigMetadata},
pbxproj, strip_semver_prerelease_tag,
},
ConfigValue, Error, Result,
@ -40,7 +39,7 @@ use crate::{
use std::{
env::{set_var, var_os},
fs::create_dir_all,
path::PathBuf,
path::Path,
str::FromStr,
thread::sleep,
time::Duration,
@ -104,16 +103,13 @@ 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) => {
crate::helpers::app_paths::resolve();
init_command(
MobileTarget::Ios,
options.ci,
options.reinstall_deps,
options.skip_targets_install,
options.config,
)?
}
Commands::Init(options) => 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)?,
@ -126,16 +122,12 @@ pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
pub fn get_config(
app: &App,
tauri_config: &TauriConfig,
features: Option<&Vec<String>>,
features: &[String],
cli_options: &CliOptions,
tauri_dir: &Path,
) -> Result<(AppleConfig, AppleMetadata)> {
let mut ios_options = cli_options.clone();
if let Some(features) = features {
ios_options
.features
.get_or_insert(Vec::new())
.extend_from_slice(features);
}
ios_options.features.extend_from_slice(features);
let bundle_version = if let Some(bundle_version) = tauri_config
.bundle
@ -232,7 +224,7 @@ pub fn get_config(
}
}
}),
ios_features: ios_options.features.clone(),
ios_features: Some(ios_options.features.clone()),
bundle_version,
bundle_version_short,
ios_version: Some(tauri_config.bundle.ios.minimum_system_version.clone()),
@ -241,8 +233,6 @@ 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
@ -252,7 +242,7 @@ pub fn get_config(
.clone()
.unwrap_or_default()
{
let framework_path = PathBuf::from(&framework);
let framework_path = Path::new(&framework);
let ext = framework_path.extension().unwrap_or_default();
if ext.is_empty() {
frameworks.push(framework);
@ -277,7 +267,7 @@ pub fn get_config(
supported: true,
ios: ApplePlatform {
cargo_args: Some(ios_options.args),
features: ios_options.features,
features: Some(ios_options.features),
frameworks: Some(frameworks),
vendor_frameworks: Some(vendor_frameworks),
..Default::default()
@ -554,26 +544,14 @@ pub fn load_pbxproj(config: &AppleConfig) -> Result<pbxproj::Pbxproj> {
pub fn synchronize_project_config(
config: &AppleConfig,
tauri_config: &ConfigHandle,
tauri_config: &ConfigMetadata,
pbxproj: &mut pbxproj::Pbxproj,
export_options_plist: &mut plist::Dictionary,
project_config: &ProjectConfig,
debug: bool,
) -> Result<()> {
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 identifier = tauri_config.identifier.clone();
let product_name = tauri_config.product_name.clone();
let manual_signing = project_config.code_sign_identity.is_some()
|| project_config.provisioning_profile_uuid.is_some();

View File

@ -10,7 +10,8 @@ use clap::{ArgAction, Parser};
use super::{device_prompt, env};
use crate::{
error::Context,
interface::{DevProcess, Interface, WatcherOptions},
helpers::config::{get_config as get_tauri_config, ConfigMetadata},
interface::{DevProcess, WatcherOptions},
mobile::{DevChild, TargetDevice},
ConfigValue, Result,
};
@ -26,7 +27,7 @@ pub struct Options {
pub release: bool,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
pub features: Vec<String>,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
@ -73,11 +74,13 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
}
};
let mut built_application = super::build::command(
let dirs = crate::helpers::app_paths::resolve_dirs();
let mut built_application = super::build::run(
super::build::Options {
debug: !options.release,
targets: Some(vec![]), /* skips IPA build since there's no target */
features: None,
features: Vec::new(),
config: options.config.clone(),
build_number: None,
open: options.open,
@ -91,12 +94,19 @@ 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 || {
let runner = move |_tauri_config: &ConfigMetadata| {
device
.run(
&built_application.config,
@ -114,14 +124,16 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
};
if options.no_watch {
runner()?;
runner(&tauri_config)?;
} 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 as get_tauri_config, reload as reload_tauri_config},
interface::{AppInterface, Interface, Options as InterfaceOptions},
helpers::config::{get_config as get_tauri_config, reload_config as reload_tauri_config},
interface::{AppInterface, Options as InterfaceOptions},
mobile::ios::LIB_OUTPUT_FILE_NAME,
Error, Result,
};
@ -89,50 +89,39 @@ pub fn command(options: Options) -> Result<()> {
.unwrap();
}
crate::helpers::app_paths::resolve();
let dirs = crate::helpers::app_paths::resolve_dirs();
let profile = profile_from_configuration(&options.configuration);
let macos = macos_from_platform(&options.platform);
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 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 (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_,
None,
&cli_options,
)?;
(config, metadata)
};
let (config, metadata) = get_config(
&get_app(
MobileTarget::Ios,
&tauri_config,
&AppInterface::new(&tauri_config, None, dirs.tauri)?,
dirs.tauri,
),
&tauri_config,
&[],
&cli_options,
dirs.tauri,
)?;
ensure_init(
&tauri_config,
config.app(),
@ -142,7 +131,8 @@ pub fn command(options: Options) -> Result<()> {
)?;
if !cli_options.config.is_empty() {
crate::helpers::config::merge_with(
crate::helpers::config::merge_config_with(
&mut tauri_config,
&cli_options
.config
.iter()
@ -236,10 +226,7 @@ pub fn command(options: Options) -> Result<()> {
}
};
let interface = AppInterface::new(
tauri_config.lock().unwrap().as_ref().unwrap(),
Some(rust_triple.into()),
)?;
let interface = AppInterface::new(&tauri_config, Some(rust_triple.into()), dirs.tauri)?;
let cflags = format!("CFLAGS_{env_triple}");
let cxxflags = format!("CFLAGS_{env_triple}");
@ -280,11 +267,14 @@ 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()
})?;
let out_dir = interface.app_settings().out_dir(
&InterfaceOptions {
debug: matches!(profile, Profile::Debug),
target: Some(rust_triple.into()),
..Default::default()
},
dirs.tauri,
)?;
let lib_path = out_dir.join(format!("lib{}.a", config.app().lib_name()));
if !lib_path.exists() {

View File

@ -4,11 +4,8 @@
use crate::{
error::{Context, ErrorExt},
helpers::{
app_paths::tauri_dir,
config::{reload as reload_config, Config as TauriConfig, ConfigHandle, ConfigMetadata},
},
interface::{AppInterface, AppSettings, DevProcess, Interface, Options as InterfaceOptions},
helpers::config::{reload_config, Config as TauriConfig, ConfigMetadata},
interface::{AppInterface, AppSettings, DevProcess, Options as InterfaceOptions},
ConfigValue, Error, Result,
};
use heck::ToSnekCase;
@ -31,7 +28,7 @@ use std::{
fmt::{Display, Write},
fs::{read_to_string, write},
net::{AddrParseError, IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
path::{Path, PathBuf},
process::{exit, ExitStatus},
str::FromStr,
sync::{
@ -70,18 +67,9 @@ impl DevChild {
impl DevProcess for DevChild {
fn kill(&self) -> std::io::Result<()> {
self.manually_killed_process.store(true, Ordering::Relaxed);
match self.child.kill() {
Ok(_) => Ok(()),
Err(e) => {
self.manually_killed_process.store(false, Ordering::Relaxed);
Err(e)
}
}
}
fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
self.child.try_wait().map(|res| res.map(|o| o.status))
self.child.kill()?;
self.manually_killed_process.store(true, Ordering::SeqCst);
Ok(())
}
fn wait(&self) -> std::io::Result<ExitStatus> {
@ -89,7 +77,7 @@ impl DevProcess for DevChild {
}
fn manually_killed_process(&self) -> bool {
self.manually_killed_process.load(Ordering::Relaxed)
self.manually_killed_process.load(Ordering::SeqCst)
}
}
@ -181,7 +169,7 @@ impl Default for DevHost {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CliOptions {
pub dev: bool,
pub features: Option<Vec<String>>,
pub features: Vec<String>,
pub args: Vec<String>,
pub noise_level: NoiseLevel,
pub vars: HashMap<String, OsString>,
@ -193,7 +181,7 @@ impl Default for CliOptions {
fn default() -> Self {
Self {
dev: false,
features: None,
features: Vec::new(),
args: vec!["--lib".into()],
noise_level: Default::default(),
vars: Default::default(),
@ -217,12 +205,9 @@ fn local_ip_address(force: bool) -> &'static IpAddr {
})
.collect();
match addresses.len() {
0 => panic!("No external IP detected."),
1 => {
let ipaddr = addresses.first().unwrap();
*ipaddr
}
match addresses.as_slice() {
[] => panic!("No external IP detected."),
[ipaddr] => *ipaddr,
_ => {
let selected = dialoguer::Select::with_theme(&dialoguer::theme::ColorfulTheme::default())
.with_prompt(
@ -252,18 +237,12 @@ struct DevUrlConfig {
}
fn use_network_address_for_dev_url(
config: &ConfigHandle,
config: &mut ConfigMetadata,
dev_options: &mut crate::dev::Options,
force_ip_prompt: bool,
tauri_dir: &Path,
) -> crate::Result<DevUrlConfig> {
let mut dev_url = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.dev_url
.clone();
let mut dev_url = config.build.dev_url.clone();
let ip = if let Some(url) = &mut dev_url {
let localhost = match url.host() {
@ -299,11 +278,13 @@ fn use_network_address_for_dev_url(
})));
reload_config(
config,
&dev_options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
tauri_dir,
)?;
Some(ip)
@ -441,7 +422,12 @@ fn read_options(config: &ConfigMetadata) -> CliOptions {
options
}
pub fn get_app(target: Target, config: &TauriConfig, interface: &AppInterface) -> App {
pub fn get_app(
target: Target,
config: &TauriConfig,
interface: &AppInterface,
tauri_dir: &Path,
) -> App {
let identifier = match target {
Target::Android => config.identifier.replace('-', "_"),
#[cfg(target_os = "macos")]
@ -478,22 +464,26 @@ pub fn get_app(target: Target, config: &TauriConfig, interface: &AppInterface) -
};
let app_settings = interface.app_settings();
App::from_raw(tauri_dir().to_path_buf(), raw)
let tauri_dir = tauri_dir.to_path_buf();
App::from_raw(tauri_dir.to_path_buf(), raw)
.unwrap()
.with_target_dir_resolver(move |target, profile| {
app_settings
.out_dir(&InterfaceOptions {
debug: matches!(profile, Profile::Debug),
target: Some(target.into()),
..Default::default()
})
.out_dir(
&InterfaceOptions {
debug: matches!(profile, Profile::Debug),
target: Some(target.into()),
..Default::default()
},
&tauri_dir,
)
.expect("failed to resolve target directory")
})
}
#[allow(unused_variables)]
fn ensure_init(
tauri_config: &ConfigHandle,
tauri_config: &ConfigMetadata,
app: &App,
project_dir: PathBuf,
target: Target,
@ -508,16 +498,13 @@ fn ensure_init(
)
}
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let mut project_outdated_reasons = Vec::new();
match target {
Target::Android => {
let java_folder = project_dir
.join("app/src/main/java")
.join(tauri_config_.identifier.replace('.', "/").replace('-', "_"));
.join(tauri_config.identifier.replace('.', "/").replace('-', "_"));
if java_folder.exists() {
#[cfg(unix)]
ensure_gradlew(&project_dir)?;

View File

@ -6,11 +6,7 @@ use clap::Parser;
use crate::{
acl,
helpers::{
app_paths::{resolve_frontend_dir, tauri_dir},
cargo,
npm::PackageManager,
},
helpers::{app_paths::resolve_frontend_dir, cargo, npm::PackageManager},
Result,
};
@ -22,11 +18,7 @@ pub struct Options {
}
pub fn command(options: Options) -> Result<()> {
crate::helpers::app_paths::resolve();
run(options)
}
pub fn run(options: Options) -> Result<()> {
let dirs = crate::helpers::app_paths::resolve_dirs();
let plugin = options.plugin;
let crate_name = format!("tauri-plugin-{plugin}");
@ -35,7 +27,6 @@ pub fn run(options: Options) -> Result<()> {
let metadata = plugins.remove(plugin.as_str()).unwrap_or_default();
let frontend_dir = resolve_frontend_dir();
let tauri_dir = tauri_dir();
let target_str = metadata
.desktop_only
@ -48,14 +39,14 @@ pub fn run(options: Options) -> Result<()> {
cargo::uninstall_one(cargo::CargoUninstallOptions {
name: &crate_name,
cwd: Some(tauri_dir),
cwd: Some(dirs.tauri),
target: target_str,
})?;
if !metadata.rust_only {
if let Some(manager) = frontend_dir.map(PackageManager::from_project) {
let npm_name = format!("@tauri-apps/plugin-{plugin}");
manager.remove(&[npm_name], tauri_dir)?;
manager.remove(&[npm_name], dirs.tauri)?;
}
acl::permission::rm::command(acl::permission::rm::Options {

View File

@ -39,26 +39,29 @@ pub fn command(mut options: Options) -> Result<()> {
save_keypair(options.force, output_path, &keypair.sk, &keypair.pk)
.expect("Unable to write keypair");
println!(
"\nYour keypair was generated successfully\nPrivate: {} (Keep it secret!)\nPublic: {}\n---------------------------",
display_path(secret_path),
display_path(public_path)
)
println!();
println!("Your keypair was generated successfully:");
println!("Private: {} (Keep it secret!)", display_path(secret_path));
println!("Public: {}", display_path(public_path));
println!("---------------------------")
} else {
println!(
"\nYour secret key was generated successfully - Keep it secret!\n{}\n\n",
keypair.sk
);
println!(
"Your public key was generated successfully:\n{}\n\nAdd the public key in your tauri.conf.json\n---------------------------\n",
keypair.pk
);
println!();
println!("Your keys were generated successfully!",);
println!();
println!("Private: (Keep it secret!)");
println!("{}", keypair.sk);
println!();
println!("Public:");
println!("{}", keypair.pk);
}
println!("\nEnvironment variables used to sign:");
println!("`TAURI_SIGNING_PRIVATE_KEY` Path or String of your private key");
println!("`TAURI_SIGNING_PRIVATE_KEY_PASSWORD` Your private key password (optional)");
println!("\nATTENTION: If you lose your private key OR password, you'll not be able to sign your update package and updates will not work.\n---------------------------\n");
println!();
println!("Environment variables used to sign:");
println!("- `TAURI_SIGNING_PRIVATE_KEY`: String of your private key");
println!("- `TAURI_SIGNING_PRIVATE_KEY_PATH`: Path to your private key file");
println!("- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`: Your private key password (optional if key has no password)");
println!();
println!("ATTENTION: If you lose your private key OR password, you'll not be able to sign your update package and updates will not work");
Ok(())
}

View File

@ -21,7 +21,7 @@ pub struct Options {
short = 'k',
long,
conflicts_with("private_key_path"),
env = "TAURI_PRIVATE_KEY"
env = "TAURI_SIGNING_PRIVATE_KEY"
)]
private_key: Option<String>,
/// Load the private key from a file
@ -29,17 +29,50 @@ pub struct Options {
short = 'f',
long,
conflicts_with("private_key"),
env = "TAURI_PRIVATE_KEY_PATH"
env = "TAURI_SIGNING_PRIVATE_KEY_PATH"
)]
private_key_path: Option<PathBuf>,
/// Set private key password when signing
#[clap(short, long, env = "TAURI_PRIVATE_KEY_PASSWORD")]
#[clap(short, long, env = "TAURI_SIGNING_PRIVATE_KEY_PASSWORD")]
password: Option<String>,
/// Sign the specified file
file: PathBuf,
}
// Backwards compatibility with old env vars
// TODO: remove in v3.0
fn backward_env_vars(mut options: Options) -> Options {
let get_env = |old, new| {
if let Ok(old_value) = std::env::var(old) {
println!(
"\x1b[33mWarning: The environment variable '{old}' is deprecated. Please use '{new}' instead.\x1b[0m",
);
Some(old_value)
} else {
None
}
};
options.private_key = options
.private_key
.or_else(|| get_env("TAURI_PRIVATE_KEY", "TAURI_SIGNING_PRIVATE_KEY"));
options.private_key_path = options.private_key_path.or_else(|| {
get_env("TAURI_PRIVATE_KEY_PATH", "TAURI_SIGNING_PRIVATE_KEY_PATH").map(PathBuf::from)
});
options.password = options.password.or_else(|| {
get_env(
"TAURI_PRIVATE_KEY_PASSWORD",
"TAURI_SIGNING_PRIVATE_KEY_PASSWORD",
)
});
options
}
pub fn command(mut options: Options) -> Result<()> {
options = backward_env_vars(options);
options.private_key = if let Some(private_key) = options.private_key_path {
Some(std::fs::read_to_string(Path::new(&private_key)).expect("Unable to extract private key"))
} else {

View File

@ -1,5 +1,29 @@
# Changelog
## \[2.5.4]
### Bug Fixes
- [`eb5d88427`](https://www.github.com/tauri-apps/tauri/commit/eb5d88427a7dcb347fb0feae9e816db05b101844) ([#14883](https://www.github.com/tauri-apps/tauri/pull/14883) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `tauri::Context` code generation failing with `can't capture dynamic environment in a fn item` when custom assets are provided.
## \[2.5.3]
### Dependencies
- Upgraded to `tauri-utils@2.8.2`
## \[2.5.2]
### Dependencies
- Upgraded to `tauri-utils@2.8.1`
## \[2.5.1]
### Performance Improvements
- [`8e3bd63db`](https://www.github.com/tauri-apps/tauri/commit/8e3bd63db919a4cf72bb3d28028033d8654deb34) ([#14457](https://www.github.com/tauri-apps/tauri/pull/14457) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Wrap the generated context code in a function to make rust analyzer faster
## \[2.5.0]
### Bug Fixes

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-codegen"
version = "2.5.0"
version = "2.5.4"
description = "code generation meant to be consumed inside of `tauri` through `tauri-build` or `tauri-macros`"
exclude = ["CHANGELOG.md", "/target"]
readme = "README.md"
@ -20,7 +20,7 @@ quote = "1"
syn = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri-utils = { version = "2.8.0", path = "../tauri-utils", features = [
tauri-utils = { version = "2.8.2", path = "../tauri-utils", features = [
"build",
] }
thiserror = "2"
@ -30,7 +30,7 @@ brotli = { version = "8", optional = true, default-features = false, features =
] }
uuid = { version = "1", features = ["v4"] }
semver = "1"
ico = "0.4"
ico = "0.5"
png = "0.17"
json-patch = "3"
url = "2"

View File

@ -453,7 +453,7 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
#[allow(unused_mut, clippy::let_and_return)]
let mut context = #root::Context::new(
#config,
::std::boxed::Box::new(#assets),
::std::boxed::Box::new(assets),
#default_window_icon,
#app_icon,
#package_info,
@ -468,21 +468,30 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
context
});
Ok(quote!({
let thread = ::std::thread::Builder::new()
.name(String::from("generated tauri context creation"))
.stack_size(8 * 1024 * 1024)
.spawn(|| #context)
.expect("unable to create thread with 8MiB stack");
// Wrapping in a function to make rust analyzer faster,
// see https://github.com/tauri-apps/tauri/pull/14457
// We take the assets as an argument so when the caller provides custom `assets` the closure
// does not capture from the caller's scope ("can't capture dynamic environment in a fn item").
let output = quote!({
fn inner<R: #root::Runtime, A: #root::Assets<R> + 'static>(assets: A) -> #root::Context<R> {
let thread = ::std::thread::Builder::new()
.name(String::from("generated tauri context creation"))
.stack_size(8 * 1024 * 1024)
.spawn(move || #context)
.expect("unable to create thread with 8MiB stack");
match thread.join() {
Ok(context) => context,
Err(_) => {
eprintln!("the generated Tauri `Context` panicked during creation");
::std::process::exit(101);
match thread.join() {
Ok(context) => context,
Err(_) => {
eprintln!("the generated Tauri `Context` panicked during creation");
::std::process::exit(101);
}
}
}
}))
inner(#assets)
});
Ok(output)
}
fn find_icon(

View File

@ -1,5 +1,11 @@
# Changelog
## \[2.0.5]
### Bug Fixes
- [`06f911aaf`](https://www.github.com/tauri-apps/tauri/commit/06f911aaff495121f08ebc77d9d1b41382298a1f) ([#14871](https://www.github.com/tauri-apps/tauri/pull/14871) by [@goosewobbler](https://www.github.com/tauri-apps/tauri/../../goosewobbler)) Prevent native WebDriver stdout from polluting tauri-driver's stdout used by test frameworks.
## \[2.0.4]
### Enhancements

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-driver"
version = "2.0.4"
version = "2.0.5"
authors = ["Tauri Programme within The Commons Conservancy"]
categories = ["gui", "web-programming"]
license = "Apache-2.0 OR MIT"
@ -31,8 +31,8 @@ tokio = { version = "1", features = ["macros"] }
which = "8"
[target."cfg(unix)".dependencies]
signal-hook = "0.3"
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
signal-hook = "0.4"
signal-hook-tokio = { version = "0.4", features = ["futures-v0_3"] }
[target."cfg(windows)".dependencies]
win32job = "2"

View File

@ -1,9 +1,12 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// Copyright 2019-2026 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::cli::Args;
use std::{env::current_dir, process::Command};
use std::{
env::current_dir,
process::{Command, Stdio},
};
// the name of the binary to find in $PATH
#[cfg(target_os = "linux")]
@ -48,5 +51,11 @@ pub fn native(args: &Args) -> Command {
cmd.env("TAURI_WEBVIEW_AUTOMATION", "true"); // 2.x
cmd.arg(format!("--port={}", args.native_port));
cmd.arg(format!("--host={}", args.native_host));
// Don't inherit stdout from parent to prevent native WebDriver binary/HTTP protocol data
// from corrupting tauri-driver's stdout (which gets captured by the test framework).
// Keep stderr inherited so WebDriver logs/errors are still visible.
cmd.stdout(Stdio::null());
cmd
}

View File

@ -1,5 +1,23 @@
# Changelog
## \[2.3.3]
### Dependencies
- [`268bb339f`](https://www.github.com/tauri-apps/tauri/commit/268bb339f0c512f021cc94e102573432cf2696d0) ([#14766](https://www.github.com/tauri-apps/tauri/pull/14766) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Remove once-cell-regex from direct dependencies.
## \[2.3.2]
### Dependencies
- [`514cf21e1`](https://www.github.com/tauri-apps/tauri/commit/514cf21e1417c7a78a0db494f891ba79d948b73d) ([#14591](https://www.github.com/tauri-apps/tauri/pull/14591) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) Update num-bigint-dig from 0.8.4 to 0.8.6
## \[2.3.1]
### Performance Improvements
- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.
## \[2.3.0]
### Enhancements

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-macos-sign"
version = "2.3.0"
version = "2.3.3"
authors = ["Tauri Programme within The Commons Conservancy"]
license = "Apache-2.0 OR MIT"
keywords = ["codesign", "signing", "macos", "ios", "tauri"]
@ -15,7 +15,8 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
tempfile = "3"
x509-certificate = "0.23"
once-cell-regex = "0.2"
once_cell = "1"
regex = "1"
os_pipe = "1"
plist = "1"
rand = "0.9"

View File

@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use once_cell_regex::regex;
use once_cell::sync::OnceCell;
use regex::Regex;
use std::{collections::BTreeSet, path::Path, process::Command};
use x509_certificate::certificate::X509Certificate;
@ -49,9 +50,10 @@ impl Team {
organization
} else {
println!(
"found cert {common_name:?} but failed to get organization; falling back to displaying common name"
);
regex!(r"Apple Develop\w+: (.*) \(.+\)")
"found cert {common_name:?} but failed to get organization; falling back to displaying common name"
);
static APPLE_DEV: OnceCell<Regex> = OnceCell::new();
APPLE_DEV.get_or_init(|| Regex::new(r"Apple Develop\w+: (.*) \(.+\)").unwrap())
.captures(&common_name)
.map(|caps| caps[1].to_owned())
.unwrap_or_else(|| {

View File

@ -238,7 +238,7 @@ fn notarize_inner(
String::from_utf8_lossy(&output.stdout)
)))
} else {
Err(Error::Notarize(log_message.to_string()))
Err(Error::Notarize(log_message))
}
} else {
Err(Error::ParseNotarytoolOutput {

View File

@ -1,5 +1,35 @@
# Changelog
## \[2.5.4]
### Dependencies
- Upgraded to `tauri-codegen@2.5.4`
## \[2.5.3]
### Dependencies
- Upgraded to `tauri-utils@2.8.2`
- Upgraded to `tauri-codegen@2.5.3`
## \[2.5.2]
### Dependencies
- Upgraded to `tauri-utils@2.8.1`
- Upgraded to `tauri-codegen@2.5.2`
## \[2.5.1]
### Bug Fixes
- [`4b00130b8`](https://www.github.com/tauri-apps/tauri/commit/4b00130b86a27b6f121bf57897b5e92d83bcc0fc) ([#14385](https://www.github.com/tauri-apps/tauri/pull/14385) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS deadlock when running on the simulator from Xcode by properly piping stdout/stderr messages through the Xcode console and OSLog.
### Dependencies
- Upgraded to `tauri-codegen@2.5.1`
## \[2.5.0]
### Bug Fixes

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-macros"
version = "2.5.0"
version = "2.5.4"
description = "Macros for the tauri crate."
exclude = ["CHANGELOG.md", "/target"]
readme = "README.md"
@ -20,8 +20,8 @@ proc-macro2 = { version = "1", features = ["span-locations"] }
quote = "1"
syn = { version = "2", features = ["full"] }
heck = "0.5"
tauri-codegen = { version = "2.5.0", default-features = false, path = "../tauri-codegen" }
tauri-utils = { version = "2.8.0", path = "../tauri-utils" }
tauri-codegen = { version = "2.5.4", default-features = false, path = "../tauri-codegen" }
tauri-utils = { version = "2.8.2", path = "../tauri-utils" }
[features]
custom-protocol = []

View File

@ -1,5 +1,17 @@
# Changelog
## \[2.5.3]
### Dependencies
- Upgraded to `tauri-utils@2.8.2`
## \[2.5.2]
### Dependencies
- Upgraded to `tauri-utils@2.8.1`
## \[2.5.1]
### Bug Fixes

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-plugin"
version = "2.5.1"
version = "2.5.3"
description = "Build script and runtime Tauri plugin definitions"
authors.workspace = true
homepage.workspace = true
@ -28,7 +28,7 @@ runtime = []
[dependencies]
anyhow = { version = "1", optional = true }
serde = { version = "1", optional = true }
tauri-utils = { version = "2.8.0", default-features = false, features = [
tauri-utils = { version = "2.8.2", default-features = false, features = [
"build",
], path = "../tauri-utils" }
serde_json = { version = "1", optional = true }

View File

@ -1,5 +1,32 @@
# Changelog
## \[2.10.0]
### Bug Fixes
- [`c1d82eb3a`](https://www.github.com/tauri-apps/tauri/commit/c1d82eb3a3fa4b555745ba699edf1cc532030117) ([#14628](https://www.github.com/tauri-apps/tauri/pull/14628) by [@KushalMeghani1644](https://www.github.com/tauri-apps/tauri/../../KushalMeghani1644)) On Linux, keep the WebContext alive to prevent zombie WebKit processes after repeatedly closing all windows and re-opening them.
- [`9b242e40c`](https://www.github.com/tauri-apps/tauri/commit/9b242e40c844189c877a91e513ae6196202d5ae9) ([#14700](https://www.github.com/tauri-apps/tauri/pull/14700) by [@mewi99](https://www.github.com/tauri-apps/tauri/../../mewi99)) Fix compilation errors when targeting BSD.
### Dependencies
- Upgraded to `tauri-utils@2.8.2`
- Upgraded to `tauri-runtime@2.10.0`
- [`75057c7c0`](https://www.github.com/tauri-apps/tauri/commit/75057c7c08f0d4d3dd8d10cea4e2217e5d61fe1a) ([#14778](https://www.github.com/tauri-apps/tauri/pull/14778) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) **Breaking Change** for `with_webview` users: Updated webkit2gtk-rs crates to `v2.0.2`.
- [`75057c7c0`](https://www.github.com/tauri-apps/tauri/commit/75057c7c08f0d4d3dd8d10cea4e2217e5d61fe1a) ([#14778](https://www.github.com/tauri-apps/tauri/pull/14778) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Update wry to `v0.54`.
## \[2.9.3]
### Bug Fixes
- [`251203b89`](https://www.github.com/tauri-apps/tauri/commit/251203b8963419cb3b40741767393e8f3c909ef9) ([#14637](https://www.github.com/tauri-apps/tauri/pull/14637) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `Monitor::work_area` returns logical position and size inside the `PhysicalRect` on Linux
## \[2.9.2]
### Dependencies
- Upgraded to `tauri-utils@2.8.1`
- Upgraded to `tauri-runtime@2.9.2`
## \[2.9.1]
### Bug Fixes

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-runtime-wry"
version = "2.9.1"
version = "2.10.0"
description = "Wry bindings to the Tauri runtime"
exclude = ["CHANGELOG.md", "/target"]
readme = "README.md"
@ -13,15 +13,15 @@ edition.workspace = true
rust-version.workspace = true
[dependencies]
wry = { version = "0.53.4", default-features = false, features = [
wry = { version = "0.54.0", default-features = false, features = [
"drag-drop",
"protocol",
"os-webview",
"linux-body",
] }
tao = { version = "0.34.5", default-features = false, features = ["rwh_06"] }
tauri-runtime = { version = "2.9.1", path = "../tauri-runtime" }
tauri-utils = { version = "2.8.0", path = "../tauri-utils" }
tauri-runtime = { version = "2.10.0", path = "../tauri-runtime" }
tauri-utils = { version = "2.8.2", path = "../tauri-utils" }
raw-window-handle = "0.6"
http = "1"
url = "2"

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