mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-11 10:43:31 +02:00
Compare commits
139 Commits
fix/mobile
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
926a57bb08 | ||
|
|
074299c08d | ||
|
|
ec5381e951 | ||
|
|
b27be063ff | ||
|
|
cdf5276478 | ||
|
|
e5b00795c2 | ||
|
|
5a0ca7edbb | ||
|
|
5dc2cee603 | ||
|
|
3b5b2cc125 | ||
|
|
e032c3b342 | ||
|
|
386312c73a | ||
|
|
d34497ef15 | ||
|
|
4017a7ed73 | ||
|
|
093e2b47c0 | ||
|
|
f0381b4bdf | ||
|
|
d730770bb9 | ||
|
|
80c1425af8 | ||
|
|
1ef6a119b1 | ||
|
|
aabb42f949 | ||
|
|
fcb702ec4d | ||
|
|
f17240bf6c | ||
|
|
1fa1db5cd9 | ||
|
|
8db451c791 | ||
|
|
15b311155f | ||
|
|
812b2990e7 | ||
|
|
c8d7003b23 | ||
|
|
2679ddd5eb | ||
|
|
3f77cc1ee6 | ||
|
|
2dd9b15a2b | ||
|
|
eacd36a4ea | ||
|
|
6cb86c9e42 | ||
|
|
50833703e4 | ||
|
|
8718d08163 | ||
|
|
8230973ae8 | ||
|
|
9b17a7aeae | ||
|
|
d86827980d | ||
|
|
3a65cc6885 | ||
|
|
52cf195b78 | ||
|
|
c3cbff3f74 | ||
|
|
33754ae5e3 | ||
|
|
3935dee121 | ||
|
|
33932a72b2 | ||
|
|
7d3c7593a9 | ||
|
|
f20256bca5 | ||
|
|
03514414d9 | ||
|
|
7b16dafb1d | ||
|
|
7782c5525a | ||
|
|
aa0bf8bd19 | ||
|
|
91fb0e161f | ||
|
|
88c05689c8 | ||
|
|
6252432f07 | ||
|
|
2d0b80eb1c | ||
|
|
86c8c870c8 | ||
|
|
476e8ee7f3 | ||
|
|
0d1cb83bab | ||
|
|
35c35f27ae | ||
|
|
7d01aa0417 | ||
|
|
7be58a1c64 | ||
|
|
06374a902a | ||
|
|
c37368f339 | ||
|
|
06f911aaff | ||
|
|
eb5d88427a | ||
|
|
540c5b4e59 | ||
|
|
5dbb37bab1 | ||
|
|
19ded696de | ||
|
|
08558b8ba4 | ||
|
|
ce8fddb464 | ||
|
|
517b81e970 | ||
|
|
cd68b03ee5 | ||
|
|
8d67af37b6 | ||
|
|
9f0306fbcc | ||
|
|
f7c083cd41 | ||
|
|
32576120fd | ||
|
|
e3fdcb5002 | ||
|
|
d453e2e06a | ||
|
|
20b99f9281 | ||
|
|
3a4e165b6f | ||
|
|
efc4c26ebc | ||
|
|
7fca58230f | ||
|
|
c769f211fc | ||
|
|
4d5d78daf6 | ||
|
|
4794a6ba22 | ||
|
|
09a4e7f55a | ||
|
|
c862a0bd8c | ||
|
|
f82594410c | ||
|
|
853ed4642f | ||
|
|
53611c4d7b | ||
|
|
62aa13a124 | ||
|
|
e919a760ed | ||
|
|
0575dd287e | ||
|
|
eccff97588 | ||
|
|
08e35fcda0 | ||
|
|
10a8066db3 | ||
|
|
ea31b07f19 | ||
|
|
7f7d9aac21 | ||
|
|
7873c4a1c6 | ||
|
|
123d63a0c1 | ||
|
|
75057c7c08 | ||
|
|
268bb339f0 | ||
|
|
07788af13f | ||
|
|
9a53c84ec0 | ||
|
|
137576e8a4 | ||
|
|
1b0e335d3f | ||
|
|
84b04c4a8d | ||
|
|
897529d7a2 | ||
|
|
3d102e0c13 | ||
|
|
fea4d02403 | ||
|
|
a03219ca19 | ||
|
|
b75ea5bead | ||
|
|
dcd1a65889 | ||
|
|
9b242e40c8 | ||
|
|
1dbf6fd067 | ||
|
|
8a43e4f9d9 | ||
|
|
a2abe2e6bc | ||
|
|
51f0fcb69c | ||
|
|
0650852d14 | ||
|
|
c1d82eb3a3 | ||
|
|
51a0d6d66d | ||
|
|
7f48ee9068 | ||
|
|
e290642fb4 | ||
|
|
b79386010d | ||
|
|
ff5d76ca21 | ||
|
|
2d28e3143e | ||
|
|
18c69df8c7 | ||
|
|
f2e0405dc2 | ||
|
|
54e8d93db1 | ||
|
|
251203b896 | ||
|
|
91becd9e4f | ||
|
|
018b4db22e | ||
|
|
731dd5bfdc | ||
|
|
7b1b3514df | ||
|
|
546b296405 | ||
|
|
514cf21e14 | ||
|
|
60174527c0 | ||
|
|
4176f93ae4 | ||
|
|
4408f72af6 | ||
|
|
1496145f82 | ||
|
|
f022b2d1ae | ||
|
|
1573c72402 |
@@ -6,4 +6,6 @@ ignore = [
|
||||
"RUSTSEC-2020-0095",
|
||||
# proc-macro-error is unmaintained
|
||||
"RUSTSEC-2024-0370",
|
||||
# time crate can't be updated in the repo because of MSRV, users are unaffected
|
||||
"RUSTSEC-2026-0009",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'tauri': 'minor:feat'
|
||||
'@tauri-apps/api': 'minor:feat'
|
||||
---
|
||||
|
||||
Add Bring All to Front predefined menu item type
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@tauri-apps/api": patch:bug
|
||||
---
|
||||
|
||||
Fix `addPluginListener` fallback added in https://github.com/tauri-apps/tauri/pull/14132 didn't work properly
|
||||
5
.changes/base64.md
Normal file
5
.changes/base64.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-macos-sign": patch:enhance
|
||||
---
|
||||
|
||||
Do not rely on system base64 CLI to decode certificates.
|
||||
6
.changes/change-pr-15117.md
Normal file
6
.changes/change-pr-15117.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-build": patch:enhance
|
||||
"tauri": patch:enhance
|
||||
---
|
||||
|
||||
Simplify async-sync code boundaries, no externally visible changes
|
||||
5
.changes/data-tauri-drag-region-deep.md
Normal file
5
.changes/data-tauri-drag-region-deep.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": minor:feat
|
||||
---
|
||||
|
||||
Add `data-tauri-drag-region="deep"` so clicks on non-clickable children will drag as well. Can still opt out of drag on some regions using `data-tauri-drag-region="false"`
|
||||
7
.changes/eval-with-callback.md
Normal file
7
.changes/eval-with-callback.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'tauri': 'minor:feat'
|
||||
'tauri-runtime': 'minor:feat'
|
||||
'tauri-runtime-wry': 'minor:feat'
|
||||
---
|
||||
|
||||
Add `eval_with_callback` to the Tauri webview APIs and runtime dispatch layers.
|
||||
13
.changes/feat-uninstaller-icon-image.md
Normal file
13
.changes/feat-uninstaller-icon-image.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
"tauri-bundler": minor:feat
|
||||
"tauri-cli": minor:feat
|
||||
"@tauri-apps/cli": minor:feat
|
||||
"tauri-utils": minor:feat
|
||||
---
|
||||
|
||||
Added uninstaller icon and uninstaller header image support for NSIS installer.
|
||||
|
||||
Notes:
|
||||
|
||||
- For `tauri-bundler` lib users, the `NsisSettings` now has 2 new fields `uninstaller_icon` and `uninstaller_header_image` which can be a breaking change
|
||||
- When bundling with NSIS, users can add `uninstallerIcon` and `uninstallerHeaderImage` under `bundle > windows > nsis` to configure them.
|
||||
7
.changes/fix-build-bundles-arg.md
Normal file
7
.changes/fix-build-bundles-arg.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"tauri-bundler": patch:bug
|
||||
"tauri-cli": patch:bug
|
||||
"@tauri-apps/cli": patch:bug
|
||||
---
|
||||
|
||||
Fix `build --bundles` to allow `nsis` arg in linux+macOS
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@tauri-apps/cli": patch:bug
|
||||
"tauri-cli": patch:bug
|
||||
---
|
||||
|
||||
Fixes Cargo features and args not being applied to the first cargo build calls of `[android|ios] [dev|build]` commands.
|
||||
6
.changes/fix-ios-metal-toolchain.md
Normal file
6
.changes/fix-ios-metal-toolchain.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-cli": patch:bug
|
||||
"@tauri-apps/cli": patch:bug
|
||||
---
|
||||
|
||||
Fix iOS build failure when `Metal Toolchain` is installed by using explicit `$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain` path instead of `$(TOOLCHAIN_DIR)` for Swift library search paths.
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
"tauri": patch:perf
|
||||
"tauri-cli": patch:perf
|
||||
"tauri-bundler": patch:perf
|
||||
"@tauri-apps/cli": patch:perf
|
||||
---
|
||||
|
||||
refactor: remove needless collect. No user facing changes.
|
||||
5
.changes/linux-deploy-link.md
Normal file
5
.changes/linux-deploy-link.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-bundler": patch:bug
|
||||
---
|
||||
|
||||
Correct GitHub Release URL path for Linux i686 tooling.
|
||||
5
.changes/liquid-glass-icon.md
Normal file
5
.changes/liquid-glass-icon.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-bundler": minor:feat
|
||||
---
|
||||
|
||||
Added support to Liquid Glass icons.
|
||||
8
.changes/multi-window-mobile.md
Normal file
8
.changes/multi-window-mobile.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"tauri": minor:feat
|
||||
"tauri-runtime-wry": minor:feat
|
||||
"tauri-runtime": minor:feat
|
||||
"tauri-utils": minor:feat
|
||||
---
|
||||
|
||||
Support creating multiple windows on Android (activity embedding) and iOS (scenes).
|
||||
6
.changes/new-window-main-thread.md
Normal file
6
.changes/new-window-main-thread.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri": minor:changes
|
||||
"tauri-runtime-wry": minor:changes
|
||||
---
|
||||
|
||||
The new window handler passed to `on_new_window` no longer requires `Sync`, and runs on main thread on Windows, aligning with other platforms
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"tauri-bundler": patch:deps
|
||||
---
|
||||
|
||||
Updated NSIS from 3.8 to 3.11
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
"tauri-bundler": patch:perf
|
||||
"tauri-cli": patch:perf
|
||||
"tauri-macos-sign": patch:perf
|
||||
"tauri": patch:perf
|
||||
---
|
||||
|
||||
perf: remove needless clones in various files for improved performance. No user facing changes.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"tauri-cli": patch:bug
|
||||
"@tauri-apps/cli": patch:bug
|
||||
---
|
||||
|
||||
Fixed the mismatched tauri package versions check didn't work for pnpm
|
||||
6
.changes/prompt-signing-key-password-context.md
Normal file
6
.changes/prompt-signing-key-password-context.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-cli": patch:enhance
|
||||
"@tauri-apps/cli": patch:enhance
|
||||
---
|
||||
|
||||
Show the context before prompting for updater signing key password
|
||||
5
.changes/safepathbuf_into_pathbuf.md
Normal file
5
.changes/safepathbuf_into_pathbuf.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch:enhance
|
||||
---
|
||||
|
||||
Implement retrieving inner PathBuf from SafePathBuf to ease using APIs that require an owned PathBuf
|
||||
6
.changes/supersede-kuchikiki.md
Normal file
6
.changes/supersede-kuchikiki.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-utils": minor:deps
|
||||
---
|
||||
|
||||
Add new `html-manipulation-2` and `build-2` feature flags that use `dom_query` instead of `kuchikiki` for HTML parsing / manipulation.
|
||||
This allows downstream users to remove `kuchikiki` and its dependencies from their dependency tree.
|
||||
5
.changes/tauri-dbus.md
Normal file
5
.changes/tauri-dbus.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
tauri: minor:feat
|
||||
---
|
||||
|
||||
Added `dbus` feature flag (enabled by default) which is required for theme detection on Linux.
|
||||
5
.changes/toml-ver.md
Normal file
5
.changes/toml-ver.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
tauri-utils: patch:deps
|
||||
---
|
||||
|
||||
Changed `toml` crate version from `0.9` to `">=0.9, <=1"`
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
tauri-cli: patch:bug
|
||||
---
|
||||
|
||||
Fixed an issue that caused the cli to print errors like `Error Failed to parse version 2 for crate tauri` when there was no `Cargo.lock` file present yet. This will still be logged in `--verbose` mode.
|
||||
12
.changes/wix-minimum-webview2-version.md
Normal file
12
.changes/wix-minimum-webview2-version.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
"tauri-bundler": minor:feat
|
||||
"tauri-cli": minor:feat
|
||||
"@tauri-apps/cli": minor:feat
|
||||
---
|
||||
|
||||
Added support for `minimumWebview2Version` option support for the MSI (Wix) installer, the old `bundle > windows > nsis > minimumWebview2Version` is now deprecated in favor of `bundle > windows > minimumWebview2Version`
|
||||
|
||||
Notes:
|
||||
|
||||
- For anyone relying on the `WVRTINSTALLED` `Property` tag in `main.wxs`, it is now renamed to `INSTALLED_WEBVIEW2_VERSION`
|
||||
- For `tauri-bundler` lib users, the `WindowsSettings` now has a new field `minimum_webview2_version` which can be a breaking change
|
||||
11
.github/CONTRIBUTING.md
vendored
11
.github/CONTRIBUTING.md
vendored
@@ -5,6 +5,7 @@ Hi! We, the maintainers, are really excited that you are interested in contribut
|
||||
- [Issue Reporting Guidelines](#issue-reporting-guidelines)
|
||||
- [Pull Request Guidelines](#pull-request-guidelines)
|
||||
- [Development Guide](#development-guide)
|
||||
- [AI Tool Policy](#ai-tool-policy)
|
||||
|
||||
## Issue Reporting Guidelines
|
||||
|
||||
@@ -104,6 +105,16 @@ $ cargo +nightly doc --all-features --open
|
||||
The JS API provides bindings between the developer's JS in the Webview and the built-in Tauri APIs, written in Rust. Its code is located in [/packages/api](https://github.com/tauri-apps/tauri/tree/dev/packages/api).
|
||||
After making changes to the code, run `pnpm build` to build it. To test your changes, we recommend using the API example app, located in [/examples/api](https://github.com/tauri-apps/tauri/tree/dev/examples/api). It will automatically use your local copy of the JS API and provides a helpful UI to test the various commands.
|
||||
|
||||
## AI Tool Policy
|
||||
|
||||
It takes a lot of time to review a Pull Request while it's very easy to make a nonsensical but plausible looking one using AI tools.
|
||||
It is unfair for other contributors and the reviewers to spend much of the time dealing with this, hence these rules:
|
||||
|
||||
1. Review and test all LLM-generated content before submitting, you're the one responsible for it, not the AI.
|
||||
2. Don't use AI to respond to review comments (except for translations).
|
||||
|
||||
We will close the Pull Request with a `ai-slop` tag if you failed to do so.
|
||||
|
||||
## Financial Contribution
|
||||
|
||||
Tauri is an MIT-licensed open source project. Its ongoing development can be supported via [GitHub Sponsors](https://github.com/sponsors/tauri-apps) or [Open Collective](https://opencollective.com/tauri). We prefer GitHub Sponsors as donations made are doubled through the matching fund program.
|
||||
|
||||
11
.github/workflows/audit.yml
vendored
11
.github/workflows/audit.yml
vendored
@@ -8,7 +8,16 @@ on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/audit.yml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/Cargo.toml'
|
||||
- '**/package.json'
|
||||
- '**/pnpm-lock.yaml'
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '.github/workflows/audit.yml'
|
||||
- '**/Cargo.lock'
|
||||
@@ -26,7 +35,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: rust audit
|
||||
uses: rustsec/audit-check@v1
|
||||
uses: rustsec/audit-check@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
17
.github/workflows/publish-cli-js.yml
vendored
17
.github/workflows/publish-cli-js.yml
vendored
@@ -20,6 +20,10 @@ defaults:
|
||||
run:
|
||||
working-directory: packages/cli/
|
||||
|
||||
permissions:
|
||||
contents: write # update release
|
||||
id-token: write # oidc token
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
@@ -116,7 +120,7 @@ jobs:
|
||||
if: ${{ !matrix.settings.docker }}
|
||||
with:
|
||||
targets: ${{ matrix.settings.target }}
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.settings.target }}
|
||||
if: ${{ matrix.settings.docker }}
|
||||
@@ -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
|
||||
|
||||
29
.github/workflows/publish-cli-rs.yml
vendored
29
.github/workflows/publish-cli-rs.yml
vendored
@@ -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
|
||||
|
||||
18
.github/workflows/test-core.yml
vendored
18
.github/workflows/test-core.yml
vendored
@@ -90,14 +90,26 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
prefix-key: v3
|
||||
key: ${{ matrix.platform.target }}
|
||||
save-if: ${{ matrix.features.key == 'all' }}
|
||||
|
||||
- name: test
|
||||
- name: test tauri-utils
|
||||
if: ${{ !matrix.platform.cross }}
|
||||
# Using --lib --bins --tests to skip doc tests
|
||||
run: cargo ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --lib --bins --tests --manifest-path crates/tauri-utils/Cargo.toml
|
||||
|
||||
- name: test tauri-utils (using cross)
|
||||
if: ${{ matrix.platform.cross }}
|
||||
# Using --lib --bins --tests to skip doc tests
|
||||
run: |
|
||||
cargo install cross --git https://github.com/cross-rs/cross --rev 51f46f296253d8122c927c5bb933e3c4f27cc317 --locked
|
||||
cross ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --lib --bins --tests --manifest-path crates/tauri-utils/Cargo.toml
|
||||
|
||||
- name: test tauri
|
||||
if: ${{ !matrix.platform.cross }}
|
||||
run: cargo ${{ matrix.features.key == 'no-default' && 'check' || matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --manifest-path crates/tauri/Cargo.toml
|
||||
|
||||
- name: test (using cross)
|
||||
- name: test tauri (using cross)
|
||||
if: ${{ matrix.platform.cross }}
|
||||
run: |
|
||||
cargo install cross --git https://github.com/cross-rs/cross --rev 51f46f296253d8122c927c5bb933e3c4f27cc317 --locked
|
||||
|
||||
16
.taurignore
16
.taurignore
@@ -1,16 +0,0 @@
|
||||
.changes
|
||||
.devcontainer
|
||||
.docker
|
||||
.github
|
||||
.scripts
|
||||
.vscode
|
||||
audits
|
||||
bench
|
||||
packages/api
|
||||
packages/cli
|
||||
crates/tauri-cli
|
||||
crates/tauri-bundler
|
||||
crates/tauri-driver
|
||||
crates/tauri-macos-sign
|
||||
crates/tauri-schema-generator
|
||||
crates/tests
|
||||
1435
Cargo.lock
generated
1435
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.5.6]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.3`
|
||||
- Upgraded to `tauri-codegen@2.5.5`
|
||||
|
||||
## \[2.5.5]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-codegen@2.5.4`
|
||||
|
||||
## \[2.5.4]
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`2d28e3143`](https://www.github.com/tauri-apps/tauri/commit/2d28e3143ee3d97d7570ea03877aa00a0d6e47d0) ([#14632](https://www.github.com/tauri-apps/tauri/pull/14632) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Small code refactors for improved code readability. No user facing changes.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.2`
|
||||
- Upgraded to `tauri-codegen@2.5.3`
|
||||
|
||||
## \[2.5.3]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.1`
|
||||
- Upgraded to `tauri-codegen@2.5.2`
|
||||
|
||||
## \[2.5.2]
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-build"
|
||||
version = "2.5.2"
|
||||
version = "2.5.6"
|
||||
description = "build time code to pair with https://crates.io/crates/tauri"
|
||||
exclude = ["CHANGELOG.md", "/target"]
|
||||
readme = "README.md"
|
||||
@@ -26,9 +26,9 @@ targets = [
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
quote = { version = "1", optional = true }
|
||||
tauri-codegen = { version = "2.5.1", path = "../tauri-codegen", optional = true }
|
||||
tauri-utils = { version = "2.8.0", path = "../tauri-utils", features = [
|
||||
"build",
|
||||
tauri-codegen = { version = "2.5.5", path = "../tauri-codegen", optional = true }
|
||||
tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
|
||||
"build-2",
|
||||
"resources",
|
||||
] }
|
||||
cargo_toml = "0.22"
|
||||
@@ -41,7 +41,6 @@ tauri-winres = "0.3"
|
||||
semver = "1"
|
||||
dirs = "6"
|
||||
glob = "0.3"
|
||||
toml = "0.9"
|
||||
# Our code requires at least 0.8.21 so don't simplify this to 0.8
|
||||
schemars = { version = "0.8.21", features = ["preserve_order"] }
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -415,7 +411,8 @@ impl Attributes {
|
||||
}
|
||||
|
||||
pub fn is_dev() -> bool {
|
||||
env::var("DEP_TAURI_DEV").expect("missing `cargo:dev` instruction, please update tauri to latest")
|
||||
env::var_os("DEP_TAURI_DEV")
|
||||
.expect("missing `cargo:dev` instruction, please update tauri to latest")
|
||||
== "true"
|
||||
}
|
||||
|
||||
@@ -462,7 +459,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
|
||||
println!("cargo:rerun-if-env-changed=TAURI_CONFIG");
|
||||
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let target_os = env::var_os("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let mobile = target_os == "ios" || target_os == "android";
|
||||
cfg_alias("desktop", !mobile);
|
||||
cfg_alias("mobile", mobile);
|
||||
@@ -507,7 +504,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
let cargo_toml_path = Path::new("Cargo.toml").canonicalize()?;
|
||||
let mut manifest = Manifest::<cargo_toml::Value>::from_path_with_metadata(cargo_toml_path)?;
|
||||
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
|
||||
manifest::check(&config, &mut manifest)?;
|
||||
|
||||
@@ -533,7 +530,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()),
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -542,7 +539,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
.bundle
|
||||
.resources
|
||||
.clone()
|
||||
.unwrap_or_else(|| BundleResources::List(Vec::new()));
|
||||
.unwrap_or(BundleResources::List(Vec::new()));
|
||||
if target_triple.contains("windows") {
|
||||
if let Some(fixed_webview2_runtime_path) = match &config.bundle.windows.webview_install_mode {
|
||||
WebviewInstallMode::FixedRuntime { path } => Some(path),
|
||||
@@ -591,21 +588,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();
|
||||
|
||||
@@ -688,7 +683,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
}
|
||||
}
|
||||
"msvc" => {
|
||||
if env::var("STATIC_VCRUNTIME").is_ok_and(|v| v == "true") {
|
||||
if env::var_os("STATIC_VCRUNTIME").is_some_and(|v| v == "true") {
|
||||
static_vcruntime::build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct AllowlistedDependency {
|
||||
name: String,
|
||||
alias: Option<String>,
|
||||
kind: DependencyKind,
|
||||
all_cli_managed_features: Option<Vec<&'static str>>,
|
||||
all_cli_managed_features: Vec<&'static str>,
|
||||
expected_features: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ pub fn check(config: &Config, manifest: &mut Manifest) -> Result<()> {
|
||||
name: "tauri-build".into(),
|
||||
alias: None,
|
||||
kind: DependencyKind::Build,
|
||||
all_cli_managed_features: Some(vec!["isolation"]),
|
||||
all_cli_managed_features: vec!["isolation"],
|
||||
expected_features: match config.app.security.pattern {
|
||||
PatternKind::Isolation { .. } => vec!["isolation".to_string()],
|
||||
_ => vec![],
|
||||
@@ -43,12 +43,10 @@ pub fn check(config: &Config, manifest: &mut Manifest) -> Result<()> {
|
||||
name: "tauri".into(),
|
||||
alias: None,
|
||||
kind: DependencyKind::Normal,
|
||||
all_cli_managed_features: Some(
|
||||
AppConfig::all_features()
|
||||
.into_iter()
|
||||
.filter(|f| f != &"tray-icon")
|
||||
.collect(),
|
||||
),
|
||||
all_cli_managed_features: AppConfig::all_features()
|
||||
.into_iter()
|
||||
.filter(|f| f != &"tray-icon")
|
||||
.collect(),
|
||||
expected_features: config
|
||||
.app
|
||||
.features()
|
||||
@@ -129,23 +127,13 @@ fn check_features(dependency: Dependency, metadata: &AllowlistedDependency) -> R
|
||||
Dependency::Inherited(dep) => dep.features,
|
||||
};
|
||||
|
||||
let diff = if let Some(all_cli_managed_features) = &metadata.all_cli_managed_features {
|
||||
features_diff(
|
||||
&features
|
||||
.into_iter()
|
||||
.filter(|f| all_cli_managed_features.contains(&f.as_str()))
|
||||
.collect::<Vec<String>>(),
|
||||
&metadata.expected_features,
|
||||
)
|
||||
} else {
|
||||
features_diff(
|
||||
&features
|
||||
.into_iter()
|
||||
.filter(|f| f.starts_with("allow-"))
|
||||
.collect::<Vec<String>>(),
|
||||
&metadata.expected_features,
|
||||
)
|
||||
};
|
||||
let diff = features_diff(
|
||||
&features
|
||||
.into_iter()
|
||||
.filter(|f| metadata.all_cli_managed_features.contains(&f.as_str()))
|
||||
.collect::<Vec<String>>(),
|
||||
&metadata.expected_features,
|
||||
);
|
||||
|
||||
let mut error_message = String::new();
|
||||
if !diff.remove.is_empty() {
|
||||
|
||||
@@ -15,7 +15,8 @@ pub fn generate_gradle_files(project_dir: PathBuf) -> Result<()> {
|
||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n".to_string();
|
||||
let mut app_build_gradle = "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
val implementation by configurations
|
||||
dependencies {"
|
||||
dependencies {
|
||||
implementation(\"androidx.lifecycle:lifecycle-process:2.10.0\")"
|
||||
.to_string();
|
||||
|
||||
for (env, value) in std::env::vars_os() {
|
||||
|
||||
@@ -1,5 +1,72 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.8.1]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`6252432f0`](https://www.github.com/tauri-apps/tauri/commit/6252432f0757d896d7a61819bbff127efac5a156) ([#14945](https://www.github.com/tauri-apps/tauri/pull/14945) by [@veeceey](https://www.github.com/tauri-apps/tauri/../../veeceey)) Fix WIX installer registry search order so that the named `InstallDir` key takes priority over the NSIS default key, preventing install location from changing on updates.
|
||||
|
||||
### What's Changed
|
||||
|
||||
- [`7be58a1c6`](https://www.github.com/tauri-apps/tauri/commit/7be58a1c643a7ed6d0f484a7e1134022618eb2b2) ([#14894](https://www.github.com/tauri-apps/tauri/pull/14894) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Log patching bundle type information again
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.3`
|
||||
|
||||
## \[2.8.0]
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`c769f211f`](https://www.github.com/tauri-apps/tauri/commit/c769f211fcaa543884c9d0f87ebd2ee106c01382) ([#14824](https://www.github.com/tauri-apps/tauri/pull/14824) by [@Kf637](https://www.github.com/tauri-apps/tauri/../../Kf637)) feat(nsis): add Norwegian language support for installer.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`7fca58230`](https://www.github.com/tauri-apps/tauri/commit/7fca58230f97c3e6834134419514a0c7dbbe784b) ([#14830](https://www.github.com/tauri-apps/tauri/pull/14830) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Updated `nsis_tauri_utils` to 0.5.3:
|
||||
|
||||
- Use an alternative method `CreateProcessWithTokenW` to run programs as user, this fixed a problem that the program launched with the previous method can't query its own handle
|
||||
|
||||
### What's Changed
|
||||
|
||||
- [`0575dd287`](https://www.github.com/tauri-apps/tauri/commit/0575dd287e021b61d2aedf64d62ae84a2c925fb4) ([#14521](https://www.github.com/tauri-apps/tauri/pull/14521) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Change the way bundle type information is added to binary files. Instead of looking up the value of a variable we simply look for the default value.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.2`
|
||||
- Upgraded to `tauri-macos-sign@2.3.3`
|
||||
|
||||
## \[2.7.5]
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`4176f93ae`](https://www.github.com/tauri-apps/tauri/commit/4176f93ae43ef66714c4934feb3df19df3a3e28a) ([#14570](https://www.github.com/tauri-apps/tauri/pull/14570) by [@chfaft](https://www.github.com/tauri-apps/tauri/../../chfaft)) Consider extensions that are defined in the wxs template.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`018b4db22`](https://www.github.com/tauri-apps/tauri/commit/018b4db22e167fa67b37b0933e192a0f3556d3e5) ([#14625](https://www.github.com/tauri-apps/tauri/pull/14625) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Skip signing for NSIS uninstaller when using `--no-sign` flag
|
||||
- [`91becd9e4`](https://www.github.com/tauri-apps/tauri/commit/91becd9e4fa2db089ddc6b21dadc06133e939e08) ([#14627](https://www.github.com/tauri-apps/tauri/pull/14627) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix NSIS plugins not being signed due to wrong path handlings
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-macos-sign@2.3.2`
|
||||
|
||||
## \[2.7.4]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`1496145f8`](https://www.github.com/tauri-apps/tauri/commit/1496145f8222649efeff22b819a96208670bbea1) ([#14585](https://www.github.com/tauri-apps/tauri/pull/14585) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the AppImage bundler to fail with 404 errors for 32-bit builds.
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
- [`ce98d87ce`](https://www.github.com/tauri-apps/tauri/commit/ce98d87ce0aaa907285852eb80691197424e03c3) ([#14474](https://www.github.com/tauri-apps/tauri/pull/14474) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) refactor: remove needless collect. No user facing changes.
|
||||
- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-macos-sign@2.3.1`
|
||||
- Upgraded to `tauri-utils@2.8.1`
|
||||
- [`b5ef603d8`](https://www.github.com/tauri-apps/tauri/commit/b5ef603d84bd8044625e50dcfdabb099b2e9fdd9) ([#14478](https://www.github.com/tauri-apps/tauri/pull/14478) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Updated NSIS from 3.8 to 3.11
|
||||
|
||||
## \[2.7.3]
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-bundler"
|
||||
version = "2.7.3"
|
||||
version = "2.8.1"
|
||||
authors = [
|
||||
"George Burton <burtonageo@gmail.com>",
|
||||
"Tauri Programme within The Commons Conservancy",
|
||||
@@ -15,7 +15,7 @@ rust-version = "1.77.2"
|
||||
exclude = ["CHANGELOG.md", "/target", "rustfmt.toml"]
|
||||
|
||||
[dependencies]
|
||||
tauri-utils = { version = "2.8.0", path = "../tauri-utils", features = [
|
||||
tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
|
||||
"resources",
|
||||
] }
|
||||
image = "0.25"
|
||||
@@ -59,7 +59,7 @@ features = ["Win32_System_SystemInformation", "Win32_System_Diagnostics_Debug"]
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
icns = { package = "tauri-icns", version = "0.1" }
|
||||
time = { version = "0.3", features = ["formatting"] }
|
||||
tauri-macos-sign = { version = "2.3.0", path = "../tauri-macos-sign" }
|
||||
tauri-macos-sign = { version = "2.3.3", path = "../tauri-macos-sign" }
|
||||
|
||||
[target."cfg(target_os = \"linux\")".dependencies]
|
||||
heck = "0.5"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
mod category;
|
||||
mod kmp;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -15,28 +16,63 @@ mod windows;
|
||||
|
||||
use tauri_utils::{display_path, platform::Target as TargetPlatform};
|
||||
|
||||
const BUNDLE_VAR_TOKEN: &[u8] = b"__TAURI_BUNDLE_TYPE_VAR_UNK";
|
||||
/// Patch a binary with bundle type information
|
||||
fn patch_binary(binary: &PathBuf, package_type: &PackageType) -> crate::Result<()> {
|
||||
match package_type {
|
||||
#[cfg(target_os = "linux")]
|
||||
PackageType::AppImage | PackageType::Deb | PackageType::Rpm => {
|
||||
log::info!(
|
||||
"Patching binary {:?} for type {}",
|
||||
binary,
|
||||
package_type.short_name()
|
||||
);
|
||||
linux::patch_binary(binary, package_type)?;
|
||||
#[cfg(target_os = "linux")]
|
||||
let bundle_type = match package_type {
|
||||
crate::PackageType::Deb => b"__TAURI_BUNDLE_TYPE_VAR_DEB",
|
||||
crate::PackageType::Rpm => b"__TAURI_BUNDLE_TYPE_VAR_RPM",
|
||||
crate::PackageType::AppImage => b"__TAURI_BUNDLE_TYPE_VAR_APP",
|
||||
// NSIS installers can be built in linux using cargo-xwin
|
||||
crate::PackageType::Nsis => b"__TAURI_BUNDLE_TYPE_VAR_NSS",
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidPackageType(
|
||||
package_type.short_name().to_owned(),
|
||||
"Linux".to_owned(),
|
||||
))
|
||||
}
|
||||
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(),
|
||||
))
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
let bundle_type = match package_type {
|
||||
// NSIS installers can be built in macOS using cargo-xwin
|
||||
crate::PackageType::Nsis => b"__TAURI_BUNDLE_TYPE_VAR_NSS",
|
||||
crate::PackageType::MacOsBundle | crate::PackageType::Dmg => {
|
||||
// skip patching for macOS-native bundles
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidPackageType(
|
||||
package_type.short_name().to_owned(),
|
||||
"macOS".to_owned(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"Patching {} with bundle type information: {}",
|
||||
display_path(binary),
|
||||
package_type.short_name()
|
||||
);
|
||||
|
||||
let mut file_data = std::fs::read(binary).expect("Could not read binary file.");
|
||||
let bundle_var_index =
|
||||
kmp::index_of(BUNDLE_VAR_TOKEN, &file_data).ok_or(crate::Error::MissingBundleTypeVar)?;
|
||||
file_data[bundle_var_index..bundle_var_index + BUNDLE_VAR_TOKEN.len()]
|
||||
.copy_from_slice(bundle_type);
|
||||
|
||||
std::fs::write(binary, &file_data).map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -92,22 +128,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
|
||||
@@ -121,16 +152,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||
|
||||
// sign main binary for every package type after patch
|
||||
if matches!(target_os, TargetPlatform::Windows) && settings.windows().can_sign() {
|
||||
if main_binary_signed && main_binary_reset_required {
|
||||
let mut signed_main_binary = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&main_binary_path)?;
|
||||
unsigned_main_binary_copy.seek(SeekFrom::Start(0))?;
|
||||
std::io::copy(&mut unsigned_main_binary_copy, &mut signed_main_binary)?;
|
||||
}
|
||||
windows::sign::try_sign(&main_binary_path, settings)?;
|
||||
main_binary_signed = true;
|
||||
}
|
||||
|
||||
let bundle_paths = match package_type {
|
||||
@@ -153,7 +175,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
PackageType::WindowsMsi => windows::msi::bundle_project(settings, false)?,
|
||||
// note: don't restrict to windows as NSIS installers can be built in linux using cargo-xwin
|
||||
// don't restrict to windows as NSIS installers can be built in linux+macOS using cargo-xwin
|
||||
PackageType::Nsis => windows::nsis::bundle_project(settings, false)?,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -172,6 +194,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 +303,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(());
|
||||
}
|
||||
|
||||
|
||||
59
crates/tauri-bundler/src/bundle/kmp/mod.rs
Normal file
59
crates/tauri-bundler/src/bundle/kmp/mod.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Knuth–Morris–Pratt algorithm
|
||||
// based on https://github.com/howeih/rust_kmp
|
||||
pub fn index_of(pattern: &[u8], target: &[u8]) -> Option<usize> {
|
||||
let failure_function = find_failure_function(pattern);
|
||||
|
||||
let mut t_i: usize = 0;
|
||||
let mut p_i: usize = 0;
|
||||
let target_len = target.len();
|
||||
let mut result_idx = None;
|
||||
let pattern_len = pattern.len();
|
||||
|
||||
while (t_i < target_len) && (p_i < pattern_len) {
|
||||
if target[t_i] == pattern[p_i] {
|
||||
if result_idx.is_none() {
|
||||
result_idx.replace(t_i);
|
||||
}
|
||||
t_i += 1;
|
||||
p_i += 1;
|
||||
if p_i >= pattern_len {
|
||||
return result_idx;
|
||||
}
|
||||
} else {
|
||||
if p_i == 0 {
|
||||
p_i = 0;
|
||||
t_i += 1;
|
||||
} else {
|
||||
p_i = failure_function[p_i - 1];
|
||||
}
|
||||
result_idx = None;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_failure_function(pattern: &[u8]) -> Vec<usize> {
|
||||
let mut i = 1;
|
||||
let mut j = 0;
|
||||
let pattern_length = pattern.len();
|
||||
let end_i = pattern_length - 1;
|
||||
let mut failure_function = vec![0usize; pattern_length];
|
||||
while i <= end_i {
|
||||
if pattern[i] == pattern[j] {
|
||||
failure_function[i] = j + 1;
|
||||
i += 1;
|
||||
j += 1;
|
||||
} else if j == 0 {
|
||||
failure_function[i] = 0;
|
||||
i += 1;
|
||||
} else {
|
||||
j = failure_function[j - 1];
|
||||
}
|
||||
}
|
||||
failure_function
|
||||
}
|
||||
@@ -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
|
||||
|
||||
282
crates/tauri-bundler/src/bundle/linux/appimage/linuxdeploy.rs
Normal file
282
crates/tauri-bundler/src/bundle/linux/appimage/linuxdeploy.rs
Normal file
@@ -0,0 +1,282 @@
|
||||
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::{super::debian, write_and_make_executable};
|
||||
use crate::{
|
||||
bundle::settings::Arch,
|
||||
error::{Context, ErrorExt},
|
||||
utils::{fs_utils, http_utils::download, CommandExt},
|
||||
Settings,
|
||||
};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
/// Bundles the project.
|
||||
/// Returns a vector of PathBuf that shows where the AppImage was created.
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// generate the deb binary name
|
||||
let appimage_arch: &str = match settings.binary_arch() {
|
||||
Arch::X86_64 => "amd64",
|
||||
Arch::X86 => "i386",
|
||||
Arch::AArch64 => "aarch64",
|
||||
Arch::Armhf => "armhf",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"Unsupported architecture: {target:?}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let tools_arch = if settings.binary_arch() == Arch::Armhf {
|
||||
"armhf"
|
||||
} else {
|
||||
settings.target().split('-').next().unwrap()
|
||||
};
|
||||
|
||||
let output_path = settings.project_out_directory().join("bundle/appimage");
|
||||
if output_path.exists() {
|
||||
fs::remove_dir_all(&output_path)?;
|
||||
}
|
||||
|
||||
let tools_path = settings
|
||||
.local_tools_directory()
|
||||
.map(|d| d.join(".tauri"))
|
||||
.unwrap_or_else(|| {
|
||||
dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join("tauri"))
|
||||
});
|
||||
|
||||
fs::create_dir_all(&tools_path)?;
|
||||
|
||||
let linuxdeploy_path = prepare_tools(
|
||||
&tools_path,
|
||||
tools_arch,
|
||||
settings.log_level() != log::Level::Error,
|
||||
)?;
|
||||
|
||||
let package_dir = settings.project_out_directory().join("bundle/appimage_deb");
|
||||
|
||||
let main_binary = settings.main_binary()?;
|
||||
let product_name = settings.product_name();
|
||||
|
||||
let mut settings = settings.clone();
|
||||
if main_binary.name().contains(' ') {
|
||||
let main_binary_path = settings.binary_path(main_binary);
|
||||
let project_out_directory = settings.project_out_directory();
|
||||
|
||||
let main_binary_name_kebab = heck::AsKebabCase(main_binary.name()).to_string();
|
||||
let new_path = project_out_directory.join(&main_binary_name_kebab);
|
||||
fs::copy(main_binary_path, new_path)?;
|
||||
|
||||
let main_binary = settings.main_binary_mut()?;
|
||||
main_binary.set_name(main_binary_name_kebab);
|
||||
}
|
||||
|
||||
// generate deb_folder structure
|
||||
let (data_dir, icons) = debian::generate_data(&settings, &package_dir)
|
||||
.with_context(|| "Failed to build data folders and files")?;
|
||||
fs_utils::copy_custom_files(&settings.appimage().files, &data_dir)
|
||||
.with_context(|| "Failed to copy custom files")?;
|
||||
|
||||
fs::create_dir_all(&output_path)?;
|
||||
let app_dir_path = output_path.join(format!("{}.AppDir", settings.product_name()));
|
||||
let appimage_filename = format!(
|
||||
"{}_{}_{}.AppImage",
|
||||
settings.product_name(),
|
||||
settings.version_string(),
|
||||
appimage_arch
|
||||
);
|
||||
let appimage_path = output_path.join(&appimage_filename);
|
||||
fs_utils::create_dir(&app_dir_path, true)?;
|
||||
|
||||
fs::create_dir_all(&tools_path)?;
|
||||
let larger_icon = icons
|
||||
.iter()
|
||||
.filter(|i| i.width == i.height)
|
||||
.max_by_key(|i| i.width)
|
||||
.expect("couldn't find a square icon to use as AppImage icon");
|
||||
let larger_icon_path = larger_icon
|
||||
.path
|
||||
.strip_prefix(package_dir.join("data"))
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
log::info!(action = "Bundling"; "{} ({})", appimage_filename, appimage_path.display());
|
||||
|
||||
let app_dir_usr = app_dir_path.join("usr/");
|
||||
let app_dir_usr_bin = app_dir_usr.join("bin/");
|
||||
let app_dir_usr_lib = app_dir_usr.join("lib/");
|
||||
|
||||
fs_utils::copy_dir(&data_dir.join("usr/"), &app_dir_usr)?;
|
||||
|
||||
// Using create_dir_all for a single dir so we don't get errors if the path already exists
|
||||
fs::create_dir_all(&app_dir_usr_bin)?;
|
||||
fs::create_dir_all(app_dir_usr_lib)?;
|
||||
|
||||
// Copy bins and libs that linuxdeploy doesn't know about
|
||||
|
||||
// we also check if the user may have provided their own copy already
|
||||
// xdg-open will be handled by the `files` config instead
|
||||
if settings.deep_link_protocols().is_some() && !app_dir_usr_bin.join("xdg-open").exists() {
|
||||
fs::copy("/usr/bin/xdg-mime", app_dir_usr_bin.join("xdg-mime"))
|
||||
.fs_context("xdg-mime binary not found", "/usr/bin/xdg-mime".to_string())?;
|
||||
}
|
||||
|
||||
// we also check if the user may have provided their own copy already
|
||||
if settings.appimage().bundle_xdg_open && !app_dir_usr_bin.join("xdg-open").exists() {
|
||||
fs::copy("/usr/bin/xdg-open", app_dir_usr_bin.join("xdg-open"))
|
||||
.fs_context("xdg-open binary not found", "/usr/bin/xdg-open".to_string())?;
|
||||
}
|
||||
|
||||
let search_dirs = [
|
||||
match settings.binary_arch() {
|
||||
Arch::X86_64 => "/usr/lib/x86_64-linux-gnu/",
|
||||
Arch::X86 => "/usr/lib/i386-linux-gnu/",
|
||||
Arch::AArch64 => "/usr/lib/aarch64-linux-gnu/",
|
||||
Arch::Armhf => "/usr/lib/arm-linux-gnueabihf/",
|
||||
_ => unreachable!(),
|
||||
},
|
||||
"/usr/lib64",
|
||||
"/usr/lib",
|
||||
"/usr/libexec",
|
||||
];
|
||||
|
||||
for file in [
|
||||
"WebKitNetworkProcess",
|
||||
"WebKitWebProcess",
|
||||
"injected-bundle/libwebkit2gtkinjectedbundle.so",
|
||||
] {
|
||||
for source in search_dirs.map(PathBuf::from) {
|
||||
// TODO: Check if it's the same dir name on all systems
|
||||
let source = source.join("webkit2gtk-4.1").join(file);
|
||||
if source.exists() {
|
||||
fs_utils::copy_file(
|
||||
&source,
|
||||
&app_dir_path.join(source.strip_prefix("/").unwrap()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs::copy(
|
||||
tools_path.join(format!("AppRun-{tools_arch}")),
|
||||
app_dir_path.join("AppRun"),
|
||||
)?;
|
||||
fs::copy(
|
||||
app_dir_path.join(larger_icon_path),
|
||||
app_dir_path.join(format!("{product_name}.png")),
|
||||
)?;
|
||||
std::os::unix::fs::symlink(
|
||||
app_dir_path.join(format!("{product_name}.png")),
|
||||
app_dir_path.join(".DirIcon"),
|
||||
)?;
|
||||
std::os::unix::fs::symlink(
|
||||
app_dir_path.join(format!("usr/share/applications/{product_name}.desktop")),
|
||||
app_dir_path.join(format!("{product_name}.desktop")),
|
||||
)?;
|
||||
|
||||
let log_level = match settings.log_level() {
|
||||
log::Level::Error => "3",
|
||||
log::Level::Warn => "2",
|
||||
log::Level::Info => "1",
|
||||
_ => "0",
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(linuxdeploy_path);
|
||||
cmd.env("OUTPUT", &appimage_path);
|
||||
cmd.env("ARCH", tools_arch);
|
||||
// Looks like the cli arg isn't enough for the updated AppImage output-plugin.
|
||||
cmd.env("APPIMAGE_EXTRACT_AND_RUN", "1");
|
||||
cmd.args([
|
||||
"--appimage-extract-and-run",
|
||||
"--verbosity",
|
||||
log_level,
|
||||
"--appdir",
|
||||
&app_dir_path.display().to_string(),
|
||||
"--plugin",
|
||||
"gtk",
|
||||
]);
|
||||
if settings.appimage().bundle_media_framework {
|
||||
cmd.args(["--plugin", "gstreamer"]);
|
||||
}
|
||||
cmd.args(["--output", "appimage"]);
|
||||
|
||||
// Linuxdeploy logs everything into stderr so we have to ignore the output ourselves here
|
||||
if settings.log_level() == log::Level::Error {
|
||||
log::debug!(action = "Running"; "Command `linuxdeploy {}`", cmd.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
|
||||
if !cmd.output()?.status.success() {
|
||||
return Err(crate::Error::GenericError(
|
||||
"failed to run linuxdeploy".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
cmd.output_ok()?;
|
||||
}
|
||||
|
||||
fs::remove_dir_all(&package_dir)?;
|
||||
Ok(vec![appimage_path])
|
||||
}
|
||||
|
||||
// returns the linuxdeploy path to keep linuxdeploy_arch contained
|
||||
fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result<PathBuf> {
|
||||
let apprun = tools_path.join(format!("AppRun-{arch}"));
|
||||
if !apprun.exists() {
|
||||
let data = download(&format!(
|
||||
"https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-{arch}"
|
||||
))?;
|
||||
write_and_make_executable(&apprun, data)?;
|
||||
}
|
||||
|
||||
let linuxdeploy_arch = if arch == "i686" { "i386" } else { arch };
|
||||
let linuxdeploy = tools_path.join(format!("linuxdeploy-{linuxdeploy_arch}.AppImage"));
|
||||
if !linuxdeploy.exists() {
|
||||
let data = download(&format!("https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-{linuxdeploy_arch}.AppImage"))?;
|
||||
write_and_make_executable(&linuxdeploy, data)?;
|
||||
}
|
||||
|
||||
let gtk = tools_path.join("linuxdeploy-plugin-gtk.sh");
|
||||
if !gtk.exists() {
|
||||
let data = download("https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh")?;
|
||||
write_and_make_executable(>k, data)?;
|
||||
}
|
||||
|
||||
let gstreamer = tools_path.join("linuxdeploy-plugin-gstreamer.sh");
|
||||
if !gstreamer.exists() {
|
||||
let data = download("https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh")?;
|
||||
write_and_make_executable(&gstreamer, data)?;
|
||||
}
|
||||
|
||||
let appimage = tools_path.join("linuxdeploy-plugin-appimage.AppImage");
|
||||
if !appimage.exists() {
|
||||
// This is optional, linuxdeploy will fall back to its built-in version if the download failed.
|
||||
let data = download(&format!("https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-{arch}.AppImage"));
|
||||
match data {
|
||||
Ok(data) => write_and_make_executable(&appimage, data)?,
|
||||
Err(err) => {
|
||||
log::error!("Download of AppImage plugin failed. Using older built-in version instead.");
|
||||
if verbose {
|
||||
log::debug!("{err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should prevent linuxdeploy to be detected by appimage integration tools
|
||||
let _ = Command::new("dd")
|
||||
.args([
|
||||
"if=/dev/zero",
|
||||
"bs=1",
|
||||
"count=3",
|
||||
"seek=8",
|
||||
"conv=notrunc",
|
||||
&format!("of={}", linuxdeploy.display()),
|
||||
])
|
||||
.output();
|
||||
|
||||
Ok(linuxdeploy)
|
||||
}
|
||||
@@ -1,287 +1,21 @@
|
||||
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::debian;
|
||||
use crate::{
|
||||
bundle::settings::Arch,
|
||||
error::{Context, ErrorExt},
|
||||
utils::{fs_utils, http_utils::download, CommandExt},
|
||||
Settings,
|
||||
};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
/// Bundles the project.
|
||||
/// Returns a vector of PathBuf that shows where the AppImage was created.
|
||||
use crate::Settings;
|
||||
|
||||
mod linuxdeploy;
|
||||
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// generate the deb binary name
|
||||
let appimage_arch: &str = match settings.binary_arch() {
|
||||
Arch::X86_64 => "amd64",
|
||||
Arch::X86 => "i386",
|
||||
Arch::AArch64 => "aarch64",
|
||||
Arch::Armhf => "armhf",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"Unsupported architecture: {target:?}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let tools_arch = if settings.binary_arch() == Arch::Armhf {
|
||||
"armhf"
|
||||
} else {
|
||||
settings.target().split('-').next().unwrap()
|
||||
};
|
||||
|
||||
let output_path = settings.project_out_directory().join("bundle/appimage");
|
||||
if output_path.exists() {
|
||||
fs::remove_dir_all(&output_path)?;
|
||||
}
|
||||
|
||||
let tools_path = settings
|
||||
.local_tools_directory()
|
||||
.map(|d| d.join(".tauri"))
|
||||
.unwrap_or_else(|| {
|
||||
dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join("tauri"))
|
||||
});
|
||||
|
||||
fs::create_dir_all(&tools_path)?;
|
||||
|
||||
let linuxdeploy_path = prepare_tools(
|
||||
&tools_path,
|
||||
tools_arch,
|
||||
settings.log_level() != log::Level::Error,
|
||||
)?;
|
||||
|
||||
let package_dir = settings.project_out_directory().join("bundle/appimage_deb");
|
||||
|
||||
let main_binary = settings.main_binary()?;
|
||||
let product_name = settings.product_name();
|
||||
|
||||
let mut settings = settings.clone();
|
||||
if main_binary.name().contains(' ') {
|
||||
let main_binary_path = settings.binary_path(main_binary);
|
||||
let project_out_directory = settings.project_out_directory();
|
||||
|
||||
let main_binary_name_kebab = heck::AsKebabCase(main_binary.name()).to_string();
|
||||
let new_path = project_out_directory.join(&main_binary_name_kebab);
|
||||
fs::copy(main_binary_path, new_path)?;
|
||||
|
||||
let main_binary = settings.main_binary_mut()?;
|
||||
main_binary.set_name(main_binary_name_kebab);
|
||||
}
|
||||
|
||||
// generate deb_folder structure
|
||||
let (data_dir, icons) = debian::generate_data(&settings, &package_dir)
|
||||
.with_context(|| "Failed to build data folders and files")?;
|
||||
fs_utils::copy_custom_files(&settings.appimage().files, &data_dir)
|
||||
.with_context(|| "Failed to copy custom files")?;
|
||||
|
||||
fs::create_dir_all(&output_path)?;
|
||||
let app_dir_path = output_path.join(format!("{}.AppDir", settings.product_name()));
|
||||
let appimage_filename = format!(
|
||||
"{}_{}_{}.AppImage",
|
||||
settings.product_name(),
|
||||
settings.version_string(),
|
||||
appimage_arch
|
||||
);
|
||||
let appimage_path = output_path.join(&appimage_filename);
|
||||
fs_utils::create_dir(&app_dir_path, true)?;
|
||||
|
||||
fs::create_dir_all(&tools_path)?;
|
||||
let larger_icon = icons
|
||||
.iter()
|
||||
.filter(|i| i.width == i.height)
|
||||
.max_by_key(|i| i.width)
|
||||
.expect("couldn't find a square icon to use as AppImage icon");
|
||||
let larger_icon_path = larger_icon
|
||||
.path
|
||||
.strip_prefix(package_dir.join("data"))
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
log::info!(action = "Bundling"; "{} ({})", appimage_filename, appimage_path.display());
|
||||
|
||||
let app_dir_usr = app_dir_path.join("usr/");
|
||||
let app_dir_usr_bin = app_dir_usr.join("bin/");
|
||||
let app_dir_usr_lib = app_dir_usr.join("lib/");
|
||||
|
||||
fs_utils::copy_dir(&data_dir.join("usr/"), &app_dir_usr)?;
|
||||
|
||||
// Using create_dir_all for a single dir so we don't get errors if the path already exists
|
||||
fs::create_dir_all(&app_dir_usr_bin)?;
|
||||
fs::create_dir_all(app_dir_usr_lib)?;
|
||||
|
||||
// Copy bins and libs that linuxdeploy doesn't know about
|
||||
|
||||
// we also check if the user may have provided their own copy already
|
||||
// xdg-open will be handled by the `files` config instead
|
||||
if settings.deep_link_protocols().is_some() && !app_dir_usr_bin.join("xdg-open").exists() {
|
||||
fs::copy("/usr/bin/xdg-mime", app_dir_usr_bin.join("xdg-mime"))
|
||||
.fs_context("xdg-mime binary not found", "/usr/bin/xdg-mime".to_string())?;
|
||||
}
|
||||
|
||||
// we also check if the user may have provided their own copy already
|
||||
if settings.appimage().bundle_xdg_open && !app_dir_usr_bin.join("xdg-open").exists() {
|
||||
fs::copy("/usr/bin/xdg-open", app_dir_usr_bin.join("xdg-open"))
|
||||
.fs_context("xdg-open binary not found", "/usr/bin/xdg-open".to_string())?;
|
||||
}
|
||||
|
||||
let search_dirs = [
|
||||
match settings.binary_arch() {
|
||||
Arch::X86_64 => "/usr/lib/x86_64-linux-gnu/",
|
||||
Arch::X86 => "/usr/lib/i386-linux-gnu/",
|
||||
Arch::AArch64 => "/usr/lib/aarch64-linux-gnu/",
|
||||
Arch::Armhf => "/usr/lib/arm-linux-gnueabihf/",
|
||||
_ => unreachable!(),
|
||||
},
|
||||
"/usr/lib64",
|
||||
"/usr/lib",
|
||||
"/usr/libexec",
|
||||
];
|
||||
|
||||
for file in [
|
||||
"WebKitNetworkProcess",
|
||||
"WebKitWebProcess",
|
||||
"injected-bundle/libwebkit2gtkinjectedbundle.so",
|
||||
] {
|
||||
for source in search_dirs.map(PathBuf::from) {
|
||||
// TODO: Check if it's the same dir name on all systems
|
||||
let source = source.join("webkit2gtk-4.1").join(file);
|
||||
if source.exists() {
|
||||
fs_utils::copy_file(
|
||||
&source,
|
||||
&app_dir_path.join(source.strip_prefix("/").unwrap()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs::copy(
|
||||
tools_path.join(format!("AppRun-{tools_arch}")),
|
||||
app_dir_path.join("AppRun"),
|
||||
)?;
|
||||
fs::copy(
|
||||
app_dir_path.join(larger_icon_path),
|
||||
app_dir_path.join(format!("{product_name}.png")),
|
||||
)?;
|
||||
std::os::unix::fs::symlink(
|
||||
app_dir_path.join(format!("{product_name}.png")),
|
||||
app_dir_path.join(".DirIcon"),
|
||||
)?;
|
||||
std::os::unix::fs::symlink(
|
||||
app_dir_path.join(format!("usr/share/applications/{product_name}.desktop")),
|
||||
app_dir_path.join(format!("{product_name}.desktop")),
|
||||
)?;
|
||||
|
||||
let log_level = match settings.log_level() {
|
||||
log::Level::Error => "3",
|
||||
log::Level::Warn => "2",
|
||||
log::Level::Info => "1",
|
||||
_ => "0",
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(linuxdeploy_path);
|
||||
cmd.env("OUTPUT", &appimage_path);
|
||||
cmd.env("ARCH", tools_arch);
|
||||
// Looks like the cli arg isn't enough for the updated AppImage output-plugin.
|
||||
cmd.env("APPIMAGE_EXTRACT_AND_RUN", "1");
|
||||
cmd.args([
|
||||
"--appimage-extract-and-run",
|
||||
"--verbosity",
|
||||
log_level,
|
||||
"--appdir",
|
||||
&app_dir_path.display().to_string(),
|
||||
"--plugin",
|
||||
"gtk",
|
||||
]);
|
||||
if settings.appimage().bundle_media_framework {
|
||||
cmd.args(["--plugin", "gstreamer"]);
|
||||
}
|
||||
cmd.args(["--output", "appimage"]);
|
||||
|
||||
// Linuxdeploy logs everything into stderr so we have to ignore the output ourselves here
|
||||
if settings.log_level() == log::Level::Error {
|
||||
log::debug!(action = "Running"; "Command `linuxdeploy {}`", cmd.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
|
||||
if !cmd.output()?.status.success() {
|
||||
return Err(crate::Error::GenericError(
|
||||
"failed to run linuxdeploy".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
cmd.output_ok()?;
|
||||
}
|
||||
|
||||
fs::remove_dir_all(&package_dir)?;
|
||||
Ok(vec![appimage_path])
|
||||
linuxdeploy::bundle_project(settings)
|
||||
}
|
||||
|
||||
// returns the linuxdeploy path to keep linuxdeploy_arch contained
|
||||
fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result<PathBuf> {
|
||||
let apprun = tools_path.join(format!("AppRun-{arch}"));
|
||||
if !apprun.exists() {
|
||||
let data = download(&format!(
|
||||
"https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-{arch}"
|
||||
))?;
|
||||
write_and_make_executable(&apprun, &data)?;
|
||||
}
|
||||
|
||||
let linuxdeploy_arch = if arch == "i686" { "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 = include_bytes!("./linuxdeploy-plugin-gtk.sh");
|
||||
write_and_make_executable(>k, data)?;
|
||||
}
|
||||
|
||||
let gstreamer = tools_path.join("linuxdeploy-plugin-gstreamer.sh");
|
||||
if !gstreamer.exists() {
|
||||
let data = include_bytes!("./linuxdeploy-plugin-gstreamer.sh");
|
||||
write_and_make_executable(&gstreamer, data)?;
|
||||
}
|
||||
|
||||
let appimage = tools_path.join("linuxdeploy-plugin-appimage.AppImage");
|
||||
if !appimage.exists() {
|
||||
// This is optional, linuxdeploy will fall back to its built-in version if the download failed.
|
||||
let data = download(&format!("https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-{arch}.AppImage"));
|
||||
match data {
|
||||
Ok(data) => write_and_make_executable(&appimage, &data)?,
|
||||
Err(err) => {
|
||||
log::error!("Download of AppImage plugin failed. Using older built-in version instead.");
|
||||
if verbose {
|
||||
log::debug!("{err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should prevent linuxdeploy to be detected by appimage integration tools
|
||||
let _ = Command::new("dd")
|
||||
.args([
|
||||
"if=/dev/zero",
|
||||
"bs=1",
|
||||
"count=3",
|
||||
"seek=8",
|
||||
"conv=notrunc",
|
||||
&format!("of={}", linuxdeploy.display()),
|
||||
])
|
||||
.output();
|
||||
|
||||
Ok(linuxdeploy)
|
||||
}
|
||||
|
||||
fn write_and_make_executable(path: &Path, data: &[u8]) -> std::io::Result<()> {
|
||||
fn write_and_make_executable(path: &Path, data: Vec<u8>) -> std::io::Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
fs::write(path, data)?;
|
||||
|
||||
@@ -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")?;
|
||||
|
||||
@@ -7,8 +7,3 @@ pub mod appimage;
|
||||
pub mod debian;
|
||||
pub mod freedesktop;
|
||||
pub mod rpm;
|
||||
|
||||
mod util;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use util::patch_binary;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/// Change value of __TAURI_BUNDLE_TYPE static variable to mark which package type it was bundled in
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn patch_binary(
|
||||
binary_path: &std::path::PathBuf,
|
||||
package_type: &crate::PackageType,
|
||||
) -> crate::Result<()> {
|
||||
let mut file_data = std::fs::read(binary_path).expect("Could not read binary file.");
|
||||
|
||||
let elf = match goblin::Object::parse(&file_data)? {
|
||||
goblin::Object::Elf(elf) => elf,
|
||||
_ => return Err(crate::Error::GenericError("Not an ELF file".to_owned())),
|
||||
};
|
||||
|
||||
let offset = find_bundle_type_symbol(elf).ok_or(crate::Error::MissingBundleTypeVar)?;
|
||||
let offset = offset as usize;
|
||||
if offset + 3 <= file_data.len() {
|
||||
let chars = &mut file_data[offset..offset + 3];
|
||||
match package_type {
|
||||
crate::PackageType::Deb => chars.copy_from_slice(b"DEB"),
|
||||
crate::PackageType::Rpm => chars.copy_from_slice(b"RPM"),
|
||||
crate::PackageType::AppImage => chars.copy_from_slice(b"APP"),
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidPackageType(
|
||||
package_type.short_name().to_owned(),
|
||||
"linux".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::write(binary_path, &file_data)
|
||||
.map_err(|error| crate::Error::BinaryWriteError(error.to_string()))?;
|
||||
} else {
|
||||
return Err(crate::Error::BinaryOffsetOutOfRange);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find address of a symbol in relocations table
|
||||
#[cfg(target_os = "linux")]
|
||||
fn find_bundle_type_symbol(elf: goblin::elf::Elf<'_>) -> Option<i64> {
|
||||
for sym in elf.syms.iter() {
|
||||
if let Some(name) = elf.strtab.get_at(sym.st_name) {
|
||||
if name == "__TAURI_BUNDLE_TYPE" {
|
||||
for reloc in elf.dynrelas.iter() {
|
||||
if reloc.r_offset == sym.st_value {
|
||||
return Some(reloc.r_addend.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
// files into the `Contents` directory of the bundle.
|
||||
|
||||
use super::{
|
||||
icon::create_icns_file,
|
||||
icon::{app_icon_name_from_assets_car, create_assets_car_file, create_icns_file},
|
||||
sign::{notarize, notarize_auth, notarize_without_stapling, sign, SignTarget},
|
||||
};
|
||||
use crate::{
|
||||
@@ -76,11 +76,19 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
let bin_dir = bundle_directory.join("MacOS");
|
||||
let mut sign_paths = Vec::new();
|
||||
|
||||
let bundle_icon_file: Option<PathBuf> =
|
||||
{ create_icns_file(&resources_dir, settings).with_context(|| "Failed to create app icon")? };
|
||||
let bundle_icon_file =
|
||||
create_icns_file(&resources_dir, settings).with_context(|| "Failed to create app icon")?;
|
||||
|
||||
create_info_plist(&bundle_directory, bundle_icon_file, settings)
|
||||
.with_context(|| "Failed to create Info.plist")?;
|
||||
let assets_car_file = create_assets_car_file(&resources_dir, settings)
|
||||
.with_context(|| "Failed to create app Assets.car")?;
|
||||
|
||||
create_info_plist(
|
||||
&bundle_directory,
|
||||
bundle_icon_file,
|
||||
assets_car_file,
|
||||
settings,
|
||||
)
|
||||
.with_context(|| "Failed to create Info.plist")?;
|
||||
|
||||
let framework_paths = copy_frameworks_to_bundle(&bundle_directory, settings)
|
||||
.with_context(|| "Failed to bundle frameworks")?;
|
||||
@@ -136,7 +144,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
if matches!(e, NotarizeAuthError::MissingTeamId) {
|
||||
return Err(e.into());
|
||||
} else {
|
||||
log::warn!("skipping app notarization, {}", e.to_string());
|
||||
log::warn!("skipping app notarization, {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,6 +212,7 @@ fn copy_custom_files_to_bundle(bundle_directory: &Path, settings: &Settings) ->
|
||||
fn create_info_plist(
|
||||
bundle_dir: &Path,
|
||||
bundle_icon_file: Option<PathBuf>,
|
||||
assets_car_file: Option<PathBuf>,
|
||||
settings: &Settings,
|
||||
) -> crate::Result<()> {
|
||||
let mut plist = plist::Dictionary::new();
|
||||
@@ -213,17 +222,6 @@ fn create_info_plist(
|
||||
"CFBundleExecutable".into(),
|
||||
settings.main_binary_name()?.into(),
|
||||
);
|
||||
if let Some(path) = bundle_icon_file {
|
||||
plist.insert(
|
||||
"CFBundleIconFile".into(),
|
||||
path
|
||||
.file_name()
|
||||
.expect("No file name")
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
plist.insert(
|
||||
"CFBundleIdentifier".into(),
|
||||
settings.bundle_identifier().into(),
|
||||
@@ -362,6 +360,27 @@ fn create_info_plist(
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(path) = bundle_icon_file {
|
||||
plist.insert(
|
||||
"CFBundleIconFile".into(),
|
||||
path
|
||||
.file_name()
|
||||
.expect("No file name")
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(assets_car_file) = assets_car_file {
|
||||
if let Some(icon_name) = app_icon_name_from_assets_car(&assets_car_file) {
|
||||
// only set CFBundleIconName for the Assets.car, CFBundleIconFile is the fallback icns file
|
||||
plist.insert("CFBundleIconName".into(), icon_name.clone().into());
|
||||
} else {
|
||||
log::warn!("Failed to get icon name from Assets.car file");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(protocols) = settings.deep_link_protocols() {
|
||||
plist.insert(
|
||||
"CFBundleURLTypes".into(),
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::bundle::Settings;
|
||||
use crate::utils::{self, fs_utils};
|
||||
use crate::utils::{self, fs_utils, CommandExt};
|
||||
use std::{
|
||||
cmp::min,
|
||||
ffi::OsStr,
|
||||
fs::{self, File},
|
||||
io::{self, BufWriter},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use image::GenericImageView;
|
||||
@@ -63,6 +64,11 @@ pub fn create_icns_file(out_dir: &Path, settings: &Settings) -> crate::Result<Op
|
||||
let mut images_to_resize: Vec<(image::DynamicImage, u32, u32)> = vec![];
|
||||
for icon_path in settings.icon_files() {
|
||||
let icon_path = icon_path?;
|
||||
|
||||
if icon_path.extension().map_or(false, |ext| ext == "car") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let icon = image::open(&icon_path)?;
|
||||
let density = if utils::is_retina(&icon_path) { 2 } else { 1 };
|
||||
let (w, h) = icon.dimensions();
|
||||
@@ -113,3 +119,206 @@ fn make_icns_image(img: image::DynamicImage) -> io::Result<icns::Image> {
|
||||
};
|
||||
icns::Image::from_data(pixel_format, img.width(), img.height(), img.into_bytes())
|
||||
}
|
||||
|
||||
/// Creates an Assets.car file from a .icon file if there are any in the settings.
|
||||
/// Uses an existing Assets.car file if it exists in the settings.
|
||||
/// Returns the path to the Assets.car file.
|
||||
pub fn create_assets_car_file(
|
||||
out_dir: &Path,
|
||||
settings: &Settings,
|
||||
) -> crate::Result<Option<PathBuf>> {
|
||||
let Some(icons) = settings.icons() else {
|
||||
return Ok(None);
|
||||
};
|
||||
// If one of the icon files is already a CAR file, just use that.
|
||||
let mut icon_composer_icon_path = None;
|
||||
for icon in icons {
|
||||
let icon_path = Path::new(&icon).to_path_buf();
|
||||
if icon_path.extension() == Some(OsStr::new("car")) {
|
||||
let dest_path = out_dir.join("Assets.car");
|
||||
fs_utils::copy_file(&icon_path, &dest_path)?;
|
||||
return Ok(Some(dest_path));
|
||||
}
|
||||
|
||||
if icon_path.extension() == Some(OsStr::new("icon")) {
|
||||
icon_composer_icon_path.replace(icon_path);
|
||||
}
|
||||
}
|
||||
|
||||
let Some(icon_composer_icon_path) = icon_composer_icon_path else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Check actool version - must be >= 26
|
||||
if let Some(version) = get_actool_version() {
|
||||
// Parse the major version number (before the dot)
|
||||
let major_version: Option<u32> = version.split('.').next().and_then(|s| s.parse().ok());
|
||||
|
||||
if let Some(major) = major_version {
|
||||
if major < 26 {
|
||||
log::error!("actool version is less than 26, skipping Assets.car file creation. Please update Xcode to 26 or above and try again.");
|
||||
return Ok(None);
|
||||
}
|
||||
} else {
|
||||
// If we can't parse the version, return None to be safe
|
||||
log::error!("failed to parse actool version, skipping Assets.car file creation");
|
||||
return Ok(None);
|
||||
}
|
||||
} else {
|
||||
log::error!("failed to get actool version, skipping Assets.car file creation");
|
||||
// If we can't get the version, return None to be safe
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Create a temporary directory for actool work
|
||||
let temp_dir = tempfile::tempdir()
|
||||
.map_err(|e| crate::Error::GenericError(format!("failed to create temp dir: {e}")))?;
|
||||
|
||||
let icon_dest_path = temp_dir.path().join("Icon.icon");
|
||||
let output_path = temp_dir.path().join("out");
|
||||
|
||||
// Copy the input .icon directory to the temp directory
|
||||
if icon_composer_icon_path.is_dir() {
|
||||
fs_utils::copy_dir(&icon_composer_icon_path, &icon_dest_path)?;
|
||||
} else {
|
||||
return Err(crate::Error::GenericError(format!(
|
||||
"{} must be a directory",
|
||||
icon_composer_icon_path.display()
|
||||
)));
|
||||
}
|
||||
|
||||
// Create the output directory
|
||||
fs::create_dir_all(&output_path)?;
|
||||
|
||||
// Run actool command
|
||||
let mut cmd = Command::new("actool");
|
||||
cmd.arg(&icon_dest_path);
|
||||
cmd.arg("--compile");
|
||||
cmd.arg(&output_path);
|
||||
cmd.arg("--output-format");
|
||||
cmd.arg("human-readable-text");
|
||||
cmd.arg("--notices");
|
||||
cmd.arg("--warnings");
|
||||
cmd.arg("--output-partial-info-plist");
|
||||
cmd.arg(output_path.join("assetcatalog_generated_info.plist"));
|
||||
cmd.arg("--app-icon");
|
||||
cmd.arg("Icon");
|
||||
cmd.arg("--include-all-app-icons");
|
||||
cmd.arg("--accent-color");
|
||||
cmd.arg("AccentColor");
|
||||
cmd.arg("--enable-on-demand-resources");
|
||||
cmd.arg("NO");
|
||||
cmd.arg("--development-region");
|
||||
cmd.arg("en");
|
||||
cmd.arg("--target-device");
|
||||
cmd.arg("mac");
|
||||
cmd.arg("--minimum-deployment-target");
|
||||
cmd.arg("26.0");
|
||||
cmd.arg("--platform");
|
||||
cmd.arg("macosx");
|
||||
|
||||
cmd.output_ok()?;
|
||||
|
||||
let assets_car_path = output_path.join("Assets.car");
|
||||
if !assets_car_path.exists() {
|
||||
return Err(crate::Error::GenericError(
|
||||
"actool did not generate Assets.car file".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
// copy to out_dir
|
||||
fs_utils::copy_file(&assets_car_path, &out_dir.join("Assets.car"))?;
|
||||
|
||||
Ok(Some(out_dir.join("Assets.car")))
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct AssetsCarInfo {
|
||||
#[serde(rename = "AssetType", default)]
|
||||
asset_type: String,
|
||||
#[serde(rename = "Name", default)]
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub fn app_icon_name_from_assets_car(assets_car_path: &Path) -> Option<String> {
|
||||
let Ok(output) = Command::new("assetutil")
|
||||
.arg("--info")
|
||||
.arg(assets_car_path)
|
||||
.output_ok()
|
||||
.inspect_err(|e| log::error!("Failed to get app icon name from Assets.car file: {e}"))
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let output = String::from_utf8(output.stdout).ok()?;
|
||||
let assets_car_info: Vec<AssetsCarInfo> = serde_json::from_str(&output)
|
||||
.inspect_err(|e| log::error!("Failed to parse Assets.car file info: {e}"))
|
||||
.ok()?;
|
||||
assets_car_info
|
||||
.iter()
|
||||
.find(|info| info.asset_type == "Icon Image")
|
||||
.map(|info| info.name.clone())
|
||||
}
|
||||
|
||||
/// Returns the actool short bundle version by running `actool --version --output-format=human-readable-text`.
|
||||
/// Returns `None` if the command fails or the output cannot be parsed.
|
||||
pub fn get_actool_version() -> Option<String> {
|
||||
let Ok(output) = Command::new("actool")
|
||||
.arg("--version")
|
||||
.arg("--output-format=human-readable-text")
|
||||
.output_ok()
|
||||
.inspect_err(|e| log::error!("Failed to get actool version: {e}"))
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let output = String::from_utf8(output.stdout).ok()?;
|
||||
parse_actool_version(&output)
|
||||
}
|
||||
|
||||
fn parse_actool_version(output: &str) -> Option<String> {
|
||||
// The output format is:
|
||||
// /* com.apple.actool.version */
|
||||
// bundle-version: 24411
|
||||
// short-bundle-version: 26.1
|
||||
for line in output.lines() {
|
||||
let line = line.trim();
|
||||
if let Some(version) = line.strip_prefix("short-bundle-version:") {
|
||||
return Some(version.trim().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_actool_version() {
|
||||
let output = r#"/* com.apple.actool.version */
|
||||
some other line
|
||||
bundle-version: 24411
|
||||
short-bundle-version: 26.1
|
||||
another line
|
||||
"#;
|
||||
|
||||
let version = parse_actool_version(output).expect("Failed to parse version");
|
||||
assert_eq!(version, "26.1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_actool_version_missing_fields() {
|
||||
let output = r#"/* com.apple.actool.version */
|
||||
bundle-version: 24411
|
||||
"#;
|
||||
|
||||
assert!(parse_actool_version(output).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_actool_version_empty() {
|
||||
assert!(parse_actool_version("").is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,10 @@ fn generate_icon_files(bundle_dir: &Path, settings: &Settings) -> crate::Result<
|
||||
// Fall back to non-PNG files for any missing sizes.
|
||||
for icon_path in settings.icon_files() {
|
||||
let icon_path = icon_path?;
|
||||
if icon_path.extension() == Some(OsStr::new("png")) {
|
||||
if icon_path
|
||||
.extension()
|
||||
.map_or(false, |ext| ext == "png" || ext == "car")
|
||||
{
|
||||
continue;
|
||||
} else if icon_path.extension() == Some(OsStr::new("icns")) {
|
||||
let icon_family = icns::IconFamily::read(File::open(&icon_path)?)?;
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -126,7 +126,7 @@ const ALL_PACKAGE_TYPES: &[PackageType] = &[
|
||||
PackageType::IosBundle,
|
||||
#[cfg(target_os = "windows")]
|
||||
PackageType::WindowsMsi,
|
||||
#[cfg(target_os = "windows")]
|
||||
// NSIS installers can be built on all platforms but it's hidden in the --help output on macOS/Linux.
|
||||
PackageType::Nsis,
|
||||
#[cfg(target_os = "macos")]
|
||||
PackageType::MacOsBundle,
|
||||
@@ -231,7 +231,7 @@ pub struct AppImageSettings {
|
||||
pub struct RpmSettings {
|
||||
/// The list of RPM dependencies your application relies on.
|
||||
pub depends: Option<Vec<String>>,
|
||||
/// the list of 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>>,
|
||||
@@ -470,6 +470,13 @@ pub struct NsisSettings {
|
||||
pub sidebar_image: Option<PathBuf>,
|
||||
/// The path to an icon file used as the installer icon.
|
||||
pub installer_icon: Option<PathBuf>,
|
||||
/// The path to an icon file used as the uninstaller icon.
|
||||
pub uninstaller_icon: Option<PathBuf>,
|
||||
/// The path to a bitmap file to display on the header of uninstallers pages.
|
||||
/// Defaults to [`Self::header_image`]. If this is set but [`Self::header_image`] is not, a default image from NSIS will be applied to `header_image`
|
||||
///
|
||||
/// The recommended dimensions are 150px x 57px.
|
||||
pub uninstaller_header_image: Option<PathBuf>,
|
||||
/// Whether the installation will be for all users or just the current user.
|
||||
pub install_mode: NSISInstallerMode,
|
||||
/// A list of installer languages.
|
||||
@@ -532,6 +539,10 @@ pub struct NsisSettings {
|
||||
/// Try to ensure that the WebView2 version is equal to or newer than this version,
|
||||
/// if the user's WebView2 is older than this version,
|
||||
/// the installer will try to trigger a WebView2 update.
|
||||
#[deprecated(
|
||||
since = "2.8.0",
|
||||
note = "Use `WindowsSettings::minimum_webview2_version` instead."
|
||||
)]
|
||||
pub minimum_webview2_version: Option<String>,
|
||||
}
|
||||
|
||||
@@ -587,6 +598,10 @@ pub struct WindowsSettings {
|
||||
/// if you are on another platform and want to cross-compile and sign you will
|
||||
/// need to use another tool like `osslsigncode`.
|
||||
pub sign_command: Option<CustomSignCommandSettings>,
|
||||
/// Try to ensure that the WebView2 version is equal to or newer than this version,
|
||||
/// if the user's WebView2 is older than this version,
|
||||
/// the installer will try to trigger a WebView2 update.
|
||||
pub minimum_webview2_version: Option<String>,
|
||||
}
|
||||
|
||||
impl WindowsSettings {
|
||||
@@ -612,6 +627,7 @@ mod _default {
|
||||
webview_install_mode: Default::default(),
|
||||
allow_downgrades: true,
|
||||
sign_command: None,
|
||||
minimum_webview2_version: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -795,6 +811,8 @@ pub struct Settings {
|
||||
local_tools_directory: Option<PathBuf>,
|
||||
/// the bundle settings.
|
||||
bundle_settings: BundleSettings,
|
||||
/// Same as `bundle_settings.icon`, but without the .icon directory.
|
||||
icon_files: Option<Vec<String>>,
|
||||
/// the binaries to bundle.
|
||||
binaries: Vec<BundleBinary>,
|
||||
/// The target platform.
|
||||
@@ -906,6 +924,14 @@ impl SettingsBuilder {
|
||||
};
|
||||
let target_platform = TargetPlatform::from_triple(&target);
|
||||
|
||||
let icon_files = self.bundle_settings.icon.as_ref().map(|paths| {
|
||||
paths
|
||||
.iter()
|
||||
.filter(|p| !p.ends_with(".icon"))
|
||||
.cloned()
|
||||
.collect()
|
||||
});
|
||||
|
||||
Ok(Settings {
|
||||
log_level: self.log_level.unwrap_or(log::Level::Error),
|
||||
package: self
|
||||
@@ -925,6 +951,7 @@ impl SettingsBuilder {
|
||||
.map(|bins| external_binaries(bins, &target, &target_platform)),
|
||||
..self.bundle_settings
|
||||
},
|
||||
icon_files,
|
||||
target_platform,
|
||||
target,
|
||||
no_sign: self.no_sign,
|
||||
@@ -958,6 +985,11 @@ impl Settings {
|
||||
&self.target_platform
|
||||
}
|
||||
|
||||
/// Raw list of icons.
|
||||
pub fn icons(&self) -> Option<&Vec<String>> {
|
||||
self.bundle_settings.icon.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the architecture for the binary being bundled (e.g. "arm", "x86" or "x86_64").
|
||||
pub fn binary_arch(&self) -> Arch {
|
||||
if self.target.starts_with("x86_64") {
|
||||
@@ -1092,7 +1124,7 @@ impl Settings {
|
||||
|
||||
/// Returns an iterator over the icon files to be used for this bundle.
|
||||
pub fn icon_files(&self) -> ResourcePaths<'_> {
|
||||
match self.bundle_settings.icon {
|
||||
match self.icon_files {
|
||||
Some(ref paths) => ResourcePaths::new(paths.as_slice(), false),
|
||||
None => ResourcePaths::new(&[], false),
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -70,12 +70,16 @@
|
||||
<Property Id="ARPURLUPDATEINFO" Value="{{homepage}}"/>
|
||||
{{/if}}
|
||||
|
||||
<!-- NOTE: The order of RegistrySearch elements below matters. In WIX, when multiple
|
||||
RegistrySearch elements are listed under a single Property, the LAST successful
|
||||
match wins. We list the NSIS default-key search first and the MSI InstallDir
|
||||
search second so that the MSI-specific path takes priority when both keys exist. -->
|
||||
<Property Id="INSTALLDIR">
|
||||
<!-- First attempt: Search for "InstallDir" -->
|
||||
<RegistrySearch Id="PrevInstallDirWithName" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw" />
|
||||
|
||||
<!-- Second attempt: If the first fails, search for the default key value (this is how the nsis installer currently stores the path) -->
|
||||
<!-- First attempt: Search for the default key value (this is how the nsis installer stores the path) -->
|
||||
<RegistrySearch Id="PrevInstallDirNoName" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Type="raw" />
|
||||
|
||||
<!-- Second attempt: Search for "InstallDir" which takes priority if found (this is how the msi installer stores the path) -->
|
||||
<RegistrySearch Id="PrevInstallDirWithName" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw" />
|
||||
</Property>
|
||||
|
||||
<!-- launch app checkbox -->
|
||||
@@ -279,38 +283,62 @@
|
||||
|
||||
{{#if install_webview}}
|
||||
<!-- WebView2 -->
|
||||
<Property Id="WVRTINSTALLED">
|
||||
<RegistrySearch Id="WVRTInstalledSystem" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no" />
|
||||
<RegistrySearch Id="WVRTInstalledUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
|
||||
<Property Id="INSTALLED_WEBVIEW2_VERSION">
|
||||
<RegistrySearch Id="Webview2VersionSystemx64" Root="HKLM" Key="SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" />
|
||||
<RegistrySearch Id="Webview2VersionSystemx86" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" />
|
||||
<RegistrySearch Id="Webview2VersionUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
{{#if download_bootstrapper}}
|
||||
<!-- Download webview bootstrapper mode -->
|
||||
<CustomAction Id='DownloadAndInvokeBootstrapper' Directory="INSTALLDIR" Execute="deferred" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\{] [\[]Net.ServicePointManager[\]]::SecurityProtocol = [\[]Net.SecurityProtocolType[\]]::Tls12 [\}] catch [\{][\}]; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ({{webview_installer_args}} '/install') -Wait' Return='check'/>
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
<![CDATA[NOT(REMOVE OR INSTALLED_WEBVIEW2_VERSION)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded webview bootstrapper mode -->
|
||||
{{#if webview2_bootstrapper_path}}
|
||||
<!-- Embedded webview bootstrapper mode -->
|
||||
<Binary Id="MicrosoftEdgeWebview2Setup.exe" SourceFile="{{webview2_bootstrapper_path}}"/>
|
||||
<CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
<![CDATA[NOT(REMOVE OR INSTALLED_WEBVIEW2_VERSION)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded offline installer -->
|
||||
{{#if webview2_installer_path}}
|
||||
<!-- Embedded offline installer -->
|
||||
<Binary Id="MicrosoftEdgeWebView2RuntimeInstaller.exe" SourceFile="{{webview2_installer_path}}"/>
|
||||
<CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeStandalone' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
<![CDATA[NOT(REMOVE OR INSTALLED_WEBVIEW2_VERSION)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
{{#if minimum_webview2_version}}
|
||||
<!-- Update WebView2 if minimum version requirement not met -->
|
||||
<Property Id="MINIMUM_WEBVIEW2_VERSION" Value="{{minimum_webview2_version}}" />
|
||||
<Property Id="EDGEUPDATE_PATH">
|
||||
<RegistrySearch Id="EdgeUpdateLocalMachine64" Root="HKLM" Key="SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate" Name="path" Type="raw" />
|
||||
<RegistrySearch Id="EdgeUpdateLocalMachine32" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate" Name="path" Type="raw"/>
|
||||
<RegistrySearch Id="EdgeUpdateCurrentUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate" Name="path" Type="raw"/>
|
||||
</Property>
|
||||
<!-- Chromium updater docs: https://source.chromium.org/chromium/chromium/src/+/main:docs/updater/user_manual.md -->
|
||||
<!-- Modified from "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft EdgeWebView\ModifyPath" -->
|
||||
<CustomAction Id="UpdateWebView2ViaEdgeUpdate" Execute="deferred" Property="EDGEUPDATE_PATH" Return="check" Impersonate="no" ExeCommand="/install appguid={F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}&needsadmin=true" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='UpdateWebView2ViaEdgeUpdate' Before='InstallFinalize'>
|
||||
<![CDATA[
|
||||
NOT REMOVE
|
||||
AND INSTALLED_WEBVIEW2_VERSION
|
||||
AND (INSTALLED_WEBVIEW2_VERSION < MINIMUM_WEBVIEW2_VERSION)
|
||||
]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
@@ -532,6 +532,13 @@ pub fn build_wix_app_installer(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(minimum_webview2_version) = &settings.windows().minimum_webview2_version {
|
||||
data.insert(
|
||||
"minimum_webview2_version",
|
||||
to_json(minimum_webview2_version),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(license) = settings.license_file() {
|
||||
if license.ends_with(".rtf") {
|
||||
data.insert("license", to_json(license));
|
||||
@@ -753,26 +760,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();
|
||||
|
||||
@@ -41,6 +41,8 @@ ${StrLoc}
|
||||
!define INSTALLERICON "{{installer_icon}}"
|
||||
!define SIDEBARIMAGE "{{sidebar_image}}"
|
||||
!define HEADERIMAGE "{{header_image}}"
|
||||
!define UNINSTALLERICON "{{uninstaller_icon}}"
|
||||
!define UNINSTALLERHEADERIMAGE "{{uninstaller_header_image}}"
|
||||
!define MAINBINARYNAME "{{main_binary_name}}"
|
||||
!define MAINBINARYSRCPATH "{{main_binary_path}}"
|
||||
!define BUNDLEID "{{bundle_id}}"
|
||||
@@ -129,10 +131,26 @@ VIAddVersionKey "ProductVersion" "${VERSION}"
|
||||
!define MUI_WELCOMEFINISHPAGE_BITMAP "${SIDEBARIMAGE}"
|
||||
!endif
|
||||
|
||||
; Installer header image
|
||||
; Enable header images for installer and uninstaller pages when either image is configured.
|
||||
!if "${HEADERIMAGE}" != ""
|
||||
!define MUI_HEADERIMAGE
|
||||
!define MUI_HEADERIMAGE_BITMAP "${HEADERIMAGE}"
|
||||
!else if "${UNINSTALLERHEADERIMAGE}" != ""
|
||||
!define MUI_HEADERIMAGE
|
||||
!endif
|
||||
|
||||
; Installer header image
|
||||
!if "${HEADERIMAGE}" != ""
|
||||
!define MUI_HEADERIMAGE_BITMAP "${HEADERIMAGE}"
|
||||
!endif
|
||||
|
||||
; Uninstaller header image
|
||||
!if "${UNINSTALLERHEADERIMAGE}" != ""
|
||||
!define MUI_HEADERIMAGE_UNBITMAP "${UNINSTALLERHEADERIMAGE}"
|
||||
!endif
|
||||
|
||||
; Uninstaller icon
|
||||
!if "${UNINSTALLERICON}" != ""
|
||||
!define MUI_UNICON "${UNINSTALLERICON}"
|
||||
!endif
|
||||
|
||||
; Define registry key to store installer language
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
LangString addOrReinstall ${LANG_NORWEGIAN} "Legg til/reinstaller komponenter"
|
||||
LangString alreadyInstalled ${LANG_NORWEGIAN} "Allerede installert"
|
||||
LangString alreadyInstalledLong ${LANG_NORWEGIAN} "${PRODUCTNAME} ${VERSION} er allerede installert. Velg operasjonen du vil utføre og klikk Neste for å fortsette."
|
||||
LangString appRunning ${LANG_NORWEGIAN} "{{product_name}} kjører! Lukk den først og prøv igjen."
|
||||
LangString appRunningOkKill ${LANG_NORWEGIAN} "{{product_name}} kjører!$\nKlikk OK for å avslutte den"
|
||||
LangString chooseMaintenanceOption ${LANG_NORWEGIAN} "Velg vedlikeholdsoperasjonen som skal utføres."
|
||||
LangString choowHowToInstall ${LANG_NORWEGIAN} "Velg hvordan du vil installere ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_NORWEGIAN} "Opprett skrivebordssnarvei"
|
||||
LangString dontUninstall ${LANG_NORWEGIAN} "Ikke avinstaller"
|
||||
LangString dontUninstallDowngrade ${LANG_NORWEGIAN} "Ikke avinstaller (nedgradering uten avinstallasjon er deaktivert for denne installasjonen)"
|
||||
LangString failedToKillApp ${LANG_NORWEGIAN} "Kunne ikke avslutte {{product_name}}. Lukk den først og prøv igjen"
|
||||
LangString installingWebview2 ${LANG_NORWEGIAN} "Installerer WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_NORWEGIAN} "En nyere versjon av ${PRODUCTNAME} er allerede installert! Det anbefales ikke at du installerer en eldre versjon. Hvis du virkelig vil installere denne eldre versjonen, er det bedre å avinstallere den nåværende versjonen først. Velg operasjonen du vil utføre og klikk Neste for å fortsette."
|
||||
LangString older ${LANG_NORWEGIAN} "eldre"
|
||||
LangString olderOrUnknownVersionInstalled ${LANG_NORWEGIAN} "En $R4-versjon av ${PRODUCTNAME} er installert på systemet ditt. Det anbefales at du avinstallerer den nåværende versjonen før installasjon. Velg operasjonen du vil utføre og klikk Neste for å fortsette."
|
||||
LangString silentDowngrades ${LANG_NORWEGIAN} "Nedgraderinger er deaktivert for denne installasjonen. Kan ikke fortsette med stille installasjon; bruk den grafiske installasjonen i stedet.$\n"
|
||||
LangString unableToUninstall ${LANG_NORWEGIAN} "Kunne ikke avinstallere!"
|
||||
LangString uninstallApp ${LANG_NORWEGIAN} "Avinstaller ${PRODUCTNAME}"
|
||||
LangString uninstallBeforeInstalling ${LANG_NORWEGIAN} "Avinstaller før installasjon"
|
||||
LangString unknown ${LANG_NORWEGIAN} "ukjent"
|
||||
LangString webview2AbortError ${LANG_NORWEGIAN} "Kunne ikke installere WebView2! Appen kan ikke kjøre uten den. Prøv å starte installasjonen på nytt."
|
||||
LangString webview2DownloadError ${LANG_NORWEGIAN} "Feil: Nedlasting av WebView2 mislyktes - $0"
|
||||
LangString webview2DownloadSuccess ${LANG_NORWEGIAN} "WebView2-bootstrapper lastet ned"
|
||||
LangString webview2Downloading ${LANG_NORWEGIAN} "Laster ned WebView2-bootstrapper..."
|
||||
LangString webview2InstallError ${LANG_NORWEGIAN} "Feil: Installering av WebView2 mislyktes med avslutningskode $1"
|
||||
LangString webview2InstallSuccess ${LANG_NORWEGIAN} "WebView2 ble installert"
|
||||
LangString deleteAppData ${LANG_NORWEGIAN} "Slett programdata"
|
||||
@@ -40,8 +40,8 @@ const NSIS_URL: &str =
|
||||
#[cfg(target_os = "windows")]
|
||||
const NSIS_SHA1: &str = "EF7FF767E5CBD9EDD22ADD3A32C9B8F4500BB10D";
|
||||
const NSIS_TAURI_UTILS_URL: &str =
|
||||
"https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.5.2/nsis_tauri_utils.dll";
|
||||
const NSIS_TAURI_UTILS_SHA1: &str = "D0C502F45DF55C0465C9406088FF016C2E7E6817";
|
||||
"https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.5.3/nsis_tauri_utils.dll";
|
||||
const NSIS_TAURI_UTILS_SHA1: &str = "75197FEE3C6A814FE035788D1C34EAD39349B860";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const NSIS_REQUIRED_FILES: &[&str] = &[
|
||||
@@ -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();
|
||||
@@ -350,6 +354,20 @@ fn build_nsis_app_installer(
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(uninstaller_icon) = &nsis.uninstaller_icon {
|
||||
data.insert(
|
||||
"uninstaller_icon",
|
||||
to_json(dunce::canonicalize(uninstaller_icon)?),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(uninstaller_header_image) = &nsis.uninstaller_header_image {
|
||||
data.insert(
|
||||
"uninstaller_header_image",
|
||||
to_json(dunce::canonicalize(uninstaller_header_image)?),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(installer_hooks) = &nsis.installer_hooks {
|
||||
let installer_hooks = dunce::canonicalize(installer_hooks)?;
|
||||
data.insert("installer_hooks", to_json(installer_hooks));
|
||||
@@ -358,7 +376,12 @@ fn build_nsis_app_installer(
|
||||
if let Some(start_menu_folder) = &nsis.start_menu_folder {
|
||||
data.insert("start_menu_folder", to_json(start_menu_folder));
|
||||
}
|
||||
if let Some(minimum_webview2_version) = &nsis.minimum_webview2_version {
|
||||
#[allow(deprecated)]
|
||||
if let Some(minimum_webview2_version) = nsis
|
||||
.minimum_webview2_version
|
||||
.as_ref()
|
||||
.or(settings.windows().minimum_webview2_version.as_ref())
|
||||
{
|
||||
data.insert(
|
||||
"minimum_webview2_version",
|
||||
to_json(minimum_webview2_version),
|
||||
@@ -617,13 +640,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 +886,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))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -77,75 +77,3 @@ pub fn os_bitness<'a>() -> Option<&'a str> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn patch_binary(binary_path: &PathBuf, package_type: &crate::PackageType) -> crate::Result<()> {
|
||||
let mut file_data = std::fs::read(binary_path)?;
|
||||
|
||||
let pe = match goblin::Object::parse(&file_data)? {
|
||||
goblin::Object::PE(pe) => pe,
|
||||
_ => {
|
||||
return Err(crate::Error::BinaryParseError(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidInput, "binary is not a PE file").into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let tauri_bundle_section = pe
|
||||
.sections
|
||||
.iter()
|
||||
.find(|s| s.name().unwrap_or_default() == ".taubndl")
|
||||
.ok_or(crate::Error::MissingBundleTypeVar)?;
|
||||
|
||||
let data_offset = tauri_bundle_section.pointer_to_raw_data as usize;
|
||||
let pointer_size = if pe.is_64 { 8 } else { 4 };
|
||||
let ptr_bytes = file_data
|
||||
.get(data_offset..data_offset + pointer_size)
|
||||
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
|
||||
// `try_into` is safe to `unwrap` here because we have already checked the slice's size through `get`
|
||||
let ptr_value = if pe.is_64 {
|
||||
u64::from_le_bytes(ptr_bytes.try_into().unwrap())
|
||||
} else {
|
||||
u32::from_le_bytes(ptr_bytes.try_into().unwrap()).into()
|
||||
};
|
||||
|
||||
let rdata_section = pe
|
||||
.sections
|
||||
.iter()
|
||||
.find(|s| s.name().unwrap_or_default() == ".rdata")
|
||||
.ok_or_else(|| {
|
||||
crate::Error::BinaryParseError(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidInput, ".rdata section not found").into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let rva = ptr_value.checked_sub(pe.image_base as u64).ok_or_else(|| {
|
||||
crate::Error::BinaryParseError(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid RVA offset").into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// see "Relative virtual address (RVA)" for explanation of offset arithmetic here:
|
||||
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#general-concepts
|
||||
let file_offset = rdata_section.pointer_to_raw_data as usize
|
||||
+ (rva as usize).saturating_sub(rdata_section.virtual_address as usize);
|
||||
|
||||
// Overwrite the string at that offset
|
||||
let string_bytes = file_data
|
||||
.get_mut(file_offset..file_offset + 3)
|
||||
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
|
||||
match package_type {
|
||||
crate::PackageType::Nsis => string_bytes.copy_from_slice(b"NSS"),
|
||||
crate::PackageType::WindowsMsi => string_bytes.copy_from_slice(b"MSI"),
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidPackageType(
|
||||
package_type.short_name().to_owned(),
|
||||
"windows".to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::write(binary_path, &file_data)
|
||||
.map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -99,19 +99,13 @@ pub enum Error {
|
||||
#[error("Wrong package type {0} for platform {1}")]
|
||||
InvalidPackageType(String, String),
|
||||
/// Bundle type symbol missing in binary
|
||||
#[cfg_attr(
|
||||
target_os = "linux",
|
||||
error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date and that symbol stripping is disabled (https://doc.rust-lang.org/cargo/reference/profiles.html#strip)")
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(target_os = "linux"),
|
||||
error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")
|
||||
)]
|
||||
#[error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")]
|
||||
MissingBundleTypeVar,
|
||||
/// Failed to write binary file changed
|
||||
#[error("Failed to write binary file changes: `{0}`")]
|
||||
BinaryWriteError(String),
|
||||
/// Invalid offset while patching binary file
|
||||
#[deprecated]
|
||||
#[error("Invalid offset while patching binary file")]
|
||||
BinaryOffsetOutOfRange,
|
||||
/// Unsupported architecture.
|
||||
|
||||
@@ -1,5 +1,94 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.10.1]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`35c35f27a`](https://www.github.com/tauri-apps/tauri/commit/35c35f27aedc430b602ec74059b271128c15ad36) ([#14931](https://www.github.com/tauri-apps/tauri/pull/14931) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Support comma-separated list of Cargo features on all commands.
|
||||
- [`0d1cb83ba`](https://www.github.com/tauri-apps/tauri/commit/0d1cb83bab2aa482c7d73116893fd7ff6aa56283) ([#14932](https://www.github.com/tauri-apps/tauri/pull/14932) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix missing Cargo args when running mobile dev and build commands.
|
||||
- [`33754ae5e`](https://www.github.com/tauri-apps/tauri/commit/33754ae5e3740d022483b6164511c5c001a3c24b) ([#15022](https://www.github.com/tauri-apps/tauri/pull/15022) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix updater signing private keys generated using `tauri signer generate` with empty password can't be used (The keys generated during tauri were broken between v2.9.3 and v2.10.0, you'll need to regenerate them)
|
||||
|
||||
### What's Changed
|
||||
|
||||
- [`7be58a1c6`](https://www.github.com/tauri-apps/tauri/commit/7be58a1c643a7ed6d0f484a7e1134022618eb2b2) ([#14894](https://www.github.com/tauri-apps/tauri/pull/14894) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Log patching bundle type information again
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.3`
|
||||
- Upgraded to `tauri-bundler@2.8.1`
|
||||
|
||||
## \[2.10.0]
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`f82594410`](https://www.github.com/tauri-apps/tauri/commit/f82594410cd57d6f794f58d4afea0ed335aa796f) ([#13253](https://www.github.com/tauri-apps/tauri/pull/13253) by [@Armaldio](https://www.github.com/tauri-apps/tauri/../../Armaldio)) Allow electron to run the CLI directly
|
||||
- [`2d28e3143`](https://www.github.com/tauri-apps/tauri/commit/2d28e3143ee3d97d7570ea03877aa00a0d6e47d0) ([#14632](https://www.github.com/tauri-apps/tauri/pull/14632) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Small code refactors for improved code readability. No user facing changes.
|
||||
- [`a2abe2e6b`](https://www.github.com/tauri-apps/tauri/commit/a2abe2e6bcb9e1eed8484240dfdb76a5bc28ae58) ([#14607](https://www.github.com/tauri-apps/tauri/pull/14607) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Simplified internal representation of `features: Option<Vec<String>>` with `Vec<String>`, no user facing changes
|
||||
- [`84b04c4a8`](https://www.github.com/tauri-apps/tauri/commit/84b04c4a8d3310b7a7091d10e36244bf94996e51) ([#14759](https://www.github.com/tauri-apps/tauri/pull/14759) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added new environment variables for `tauri signer sign` command, to align with existing environment variables used in `tauri build`, `tauri bundle` and `tauri signer generate`
|
||||
|
||||
- `TAURI_SIGNING_PRIVATE_KEY`
|
||||
- `TAURI_SIGNING_PRIVATE_KEY_PATH`
|
||||
- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`
|
||||
|
||||
The old environment variables are deprecated and will be removed in a future release.
|
||||
|
||||
- `TAURI_PRIVATE_KEY`
|
||||
- `TAURI_PRIVATE_KEY_PATH`
|
||||
- `TAURI_PRIVATE_KEY_PASSWORD`
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`62aa13a12`](https://www.github.com/tauri-apps/tauri/commit/62aa13a124ef46bb5ce9887a2a574dd35ef86d4f) ([#14629](https://www.github.com/tauri-apps/tauri/pull/14629) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `android build`'s `--aab` and `--apk` flags requiring a value to be provided.
|
||||
- [`eccff9758`](https://www.github.com/tauri-apps/tauri/commit/eccff97588232055bd0cafd83e6ee03d11a501fb) ([#14779](https://www.github.com/tauri-apps/tauri/pull/14779) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix empty associated-domains entitlements when domains are not configured for deep links.
|
||||
- [`ea31b07f1`](https://www.github.com/tauri-apps/tauri/commit/ea31b07f19e0aa467ed0f921f60575cfe09809c8) ([#14789](https://www.github.com/tauri-apps/tauri/pull/14789) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fixed the command description for `tauri inspect`
|
||||
- [`7fca58230`](https://www.github.com/tauri-apps/tauri/commit/7fca58230f97c3e6834134419514a0c7dbbe784b) ([#14830](https://www.github.com/tauri-apps/tauri/pull/14830) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Updated `nsis_tauri_utils` to 0.5.3:
|
||||
|
||||
- Use an alternative method `CreateProcessWithTokenW` to run programs as user, this fixed a problem that the program launched with the previous method can't query its own handle
|
||||
- [`53611c4d7`](https://www.github.com/tauri-apps/tauri/commit/53611c4d7bdaf89b9a5d7c46a9c4bf4e34216148) ([#14747](https://www.github.com/tauri-apps/tauri/pull/14747) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Only watch dependent workspace members when running `tauri dev` instead of watching on all members
|
||||
- [`1b0e335d3`](https://www.github.com/tauri-apps/tauri/commit/1b0e335d3f3445948d6590f7e074275d97cd9859) ([#14713](https://www.github.com/tauri-apps/tauri/pull/14713) by [@wasuaje](https://www.github.com/tauri-apps/tauri/../../wasuaje)) `tauri signer sign` doesn't work for files without an extension
|
||||
|
||||
### What's Changed
|
||||
|
||||
- [`e3fdcb500`](https://www.github.com/tauri-apps/tauri/commit/e3fdcb5002b362b46cde2a1971e4e7f2a1161208) ([#14836](https://www.github.com/tauri-apps/tauri/pull/14836) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Continued refactors of tauri-cli, fix too weak atomics.
|
||||
- [`0575dd287`](https://www.github.com/tauri-apps/tauri/commit/0575dd287e021b61d2aedf64d62ae84a2c925fb4) ([#14521](https://www.github.com/tauri-apps/tauri/pull/14521) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Change the way bundle type information is added to binary files. Instead of looking up the value of a variable we simply look for the default value.
|
||||
- [`7f7d9aac2`](https://www.github.com/tauri-apps/tauri/commit/7f7d9aac214e22d9492490543f7a9bcae0a6659e) ([#14668](https://www.github.com/tauri-apps/tauri/pull/14668) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Refactored internal use of static on config and directory resolvings, no user facing changes, please report any regressions if you encounter any
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.2`
|
||||
- Upgraded to `tauri-macos-sign@2.3.3`
|
||||
- Upgraded to `tauri-bundler@2.8.0`
|
||||
|
||||
## \[2.9.6]
|
||||
|
||||
### What's Changed
|
||||
|
||||
- [`7b1b3514d`](https://www.github.com/tauri-apps/tauri/commit/7b1b3514df771e6e9859b9f54fa4df332433948e) ([#14621](https://www.github.com/tauri-apps/tauri/pull/14621) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Errors like `Error Failed to parse version 2 for for NPM package tauri` when there was no `package-lock.json` file present yet or when using ones like `link:./tauri` are now only logged in `--verbose` mode.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-macos-sign@2.3.2`
|
||||
- Upgraded to `tauri-bundler@2.7.5`
|
||||
|
||||
## \[2.9.5]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`f022b2d1a`](https://www.github.com/tauri-apps/tauri/commit/f022b2d1ae57612e39c75782926f2f341d9034a8) ([#14582](https://www.github.com/tauri-apps/tauri/pull/14582) by [@hrzlgnm](https://www.github.com/tauri-apps/tauri/../../hrzlgnm)) Fixed an issue that caused the cli to error out with missing private key, in case the option `--no-sign` was requested and the `tauri.config` has signing key set and the plugin `tauri-plugin-updater` is used.
|
||||
- [`f855caf8a`](https://www.github.com/tauri-apps/tauri/commit/f855caf8a3830aa5dd6d0b039312866a5d9c3606) ([#14481](https://www.github.com/tauri-apps/tauri/pull/14481) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fixed the mismatched tauri package versions check didn't work for pnpm
|
||||
- [`79a7d9ec0`](https://www.github.com/tauri-apps/tauri/commit/79a7d9ec01be1a371b8e923848140fea75e9caed) ([#14468](https://www.github.com/tauri-apps/tauri/pull/14468) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the cli to print errors like `Error Failed to parse version 2 for crate tauri` when there was no `Cargo.lock` file present yet. This will still be logged in `--verbose` mode.
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
- [`ce98d87ce`](https://www.github.com/tauri-apps/tauri/commit/ce98d87ce0aaa907285852eb80691197424e03c3) ([#14474](https://www.github.com/tauri-apps/tauri/pull/14474) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) refactor: remove needless collect. No user facing changes.
|
||||
- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-bundler@2.7.4`
|
||||
- Upgraded to `tauri-macos-sign@2.3.1`
|
||||
- Upgraded to `tauri-utils@2.8.1`
|
||||
|
||||
## \[2.9.4]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-cli"
|
||||
version = "2.9.4"
|
||||
version = "2.10.1"
|
||||
authors = ["Tauri Programme within The Commons Conservancy"]
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
@@ -36,7 +36,7 @@ name = "cargo-tauri"
|
||||
path = "src/main.rs"
|
||||
|
||||
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies]
|
||||
cargo-mobile2 = { version = "0.21.1", default-features = false }
|
||||
cargo-mobile2 = { version = "0.22.3", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
jsonrpsee = { version = "0.24", features = ["server"] }
|
||||
@@ -47,7 +47,7 @@ sublime_fuzzy = "0.7"
|
||||
clap_complete = "4"
|
||||
clap = { version = "4", features = ["derive", "env"] }
|
||||
thiserror = "2"
|
||||
tauri-bundler = { version = "2.7.3", default-features = false, path = "../tauri-bundler" }
|
||||
tauri-bundler = { version = "2.8.1", default-features = false, path = "../tauri-bundler" }
|
||||
colored = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1", features = ["preserve_order"] }
|
||||
@@ -56,21 +56,23 @@ notify = "8"
|
||||
notify-debouncer-full = "0.6"
|
||||
shared_child = "1"
|
||||
duct = "1.0"
|
||||
toml_edit = { version = "0.23", features = ["serde"] }
|
||||
toml_edit = { version = "0.25", features = ["serde"] }
|
||||
json-patch = "3"
|
||||
tauri-utils = { version = "2.8.0", path = "../tauri-utils", features = [
|
||||
tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
|
||||
"isolation",
|
||||
"schema",
|
||||
"config-json5",
|
||||
"config-toml",
|
||||
"html-manipulation",
|
||||
"html-manipulation-2",
|
||||
] }
|
||||
toml = "0.9"
|
||||
jsonschema = "0.33"
|
||||
toml = "1"
|
||||
jsonschema = { version = "0.33", default-features = false }
|
||||
handlebars = "6"
|
||||
include_dir = "0.7"
|
||||
dirs = "6"
|
||||
minisign = "0.8"
|
||||
# 0.7.4 to 0.8.0 were broken, 0.9 pulls in getrandom 0.4 with a high MSRV
|
||||
# see https://github.com/tauri-apps/tauri/pull/15022
|
||||
minisign = "=0.7.3"
|
||||
base64 = "0.22"
|
||||
ureq = { version = "3", default-features = false, features = ["gzip"] }
|
||||
os_info = "3"
|
||||
@@ -87,8 +89,6 @@ env_logger = "0.11"
|
||||
icns = { package = "tauri-icns", version = "0.1" }
|
||||
image = { version = "0.25", default-features = false, features = ["ico"] }
|
||||
axum = { version = "0.8", features = ["ws"] }
|
||||
html5ever = "0.29"
|
||||
kuchiki = { package = "kuchikiki", version = "=0.8.8-speedreader" }
|
||||
tokio = { version = "1", features = ["macros", "sync"] }
|
||||
common-path = "1"
|
||||
serde-value = "0.7"
|
||||
@@ -133,7 +133,7 @@ libc = "0.2"
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
plist = "1"
|
||||
tauri-macos-sign = { version = "2.3.0", path = "../tauri-macos-sign" }
|
||||
tauri-macos-sign = { version = "2.3.3", path = "../tauri-macos-sign" }
|
||||
object = { version = "0.36", default-features = false, features = [
|
||||
"macho",
|
||||
"read_core",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://schema.tauri.app/config/2.9.3",
|
||||
"$id": "https://schema.tauri.app/config/2.10.3",
|
||||
"title": "Config",
|
||||
"description": "The Tauri configuration object.\n It is read from a file where you can define your frontend assets,\n configure the bundler and define a tray icon.\n\n The configuration file is generated by the\n [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in\n your Tauri application source directory (src-tauri).\n\n Once generated, you may modify it at will to customize your Tauri application.\n\n ## File Formats\n\n By default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\n Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\n The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\n The TOML file name is `Tauri.toml`.\n\n ## Platform-Specific Configuration\n\n In addition to the default configuration file, Tauri can\n read a platform-specific configuration from `tauri.linux.conf.json`,\n `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\n (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\n which gets merged with the main configuration object.\n\n ## Configuration Structure\n\n The configuration is composed of the following objects:\n\n - [`app`](#appconfig): The Tauri configuration\n - [`build`](#buildconfig): The build configuration\n - [`bundle`](#bundleconfig): The bundle configurations\n - [`plugins`](#pluginconfig): The plugins configuration\n\n Example tauri.config.json file:\n\n ```json\n {\n \"productName\": \"tauri-app\",\n \"version\": \"0.1.0\",\n \"build\": {\n \"beforeBuildCommand\": \"\",\n \"beforeDevCommand\": \"\",\n \"devUrl\": \"http://localhost:3000\",\n \"frontendDist\": \"../dist\"\n },\n \"app\": {\n \"security\": {\n \"csp\": null\n },\n \"windows\": [\n {\n \"fullscreen\": false,\n \"height\": 600,\n \"resizable\": true,\n \"title\": \"Tauri App\",\n \"width\": 800\n }\n ]\n },\n \"bundle\": {},\n \"plugins\": {}\n }\n ```",
|
||||
"type": "object",
|
||||
@@ -131,6 +131,7 @@
|
||||
"allowDowngrades": true,
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": null,
|
||||
"minimumWebview2Version": null,
|
||||
"nsis": null,
|
||||
"signCommand": null,
|
||||
"timestampUrl": null,
|
||||
@@ -165,7 +166,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 +232,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 +263,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 +271,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 +279,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 +299,7 @@
|
||||
"format": "double"
|
||||
},
|
||||
"minHeight": {
|
||||
"description": "The min window height.",
|
||||
"description": "The min window height in logical pixels.",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
@@ -306,7 +307,7 @@
|
||||
"format": "double"
|
||||
},
|
||||
"maxWidth": {
|
||||
"description": "The max window width.",
|
||||
"description": "The max window width in logical pixels.",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
@@ -314,7 +315,7 @@
|
||||
"format": "double"
|
||||
},
|
||||
"maxHeight": {
|
||||
"description": "The max window height.",
|
||||
"description": "The max window height in logical pixels.",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
@@ -603,6 +604,27 @@
|
||||
"$ref": "#/definitions/ScrollBarStyle"
|
||||
}
|
||||
]
|
||||
},
|
||||
"activityName": {
|
||||
"description": "The name of the Android activity to create for this window.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"createdByActivityName": {
|
||||
"description": "The name of the Android activity that is creating this webview window.\n\n This is important to determine which stack the activity will belong to.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"requestedBySceneIdentifier": {
|
||||
"description": "Sets the identifier of the scene that is requesting the new scene,\n establishing a relationship between the two scenes.\n\n By default the system uses the foreground scene.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -652,13 +674,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 +1318,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 +2013,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 +2022,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"
|
||||
@@ -2204,6 +2226,7 @@
|
||||
"allowDowngrades": true,
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": null,
|
||||
"minimumWebview2Version": null,
|
||||
"nsis": null,
|
||||
"signCommand": null,
|
||||
"timestampUrl": null,
|
||||
@@ -2646,6 +2669,13 @@
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"minimumWebview2Version": {
|
||||
"description": "Try to ensure that the WebView2 version is equal to or newer than this version,\n if the user's WebView2 is older than this version,\n the installer will try to trigger a WebView2 update.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"wix": {
|
||||
"description": "Configuration for the MSI generated with WiX.",
|
||||
"anyOf": [
|
||||
@@ -2792,7 +2822,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"
|
||||
@@ -2966,6 +2996,20 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"uninstallerIcon": {
|
||||
"description": "The path to an icon file used as the uninstaller icon.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"uninstallerHeaderImage": {
|
||||
"description": "The path to a bitmap file to display on the header of uninstallers pages.\n Defaults to [`Self::header_image`]. If this is set but [`Self::header_image`] is not, a default image from NSIS will be applied to `header_image`\n\n The recommended dimensions are 150px x 57px.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"installMode": {
|
||||
"description": "Whether the installation will be for all users or just the current user.",
|
||||
"default": "currentUser",
|
||||
@@ -3024,7 +3068,8 @@
|
||||
]
|
||||
},
|
||||
"minimumWebview2Version": {
|
||||
"description": "Try to ensure that the WebView2 version is equal to or newer than this version,\n if the user's WebView2 is older than this version,\n the installer will try to trigger a WebView2 update.",
|
||||
"description": "Deprecated: use [`WindowsConfig::minimum_webview2_version`] (`bundle > windows > minimumWebview2Version`) instead.\n\n Try to ensure that the WebView2 version is equal to or newer than this version,\n if the user's WebView2 is older than this version,\n the installer will try to trigger a WebView2 update.",
|
||||
"deprecated": true,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -3096,7 +3141,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"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"cli.js": {
|
||||
"version": "2.9.4",
|
||||
"version": "2.10.1",
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"tauri": "2.9.3",
|
||||
"tauri-build": "2.5.2",
|
||||
"tauri-plugin": "2.5.1"
|
||||
"tauri": "2.10.3",
|
||||
"tauri-build": "2.5.6",
|
||||
"tauri-plugin": "2.5.4"
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -24,10 +24,10 @@ pub struct Options {
|
||||
#[clap(long)]
|
||||
description: Option<String>,
|
||||
/// List of commands to allow
|
||||
#[clap(short, long, use_value_delimiter = true)]
|
||||
#[clap(short, long, value_delimiter = ',')]
|
||||
allow: Option<Vec<String>>,
|
||||
/// List of commands to deny
|
||||
#[clap(short, long, use_value_delimiter = true)]
|
||||
#[clap(short, long, value_delimiter = ',')]
|
||||
deny: Option<Vec<String>>,
|
||||
/// Output file format.
|
||||
#[clap(long, default_value_t = FileFormat::Json)]
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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};
|
||||
@@ -39,8 +39,8 @@ pub struct Options {
|
||||
#[clap(short, long)]
|
||||
pub target: Option<String>,
|
||||
/// Space or comma separated list of features to activate
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
|
||||
pub features: Option<Vec<String>>,
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
|
||||
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));
|
||||
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(())
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ impl ValueEnum for BundleFormat {
|
||||
}
|
||||
|
||||
fn to_possible_value(&self) -> Option<PossibleValue> {
|
||||
let hide = self.0 == PackageType::Updater;
|
||||
let hide = (!cfg!(windows) && self.0 == PackageType::Nsis) || self.0 == PackageType::Updater;
|
||||
Some(PossibleValue::new(self.0.short_name()).hide(hide))
|
||||
}
|
||||
}
|
||||
@@ -70,8 +70,8 @@ pub struct Options {
|
||||
#[clap(short, long)]
|
||||
pub config: Vec<ConfigValue>,
|
||||
/// Space or comma separated list of features, should be the same features passed to `tauri build` if any.
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
|
||||
pub features: Option<Vec<String>>,
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
|
||||
pub features: Vec<String>,
|
||||
/// Target triple to build against.
|
||||
///
|
||||
/// It must be one of the values outputted by `$rustc --print target-list` or `universal-apple-darwin` for an universal macOS application.
|
||||
@@ -118,7 +118,7 @@ impl From<crate::build::Options> for Options {
|
||||
}
|
||||
|
||||
pub fn command(options: Options, verbosity: u8) -> crate::Result<()> {
|
||||
crate::helpers::app_paths::resolve();
|
||||
let dirs = crate::helpers::app_paths::resolve_dirs();
|
||||
|
||||
let ci = options.ci;
|
||||
|
||||
@@ -131,35 +131,30 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> {
|
||||
let config = get_config(
|
||||
target,
|
||||
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
|
||||
dirs.tauri,
|
||||
)?;
|
||||
|
||||
let interface = AppInterface::new(
|
||||
config.lock().unwrap().as_ref().unwrap(),
|
||||
options.target.clone(),
|
||||
)?;
|
||||
let interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;
|
||||
|
||||
let tauri_path = tauri_dir();
|
||||
std::env::set_current_dir(tauri_path).context("failed to set current directory")?;
|
||||
std::env::set_current_dir(dirs.tauri).context("failed to set current directory")?;
|
||||
|
||||
let config_guard = config.lock().unwrap();
|
||||
let config_ = config_guard.as_ref().unwrap();
|
||||
|
||||
if let Some(minimum_system_version) = &config_.bundle.macos.minimum_system_version {
|
||||
if let Some(minimum_system_version) = &config.bundle.macos.minimum_system_version {
|
||||
std::env::set_var("MACOSX_DEPLOYMENT_TARGET", minimum_system_version);
|
||||
}
|
||||
|
||||
let app_settings = interface.app_settings();
|
||||
let interface_options = options.clone().into();
|
||||
|
||||
let out_dir = app_settings.out_dir(&interface_options)?;
|
||||
let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?;
|
||||
|
||||
bundle(
|
||||
&options,
|
||||
verbosity,
|
||||
ci,
|
||||
&interface,
|
||||
&app_settings,
|
||||
config_,
|
||||
&*app_settings,
|
||||
&config,
|
||||
&dirs,
|
||||
&out_dir,
|
||||
)
|
||||
}
|
||||
@@ -170,8 +165,9 @@ pub fn bundle<A: AppSettings>(
|
||||
verbosity: u8,
|
||||
ci: bool,
|
||||
interface: &AppInterface,
|
||||
app_settings: &std::sync::Arc<A>,
|
||||
app_settings: &A,
|
||||
config: &ConfigMetadata,
|
||||
dirs: &Dirs,
|
||||
out_dir: &Path,
|
||||
) -> crate::Result<()> {
|
||||
let package_types: Vec<PackageType> = if let Some(bundles) = &options.bundles {
|
||||
@@ -198,12 +194,19 @@ pub fn bundle<A: AppSettings>(
|
||||
before_bundle,
|
||||
interface,
|
||||
options.debug,
|
||||
dirs.frontend,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut settings = app_settings
|
||||
.get_bundler_settings(options.clone().into(), config, out_dir, package_types)
|
||||
.get_bundler_settings(
|
||||
options.clone().into(),
|
||||
config,
|
||||
out_dir,
|
||||
package_types,
|
||||
dirs.tauri,
|
||||
)
|
||||
.with_context(|| "failed to build bundler settings")?;
|
||||
settings.set_no_sign(options.no_sign);
|
||||
|
||||
@@ -249,6 +252,11 @@ fn sign_updaters(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if settings.no_sign() {
|
||||
log::warn!("Updater signing is skipped due to --no-sign flag.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// get the public key
|
||||
let pubkey = &update_settings.pubkey;
|
||||
// check if pubkey points to a file...
|
||||
@@ -279,6 +287,9 @@ fn sign_updaters(
|
||||
} else {
|
||||
private_key
|
||||
};
|
||||
if password.is_none() {
|
||||
log::info!("Decrypting updater signing key, expect a prompt for password")
|
||||
}
|
||||
let secret_key =
|
||||
updater_signature::secret_key(private_key, password).context("failed to decode secret key")?;
|
||||
let public_key = updater_signature::pub_key(pubkey).context("failed to decode pubkey")?;
|
||||
|
||||
@@ -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");
|
||||
@@ -56,8 +54,8 @@ pub struct Options {
|
||||
#[clap(short, long)]
|
||||
pub target: Option<String>,
|
||||
/// List of cargo features to activate
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
|
||||
pub features: Option<Vec<String>>,
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
|
||||
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() {
|
||||
@@ -282,9 +241,7 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
|
||||
.canonicalize()
|
||||
.fs_context("failed to canonicalize path", path.to_path_buf())?;
|
||||
|
||||
let ip = options
|
||||
.host
|
||||
.unwrap_or_else(|| Ipv4Addr::new(127, 0, 0, 1).into());
|
||||
let ip = options.host.unwrap_or_else(|| Ipv4Addr::LOCALHOST.into());
|
||||
|
||||
let server_url = builtin_dev_server::start(path, ip, options.port)
|
||||
.context("failed to start builtin dev server")?;
|
||||
@@ -297,19 +254,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 +311,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 +331,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(
|
||||
|
||||
@@ -7,8 +7,6 @@ use axum::{
|
||||
http::{header, StatusCode, Uri},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use html5ever::{namespace_url, ns, LocalName, QualName};
|
||||
use kuchiki::{traits::TendrilSink, NodeRef};
|
||||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::{Path, PathBuf},
|
||||
@@ -128,30 +126,14 @@ async fn ws_handler(ws: WebSocketUpgrade, state: State<ServerState>) -> Response
|
||||
}
|
||||
|
||||
fn inject_address(html_bytes: Vec<u8>, address: &SocketAddr) -> Vec<u8> {
|
||||
fn with_html_head<F: FnOnce(&NodeRef)>(document: &mut NodeRef, f: F) {
|
||||
if let Ok(ref node) = document.select_first("head") {
|
||||
f(node.as_node())
|
||||
} else {
|
||||
let node = NodeRef::new_element(
|
||||
QualName::new(None, ns!(html), LocalName::from("head")),
|
||||
None,
|
||||
);
|
||||
f(&node);
|
||||
document.prepend(node)
|
||||
}
|
||||
}
|
||||
let document = tauri_utils::html2::parse_doc(String::from_utf8_lossy(&html_bytes).into_owned());
|
||||
|
||||
let mut document = kuchiki::parse_html()
|
||||
.one(String::from_utf8_lossy(&html_bytes).into_owned())
|
||||
.document_node;
|
||||
with_html_head(&mut document, |head| {
|
||||
let script = RELOAD_SCRIPT.replace("{{reload_url}}", &format!("ws://{address}/__tauri_cli"));
|
||||
let script_el = NodeRef::new_element(QualName::new(None, ns!(html), "script".into()), None);
|
||||
script_el.append(NodeRef::new_text(script));
|
||||
head.prepend(script_el);
|
||||
});
|
||||
tauri_utils::html2::append_script_to_head(
|
||||
&document,
|
||||
&RELOAD_SCRIPT.replace("{{reload_url}}", &format!("ws://{address}/__tauri_cli")),
|
||||
);
|
||||
|
||||
tauri_utils::html::serialize_node(&document)
|
||||
tauri_utils::html2::serialize_doc(&document)
|
||||
}
|
||||
|
||||
fn fs_read_scoped(path: PathBuf, scope: &Path) -> crate::Result<Vec<u8>> {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 {
|
||||
@@ -113,6 +110,8 @@ pub fn nsis_settings(config: NsisConfig) -> tauri_bundler::NsisSettings {
|
||||
header_image: config.header_image,
|
||||
sidebar_image: config.sidebar_image,
|
||||
installer_icon: config.installer_icon,
|
||||
uninstaller_icon: config.uninstaller_icon,
|
||||
uninstaller_header_image: config.uninstaller_header_image,
|
||||
install_mode: config.install_mode,
|
||||
languages: config.languages,
|
||||
custom_language_files: config.custom_language_files,
|
||||
@@ -120,6 +119,7 @@ pub fn nsis_settings(config: NsisConfig) -> tauri_bundler::NsisSettings {
|
||||
compression: config.compression,
|
||||
start_menu_folder: config.start_menu_folder,
|
||||
installer_hooks: config.installer_hooks,
|
||||
#[allow(deprecated)]
|
||||
minimum_webview2_version: config.minimum_webview2_version,
|
||||
}
|
||||
}
|
||||
@@ -141,32 +141,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 +173,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 +191,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 +229,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`
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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}`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:{}",
|
||||
@@ -153,6 +158,8 @@ where
|
||||
}
|
||||
|
||||
/// Gets the updater secret key from the given private key and password.
|
||||
///
|
||||
/// If `password` is `None`, a password is going to be prompted interactively.
|
||||
pub fn secret_key<S: AsRef<[u8]>>(
|
||||
private_key: S,
|
||||
password: Option<String>,
|
||||
@@ -199,16 +206,30 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// This was encrypted with an empty string
|
||||
const PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5dkpDN09RZm5GeVAzc2RuYlNzWVVJelJRQnNIV2JUcGVXZUplWXZXYXpqUUFBQkFBQUFBQUFBQUFBQUlBQUFBQTZrN2RnWGh5dURxSzZiL1ZQSDdNcktiaHRxczQwMXdQelRHbjRNcGVlY1BLMTBxR2dpa3I3dDE1UTVDRDE4MXR4WlQwa1BQaXdxKy9UU2J2QmVSNXhOQWFDeG1GSVllbUNpTGJQRkhhTnROR3I5RmdUZi90OGtvaGhJS1ZTcjdZU0NyYzhQWlQ5cGM9Cg==";
|
||||
|
||||
// minisign >=0.7.4,<0.8.0 couldn't handle empty passwords.
|
||||
// minisign >=0.7.4,<0.8.0 couldn't handle empty passwords if the private key is encrypted with an empty string.
|
||||
#[test]
|
||||
fn empty_password_is_valid() {
|
||||
let path = std::env::temp_dir().join("minisign-password-text.txt");
|
||||
std::fs::write(&path, b"TAURI").expect("failed to write test file");
|
||||
|
||||
let secret_key =
|
||||
super::secret_key(PRIVATE_KEY, Some("".into())).expect("failed to resolve secret key");
|
||||
super::sign_file(&secret_key, &path).expect("failed to sign file");
|
||||
secret_key(PRIVATE_KEY, Some("".into())).expect("failed to resolve secret key");
|
||||
sign_file(&secret_key, &path).expect("failed to sign file");
|
||||
}
|
||||
|
||||
// This tests the newly generated keys with empty string password works
|
||||
// minisign >=0.7.4,<=0.8.0 generate keys unencrypted if the password is empty but is marked encrypted hence unusable
|
||||
#[test]
|
||||
fn generate_empty_password_keys_and_use() {
|
||||
let KeyPair { pk, sk } = generate_key(Some("".to_owned())).unwrap();
|
||||
let pk = pub_key(pk).unwrap();
|
||||
let sk = secret_key(sk, Some("".into())).unwrap();
|
||||
let data = b"TAURI".as_slice();
|
||||
sign(Some(&pk), &sk, data, None, None).expect("failed to sign file");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
use crate::{
|
||||
error::{Context, Error, ErrorExt},
|
||||
helpers::app_paths::tauri_dir,
|
||||
Result,
|
||||
};
|
||||
|
||||
@@ -95,7 +94,7 @@ pub struct Options {
|
||||
output: Option<PathBuf>,
|
||||
|
||||
/// Custom PNG icon sizes to generate. When set, the default icons are not generated.
|
||||
#[clap(short, long, use_value_delimiter = true)]
|
||||
#[clap(short, long, value_delimiter = ',')]
|
||||
png: Option<Vec<u32>>,
|
||||
|
||||
/// The background color of the iOS icon - string as defined in the W3C's CSS Color Module Level 4 <https://www.w3.org/TR/css-color-4/>.
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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}")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
|
||||
@@ -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<()>;
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
error::{bail, 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| {
|
||||
@@ -146,15 +144,11 @@ impl Interface for Rust {
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
let manifest_path = tauri_dir.join("Cargo.toml");
|
||||
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(&manifest_path, RecursiveMode::NonRecursive)
|
||||
.with_context(|| format!("failed to watch {}", manifest_path.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 +166,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 +176,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 +211,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 +220,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 +256,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 +364,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 +396,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 +410,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();
|
||||
@@ -409,7 +426,7 @@ fn dev_options(
|
||||
}
|
||||
*args = dev_args;
|
||||
|
||||
if mobile {
|
||||
if mobile && !args.contains(&"--lib".into()) {
|
||||
args.push("--lib".into());
|
||||
}
|
||||
|
||||
@@ -429,35 +446,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,45 +477,16 @@ 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());
|
||||
if !args.contains(&"--lib".into()) {
|
||||
args.push("--lib".into());
|
||||
}
|
||||
} else {
|
||||
args.push("--bins".into());
|
||||
}
|
||||
@@ -517,9 +495,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 +505,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| {
|
||||
@@ -573,49 +556,47 @@ impl Rust {
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if let Ok(events) = rx.recv() {
|
||||
for event in events {
|
||||
if event.kind.is_access() {
|
||||
continue;
|
||||
}
|
||||
while let Ok(events) = rx.recv() {
|
||||
let paths: Vec<PathBuf> = events
|
||||
.into_iter()
|
||||
.filter(|event| !event.kind.is_access())
|
||||
.flat_map(|event| event.event.paths)
|
||||
.filter(|path| !ignore_matcher.is_ignore(path, path.is_dir()))
|
||||
.collect();
|
||||
|
||||
if let Some(event_path) = event.paths.first() {
|
||||
if !ignore_matcher.is_ignore(event_path, event_path.is_dir()) {
|
||||
if is_configuration_file(self.app_settings.target_platform, event_path) {
|
||||
if let Ok(config) = reload_config(merge_configs) {
|
||||
let (manifest, modified) =
|
||||
rewrite_manifest(config.lock().unwrap().as_ref().unwrap())?;
|
||||
if modified {
|
||||
*self.app_settings.manifest.lock().unwrap() = manifest;
|
||||
// no need to run the watcher logic, the manifest was modified
|
||||
// and it will trigger the watcher again
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"File {} changed. Rebuilding application...",
|
||||
display_path(event_path.strip_prefix(frontend_path).unwrap_or(event_path))
|
||||
);
|
||||
|
||||
let mut p = process.lock().unwrap();
|
||||
p.kill().context("failed to kill app process")?;
|
||||
|
||||
// wait for the process to exit
|
||||
// note that on mobile, kill() already waits for the process to exit (duct implementation)
|
||||
loop {
|
||||
if !matches!(p.try_wait(), Ok(None)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
*p = run(self)?;
|
||||
}
|
||||
}
|
||||
let config_file_changed = paths
|
||||
.iter()
|
||||
.any(|path| is_configuration_file(self.app_settings.target_platform, path));
|
||||
if config_file_changed && reload_config(config, merge_configs, dirs.tauri).is_ok() {
|
||||
let (manifest, modified) = rewrite_manifest(config, dirs.tauri)?;
|
||||
if modified {
|
||||
*self.app_settings.manifest.lock().unwrap() = manifest;
|
||||
// no need to run the watcher logic, the manifest was modified
|
||||
// and it will trigger the watcher again
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(first_changed_path) = paths.first() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"File {} changed. Rebuilding application...",
|
||||
display_path(
|
||||
first_changed_path
|
||||
.strip_prefix(dirs.frontend)
|
||||
.unwrap_or(first_changed_path)
|
||||
)
|
||||
);
|
||||
|
||||
child.kill().context("failed to kill app process")?;
|
||||
// wait for the process to exit
|
||||
// note that on mobile, kill() already waits for the process to exit (duct implementation)
|
||||
let _ = child.wait();
|
||||
child = run(self, config)?;
|
||||
}
|
||||
bail!("File watcher exited unexpectedly")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,7 +662,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 +749,7 @@ pub struct RustAppSettings {
|
||||
cargo_config: CargoConfig,
|
||||
target_triple: String,
|
||||
target_platform: TargetPlatform,
|
||||
workspace_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -846,6 +828,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 +859,7 @@ impl AppSettings for RustAppSettings {
|
||||
self,
|
||||
features,
|
||||
config,
|
||||
tauri_dir,
|
||||
config.bundle.clone(),
|
||||
updater_settings,
|
||||
arch64bits,
|
||||
@@ -920,8 +904,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 +913,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 +931,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 +941,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 +960,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 +1051,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 +1067,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 +1163,7 @@ impl RustAppSettings {
|
||||
cargo_config,
|
||||
target_triple,
|
||||
target_platform,
|
||||
workspace_dir,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1185,8 +1174,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 +1183,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 +1222,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 +1291,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 +1321,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 +1349,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 +1381,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 +1403,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 +1494,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 +1633,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());
|
||||
}
|
||||
@@ -1594,6 +1657,7 @@ fn tauri_config_to_bundle_settings(
|
||||
webview_install_mode: config.windows.webview_install_mode,
|
||||
allow_downgrades: config.windows.allow_downgrades,
|
||||
sign_command: config.windows.sign_command.map(custom_sign_settings),
|
||||
minimum_webview2_version: config.windows.minimum_webview2_version,
|
||||
},
|
||||
license: config.license.or_else(|| {
|
||||
settings
|
||||
@@ -1612,7 +1676,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 +1813,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 +1830,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 +1853,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 +1882,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")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(","));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}'"))?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
@@ -47,8 +48,8 @@ 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>>,
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
|
||||
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,38 +153,24 @@ 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 build_options.args, &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(),
|
||||
&CliOptions {
|
||||
dev: false,
|
||||
features: build_options.features.clone(),
|
||||
args: build_options.args.clone(),
|
||||
noise_level,
|
||||
vars: Default::default(),
|
||||
config: build_options.config.clone(),
|
||||
target_device: options.target_device.clone(),
|
||||
},
|
||||
);
|
||||
(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,
|
||||
&CliOptions {
|
||||
dev: false,
|
||||
features: build_options.features.clone(),
|
||||
args: build_options.args.clone(),
|
||||
noise_level,
|
||||
vars: Default::default(),
|
||||
config: build_options.config.clone(),
|
||||
target_device: None,
|
||||
},
|
||||
);
|
||||
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
@@ -172,11 +178,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,
|
||||
@@ -186,13 +191,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();
|
||||
@@ -222,6 +223,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
|
||||
&config,
|
||||
&mut env,
|
||||
noise_level,
|
||||
dirs.tauri,
|
||||
)?;
|
||||
|
||||
if open {
|
||||
@@ -240,16 +242,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 {
|
||||
@@ -260,7 +263,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 {
|
||||
@@ -272,11 +275,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,
|
||||
@@ -290,7 +293,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,
|
||||
|
||||
@@ -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,
|
||||
@@ -44,8 +44,8 @@ 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>>,
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
|
||||
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)?;
|
||||
@@ -181,36 +182,27 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
.map(|d| d.target().triple.to_string())
|
||||
.unwrap_or_else(|| Target::all().values().next().unwrap().triple.into());
|
||||
dev_options.target = Some(target_triple);
|
||||
dev_options.args.push("--lib".into());
|
||||
|
||||
let (interface, 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(),
|
||||
&CliOptions {
|
||||
dev: true,
|
||||
features: dev_options.features.clone(),
|
||||
args: dev_options.args.clone(),
|
||||
noise_level,
|
||||
vars: Default::default(),
|
||||
config: dev_options.config.clone(),
|
||||
target_device: None,
|
||||
},
|
||||
);
|
||||
|
||||
let app = get_app(MobileTarget::Android, tauri_config_, &interface);
|
||||
let (config, metadata) = get_config(
|
||||
&app,
|
||||
tauri_config_,
|
||||
dev_options.features.as_ref(),
|
||||
&CliOptions {
|
||||
dev: true,
|
||||
features: dev_options.features.clone(),
|
||||
args: dev_options.args.clone(),
|
||||
noise_level,
|
||||
vars: Default::default(),
|
||||
config: dev_options.config.clone(),
|
||||
target_device: device.as_ref().map(|d| TargetDevice {
|
||||
id: d.serial_no().to_string(),
|
||||
name: d.name().to_string(),
|
||||
}),
|
||||
},
|
||||
);
|
||||
(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,
|
||||
@@ -229,6 +221,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
&config,
|
||||
&metadata,
|
||||
noise_level,
|
||||
&dirs,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -237,12 +230,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()
|
||||
@@ -250,25 +244,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,
|
||||
@@ -277,12 +268,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();
|
||||
@@ -318,6 +309,7 @@ fn run_dev(
|
||||
|
||||
let open = options.open;
|
||||
interface.mobile_dev(
|
||||
&mut tauri_config,
|
||||
MobileOptions {
|
||||
debug: !options.release_mode,
|
||||
features: options.features,
|
||||
@@ -326,7 +318,7 @@ fn run_dev(
|
||||
no_watch: options.no_watch,
|
||||
additional_watch_folders: options.additional_watch_folders,
|
||||
},
|
||||
|options| {
|
||||
|options, tauri_config| {
|
||||
let cli_options = CliOptions {
|
||||
dev: true,
|
||||
features: options.features.clone(),
|
||||
@@ -340,9 +332,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)
|
||||
@@ -358,6 +350,7 @@ fn run_dev(
|
||||
open_and_wait(config, &env)
|
||||
}
|
||||
},
|
||||
dirs,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -28,8 +29,8 @@ pub struct Options {
|
||||
#[clap(short, long)]
|
||||
pub release: bool,
|
||||
/// List of cargo features to activate
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
|
||||
pub features: Option<Vec<String>>,
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
|
||||
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,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -59,8 +59,8 @@ 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>>,
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
|
||||
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,34 +192,29 @@ 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 build_options.args, &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,
|
||||
&CliOptions {
|
||||
dev: false,
|
||||
features: build_options.features.clone(),
|
||||
args: build_options.args.clone(),
|
||||
noise_level,
|
||||
vars: Default::default(),
|
||||
config: build_options.config.clone(),
|
||||
target_device: None,
|
||||
},
|
||||
dirs.tauri,
|
||||
)?;
|
||||
|
||||
let app = get_app(MobileTarget::Ios, tauri_config_, &interface);
|
||||
let (config, _metadata) = get_config(
|
||||
&app,
|
||||
tauri_config_,
|
||||
build_options.features.as_ref(),
|
||||
&CliOptions {
|
||||
dev: false,
|
||||
features: build_options.features.clone(),
|
||||
args: build_options.args.clone(),
|
||||
noise_level,
|
||||
vars: Default::default(),
|
||||
config: build_options.config.clone(),
|
||||
target_device: options.target_device.clone(),
|
||||
},
|
||||
)?;
|
||||
(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,
|
||||
@@ -225,7 +223,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(
|
||||
@@ -239,21 +237,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)?;
|
||||
@@ -346,6 +336,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplica
|
||||
&mut config,
|
||||
&mut env,
|
||||
noise_level,
|
||||
dirs,
|
||||
)?;
|
||||
|
||||
if open {
|
||||
@@ -364,10 +355,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
|
||||
@@ -375,15 +367,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 {
|
||||
@@ -395,7 +390,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);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user