Compare commits

...

79 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
137 changed files with 3310 additions and 2781 deletions

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

@ -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,10 +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'
node-version: 24
- name: cargo login
run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
@ -95,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

658
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,22 @@
# 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

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-build"
version = "2.5.3"
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.2", path = "../tauri-codegen", optional = true }
tauri-utils = { version = "2.8.1", 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,41 @@
# 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

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-bundler"
version = "2.7.4"
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.1", 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.1", 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() {
@ -273,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

@ -1,7 +1,7 @@
#! /usr/bin/env bash
# GTK3 environment variables: https://developer.gnome.org/gtk3/stable/gtk-running.html
# GTK4 environment variables: https://developer.gnome.org/gtk4/stable/gtk-running.html
# 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

View File

@ -0,0 +1,282 @@
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{super::debian, write_and_make_executable};
use crate::{
bundle::settings::Arch,
error::{Context, ErrorExt},
utils::{fs_utils, http_utils::download, CommandExt},
Settings,
};
use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
/// Bundles the project.
/// Returns a vector of PathBuf that shows where the AppImage was created.
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
// generate the deb binary name
let appimage_arch: &str = match settings.binary_arch() {
Arch::X86_64 => "amd64",
Arch::X86 => "i386",
Arch::AArch64 => "aarch64",
Arch::Armhf => "armhf",
target => {
return Err(crate::Error::ArchError(format!(
"Unsupported architecture: {target:?}"
)));
}
};
let tools_arch = if settings.binary_arch() == Arch::Armhf {
"armhf"
} else {
settings.target().split('-').next().unwrap()
};
let output_path = settings.project_out_directory().join("bundle/appimage");
if output_path.exists() {
fs::remove_dir_all(&output_path)?;
}
let tools_path = settings
.local_tools_directory()
.map(|d| d.join(".tauri"))
.unwrap_or_else(|| {
dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join("tauri"))
});
fs::create_dir_all(&tools_path)?;
let linuxdeploy_path = prepare_tools(
&tools_path,
tools_arch,
settings.log_level() != log::Level::Error,
)?;
let package_dir = settings.project_out_directory().join("bundle/appimage_deb");
let main_binary = settings.main_binary()?;
let product_name = settings.product_name();
let mut settings = settings.clone();
if main_binary.name().contains(' ') {
let main_binary_path = settings.binary_path(main_binary);
let project_out_directory = settings.project_out_directory();
let main_binary_name_kebab = heck::AsKebabCase(main_binary.name()).to_string();
let new_path = project_out_directory.join(&main_binary_name_kebab);
fs::copy(main_binary_path, new_path)?;
let main_binary = settings.main_binary_mut()?;
main_binary.set_name(main_binary_name_kebab);
}
// generate deb_folder structure
let (data_dir, icons) = debian::generate_data(&settings, &package_dir)
.with_context(|| "Failed to build data folders and files")?;
fs_utils::copy_custom_files(&settings.appimage().files, &data_dir)
.with_context(|| "Failed to copy custom files")?;
fs::create_dir_all(&output_path)?;
let app_dir_path = output_path.join(format!("{}.AppDir", settings.product_name()));
let appimage_filename = format!(
"{}_{}_{}.AppImage",
settings.product_name(),
settings.version_string(),
appimage_arch
);
let appimage_path = output_path.join(&appimage_filename);
fs_utils::create_dir(&app_dir_path, true)?;
fs::create_dir_all(&tools_path)?;
let larger_icon = icons
.iter()
.filter(|i| i.width == i.height)
.max_by_key(|i| i.width)
.expect("couldn't find a square icon to use as AppImage icon");
let larger_icon_path = larger_icon
.path
.strip_prefix(package_dir.join("data"))
.unwrap()
.to_string_lossy()
.to_string();
log::info!(action = "Bundling"; "{} ({})", appimage_filename, appimage_path.display());
let app_dir_usr = app_dir_path.join("usr/");
let app_dir_usr_bin = app_dir_usr.join("bin/");
let app_dir_usr_lib = app_dir_usr.join("lib/");
fs_utils::copy_dir(&data_dir.join("usr/"), &app_dir_usr)?;
// Using create_dir_all for a single dir so we don't get errors if the path already exists
fs::create_dir_all(&app_dir_usr_bin)?;
fs::create_dir_all(app_dir_usr_lib)?;
// Copy bins and libs that linuxdeploy doesn't know about
// we also check if the user may have provided their own copy already
// xdg-open will be handled by the `files` config instead
if settings.deep_link_protocols().is_some() && !app_dir_usr_bin.join("xdg-open").exists() {
fs::copy("/usr/bin/xdg-mime", app_dir_usr_bin.join("xdg-mime"))
.fs_context("xdg-mime binary not found", "/usr/bin/xdg-mime".to_string())?;
}
// we also check if the user may have provided their own copy already
if settings.appimage().bundle_xdg_open && !app_dir_usr_bin.join("xdg-open").exists() {
fs::copy("/usr/bin/xdg-open", app_dir_usr_bin.join("xdg-open"))
.fs_context("xdg-open binary not found", "/usr/bin/xdg-open".to_string())?;
}
let search_dirs = [
match settings.binary_arch() {
Arch::X86_64 => "/usr/lib/x86_64-linux-gnu/",
Arch::X86 => "/usr/lib/i386-linux-gnu/",
Arch::AArch64 => "/usr/lib/aarch64-linux-gnu/",
Arch::Armhf => "/usr/lib/arm-linux-gnueabihf/",
_ => unreachable!(),
},
"/usr/lib64",
"/usr/lib",
"/usr/libexec",
];
for file in [
"WebKitNetworkProcess",
"WebKitWebProcess",
"injected-bundle/libwebkit2gtkinjectedbundle.so",
] {
for source in search_dirs.map(PathBuf::from) {
// TODO: Check if it's the same dir name on all systems
let source = source.join("webkit2gtk-4.1").join(file);
if source.exists() {
fs_utils::copy_file(
&source,
&app_dir_path.join(source.strip_prefix("/").unwrap()),
)?;
}
}
}
fs::copy(
tools_path.join(format!("AppRun-{tools_arch}")),
app_dir_path.join("AppRun"),
)?;
fs::copy(
app_dir_path.join(larger_icon_path),
app_dir_path.join(format!("{product_name}.png")),
)?;
std::os::unix::fs::symlink(
app_dir_path.join(format!("{product_name}.png")),
app_dir_path.join(".DirIcon"),
)?;
std::os::unix::fs::symlink(
app_dir_path.join(format!("usr/share/applications/{product_name}.desktop")),
app_dir_path.join(format!("{product_name}.desktop")),
)?;
let log_level = match settings.log_level() {
log::Level::Error => "3",
log::Level::Warn => "2",
log::Level::Info => "1",
_ => "0",
};
let mut cmd = Command::new(linuxdeploy_path);
cmd.env("OUTPUT", &appimage_path);
cmd.env("ARCH", tools_arch);
// Looks like the cli arg isn't enough for the updated AppImage output-plugin.
cmd.env("APPIMAGE_EXTRACT_AND_RUN", "1");
cmd.args([
"--appimage-extract-and-run",
"--verbosity",
log_level,
"--appdir",
&app_dir_path.display().to_string(),
"--plugin",
"gtk",
]);
if settings.appimage().bundle_media_framework {
cmd.args(["--plugin", "gstreamer"]);
}
cmd.args(["--output", "appimage"]);
// Linuxdeploy logs everything into stderr so we have to ignore the output ourselves here
if settings.log_level() == log::Level::Error {
log::debug!(action = "Running"; "Command `linuxdeploy {}`", cmd.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
if !cmd.output()?.status.success() {
return Err(crate::Error::GenericError(
"failed to run linuxdeploy".to_string(),
));
}
} else {
cmd.output_ok()?;
}
fs::remove_dir_all(&package_dir)?;
Ok(vec![appimage_path])
}
// returns the linuxdeploy path to keep linuxdeploy_arch contained
fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result<PathBuf> {
let apprun = tools_path.join(format!("AppRun-{arch}"));
if !apprun.exists() {
let data = download(&format!(
"https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-{arch}"
))?;
write_and_make_executable(&apprun, data)?;
}
let linuxdeploy_arch = if arch == "i686" { "i383" } else { arch };
let linuxdeploy = tools_path.join(format!("linuxdeploy-{linuxdeploy_arch}.AppImage"));
if !linuxdeploy.exists() {
let data = download(&format!("https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-{linuxdeploy_arch}.AppImage"))?;
write_and_make_executable(&linuxdeploy, data)?;
}
let gtk = tools_path.join("linuxdeploy-plugin-gtk.sh");
if !gtk.exists() {
let data = download("https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh")?;
write_and_make_executable(&gtk, data)?;
}
let gstreamer = tools_path.join("linuxdeploy-plugin-gstreamer.sh");
if !gstreamer.exists() {
let data = download("https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh")?;
write_and_make_executable(&gstreamer, data)?;
}
let appimage = tools_path.join("linuxdeploy-plugin-appimage.AppImage");
if !appimage.exists() {
// This is optional, linuxdeploy will fall back to its built-in version if the download failed.
let data = download(&format!("https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-{arch}.AppImage"));
match data {
Ok(data) => write_and_make_executable(&appimage, data)?,
Err(err) => {
log::error!("Download of AppImage plugin failed. Using older built-in version instead.");
if verbose {
log::debug!("{err:?}");
}
}
}
}
// This should prevent linuxdeploy to be detected by appimage integration tools
let _ = Command::new("dd")
.args([
"if=/dev/zero",
"bs=1",
"count=3",
"seek=8",
"conv=notrunc",
&format!("of={}", linuxdeploy.display()),
])
.output();
Ok(linuxdeploy)
}

View File

@ -1,287 +1,21 @@
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::debian;
use crate::{
bundle::settings::Arch,
error::{Context, ErrorExt},
utils::{fs_utils, http_utils::download, CommandExt},
Settings,
};
use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
/// Bundles the project.
/// Returns a vector of PathBuf that shows where the AppImage was created.
use crate::Settings;
mod linuxdeploy;
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
// generate the deb binary name
let appimage_arch: &str = match settings.binary_arch() {
Arch::X86_64 => "amd64",
Arch::X86 => "i386",
Arch::AArch64 => "aarch64",
Arch::Armhf => "armhf",
target => {
return Err(crate::Error::ArchError(format!(
"Unsupported architecture: {target:?}"
)));
}
};
let tools_arch = if settings.binary_arch() == Arch::Armhf {
"armhf"
} else {
settings.target().split('-').next().unwrap()
};
let output_path = settings.project_out_directory().join("bundle/appimage");
if output_path.exists() {
fs::remove_dir_all(&output_path)?;
}
let tools_path = settings
.local_tools_directory()
.map(|d| d.join(".tauri"))
.unwrap_or_else(|| {
dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join("tauri"))
});
fs::create_dir_all(&tools_path)?;
let linuxdeploy_path = prepare_tools(
&tools_path,
tools_arch,
settings.log_level() != log::Level::Error,
)?;
let package_dir = settings.project_out_directory().join("bundle/appimage_deb");
let main_binary = settings.main_binary()?;
let product_name = settings.product_name();
let mut settings = settings.clone();
if main_binary.name().contains(' ') {
let main_binary_path = settings.binary_path(main_binary);
let project_out_directory = settings.project_out_directory();
let main_binary_name_kebab = heck::AsKebabCase(main_binary.name()).to_string();
let new_path = project_out_directory.join(&main_binary_name_kebab);
fs::copy(main_binary_path, new_path)?;
let main_binary = settings.main_binary_mut()?;
main_binary.set_name(main_binary_name_kebab);
}
// generate deb_folder structure
let (data_dir, icons) = debian::generate_data(&settings, &package_dir)
.with_context(|| "Failed to build data folders and files")?;
fs_utils::copy_custom_files(&settings.appimage().files, &data_dir)
.with_context(|| "Failed to copy custom files")?;
fs::create_dir_all(&output_path)?;
let app_dir_path = output_path.join(format!("{}.AppDir", settings.product_name()));
let appimage_filename = format!(
"{}_{}_{}.AppImage",
settings.product_name(),
settings.version_string(),
appimage_arch
);
let appimage_path = output_path.join(&appimage_filename);
fs_utils::create_dir(&app_dir_path, true)?;
fs::create_dir_all(&tools_path)?;
let larger_icon = icons
.iter()
.filter(|i| i.width == i.height)
.max_by_key(|i| i.width)
.expect("couldn't find a square icon to use as AppImage icon");
let larger_icon_path = larger_icon
.path
.strip_prefix(package_dir.join("data"))
.unwrap()
.to_string_lossy()
.to_string();
log::info!(action = "Bundling"; "{} ({})", appimage_filename, appimage_path.display());
let app_dir_usr = app_dir_path.join("usr/");
let app_dir_usr_bin = app_dir_usr.join("bin/");
let app_dir_usr_lib = app_dir_usr.join("lib/");
fs_utils::copy_dir(&data_dir.join("usr/"), &app_dir_usr)?;
// Using create_dir_all for a single dir so we don't get errors if the path already exists
fs::create_dir_all(&app_dir_usr_bin)?;
fs::create_dir_all(app_dir_usr_lib)?;
// Copy bins and libs that linuxdeploy doesn't know about
// we also check if the user may have provided their own copy already
// xdg-open will be handled by the `files` config instead
if settings.deep_link_protocols().is_some() && !app_dir_usr_bin.join("xdg-open").exists() {
fs::copy("/usr/bin/xdg-mime", app_dir_usr_bin.join("xdg-mime"))
.fs_context("xdg-mime binary not found", "/usr/bin/xdg-mime".to_string())?;
}
// we also check if the user may have provided their own copy already
if settings.appimage().bundle_xdg_open && !app_dir_usr_bin.join("xdg-open").exists() {
fs::copy("/usr/bin/xdg-open", app_dir_usr_bin.join("xdg-open"))
.fs_context("xdg-open binary not found", "/usr/bin/xdg-open".to_string())?;
}
let search_dirs = [
match settings.binary_arch() {
Arch::X86_64 => "/usr/lib/x86_64-linux-gnu/",
Arch::X86 => "/usr/lib/i386-linux-gnu/",
Arch::AArch64 => "/usr/lib/aarch64-linux-gnu/",
Arch::Armhf => "/usr/lib/arm-linux-gnueabihf/",
_ => unreachable!(),
},
"/usr/lib64",
"/usr/lib",
"/usr/libexec",
];
for file in [
"WebKitNetworkProcess",
"WebKitWebProcess",
"injected-bundle/libwebkit2gtkinjectedbundle.so",
] {
for source in search_dirs.map(PathBuf::from) {
// TODO: Check if it's the same dir name on all systems
let source = source.join("webkit2gtk-4.1").join(file);
if source.exists() {
fs_utils::copy_file(
&source,
&app_dir_path.join(source.strip_prefix("/").unwrap()),
)?;
}
}
}
fs::copy(
tools_path.join(format!("AppRun-{tools_arch}")),
app_dir_path.join("AppRun"),
)?;
fs::copy(
app_dir_path.join(larger_icon_path),
app_dir_path.join(format!("{product_name}.png")),
)?;
std::os::unix::fs::symlink(
app_dir_path.join(format!("{product_name}.png")),
app_dir_path.join(".DirIcon"),
)?;
std::os::unix::fs::symlink(
app_dir_path.join(format!("usr/share/applications/{product_name}.desktop")),
app_dir_path.join(format!("{product_name}.desktop")),
)?;
let log_level = match settings.log_level() {
log::Level::Error => "3",
log::Level::Warn => "2",
log::Level::Info => "1",
_ => "0",
};
let mut cmd = Command::new(linuxdeploy_path);
cmd.env("OUTPUT", &appimage_path);
cmd.env("ARCH", tools_arch);
// Looks like the cli arg isn't enough for the updated AppImage output-plugin.
cmd.env("APPIMAGE_EXTRACT_AND_RUN", "1");
cmd.args([
"--appimage-extract-and-run",
"--verbosity",
log_level,
"--appdir",
&app_dir_path.display().to_string(),
"--plugin",
"gtk",
]);
if settings.appimage().bundle_media_framework {
cmd.args(["--plugin", "gstreamer"]);
}
cmd.args(["--output", "appimage"]);
// Linuxdeploy logs everything into stderr so we have to ignore the output ourselves here
if settings.log_level() == log::Level::Error {
log::debug!(action = "Running"; "Command `linuxdeploy {}`", cmd.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
if !cmd.output()?.status.success() {
return Err(crate::Error::GenericError(
"failed to run linuxdeploy".to_string(),
));
}
} else {
cmd.output_ok()?;
}
fs::remove_dir_all(&package_dir)?;
Ok(vec![appimage_path])
linuxdeploy::bundle_project(settings)
}
// returns the linuxdeploy path to keep linuxdeploy_arch contained
fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result<PathBuf> {
let apprun = tools_path.join(format!("AppRun-{arch}"));
if !apprun.exists() {
let data = download(&format!(
"https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-{arch}"
))?;
write_and_make_executable(&apprun, &data)?;
}
let linuxdeploy_arch = if arch == "i686" { "i386" } else { arch };
let linuxdeploy = tools_path.join(format!("linuxdeploy-{linuxdeploy_arch}.AppImage"));
if !linuxdeploy.exists() {
let data = download(&format!("https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-{linuxdeploy_arch}.AppImage"))?;
write_and_make_executable(&linuxdeploy, &data)?;
}
let gtk = tools_path.join("linuxdeploy-plugin-gtk.sh");
if !gtk.exists() {
let data = include_bytes!("./linuxdeploy-plugin-gtk.sh");
write_and_make_executable(&gtk, data)?;
}
let gstreamer = tools_path.join("linuxdeploy-plugin-gstreamer.sh");
if !gstreamer.exists() {
let data = include_bytes!("./linuxdeploy-plugin-gstreamer.sh");
write_and_make_executable(&gstreamer, data)?;
}
let appimage = tools_path.join("linuxdeploy-plugin-appimage.AppImage");
if !appimage.exists() {
// This is optional, linuxdeploy will fall back to its built-in version if the download failed.
let data = download(&format!("https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-{arch}.AppImage"));
match data {
Ok(data) => write_and_make_executable(&appimage, &data)?,
Err(err) => {
log::error!("Download of AppImage plugin failed. Using older built-in version instead.");
if verbose {
log::debug!("{err:?}");
}
}
}
}
// This should prevent linuxdeploy to be detected by appimage integration tools
let _ = Command::new("dd")
.args([
"if=/dev/zero",
"bs=1",
"count=3",
"seek=8",
"conv=notrunc",
&format!("of={}", linuxdeploy.display()),
])
.output();
Ok(linuxdeploy)
}
fn write_and_make_executable(path: &Path, data: &[u8]) -> std::io::Result<()> {
fn write_and_make_executable(path: &Path, data: Vec<u8>) -> std::io::Result<()> {
use std::os::unix::fs::PermissionsExt;
fs::write(path, data)?;

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

@ -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

@ -40,8 +40,8 @@ const NSIS_URL: &str =
#[cfg(target_os = "windows")]
const NSIS_SHA1: &str = "EF7FF767E5CBD9EDD22ADD3A32C9B8F4500BB10D";
const NSIS_TAURI_UTILS_URL: &str =
"https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.5.2/nsis_tauri_utils.dll";
const NSIS_TAURI_UTILS_SHA1: &str = "D0C502F45DF55C0465C9406088FF016C2E7E6817";
"https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.5.3/nsis_tauri_utils.dll";
const NSIS_TAURI_UTILS_SHA1: &str = "75197FEE3C6A814FE035788D1C34EAD39349B860";
#[cfg(target_os = "windows")]
const NSIS_REQUIRED_FILES: &[&str] = &[
@ -298,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();
@ -617,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());
}
}
}
}
@ -860,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

@ -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

@ -99,19 +99,13 @@ pub enum Error {
#[error("Wrong package type {0} for platform {1}")]
InvalidPackageType(String, String),
/// Bundle type symbol missing in binary
#[cfg_attr(
target_os = "linux",
error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date and that symbol stripping is disabled (https://doc.rust-lang.org/cargo/reference/profiles.html#strip)")
)]
#[cfg_attr(
not(target_os = "linux"),
error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")
)]
#[error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")]
MissingBundleTypeVar,
/// Failed to write binary file changed
#[error("Failed to write binary file changes: `{0}`")]
BinaryWriteError(String),
/// Invalid offset while patching binary file
#[deprecated]
#[error("Invalid offset while patching binary file")]
BinaryOffsetOutOfRange,
/// Unsupported architecture.

View File

@ -1,5 +1,58 @@
# 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

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-cli"
version = "2.9.5"
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.4", 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.1", path = "../tauri-utils", features = [
tauri-utils = { version = "2.8.2", path = "../tauri-utils", features = [
"isolation",
"schema",
"config-json5",
@ -66,7 +66,7 @@ tauri-utils = { version = "2.8.1", 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"
@ -133,7 +133,7 @@ libc = "0.2"
[target."cfg(target_os = \"macos\")".dependencies]
plist = "1"
tauri-macos-sign = { version = "2.3.1", 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

@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://schema.tauri.app/config/2.9.4",
"$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.5",
"version": "2.10.0",
"node": ">= 10.0.0"
},
"tauri": "2.9.4",
"tauri-build": "2.5.3",
"tauri-plugin": "2.5.2"
"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,9 +24,10 @@ 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");

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);

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

@ -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

@ -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

@ -345,7 +345,7 @@ impl PackageManager {
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)
@ -397,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);
@ -450,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:{}",

View File

@ -4,7 +4,6 @@
use crate::{
error::{Context, Error, ErrorExt},
helpers::app_paths::tauri_dir,
Result,
};
@ -237,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();

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

@ -111,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

@ -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, options: &Options) -> 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(&options)?;
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>,
}
@ -768,6 +742,7 @@ pub struct RustAppSettings {
cargo_config: CargoConfig,
target_triple: String,
target_platform: TargetPlatform,
workspace_dir: PathBuf,
}
#[derive(Deserialize)]
@ -846,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")
@ -876,6 +852,7 @@ impl AppSettings for RustAppSettings {
self,
features,
config,
tauri_dir,
config.bundle.clone(),
updater_settings,
arch64bits,
@ -920,8 +897,8 @@ impl AppSettings for RustAppSettings {
Ok(settings)
}
fn app_binary_path(&self, options: &Options) -> crate::Result<PathBuf> {
let binaries = self.get_binaries(options)?;
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())
@ -929,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);
@ -947,7 +924,7 @@ impl AppSettings for RustAppSettings {
Ok(path)
}
fn get_binaries(&self, options: &Options) -> crate::Result<Vec<BundleBinary>> {
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 {
@ -957,11 +934,12 @@ impl AppSettings for RustAppSettings {
.clone()
.unwrap_or_default();
for bin in bins {
if let (Some(req_features), Some(opt_features)) =
(&bin.required_features, &options.features)
{
if let Some(req_features) = &bin.required_features {
// Check if all required features are enabled.
if !req_features.iter().all(|feat| opt_features.contains(feat)) {
if !req_features
.iter()
.all(|feat| options.features.contains(feat))
{
continue;
}
}
@ -975,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
@ -1068,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(),
@ -1080,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);
@ -1175,6 +1156,7 @@ impl RustAppSettings {
cargo_config,
target_triple,
target_platform,
workspace_dir,
})
}
@ -1185,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)
}
}
@ -1194,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(),
@ -1216,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
};
@ -1235,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);
@ -1261,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,
)
@ -1289,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,
@ -1320,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" {
(
@ -1342,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());
@ -1433,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(),
@ -1570,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());
}
@ -1612,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()
})
@ -1749,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 {
@ -1766,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")
@ -1789,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")
);
}
@ -1814,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(","));

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();
@ -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

@ -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,17 +13,14 @@ 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)?;

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)?;
@ -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,17 @@
# 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

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-codegen"
version = "2.5.2"
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.1", 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,14 +468,16 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
context
});
Ok(quote!({
// Wrapping in a function to make rust analyzer faster,
// see https://github.com/tauri-apps/tauri/pull/14457
fn inner<R: #root::Runtime>() -> #root::Context<R> {
// 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(|| #context)
.spawn(move || #context)
.expect("unable to create thread with 8MiB stack");
match thread.join() {
@ -486,8 +488,10 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
}
}
}
inner()
}))
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,17 @@
# 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

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-macos-sign"
version = "2.3.1"
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

@ -1,5 +1,18 @@
# 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

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-macros"
version = "2.5.2"
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.2", default-features = false, path = "../tauri-codegen" }
tauri-utils = { version = "2.8.1", 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,11 @@
# Changelog
## \[2.5.3]
### Dependencies
- Upgraded to `tauri-utils@2.8.2`
## \[2.5.2]
### Dependencies

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-plugin"
version = "2.5.2"
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.1", 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,25 @@
# 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

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-runtime-wry"
version = "2.9.2"
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.2", path = "../tauri-runtime" }
tauri-utils = { version = "2.8.1", 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"

View File

@ -37,7 +37,13 @@ use tauri_runtime::{
use objc2::rc::Retained;
#[cfg(target_os = "macos")]
use tao::platform::macos::{EventLoopWindowTargetExtMacOS, WindowBuilderExtMacOS};
#[cfg(target_os = "linux")]
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use tao::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
#[cfg(windows)]
use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};
@ -862,7 +868,13 @@ impl WindowBuilder for WindowBuilderWrapper {
");
}
#[cfg(target_os = "linux")]
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
// Mouse event is disabled on Linux since sudden event bursts could block event loop.
window.inner = window.inner.with_cursor_moved_event(false);
@ -1194,7 +1206,14 @@ impl WindowBuilder for WindowBuilderWrapper {
self
}
#[cfg(any(windows, target_os = "linux"))]
#[cfg(any(
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
fn skip_taskbar(mut self, skip: bool) -> Self {
self.inner = self.inner.with_skip_taskbar(skip);
self
@ -2422,6 +2441,18 @@ impl Drop for WebviewWrapper {
if let Some(web_context) = context_store.get_mut(&self.context_key) {
web_context.referenced_by_webviews.remove(&self.label);
// https://github.com/tauri-apps/tauri/issues/14626
// Because WebKit does not close its network process even when no webviews are running,
// we need to ensure to re-use the existing process on Linux by keeping the WebContext
// alive for the lifetime of the app.
// WebKit on macOS handles this itself.
#[cfg(not(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)))]
if web_context.referenced_by_webviews.is_empty() {
context_store.remove(&self.context_key);
}
@ -3405,7 +3436,14 @@ fn handle_user_message<T: UserEvent>(
}
#[allow(unused_variables)]
WindowMessage::SetSkipTaskbar(skip) => {
#[cfg(any(windows, target_os = "linux"))]
#[cfg(any(
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
let _ = window.set_skip_taskbar(skip);
}
WindowMessage::SetCursorGrab(grab) => {

View File

@ -4,14 +4,15 @@
use gtk::prelude::MonitorExt;
use tao::platform::unix::MonitorHandleExtUnix;
use tauri_runtime::dpi::{PhysicalPosition, PhysicalRect, PhysicalSize};
use tauri_runtime::dpi::{LogicalPosition, LogicalSize, PhysicalRect};
impl super::MonitorExt for tao::monitor::MonitorHandle {
fn work_area(&self) -> PhysicalRect<i32, u32> {
let rect = self.gdk_monitor().workarea();
let scale_factor = self.scale_factor();
PhysicalRect {
size: PhysicalSize::new(rect.width() as u32, rect.height() as u32),
position: PhysicalPosition::new(rect.x(), rect.y()),
size: LogicalSize::new(rect.width() as u32, rect.height() as u32).to_physical(scale_factor),
position: LogicalPosition::new(rect.x(), rect.y()).to_physical(scale_factor),
}
}
}

View File

@ -39,7 +39,7 @@ pub trait WindowExt {
/// - **Android / iOS**: Unsupported.
fn center(&self) {}
/// Clears the window surface. i.e make it it transparent.
/// Clears the window surface. i.e make it transparent.
#[cfg(windows)]
fn draw_surface(
&self,

View File

@ -1,5 +1,16 @@
# Changelog
## \[2.10.0]
### Bug Fixes
- [`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`
- [`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`.
## \[2.9.2]
### Dependencies

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-runtime"
version = "2.9.2"
version = "2.10.0"
description = "Runtime for Tauri applications"
exclude = ["CHANGELOG.md", "/target"]
readme = "README.md"
@ -27,7 +27,7 @@ targets = [
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
tauri-utils = { version = "2.8.1", path = "../tauri-utils" }
tauri-utils = { version = "2.8.2", path = "../tauri-utils" }
http = "1"
raw-window-handle = "0.6"
url = { version = "2" }

View File

@ -399,8 +399,25 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
fn new(args: RuntimeInitArgs) -> Result<Self>;
/// Creates a new webview runtime on any thread.
#[cfg(any(windows, target_os = "linux"))]
#[cfg_attr(docsrs, doc(cfg(any(windows, target_os = "linux"))))]
#[cfg(any(
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)))
)]
fn new_any_thread(args: RuntimeInitArgs) -> Result<Self>;
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.

View File

@ -23,7 +23,7 @@ use std::{
sync::Arc,
};
type UriSchemeProtocol = dyn Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
type UriSchemeProtocolHandler = dyn Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
+ Send
+ Sync
+ 'static;
@ -199,7 +199,8 @@ pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
/// The [`WebviewAttributes`] that the webview will be created with.
pub webview_attributes: WebviewAttributes,
pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
/// Custom protocols to register on the webview
pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocolHandler>>,
/// How to handle IPC calls on the webview.
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
@ -263,12 +264,12 @@ impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
>(
&mut self,
uri_scheme: N,
protocol: H,
protocol_handler: H,
) {
let uri_scheme = uri_scheme.into();
self
.uri_scheme_protocols
.insert(uri_scheme, Box::new(protocol));
.insert(uri_scheme, Box::new(protocol_handler));
}
#[cfg(target_os = "android")]
@ -362,7 +363,7 @@ pub struct WebviewAttributes {
/// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
pub allow_link_preview: bool,
pub scroll_bar_style: ScrollBarStyle,
/// Allows overriding the the keyboard accessory view on iOS.
/// Allows overriding the keyboard accessory view on iOS.
/// Returning `None` effectively removes the view.
///
/// The closure parameter is the webview instance.

View File

@ -247,19 +247,19 @@ pub trait WindowBuilder: WindowBuilderBase {
#[must_use]
fn center(self) -> Self;
/// The initial position of the window's.
/// The initial position of the window in logical pixels.
#[must_use]
fn position(self, x: f64, y: f64) -> Self;
/// Window size.
/// Window size in logical pixels.
#[must_use]
fn inner_size(self, width: f64, height: f64) -> Self;
/// Window min inner size.
/// Window min inner size in logical pixels.
#[must_use]
fn min_inner_size(self, min_width: f64, min_height: f64) -> Self;
/// Window max inner size.
/// Window max inner size in logical pixels.
#[must_use]
fn max_inner_size(self, max_width: f64, max_height: f64) -> Self;

View File

@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://schema.tauri.app/config/2.9.4",
"$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

@ -8,8 +8,8 @@ publish = false
crate-type = ["cdylib"]
[dependencies]
worker = { version = "0.6", features = ['http', 'axum'] }
worker-macros = { version = "0.6", features = ['http'] }
worker = { version = "0.7", features = ['http', 'axum'] }
worker-macros = { version = "0.7", features = ['http'] }
console_error_panic_hook = { version = "0.1" }
axum = { version = "0.8", default-features = false }
tower-service = "0.3"

View File

@ -8,6 +8,6 @@
"dev": "wrangler dev"
},
"devDependencies": {
"wrangler": "^4.20.3"
"wrangler": "^4.61.1"
}
}

View File

@ -7,8 +7,9 @@ main = "build/worker/shim.mjs"
compatibility_date = "2023-08-23"
send_metrics = false
# The minor version of worker-build must match worker/worker-macros in Cargo.toml!
[build]
command = "cargo install -q worker-build && worker-build --release"
command = "cargo install -q worker-build@^0.7 && worker-build --release"
[observability]
enabled = true

View File

@ -1,5 +1,11 @@
# Changelog
## \[2.8.2]
### 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.
## \[2.8.1]
### Bug Fixes

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-utils"
version = "2.8.1"
version = "2.8.2"
description = "Utilities for Tauri"
exclude = ["CHANGELOG.md", "/target"]
readme = "README.md"
@ -57,6 +57,7 @@ swift-rs = { version = "1", optional = true, features = ["build"] }
[dev-dependencies]
getrandom = { version = "0.3", features = ["std"] }
serial_test = "3"
tauri = { path = "../tauri" }
[features]
build = [

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