mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-11 10:43:31 +02:00
Compare commits
1 Commits
@tauri-app
...
fix/wait-w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1b6e9314e |
5
.changes/bundler-appimage-arch-env.md
Normal file
5
.changes/bundler-appimage-arch-env.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
tauri-bundler: "patch:bug"
|
||||
---
|
||||
|
||||
The bundler now sets the `ARCH` env var to the current build target to prevent potential issues with `appimagetool`'s auto-detection.
|
||||
5
.changes/change-pr-13288.md
Normal file
5
.changes/change-pr-13288.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": 'patch:bug'
|
||||
---
|
||||
|
||||
Prevent the JavaScript runtime crashing when channel events fire in a webview that no longer has callbacks for the channel.
|
||||
@@ -27,6 +27,12 @@
|
||||
"dryRunCommand": true,
|
||||
"pipe": true
|
||||
},
|
||||
{
|
||||
"command": "cargo generate-lockfile",
|
||||
"dryRunCommand": true,
|
||||
"runFromRoot": true,
|
||||
"pipe": true
|
||||
},
|
||||
{
|
||||
"command": "cargo audit ${ process.env.CARGO_AUDIT_OPTIONS || '' }",
|
||||
"dryRunCommand": true,
|
||||
|
||||
7
.changes/feat-webview-auto-resize.md
Normal file
7
.changes/feat-webview-auto-resize.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"tauri": "minor:feat"
|
||||
"@tauri-apps/api": "minor:feat"
|
||||
---
|
||||
|
||||
Expose the `setAutoResize` API for webviews in `@tauri-apps/api`.
|
||||
|
||||
5
.changes/fix-custom-signer-uninstaller.md
Normal file
5
.changes/fix-custom-signer-uninstaller.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-bundler": patch:bug
|
||||
---
|
||||
|
||||
Fix custom Windows sign command failing to sign app uninstaller if it references relative paths.
|
||||
6
.changes/fix-frontenddir-target-error.md
Normal file
6
.changes/fix-frontenddir-target-error.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-cli": "patch:bug"
|
||||
"@tauri-apps/cli": "patch:bug"
|
||||
---
|
||||
|
||||
fix: allow the target directory to be inside frontendDir as long as it is not the Rust target directory inside frontendDir.
|
||||
5
.changes/fix-ios-register-listener.md
Normal file
5
.changes/fix-ios-register-listener.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch:bug
|
||||
---
|
||||
|
||||
Fixes multiple event listeners registration for iOS plugins.
|
||||
6
.changes/fix-path-join-error.md
Normal file
6
.changes/fix-path-join-error.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri": "minor:bug"
|
||||
"@tauri-apps/api": "minor:bug"
|
||||
---
|
||||
|
||||
Fixed path joining behavior where `path.join('', 'a')` incorrectly returns "/a" instead of "a".
|
||||
5
.changes/fix-prevent-overflow-monitor-check.md
Normal file
5
.changes/fix-prevent-overflow-monitor-check.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-runtime-wry": patch:bug
|
||||
---
|
||||
|
||||
Fix monitor check on the window prevent overflow implementation.
|
||||
5
.changes/fix-tray-get-by-id.md
Normal file
5
.changes/fix-tray-get-by-id.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch:bug
|
||||
---
|
||||
|
||||
Fix `TrayIcon.getById` returning a new resource ID instead of reusing a previously created id from `TrayIcon.new`.
|
||||
5
.changes/fix-webview-proxy-url.md
Normal file
5
.changes/fix-webview-proxy-url.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri': 'patch:bug'
|
||||
---
|
||||
|
||||
Fix JavaScript API `Webview.proxyUrl` had no effect when used in the `Webview` constructor
|
||||
5
.changes/monitor-workarea-js.md
Normal file
5
.changes/monitor-workarea-js.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tauri-apps/api": "minor:feat"
|
||||
---
|
||||
|
||||
Add `Monitor.workArea` field.
|
||||
6
.changes/monitor-workarea-rust.md
Normal file
6
.changes/monitor-workarea-rust.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri": "minor:feat"
|
||||
---
|
||||
|
||||
Add `Monitor::work_area` getter
|
||||
|
||||
6
.changes/physical-logical-rect.md
Normal file
6
.changes/physical-logical-rect.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri": "minor:feat"
|
||||
---
|
||||
|
||||
Added `tauri::PhysicalRect` and `tauri::LogicalRect` types.
|
||||
|
||||
5
.changes/service-worker-allowed-http-header.md
Normal file
5
.changes/service-worker-allowed-http-header.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-utils": 'minor:feat'
|
||||
"tauri": 'minor:feat'
|
||||
---
|
||||
Adds the option to configure the HTTP `Service-Worker-Allowed` response header in `app > security > headers`
|
||||
6
.changes/transform-callback.md
Normal file
6
.changes/transform-callback.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tauri-apps/api": minor:changes
|
||||
"tauri": minor:changes
|
||||
---
|
||||
|
||||
`transformCallback` now registers the callbacks inside `window.__TAURI_INTERNALS__.callbacks` instead of directly on `window['_{id}']`
|
||||
6
.changes/unlisten-race-condition.md
Normal file
6
.changes/unlisten-race-condition.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tauri-apps/api": minor:bug
|
||||
"tauri": minor:bug
|
||||
---
|
||||
|
||||
Immediately unregister event listener when the unlisten function is called.
|
||||
38
.github/CONTRIBUTING.md
vendored
38
.github/CONTRIBUTING.md
vendored
@@ -33,9 +33,11 @@ Hi! We, the maintainers, are really excited that you are interested in contribut
|
||||
- It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging.
|
||||
|
||||
- If adding new feature:
|
||||
|
||||
- Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it greenlighted before working on it.
|
||||
|
||||
- If fixing a bug:
|
||||
|
||||
- If you are resolving a special issue, add `(fix: #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`.
|
||||
- Provide detailed description of the bug in the PR, or link to an issue that does.
|
||||
|
||||
@@ -49,10 +51,7 @@ Hi! We, the maintainers, are really excited that you are interested in contribut
|
||||
|
||||
First, [join our Discord server](https://discord.gg/SpmNs4S) and let us know that you want to contribute. This way we can point you in the right direction and help ensure your contribution will be as helpful as possible.
|
||||
|
||||
To set up your machine for development, follow the [Tauri setup guide](https://v2.tauri.app/start/prerequisites/) to get all the tools you need to develop Tauri apps. The only additional tool you may need is [PNPM](https://pnpm.io/), it is only required if you are developing the Node CLI or API packages (`packages/cli` and `packages/api`).
|
||||
|
||||
Next, [fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) and clone [this repository](https://github.com/tauri-apps).
|
||||
The development process varies depending on what part of Tauri you are contributing to, see the guides below for per-package instructions.
|
||||
To set up your machine for development, follow the [Tauri setup guide](https://v2.tauri.app/start/prerequisites/) to get all the tools you need to develop Tauri apps. The only additional tool you may need is [PNPM](https://pnpm.io/), it is only required if you are developing the Node CLI or API packages (`packages/cli` and `packages/api`). Next, fork and clone this repo. It is structured as a monorepo, which means that all the various Tauri packages are under the same repository. The development process varies depending on what part of Tauri you are contributing to, see the guides below for per-package instructions.
|
||||
|
||||
Some Tauri packages will be automatically built when running one of the examples. Others, however, will need to be built beforehand. To initialize, execute these commands in the repository root:
|
||||
|
||||
@@ -65,44 +64,29 @@ pnpm build
|
||||
|
||||
See [Architecture](../ARCHITECTURE.md#major-components) for an overview of the packages in this repository.
|
||||
|
||||
### Developing Tauri Core and Related Components (Rust API, Macros, Codegen, and Utils)
|
||||
|
||||
The code for the Rust crates, including the Core, Macros, Utils, WRY runtime, and a few more are located in the [main Tauri repository](https://github.com/tauri-apps/tauri/tree/dev/crates).
|
||||
|
||||
The easiest way to test your changes is to use the [helloworld](https://github.com/tauri-apps/tauri/tree/dev/examples/helloworld) example app. It automatically rebuilds and uses your local copy of the Tauri core packages. Just run `cargo run --example helloworld` after making changes to test them out.
|
||||
|
||||
To test local changes against your own application simply point the Tauri create to your local repository. In `src-tauri/Cargo.toml` file change:
|
||||
|
||||
`tauri = { version = "2.1.1" }`
|
||||
|
||||
to:
|
||||
|
||||
`tauri = { path = "path/to/local/tauri/crates/tauri" }`
|
||||
|
||||
If any other crates depend on Tauri you will have to point them to the local repo as well.
|
||||
|
||||
### Developing Tauri Bundler and Rust CLI
|
||||
|
||||
The code for the bundler is located in [crates/tauri-bundler](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-bundler), and the code for the Rust CLI is located in [tauri-cli](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-cli).
|
||||
Running `cargo install --path .` in the Rust CLI directory will allow you to run `cargo tauri build` and `cargo tauri dev` anywhere, using the updated copy of the bundler and cli. You will have to run this command each time you make a change in either package.
|
||||
You can use `cargo install --path . --debug` to speed up test builds.
|
||||
The code for the bundler is located in `[Tauri repo root]/crates/tauri-bundler`, and the code for the Rust CLI is located in `[Tauri repo root]/crates/tauri-cli`. If you are using your local copy of `@tauri-apps/cli` (see above), any changes you make to the bundler and CLI will be automatically built and applied when running the build or dev command. Otherwise, running `cargo install --path .` in the Rust CLI directory will allow you to run `cargo tauri build` and `cargo tauri dev` anywhere, using the updated copy of the bundler and cli. You will have to run this command each time you make a change in either package.
|
||||
|
||||
### Developing The Node.js CLI (`@tauri-apps/cli`)
|
||||
|
||||
[`@tauri-apps/cli`](https://github.com/tauri-apps/tauri/tree/dev/packages/cli) is a small wrapper around `tauri-cli` so most changes should be happen in the Rust CLI (see above).
|
||||
`@tauri-apps/cli` is a wrapper to `tauri-cli` so most changes should be written on the Rust CLI. The `[Tauri repo root]/crates/tauri-cli` folder contains only packaging scripts to properly publish the Rust CLI binaries to NPM.
|
||||
|
||||
### Developing Tauri Core and Related Components (Rust API, Macros, Codegen, and Utils)
|
||||
|
||||
The code for the Rust crates, including the Core, Macros, Utils, WRY runtime, and a few more are located in `[Tauri repo root]/crates/tauri-(macros/utils)`. The easiest way to test your changes is to use the `[Tauri repo root]/examples/helloworld` app. It automatically rebuilds and uses your local copy of the Tauri core packages. Just run `cargo run --example helloworld` after making changes to test them out.
|
||||
|
||||
#### Building the documentation locally
|
||||
|
||||
You can build the Rust documentation locally running the following script:
|
||||
|
||||
```bash
|
||||
$ cargo +nightly doc --all-features --open
|
||||
$ RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --open
|
||||
```
|
||||
|
||||
### Developing the JS API
|
||||
|
||||
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.
|
||||
The JS API provides bindings between the developer's JS in the Webview and the builtin Tauri APIs, written in Rust. Its code is located in `[Tauri repo root]/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 `[Tauri repo root]/examples/api`. It will automatically use your local copy of the JS API and provides a helpful UI to test the various commands.
|
||||
|
||||
## Financial Contribution
|
||||
|
||||
|
||||
5
.github/RELEASING.md
vendored
5
.github/RELEASING.md
vendored
@@ -33,6 +33,11 @@ Releasing can be as easy as merging the version pull request but here is a check
|
||||
|
||||
- [ ] Double check that every package is bumped correctly and there are no accidental major or minor being released unless that is indeed the intention.
|
||||
- [ ] Make sure that there are no pending or unfinished [covector-version-or-publish.yml](./workflows/covector-version-or-publish.yml) workflow runs.
|
||||
- [ ] Sign the Version PR before merging as we require signed commits
|
||||
- [ ] `git fetch --all`
|
||||
- [ ] `git checkout release/version-updates`
|
||||
- [ ] `git commit --amend -S`
|
||||
- [ ] `git push --force`
|
||||
- [ ] Approve and merge the version pull request
|
||||
|
||||
## Publishing failed, what to do?
|
||||
|
||||
2
.github/workflows/check-generated-files.yml
vendored
2
.github/workflows/check-generated-files.yml
vendored
@@ -8,7 +8,6 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/check-generated-files.yml'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'packages/api/src/**'
|
||||
- 'crates/tauri/scripts/bundle.global.js'
|
||||
- 'crates/tauri-utils/src/config.rs'
|
||||
@@ -32,7 +31,6 @@ jobs:
|
||||
with:
|
||||
filters: |
|
||||
api:
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'packages/api/src/**'
|
||||
- 'crates/tauri/scripts/bundle.global.js'
|
||||
schema:
|
||||
|
||||
@@ -78,6 +78,7 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: cargo login
|
||||
run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
|
||||
@@ -118,7 +119,6 @@ jobs:
|
||||
commit-message: 'apply version updates'
|
||||
labels: 'version updates'
|
||||
body: ${{ steps.covector.outputs.change }}
|
||||
sign-commits: true
|
||||
|
||||
- name: Trigger doc update
|
||||
if: |
|
||||
|
||||
8
.github/workflows/test-core.yml
vendored
8
.github/workflows/test-core.yml
vendored
@@ -90,15 +90,15 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
prefix-key: v3
|
||||
prefix-key: v2
|
||||
save-if: ${{ matrix.features.key == 'all' }}
|
||||
|
||||
- name: test
|
||||
if: ${{ !matrix.platform.cross }}
|
||||
run: cargo ${{ matrix.features.key == 'no-default' && 'check' || matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --manifest-path crates/tauri/Cargo.toml
|
||||
run: cargo ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --manifest-path crates/tauri/Cargo.toml
|
||||
|
||||
- name: test (using cross)
|
||||
if: ${{ matrix.platform.cross }}
|
||||
run: |
|
||||
cargo install cross --git https://github.com/cross-rs/cross --rev 51f46f296253d8122c927c5bb933e3c4f27cc317 --locked
|
||||
cross ${{ matrix.features.key == 'no-default' && 'check' || matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --manifest-path crates/tauri/Cargo.toml
|
||||
cargo install cross --git https://github.com/cross-rs/cross --rev ac4c11cedc97cd7c27faed36e55377a90e6ed618 --locked
|
||||
cross ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --manifest-path crates/tauri/Cargo.toml
|
||||
|
||||
109
.gitignore
vendored
109
.gitignore
vendored
@@ -1,55 +1,54 @@
|
||||
# dependency directories
|
||||
node_modules/
|
||||
|
||||
# Optional npm and yarn cache directory
|
||||
.npm/
|
||||
.yarn/
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# .vscode workspace settings file
|
||||
.vscode/settings.json
|
||||
.vscode/launch.json
|
||||
.vscode/tasks.json
|
||||
|
||||
# npm, yarn and bun lock files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
bun.lockb
|
||||
|
||||
# rust compiled folders
|
||||
target/
|
||||
|
||||
# test video for streaming example
|
||||
streaming_example_test_video.mp4
|
||||
|
||||
# examples /gen directory
|
||||
/examples/**/src-tauri/gen/
|
||||
/bench/**/src-tauri/gen/
|
||||
|
||||
# logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# miscellaneous
|
||||
/.vs
|
||||
.DS_Store
|
||||
.Thumbs.db
|
||||
*.sublime*
|
||||
.idea
|
||||
debug.log
|
||||
TODO.md
|
||||
.aider*
|
||||
# dependency directories
|
||||
node_modules/
|
||||
|
||||
# Optional npm and yarn cache directory
|
||||
.npm/
|
||||
.yarn/
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# .vscode workspace settings file
|
||||
.vscode/settings.json
|
||||
.vscode/launch.json
|
||||
.vscode/tasks.json
|
||||
|
||||
# npm, yarn and bun lock files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
bun.lockb
|
||||
|
||||
# rust compiled folders
|
||||
target/
|
||||
|
||||
# test video for streaming example
|
||||
streaming_example_test_video.mp4
|
||||
|
||||
# examples /gen directory
|
||||
/examples/**/src-tauri/gen/
|
||||
/bench/**/src-tauri/gen/
|
||||
|
||||
# logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# miscellaneous
|
||||
/.vs
|
||||
.DS_Store
|
||||
.Thumbs.db
|
||||
*.sublime*
|
||||
.idea
|
||||
debug.log
|
||||
TODO.md
|
||||
|
||||
@@ -57,7 +57,7 @@ function checkChangeFiles(changeFiles) {
|
||||
for (const [file, packages] of unknownTagsEntries) {
|
||||
for (const { package, tag } of packages) {
|
||||
console.error(
|
||||
`Package \`${package}\` has an unknown change tag ${tag} in ${file} `
|
||||
`Package \`${package}\` has an uknown change tag ${tag} in ${file} `
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ const ignore = [
|
||||
async function checkFile(file) {
|
||||
if (
|
||||
extensions.some((e) => file.endsWith(e))
|
||||
&& !ignore.some((i) => file.includes(`/${i}/`) || path.basename(file) === i)
|
||||
&& !ignore.some((i) => file.includes(`/${i}/`) || path.basename(file) == i)
|
||||
) {
|
||||
const fileStream = fs.createReadStream(file)
|
||||
const rl = readline.createInterface({
|
||||
|
||||
1152
Cargo.lock
generated
1152
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -71,4 +71,3 @@ opt-level = "s"
|
||||
schemars_derive = { git = 'https://github.com/tauri-apps/schemars.git', branch = 'feat/preserve-description-newlines' }
|
||||
tauri = { path = "./crates/tauri" }
|
||||
tauri-plugin = { path = "./crates/tauri-plugin" }
|
||||
tauri-utils = { path = "./crates/tauri-utils" }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[](https://opencollective.com/tauri)
|
||||
[](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)
|
||||
[](https://discord.com/invite/tauri)
|
||||
[](https://discord.gg/SpmNs4S)
|
||||
[](https://tauri.app)
|
||||
[](https://good-labs.github.io/greater-good-affirmation)
|
||||
[](https://opencollective.com/tauri)
|
||||
@@ -81,7 +81,7 @@ For the complete list of sponsors please visit our [website](https://tauri.app#s
|
||||
|
||||
## Organization
|
||||
|
||||
Tauri aims to be a sustainable collective based on principles that guide sustainable free and open software communities. To this end it has become a Programme within the [Commons Conservancy](https://commonsconservancy.org/), and you can contribute financially via [Open Collective](https://opencollective.com/tauri).
|
||||
Tauri aims to be a sustainable collective based on principles that guide [sustainable free and open software communities](https://sfosc.org). To this end it has become a Programme within the [Commons Conservancy](https://commonsconservancy.org/), and you can contribute financially via [Open Collective](https://opencollective.com/tauri).
|
||||
|
||||
## Licenses
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
|
||||
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
|
||||
)]
|
||||
// file is used by multiple binaries
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::{fs::File, io::BufReader};
|
||||
mod utils;
|
||||
@@ -54,7 +52,7 @@ fn main() {
|
||||
.expect("Something wrong with tauri_data"),
|
||||
&serde_json::to_value(all_data).expect("Unable to build final json (all)"),
|
||||
)
|
||||
.unwrap_or_else(|_| panic!("Unable to write {tauri_data:?}"));
|
||||
.unwrap_or_else(|_| panic!("Unable to write {:?}", tauri_data));
|
||||
|
||||
utils::write_json(
|
||||
tauri_recent
|
||||
@@ -62,5 +60,5 @@ fn main() {
|
||||
.expect("Something wrong with tauri_recent"),
|
||||
&serde_json::to_value(recent).expect("Unable to build final json (recent)"),
|
||||
)
|
||||
.unwrap_or_else(|_| panic!("Unable to write {tauri_recent:?}"));
|
||||
.unwrap_or_else(|_| panic!("Unable to write {:?}", tauri_recent));
|
||||
}
|
||||
|
||||
@@ -2,17 +2,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! This Rust binary runs on CI and provides internal metrics results of Tauri.
|
||||
//! To learn more see [benchmark_results](https://github.com/tauri-apps/benchmark_results) repository.
|
||||
//! This Rust binary runs on CI and provides internal metrics results of Tauri. To learn more see [benchmark_results](https://github.com/tauri-apps/benchmark_results) repository.
|
||||
//!
|
||||
//! ***_Internal use only_***
|
||||
//! ***_Internal use only_**
|
||||
|
||||
#![doc(
|
||||
html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
|
||||
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
|
||||
)]
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
env,
|
||||
@@ -22,66 +21,62 @@ use std::{
|
||||
|
||||
mod utils;
|
||||
|
||||
/// The list of examples for benchmarks
|
||||
fn get_all_benchmarks(target: &str) -> Vec<(String, String)> {
|
||||
/// The list of the examples of the benchmark name and binary relative path
|
||||
fn get_all_benchmarks() -> Vec<(String, String)> {
|
||||
vec![
|
||||
(
|
||||
"tauri_hello_world".into(),
|
||||
format!("../target/{target}/release/bench_helloworld"),
|
||||
format!("../target/{}/release/bench_helloworld", utils::get_target()),
|
||||
),
|
||||
(
|
||||
"tauri_cpu_intensive".into(),
|
||||
format!("../target/{target}/release/bench_cpu_intensive"),
|
||||
format!(
|
||||
"../target/{}/release/bench_cpu_intensive",
|
||||
utils::get_target()
|
||||
),
|
||||
),
|
||||
(
|
||||
"tauri_3mb_transfer".into(),
|
||||
format!("../target/{target}/release/bench_files_transfer"),
|
||||
format!(
|
||||
"../target/{}/release/bench_files_transfer",
|
||||
utils::get_target()
|
||||
),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn run_strace_benchmarks(new_data: &mut utils::BenchResult, target: &str) -> Result<()> {
|
||||
fn run_strace_benchmarks(new_data: &mut utils::BenchResult) -> Result<()> {
|
||||
use std::io::Read;
|
||||
|
||||
let mut thread_count = HashMap::<String, u64>::new();
|
||||
let mut syscall_count = HashMap::<String, u64>::new();
|
||||
|
||||
for (name, example_exe) in get_all_benchmarks(target) {
|
||||
let mut file = tempfile::NamedTempFile::new()
|
||||
.context("failed to create temporary file for strace output")?;
|
||||
|
||||
let exe_path = utils::bench_root_path().join(&example_exe);
|
||||
let exe_path_str = exe_path
|
||||
.to_str()
|
||||
.context("executable path contains invalid UTF-8")?;
|
||||
let temp_path_str = file
|
||||
.path()
|
||||
.to_str()
|
||||
.context("temporary file path contains invalid UTF-8")?;
|
||||
for (name, example_exe) in get_all_benchmarks() {
|
||||
let mut file = tempfile::NamedTempFile::new()?;
|
||||
|
||||
Command::new("strace")
|
||||
.args(["-c", "-f", "-o", temp_path_str, exe_path_str])
|
||||
.args([
|
||||
"-c",
|
||||
"-f",
|
||||
"-o",
|
||||
file.path().to_str().unwrap(),
|
||||
utils::bench_root_path().join(example_exe).to_str().unwrap(),
|
||||
])
|
||||
.stdout(Stdio::inherit())
|
||||
.spawn()
|
||||
.context("failed to spawn strace process")?
|
||||
.wait()
|
||||
.context("failed to wait for strace process")?;
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
|
||||
let mut output = String::new();
|
||||
file
|
||||
.as_file_mut()
|
||||
.read_to_string(&mut output)
|
||||
.context("failed to read strace output")?;
|
||||
file.as_file_mut().read_to_string(&mut output)?;
|
||||
|
||||
let strace_result = utils::parse_strace_output(&output);
|
||||
// Count clone/clone3 syscalls as thread creation indicators
|
||||
let clone_calls = strace_result.get("clone").map(|d| d.calls).unwrap_or(0)
|
||||
// Note, we always have 1 thread. Use cloneX calls as counter for additional threads created.
|
||||
let clone = 1
|
||||
+ strace_result.get("clone").map(|d| d.calls).unwrap_or(0)
|
||||
+ strace_result.get("clone3").map(|d| d.calls).unwrap_or(0);
|
||||
|
||||
if let Some(total) = strace_result.get("total") {
|
||||
thread_count.insert(name.clone(), clone_calls);
|
||||
syscall_count.insert(name, total.calls);
|
||||
}
|
||||
let total = strace_result.get("total").unwrap().calls;
|
||||
thread_count.insert(name.to_string(), clone);
|
||||
syscall_count.insert(name.to_string(), total);
|
||||
}
|
||||
|
||||
new_data.thread_count = thread_count;
|
||||
@@ -90,100 +85,70 @@ fn run_strace_benchmarks(new_data: &mut utils::BenchResult, target: &str) -> Res
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_max_mem_benchmark(target: &str) -> Result<HashMap<String, u64>> {
|
||||
fn run_max_mem_benchmark() -> Result<HashMap<String, u64>> {
|
||||
let mut results = HashMap::<String, u64>::new();
|
||||
|
||||
for (name, example_exe) in get_all_benchmarks(target) {
|
||||
let benchmark_file = utils::target_dir().join(format!("mprof{name}_.dat"));
|
||||
let benchmark_file_str = benchmark_file
|
||||
.to_str()
|
||||
.context("benchmark file path contains invalid UTF-8")?;
|
||||
|
||||
let exe_path = utils::bench_root_path().join(&example_exe);
|
||||
let exe_path_str = exe_path
|
||||
.to_str()
|
||||
.context("executable path contains invalid UTF-8")?;
|
||||
for (name, example_exe) in get_all_benchmarks() {
|
||||
let benchmark_file = utils::target_dir().join(format!("mprof{}_.dat", name));
|
||||
let benchmark_file = benchmark_file.to_str().unwrap();
|
||||
|
||||
let proc = Command::new("mprof")
|
||||
.args(["run", "-C", "-o", benchmark_file_str, exe_path_str])
|
||||
.args([
|
||||
"run",
|
||||
"-C",
|
||||
"-o",
|
||||
benchmark_file,
|
||||
utils::bench_root_path().join(example_exe).to_str().unwrap(),
|
||||
])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.with_context(|| format!("failed to spawn mprof for benchmark {name}"))?;
|
||||
.spawn()?;
|
||||
|
||||
let proc_result = proc
|
||||
.wait_with_output()
|
||||
.with_context(|| format!("failed to wait for mprof {name}"))?;
|
||||
|
||||
if !proc_result.status.success() {
|
||||
eprintln!(
|
||||
"mprof failed for {name}: {}",
|
||||
String::from_utf8_lossy(&proc_result.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(mem) = utils::parse_max_mem(benchmark_file_str)
|
||||
.with_context(|| format!("failed to parse mprof data for {name}"))?
|
||||
{
|
||||
results.insert(name, mem);
|
||||
}
|
||||
|
||||
// Clean up the temporary file
|
||||
if let Err(e) = std::fs::remove_file(&benchmark_file) {
|
||||
eprintln!("Warning: failed to remove temporary file {benchmark_file_str}: {e}");
|
||||
}
|
||||
let proc_result = proc.wait_with_output()?;
|
||||
println!("{:?}", proc_result);
|
||||
results.insert(
|
||||
name.to_string(),
|
||||
utils::parse_max_mem(benchmark_file).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn rlib_size(target_dir: &Path, prefix: &str) -> Result<u64> {
|
||||
fn rlib_size(target_dir: &std::path::Path, prefix: &str) -> u64 {
|
||||
let mut size = 0;
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
let deps_dir = target_dir.join("deps");
|
||||
for entry in std::fs::read_dir(&deps_dir).with_context(|| {
|
||||
format!(
|
||||
"failed to read target deps directory: {}",
|
||||
deps_dir.display()
|
||||
)
|
||||
})? {
|
||||
let entry = entry.context("failed to read directory entry")?;
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
|
||||
for entry in std::fs::read_dir(target_dir.join("deps")).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let os_str = entry.file_name();
|
||||
let name = os_str.to_str().unwrap();
|
||||
if name.starts_with(prefix) && name.ends_with(".rlib") {
|
||||
if let Some(start) = name.split('-').next() {
|
||||
if seen.insert(start.to_string()) {
|
||||
size += entry
|
||||
.metadata()
|
||||
.context("failed to read file metadata")?
|
||||
.len();
|
||||
}
|
||||
let start = name.split('-').next().unwrap().to_string();
|
||||
if seen.contains(&start) {
|
||||
println!("skip {}", name);
|
||||
} else {
|
||||
seen.insert(start);
|
||||
size += entry.metadata().unwrap().len();
|
||||
println!("check size {} {}", name, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
anyhow::bail!(
|
||||
"no rlib files found for prefix {prefix} in {}",
|
||||
deps_dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
assert!(size > 0);
|
||||
size
|
||||
}
|
||||
|
||||
fn get_binary_sizes(target_dir: &Path, target: &str) -> Result<HashMap<String, u64>> {
|
||||
fn get_binary_sizes(target_dir: &Path) -> Result<HashMap<String, u64>> {
|
||||
let mut sizes = HashMap::<String, u64>::new();
|
||||
|
||||
let wry_size = rlib_size(target_dir, "libwry")?;
|
||||
let wry_size = rlib_size(target_dir, "libwry");
|
||||
println!("wry {} bytes", wry_size);
|
||||
sizes.insert("wry_rlib".to_string(), wry_size);
|
||||
|
||||
for (name, example_exe) in get_all_benchmarks(target) {
|
||||
let exe_path = utils::bench_root_path().join(&example_exe);
|
||||
let meta = std::fs::metadata(&exe_path)
|
||||
.with_context(|| format!("failed to read metadata for {}", exe_path.display()))?;
|
||||
sizes.insert(name, meta.len());
|
||||
// add size for all EXEC_TIME_BENCHMARKS
|
||||
for (name, example_exe) in get_all_benchmarks() {
|
||||
let meta = std::fs::metadata(example_exe).unwrap();
|
||||
sizes.insert(name.to_string(), meta.len());
|
||||
}
|
||||
|
||||
Ok(sizes)
|
||||
@@ -223,33 +188,14 @@ fn cargo_deps() -> HashMap<String, usize> {
|
||||
cmd.args(["--target", target]);
|
||||
cmd.current_dir(utils::tauri_root_path());
|
||||
|
||||
match cmd.output() {
|
||||
Ok(output) if output.status.success() => {
|
||||
let full_deps = String::from_utf8_lossy(&output.stdout);
|
||||
let count = full_deps
|
||||
.lines()
|
||||
.collect::<HashSet<_>>()
|
||||
.len()
|
||||
.saturating_sub(1); // output includes wry itself
|
||||
let full_deps = cmd.output().expect("failed to run cargo tree").stdout;
|
||||
let full_deps = String::from_utf8(full_deps).expect("cargo tree output not utf-8");
|
||||
let count = full_deps.lines().collect::<HashSet<_>>().len() - 1; // output includes wry itself
|
||||
|
||||
// set the count to the highest count seen for this OS
|
||||
let existing = results.entry(os.to_string()).or_default();
|
||||
*existing = count.max(*existing);
|
||||
|
||||
if count <= 10 {
|
||||
eprintln!("Warning: dependency count for {target} seems low: {count}");
|
||||
}
|
||||
}
|
||||
Ok(output) => {
|
||||
eprintln!(
|
||||
"cargo tree failed for {target}: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to run cargo tree for {target}: {e}");
|
||||
}
|
||||
}
|
||||
// set the count to the highest count seen for this OS
|
||||
let existing = results.entry(os.to_string()).or_default();
|
||||
*existing = count.max(*existing);
|
||||
assert!(count > 10); // sanity check
|
||||
}
|
||||
}
|
||||
results
|
||||
@@ -257,127 +203,104 @@ fn cargo_deps() -> HashMap<String, usize> {
|
||||
|
||||
const RESULT_KEYS: &[&str] = &["mean", "stddev", "user", "system", "min", "max"];
|
||||
|
||||
fn run_exec_time(target: &str) -> Result<HashMap<String, HashMap<String, f64>>> {
|
||||
let target_dir = utils::target_dir();
|
||||
fn run_exec_time(target_dir: &Path) -> Result<HashMap<String, HashMap<String, f64>>> {
|
||||
let benchmark_file = target_dir.join("hyperfine_results.json");
|
||||
let benchmark_file_str = benchmark_file
|
||||
.to_str()
|
||||
.context("benchmark file path contains invalid UTF-8")?;
|
||||
let benchmark_file = benchmark_file.to_str().unwrap();
|
||||
|
||||
let mut command = vec![
|
||||
let mut command = [
|
||||
"hyperfine",
|
||||
"--export-json",
|
||||
benchmark_file_str,
|
||||
benchmark_file,
|
||||
"--show-output",
|
||||
"--warmup",
|
||||
"3",
|
||||
];
|
||||
]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let benchmarks = get_all_benchmarks(target);
|
||||
let mut benchmark_paths = Vec::new();
|
||||
|
||||
for (_, example_exe) in &benchmarks {
|
||||
let exe_path = utils::bench_root_path().join(example_exe);
|
||||
let exe_path_str = exe_path
|
||||
.to_str()
|
||||
.context("executable path contains invalid UTF-8")?;
|
||||
benchmark_paths.push(exe_path_str.to_string());
|
||||
for (_, example_exe) in get_all_benchmarks() {
|
||||
command.push(
|
||||
utils::bench_root_path()
|
||||
.join(example_exe)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
for path in &benchmark_paths {
|
||||
command.push(path.as_str());
|
||||
}
|
||||
|
||||
utils::run(&command)?;
|
||||
utils::run(&command.iter().map(|s| s.as_ref()).collect::<Vec<_>>());
|
||||
|
||||
let mut results = HashMap::<String, HashMap<String, f64>>::new();
|
||||
let hyperfine_results = utils::read_json(benchmark_file_str)?;
|
||||
|
||||
if let Some(results_array) = hyperfine_results
|
||||
.as_object()
|
||||
.and_then(|obj| obj.get("results"))
|
||||
.and_then(|val| val.as_array())
|
||||
{
|
||||
for ((name, _), data) in benchmarks.iter().zip(results_array.iter()) {
|
||||
if let Some(data_obj) = data.as_object() {
|
||||
let filtered_data: HashMap<String, f64> = data_obj
|
||||
.iter()
|
||||
.filter(|(key, _)| RESULT_KEYS.contains(&key.as_str()))
|
||||
.filter_map(|(key, val)| val.as_f64().map(|v| (key.clone(), v)))
|
||||
.collect();
|
||||
|
||||
results.insert(name.clone(), filtered_data);
|
||||
}
|
||||
}
|
||||
let hyperfine_results = utils::read_json(benchmark_file)?;
|
||||
for ((name, _), data) in get_all_benchmarks().iter().zip(
|
||||
hyperfine_results
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("results")
|
||||
.unwrap()
|
||||
.as_array()
|
||||
.unwrap(),
|
||||
) {
|
||||
let data = data.as_object().unwrap().clone();
|
||||
results.insert(
|
||||
name.to_string(),
|
||||
data
|
||||
.into_iter()
|
||||
.filter(|(key, _)| RESULT_KEYS.contains(&key.as_str()))
|
||||
.map(|(key, val)| (key, val.as_f64().unwrap()))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// download big files if not present
|
||||
let json_3mb = utils::home_path().join(".tauri_3mb.json");
|
||||
|
||||
if !json_3mb.exists() {
|
||||
println!("Downloading test data...");
|
||||
utils::download_file(
|
||||
"https://github.com/lemarier/tauri-test/releases/download/v2.0.0/json_3mb.json",
|
||||
json_3mb,
|
||||
)
|
||||
.context("failed to download test data")?;
|
||||
);
|
||||
}
|
||||
|
||||
println!("Starting tauri benchmark");
|
||||
|
||||
let target_dir = utils::target_dir();
|
||||
let target = utils::get_target();
|
||||
|
||||
env::set_current_dir(utils::bench_root_path())
|
||||
.context("failed to set working directory to bench root")?;
|
||||
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.context("failed to get current time")?;
|
||||
let timestamp = format!("{}", now.as_secs());
|
||||
|
||||
println!("Running execution time benchmarks...");
|
||||
let exec_time = run_exec_time(target)?;
|
||||
|
||||
println!("Getting binary sizes...");
|
||||
let binary_size = get_binary_sizes(&target_dir, target)?;
|
||||
|
||||
println!("Analyzing cargo dependencies...");
|
||||
let cargo_deps = cargo_deps();
|
||||
env::set_current_dir(utils::bench_root_path())?;
|
||||
|
||||
let format =
|
||||
time::format_description::parse("[year]-[month]-[day]T[hour]:[minute]:[second]Z").unwrap();
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
let mut new_data = utils::BenchResult {
|
||||
created_at: timestamp,
|
||||
sha1: {
|
||||
let output = utils::run_collect(&["git", "rev-parse", "HEAD"])?;
|
||||
output.0.trim().to_string()
|
||||
},
|
||||
exec_time,
|
||||
binary_size,
|
||||
cargo_deps,
|
||||
created_at: now.format(&format).unwrap(),
|
||||
sha1: utils::run_collect(&["git", "rev-parse", "HEAD"])
|
||||
.0
|
||||
.trim()
|
||||
.to_string(),
|
||||
exec_time: run_exec_time(&target_dir)?,
|
||||
binary_size: get_binary_sizes(&target_dir)?,
|
||||
cargo_deps: cargo_deps(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if cfg!(target_os = "linux") {
|
||||
println!("Running Linux-specific benchmarks...");
|
||||
run_strace_benchmarks(&mut new_data, target)?;
|
||||
new_data.max_memory = run_max_mem_benchmark(target)?;
|
||||
run_strace_benchmarks(&mut new_data)?;
|
||||
new_data.max_memory = run_max_mem_benchmark()?;
|
||||
}
|
||||
|
||||
println!("===== <BENCHMARK RESULTS>");
|
||||
serde_json::to_writer_pretty(std::io::stdout(), &new_data)
|
||||
.context("failed to serialize benchmark results")?;
|
||||
serde_json::to_writer_pretty(std::io::stdout(), &new_data)?;
|
||||
println!("\n===== </BENCHMARK RESULTS>");
|
||||
|
||||
let bench_file = target_dir.join("bench.json");
|
||||
if let Some(filename) = bench_file.to_str() {
|
||||
utils::write_json(filename, &serde_json::to_value(&new_data)?)
|
||||
.context("failed to write benchmark results to file")?;
|
||||
println!("Results written to: {filename}");
|
||||
if let Some(filename) = target_dir.join("bench.json").to_str() {
|
||||
utils::write_json(filename, &serde_json::to_value(&new_data)?)?;
|
||||
} else {
|
||||
eprintln!("Cannot write bench.json, path contains invalid UTF-8");
|
||||
eprintln!("Cannot write bench.json, path is invalid");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! Utility functions for benchmarking tasks in the Tauri project.
|
||||
//!
|
||||
//! This module provides helpers for:
|
||||
//! - Paths to project directories and targets
|
||||
//! - Running and collecting process outputs
|
||||
//! - Parsing memory profiler (`mprof`) and syscall profiler (`strace`) outputs
|
||||
//! - JSON read/write utilities
|
||||
//! - File download utilities (via `curl` or file copy)
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
@@ -22,7 +13,6 @@ use std::{
|
||||
process::{Command, Output, Stdio},
|
||||
};
|
||||
|
||||
/// Holds the results of a benchmark run.
|
||||
#[derive(Default, Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct BenchResult {
|
||||
pub created_at: String,
|
||||
@@ -35,7 +25,7 @@ pub struct BenchResult {
|
||||
pub cargo_deps: HashMap<String, usize>,
|
||||
}
|
||||
|
||||
/// Represents a single line of parsed `strace` output.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct StraceOutput {
|
||||
pub percent_time: f64,
|
||||
@@ -45,7 +35,6 @@ pub struct StraceOutput {
|
||||
pub errors: u64,
|
||||
}
|
||||
|
||||
/// Get the compilation target triple for the current platform.
|
||||
pub fn get_target() -> &'static str {
|
||||
#[cfg(target_os = "macos")]
|
||||
return if cfg!(target_arch = "aarch64") {
|
||||
@@ -53,22 +42,18 @@ pub fn get_target() -> &'static str {
|
||||
} else {
|
||||
"x86_64-apple-darwin"
|
||||
};
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
return if cfg!(target_arch = "aarch64") {
|
||||
"aarch64-apple-ios"
|
||||
} else {
|
||||
"x86_64-apple-ios"
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return "x86_64-unknown-linux-gnu";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
unimplemented!("Windows target not implemented yet");
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
/// Get the `target/release` directory path for benchmarks.
|
||||
pub fn target_dir() -> PathBuf {
|
||||
bench_root_path()
|
||||
.join("..")
|
||||
@@ -77,90 +62,83 @@ pub fn target_dir() -> PathBuf {
|
||||
.join("release")
|
||||
}
|
||||
|
||||
/// Get the root path of the current benchmark crate.
|
||||
pub fn bench_root_path() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
}
|
||||
|
||||
/// Get the home directory of the current user.
|
||||
#[allow(dead_code)]
|
||||
pub fn home_path() -> PathBuf {
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "linux"))]
|
||||
{
|
||||
PathBuf::from(std::env::var("HOME").unwrap_or_default())
|
||||
}
|
||||
|
||||
return PathBuf::from(env!("HOME"));
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
PathBuf::from(std::env::var("USERPROFILE").unwrap_or_default())
|
||||
}
|
||||
return PathBuf::from(env!("HOMEPATH"));
|
||||
}
|
||||
|
||||
/// Get the root path of the Tauri repository.
|
||||
#[allow(dead_code)]
|
||||
pub fn tauri_root_path() -> PathBuf {
|
||||
bench_root_path().parent().map(|p| p.to_path_buf()).unwrap()
|
||||
bench_root_path().parent().unwrap().to_path_buf()
|
||||
}
|
||||
|
||||
/// Run a command and collect its stdout and stderr as strings.
|
||||
/// Returns an error if the command fails or exits with a non-zero status.
|
||||
pub fn run_collect(cmd: &[&str]) -> Result<(String, String)> {
|
||||
let output: Output = Command::new(cmd[0])
|
||||
#[allow(dead_code)]
|
||||
pub fn run_collect(cmd: &[&str]) -> (String, String) {
|
||||
let mut process_builder = Command::new(cmd[0]);
|
||||
process_builder
|
||||
.args(&cmd[1..])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.with_context(|| format!("failed to execute command: {cmd:?}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"Command {:?} exited with {:?}\nstdout:\n{}\nstderr:\n{}",
|
||||
cmd,
|
||||
output.status.code(),
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
.stderr(Stdio::piped());
|
||||
let prog = process_builder.spawn().expect("failed to spawn script");
|
||||
let Output {
|
||||
stdout,
|
||||
stderr,
|
||||
status,
|
||||
} = prog.wait_with_output().expect("failed to wait on child");
|
||||
let stdout = String::from_utf8_lossy(&stdout).to_string();
|
||||
let stderr = String::from_utf8_lossy(&stderr).to_string();
|
||||
if !status.success() {
|
||||
eprintln!("stdout: <<<{}>>>", stdout);
|
||||
eprintln!("stderr: <<<{}>>>", stderr);
|
||||
panic!("Unexpected exit code: {:?}", status.code());
|
||||
}
|
||||
|
||||
Ok((
|
||||
String::from_utf8_lossy(&output.stdout).to_string(),
|
||||
String::from_utf8_lossy(&output.stderr).to_string(),
|
||||
))
|
||||
(stdout, stderr)
|
||||
}
|
||||
|
||||
/// Parse a memory profiler (`mprof`) output file and return the maximum
|
||||
/// memory usage in bytes. Returns `None` if no values are found.
|
||||
pub fn parse_max_mem(file_path: &str) -> Result<Option<u64>> {
|
||||
let file = fs::File::open(file_path)
|
||||
.with_context(|| format!("failed to open mprof output file {file_path}"))?;
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_max_mem(file_path: &str) -> Option<u64> {
|
||||
let file = fs::File::open(file_path).unwrap();
|
||||
let output = BufReader::new(file);
|
||||
|
||||
let mut highest: u64 = 0;
|
||||
|
||||
// MEM 203.437500 1621617192.4123
|
||||
for line in output.lines().map_while(Result::ok) {
|
||||
let split: Vec<&str> = line.split(' ').collect();
|
||||
// split line by space
|
||||
let split = line.split(' ').collect::<Vec<_>>();
|
||||
if split.len() == 3 {
|
||||
if let Ok(mb) = split[1].parse::<f64>() {
|
||||
let current_bytes = (mb * 1024.0 * 1024.0) as u64;
|
||||
highest = highest.max(current_bytes);
|
||||
// mprof generate result in MB
|
||||
let current_bytes = str::parse::<f64>(split[1]).unwrap() as u64 * 1024 * 1024;
|
||||
if current_bytes > highest {
|
||||
highest = current_bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Best-effort cleanup
|
||||
let _ = fs::remove_file(file_path);
|
||||
fs::remove_file(file_path).unwrap();
|
||||
|
||||
Ok(if highest > 0 { Some(highest) } else { None })
|
||||
if highest > 0 {
|
||||
return Some(highest);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Parse the output of `strace -c` and return a summary of syscalls.
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_strace_output(output: &str) -> HashMap<String, StraceOutput> {
|
||||
let mut summary = HashMap::new();
|
||||
|
||||
let mut lines = output
|
||||
.lines()
|
||||
.filter(|line| !line.is_empty() && !line.contains("detached ..."));
|
||||
|
||||
let count = lines.clone().count();
|
||||
|
||||
if count < 4 {
|
||||
return summary;
|
||||
}
|
||||
@@ -170,91 +148,89 @@ pub fn parse_strace_output(output: &str) -> HashMap<String, StraceOutput> {
|
||||
let data_lines = lines.skip(2);
|
||||
|
||||
for line in data_lines {
|
||||
let syscall_fields: Vec<&str> = line.split_whitespace().collect();
|
||||
let syscall_fields = line.split_whitespace().collect::<Vec<_>>();
|
||||
let len = syscall_fields.len();
|
||||
let syscall_name = syscall_fields.last().unwrap();
|
||||
|
||||
if let Some(&syscall_name) = syscall_fields.last() {
|
||||
if (5..=6).contains(&len) {
|
||||
let output = StraceOutput {
|
||||
percent_time: syscall_fields[0].parse().unwrap_or(0.0),
|
||||
seconds: syscall_fields[1].parse().unwrap_or(0.0),
|
||||
usecs_per_call: syscall_fields[2].parse().ok(),
|
||||
calls: syscall_fields[3].parse().unwrap_or(0),
|
||||
errors: if len < 6 {
|
||||
if (5..=6).contains(&len) {
|
||||
summary.insert(
|
||||
syscall_name.to_string(),
|
||||
StraceOutput {
|
||||
percent_time: str::parse::<f64>(syscall_fields[0]).unwrap(),
|
||||
seconds: str::parse::<f64>(syscall_fields[1]).unwrap(),
|
||||
usecs_per_call: Some(str::parse::<u64>(syscall_fields[2]).unwrap()),
|
||||
calls: str::parse::<u64>(syscall_fields[3]).unwrap(),
|
||||
errors: if syscall_fields.len() < 6 {
|
||||
0
|
||||
} else {
|
||||
syscall_fields[4].parse().unwrap_or(0)
|
||||
str::parse::<u64>(syscall_fields[4]).unwrap()
|
||||
},
|
||||
};
|
||||
summary.insert(syscall_name.to_string(), output);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let total_fields: Vec<&str> = total_line.split_whitespace().collect();
|
||||
let total = match total_fields.len() {
|
||||
5 => StraceOutput {
|
||||
percent_time: total_fields[0].parse().unwrap_or(0.0),
|
||||
seconds: total_fields[1].parse().unwrap_or(0.0),
|
||||
usecs_per_call: None,
|
||||
calls: total_fields[2].parse().unwrap_or(0),
|
||||
errors: total_fields[3].parse().unwrap_or(0),
|
||||
},
|
||||
6 => StraceOutput {
|
||||
percent_time: total_fields[0].parse().unwrap_or(0.0),
|
||||
seconds: total_fields[1].parse().unwrap_or(0.0),
|
||||
usecs_per_call: total_fields[2].parse().ok(),
|
||||
calls: total_fields[3].parse().unwrap_or(0),
|
||||
errors: total_fields[4].parse().unwrap_or(0),
|
||||
},
|
||||
_ => {
|
||||
panic!("Unexpected total field count: {}", total_fields.len());
|
||||
}
|
||||
};
|
||||
let total_fields = total_line.split_whitespace().collect::<Vec<_>>();
|
||||
|
||||
summary.insert(
|
||||
"total".to_string(),
|
||||
match total_fields.len() {
|
||||
// Old format, has no usecs/call
|
||||
5 => StraceOutput {
|
||||
percent_time: str::parse::<f64>(total_fields[0]).unwrap(),
|
||||
seconds: str::parse::<f64>(total_fields[1]).unwrap(),
|
||||
usecs_per_call: None,
|
||||
calls: str::parse::<u64>(total_fields[2]).unwrap(),
|
||||
errors: str::parse::<u64>(total_fields[3]).unwrap(),
|
||||
},
|
||||
6 => StraceOutput {
|
||||
percent_time: str::parse::<f64>(total_fields[0]).unwrap(),
|
||||
seconds: str::parse::<f64>(total_fields[1]).unwrap(),
|
||||
usecs_per_call: Some(str::parse::<u64>(total_fields[2]).unwrap()),
|
||||
calls: str::parse::<u64>(total_fields[3]).unwrap(),
|
||||
errors: str::parse::<u64>(total_fields[4]).unwrap(),
|
||||
},
|
||||
_ => panic!("Unexpected total field count: {}", total_fields.len()),
|
||||
},
|
||||
);
|
||||
|
||||
summary.insert("total".to_string(), total);
|
||||
summary
|
||||
}
|
||||
|
||||
/// Run a command and wait for completion.
|
||||
/// Returns an error if the command fails.
|
||||
pub fn run(cmd: &[&str]) -> Result<()> {
|
||||
let status = Command::new(cmd[0])
|
||||
.args(&cmd[1..])
|
||||
.stdin(Stdio::piped())
|
||||
.status()
|
||||
.with_context(|| format!("failed to execute command: {cmd:?}"))?;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn run(cmd: &[&str]) {
|
||||
let mut process_builder = Command::new(cmd[0]);
|
||||
process_builder.args(&cmd[1..]).stdin(Stdio::piped());
|
||||
let mut prog = process_builder.spawn().expect("failed to spawn script");
|
||||
let status = prog.wait().expect("failed to wait on child");
|
||||
if !status.success() {
|
||||
bail!("Command {:?} exited with {:?}", cmd, status.code());
|
||||
panic!("Unexpected exit code: {:?}", status.code());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read a JSON file into a [`serde_json::Value`].
|
||||
#[allow(dead_code)]
|
||||
pub fn read_json(filename: &str) -> Result<Value> {
|
||||
let f =
|
||||
fs::File::open(filename).with_context(|| format!("failed to open JSON file {filename}"))?;
|
||||
let f = fs::File::open(filename)?;
|
||||
Ok(serde_json::from_reader(f)?)
|
||||
}
|
||||
|
||||
/// Write a [`serde_json::Value`] into a JSON file.
|
||||
#[allow(dead_code)]
|
||||
pub fn write_json(filename: &str, value: &Value) -> Result<()> {
|
||||
let f =
|
||||
fs::File::create(filename).with_context(|| format!("failed to create JSON file {filename}"))?;
|
||||
let f = fs::File::create(filename)?;
|
||||
serde_json::to_writer(f, value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Download a file from either a local path or an HTTP/HTTPS URL.
|
||||
/// Falls back to copying the file if the URL does not start with http/https.
|
||||
pub fn download_file(url: &str, filename: PathBuf) -> Result<()> {
|
||||
#[allow(dead_code)]
|
||||
pub fn download_file(url: &str, filename: PathBuf) {
|
||||
if !url.starts_with("http:") && !url.starts_with("https:") {
|
||||
fs::copy(url, &filename).with_context(|| format!("failed to copy from {url}"))?;
|
||||
return Ok(());
|
||||
fs::copy(url, filename).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Downloading {url}");
|
||||
// Downloading with curl this saves us from adding
|
||||
// a Rust HTTP client dependency.
|
||||
println!("Downloading {}", url);
|
||||
let status = Command::new("curl")
|
||||
.arg("-L")
|
||||
.arg("-s")
|
||||
@@ -262,14 +238,8 @@ pub fn download_file(url: &str, filename: PathBuf) -> Result<()> {
|
||||
.arg(&filename)
|
||||
.arg(url)
|
||||
.status()
|
||||
.with_context(|| format!("failed to execute curl for {url}"))?;
|
||||
.unwrap();
|
||||
|
||||
if !status.success() {
|
||||
bail!("curl failed with exit code {:?}", status.code());
|
||||
}
|
||||
if !filename.exists() {
|
||||
bail!("expected file {:?} to exist after download", filename);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
assert!(status.success());
|
||||
assert!(filename.exists());
|
||||
}
|
||||
|
||||
@@ -1,62 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.5.3]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.1`
|
||||
- Upgraded to `tauri-codegen@2.5.2`
|
||||
|
||||
## \[2.5.2]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-codegen@2.5.1`
|
||||
|
||||
## \[2.5.1]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`4b6b8690a`](https://www.github.com/tauri-apps/tauri/commit/4b6b8690ab886ebdf1307951cffbe03e31280baa) ([#14347](https://www.github.com/tauri-apps/tauri/pull/14347) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
|
||||
|
||||
## \[2.5.0]
|
||||
|
||||
### New Features
|
||||
|
||||
- [`3b4fac201`](https://www.github.com/tauri-apps/tauri/commit/3b4fac2017832d426dd07c5e24e26684eda57f7b) ([#14194](https://www.github.com/tauri-apps/tauri/pull/14194)) Add `tauri.conf.json > bundle > android > autoIncrementVersionCode` config option to automatically increment the Android version code.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.0`
|
||||
- Upgraded to `tauri-codegen@2.5.0`
|
||||
|
||||
## \[2.4.1]
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`c23bec62d`](https://www.github.com/tauri-apps/tauri/commit/c23bec62d6d5724798869681aa1534423aae28e2) ([#14083](https://www.github.com/tauri-apps/tauri/pull/14083) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Tauri now ignores `macOS.minimumSystemVersion` in `tauri dev` to prevent forced rebuilds of macOS specific dependencies when using something like `rust-analyzer` at the same time as `tauri dev`.
|
||||
|
||||
## \[2.4.0]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.7.0`
|
||||
- Upgraded to `tauri-codegen@2.4.0`
|
||||
|
||||
## \[2.3.1]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.6.0`
|
||||
- Upgraded to `tauri-codegen@2.3.1`
|
||||
|
||||
## \[2.2.1]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-codegen@2.3.0`
|
||||
- Upgraded to `tauri-utils@2.5.0`
|
||||
|
||||
## \[2.2.0]
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-build"
|
||||
version = "2.5.3"
|
||||
version = "2.2.0"
|
||||
description = "build time code to pair with https://crates.io/crates/tauri"
|
||||
exclude = ["CHANGELOG.md", "/target"]
|
||||
readme = "README.md"
|
||||
@@ -22,12 +22,14 @@ targets = [
|
||||
"x86_64-linux-android",
|
||||
"x86_64-apple-ios",
|
||||
]
|
||||
rustc-args = ["--cfg", "docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
quote = { version = "1", optional = true }
|
||||
tauri-codegen = { version = "2.5.2", path = "../tauri-codegen", optional = true }
|
||||
tauri-utils = { version = "2.8.1", path = "../tauri-utils", features = [
|
||||
tauri-codegen = { version = "2.2.0", path = "../tauri-codegen", optional = true }
|
||||
tauri-utils = { version = "2.4.0", path = "../tauri-utils", features = [
|
||||
"build",
|
||||
"resources",
|
||||
] }
|
||||
@@ -41,9 +43,9 @@ 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"] }
|
||||
toml = "0.8"
|
||||
# Our code requires at least 0.8.18 so don't simplify this to 0.8
|
||||
schemars = { version = "0.8.18", features = ["preserve_order"] }
|
||||
|
||||
[features]
|
||||
default = ["config-json"]
|
||||
|
||||
@@ -157,7 +157,7 @@ fn read_plugins_manifests() -> Result<BTreeMap<String, Manifest>> {
|
||||
Ok(manifests)
|
||||
}
|
||||
|
||||
struct InlinedPluginsAcl {
|
||||
struct InlinedPuginsAcl {
|
||||
manifests: BTreeMap<String, Manifest>,
|
||||
permission_files: BTreeMap<String, Vec<PermissionFile>>,
|
||||
}
|
||||
@@ -165,7 +165,7 @@ struct InlinedPluginsAcl {
|
||||
fn inline_plugins(
|
||||
out_dir: &Path,
|
||||
inlined_plugins: HashMap<&'static str, InlinedPlugin>,
|
||||
) -> Result<InlinedPluginsAcl> {
|
||||
) -> Result<InlinedPuginsAcl> {
|
||||
let mut acl_manifests = BTreeMap::new();
|
||||
let mut permission_files_map = BTreeMap::new();
|
||||
|
||||
@@ -250,7 +250,7 @@ permissions = [{default_permissions}]
|
||||
acl_manifests.insert(name.into(), manifest);
|
||||
}
|
||||
|
||||
Ok(InlinedPluginsAcl {
|
||||
Ok(InlinedPuginsAcl {
|
||||
manifests: acl_manifests,
|
||||
permission_files: permission_files_map,
|
||||
})
|
||||
@@ -437,7 +437,7 @@ pub fn build(out_dir: &Path, target: Target, attributes: &Attributes) -> super::
|
||||
permissions_map.insert(APP_ACL_KEY.to_string(), app_acl.permission_files);
|
||||
}
|
||||
|
||||
tauri_utils::acl::build::generate_allowed_commands(out_dir, Some(capabilities), permissions_map)?;
|
||||
tauri_utils::acl::build::generate_allowed_commands(out_dir, permissions_map)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -120,13 +120,6 @@ impl CodegenContext {
|
||||
if info_plist_path.exists() {
|
||||
println!("cargo:rerun-if-changed={}", info_plist_path.display());
|
||||
}
|
||||
|
||||
if let Some(plist_path) = &config.bundle.macos.info_plist {
|
||||
let info_plist_path = config_parent.join(plist_path);
|
||||
if info_plist_path.exists() {
|
||||
println!("cargo:rerun-if-changed={}", info_plist_path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let code = context_codegen(ContextData {
|
||||
|
||||
@@ -263,7 +263,7 @@ impl WindowsAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the default attribute set without the default app manifest.
|
||||
/// Creates the default attriute set wihtou the default app manifest.
|
||||
#[must_use]
|
||||
pub fn new_without_app_manifest() -> Self {
|
||||
Self {
|
||||
@@ -367,8 +367,6 @@ impl Attributes {
|
||||
|
||||
/// Set the glob pattern to be used to find the capabilities.
|
||||
///
|
||||
/// **WARNING:** The `removeUnusedCommands` option does not work with a custom capabilities path.
|
||||
///
|
||||
/// **Note:** You must emit [rerun-if-changed] instructions for your capabilities directory.
|
||||
///
|
||||
/// [rerun-if-changed]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed
|
||||
@@ -499,7 +497,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
println!("cargo:rustc-env=TAURI_ANDROID_PACKAGE_NAME_PREFIX={android_package_prefix}");
|
||||
|
||||
if let Some(project_dir) = env::var_os("TAURI_ANDROID_PROJECT_PATH").map(PathBuf::from) {
|
||||
mobile::generate_gradle_files(project_dir)?;
|
||||
mobile::generate_gradle_files(project_dir, &config)?;
|
||||
}
|
||||
|
||||
cfg_alias("dev", is_dev());
|
||||
@@ -530,7 +528,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
|
||||
if let Some(paths) = &config.bundle.external_bin {
|
||||
copy_binaries(
|
||||
ResourcePaths::new(&external_binaries(paths, &target_triple, &target), true),
|
||||
ResourcePaths::new(external_binaries(paths, &target_triple).as_slice(), true),
|
||||
&target_triple,
|
||||
target_dir,
|
||||
manifest.package.as_ref().map(|p| &p.name),
|
||||
@@ -573,10 +571,8 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
if !is_dev() {
|
||||
if let Some(version) = &config.bundle.macos.minimum_system_version {
|
||||
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={version}");
|
||||
}
|
||||
if let Some(version) = &config.bundle.macos.minimum_system_version {
|
||||
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={version}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{fs::write, path::PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use tauri_utils::write_if_changed;
|
||||
use semver::Version;
|
||||
use tauri_utils::{config::Config, write_if_changed};
|
||||
|
||||
pub fn generate_gradle_files(project_dir: PathBuf) -> Result<()> {
|
||||
use crate::is_dev;
|
||||
|
||||
pub fn generate_gradle_files(project_dir: PathBuf, config: &Config) -> Result<()> {
|
||||
let gradle_settings_path = project_dir.join("tauri.settings.gradle");
|
||||
let app_build_gradle_path = project_dir.join("app").join("tauri.build.gradle.kts");
|
||||
let app_tauri_properties_path = project_dir.join("app").join("tauri.properties");
|
||||
|
||||
let mut gradle_settings =
|
||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n".to_string();
|
||||
@@ -17,6 +21,7 @@ pub fn generate_gradle_files(project_dir: PathBuf) -> Result<()> {
|
||||
val implementation by configurations
|
||||
dependencies {"
|
||||
.to_string();
|
||||
let mut app_tauri_properties = Vec::new();
|
||||
|
||||
for (env, value) in std::env::vars_os() {
|
||||
let env = env.to_string_lossy();
|
||||
@@ -49,6 +54,32 @@ dependencies {"
|
||||
|
||||
app_build_gradle.push_str("\n}");
|
||||
|
||||
if let Some(version) = config.version.as_ref() {
|
||||
app_tauri_properties.push(format!("tauri.android.versionName={version}"));
|
||||
if let Some(version_code) = config.bundle.android.version_code.as_ref() {
|
||||
app_tauri_properties.push(format!("tauri.android.versionCode={version_code}"));
|
||||
} else if let Ok(version) = Version::parse(version) {
|
||||
let mut version_code = version.major * 1000000 + version.minor * 1000 + version.patch;
|
||||
|
||||
if is_dev() {
|
||||
version_code = version_code.clamp(1, 2100000000);
|
||||
}
|
||||
|
||||
if version_code == 0 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"You must change the `version` in `tauri.conf.json`. The default value `0.0.0` is not allowed for Android package and must be at least `0.0.1`."
|
||||
));
|
||||
} else if version_code > 2100000000 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid version code {}. Version code must be between 1 and 2100000000. You must change the `version` in `tauri.conf.json`.",
|
||||
version_code
|
||||
));
|
||||
}
|
||||
|
||||
app_tauri_properties.push(format!("tauri.android.versionCode={version_code}"));
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite only if changed to not trigger rebuilds
|
||||
write_if_changed(&gradle_settings_path, gradle_settings)
|
||||
.context("failed to write tauri.settings.gradle")?;
|
||||
@@ -56,8 +87,28 @@ dependencies {"
|
||||
write_if_changed(&app_build_gradle_path, app_build_gradle)
|
||||
.context("failed to write tauri.build.gradle.kts")?;
|
||||
|
||||
if !app_tauri_properties.is_empty() {
|
||||
let app_tauri_properties_content = format!(
|
||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n{}",
|
||||
app_tauri_properties.join("\n")
|
||||
);
|
||||
if std::fs::read_to_string(&app_tauri_properties_path)
|
||||
.map(|o| o != app_tauri_properties_content)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
write(&app_tauri_properties_path, app_tauri_properties_content)
|
||||
.context("failed to write tauri.properties")?;
|
||||
}
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-changed={}", gradle_settings_path.display());
|
||||
println!("cargo:rerun-if-changed={}", app_build_gradle_path.display());
|
||||
if !app_tauri_properties.is_empty() {
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
app_tauri_properties_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,131 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.7.4]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`1496145f8`](https://www.github.com/tauri-apps/tauri/commit/1496145f8222649efeff22b819a96208670bbea1) ([#14585](https://www.github.com/tauri-apps/tauri/pull/14585) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the AppImage bundler to fail with 404 errors for 32-bit builds.
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
- [`ce98d87ce`](https://www.github.com/tauri-apps/tauri/commit/ce98d87ce0aaa907285852eb80691197424e03c3) ([#14474](https://www.github.com/tauri-apps/tauri/pull/14474) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) refactor: remove needless collect. No user facing changes.
|
||||
- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-macos-sign@2.3.1`
|
||||
- Upgraded to `tauri-utils@2.8.1`
|
||||
- [`b5ef603d8`](https://www.github.com/tauri-apps/tauri/commit/b5ef603d84bd8044625e50dcfdabb099b2e9fdd9) ([#14478](https://www.github.com/tauri-apps/tauri/pull/14478) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Updated NSIS from 3.8 to 3.11
|
||||
|
||||
## \[2.7.3]
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`22edc65aa`](https://www.github.com/tauri-apps/tauri/commit/22edc65aad0b3e45515008e8e0866112da70c8a1) ([#14408](https://www.github.com/tauri-apps/tauri/pull/14408) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Set user-agent in bundler and cli http requests when fetching build tools.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`9a1922636`](https://www.github.com/tauri-apps/tauri/commit/9a192263693d71123a9953e2a6ee60fad07500b4) ([#14410](https://www.github.com/tauri-apps/tauri/pull/14410) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix uninstall fails if you close the app manually during the 'Click Ok to kill it' dialog
|
||||
|
||||
## \[2.7.2]
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`7f710b8f3`](https://www.github.com/tauri-apps/tauri/commit/7f710b8f3b509ed327d76761926511cf56e66b2d) ([#14390](https://www.github.com/tauri-apps/tauri/pull/14390) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Inline linuxdeploy plugins which were previously downloaded from `https://raw.githubusercontent.com` which lately blocks many users with a 429 error.
|
||||
- [`fc017ee25`](https://www.github.com/tauri-apps/tauri/commit/fc017ee2577f48615367ea519386d3f37837e2c1) ([#14368](https://www.github.com/tauri-apps/tauri/pull/14368) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Mention symbol stripping on Linux in binary patch failed warning message
|
||||
|
||||
## \[2.7.1]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-macos-sign@2.3.0`
|
||||
|
||||
## \[2.7.0]
|
||||
|
||||
### New Features
|
||||
|
||||
- [`2a06d1006`](https://www.github.com/tauri-apps/tauri/commit/2a06d10066a806e392efe8bfb16d943ee0b0b61d) ([#14052](https://www.github.com/tauri-apps/tauri/pull/14052)) Add a `--no-sign` flag to the `tauri build` and `tauri bundle` commands to skip the code signing step, improving the developer experience for local testing and development without requiring code signing keys.
|
||||
- [`cc8c0b531`](https://www.github.com/tauri-apps/tauri/commit/cc8c0b53171173dbd1d01781a50de1a3ea159031) ([#14031](https://www.github.com/tauri-apps/tauri/pull/14031)) Support providing `plist::Value` as macOS entitlements.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`b06b3bd09`](https://www.github.com/tauri-apps/tauri/commit/b06b3bd091b0fed26cdcfb23cacb0462a7a9cc2d) ([#14126](https://www.github.com/tauri-apps/tauri/pull/14126)) Improve error messages with more context.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`06d4a4ed6`](https://www.github.com/tauri-apps/tauri/commit/06d4a4ed6c146d6c7782016cf90037b56b944445) ([#14241](https://www.github.com/tauri-apps/tauri/pull/14241)) Set `APPIMAGE_EXTRACT_AND_RUN` on top of using the `--appimage-extra-and-run` cli arg for linuxdeploy.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.0`
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [`ed7c9a410`](https://www.github.com/tauri-apps/tauri/commit/ed7c9a4100e08c002212265549d12130d021ad1e) ([#14108](https://www.github.com/tauri-apps/tauri/pull/14108)) Changed `MacOsSettings::info_plist_path` to `MacOsSettings::info_plist`.
|
||||
|
||||
## \[2.6.1]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`f3df96fb3`](https://www.github.com/tauri-apps/tauri/commit/f3df96fb38e2f27ce6bf232fe87f35bcfec50ce4) ([#14065](https://www.github.com/tauri-apps/tauri/pull/14065) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix binary patching updater type fails on 32 bit Windows builds
|
||||
|
||||
## \[2.6.0]
|
||||
|
||||
### New Features
|
||||
|
||||
- [`a9ec12843`](https://www.github.com/tauri-apps/tauri/commit/a9ec12843aa7d0eb774bd3a53e2e63da12cfa77b) ([#13521](https://www.github.com/tauri-apps/tauri/pull/13521) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added a `--skip-stapling` option to make `tauri build|bundle` *not* wait for notarization to finish on macOS.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`8b465a12b`](https://www.github.com/tauri-apps/tauri/commit/8b465a12ba73e94d7a3995defd9cc362d15eeebe) ([#13913](https://www.github.com/tauri-apps/tauri/pull/13913) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler now pulls the latest AppImage linuxdeploy plugin instead of using the built-in one. This should remove the libfuse requirement.
|
||||
- [`4475e93e1`](https://www.github.com/tauri-apps/tauri/commit/4475e93e136e9e2bd5f3c7817fa2040924f630f6) ([#13824](https://www.github.com/tauri-apps/tauri/pull/13824) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler and cli will now read TLS Certificates installed on the system when downloading tools and checking versions.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`a8f1569b0`](https://www.github.com/tauri-apps/tauri/commit/a8f1569b04edf7b54a19e19ad37b421b0808f512) ([#13921](https://www.github.com/tauri-apps/tauri/pull/13921) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) The bundler will no longer try to sign non-binary and already signed binary files on Windows
|
||||
- [`bc6b125b2`](https://www.github.com/tauri-apps/tauri/commit/bc6b125b24589ffc412a4f17d899a387a0fc0bb2) ([#13909](https://www.github.com/tauri-apps/tauri/pull/13909) by [@Andrew15-5](https://www.github.com/tauri-apps/tauri/../../Andrew15-5)) The bundler now falls back to `1` for the release in case an empty string was provided instead of using `-.` in the file name.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.7.0`
|
||||
- Upgraded to `tauri-macos-sign@2.2.0`
|
||||
|
||||
## \[2.5.2]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`af95fb601`](https://www.github.com/tauri-apps/tauri/commit/af95fb6014ea54a2636bfd299095608f6cd93221) ([#13870](https://www.github.com/tauri-apps/tauri/pull/13870) by [@kittuov](https://www.github.com/tauri-apps/tauri/../../kittuov)) The bundler now signs the main binary after patching it for every package type on windows
|
||||
|
||||
## \[2.5.1]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`f94af9035`](https://www.github.com/tauri-apps/tauri/commit/f94af90359ec8b01138ae542391caa704ec18ca8) ([#13786](https://www.github.com/tauri-apps/tauri/pull/13786) by [@catalinsh](https://www.github.com/tauri-apps/tauri/../../catalinsh)) Fix NSIS per-machine installer not requesting elevation when run by non-admin users.
|
||||
- [`f2dbe7309`](https://www.github.com/tauri-apps/tauri/commit/f2dbe730979d570be3ee3ecac9621204c4ceb788) ([#13772](https://www.github.com/tauri-apps/tauri/pull/13772) by [@catalinsh](https://www.github.com/tauri-apps/tauri/../../catalinsh)) Fix incorrect expected file path for `nsis_tauri_utils.dll` resulting in tauri-cli re-downloading the file on every build.
|
||||
- [`7a6fd5b75`](https://www.github.com/tauri-apps/tauri/commit/7a6fd5b75d61071e2771f6277c0376ec206d302a) ([#13863](https://www.github.com/tauri-apps/tauri/pull/13863) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The AppImage bundler now pulls the AppRun binaries from our GitHub mirror, fixing 404 errors.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.6.0`
|
||||
|
||||
## \[2.5.0]
|
||||
|
||||
### New Features
|
||||
|
||||
- [`414619c36`](https://www.github.com/tauri-apps/tauri/commit/414619c36e94e21939534dd72c0438b93da75546) ([#13536](https://www.github.com/tauri-apps/tauri/pull/13536) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) Added support for the `bundleName` property in the macOS bundler configuration. This allows specifying the `CFBundleName` value for generated macOS bundles.
|
||||
- [`7322f0579`](https://www.github.com/tauri-apps/tauri/commit/7322f057923aaec88960ad5556776774b745762f) ([#13502](https://www.github.com/tauri-apps/tauri/pull/13502) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Allow using `CheckIfAppIsRunning` macro inside NSIS hooks, for example `!insertmacro CheckIfAppIsRunning "another-executable.exe" "Another Executable"`.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`479cee3d3`](https://www.github.com/tauri-apps/tauri/commit/479cee3d3680f9020005bdfb380d3a9482e286a1) ([#13260](https://www.github.com/tauri-apps/tauri/pull/13260) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler now sets the `ARCH` env var to the current build target to prevent potential issues with `appimagetool`'s auto-detection.
|
||||
- [`e045fe32c`](https://www.github.com/tauri-apps/tauri/commit/e045fe32c9b0bed954916dc42528e28ee19f75b8) ([#13334](https://www.github.com/tauri-apps/tauri/pull/13334) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix custom Windows sign command failing to sign app uninstaller if it references relative paths.
|
||||
- [`bd8a7cf39`](https://www.github.com/tauri-apps/tauri/commit/bd8a7cf39df316bf27c73a303d5e650301af0104) ([#13581](https://www.github.com/tauri-apps/tauri/pull/13581) by [@martpie](https://www.github.com/tauri-apps/tauri/../../martpie)) Fixes app icon not being displayed on Gnome dock and grid view when using Wayland.
|
||||
- [`b52da29d5`](https://www.github.com/tauri-apps/tauri/commit/b52da29d5dbdb675ddba438a335e6a59f620e536) ([#13429](https://www.github.com/tauri-apps/tauri/pull/13429) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `mainBinaryName` doesn't work when there's `.` in it
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.5.0`
|
||||
|
||||
## \[2.4.0]
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-bundler"
|
||||
version = "2.7.4"
|
||||
version = "2.4.0"
|
||||
authors = [
|
||||
"George Burton <burtonageo@gmail.com>",
|
||||
"Tauri Programme within The Commons Conservancy",
|
||||
@@ -15,13 +15,13 @@ rust-version = "1.77.2"
|
||||
exclude = ["CHANGELOG.md", "/target", "rustfmt.toml"]
|
||||
|
||||
[dependencies]
|
||||
tauri-utils = { version = "2.8.1", path = "../tauri-utils", features = [
|
||||
tauri-utils = { version = "2.4.0", path = "../tauri-utils", features = [
|
||||
"resources",
|
||||
] }
|
||||
image = "0.25"
|
||||
flate2 = "1"
|
||||
thiserror = "2"
|
||||
anyhow = "1"
|
||||
thiserror = "2"
|
||||
serde_json = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
strsim = "0.11"
|
||||
@@ -38,14 +38,11 @@ hex = "0.4"
|
||||
semver = "1"
|
||||
sha1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
zip = { version = "4", default-features = false, features = ["deflate"] }
|
||||
zip = { version = "2", default-features = false, features = ["deflate"] }
|
||||
dunce = "1"
|
||||
url = "2"
|
||||
uuid = { version = "1", features = ["v4", "v5"] }
|
||||
regex = "1"
|
||||
goblin = "0.9"
|
||||
plist = "1"
|
||||
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies]
|
||||
bitness = "0.4"
|
||||
@@ -53,30 +50,30 @@ windows-registry = "0.5"
|
||||
glob = "0.3"
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
|
||||
version = "0.60"
|
||||
version = "0.59"
|
||||
features = ["Win32_System_SystemInformation", "Win32_System_Diagnostics_Debug"]
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
icns = { package = "tauri-icns", version = "0.1" }
|
||||
time = { version = "0.3", features = ["formatting"] }
|
||||
tauri-macos-sign = { version = "2.3.1", path = "../tauri-macos-sign" }
|
||||
plist = "1"
|
||||
tauri-macos-sign = { version = "2.1.0", path = "../tauri-macos-sign" }
|
||||
|
||||
[target."cfg(target_os = \"linux\")".dependencies]
|
||||
heck = "0.5"
|
||||
ar = "0.9"
|
||||
md5 = "0.8"
|
||||
md5 = "0.7"
|
||||
rpm = { version = "0.16", features = ["bzip2-compression"] }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
which = "8"
|
||||
which = "7"
|
||||
|
||||
[lib]
|
||||
name = "tauri_bundler"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["rustls", "platform-certs"]
|
||||
default = ["rustls"]
|
||||
native-tls = ["ureq/native-tls"]
|
||||
native-tls-vendored = ["native-tls", "native-tls/vendored"]
|
||||
rustls = ["ureq/rustls"]
|
||||
platform-certs = ["ureq/platform-verifier"]
|
||||
|
||||
@@ -13,49 +13,21 @@ mod settings;
|
||||
mod updater_bundle;
|
||||
mod windows;
|
||||
|
||||
use tauri_utils::{display_path, platform::Target as TargetPlatform};
|
||||
|
||||
/// 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)?;
|
||||
}
|
||||
PackageType::Nsis | PackageType::WindowsMsi => {
|
||||
log::info!(
|
||||
"Patching binary {:?} for type {}",
|
||||
binary,
|
||||
package_type.short_name()
|
||||
);
|
||||
windows::patch_binary(binary, package_type)?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
use tauri_utils::display_path;
|
||||
|
||||
pub use self::{
|
||||
category::AppCategory,
|
||||
settings::{
|
||||
AppImageSettings, BundleBinary, BundleSettings, CustomSignCommandSettings, DebianSettings,
|
||||
DmgSettings, Entitlements, IosSettings, MacOsSettings, PackageSettings, PackageType, PlistKind,
|
||||
Position, RpmSettings, Settings, SettingsBuilder, Size, UpdaterSettings,
|
||||
DmgSettings, IosSettings, MacOsSettings, PackageSettings, PackageType, Position, RpmSettings,
|
||||
Settings, SettingsBuilder, Size, UpdaterSettings,
|
||||
},
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use anyhow::Context;
|
||||
pub use settings::{NsisSettings, WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings};
|
||||
|
||||
use std::{
|
||||
fmt::Write,
|
||||
io::{Seek, SeekFrom},
|
||||
path::PathBuf,
|
||||
};
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
/// Generated bundle metadata.
|
||||
#[derive(Debug)]
|
||||
@@ -76,38 +48,50 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||
|
||||
package_types.sort_by_key(|a| a.priority());
|
||||
|
||||
let target_os = settings.target_platform();
|
||||
let target_os = settings
|
||||
.target()
|
||||
.split('-')
|
||||
.nth(2)
|
||||
.unwrap_or(std::env::consts::OS)
|
||||
.replace("darwin", "macos");
|
||||
|
||||
if *target_os != TargetPlatform::current() {
|
||||
if target_os != std::env::consts::OS {
|
||||
log::warn!("Cross-platform compilation is experimental and does not support all features. Please use a matching host system for full compatibility.");
|
||||
}
|
||||
|
||||
// Sign windows binaries before the bundling step in case neither wix and nsis bundles are enabled
|
||||
sign_binaries_if_needed(settings, target_os)?;
|
||||
if target_os == "windows" {
|
||||
if settings.can_sign() {
|
||||
for bin in settings.binaries() {
|
||||
let bin_path = settings.binary_path(bin);
|
||||
windows::sign::try_sign(&bin_path, settings)?;
|
||||
}
|
||||
|
||||
let main_binary = settings
|
||||
.binaries()
|
||||
.iter()
|
||||
.find(|b| b.main())
|
||||
.expect("Main binary missing in settings");
|
||||
let main_binary_path = settings.binary_path(main_binary);
|
||||
// Sign the sidecar binaries
|
||||
for bin in settings.external_binaries() {
|
||||
let path = bin?;
|
||||
let skip = std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").is_ok_and(|v| v == "true");
|
||||
if skip {
|
||||
continue;
|
||||
}
|
||||
|
||||
// When packaging multiple binary types, we make a copy of the unsigned main_binary so that we can
|
||||
// restore it after each package_type step. This avoids two issues:
|
||||
// - modifying a signed binary without updating its PE checksum can break signature verification
|
||||
// - codesigning tools should handle calculating+updating this, we just need to ensure
|
||||
// (re)signing is performed after every `patch_binary()` operation
|
||||
// - signing an already-signed binary can result in multiple signatures, causing verification errors
|
||||
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)?;
|
||||
#[cfg(windows)]
|
||||
if windows::sign::verify(&path)? {
|
||||
log::info!(
|
||||
"sidecar at \"{}\" already signed. Skipping...",
|
||||
path.display()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
windows::sign::try_sign(&path, settings)?;
|
||||
}
|
||||
} else {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
log::warn!("Signing, by default, is only supported on Windows hosts, but you can specify a custom signing command in `bundler > windows > sign_command`, for now, skipping signing the installer...");
|
||||
}
|
||||
}
|
||||
|
||||
let mut main_binary_signed = false;
|
||||
let mut bundles = Vec::<Bundle>::new();
|
||||
for package_type in &package_types {
|
||||
// bundle was already built! e.g. DMG already built .app
|
||||
@@ -115,24 +99,6 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = patch_binary(&main_binary_path, package_type) {
|
||||
log::warn!("Failed to add bundler type to the binary: {e}. Updater plugin may not be able to update this package. This shouldn't normally happen, please report it to https://github.com/tauri-apps/tauri/issues");
|
||||
}
|
||||
|
||||
// sign main binary for every package type after patch
|
||||
if matches!(target_os, TargetPlatform::Windows) && settings.windows().can_sign() {
|
||||
if main_binary_signed && main_binary_reset_required {
|
||||
let mut signed_main_binary = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&main_binary_path)?;
|
||||
unsigned_main_binary_copy.seek(SeekFrom::Start(0))?;
|
||||
std::io::copy(&mut unsigned_main_binary_copy, &mut signed_main_binary)?;
|
||||
}
|
||||
windows::sign::try_sign(&main_binary_path, settings)?;
|
||||
main_binary_signed = true;
|
||||
}
|
||||
|
||||
let bundle_paths = match package_type {
|
||||
#[cfg(target_os = "macos")]
|
||||
PackageType::MacOsBundle => macos::app::bundle_project(settings)?,
|
||||
@@ -153,7 +119,6 @@ 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
|
||||
PackageType::Nsis => windows::nsis::bundle_project(settings, false)?,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -221,30 +186,31 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||
.map(|b| b.bundle_paths)
|
||||
{
|
||||
for app_bundle_path in &app_bundle_paths {
|
||||
use crate::error::ErrorExt;
|
||||
|
||||
log::info!(action = "Cleaning"; "{}", app_bundle_path.display());
|
||||
match app_bundle_path.is_dir() {
|
||||
true => std::fs::remove_dir_all(app_bundle_path),
|
||||
false => std::fs::remove_file(app_bundle_path),
|
||||
}
|
||||
.fs_context(
|
||||
"failed to clean the app bundle",
|
||||
app_bundle_path.to_path_buf(),
|
||||
)?;
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to clean the app bundle at {}",
|
||||
app_bundle_path.display()
|
||||
)
|
||||
})?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bundles.is_empty() {
|
||||
return Ok(bundles);
|
||||
return Err(anyhow::anyhow!("No bundles were built").into());
|
||||
}
|
||||
|
||||
let finished_bundles = bundles
|
||||
let bundles_wo_updater = bundles
|
||||
.iter()
|
||||
.filter(|b| b.package_type != PackageType::Updater)
|
||||
.count();
|
||||
.collect::<Vec<_>>();
|
||||
let finished_bundles = bundles_wo_updater.len();
|
||||
let pluralised = if finished_bundles == 1 {
|
||||
"bundle"
|
||||
} else {
|
||||
@@ -269,51 +235,6 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||
Ok(bundles)
|
||||
}
|
||||
|
||||
fn sign_binaries_if_needed(settings: &Settings, target_os: &TargetPlatform) -> crate::Result<()> {
|
||||
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.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for bin in settings.binaries() {
|
||||
if bin.main() {
|
||||
// we will sign the main binary after patching per "package type"
|
||||
continue;
|
||||
}
|
||||
let bin_path = settings.binary_path(bin);
|
||||
windows::sign::try_sign(&bin_path, settings)?;
|
||||
}
|
||||
|
||||
// Sign the sidecar binaries
|
||||
for bin in settings.external_binaries() {
|
||||
let path = bin?;
|
||||
let skip = std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").is_ok_and(|v| v == "true");
|
||||
if skip {
|
||||
continue;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
if windows::sign::verify(&path)? {
|
||||
log::info!(
|
||||
"sidecar at \"{}\" already signed. Skipping...",
|
||||
path.display()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
windows::sign::try_sign(&path, settings)?;
|
||||
}
|
||||
} else {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
log::warn!("Signing, by default, is only supported on Windows hosts, but you can specify a custom signing command in `bundler > windows > sign_command`, for now, skipping signing the installer...");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check to see if there are icons in the settings struct
|
||||
pub fn check_icons(settings: &Settings) -> crate::Result<bool> {
|
||||
// make a peekable iterator of the icon_files
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
use super::debian;
|
||||
use crate::{
|
||||
bundle::settings::Arch,
|
||||
error::{Context, ErrorExt},
|
||||
utils::{fs_utils, http_utils::download, CommandExt},
|
||||
Settings,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
@@ -27,7 +27,8 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
Arch::Armhf => "armhf",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"Unsupported architecture: {target:?}"
|
||||
"Unsupported architecture: {:?}",
|
||||
target
|
||||
)));
|
||||
}
|
||||
};
|
||||
@@ -52,11 +53,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
|
||||
fs::create_dir_all(&tools_path)?;
|
||||
|
||||
let linuxdeploy_path = prepare_tools(
|
||||
&tools_path,
|
||||
tools_arch,
|
||||
settings.log_level() != log::Level::Error,
|
||||
)?;
|
||||
let linuxdeploy_path = prepare_tools(&tools_path, tools_arch)?;
|
||||
|
||||
let package_dir = settings.project_out_directory().join("bundle/appimage_deb");
|
||||
|
||||
@@ -124,13 +121,13 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// 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())?;
|
||||
.context("xdg-mime binary not found")?;
|
||||
}
|
||||
|
||||
// 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())?;
|
||||
.context("xdg-open binary not found")?;
|
||||
}
|
||||
|
||||
let search_dirs = [
|
||||
@@ -190,8 +187,6 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
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",
|
||||
@@ -223,49 +218,34 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
}
|
||||
|
||||
// returns the linuxdeploy path to keep linuxdeploy_arch contained
|
||||
fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result<PathBuf> {
|
||||
fn prepare_tools(tools_path: &Path, arch: &str) -> 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}"
|
||||
"https://github.com/AppImage/AppImageKit/releases/download/continuous/AppRun-{arch}"
|
||||
))?;
|
||||
write_and_make_executable(&apprun, &data)?;
|
||||
write_and_make_executable(&apprun, data)?;
|
||||
}
|
||||
|
||||
let linuxdeploy_arch = if arch == "i686" { "i386" } else { arch };
|
||||
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)?;
|
||||
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");
|
||||
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 = include_bytes!("./linuxdeploy-plugin-gstreamer.sh");
|
||||
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([
|
||||
@@ -281,7 +261,7 @@ fn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result<
|
||||
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)?;
|
||||
@@ -1,165 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
# abort on all errors
|
||||
set -e
|
||||
|
||||
if [ "$DEBUG" != "" ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
script=$(readlink -f "$0")
|
||||
|
||||
show_usage() {
|
||||
echo "Usage: $script --appdir <path to AppDir>"
|
||||
echo
|
||||
echo "Bundles GStreamer plugins into an AppDir"
|
||||
echo
|
||||
echo "Required variables:"
|
||||
echo " LINUXDEPLOY=\".../linuxdeploy\" path to linuxdeploy (e.g., AppImage); set automatically when plugin is run directly by linuxdeploy"
|
||||
echo
|
||||
echo "Optional variables:"
|
||||
echo " GSTREAMER_INCLUDE_BAD_PLUGINS=\"1\" (default: disabled; set to empty string or unset to disable)"
|
||||
echo " GSTREAMER_PLUGINS_DIR=\"...\" (directory containing GStreamer plugins; default: guessed based on main distro architecture)"
|
||||
echo " GSTREAMER_HELPERS_DIR=\"...\" (directory containing GStreamer helper tools like gst-plugin-scanner; default: guessed based on main distro architecture)"
|
||||
echo " GSTREAMER_VERSION=\"1.0\" (default: 1.0)"
|
||||
}
|
||||
|
||||
while [ "$1" != "" ]; do
|
||||
case "$1" in
|
||||
--plugin-api-version)
|
||||
echo "0"
|
||||
exit 0
|
||||
;;
|
||||
--appdir)
|
||||
APPDIR="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Invalid argument: $1"
|
||||
echo
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$APPDIR" == "" ]; then
|
||||
show_usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! which patchelf &>/dev/null && ! type patchelf &>/dev/null; then
|
||||
echo "Error: patchelf not found"
|
||||
echo
|
||||
show_usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ "$LINUXDEPLOY" == "" ]]; then
|
||||
echo "Error: \$LINUXDEPLOY not set"
|
||||
echo
|
||||
show_usage
|
||||
exit 3
|
||||
fi
|
||||
|
||||
mkdir -p "$APPDIR"
|
||||
|
||||
export GSTREAMER_VERSION="${GSTREAMER_VERSION:-1.0}"
|
||||
|
||||
plugins_target_dir="$APPDIR"/usr/lib/gstreamer-"$GSTREAMER_VERSION"
|
||||
helpers_target_dir="$APPDIR"/usr/lib/gstreamer"$GSTREAMER_VERSION"/gstreamer-"$GSTREAMER_VERSION"
|
||||
|
||||
if [ "$GSTREAMER_PLUGINS_DIR" != "" ]; then
|
||||
plugins_dir="${GSTREAMER_PLUGINS_DIR}"
|
||||
elif [ -d /usr/lib/"$(uname -m)"-linux-gnu/gstreamer-"$GSTREAMER_VERSION" ]; then
|
||||
plugins_dir=/usr/lib/$(uname -m)-linux-gnu/gstreamer-"$GSTREAMER_VERSION"
|
||||
else
|
||||
plugins_dir=/usr/lib/gstreamer-"$GSTREAMER_VERSION"
|
||||
fi
|
||||
|
||||
if [ "$GSTREAMER_HELPERS_DIR" != "" ]; then
|
||||
helpers_dir="${GSTREAMER_HELPERS_DIR}"
|
||||
else
|
||||
helpers_dir=/usr/lib/$(uname -m)-linux-gnu/gstreamer"$GSTREAMER_VERSION"/gstreamer-"$GSTREAMER_VERSION"
|
||||
fi
|
||||
|
||||
if [ ! -d "$plugins_dir" ]; then
|
||||
echo "Error: could not find plugins directory: $plugins_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$plugins_target_dir"
|
||||
|
||||
echo "Copying plugins into $plugins_target_dir"
|
||||
for i in "$plugins_dir"/*; do
|
||||
[ -d "$i" ] && continue
|
||||
[ ! -f "$i" ] && echo "File does not exist: $i" && continue
|
||||
|
||||
echo "Copying plugin: $i"
|
||||
cp "$i" "$plugins_target_dir"
|
||||
done
|
||||
|
||||
"$LINUXDEPLOY" --appdir "$APPDIR"
|
||||
|
||||
for i in "$plugins_target_dir"/*; do
|
||||
[ -d "$i" ] && continue
|
||||
[ ! -f "$i" ] && echo "File does not exist: $i" && continue
|
||||
(file "$i" | grep -v ELF --silent) && echo "Ignoring non ELF file: $i" && continue
|
||||
|
||||
echo "Manually setting rpath for $i"
|
||||
patchelf --set-rpath '$ORIGIN/..:$ORIGIN' "$i"
|
||||
done
|
||||
|
||||
mkdir -p "$helpers_target_dir"
|
||||
|
||||
echo "Copying helpers in $helpers_target_dir"
|
||||
for i in "$helpers_dir"/*; do
|
||||
[ -d "$i" ] && continue
|
||||
[ ! -f "$i" ] && echo "File does not exist: $i" && continue
|
||||
|
||||
echo "Copying helper: $i"
|
||||
cp "$i" "$helpers_target_dir"
|
||||
done
|
||||
|
||||
for i in "$helpers_target_dir"/*; do
|
||||
[ -d "$i" ] && continue
|
||||
[ ! -f "$i" ] && echo "File does not exist: $i" && continue
|
||||
(file "$i" | grep -v ELF --silent) && echo "Ignoring non ELF file: $i" && continue
|
||||
|
||||
echo "Manually setting rpath for $i"
|
||||
patchelf --set-rpath '$ORIGIN/../..' "$i"
|
||||
done
|
||||
|
||||
echo "Installing AppRun hook"
|
||||
mkdir -p "$APPDIR"/apprun-hooks
|
||||
|
||||
if [ "$GSTREAMER_VERSION" == "1.0" ]; then
|
||||
cat > "$APPDIR"/apprun-hooks/linuxdeploy-plugin-gstreamer.sh <<\EOF
|
||||
#! /bin/bash
|
||||
|
||||
export GST_REGISTRY_REUSE_PLUGIN_SCANNER="no"
|
||||
export GST_PLUGIN_SYSTEM_PATH_1_0="${APPDIR}/usr/lib/gstreamer-1.0"
|
||||
export GST_PLUGIN_PATH_1_0="${APPDIR}/usr/lib/gstreamer-1.0"
|
||||
|
||||
export GST_PLUGIN_SCANNER_1_0="${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner"
|
||||
export GST_PTP_HELPER_1_0="${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-ptp-helper"
|
||||
EOF
|
||||
elif [ "$GSTREAMER_VERSION" == "0.10" ]; then
|
||||
cat > "$APPDIR"/apprun-hooks/linuxdeploy-plugin-gstreamer.sh <<\EOF
|
||||
#! /bin/bash
|
||||
|
||||
export GST_REGISTRY_REUSE_PLUGIN_SCANNER="no"
|
||||
export GST_PLUGIN_SYSTEM_PATH_0_10="${APPDIR}/usr/lib/gstreamer-1.0"
|
||||
|
||||
export GST_PLUGIN_SCANNER_0_10="${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner"
|
||||
export GST_PTP_HELPER_0_10="${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-ptp-helper"
|
||||
EOF
|
||||
else
|
||||
echo "Warning: unknown GStreamer version: $GSTREAMER_VERSION, cannot install AppRun hook"
|
||||
fi
|
||||
|
||||
@@ -1,327 +0,0 @@
|
||||
#! /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
|
||||
|
||||
# abort on all errors
|
||||
set -e
|
||||
|
||||
if [ "$DEBUG" != "" ]; then
|
||||
set -x
|
||||
verbose="--verbose"
|
||||
fi
|
||||
|
||||
script=$(readlink -f "$0")
|
||||
|
||||
show_usage() {
|
||||
echo "Usage: $script --appdir <path to AppDir>"
|
||||
echo
|
||||
echo "Bundles resources for applications that use GTK into an AppDir"
|
||||
echo
|
||||
echo "Required variables:"
|
||||
echo " LINUXDEPLOY=\".../linuxdeploy\" path to linuxdeploy (e.g., AppImage); set automatically when plugin is run directly by linuxdeploy"
|
||||
#echo
|
||||
#echo "Optional variables:"
|
||||
#echo " DEPLOY_GTK_VERSION (major version of GTK to deploy, e.g. '2', '3' or '4'; auto-detect by default)"
|
||||
}
|
||||
|
||||
variable_is_true() {
|
||||
local var="$1"
|
||||
|
||||
if [ -n "$var" ] && { [ "$var" == "true" ] || [ "$var" -gt 0 ]; } 2> /dev/null; then
|
||||
return 0 # true
|
||||
else
|
||||
return 1 # false
|
||||
fi
|
||||
}
|
||||
|
||||
get_pkgconf_variable() {
|
||||
local variable="$1"
|
||||
local library="$2"
|
||||
local default_path="$3"
|
||||
|
||||
path="$("$PKG_CONFIG" --variable="$variable" "$library")"
|
||||
if [ -n "$path" ]; then
|
||||
echo "$path"
|
||||
elif [ -n "$default_path" ]; then
|
||||
echo "$default_path"
|
||||
else
|
||||
echo "$0: there is no '$variable' variable for '$library' library." > /dev/stderr
|
||||
echo "Please check the '$library.pc' file is present in \$PKG_CONFIG_PATH (you may need to install the appropriate -dev/-devel package)." > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
copy_tree() {
|
||||
local src=("${@:1:$#-1}")
|
||||
local dst="${*:$#}"
|
||||
|
||||
for elem in "${src[@]}"; do
|
||||
mkdir -p "${dst::-1}$elem"
|
||||
cp "$elem" --archive --parents --target-directory="$dst" $verbose
|
||||
done
|
||||
}
|
||||
|
||||
search_tool() {
|
||||
local tool="$1"
|
||||
local directory="$2"
|
||||
|
||||
if command -v "$tool"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
PATH_ARRAY=(
|
||||
"/usr/lib/$(uname -m)-linux-gnu/$directory/$tool"
|
||||
"/usr/lib/$directory/$tool"
|
||||
"/usr/bin/$tool"
|
||||
"/usr/bin/$tool-64"
|
||||
"/usr/bin/$tool-32"
|
||||
)
|
||||
|
||||
for path in "${PATH_ARRAY[@]}"; do
|
||||
if [ -x "$path" ]; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
#DEPLOY_GTK_VERSION="${DEPLOY_GTK_VERSION:-0}" # When not set by user, this variable use the integer '0' as a sentinel value
|
||||
DEPLOY_GTK_VERSION=3 # Force GTK3 for tauri apps
|
||||
APPDIR=
|
||||
|
||||
while [ "$1" != "" ]; do
|
||||
case "$1" in
|
||||
--plugin-api-version)
|
||||
echo "0"
|
||||
exit 0
|
||||
;;
|
||||
--appdir)
|
||||
APPDIR="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Invalid argument: $1"
|
||||
echo
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$APPDIR" == "" ]; then
|
||||
show_usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$APPDIR"
|
||||
# make lib64 writable again.
|
||||
chmod +w "$APPDIR"/usr/lib64 || true
|
||||
|
||||
if command -v pkgconf > /dev/null; then
|
||||
PKG_CONFIG="pkgconf"
|
||||
elif command -v pkg-config > /dev/null; then
|
||||
PKG_CONFIG="pkg-config"
|
||||
else
|
||||
echo "$0: pkg-config/pkgconf not found in PATH, aborting"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v find &>/dev/null && ! type find &>/dev/null; then
|
||||
echo -e "$0: find not found.\nInstall findutils then re-run the plugin."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$LINUXDEPLOY" ]; then
|
||||
echo -e "$0: LINUXDEPLOY environment variable is not set.\nDownload a suitable linuxdeploy AppImage, set the environment variable and re-run the plugin."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gtk_versions=0 # Count major versions of GTK when auto-detect GTK version
|
||||
if [ "$DEPLOY_GTK_VERSION" -eq 0 ]; then
|
||||
echo "Determining which GTK version to deploy"
|
||||
while IFS= read -r -d '' file; do
|
||||
if [ "$DEPLOY_GTK_VERSION" -ne 2 ] && ldd "$file" | grep -q "libgtk-x11-2.0.so"; then
|
||||
DEPLOY_GTK_VERSION=2
|
||||
gtk_versions="$((gtk_versions+1))"
|
||||
fi
|
||||
if [ "$DEPLOY_GTK_VERSION" -ne 3 ] && ldd "$file" | grep -q "libgtk-3.so"; then
|
||||
DEPLOY_GTK_VERSION=3
|
||||
gtk_versions="$((gtk_versions+1))"
|
||||
fi
|
||||
if [ "$DEPLOY_GTK_VERSION" -ne 4 ] && ldd "$file" | grep -q "libgtk-4.so"; then
|
||||
DEPLOY_GTK_VERSION=4
|
||||
gtk_versions="$((gtk_versions+1))"
|
||||
fi
|
||||
done < <(find "$APPDIR/usr/bin" -executable -type f -print0)
|
||||
fi
|
||||
|
||||
if [ "$gtk_versions" -gt 1 ]; then
|
||||
echo "$0: can not deploy multiple GTK versions at the same time."
|
||||
echo "Please set DEPLOY_GTK_VERSION to {2, 3, 4}."
|
||||
exit 1
|
||||
elif [ "$DEPLOY_GTK_VERSION" -eq 0 ]; then
|
||||
echo "$0: failed to auto-detect GTK version."
|
||||
echo "Please set DEPLOY_GTK_VERSION to {2, 3, 4}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Installing AppRun hook"
|
||||
HOOKSDIR="$APPDIR/apprun-hooks"
|
||||
HOOKFILE="$HOOKSDIR/linuxdeploy-plugin-gtk.sh"
|
||||
mkdir -p "$HOOKSDIR"
|
||||
cat > "$HOOKFILE" <<\EOF
|
||||
#! /usr/bin/env bash
|
||||
|
||||
gsettings get org.gnome.desktop.interface gtk-theme 2> /dev/null | grep -qi "dark" && GTK_THEME_VARIANT="dark" || GTK_THEME_VARIANT="light"
|
||||
APPIMAGE_GTK_THEME="${APPIMAGE_GTK_THEME:-"Adwaita:$GTK_THEME_VARIANT"}" # Allow user to override theme (discouraged)
|
||||
|
||||
export APPDIR="${APPDIR:-"$(dirname "$(realpath "$0")")"}" # Workaround to run extracted AppImage
|
||||
export GTK_DATA_PREFIX="$APPDIR"
|
||||
export GTK_THEME="$APPIMAGE_GTK_THEME" # Custom themes are broken
|
||||
export GDK_BACKEND=x11 # Crash with Wayland backend on Wayland - We tested it without it and ended up with this: https://github.com/tauri-apps/tauri/issues/8541
|
||||
export XDG_DATA_DIRS="$APPDIR/usr/share:/usr/share:$XDG_DATA_DIRS" # g_get_system_data_dirs() from GLib
|
||||
EOF
|
||||
|
||||
echo "Installing GLib schemas"
|
||||
# Note: schemasdir is undefined on Ubuntu 16.04
|
||||
glib_schemasdir="$(get_pkgconf_variable "schemasdir" "gio-2.0" "/usr/share/glib-2.0/schemas")"
|
||||
copy_tree "$glib_schemasdir" "$APPDIR/"
|
||||
glib-compile-schemas "$APPDIR/$glib_schemasdir"
|
||||
cat >> "$HOOKFILE" <<EOF
|
||||
export GSETTINGS_SCHEMA_DIR="\$APPDIR/$glib_schemasdir"
|
||||
EOF
|
||||
|
||||
case "$DEPLOY_GTK_VERSION" in
|
||||
2)
|
||||
# https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/pull/20#issuecomment-826354261
|
||||
echo "WARNING: Gtk+2 applications are not fully supported by this plugin"
|
||||
;;
|
||||
3)
|
||||
echo "Installing GTK 3.0 modules"
|
||||
gtk3_exec_prefix="$(get_pkgconf_variable "exec_prefix" "gtk+-3.0")"
|
||||
gtk3_libdir="$(get_pkgconf_variable "libdir" "gtk+-3.0")/gtk-3.0"
|
||||
#gtk3_path="$gtk3_libdir/modules" export GTK_PATH="\$APPDIR/$gtk3_path"
|
||||
gtk3_immodulesdir="$gtk3_libdir/$(get_pkgconf_variable "gtk_binary_version" "gtk+-3.0")/immodules"
|
||||
gtk3_printbackendsdir="$gtk3_libdir/$(get_pkgconf_variable "gtk_binary_version" "gtk+-3.0")/printbackends"
|
||||
gtk3_immodules_cache_file="$(dirname "$gtk3_immodulesdir")/immodules.cache"
|
||||
gtk3_immodules_query="$(search_tool "gtk-query-immodules-3.0" "libgtk-3-0")"
|
||||
copy_tree "$gtk3_libdir" "$APPDIR/"
|
||||
cat >> "$HOOKFILE" <<EOF
|
||||
export GTK_EXE_PREFIX="\$APPDIR/$gtk3_exec_prefix"
|
||||
export GTK_PATH="\$APPDIR/$gtk3_libdir:/usr/lib64/gtk-3.0:/usr/lib/x86_64-linux-gnu/gtk-3.0"
|
||||
export GTK_IM_MODULE_FILE="\$APPDIR/$gtk3_immodules_cache_file"
|
||||
|
||||
EOF
|
||||
if [ -x "$gtk3_immodules_query" ]; then
|
||||
echo "Updating immodules cache in $APPDIR/$gtk3_immodules_cache_file"
|
||||
"$gtk3_immodules_query" > "$APPDIR/$gtk3_immodules_cache_file"
|
||||
else
|
||||
echo "WARNING: gtk-query-immodules-3.0 not found"
|
||||
fi
|
||||
if [ ! -f "$APPDIR/$gtk3_immodules_cache_file" ]; then
|
||||
echo "WARNING: immodules.cache file is missing"
|
||||
fi
|
||||
sed -i "s|$gtk3_libdir/3.0.0/immodules/||g" "$APPDIR/$gtk3_immodules_cache_file"
|
||||
;;
|
||||
4)
|
||||
echo "Installing GTK 4.0 modules"
|
||||
gtk4_exec_prefix="$(get_pkgconf_variable "exec_prefix" "gtk4" "/usr")"
|
||||
gtk4_libdir="$(get_pkgconf_variable "libdir" "gtk4")/gtk-4.0"
|
||||
gtk4_path="$gtk4_libdir/modules"
|
||||
copy_tree "$gtk4_libdir" "$APPDIR/"
|
||||
cat >> "$HOOKFILE" <<EOF
|
||||
export GTK_EXE_PREFIX="\$APPDIR/$gtk4_exec_prefix"
|
||||
export GTK_PATH="\$APPDIR/$gtk4_path"
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
echo "$0: '$DEPLOY_GTK_VERSION' is not a valid GTK major version."
|
||||
echo "Please set DEPLOY_GTK_VERSION to {2, 3, 4}."
|
||||
exit 1
|
||||
esac
|
||||
|
||||
echo "Installing GDK PixBufs"
|
||||
gdk_libdir="$(get_pkgconf_variable "libdir" "gdk-pixbuf-2.0")"
|
||||
gdk_pixbuf_binarydir="$(get_pkgconf_variable "gdk_pixbuf_binarydir" "gdk-pixbuf-2.0")"
|
||||
gdk_pixbuf_cache_file="$(get_pkgconf_variable "gdk_pixbuf_cache_file" "gdk-pixbuf-2.0")"
|
||||
gdk_pixbuf_moduledir="$(get_pkgconf_variable "gdk_pixbuf_moduledir" "gdk-pixbuf-2.0")"
|
||||
# Note: gdk_pixbuf_query_loaders variable is not defined on some systems
|
||||
gdk_pixbuf_query="$(search_tool "gdk-pixbuf-query-loaders" "gdk-pixbuf-2.0")"
|
||||
copy_tree "$gdk_pixbuf_binarydir" "$APPDIR/"
|
||||
cat >> "$HOOKFILE" <<EOF
|
||||
export GDK_PIXBUF_MODULE_FILE="\$APPDIR/$gdk_pixbuf_cache_file"
|
||||
EOF
|
||||
if [ -x "$gdk_pixbuf_query" ]; then
|
||||
echo "Updating pixbuf cache in $APPDIR/$gdk_pixbuf_cache_file"
|
||||
"$gdk_pixbuf_query" > "$APPDIR/$gdk_pixbuf_cache_file"
|
||||
else
|
||||
echo "WARNING: gdk-pixbuf-query-loaders not found"
|
||||
fi
|
||||
if [ ! -f "$APPDIR/$gdk_pixbuf_cache_file" ]; then
|
||||
echo "WARNING: loaders.cache file is missing"
|
||||
fi
|
||||
sed -i "s|$gdk_pixbuf_moduledir/||g" "$APPDIR/$gdk_pixbuf_cache_file"
|
||||
|
||||
echo "Copying more libraries"
|
||||
gobject_libdir="$(get_pkgconf_variable "libdir" "gobject-2.0")"
|
||||
gio_libdir="$(get_pkgconf_variable "libdir" "gio-2.0")"
|
||||
librsvg_libdir="$(get_pkgconf_variable "libdir" "librsvg-2.0")"
|
||||
pango_libdir="$(get_pkgconf_variable "libdir" "pango")"
|
||||
pangocairo_libdir="$(get_pkgconf_variable "libdir" "pangocairo")"
|
||||
pangoft2_libdir="$(get_pkgconf_variable "libdir" "pangoft2")"
|
||||
FIND_ARRAY=(
|
||||
"$gdk_libdir" "libgdk_pixbuf-*.so*"
|
||||
"$gobject_libdir" "libgobject-*.so*"
|
||||
"$gio_libdir" "libgio-*.so*"
|
||||
"$librsvg_libdir" "librsvg-*.so*"
|
||||
"$pango_libdir" "libpango-*.so*"
|
||||
"$pangocairo_libdir" "libpangocairo-*.so*"
|
||||
"$pangoft2_libdir" "libpangoft2-*.so*"
|
||||
)
|
||||
LIBRARIES=()
|
||||
for (( i=0; i<${#FIND_ARRAY[@]}; i+=2 )); do
|
||||
directory=${FIND_ARRAY[i]}
|
||||
library=${FIND_ARRAY[i+1]}
|
||||
while IFS= read -r -d '' file; do
|
||||
LIBRARIES+=( "--library=$file" )
|
||||
done < <(find "$directory" \( -type l -o -type f \) -name "$library" -print0)
|
||||
done
|
||||
|
||||
env LINUXDEPLOY_PLUGIN_MODE=1 "$LINUXDEPLOY" --appdir="$APPDIR" "${LIBRARIES[@]}"
|
||||
|
||||
# Create symbolic links as a workaround
|
||||
# Details: https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/issues/24#issuecomment-1030026529
|
||||
echo "Manually setting rpath for GTK modules"
|
||||
PATCH_ARRAY=(
|
||||
"$gtk3_immodulesdir"
|
||||
"$gtk3_printbackendsdir"
|
||||
"$gdk_pixbuf_moduledir"
|
||||
)
|
||||
for directory in "${PATCH_ARRAY[@]}"; do
|
||||
while IFS= read -r -d '' file; do
|
||||
ln $verbose -s "${file/\/usr\/lib\//}" "$APPDIR/usr/lib"
|
||||
done < <(find "$directory" -name '*.so' -print0)
|
||||
done
|
||||
|
||||
# set write permission on lib64 again to make it deletable.
|
||||
chmod +w "$APPDIR"/usr/lib64 || true
|
||||
|
||||
# We have to copy the files first to not get permission errors when we assign gio_extras_dir
|
||||
find /usr/lib* -name libgiognutls.so -exec mkdir -p "$APPDIR"/"$(dirname '{}')" \; -exec cp --parents '{}' "$APPDIR/" \; || true
|
||||
# related files that we seemingly don't need:
|
||||
# libgiolibproxy.so - libgiognomeproxy.so - glib-pacrunner
|
||||
|
||||
gio_extras_dir=$(find "$APPDIR"/usr/lib* -name libgiognutls.so -exec dirname '{}' \; 2>/dev/null)
|
||||
cat >> "$HOOKFILE" <<EOF
|
||||
export GIO_EXTRA_MODULES="\$APPDIR/${gio_extras_dir#"$APPDIR"/}"
|
||||
EOF
|
||||
|
||||
#binary patch absolute paths in libwebkit files
|
||||
find "$APPDIR"/usr/lib* -name 'libwebkit*' -exec sed -i -e "s|/usr|././|g" '{}' \;
|
||||
|
||||
@@ -24,12 +24,8 @@
|
||||
// generate postinst or prerm files.
|
||||
|
||||
use super::freedesktop;
|
||||
use crate::{
|
||||
bundle::settings::Arch,
|
||||
error::{Context, ErrorExt},
|
||||
utils::fs_utils,
|
||||
Settings,
|
||||
};
|
||||
use crate::{bundle::settings::Arch, utils::fs_utils, Settings};
|
||||
use anyhow::Context;
|
||||
use flate2::{write::GzEncoder, Compression};
|
||||
use tar::HeaderMode;
|
||||
use walkdir::WalkDir;
|
||||
@@ -53,7 +49,8 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
Arch::Riscv64 => "riscv64",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"Unsupported architecture: {target:?}"
|
||||
"Unsupported architecture: {:?}",
|
||||
target
|
||||
)));
|
||||
}
|
||||
};
|
||||
@@ -68,32 +65,30 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
let base_dir = settings.project_out_directory().join("bundle/deb");
|
||||
let package_dir = base_dir.join(&package_base_name);
|
||||
if package_dir.exists() {
|
||||
fs::remove_dir_all(&package_dir).fs_context(
|
||||
"Failed to Remove old package directory",
|
||||
package_dir.clone(),
|
||||
)?;
|
||||
fs::remove_dir_all(&package_dir)
|
||||
.with_context(|| format!("Failed to remove old {package_base_name}"))?;
|
||||
}
|
||||
let package_path = base_dir.join(&package_name);
|
||||
|
||||
log::info!(action = "Bundling"; "{} ({})", package_name, package_path.display());
|
||||
|
||||
let (data_dir, _) =
|
||||
generate_data(settings, &package_dir).context("Failed to build data folders and files")?;
|
||||
let (data_dir, _) = generate_data(settings, &package_dir)
|
||||
.with_context(|| "Failed to build data folders and files")?;
|
||||
fs_utils::copy_custom_files(&settings.deb().files, &data_dir)
|
||||
.context("Failed to copy custom files")?;
|
||||
.with_context(|| "Failed to copy custom files")?;
|
||||
|
||||
// Generate control files.
|
||||
let control_dir = package_dir.join("control");
|
||||
generate_control_file(settings, arch, &control_dir, &data_dir)
|
||||
.context("Failed to create control file")?;
|
||||
generate_scripts(settings, &control_dir).context("Failed to create control scripts")?;
|
||||
generate_md5sums(&control_dir, &data_dir).context("Failed to create md5sums file")?;
|
||||
.with_context(|| "Failed to create control file")?;
|
||||
generate_scripts(settings, &control_dir).with_context(|| "Failed to create control scripts")?;
|
||||
generate_md5sums(&control_dir, &data_dir).with_context(|| "Failed to create md5sums file")?;
|
||||
|
||||
// Generate `debian-binary` file; see
|
||||
// http://www.tldp.org/HOWTO/Debian-Binary-Package-Building-HOWTO/x60.html#AEN66
|
||||
let debian_binary_path = package_dir.join("debian-binary");
|
||||
create_file_with_data(&debian_binary_path, "2.0\n")
|
||||
.context("Failed to create debian-binary file")?;
|
||||
.with_context(|| "Failed to create debian-binary file")?;
|
||||
|
||||
// Apply tar/gzip/ar to create the final package file.
|
||||
let control_tar_gz_path =
|
||||
@@ -169,7 +164,7 @@ fn generate_control_file(
|
||||
let dest_path = control_dir.join("control");
|
||||
let mut file = fs_utils::create_file(&dest_path)?;
|
||||
let package = heck::AsKebabCase(settings.product_name());
|
||||
writeln!(file, "Package: {package}")?;
|
||||
writeln!(file, "Package: {}", package)?;
|
||||
writeln!(file, "Version: {}", settings.version_string())?;
|
||||
writeln!(file, "Architecture: {arch}")?;
|
||||
// Installed-Size must be divided by 1024, see https://www.debian.org/doc/debian-policy/ch-controlfields.html#installed-size
|
||||
@@ -188,16 +183,16 @@ fn generate_control_file(
|
||||
|
||||
writeln!(file, "Maintainer: {authors}")?;
|
||||
if let Some(section) = &settings.deb().section {
|
||||
writeln!(file, "Section: {section}")?;
|
||||
writeln!(file, "Section: {}", section)?;
|
||||
}
|
||||
if let Some(priority) = &settings.deb().priority {
|
||||
writeln!(file, "Priority: {priority}")?;
|
||||
writeln!(file, "Priority: {}", priority)?;
|
||||
} else {
|
||||
writeln!(file, "Priority: optional")?;
|
||||
}
|
||||
|
||||
if let Some(homepage) = settings.homepage_url() {
|
||||
writeln!(file, "Homepage: {homepage}")?;
|
||||
writeln!(file, "Homepage: {}", homepage)?;
|
||||
}
|
||||
|
||||
let dependencies = settings.deb().depends.as_ref().cloned().unwrap_or_default();
|
||||
@@ -310,7 +305,7 @@ fn generate_md5sums(control_dir: &Path, data_dir: &Path) -> crate::Result<()> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut hash = md5::Context::new();
|
||||
io::copy(&mut file, &mut hash)?;
|
||||
for byte in hash.finalize().iter() {
|
||||
for byte in hash.compute().iter() {
|
||||
write!(md5sums_file, "{byte:02x}")?;
|
||||
}
|
||||
let rel_path = path.strip_prefix(data_dir)?;
|
||||
|
||||
@@ -4,7 +4,6 @@ Categories={{categories}}
|
||||
Comment={{comment}}
|
||||
{{/if}}
|
||||
Exec={{exec}}
|
||||
StartupWMClass={{exec}}
|
||||
Icon={{icon}}
|
||||
Name={{name}}
|
||||
Terminal=false
|
||||
|
||||
@@ -21,12 +21,12 @@ use std::fs::{read_to_string, File};
|
||||
use std::io::BufReader;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Context;
|
||||
use handlebars::Handlebars;
|
||||
use image::{self, codecs::png::PngDecoder, ImageDecoder};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
error::Context,
|
||||
utils::{self, fs_utils},
|
||||
Settings,
|
||||
};
|
||||
@@ -114,13 +114,11 @@ pub fn generate_desktop_file(
|
||||
if let Some(template) = custom_template_path {
|
||||
handlebars
|
||||
.register_template_string("main.desktop", read_to_string(template)?)
|
||||
.map_err(Into::into)
|
||||
.context("Failed to setup custom handlebar template")?;
|
||||
.with_context(|| "Failed to setup custom handlebar template")?;
|
||||
} else {
|
||||
handlebars
|
||||
.register_template_string("main.desktop", include_str!("./main.desktop"))
|
||||
.map_err(Into::into)
|
||||
.context("Failed to setup default handlebar template")?;
|
||||
.with_context(|| "Failed to setup default handlebar template")?;
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{bundle::settings::Arch, error::ErrorExt, Settings};
|
||||
use crate::{bundle::settings::Arch, Settings};
|
||||
|
||||
use anyhow::Context;
|
||||
use rpm::{self, signature::pgp, Dependency, FileMode, FileOptions};
|
||||
use std::{
|
||||
env,
|
||||
@@ -20,10 +21,7 @@ use super::freedesktop;
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
let product_name = settings.product_name();
|
||||
let version = settings.version_string();
|
||||
let release = match settings.rpm().release.as_str() {
|
||||
"" => "1", // Considered the default. If left empty, you get file with "-.".
|
||||
v => v,
|
||||
};
|
||||
let release = settings.rpm().release.as_str();
|
||||
let epoch = settings.rpm().epoch;
|
||||
let arch = match settings.binary_arch() {
|
||||
Arch::X86_64 => "x86_64",
|
||||
@@ -34,7 +32,8 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
Arch::Riscv64 => "riscv64",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"Unsupported architecture: {target:?}"
|
||||
"Unsupported architecture: {:?}",
|
||||
target
|
||||
)));
|
||||
}
|
||||
};
|
||||
@@ -47,13 +46,10 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
let base_dir = settings.project_out_directory().join("bundle/rpm");
|
||||
let package_dir = base_dir.join(&package_base_name);
|
||||
if package_dir.exists() {
|
||||
fs::remove_dir_all(&package_dir).fs_context(
|
||||
"Failed to remove old package directory",
|
||||
package_dir.clone(),
|
||||
)?;
|
||||
fs::remove_dir_all(&package_dir)
|
||||
.with_context(|| format!("Failed to remove old {package_base_name}"))?;
|
||||
}
|
||||
fs::create_dir_all(&package_dir)
|
||||
.fs_context("Failed to create package directory", package_dir.clone())?;
|
||||
fs::create_dir_all(&package_dir)?;
|
||||
let package_path = base_dir.join(&package_name);
|
||||
|
||||
log::info!(action = "Bundling"; "{} ({})", package_name, package_path.display());
|
||||
@@ -239,5 +235,6 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
|
||||
let mut f = fs::File::create(&package_path)?;
|
||||
pkg.write(&mut f)?;
|
||||
|
||||
Ok(vec![package_path])
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -24,16 +24,15 @@
|
||||
|
||||
use super::{
|
||||
icon::create_icns_file,
|
||||
sign::{notarize, notarize_auth, notarize_without_stapling, sign, SignTarget},
|
||||
sign::{notarize, notarize_auth, sign, NotarizeAuthError, SignTarget},
|
||||
};
|
||||
use crate::{
|
||||
bundle::settings::PlistKind,
|
||||
error::{Context, ErrorExt, NotarizeAuthError},
|
||||
utils::{fs_utils, CommandExt},
|
||||
Error::GenericError,
|
||||
Settings,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
@@ -66,11 +65,15 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
|
||||
if app_bundle_path.exists() {
|
||||
fs::remove_dir_all(&app_bundle_path)
|
||||
.fs_context("failed to remove old app bundle", &app_bundle_path)?;
|
||||
.with_context(|| format!("Failed to remove old {}", app_product_name))?;
|
||||
}
|
||||
let bundle_directory = app_bundle_path.join("Contents");
|
||||
fs::create_dir_all(&bundle_directory)
|
||||
.fs_context("failed to create bundle directory", &bundle_directory)?;
|
||||
fs::create_dir_all(&bundle_directory).with_context(|| {
|
||||
format!(
|
||||
"Failed to create bundle directory at {:?}",
|
||||
bundle_directory
|
||||
)
|
||||
})?;
|
||||
|
||||
let resources_dir = bundle_directory.join("Resources");
|
||||
let bin_dir = bundle_directory.join("MacOS");
|
||||
@@ -104,11 +107,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
|
||||
copy_custom_files_to_bundle(&bundle_directory, settings)?;
|
||||
|
||||
if settings.no_sign() {
|
||||
log::warn!("Skipping signing due to --no-sign flag.",);
|
||||
} else if let Some(keychain) =
|
||||
super::sign::keychain(settings.macos().signing_identity.as_deref())?
|
||||
{
|
||||
if let Some(keychain) = super::sign::keychain(settings.macos().signing_identity.as_deref())? {
|
||||
// Sign frameworks and sidecar binaries first, per apple, signing must be done inside out
|
||||
// https://developer.apple.com/forums/thread/701514
|
||||
sign_paths.push(SignTarget {
|
||||
@@ -126,15 +125,11 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// notarization is required for distribution
|
||||
match notarize_auth() {
|
||||
Ok(auth) => {
|
||||
if settings.macos().skip_stapling {
|
||||
notarize_without_stapling(&keychain, app_bundle_path.clone(), &auth)?;
|
||||
} else {
|
||||
notarize(&keychain, app_bundle_path.clone(), &auth)?;
|
||||
}
|
||||
notarize(&keychain, app_bundle_path.clone(), &auth)?;
|
||||
}
|
||||
Err(e) => {
|
||||
if matches!(e, NotarizeAuthError::MissingTeamId) {
|
||||
return Err(e.into());
|
||||
return Err(anyhow::anyhow!("{e}").into());
|
||||
} else {
|
||||
log::warn!("skipping app notarization, {}", e.to_string());
|
||||
}
|
||||
@@ -165,7 +160,7 @@ fn copy_binaries_to_bundle(
|
||||
let bin_path = settings.binary_path(bin);
|
||||
let dest_path = dest_dir.join(bin.name());
|
||||
fs_utils::copy_file(&bin_path, &dest_path)
|
||||
.with_context(|| format!("Failed to copy binary from {bin_path:?}"))?;
|
||||
.with_context(|| format!("Failed to copy binary from {:?}", bin_path))?;
|
||||
paths.push(dest_path);
|
||||
}
|
||||
Ok(paths)
|
||||
@@ -174,12 +169,6 @@ fn copy_binaries_to_bundle(
|
||||
/// Copies user-defined files to the app under Contents.
|
||||
fn copy_custom_files_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> {
|
||||
for (contents_path, path) in settings.macos().files.iter() {
|
||||
if !path.try_exists()? {
|
||||
return Err(GenericError(format!(
|
||||
"Failed to copy {path:?} to {contents_path:?}. {path:?} does not exist."
|
||||
)));
|
||||
}
|
||||
|
||||
let contents_path = if contents_path.is_absolute() {
|
||||
contents_path.strip_prefix("/").unwrap()
|
||||
} else {
|
||||
@@ -187,14 +176,10 @@ fn copy_custom_files_to_bundle(bundle_directory: &Path, settings: &Settings) ->
|
||||
};
|
||||
if path.is_file() {
|
||||
fs_utils::copy_file(path, &bundle_directory.join(contents_path))
|
||||
.with_context(|| format!("Failed to copy file {path:?} to {contents_path:?}"))?;
|
||||
} else if path.is_dir() {
|
||||
fs_utils::copy_dir(path, &bundle_directory.join(contents_path))
|
||||
.with_context(|| format!("Failed to copy directory {path:?} to {contents_path:?}"))?;
|
||||
.with_context(|| format!("Failed to copy file {:?} to {:?}", path, contents_path))?;
|
||||
} else {
|
||||
return Err(GenericError(format!(
|
||||
"{path:?} is not a file or directory."
|
||||
)));
|
||||
fs_utils::copy_dir(path, &bundle_directory.join(contents_path))
|
||||
.with_context(|| format!("Failed to copy directory {:?} to {:?}", path, contents_path))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -229,15 +214,7 @@ fn create_info_plist(
|
||||
settings.bundle_identifier().into(),
|
||||
);
|
||||
plist.insert("CFBundleInfoDictionaryVersion".into(), "6.0".into());
|
||||
if let Some(bundle_name) = settings
|
||||
.macos()
|
||||
.bundle_name
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| settings.product_name())
|
||||
.into()
|
||||
{
|
||||
plist.insert("CFBundleName".into(), bundle_name.into());
|
||||
}
|
||||
plist.insert("CFBundleName".into(), settings.product_name().into());
|
||||
plist.insert("CFBundlePackageType".into(), "APPL".into());
|
||||
plist.insert(
|
||||
"CFBundleShortVersionString".into(),
|
||||
@@ -264,55 +241,6 @@ fn create_info_plist(
|
||||
}
|
||||
|
||||
if let Some(associations) = settings.file_associations() {
|
||||
let exported_associations = associations
|
||||
.iter()
|
||||
.filter_map(|association| {
|
||||
association.exported_type.as_ref().map(|exported_type| {
|
||||
let mut dict = plist::Dictionary::new();
|
||||
|
||||
dict.insert(
|
||||
"UTTypeIdentifier".into(),
|
||||
exported_type.identifier.clone().into(),
|
||||
);
|
||||
if let Some(description) = &association.description {
|
||||
dict.insert("UTTypeDescription".into(), description.clone().into());
|
||||
}
|
||||
if let Some(conforms_to) = &exported_type.conforms_to {
|
||||
dict.insert(
|
||||
"UTTypeConformsTo".into(),
|
||||
plist::Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),
|
||||
);
|
||||
}
|
||||
|
||||
let mut specification = plist::Dictionary::new();
|
||||
specification.insert(
|
||||
"public.filename-extension".into(),
|
||||
plist::Value::Array(
|
||||
association
|
||||
.ext
|
||||
.iter()
|
||||
.map(|s| s.to_string().into())
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
if let Some(mime_type) = &association.mime_type {
|
||||
specification.insert("public.mime-type".into(), mime_type.clone().into());
|
||||
}
|
||||
|
||||
dict.insert("UTTypeTagSpecification".into(), specification.into());
|
||||
|
||||
plist::Value::Dictionary(dict)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !exported_associations.is_empty() {
|
||||
plist.insert(
|
||||
"UTExportedTypeDeclarations".into(),
|
||||
plist::Value::Array(exported_associations),
|
||||
);
|
||||
}
|
||||
|
||||
plist.insert(
|
||||
"CFBundleDocumentTypes".into(),
|
||||
plist::Value::Array(
|
||||
@@ -320,27 +248,16 @@ fn create_info_plist(
|
||||
.iter()
|
||||
.map(|association| {
|
||||
let mut dict = plist::Dictionary::new();
|
||||
|
||||
if !association.ext.is_empty() {
|
||||
dict.insert(
|
||||
"CFBundleTypeExtensions".into(),
|
||||
plist::Value::Array(
|
||||
association
|
||||
.ext
|
||||
.iter()
|
||||
.map(|ext| ext.to_string().into())
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(content_types) = &association.content_types {
|
||||
dict.insert(
|
||||
"LSItemContentTypes".into(),
|
||||
plist::Value::Array(content_types.iter().map(|s| s.to_string().into()).collect()),
|
||||
);
|
||||
}
|
||||
|
||||
dict.insert(
|
||||
"CFBundleTypeExtensions".into(),
|
||||
plist::Value::Array(
|
||||
association
|
||||
.ext
|
||||
.iter()
|
||||
.map(|ext| ext.to_string().into())
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
dict.insert(
|
||||
"CFBundleTypeName".into(),
|
||||
association
|
||||
@@ -368,7 +285,6 @@ fn create_info_plist(
|
||||
plist::Value::Array(
|
||||
protocols
|
||||
.iter()
|
||||
.filter(|p| !p.schemes.is_empty())
|
||||
.map(|protocol| {
|
||||
let mut dict = plist::Dictionary::new();
|
||||
dict.insert(
|
||||
@@ -419,11 +335,8 @@ fn create_info_plist(
|
||||
plist.insert("NSAppTransportSecurity".into(), security.into());
|
||||
}
|
||||
|
||||
if let Some(user_plist) = &settings.macos().info_plist {
|
||||
let user_plist = match user_plist {
|
||||
PlistKind::Path(path) => plist::Value::from_file(path)?,
|
||||
PlistKind::Plist(value) => value.clone(),
|
||||
};
|
||||
if let Some(user_plist_path) = &settings.macos().info_plist_path {
|
||||
let user_plist = plist::Value::from_file(user_plist_path)?;
|
||||
if let Some(dict) = user_plist.into_dictionary() {
|
||||
for (key, value) in dict {
|
||||
plist.insert(key, value);
|
||||
@@ -438,7 +351,7 @@ fn create_info_plist(
|
||||
|
||||
// Copies the framework under `{src_dir}/{framework}.framework` to `{dest_dir}/{framework}.framework`.
|
||||
fn copy_framework_from(dest_dir: &Path, framework: &str, src_dir: &Path) -> crate::Result<bool> {
|
||||
let src_name = format!("{framework}.framework");
|
||||
let src_name = format!("{}.framework", framework);
|
||||
let src_path = src_dir.join(&src_name);
|
||||
if src_path.exists() {
|
||||
fs_utils::copy_dir(&src_path, &dest_dir.join(&src_name))?;
|
||||
@@ -455,12 +368,18 @@ fn copy_frameworks_to_bundle(
|
||||
) -> crate::Result<Vec<SignTarget>> {
|
||||
let mut paths = Vec::new();
|
||||
|
||||
let frameworks = settings.macos().frameworks.clone().unwrap_or_default();
|
||||
let frameworks = settings
|
||||
.macos()
|
||||
.frameworks
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
if frameworks.is_empty() {
|
||||
return Ok(paths);
|
||||
}
|
||||
let dest_dir = bundle_directory.join("Frameworks");
|
||||
fs::create_dir_all(&dest_dir).fs_context("failed to create Frameworks directory", &dest_dir)?;
|
||||
fs::create_dir_all(bundle_directory)
|
||||
.with_context(|| format!("Failed to create Frameworks directory at {:?}", dest_dir))?;
|
||||
for framework in frameworks.iter() {
|
||||
if framework.ends_with(".framework") {
|
||||
let src_path = PathBuf::from(framework);
|
||||
@@ -474,7 +393,10 @@ fn copy_frameworks_to_bundle(
|
||||
} else if framework.ends_with(".dylib") {
|
||||
let src_path = PathBuf::from(framework);
|
||||
if !src_path.exists() {
|
||||
return Err(GenericError(format!("Library not found: {framework}")));
|
||||
return Err(crate::Error::GenericError(format!(
|
||||
"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);
|
||||
@@ -485,8 +407,9 @@ fn copy_frameworks_to_bundle(
|
||||
});
|
||||
continue;
|
||||
} else if framework.contains('/') {
|
||||
return Err(GenericError(format!(
|
||||
"Framework path should have .framework extension: {framework}"
|
||||
return Err(crate::Error::GenericError(format!(
|
||||
"Framework path should have .framework extension: {}",
|
||||
framework
|
||||
)));
|
||||
}
|
||||
if let Some(home_dir) = dirs::home_dir() {
|
||||
@@ -503,8 +426,9 @@ fn copy_frameworks_to_bundle(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return Err(GenericError(format!(
|
||||
"Could not locate framework: {framework}"
|
||||
return Err(crate::Error::GenericError(format!(
|
||||
"Could not locate framework: {}",
|
||||
framework
|
||||
)));
|
||||
}
|
||||
Ok(paths)
|
||||
@@ -596,153 +520,3 @@ fn add_nested_code_sign_path(src_path: &Path, dest_path: &Path, sign_paths: &mut
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bundle::{BundleSettings, MacOsSettings, PackageSettings, SettingsBuilder};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Helper that builds a `Settings` instance and bundle directory for tests.
|
||||
/// It receives a mapping of bundle-relative paths to source paths and
|
||||
/// returns the generated bundle directory and settings.
|
||||
fn create_test_bundle(
|
||||
project_dir: &Path,
|
||||
files: HashMap<PathBuf, PathBuf>,
|
||||
) -> (PathBuf, crate::bundle::Settings) {
|
||||
let macos_settings = MacOsSettings {
|
||||
files,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let settings = SettingsBuilder::new()
|
||||
.project_out_directory(project_dir)
|
||||
.package_settings(PackageSettings {
|
||||
product_name: "TestApp".into(),
|
||||
version: "0.1.0".into(),
|
||||
description: "test".into(),
|
||||
homepage: None,
|
||||
authors: None,
|
||||
default_run: None,
|
||||
})
|
||||
.bundle_settings(BundleSettings {
|
||||
macos: macos_settings,
|
||||
..Default::default()
|
||||
})
|
||||
.target("x86_64-apple-darwin".into())
|
||||
.build()
|
||||
.expect("failed to build settings");
|
||||
|
||||
let bundle_dir = project_dir.join("TestApp.app/Contents");
|
||||
fs::create_dir_all(&bundle_dir).expect("failed to create bundle dir");
|
||||
|
||||
(bundle_dir, settings)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_custom_file_to_bundle_file() {
|
||||
let tmp_dir = tempfile::tempdir().expect("failed to create temp dir");
|
||||
|
||||
// Prepare a single file to copy.
|
||||
let src_file = tmp_dir.path().join("sample.txt");
|
||||
fs::write(&src_file, b"hello tauri").expect("failed to write sample file");
|
||||
|
||||
let files_map = HashMap::from([(PathBuf::from("Resources/sample.txt"), src_file.clone())]);
|
||||
|
||||
let (bundle_dir, settings) = create_test_bundle(tmp_dir.path(), files_map);
|
||||
|
||||
copy_custom_files_to_bundle(&bundle_dir, &settings)
|
||||
.expect("copy_custom_files_to_bundle failed");
|
||||
|
||||
let dest_file = bundle_dir.join("Resources/sample.txt");
|
||||
assert!(dest_file.exists() && dest_file.is_file());
|
||||
assert_eq!(fs::read_to_string(dest_file).unwrap(), "hello tauri");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_custom_file_to_bundle_dir() {
|
||||
let tmp_dir = tempfile::tempdir().expect("failed to create temp dir");
|
||||
|
||||
// Create a source directory with a nested file.
|
||||
let src_dir = tmp_dir.path().join("assets");
|
||||
fs::create_dir_all(&src_dir).expect("failed to create assets directory");
|
||||
let nested_file = src_dir.join("nested.txt");
|
||||
fs::write(&nested_file, b"nested").expect("failed to write nested file");
|
||||
|
||||
let files_map = HashMap::from([(PathBuf::from("MyAssets"), src_dir.clone())]);
|
||||
|
||||
let (bundle_dir, settings) = create_test_bundle(tmp_dir.path(), files_map);
|
||||
|
||||
copy_custom_files_to_bundle(&bundle_dir, &settings)
|
||||
.expect("copy_custom_files_to_bundle failed");
|
||||
|
||||
let dest_nested_file = bundle_dir.join("MyAssets/nested.txt");
|
||||
assert!(
|
||||
dest_nested_file.exists(),
|
||||
"{dest_nested_file:?} does not exist"
|
||||
);
|
||||
assert!(
|
||||
dest_nested_file.is_file(),
|
||||
"{dest_nested_file:?} is not a file"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(dest_nested_file).unwrap().trim(),
|
||||
"nested"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_custom_files_to_bundle_missing_source() {
|
||||
let tmp_dir = tempfile::tempdir().expect("failed to create temp dir");
|
||||
|
||||
// Intentionally reference a non-existent path.
|
||||
let missing_path = tmp_dir.path().join("does_not_exist.txt");
|
||||
|
||||
let files_map = HashMap::from([(PathBuf::from("Missing.txt"), missing_path)]);
|
||||
|
||||
let (bundle_dir, settings) = create_test_bundle(tmp_dir.path(), files_map);
|
||||
|
||||
let result = copy_custom_files_to_bundle(&bundle_dir, &settings);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.err().unwrap().to_string().contains("does not exist"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_custom_files_to_bundle_invalid_source() {
|
||||
let tmp_dir = tempfile::tempdir().expect("failed to create temp dir");
|
||||
|
||||
let files_map = HashMap::from([(PathBuf::from("Invalid.txt"), PathBuf::from("///"))]);
|
||||
|
||||
let (bundle_dir, settings) = create_test_bundle(tmp_dir.path(), files_map);
|
||||
|
||||
let result = copy_custom_files_to_bundle(&bundle_dir, &settings);
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains("Failed to copy directory"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_custom_files_to_bundle_dev_null() {
|
||||
let tmp_dir = tempfile::tempdir().expect("failed to create temp dir");
|
||||
|
||||
let files_map = HashMap::from([(PathBuf::from("Invalid.txt"), PathBuf::from("/dev/null"))]);
|
||||
|
||||
let (bundle_dir, settings) = create_test_bundle(tmp_dir.path(), files_map);
|
||||
|
||||
let result = copy_custom_files_to_bundle(&bundle_dir, &settings);
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains("is not a file or directory."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
use super::{app, icon::create_icns_file};
|
||||
use crate::{
|
||||
bundle::{settings::Arch, Bundle},
|
||||
error::{Context, ErrorExt},
|
||||
utils::CommandExt,
|
||||
PackageType, Settings,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use std::{
|
||||
env,
|
||||
fs::{self, write},
|
||||
@@ -48,7 +49,8 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
|
||||
Arch::Universal => "universal",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"Unsupported architecture: {target:?}"
|
||||
"Unsupported architecture: {:?}",
|
||||
target
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -57,7 +59,7 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
|
||||
let dmg_path = output_path.join(&dmg_name);
|
||||
|
||||
let product_name = settings.product_name();
|
||||
let bundle_file_name = format!("{product_name}.app");
|
||||
let bundle_file_name = format!("{}.app", product_name);
|
||||
let bundle_dir = settings.project_out_directory().join("bundle/macos");
|
||||
|
||||
let support_directory_path = output_path
|
||||
@@ -67,9 +69,10 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
|
||||
|
||||
for path in &[&support_directory_path, &output_path] {
|
||||
if path.exists() {
|
||||
fs::remove_dir_all(path).fs_context("failed to remove old dmg", path.to_path_buf())?;
|
||||
fs::remove_dir_all(path).with_context(|| format!("Failed to remove old {}", dmg_name))?;
|
||||
}
|
||||
fs::create_dir_all(path).fs_context("failed to create output directory", path.to_path_buf())?;
|
||||
fs::create_dir_all(path)
|
||||
.with_context(|| format!("Failed to create output directory at {:?}", path))?;
|
||||
}
|
||||
|
||||
// create paths for script
|
||||
@@ -193,7 +196,7 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
|
||||
// Sign DMG if needed
|
||||
// skipping self-signing DMGs https://github.com/tauri-apps/tauri/issues/12288
|
||||
let identity = settings.macos().signing_identity.as_deref();
|
||||
if !settings.no_sign() && identity != Some("-") {
|
||||
if identity != Some("-") {
|
||||
if let Some(keychain) = super::sign::keychain(identity)? {
|
||||
super::sign::sign(
|
||||
&keychain,
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
// explanation.
|
||||
|
||||
use crate::{
|
||||
error::{Context, ErrorExt},
|
||||
utils::{self, fs_utils},
|
||||
Settings,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use image::{codecs::png::PngDecoder, GenericImageView, ImageDecoder};
|
||||
|
||||
use std::{
|
||||
@@ -45,16 +45,16 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
|
||||
if app_bundle_path.exists() {
|
||||
fs::remove_dir_all(&app_bundle_path)
|
||||
.fs_context("failed to remove old app bundle", &app_bundle_path)?;
|
||||
.with_context(|| format!("Failed to remove old {}", app_product_name))?;
|
||||
}
|
||||
fs::create_dir_all(&app_bundle_path)
|
||||
.fs_context("failed to create bundle directory", &app_bundle_path)?;
|
||||
.with_context(|| format!("Failed to create bundle directory at {:?}", app_bundle_path))?;
|
||||
|
||||
for src in settings.resource_files() {
|
||||
let src = src?;
|
||||
let dest = app_bundle_path.join(tauri_utils::resources::resource_relpath(&src));
|
||||
fs_utils::copy_file(&src, &dest)
|
||||
.with_context(|| format!("Failed to copy resource file {src:?}"))?;
|
||||
.with_context(|| format!("Failed to copy resource file {:?}", src))?;
|
||||
}
|
||||
|
||||
let icon_filenames = generate_icon_files(&app_bundle_path, settings)
|
||||
@@ -65,7 +65,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
for bin in settings.binaries() {
|
||||
let bin_path = settings.binary_path(bin);
|
||||
fs_utils::copy_file(&bin_path, &app_bundle_path.join(bin.name()))
|
||||
.with_context(|| format!("Failed to copy binary from {bin_path:?}"))?;
|
||||
.with_context(|| format!("Failed to copy binary from {:?}", bin_path))?;
|
||||
}
|
||||
|
||||
Ok(vec![app_bundle_path])
|
||||
@@ -197,7 +197,7 @@ fn generate_info_plist(
|
||||
if !icon_filenames.is_empty() {
|
||||
writeln!(file, " <key>CFBundleIconFiles</key>\n <array>")?;
|
||||
for filename in icon_filenames {
|
||||
writeln!(file, " <string>{filename}</string>")?;
|
||||
writeln!(file, " <string>{}</string>", filename)?;
|
||||
}
|
||||
writeln!(file, " </array>")?;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
use std::{
|
||||
env::{var, var_os},
|
||||
ffi::OsString,
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::{error::NotarizeAuthError, Entitlements, Settings};
|
||||
use crate::Settings;
|
||||
|
||||
pub struct SignTarget {
|
||||
pub path: PathBuf,
|
||||
@@ -23,14 +23,11 @@ pub fn keychain(identity: Option<&str>) -> crate::Result<Option<tauri_macos_sign
|
||||
) {
|
||||
// import user certificate - useful for for CI build
|
||||
let keychain =
|
||||
tauri_macos_sign::Keychain::with_certificate(&certificate_encoded, &certificate_password)
|
||||
.map_err(Box::new)?;
|
||||
tauri_macos_sign::Keychain::with_certificate(&certificate_encoded, &certificate_password)?;
|
||||
if let Some(identity) = identity {
|
||||
let certificate_identity = keychain.signing_identity();
|
||||
if !certificate_identity.contains(identity) {
|
||||
return Err(crate::Error::GenericError(format!(
|
||||
"certificate from APPLE_CERTIFICATE \"{certificate_identity}\" environment variable does not match provided identity \"{identity}\""
|
||||
)));
|
||||
return Err(anyhow::anyhow!("certificate from APPLE_CERTIFICATE \"{certificate_identity}\" environment variable does not match provided identity \"{identity}\"").into());
|
||||
}
|
||||
}
|
||||
Ok(Some(keychain))
|
||||
@@ -51,23 +48,16 @@ pub fn sign(
|
||||
log::info!(action = "Signing"; "with identity \"{}\"", keychain.signing_identity());
|
||||
|
||||
for target in targets {
|
||||
let (entitlements_path, _temp_file) = match settings.macos().entitlements.as_ref() {
|
||||
Some(Entitlements::Path(path)) => (Some(path.to_owned()), None),
|
||||
Some(Entitlements::Plist(plist)) => {
|
||||
let mut temp_file = tempfile::NamedTempFile::new()?;
|
||||
plist::to_writer_xml(temp_file.as_file_mut(), &plist)?;
|
||||
(Some(temp_file.path().to_path_buf()), Some(temp_file))
|
||||
}
|
||||
None => (None, None),
|
||||
let entitlements_path = if target.is_an_executable {
|
||||
settings.macos().entitlements.as_ref().map(Path::new)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
keychain
|
||||
.sign(
|
||||
&target.path,
|
||||
entitlements_path.as_deref(),
|
||||
target.is_an_executable && settings.macos().hardened_runtime,
|
||||
)
|
||||
.map_err(Box::new)?;
|
||||
keychain.sign(
|
||||
&target.path,
|
||||
entitlements_path,
|
||||
target.is_an_executable && settings.macos().hardened_runtime,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -78,19 +68,17 @@ pub fn notarize(
|
||||
app_bundle_path: PathBuf,
|
||||
credentials: &tauri_macos_sign::AppleNotarizationCredentials,
|
||||
) -> crate::Result<()> {
|
||||
tauri_macos_sign::notarize(keychain, &app_bundle_path, credentials)
|
||||
.map_err(Box::new)
|
||||
.map_err(Into::into)
|
||||
tauri_macos_sign::notarize(keychain, &app_bundle_path, credentials).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn notarize_without_stapling(
|
||||
keychain: &tauri_macos_sign::Keychain,
|
||||
app_bundle_path: PathBuf,
|
||||
credentials: &tauri_macos_sign::AppleNotarizationCredentials,
|
||||
) -> crate::Result<()> {
|
||||
tauri_macos_sign::notarize_without_stapling(keychain, &app_bundle_path, credentials)
|
||||
.map_err(Box::new)
|
||||
.map_err(Into::into)
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum NotarizeAuthError {
|
||||
#[error(
|
||||
"The team ID is now required for notarization with app-specific password as authentication. Please set the `APPLE_TEAM_ID` environment variable. You can find the team ID in https://developer.apple.com/account#MembershipDetailsCard."
|
||||
)]
|
||||
MissingTeamId,
|
||||
#[error(transparent)]
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
pub fn notarize_auth() -> Result<tauri_macos_sign::AppleNotarizationCredentials, NotarizeAuthError>
|
||||
@@ -109,18 +97,10 @@ pub fn notarize_auth() -> Result<tauri_macos_sign::AppleNotarizationCredentials,
|
||||
}
|
||||
(Some(_apple_id), Some(_password), None) => Err(NotarizeAuthError::MissingTeamId),
|
||||
_ => {
|
||||
match (
|
||||
var_os("APPLE_API_KEY"),
|
||||
var_os("APPLE_API_ISSUER"),
|
||||
var("APPLE_API_KEY_PATH"),
|
||||
) {
|
||||
match (var_os("APPLE_API_KEY"), var_os("APPLE_API_ISSUER"), var("APPLE_API_KEY_PATH")) {
|
||||
(Some(key_id), Some(issuer), Ok(key_path)) => {
|
||||
Ok(tauri_macos_sign::AppleNotarizationCredentials::ApiKey {
|
||||
key_id,
|
||||
key: tauri_macos_sign::ApiKey::Path(key_path.into()),
|
||||
issuer,
|
||||
})
|
||||
}
|
||||
Ok(tauri_macos_sign::AppleNotarizationCredentials::ApiKey { key_id, key: tauri_macos_sign::ApiKey::Path( key_path.into()), issuer })
|
||||
},
|
||||
(Some(key_id), Some(issuer), Err(_)) => {
|
||||
let mut api_key_file_name = OsString::from("AuthKey_");
|
||||
api_key_file_name.push(&key_id);
|
||||
@@ -142,18 +122,12 @@ pub fn notarize_auth() -> Result<tauri_macos_sign::AppleNotarizationCredentials,
|
||||
}
|
||||
|
||||
if let Some(key_path) = key_path {
|
||||
Ok(tauri_macos_sign::AppleNotarizationCredentials::ApiKey {
|
||||
key_id,
|
||||
key: tauri_macos_sign::ApiKey::Path(key_path),
|
||||
issuer,
|
||||
})
|
||||
Ok(tauri_macos_sign::AppleNotarizationCredentials::ApiKey { key_id, key: tauri_macos_sign::ApiKey::Path(key_path), issuer })
|
||||
} else {
|
||||
Err(NotarizeAuthError::MissingApiKey {
|
||||
file_name: api_key_file_name.to_string_lossy().into_owned(),
|
||||
})
|
||||
Err(anyhow::anyhow!("could not find API key file. Please set the APPLE_API_KEY_PATH environment variables to the path to the {api_key_file_name:?} file").into())
|
||||
}
|
||||
}
|
||||
_ => Err(NotarizeAuthError::MissingCredentials),
|
||||
_ => Err(anyhow::anyhow!("no APPLE_ID & APPLE_PASSWORD & APPLE_TEAM_ID or APPLE_API_KEY & APPLE_API_ISSUER & APPLE_API_KEY_PATH environment variables found").into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::category::AppCategory;
|
||||
use crate::{bundle::platform::target_triple, error::Context, utils::fs_utils};
|
||||
use crate::{bundle::platform::target_triple, utils::fs_utils};
|
||||
use anyhow::Context;
|
||||
pub use tauri_utils::config::WebviewInstallMode;
|
||||
use tauri_utils::{
|
||||
config::{
|
||||
BundleType, DeepLinkProtocol, FileAssociation, NSISInstallerMode, NsisCompression,
|
||||
RpmCompression,
|
||||
},
|
||||
platform::Target as TargetPlatform,
|
||||
resources::{external_binaries, ResourcePaths},
|
||||
};
|
||||
|
||||
@@ -332,10 +332,6 @@ pub struct MacOsSettings {
|
||||
pub files: HashMap<PathBuf, PathBuf>,
|
||||
/// The version of the build that identifies an iteration of the bundle.
|
||||
pub bundle_version: Option<String>,
|
||||
/// The name of the build that identifies a string of the bundle.
|
||||
///
|
||||
/// If not set, defaults to the package's product name.
|
||||
pub bundle_name: Option<String>,
|
||||
/// A version string indicating the minimum MacOS version that the bundled app supports (e.g. `"10.11"`).
|
||||
/// If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.
|
||||
pub minimum_system_version: Option<String>,
|
||||
@@ -345,43 +341,16 @@ pub struct MacOsSettings {
|
||||
pub exception_domain: Option<String>,
|
||||
/// Code signing identity.
|
||||
pub signing_identity: Option<String>,
|
||||
/// Whether to wait for notarization to finish and `staple` the ticket onto the app.
|
||||
///
|
||||
/// Gatekeeper will look for stapled tickets to tell whether your app was notarized without
|
||||
/// reaching out to Apple's servers which is helpful in offline environments.
|
||||
///
|
||||
/// Enabling this option will also result in `tauri build` not waiting for notarization to finish
|
||||
/// which is helpful for the very first time your app is notarized as this can take multiple hours.
|
||||
/// On subsequent runs, it's recommended to disable this setting again.
|
||||
pub skip_stapling: bool,
|
||||
/// Preserve the hardened runtime version flag, see <https://developer.apple.com/documentation/security/hardened_runtime>
|
||||
///
|
||||
/// Settings this to `false` is useful when using an ad-hoc signature, making it less strict.
|
||||
pub hardened_runtime: bool,
|
||||
/// Provider short name for notarization.
|
||||
pub provider_short_name: Option<String>,
|
||||
/// Path or contents of the entitlements.plist file.
|
||||
pub entitlements: Option<Entitlements>,
|
||||
/// Path to the Info.plist file or raw plist value to merge with the bundle Info.plist.
|
||||
pub info_plist: Option<PlistKind>,
|
||||
}
|
||||
|
||||
/// Entitlements for macOS code signing.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Entitlements {
|
||||
/// Path to the entitlements.plist file.
|
||||
Path(PathBuf),
|
||||
/// Raw plist::Value.
|
||||
Plist(plist::Value),
|
||||
}
|
||||
|
||||
/// Plist format.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PlistKind {
|
||||
/// Path to a .plist file.
|
||||
Path(PathBuf),
|
||||
/// Raw plist value.
|
||||
Plist(plist::Value),
|
||||
pub entitlements: Option<String>,
|
||||
/// Path to the Info.plist file for the bundle.
|
||||
pub info_plist_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Configuration for a target language for the WiX build.
|
||||
@@ -589,12 +558,6 @@ pub struct WindowsSettings {
|
||||
pub sign_command: Option<CustomSignCommandSettings>,
|
||||
}
|
||||
|
||||
impl WindowsSettings {
|
||||
pub(crate) fn can_sign(&self) -> bool {
|
||||
self.sign_command.is_some() || self.certificate_thumbprint.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
mod _default {
|
||||
use super::*;
|
||||
@@ -797,12 +760,8 @@ pub struct Settings {
|
||||
bundle_settings: BundleSettings,
|
||||
/// the binaries to bundle.
|
||||
binaries: Vec<BundleBinary>,
|
||||
/// The target platform.
|
||||
target_platform: TargetPlatform,
|
||||
/// The target triple.
|
||||
target: String,
|
||||
/// Whether to disable code signing during the bundling process.
|
||||
no_sign: bool,
|
||||
}
|
||||
|
||||
/// A builder for [`Settings`].
|
||||
@@ -816,7 +775,6 @@ pub struct SettingsBuilder {
|
||||
binaries: Vec<BundleBinary>,
|
||||
target: Option<String>,
|
||||
local_tools_directory: Option<PathBuf>,
|
||||
no_sign: bool,
|
||||
}
|
||||
|
||||
impl SettingsBuilder {
|
||||
@@ -886,13 +844,6 @@ impl SettingsBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether to skip code signing.
|
||||
#[must_use]
|
||||
pub fn no_sign(mut self, no_sign: bool) -> Self {
|
||||
self.no_sign = no_sign;
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds a Settings from the CLI args.
|
||||
///
|
||||
/// Package settings will be read from Cargo.toml.
|
||||
@@ -904,7 +855,6 @@ impl SettingsBuilder {
|
||||
} else {
|
||||
target_triple()?
|
||||
};
|
||||
let target_platform = TargetPlatform::from_triple(&target);
|
||||
|
||||
Ok(Settings {
|
||||
log_level: self.log_level.unwrap_or(log::Level::Error),
|
||||
@@ -922,12 +872,10 @@ impl SettingsBuilder {
|
||||
.bundle_settings
|
||||
.external_bin
|
||||
.as_ref()
|
||||
.map(|bins| external_binaries(bins, &target, &target_platform)),
|
||||
.map(|bins| external_binaries(bins, &target)),
|
||||
..self.bundle_settings
|
||||
},
|
||||
target_platform,
|
||||
target,
|
||||
no_sign: self.no_sign,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -953,11 +901,6 @@ impl Settings {
|
||||
&self.target
|
||||
}
|
||||
|
||||
/// Returns the [`TargetPlatform`].
|
||||
pub fn target_platform(&self) -> &TargetPlatform {
|
||||
&self.target_platform
|
||||
}
|
||||
|
||||
/// 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") {
|
||||
@@ -986,6 +929,7 @@ impl Settings {
|
||||
.iter()
|
||||
.find(|bin| bin.main)
|
||||
.context("failed to find main binary, make sure you have a `package > default-run` in the Cargo.toml file")
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns the file name of the binary being bundled.
|
||||
@@ -995,6 +939,7 @@ impl Settings {
|
||||
.iter_mut()
|
||||
.find(|bin| bin.main)
|
||||
.context("failed to find main binary, make sure you have a `package > default-run` in the Cargo.toml file")
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns the file name of the binary being bundled.
|
||||
@@ -1005,27 +950,24 @@ impl Settings {
|
||||
.find(|bin| bin.main)
|
||||
.context("failed to find main binary, make sure you have a `package > default-run` in the Cargo.toml file")
|
||||
.map(|b| b.name())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns the path to the specified binary.
|
||||
pub fn binary_path(&self, binary: &BundleBinary) -> PathBuf {
|
||||
let target_os = self.target_platform();
|
||||
let target_os = self
|
||||
.target()
|
||||
.split('-')
|
||||
.nth(2)
|
||||
.unwrap_or(std::env::consts::OS);
|
||||
|
||||
let mut path = self.project_out_directory.join(binary.name());
|
||||
let path = self.project_out_directory.join(binary.name());
|
||||
|
||||
if matches!(target_os, TargetPlatform::Windows) {
|
||||
// Append the `.exe` extension without overriding the existing extensions
|
||||
let extension = if let Some(extension) = path.extension() {
|
||||
let mut extension = extension.to_os_string();
|
||||
extension.push(".exe");
|
||||
extension
|
||||
} else {
|
||||
"exe".into()
|
||||
};
|
||||
path.set_extension(extension);
|
||||
};
|
||||
|
||||
path
|
||||
if target_os == "windows" {
|
||||
path.with_extension("exe")
|
||||
} else {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of binaries to bundle.
|
||||
@@ -1043,13 +985,18 @@ impl Settings {
|
||||
///
|
||||
/// Fails if the host/target's native package type is not supported.
|
||||
pub fn package_types(&self) -> crate::Result<Vec<PackageType>> {
|
||||
let target_os = self.target_platform();
|
||||
let target_os = self
|
||||
.target
|
||||
.split('-')
|
||||
.nth(2)
|
||||
.unwrap_or(std::env::consts::OS)
|
||||
.replace("darwin", "macos");
|
||||
|
||||
let platform_types = match target_os {
|
||||
TargetPlatform::MacOS => vec![PackageType::MacOsBundle, PackageType::Dmg],
|
||||
TargetPlatform::Ios => vec![PackageType::IosBundle],
|
||||
TargetPlatform::Linux => vec![PackageType::Deb, PackageType::Rpm, PackageType::AppImage],
|
||||
TargetPlatform::Windows => vec![PackageType::WindowsMsi, PackageType::Nsis],
|
||||
let platform_types = match target_os.as_str() {
|
||||
"macos" => vec![PackageType::MacOsBundle, PackageType::Dmg],
|
||||
"ios" => vec![PackageType::IosBundle],
|
||||
"linux" => vec![PackageType::Deb, PackageType::Rpm, PackageType::AppImage],
|
||||
"windows" => vec![PackageType::WindowsMsi, PackageType::Nsis],
|
||||
os => {
|
||||
return Err(crate::Error::GenericError(format!(
|
||||
"Native {os} bundles not yet supported."
|
||||
@@ -1273,14 +1220,4 @@ impl Settings {
|
||||
pub fn updater(&self) -> Option<&UpdaterSettings> {
|
||||
self.bundle_settings.updater.as_ref()
|
||||
}
|
||||
|
||||
/// Whether to skip signing.
|
||||
pub fn no_sign(&self) -> bool {
|
||||
self.no_sign
|
||||
}
|
||||
|
||||
/// Set whether to skip signing.
|
||||
pub fn set_no_sign(&mut self, no_sign: bool) {
|
||||
self.no_sign = no_sign;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,10 @@ use crate::{
|
||||
},
|
||||
Bundle,
|
||||
},
|
||||
error::{Context, ErrorExt},
|
||||
utils::fs_utils,
|
||||
Settings,
|
||||
};
|
||||
use tauri_utils::{display_path, platform::Target as TargetPlatform};
|
||||
use tauri_utils::display_path;
|
||||
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
@@ -23,13 +22,19 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use zip::write::SimpleFileOptions;
|
||||
|
||||
// Build update
|
||||
pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<Vec<PathBuf>> {
|
||||
let target_os = settings.target_platform();
|
||||
let target_os = settings
|
||||
.target()
|
||||
.split('-')
|
||||
.nth(2)
|
||||
.unwrap_or(std::env::consts::OS)
|
||||
.replace("darwin", "macos");
|
||||
|
||||
if matches!(target_os, TargetPlatform::Windows) {
|
||||
if target_os == "windows" {
|
||||
return bundle_update_windows(settings, bundles);
|
||||
}
|
||||
|
||||
@@ -188,7 +193,7 @@ fn bundle_update_windows(settings: &Settings, bundles: &[Bundle]) -> crate::Resu
|
||||
p.push(c);
|
||||
(p, b)
|
||||
});
|
||||
let archived_path = archived_path.with_extension(format!("{bundle_name}.zip"));
|
||||
let archived_path = archived_path.with_extension(format!("{}.zip", bundle_name));
|
||||
|
||||
log::info!(action = "Bundling"; "{}", display_path(&archived_path));
|
||||
|
||||
@@ -216,9 +221,7 @@ pub fn create_zip(src_file: &Path, dst_file: &Path) -> crate::Result<PathBuf> {
|
||||
.unix_permissions(0o755);
|
||||
|
||||
zip.start_file(file_name.to_string_lossy(), options)?;
|
||||
let mut f =
|
||||
File::open(src_file).fs_context("failed to open updater ZIP file", src_file.to_path_buf())?;
|
||||
|
||||
let mut f = File::open(src_file)?;
|
||||
let mut buffer = Vec::new();
|
||||
f.read_to_end(&mut buffer)?;
|
||||
zip.write_all(&buffer)?;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod msi;
|
||||
|
||||
pub mod nsis;
|
||||
pub mod sign;
|
||||
|
||||
@@ -14,5 +13,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;
|
||||
|
||||
@@ -7,20 +7,20 @@ use crate::{
|
||||
bundle::{
|
||||
settings::{Arch, Settings},
|
||||
windows::{
|
||||
sign::{should_sign, try_sign},
|
||||
sign::try_sign,
|
||||
util::{
|
||||
download_webview2_bootstrapper, download_webview2_offline_installer,
|
||||
WIX_OUTPUT_FOLDER_NAME, WIX_UPDATER_OUTPUT_FOLDER_NAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
error::Context,
|
||||
utils::{
|
||||
fs_utils::copy_file,
|
||||
http_utils::{download_and_verify, extract_zip, HashAlgorithm},
|
||||
CommandExt,
|
||||
},
|
||||
};
|
||||
use anyhow::{bail, Context};
|
||||
use handlebars::{html_escape, to_json, Handlebars};
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -177,7 +177,7 @@ impl ResourceDirectory {
|
||||
directories.push_str(wix_string.as_str());
|
||||
}
|
||||
let wix_string = if self.name.is_empty() {
|
||||
format!("{files}{directories}")
|
||||
format!("{}{}", files, directories)
|
||||
} else {
|
||||
format!(
|
||||
r#"<Directory Id="I{id}" Name="{name}">{files}{directories}</Directory>"#,
|
||||
@@ -221,7 +221,8 @@ fn app_installer_output_path(
|
||||
Arch::AArch64 => "arm64",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"Unsupported architecture: {target:?}"
|
||||
"Unsupported architecture: {:?}",
|
||||
target
|
||||
)))
|
||||
}
|
||||
};
|
||||
@@ -279,40 +280,37 @@ fn clear_env_for_wix(cmd: &mut Command) {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_wix_version(version_str: &str) -> crate::Result<()> {
|
||||
fn validate_wix_version(version_str: &str) -> anyhow::Result<()> {
|
||||
let components = version_str
|
||||
.split('.')
|
||||
.flat_map(|c| c.parse::<u64>().ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if components.len() < 3 {
|
||||
crate::error::bail!(
|
||||
"app wix version should be in the format major.minor.patch.build (build is optional)"
|
||||
);
|
||||
}
|
||||
anyhow::ensure!(
|
||||
components.len() >= 3,
|
||||
"app wix version should be in the format major.minor.patch.build (build is optional)"
|
||||
);
|
||||
|
||||
if components[0] > 255 {
|
||||
crate::error::bail!("app version major number cannot be greater than 255");
|
||||
bail!("app version major number cannot be greater than 255");
|
||||
}
|
||||
if components[1] > 255 {
|
||||
crate::error::bail!("app version minor number cannot be greater than 255");
|
||||
bail!("app version minor number cannot be greater than 255");
|
||||
}
|
||||
if components[2] > 65535 {
|
||||
crate::error::bail!("app version patch number cannot be greater than 65535");
|
||||
bail!("app version patch number cannot be greater than 65535");
|
||||
}
|
||||
|
||||
if components.len() == 4 && components[3] > 65535 {
|
||||
crate::error::bail!("app version build number cannot be greater than 65535");
|
||||
bail!("app version build number cannot be greater than 65535");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// WiX requires versions to be numeric only in a `major.minor.patch.build` format
|
||||
fn convert_version(version_str: &str) -> crate::Result<String> {
|
||||
let version = semver::Version::parse(version_str)
|
||||
.map_err(Into::into)
|
||||
.context("invalid app version")?;
|
||||
fn convert_version(version_str: &str) -> anyhow::Result<String> {
|
||||
let version = semver::Version::parse(version_str).context("invalid app version")?;
|
||||
if !version.build.is_empty() {
|
||||
let build = version.build.parse::<u64>();
|
||||
if build.map(|b| b <= 65535).unwrap_or_default() {
|
||||
@@ -321,7 +319,7 @@ fn convert_version(version_str: &str) -> crate::Result<String> {
|
||||
version.major, version.minor, version.patch, version.build
|
||||
));
|
||||
} else {
|
||||
crate::error::bail!("optional build metadata in app version must be numeric-only and cannot be greater than 65535 for msi target");
|
||||
bail!("optional build metadata in app version must be numeric-only and cannot be greater than 65535 for msi target");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +331,7 @@ fn convert_version(version_str: &str) -> crate::Result<String> {
|
||||
version.major, version.minor, version.patch, version.pre
|
||||
));
|
||||
} else {
|
||||
crate::error::bail!("optional pre-release identifier in app version must be numeric-only and cannot be greater than 65535 for msi target");
|
||||
bail!("optional pre-release identifier in app version must be numeric-only and cannot be greater than 65535 for msi target");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +352,8 @@ fn run_candle(
|
||||
Arch::AArch64 => "arm64",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"unsupported architecture: {target:?}"
|
||||
"unsupported architecture: {:?}",
|
||||
target
|
||||
)))
|
||||
}
|
||||
};
|
||||
@@ -390,7 +389,11 @@ fn run_candle(
|
||||
cmd.arg(ext);
|
||||
}
|
||||
clear_env_for_wix(&mut cmd);
|
||||
cmd.args(&args).current_dir(cwd).output_ok()?;
|
||||
cmd
|
||||
.args(&args)
|
||||
.current_dir(cwd)
|
||||
.output_ok()
|
||||
.context("error running candle.exe")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -415,7 +418,11 @@ fn run_light(
|
||||
cmd.arg(ext);
|
||||
}
|
||||
clear_env_for_wix(&mut cmd);
|
||||
cmd.args(&args).current_dir(build_path).output_ok()?;
|
||||
cmd
|
||||
.args(&args)
|
||||
.current_dir(build_path)
|
||||
.output_ok()
|
||||
.context("error running light.exe")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -436,7 +443,8 @@ pub fn build_wix_app_installer(
|
||||
Arch::AArch64 => "arm64",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"unsupported architecture: {target:?}"
|
||||
"unsupported architecture: {:?}",
|
||||
target
|
||||
)))
|
||||
}
|
||||
};
|
||||
@@ -465,9 +473,10 @@ pub fn build_wix_app_installer(
|
||||
fs::create_dir_all(&output_path)?;
|
||||
|
||||
// when we're performing code signing, we'll sign some WiX DLLs, so we make a local copy
|
||||
let wix_toolset_path = if settings.windows().can_sign() {
|
||||
let wix_toolset_path = if settings.can_sign() {
|
||||
let wix_path = output_path.join("wix");
|
||||
crate::utils::fs_utils::copy_dir(wix_toolset_path, &wix_path)?;
|
||||
crate::utils::fs_utils::copy_dir(wix_toolset_path, &wix_path)
|
||||
.context("failed to copy wix directory")?;
|
||||
wix_path
|
||||
} else {
|
||||
wix_toolset_path.to_path_buf()
|
||||
@@ -697,9 +706,7 @@ pub fn build_wix_app_installer(
|
||||
.iter()
|
||||
.flat_map(|p| &p.schemes)
|
||||
.collect::<Vec<_>>();
|
||||
if !schemes.is_empty() {
|
||||
data.insert("deep_link_protocols", to_json(schemes));
|
||||
}
|
||||
data.insert("deep_link_protocols", to_json(schemes));
|
||||
}
|
||||
|
||||
if let Some(path) = custom_template_path {
|
||||
@@ -767,7 +774,7 @@ pub fn build_wix_app_installer(
|
||||
let mut extensions = Vec::new();
|
||||
for cap in extension_regex.captures_iter(&fragment) {
|
||||
let path = wix_toolset_path.join(format!("Wix{}.dll", &cap[1]));
|
||||
if settings.windows().can_sign() {
|
||||
if settings.can_sign() {
|
||||
try_sign(&path, settings)?;
|
||||
}
|
||||
extensions.push(path);
|
||||
@@ -781,7 +788,7 @@ pub fn build_wix_app_installer(
|
||||
fragment_extensions.insert(wix_toolset_path.join("WixUtilExtension.dll"));
|
||||
|
||||
// sign default extensions
|
||||
if settings.windows().can_sign() {
|
||||
if settings.can_sign() {
|
||||
for path in &fragment_extensions {
|
||||
try_sign(path, settings)?;
|
||||
}
|
||||
@@ -838,7 +845,7 @@ pub fn build_wix_app_installer(
|
||||
|
||||
let locale_contents = locale_contents.replace(
|
||||
"</WixLocalization>",
|
||||
&format!("{unset_locale_strings}</WixLocalization>"),
|
||||
&format!("{}</WixLocalization>", unset_locale_strings),
|
||||
);
|
||||
let locale_path = output_path.join("locale.wxl");
|
||||
{
|
||||
@@ -875,7 +882,7 @@ pub fn build_wix_app_installer(
|
||||
)?;
|
||||
fs::rename(&msi_output_path, &msi_path)?;
|
||||
|
||||
if settings.windows().can_sign() {
|
||||
if settings.can_sign() {
|
||||
try_sign(&msi_path, settings)?;
|
||||
}
|
||||
|
||||
@@ -984,7 +991,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
|
||||
}
|
||||
added_resources.push(resource_path.clone());
|
||||
|
||||
if settings.windows().can_sign() && should_sign(&resource_path)? {
|
||||
if settings.can_sign() {
|
||||
try_sign(&resource_path, settings)?;
|
||||
}
|
||||
|
||||
@@ -1072,7 +1079,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
if !added_resources.iter().any(|r| r.ends_with(&relative_path)) {
|
||||
if settings.windows().can_sign() {
|
||||
if settings.can_sign() {
|
||||
try_sign(resource_path, settings)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ VIAddVersionKey "ProductVersion" "${VERSION}"
|
||||
|
||||
; Handle install mode, `perUser`, `perMachine` or `both`
|
||||
!if "${INSTALLMODE}" == "perMachine"
|
||||
RequestExecutionLevel admin
|
||||
RequestExecutionLevel highest
|
||||
!endif
|
||||
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
@@ -618,7 +618,7 @@ Section Install
|
||||
!insertmacro NSIS_HOOK_PREINSTALL
|
||||
!endif
|
||||
|
||||
!insertmacro CheckIfAppIsRunning "${MAINBINARYNAME}.exe" "${PRODUCTNAME}"
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
|
||||
; Copy main executable
|
||||
File "${MAINBINARYSRCPATH}"
|
||||
@@ -755,7 +755,7 @@ Section Uninstall
|
||||
!insertmacro NSIS_HOOK_PREUNINSTALL
|
||||
!endif
|
||||
|
||||
!insertmacro CheckIfAppIsRunning "${MAINBINARYNAME}.exe" "${PRODUCTNAME}"
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
|
||||
; Delete the app directory and its content from disk
|
||||
; Copy main executable
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_ARABIC} "إضافة أو إزالة المكونات"
|
||||
LangString alreadyInstalled ${LANG_ARABIC} "التطبيق مثبت بالفعل"
|
||||
LangString alreadyInstalledLong ${LANG_ARABIC} "${PRODUCTNAME} ${VERSION} مثبت بالفعل. قم باختيار العملية التى تريدها ثم اضغط على التالى."
|
||||
LangString appRunning ${LANG_ARABIC} "{{product_name}} مازال يعمل! من فضلك، قم بإغلاق التطبيق أولاً ثم حاول مرة أخرى."
|
||||
LangString appRunningOkKill ${LANG_ARABIC} "{{product_name}} مازال يعمل!$\nاضغط OK لإغلاقه"
|
||||
LangString appRunning ${LANG_ARABIC} "${PRODUCTNAME} مازال يعمل! من فضلك، قم بإغلاق التطبيق أولاً ثم حاول مرة أخرى."
|
||||
LangString appRunningOkKill ${LANG_ARABIC} "${PRODUCTNAME} مازال يعمل!$\nاضغط OK لإغلاقه"
|
||||
LangString chooseMaintenanceOption ${LANG_ARABIC} "قم باختيار نوع الصيانة التى تريدها."
|
||||
LangString choowHowToInstall ${LANG_ARABIC} "قم باختيار طريقة تنصيب ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_ARABIC} "اضف اختصار على سطح المكتب"
|
||||
LangString dontUninstall ${LANG_ARABIC} "عدم إزالة"
|
||||
LangString dontUninstallDowngrade ${LANG_ARABIC} "عدم إزالة (التخفيض بدون إزالة غير مسموح لهذا المثبت)"
|
||||
LangString failedToKillApp ${LANG_ARABIC} "فشل فى غلف {{product_name}}. من فضلك، قم بإغلاق التطبيق أولاً ثم حاول مرة أخرى."
|
||||
LangString failedToKillApp ${LANG_ARABIC} "فشل فى غلف ${PRODUCTNAME}. من فضلك، قم بإغلاق التطبيق أولاً ثم حاول مرة أخرى."
|
||||
LangString installingWebview2 ${LANG_ARABIC} "تنصيب WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_ARABIC} "يوجد نسخة جديدة من ${PRODUCTNAME} مثبتة بالغعل! لا ينصح بتنصيب نسخة اقدم من النسخة الحالية. اذا مازلت ترغب فى تنصيب النسخة الأقدم، فينصح بإزالة النسخة الحالية أولاً. قم باختيار العملية التى تريدها ثم اضغط على التالى للاستمرار."
|
||||
LangString older ${LANG_ARABIC} "أقدم"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_BULGARIAN} "Добавяне/Преинсталиране на компоненти"
|
||||
LangString alreadyInstalled ${LANG_BULGARIAN} "Вече инсталиран"
|
||||
LangString alreadyInstalledLong ${LANG_BULGARIAN} "${PRODUCTNAME} ${VERSION} е вече е инсталиран. Изберете операцията, която искате да извършите и натиснете Напред, за да продължите."
|
||||
LangString appRunning ${LANG_BULGARIAN} "{{product_name}} е отворен! Моля, затворете го първо и опитайте отново."
|
||||
LangString appRunningOkKill ${LANG_BULGARIAN} "{{product_name}} е отворен!$\nНатиснете ОК, за да го затворите."
|
||||
LangString appRunning ${LANG_BULGARIAN} "${PRODUCTNAME} е отворен! Моля, затворете го първо и опитайте отново."
|
||||
LangString appRunningOkKill ${LANG_BULGARIAN} "${PRODUCTNAME} е отворен!$\nНатиснете ОК, за да го затворите."
|
||||
LangString chooseMaintenanceOption ${LANG_BULGARIAN} "Изберете опция за поддръжка."
|
||||
LangString choowHowToInstall ${LANG_BULGARIAN} "Изберете как искате да инсталирате ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_BULGARIAN} "Създайте пряк път на работния плот"
|
||||
LangString dontUninstall ${LANG_BULGARIAN} "Не деинсталирайте"
|
||||
LangString dontUninstallDowngrade ${LANG_BULGARIAN} "Не деинсталирайте (Понижаването без деинсталация е забранено за този инсталатор)"
|
||||
LangString failedToKillApp ${LANG_BULGARIAN} "Неуспешно прекратяване на {{product_name}}. Моля, затворете го първо и опитайте отново."
|
||||
LangString failedToKillApp ${LANG_BULGARIAN} "Неуспешно прекратяване на ${PRODUCTNAME}. Моля, затворете го първо и опитайте отново."
|
||||
LangString installingWebview2 ${LANG_BULGARIAN} "Инсталиране на WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_BULGARIAN} "Вече е инсталирана по-нова версия на ${PRODUCTNAME}! Не се препоръчва да инсталирате по-стара версия. Ако наистина желаете да инсталирате тази по-стара версия, по-добре е да деинсталирате текущата версия първо. Изберете операцията, която искате да извършите и натиснете Напред, за да продължите."
|
||||
LangString older ${LANG_BULGARIAN} "по-стара"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_DUTCH} "(Her)installeer componenten"
|
||||
LangString alreadyInstalled ${LANG_DUTCH} "Al geïnstalleerd"
|
||||
LangString alreadyInstalledLong ${LANG_DUTCH} "${PRODUCTNAME} ${VERSION} is al geïnstalleerd. Kies een van de volgende opties en klik op Volgende om door te gaan."
|
||||
LangString appRunning ${LANG_DUTCH} "{{product_name}} is geopend! Sluit het programma eerst en probeer het dan opnieuw."
|
||||
LangString appRunningOkKill ${LANG_DUTCH} "{{product_name}} is geopend!$\nKlik op OK om het te stoppen."
|
||||
LangString appRunning ${LANG_DUTCH} "${PRODUCTNAME} is geopend! Sluit het programma eerst en probeer het dan opnieuw."
|
||||
LangString appRunningOkKill ${LANG_DUTCH} "${PRODUCTNAME} is geopend!$\nKlik op OK om het te stoppen."
|
||||
LangString chooseMaintenanceOption ${LANG_DUTCH} "Kies de onderhoudsoptie die u wilt uitvoeren."
|
||||
LangString choowHowToInstall ${LANG_DUTCH} "Kies hoe u ${PRODUCTNAME} wilt installeren."
|
||||
LangString createDesktop ${LANG_DUTCH} "Maak een snelkoppeling aan op het bureaublad"
|
||||
LangString dontUninstall ${LANG_DUTCH} "Deïnstalleer niet"
|
||||
LangString dontUninstallDowngrade ${LANG_DUTCH} "Deïnstalleer niet (Downgraden zonder deïnstalleren is uitgeschakeld voor deze installer)"
|
||||
LangString failedToKillApp ${LANG_DUTCH} "Het is niet gelukt {{product_name}} te stoppen. Sluit het eerst zelf en probeer het dan nog een keer"
|
||||
LangString failedToKillApp ${LANG_DUTCH} "Het is niet gelukt ${PRODUCTNAME} te stoppen. Sluit het eerst zelf en probeer het dan nog een keer"
|
||||
LangString installingWebview2 ${LANG_DUTCH} "WebView2 wordt geïnstalleerd..."
|
||||
LangString newerVersionInstalled ${LANG_DUTCH} "Een nieuwere versie van ${PRODUCTNAME} is al geïnstalleerd! Het word niet aangeraden om een oudere versie te installeren. Als u echt deze oudere versie wilt installeren, kunt u beter de huidige versie eerst deïnstalleren. Kies een van de volgende opties en klik op Volgende om door te gaan."
|
||||
LangString older ${LANG_DUTCH} "oudere"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_ENGLISH} "Add/Reinstall components"
|
||||
LangString alreadyInstalled ${LANG_ENGLISH} "Already Installed"
|
||||
LangString alreadyInstalledLong ${LANG_ENGLISH} "${PRODUCTNAME} ${VERSION} is already installed. Select the operation you want to perform and click Next to continue."
|
||||
LangString appRunning ${LANG_ENGLISH} "{{product_name}} is running! Please close it first then try again."
|
||||
LangString appRunningOkKill ${LANG_ENGLISH} "{{product_name}} is running!$\nClick OK to kill it"
|
||||
LangString appRunning ${LANG_ENGLISH} "${PRODUCTNAME} is running! Please close it first then try again."
|
||||
LangString appRunningOkKill ${LANG_ENGLISH} "${PRODUCTNAME} is running!$\nClick OK to kill it"
|
||||
LangString chooseMaintenanceOption ${LANG_ENGLISH} "Choose the maintenance option to perform."
|
||||
LangString choowHowToInstall ${LANG_ENGLISH} "Choose how you want to install ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_ENGLISH} "Create desktop shortcut"
|
||||
LangString dontUninstall ${LANG_ENGLISH} "Do not uninstall"
|
||||
LangString dontUninstallDowngrade ${LANG_ENGLISH} "Do not uninstall (Downgrading without uninstall is disabled for this installer)"
|
||||
LangString failedToKillApp ${LANG_ENGLISH} "Failed to kill {{product_name}}. Please close it first then try again"
|
||||
LangString failedToKillApp ${LANG_ENGLISH} "Failed to kill ${PRODUCTNAME}. Please close it first then try again"
|
||||
LangString installingWebview2 ${LANG_ENGLISH} "Installing WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_ENGLISH} "A newer version of ${PRODUCTNAME} is already installed! It is not recommended that you install an older version. If you really want to install this older version, it's better to uninstall the current version first. Select the operation you want to perform and click Next to continue."
|
||||
LangString older ${LANG_ENGLISH} "older"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_FRENCH} "Ajouter/Réinstaller un composant."
|
||||
LangString alreadyInstalled ${LANG_FRENCH} "Déja installé."
|
||||
LangString alreadyInstalledLong ${LANG_FRENCH} "${PRODUCTNAME} ${VERSION} est déja installé. Sélectionnez l'opération que vous souhaitez effectuer, puis cliquez sur Suivant pour continuer."
|
||||
LangString appRunning ${LANG_FRENCH} "{{product_name}} est en cours d'exécution. Veuillez fermer l'application avant de réessayer."
|
||||
LangString appRunningOkKill ${LANG_FRENCH} "{{product_name}} est en cours d'exécution.$\nCliquez sur OK pour fermer l'application."
|
||||
LangString appRunning ${LANG_FRENCH} "${PRODUCTNAME} est en cours d'exécution. Veuillez fermer l'application avant de réessayer."
|
||||
LangString appRunningOkKill ${LANG_FRENCH} "${PRODUCTNAME} est en cours d'exécution.$\nCliquez sur OK pour fermer l'application."
|
||||
LangString chooseMaintenanceOption ${LANG_FRENCH} "Veuillez choisir l'option de maintenance à effectuer."
|
||||
LangString choowHowToInstall ${LANG_FRENCH} "Veuillez choisir l'emplacement d'installation de ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_FRENCH} "Créer un raccourci sur le bureau."
|
||||
LangString dontUninstall ${LANG_FRENCH} "Ne pas désinstaller"
|
||||
LangString dontUninstallDowngrade ${LANG_FRENCH} "Ne pas désinstaller (revenir à une ancienne version sans désinstallation est désactivé pour cet installateur)"
|
||||
LangString failedToKillApp ${LANG_FRENCH} "La fermeture de {{product_name}} a échoué. Veuillez fermer l'application et réessayer."
|
||||
LangString failedToKillApp ${LANG_FRENCH} "La fermeture de ${PRODUCTNAME} a échoué. Veuillez fermer l'application et réessayer."
|
||||
LangString installingWebview2 ${LANG_FRENCH} "Installation de WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_FRENCH} "Une version plus récente de ${PRODUCTNAME} est déja installée. Il n'est pas recommandé d'installer une ancienne version. Si vous souhaitez installer cette ancienne version, il est conseillé de désinstaller la version courante en premier. Veuillez sélectionner l'opération que vous souhaitez effectuer, puis cliquez sur Suivant pour continer."
|
||||
LangString older ${LANG_FRENCH} "ancien"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_GERMAN} "Komponenten hinzufügen/neu installieren"
|
||||
LangString alreadyInstalled ${LANG_GERMAN} "Bereits installiert"
|
||||
LangString alreadyInstalledLong ${LANG_GERMAN} "${PRODUCTNAME} ${VERSION} ist bereits installiert. Wählen Sie den gewünschten Vorgang aus und klicken Sie auf Weiter, um fortzufahren."
|
||||
LangString appRunning ${LANG_GERMAN} "{{product_name}} wird ausgeführt! Bitte schließen Sie es zuerst und versuchen Sie es dann erneut."
|
||||
LangString appRunningOkKill ${LANG_GERMAN} "{{product_name}} läuft! $\nKlicken Sie auf OK, um es zu beenden"
|
||||
LangString appRunning ${LANG_GERMAN} "${PRODUCTNAME} wird ausgeführt! Bitte schließen Sie es zuerst und versuchen Sie es dann erneut."
|
||||
LangString appRunningOkKill ${LANG_GERMAN} "${PRODUCTNAME} läuft! $\nKlicken Sie auf OK, um es zu beenden"
|
||||
LangString chooseMaintenanceOption ${LANG_GERMAN} "Wählen Sie die auszuführende Wartungsoption."
|
||||
LangString choowHowToInstall ${LANG_GERMAN} "Wählen Sie, wie Sie ${PRODUCTNAME} installieren möchten."
|
||||
LangString createDesktop ${LANG_GERMAN} "Desktop-Verknüpfung erstellen"
|
||||
LangString dontUninstall ${LANG_GERMAN} "Nicht deinstallieren"
|
||||
LangString dontUninstallDowngrade ${LANG_GERMAN} "Nicht deinstallieren (Downgrading ohne Deinstallation ist für dieses Installationsprogramm deaktiviert)"
|
||||
LangString failedToKillApp ${LANG_GERMAN} "Failed to kill {{product_name}}. Bitte schließen Sie es zuerst und versuchen Sie es dann erneut"
|
||||
LangString failedToKillApp ${LANG_GERMAN} "Failed to kill ${PRODUCTNAME}. Bitte schließen Sie es zuerst und versuchen Sie es dann erneut"
|
||||
LangString installingWebview2 ${LANG_GERMAN} "Installiere WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_GERMAN} "Eine neuere Version von ${PRODUCTNAME} ist bereits installiert! Es wird nicht empfohlen, eine ältere Version zu installieren. Wenn Sie diese ältere Version wirklich installieren wollen, ist es besser, die aktuelle Version zuerst zu deinstallieren. Wählen Sie den gewünschten Vorgang aus und klicken Sie auf Weiter, um fortzufahren."
|
||||
LangString älter ${LANG_GERMAN} "älter"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_HEBREW} "הוסף או התקן מחדש"
|
||||
LangString alreadyInstalled ${LANG_HEBREW} "כבר מותקן"
|
||||
LangString alreadyInstalledLong ${LANG_HEBREW} "${PRODUCTNAME} ${VERSION} כבר מותקן. בחר את הפעולה שברצונך לבצע ולחץ על הבא כדי להמשיך."
|
||||
LangString appRunning ${LANG_HEBREW} "{{product_name}} פועל! נא לסגור אותו ולנסות שוב."
|
||||
LangString appRunningOkKill ${LANG_HEBREW} "{{product_name}} פועל!$\nלחץ אישור כדי לסגור אותו."
|
||||
LangString appRunning ${LANG_HEBREW} "${PRODUCTNAME} פועל! נא לסגור אותו ולנסות שוב."
|
||||
LangString appRunningOkKill ${LANG_HEBREW} "${PRODUCTNAME} פועל!$\nלחץ אישור כדי לסגור אותו."
|
||||
LangString chooseMaintenanceOption ${LANG_HEBREW} "בחר את פעולת התחזוקה לביצוע"
|
||||
LangString choowHowToInstall ${LANG_HEBREW} "בחר איך תרצה להתקין את ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_HEBREW} "צור קיצור דרך בשולחן העבודה"
|
||||
LangString dontUninstall ${LANG_HEBREW} "אל תסיר"
|
||||
LangString dontUninstallDowngrade ${LANG_HEBREW} "אל תסיר (התקנת גרסה ישנה ללא הסרת הגרסה הנוכחית מושעית עבור התקנה זו)"
|
||||
LangString failedToKillApp ${LANG_HEBREW} "עצירת {{product_name}} נכשלה. נא לסגור את היישום ולנסות שוב."
|
||||
LangString failedToKillApp ${LANG_HEBREW} "עצירת ${PRODUCTNAME} נכשלה. נא לסגור את היישום ולנסות שוב."
|
||||
LangString installingWebview2 ${LANG_HEBREW} "מתקין את WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_HEBREW} "גרסה חדשה יותר של ${PRODUCTNAME} כבר מותקנת! לא מומלץ להתקין גרסה ישנה. אם בכל זאת תרצה להתקין את הגרסה הזו, מומלץ קודם להסיר את הגרסה הנוכחית. בחר את הפעולה שברצונך לבצע ולחץ הבא להמשך."
|
||||
LangString older ${LANG_HEBREW} "ישנה"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_ITALIAN} "Aggiungi/Reinstalla componenti"
|
||||
LangString alreadyInstalled ${LANG_ITALIAN} "Già installato"
|
||||
LangString alreadyInstalledLong ${LANG_ITALIAN} "${PRODUCTNAME} ${VERSION} è già installato. Seleziona l'operazione che vuoi eseguire e clicca Avanti per continuare."
|
||||
LangString appRunning ${LANG_ITALIAN} "{{product_name}} è in esecuzione! Chiudi e poi riprova."
|
||||
LangString appRunningOkKill ${LANG_ITALIAN} "{{product_name}} è in esecuzione!$\nSeleziona OK per chiuderlo"
|
||||
LangString appRunning ${LANG_ITALIAN} "${PRODUCTNAME} è in esecuzione! Chiudi e poi riprova."
|
||||
LangString appRunningOkKill ${LANG_ITALIAN} "${PRODUCTNAME} è in esecuzione!$\nSeleziona OK per chiuderlo"
|
||||
LangString chooseMaintenanceOption ${LANG_ITALIAN} "Seleziona l'operazione di manutenzione da eseguire."
|
||||
LangString choowHowToInstall ${LANG_ITALIAN} "Seleziona come vuoi installare ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_ITALIAN} "Crea scorciatoia sul Desktop"
|
||||
LangString dontUninstall ${LANG_ITALIAN} "Non disinstallare"
|
||||
LangString dontUninstallDowngrade ${LANG_ITALIAN} "Non disinstallare (Il downgrade senza la disinstallazione è disabilitato per questo installer)"
|
||||
LangString failedToKillApp ${LANG_ITALIAN} "Impossibile chiudere {{product_name}}. Chiudi e poi riprova"
|
||||
LangString failedToKillApp ${LANG_ITALIAN} "Impossibile chiudere ${PRODUCTNAME}. Chiudi e poi riprova"
|
||||
LangString installingWebview2 ${LANG_ITALIAN} "Installando WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_ITALIAN} "Una versione più recente di ${PRODUCTNAME} è già installata! Non è consigliato installare una versione più vecchia. Se vuoi comunque procedere, è meglio prima disinstallare la versione corrente. Seleziona l'operazione che vuoi eseguire e clicca Avanti per continuare."
|
||||
LangString older ${LANG_ITALIAN} "più vecchia"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_JAPANESE} "コンポーネントの追加・再インストール"
|
||||
LangString alreadyInstalled ${LANG_JAPANESE} "既にインストールされています"
|
||||
LangString alreadyInstalledLong ${LANG_JAPANESE} "${PRODUCTNAME} ${VERSION} は既にインストールされています。実行したい操作を選択し、「次へ」をクリックして続行します。"
|
||||
LangString appRunning ${LANG_JAPANESE} "{{product_name}} は動作中です。動作中のプログラムを終了し、もう一度やり直してください。"
|
||||
LangString appRunningOkKill ${LANG_JAPANESE} "{{product_name}} は動作中です。$\n「OK」を押すと動作中のプログラムを終了します。"
|
||||
LangString appRunning ${LANG_JAPANESE} "${PRODUCTNAME} は動作中です。動作中のプログラムを終了し、もう一度やり直してください。"
|
||||
LangString appRunningOkKill ${LANG_JAPANESE} "${PRODUCTNAME} は動作中です。$\n「OK」を押すと動作中のプログラムを終了します。"
|
||||
LangString chooseMaintenanceOption ${LANG_JAPANESE} "メンテナンスオプションを選択して実行します。"
|
||||
LangString choowHowToInstall ${LANG_JAPANESE} "${PRODUCTNAME} のインストール方法を選択してください。"
|
||||
LangString createDesktop ${LANG_JAPANESE} "デスクトップショートカットを作成する"
|
||||
LangString dontUninstall ${LANG_JAPANESE} "アンインストールしない"
|
||||
LangString dontUninstallDowngrade ${LANG_JAPANESE} "アンインストールしない (このインストーラーでは、アンインストールをせずにダウングレードすることはできません)"
|
||||
LangString failedToKillApp ${LANG_JAPANESE} "{{product_name}} の終了に失敗しました。動作中のプログラムを終了し、もう一度やり直してください。"
|
||||
LangString failedToKillApp ${LANG_JAPANESE} "${PRODUCTNAME} の終了に失敗しました。動作中のプログラムを終了し、もう一度やり直してください。"
|
||||
LangString installingWebview2 ${LANG_JAPANESE} "WebView2 をインストール中です..."
|
||||
LangString newerVersionInstalled ${LANG_JAPANESE} "既に新しいバージョンの ${PRODUCTNAME} がインストールされています。古いバージョンをインストールすることは推奨されません。どうしてもこの旧バージョンをインストールしたい場合は、先に現行バージョンをアンインストールしておく方がよいでしょう。実行したい操作を選択し、「次へ」をクリックして続行します。"
|
||||
LangString older ${LANG_JAPANESE} "旧"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_KOREAN} "컴포넌트 추가 및 재설치"
|
||||
LangString alreadyInstalled ${LANG_KOREAN} "이미 설치되어 있습니다"
|
||||
LangString alreadyInstalledLong ${LANG_KOREAN} "${PRODUCTNAME} ${VERSION}이(가) 이미 설치되어 있습니다. 수행하고자 하는 작업을 선택하고 '다음'을 클릭하여 계속합니다."
|
||||
LangString appRunning ${LANG_KOREAN} "{{product_name}}이(가) 실행 중입니다! 먼저 닫은 후 다시 시도하세요."
|
||||
LangString appRunningOkKill ${LANG_KOREAN} "{{product_name}}이(가) 실행 중입니다!$\n'OK'를 누르면 실행 중인 프로그램을 종료합니다."
|
||||
LangString appRunning ${LANG_KOREAN} "${PRODUCTNAME}이(가) 실행 중입니다! 먼저 닫은 후 다시 시도하세요."
|
||||
LangString appRunningOkKill ${LANG_KOREAN} "${PRODUCTNAME}이(가) 실행 중입니다!$\n'OK'를 누르면 실행 중인 프로그램을 종료합니다."
|
||||
LangString chooseMaintenanceOption ${LANG_KOREAN} "수행하려는 관리 옵션을 선택합니다."
|
||||
LangString choowHowToInstall ${LANG_KOREAN} "${PRODUCTNAME}의 설치 방법을 선택하세요.."
|
||||
LangString createDesktop ${LANG_KOREAN} "바탕화면 바로가기 만들기"
|
||||
LangString dontUninstall ${LANG_KOREAN} "제거하지 않기"
|
||||
LangString dontUninstallDowngrade ${LANG_KOREAN} "제거하지 않기 (이 설치 프로그램에서는 제거하지 않고 다운그레이드할 수 없습니다.)"
|
||||
LangString failedToKillApp ${LANG_KOREAN} "{{product_name}}을(를) 종료하지 못했습니다. 먼저 닫은 후 다시 시도하세요."
|
||||
LangString failedToKillApp ${LANG_KOREAN} "${PRODUCTNAME}을(를) 종료하지 못했습니다. 먼저 닫은 후 다시 시도하세요."
|
||||
LangString installingWebview2 ${LANG_KOREAN} "WebView2를 설치하는 중입니다..."
|
||||
LangString newerVersionInstalled ${LANG_KOREAN} "${PRODUCTNAME}의 최신 버전이 이미 설치되어 있습니다! 이전 버전을 설치하지 않는 것이 좋습니다. 이 이전 버전을 꼭 설치하려면 먼저 현재 버전을 제거하는 것이 좋습니다. 수행하려는 작업을 선택하고 '다음'을 클릭하여 계속합니다."
|
||||
LangString older ${LANG_KOREAN} "구"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_PERSIAN} "اضافه کردن/نصب مجدد کامپونتت"
|
||||
LangString alreadyInstalled ${LANG_PERSIAN} "قبلا نصب شده است"
|
||||
LangString alreadyInstalledLong ${LANG_PERSIAN} "${PRODUCTNAME} ${VERSION} قبلا نصب شده است. عملیات مدنظر را انتخاب کنید و بروی بعدی کلیک کنید."
|
||||
LangString appRunning ${LANG_PERSIAN} "{{product_name}} در حال اجر می باشد ! لطفا اول الان را ببندید و دوباره تلاش کنید"
|
||||
LangString appRunningOkKill ${LANG_PERSIAN} "{{product_name}} در حال اجرا می باشد!$\nبرای از بین بردن اوکی را انتخاب کنید"
|
||||
LangString appRunning ${LANG_PERSIAN} "${PRODUCTNAME} در حال اجر می باشد ! لطفا اول الان را ببندید و دوباره تلاش کنید"
|
||||
LangString appRunningOkKill ${LANG_PERSIAN} "${PRODUCTNAME} در حال اجرا می باشد!$\nبرای از بین بردن اوکی را انتخاب کنید"
|
||||
LangString chooseMaintenanceOption ${LANG_PERSIAN} "عملیات نگهداری مدنظر را برای اجرا انتخاب کنید"
|
||||
LangString choowHowToInstall ${LANG_PERSIAN} "نحوه نصب ${PRODUCTNAME} را انتخاب کنید"
|
||||
LangString createDesktop ${LANG_PERSIAN} "ایجاد میانبر دسکتاپ"
|
||||
LangString dontUninstall ${LANG_PERSIAN} "حذف نکنید"
|
||||
LangString dontUninstallDowngrade ${LANG_PERSIAN} "حذف نکنید (تنزل ورژن بدون حذف برای نصب کننده غیرفعال است)"
|
||||
LangString failedToKillApp ${LANG_PERSIAN} "{{product_name}} قابل کشته شدن نیست. اول آن را ببندید و دوباره تلاش کنید"
|
||||
LangString failedToKillApp ${LANG_PERSIAN} "${PRODUCTNAME} قابل کشته شدن نیست. اول آن را ببندید و دوباره تلاش کنید"
|
||||
LangString installingWebview2 ${LANG_PERSIAN} "در حال نصب WebView2 ..."
|
||||
LangString newerVersionInstalled ${LANG_PERSIAN} "ورژن جدید ${PRODUCTNAME} قبلا نصب شده است! نصب ورژن قدیمی تر به هیچ عنوان پیشنهاد نمی شود. اگر از این بابت اطمینان دارید , بهتر است ورژن فعلی را حذف کنید. عملیات مدنظر را انتخاب کنید و بروی بعدی کلیک کنید."
|
||||
LangString older ${LANG_PERSIAN} "قدیمی تر"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_PORTUGUESE} "Adicionar/Reinstalar componentes"
|
||||
LangString alreadyInstalled ${LANG_PORTUGUESE} "Já instalado"
|
||||
LangString alreadyInstalledLong ${LANG_PORTUGUESE} "${PRODUCTNAME} ${VERSION} já está instalado. Selecione a operação que deseja realizar e clique em Seguinte para continuar."
|
||||
LangString appRunning ${LANG_PORTUGUESE} "{{product_name}} está em execução! Por favor, feche-o primeiro e tente novamente."
|
||||
LangString appRunningOkKill ${LANG_PORTUGUESE} "{{product_name}} está em execução!$\nClique em OK para encerrá-lo."
|
||||
LangString appRunning ${LANG_PORTUGUESE} "${PRODUCTNAME} está em execução! Por favor, feche-o primeiro e tente novamente."
|
||||
LangString appRunningOkKill ${LANG_PORTUGUESE} "${PRODUCTNAME} está em execução!$\nClique em OK para encerrá-lo."
|
||||
LangString chooseMaintenanceOption ${LANG_PORTUGUESE} "Escolha a opção de manutenção a realizar."
|
||||
LangString choowHowToInstall ${LANG_PORTUGUESE} "Escolha como deseja instalar o ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_PORTUGUESE} "Criar atalho no ambiente de trabalho"
|
||||
LangString dontUninstall ${LANG_PORTUGUESE} "Não desinstalar"
|
||||
LangString dontUninstallDowngrade ${LANG_PORTUGUESE} "Não desinstalar (Instalar uma versão anterior sem desinstalar está desativado neste instalador)"
|
||||
LangString failedToKillApp ${LANG_PORTUGUESE} "Falha ao encerrar {{product_name}}. Por favor, feche-o primeiro e tente novamente."
|
||||
LangString failedToKillApp ${LANG_PORTUGUESE} "Falha ao encerrar ${PRODUCTNAME}. Por favor, feche-o primeiro e tente novamente."
|
||||
LangString installingWebview2 ${LANG_PORTUGUESE} "A instalar WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_PORTUGUESE} "Uma versão mais recente do ${PRODUCTNAME} já está instalada! Não é recomendada a instalação de uma versão mais antiga. Se realmente deseja instalar esta versão mais antiga, é melhor desinstalar a versão atual primeiro. Selecione a operação que deseja realizar e clique em Seguinte para continuar."
|
||||
LangString older ${LANG_PORTUGUESE} "mais antiga"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_PORTUGUESEBR} "Adicionar/Reinstalar componentes"
|
||||
LangString alreadyInstalled ${LANG_PORTUGUESEBR} "Já instalado"
|
||||
LangString alreadyInstalledLong ${LANG_PORTUGUESEBR} "${PRODUCTNAME} ${VERSION} já está instalado. Selecione a operação que deseja realizar e clique Próximo para continuar."
|
||||
LangString appRunning ${LANG_PORTUGUESEBR} "{{product_name}} está aberto! Por favor feche a janela dele e tente novamente."
|
||||
LangString appRunningOkKill ${LANG_PORTUGUESEBR} "{{product_name}} está aberto!$\nClique OK para fechar ele."
|
||||
LangString appRunning ${LANG_PORTUGUESEBR} "${PRODUCTNAME} está aberto! Por favor feche a janela dele e tente novamente."
|
||||
LangString appRunningOkKill ${LANG_PORTUGUESEBR} "${PRODUCTNAME} está aberto!$\nClique OK para fechar ele."
|
||||
LangString chooseMaintenanceOption ${LANG_PORTUGUESEBR} "Escolha a opção de manutenção a realizar."
|
||||
LangString choowHowToInstall ${LANG_PORTUGUESEBR} "Escolha como deseja instalar ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_PORTUGUESEBR} "Criar atalho na área de trabalho"
|
||||
LangString dontUninstall ${LANG_PORTUGUESEBR} "Não desinstalar"
|
||||
LangString dontUninstallDowngrade ${LANG_PORTUGUESEBR} "Não desinstalar (Instalar versão anterior sem desinstalar está desabilitado nesse instalador)"
|
||||
LangString failedToKillApp ${LANG_PORTUGUESEBR} "Falha ao fechar {{product_name}}. Por favor feche a janela dele primeiro e tente novamente"
|
||||
LangString failedToKillApp ${LANG_PORTUGUESEBR} "Falha ao fechar ${PRODUCTNAME}. Por favor feche a janela dele primeiro e tente novamente"
|
||||
LangString installingWebview2 ${LANG_PORTUGUESEBR} "Instalando WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_PORTUGUESEBR} "Uma nova versão do ${PRODUCTNAME} já está instalado! Não é recomendado instalar uma versão anterior. Se realmente deseja instalar essa versão antiga, é recomendado desinstalar a versão atual primeirl. Selecione a operação que deseja executare clique Próximo para continuar."
|
||||
LangString older ${LANG_PORTUGUESEBR} "mais antiga"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_RUSSIAN} "Добавить/Переустановить компоненты"
|
||||
LangString alreadyInstalled ${LANG_RUSSIAN} "Уже установлено"
|
||||
LangString alreadyInstalledLong ${LANG_RUSSIAN} "${PRODUCTNAME} ${VERSION} уже установлен. Выберите действие, которое вы хотите выполнить и нажмите Далее для продолжения."
|
||||
LangString appRunning ${LANG_RUSSIAN} "{{product_name}} запущен! Пожалуйста, закройте приложение и попробуйте еще раз."
|
||||
LangString appRunningOkKill ${LANG_RUSSIAN} "{{product_name}} запущен!$\nНажмите OK чтобы закрыть приложение"
|
||||
LangString appRunning ${LANG_RUSSIAN} "${PRODUCTNAME} запущен! Пожалуйста, закройте приложение и попробуйте еще раз."
|
||||
LangString appRunningOkKill ${LANG_RUSSIAN} "${PRODUCTNAME} запущен!$\nНажмите OK чтобы закрыть приложение"
|
||||
LangString chooseMaintenanceOption ${LANG_RUSSIAN} "Выберите действие, которое вы хотите выполнить."
|
||||
LangString choowHowToInstall ${LANG_RUSSIAN} "Выберите, как вы хотите установить ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_RUSSIAN} "Добавить ярлык на рабочий стол"
|
||||
LangString dontUninstall ${LANG_RUSSIAN} "Не удалять"
|
||||
LangString dontUninstallDowngrade ${LANG_RUSSIAN} "Не удалять (Установка более ранних версий без удаления невозможна)"
|
||||
LangString failedToKillApp ${LANG_RUSSIAN} "Не удалось закрыть {{product_name}}. Пожалуйста, закройте приложение и попробуйте еще раз"
|
||||
LangString failedToKillApp ${LANG_RUSSIAN} "Не удалось закрыть ${PRODUCTNAME}. Пожалуйста, закройте приложение и попробуйте еще раз"
|
||||
LangString installingWebview2 ${LANG_RUSSIAN} "Установка WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_RUSSIAN} "Более новая версия ${PRODUCTNAME} уже установлена! Не рекомендуется устанавливать более раннюю версию. Если вы действительно хотите установить эту версию, рекомендуется сначала удалить текущую. Выберите действие, которое вы хотите выполнить и нажмите Далее для продолжения."
|
||||
LangString older ${LANG_RUSSIAN} "Более ранняя"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_SIMPCHINESE} "添加/重新安装组件"
|
||||
LangString alreadyInstalled ${LANG_SIMPCHINESE} "已安装"
|
||||
LangString alreadyInstalledLong ${LANG_SIMPCHINESE} "${PRODUCTNAME} ${VERSION} 已经安装了。选择你想要执行的操作后点击下一步以继续。"
|
||||
LangString appRunning ${LANG_SIMPCHINESE} "{{product_name}} 正在运行!请关闭后再试。"
|
||||
LangString appRunningOkKill ${LANG_SIMPCHINESE} "{{product_name}} 正在运行!$\n点击确定以终止运行。"
|
||||
LangString appRunning ${LANG_SIMPCHINESE} "${PRODUCTNAME} 正在运行!请关闭后再试。"
|
||||
LangString appRunningOkKill ${LANG_SIMPCHINESE} "${PRODUCTNAME} 正在运行!$\n点击确定以终止运行。"
|
||||
LangString chooseMaintenanceOption ${LANG_SIMPCHINESE} "选择要执行的维护操作。"
|
||||
LangString choowHowToInstall ${LANG_SIMPCHINESE} "选择你想要安装 ${PRODUCTNAME} 的方式。"
|
||||
LangString createDesktop ${LANG_SIMPCHINESE} "创建桌面快捷方式"
|
||||
LangString dontUninstall ${LANG_SIMPCHINESE} "请勿卸载"
|
||||
LangString dontUninstallDowngrade ${LANG_SIMPCHINESE} "请勿卸载(此安装程序禁止未卸载就进行版本降级的操作)"
|
||||
LangString failedToKillApp ${LANG_SIMPCHINESE} "无法终止 {{product_name}}。请关闭后再试。"
|
||||
LangString failedToKillApp ${LANG_SIMPCHINESE} "无法终止 ${PRODUCTNAME}。请关闭后再试。"
|
||||
LangString installingWebview2 ${LANG_SIMPCHINESE} "正在安装 WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_SIMPCHINESE} "有一个更新版本的 ${PRODUCTNAME} 已经安装了!不推荐你安装旧的版本。如果你真的想要安装这个旧的版本,推荐先卸载当前版本。选择你想要执行的操作后点击下一步以继续。"
|
||||
LangString older ${LANG_SIMPCHINESE} "旧的"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_SPANISH} "Añadir o reinstalar componentes"
|
||||
LangString alreadyInstalled ${LANG_SPANISH} "Ya está instalado"
|
||||
LangString alreadyInstalledLong ${LANG_SPANISH} "${PRODUCTNAME} ${VERSION} ya está instalado. Seleccione la operación que desee realizar y pulse Siguiente para continuar."
|
||||
LangString appRunning ${LANG_SPANISH} "¡{{product_name}} está abierto! Por favor ciérrelo e intente de nuevo."
|
||||
LangString appRunningOkKill ${LANG_SPANISH} "¡{{product_name}} está abierto!$\nPulse Aceptar para cerrarlo."
|
||||
LangString appRunning ${LANG_SPANISH} "¡${PRODUCTNAME} está abierto! Por favor ciérrelo e intente de nuevo."
|
||||
LangString appRunningOkKill ${LANG_SPANISH} "¡${PRODUCTNAME} está abierto!$\nPulse Aceptar para cerrarlo."
|
||||
LangString chooseMaintenanceOption ${LANG_SPANISH} "Elija la operación de mantenimiento que desee realizar."
|
||||
LangString choowHowToInstall ${LANG_SPANISH} "Elija cómo desea instalar ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_SPANISH} "Crear acceso directo en el escritorio"
|
||||
LangString dontUninstall ${LANG_SPANISH} "No desinstalar"
|
||||
LangString dontUninstallDowngrade ${LANG_SPANISH} "No desinstalar (Disminuir la versión sin desinstalar está deshabilitado para este instalador)"
|
||||
LangString failedToKillApp ${LANG_SPANISH} "No se ha podido cerrar {{product_name}}. Por favor ciérrelo e intente de nuevo."
|
||||
LangString failedToKillApp ${LANG_SPANISH} "No se ha podido cerrar ${PRODUCTNAME}. Por favor ciérrelo e intente de nuevo."
|
||||
LangString installingWebview2 ${LANG_SPANISH} "Instalando WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_SPANISH} "Ya está instalada una versión más reciente de ${PRODUCTNAME}. No se recomienda que instale una versión anterior. Si realmente desea instalar esta versión anterior, es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar."
|
||||
LangString older ${LANG_SPANISH} "anterior"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_SPANISHINTERNATIONAL} "Añadir o reinstalar componentes"
|
||||
LangString alreadyInstalled ${LANG_SPANISHINTERNATIONAL} "Ya está instalado"
|
||||
LangString alreadyInstalledLong ${LANG_SPANISHINTERNATIONAL} "${PRODUCTNAME} ${VERSION} ya está instalado. Seleccione la operación que desee realizar y pulse Siguiente para continuar."
|
||||
LangString appRunning ${LANG_SPANISHINTERNATIONAL} "¡{{product_name}} está abierto! Por favor ciérrelo e intente de nuevo."
|
||||
LangString appRunningOkKill ${LANG_SPANISHINTERNATIONAL} "¡{{product_name}} está abierto!$\nPulse Aceptar para cerrarlo."
|
||||
LangString appRunning ${LANG_SPANISHINTERNATIONAL} "¡${PRODUCTNAME} está abierto! Por favor ciérrelo e intente de nuevo."
|
||||
LangString appRunningOkKill ${LANG_SPANISHINTERNATIONAL} "¡${PRODUCTNAME} está abierto!$\nPulse Aceptar para cerrarlo."
|
||||
LangString chooseMaintenanceOption ${LANG_SPANISHINTERNATIONAL} "Elija la operación de mantenimiento que desee realizar."
|
||||
LangString choowHowToInstall ${LANG_SPANISHINTERNATIONAL} "Elija cómo desea instalar ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_SPANISHINTERNATIONAL} "Crear acceso directo en el escritorio"
|
||||
LangString dontUninstall ${LANG_SPANISHINTERNATIONAL} "No desinstalar"
|
||||
LangString dontUninstallDowngrade ${LANG_SPANISHINTERNATIONAL} "No desinstalar (Disminuir la versión sin desinstalar está deshabilitado para este instalador)"
|
||||
LangString failedToKillApp ${LANG_SPANISHINTERNATIONAL} "No se ha podido cerrar {{product_name}}. Por favor ciérrelo e intente de nuevo."
|
||||
LangString failedToKillApp ${LANG_SPANISHINTERNATIONAL} "No se ha podido cerrar ${PRODUCTNAME}. Por favor ciérrelo e intente de nuevo."
|
||||
LangString installingWebview2 ${LANG_SPANISHINTERNATIONAL} "Instalando WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_SPANISHINTERNATIONAL} "Ya está instalada una versión más reciente de ${PRODUCTNAME}. No se recomienda que instale una versión anterior. Si realmente desea instalar esta versión anterior, es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar."
|
||||
LangString older ${LANG_SPANISHINTERNATIONAL} "anterior"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_SWEDISH} "Lägg till/Installera om komponenter"
|
||||
LangString alreadyInstalled ${LANG_SWEDISH}} "Redan installerad"
|
||||
LangString alreadyInstalledLong ${LANG_SWEDISH}} "${PRODUCTNAME} ${VERSION} är redan installerad. Välj åtgärd och klicka på Nästa för att fortsätta."
|
||||
LangString appRunning ${LANG_SWEDISH} "{{product_name}} körs! Stäng det först och försök igen."
|
||||
LangString appRunningOkKill ${LANG_SWEDISH} "{{product_name}} körs!$\nKlicka på OK för att avsluta det."
|
||||
LangString appRunning ${LANG_SWEDISH} "${PRODUCTNAME} körs! Stäng det först och försök igen."
|
||||
LangString appRunningOkKill ${LANG_SWEDISH} "${PRODUCTNAME} körs!$\nKlicka på OK för att avsluta det."
|
||||
LangString chooseMaintenanceOption ${LANG_SWEDISH} "Välj underhållsåtgärd."
|
||||
LangString choowHowToInstall ${LANG_SWEDISH} "Välj hur du vill installera ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_SWEDISH} "Skapa genväg på skrivbordet"
|
||||
LangString dontUninstall ${LANG_SWEDISH} "Avinstallera inte"
|
||||
LangString dontUninstallDowngrade ${LANG_SWEDISH} "Avinstallera inte (nedgradering utan avinstallation är inaktiverad för den här installationsprogrammet)"
|
||||
LangString failedToKillApp ${LANG_SWEDISH} "Kunde inte avsluta {{product_name}}. Stäng det först och försök igen."
|
||||
LangString failedToKillApp ${LANG_SWEDISH} "Kunde inte avsluta ${PRODUCTNAME}. Stäng det först och försök igen."
|
||||
LangString installingWebview2 ${LANG_SWEDISH} "Installerar WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_SWEDISH} "En nyare version av ${PRODUCTNAME} är redan installerad! Det rekommenderas inte att installera en äldre version. Om du verkligen vill installera denna äldre version är det bättre att avinstallera den nuvarande versionen först. Välj åtgärd och klicka på Nästa för att fortsätta."
|
||||
LangString older ${LANG_SWEDISH} "äldre"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_TRADCHINESE} "增加或重新安裝元件"
|
||||
LangString alreadyInstalled ${LANG_TRADCHINESE} "已安裝"
|
||||
LangString alreadyInstalledLong ${LANG_TRADCHINESE} "${PRODUCTNAME} ${VERSION} 已經安裝了。選擇你想要進行的操作並且點選下一步。"
|
||||
LangString appRunning ${LANG_TRADCHINESE} "{{product_name}} 正在執行中!請先關閉再進行嘗試。"
|
||||
LangString appRunningOkKill ${LANG_TRADCHINESE} "{{product_name}} 正在執行中!點選確定後終止。"
|
||||
LangString appRunning ${LANG_TRADCHINESE} "${PRODUCTNAME} 正在執行中!請先關閉再進行嘗試。"
|
||||
LangString appRunningOkKill ${LANG_TRADCHINESE} "${PRODUCTNAME} 正在執行中!點選確定後終止。"
|
||||
LangString chooseMaintenanceOption ${LANG_TRADCHINESE} "請選擇你要進行的維護選項。"
|
||||
LangString choowHowToInstall ${LANG_TRADCHINESE} "選擇你要如何安裝 ${PRODUCTNAME}。"
|
||||
LangString createDesktop ${LANG_TRADCHINESE} "建立桌面捷徑"
|
||||
LangString dontUninstall ${LANG_TRADCHINESE} "請勿解除安裝"
|
||||
LangString dontUninstallDowngrade ${LANG_TRADCHINESE} "請勿解除安裝(本安裝程式不允許未解除安裝就進行版本降低的操作)"
|
||||
LangString failedToKillApp ${LANG_TRADCHINESE} "無法終止 {{product_name}}。請先關閉再進行嘗試。"
|
||||
LangString failedToKillApp ${LANG_TRADCHINESE} "無法終止 ${PRODUCTNAME}。請先關閉再進行嘗試。"
|
||||
LangString installingWebview2 ${LANG_TRADCHINESE} "WebView2 安裝中..."
|
||||
LangString newerVersionInstalled ${LANG_TRADCHINESE} "已安裝更新版本的 ${PRODUCTNAME}!不建議安裝舊版。如果真的想要安裝舊版的話,最好先解除安裝現在的版本。選擇你想要進行的操作後再進行下一步。"
|
||||
LangString older ${LANG_TRADCHINESE} "舊版"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_TURKISH} "Bileşen Ekle/Yeniden Yükle"
|
||||
LangString alreadyInstalled ${LANG_TURKISH} "Daha Önceden Yüklenmiş"
|
||||
LangString alreadyInstalledLong ${LANG_TURKISH} "${PRODUCTNAME} ${VERSION} daha önceden yüklenmiş. Gerçekleştirmek istediğiniz işlemi seçin ve devam etmek için İleri'ye tıklayın."
|
||||
LangString appRunning ${LANG_TURKISH} "{{product_name}} çalışır durumda! Lütfen önce uygulamayı kapatın ve sonra tekrar deneyin."
|
||||
LangString appRunningOkKill ${LANG_TURKISH} "{{product_name}} çalışır durumda!$\nUygulamayı sonlandırmak için Tamam'a tıklayın."
|
||||
LangString appRunning ${LANG_TURKISH} "${PRODUCTNAME} çalışır durumda! Lütfen önce uygulamayı kapatın ve sonra tekrar deneyin."
|
||||
LangString appRunningOkKill ${LANG_TURKISH} "${PRODUCTNAME} çalışır durumda!$\nUygulamayı sonlandırmak için Tamam'a tıklayın."
|
||||
LangString chooseMaintenanceOption ${LANG_TURKISH} "Gerçekleştirmek istediğiniz bakım seçeneğini belirleyin."
|
||||
LangString choowHowToInstall ${LANG_TURKISH} "${PRODUCTNAME} uygulamasını nasıl yüklemek istediğinizi seçin."
|
||||
LangString createDesktop ${LANG_TURKISH} "Masaüstü kısayolu oluştur"
|
||||
LangString dontUninstall ${LANG_TURKISH} "Kaldırma işlemini gerçekleştirme"
|
||||
LangString dontUninstallDowngrade ${LANG_TURKISH} "Kaldırma işlemini gerçekleştirme (Kaldırma işlemi yapmadan sürüm düşürme bu yükleyici için devre dışı bırakılmıştır)"
|
||||
LangString failedToKillApp ${LANG_TURKISH} "{{product_name}} sonlandırılamadı. Lütfen önce kapatın sonra tekrar deneyin."
|
||||
LangString failedToKillApp ${LANG_TURKISH} "${PRODUCTNAME} sonlandırılamadı. Lütfen önce kapatın sonra tekrar deneyin."
|
||||
LangString installingWebview2 ${LANG_TURKISH} "WebView2 yükleniyor..."
|
||||
LangString newerVersionInstalled ${LANG_TURKISH} "${PRODUCTNAME} uygulamasının daha yeni bir sürümü zaten yüklü! Daha eski bir sürümü yüklemeniz önerilmez. Bu eski sürümü gerçekten yüklemek istiyorsanız, önce mevcut sürümü kaldırmanız daha uygundur. Gerçekleştirmek istediğiniz işlemi seçin ve devam etmek için İleri'ye tıklayın."
|
||||
LangString older ${LANG_TURKISH} "daha eski"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
LangString addOrReinstall ${LANG_UKRAINIAN} "Додати/Перевстановити компоненти"
|
||||
LangString alreadyInstalled ${LANG_UKRAINIAN} "Вже встановлено"
|
||||
LangString alreadyInstalledLong ${LANG_UKRAINIAN} "${PRODUCTNAME} ${VERSION} вже встановлено. Виберіть дію, яку ви хочете виконати, і натисніть Далі, щоб продовжити."
|
||||
LangString appRunning ${LANG_UKRAINIAN} "{{product_name}} запущено! Будь ласка, спочатку закрийте його, а потім спробуйте ще раз."
|
||||
LangString appRunningOkKill ${LANG_UKRAINIAN} "{{product_name}} запущено!$\nНатисніть ОК, щоб примусово закрити його"
|
||||
LangString appRunning ${LANG_UKRAINIAN} "${PRODUCTNAME} запущено! Будь ласка, спочатку закрийте його, а потім спробуйте ще раз."
|
||||
LangString appRunningOkKill ${LANG_UKRAINIAN} "${PRODUCTNAME} запущено!$\nНатисніть ОК, щоб примусово закрити його"
|
||||
LangString chooseMaintenanceOption ${LANG_UKRAINIAN} "Виберіть дію, яку треба виконати."
|
||||
LangString choowHowToInstall ${LANG_UKRAINIAN} "Виберіть, як ви хочете встановити ${PRODUCTNAME}."
|
||||
LangString createDesktop ${LANG_UKRAINIAN} "Створити ярлик на робочому столі"
|
||||
LangString dontUninstall ${LANG_UKRAINIAN} "Не видаляти"
|
||||
LangString dontUninstallDowngrade ${LANG_UKRAINIAN} "Не видаляти (для цього встановлювача вимкнено зниження версії без видалення)"
|
||||
LangString failedToKillApp ${LANG_UKRAINIAN} "Не вдалося примусово закрити {{product_name}}. Будь ласка, спочатку закрийте його, а потім спробуйте ще раз"
|
||||
LangString failedToKillApp ${LANG_UKRAINIAN} "Не вдалося примусово закрити ${PRODUCTNAME}. Будь ласка, спочатку закрийте його, а потім спробуйте ще раз"
|
||||
LangString installingWebview2 ${LANG_UKRAINIAN} "Встановлення WebView2..."
|
||||
LangString newerVersionInstalled ${LANG_UKRAINIAN} "Новіша версія ${PRODUCTNAME} вже встановлена! Встановлювати старішу версію не рекомендується. Якщо ви дійсно хочете встановити цю версію, краще спочатку видаліть поточну. Виберіть дію, яку ви хочете виконати, і натисніть Далі, щоб продовжити."
|
||||
LangString older ${LANG_UKRAINIAN} "старішу"
|
||||
|
||||
@@ -6,23 +6,22 @@ use crate::{
|
||||
bundle::{
|
||||
settings::Arch,
|
||||
windows::{
|
||||
sign::{should_sign, sign_command, try_sign},
|
||||
sign::{sign_command, try_sign},
|
||||
util::{
|
||||
download_webview2_bootstrapper, download_webview2_offline_installer,
|
||||
NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
error::ErrorExt,
|
||||
utils::{
|
||||
http_utils::{download_and_verify, verify_file_hash, HashAlgorithm},
|
||||
CommandExt,
|
||||
},
|
||||
Error, Settings,
|
||||
Settings,
|
||||
};
|
||||
use tauri_utils::display_path;
|
||||
|
||||
use crate::error::Context;
|
||||
use anyhow::Context;
|
||||
use handlebars::{to_json, Handlebars};
|
||||
use tauri_utils::config::{NSISInstallerMode, NsisCompression, WebviewInstallMode};
|
||||
|
||||
@@ -36,12 +35,12 @@ use std::{
|
||||
// URLS for the NSIS toolchain.
|
||||
#[cfg(target_os = "windows")]
|
||||
const NSIS_URL: &str =
|
||||
"https://github.com/tauri-apps/binary-releases/releases/download/nsis-3.11/nsis-3.11.zip";
|
||||
"https://github.com/tauri-apps/binary-releases/releases/download/nsis-3/nsis-3.zip";
|
||||
#[cfg(target_os = "windows")]
|
||||
const NSIS_SHA1: &str = "EF7FF767E5CBD9EDD22ADD3A32C9B8F4500BB10D";
|
||||
const NSIS_SHA1: &str = "057e83c7d82462ec394af76c87d06733605543d4";
|
||||
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.4.2/nsis_tauri_utils.dll";
|
||||
const NSIS_TAURI_UTILS_SHA1: &str = "6532DA4545864C6EC95F62F27F2199BFD668560B";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const NSIS_REQUIRED_FILES: &[&str] = &[
|
||||
@@ -49,15 +48,12 @@ const NSIS_REQUIRED_FILES: &[&str] = &[
|
||||
"Bin/makensis.exe",
|
||||
"Stubs/lzma-x86-unicode",
|
||||
"Stubs/lzma_solid-x86-unicode",
|
||||
"Plugins/x86-unicode/additional/nsis_tauri_utils.dll",
|
||||
"Plugins/x86-unicode/nsis_tauri_utils.dll",
|
||||
"Include/MUI2.nsh",
|
||||
"Include/FileFunc.nsh",
|
||||
"Include/x64.nsh",
|
||||
"Include/nsDialogs.nsh",
|
||||
"Include/WinMessages.nsh",
|
||||
"Include/Win/COM.nsh",
|
||||
"Include/Win/Propkey.nsh",
|
||||
"Include/Win/RestartManager.nsh",
|
||||
];
|
||||
const NSIS_PLUGIN_FILES: &[&str] = &[
|
||||
"NSISdl.dll",
|
||||
@@ -109,9 +105,8 @@ pub fn bundle_project(settings: &Settings, updater: bool) -> crate::Result<Vec<P
|
||||
let data = download_and_verify(url, hash, *hash_algorithm)?;
|
||||
let out_path = nsis_toolset_path.join(path);
|
||||
std::fs::create_dir_all(out_path.parent().context("output path has no parent")?)
|
||||
.fs_context("failed to create file output directory", out_path.clone())?;
|
||||
fs::write(&out_path, data)
|
||||
.fs_context("failed to save NSIS downloaded file", out_path.clone())?;
|
||||
.context("failed to create file output directory")?;
|
||||
fs::write(out_path, data).with_context(|| format!("failed to save {path}"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +123,7 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, _tauri_tools_path: &Path) -> c
|
||||
let data = download_and_verify(NSIS_URL, NSIS_SHA1, HashAlgorithm::Sha1)?;
|
||||
log::info!("extracting NSIS");
|
||||
crate::utils::http_utils::extract_zip(&data, _tauri_tools_path)?;
|
||||
fs::rename(_tauri_tools_path.join("nsis-3.11"), nsis_toolset_path)?;
|
||||
fs::rename(_tauri_tools_path.join("nsis-3.08"), nsis_toolset_path)?;
|
||||
}
|
||||
|
||||
// download additional plugins
|
||||
@@ -147,9 +142,8 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, _tauri_tools_path: &Path) -> c
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_add_numeric_build_number(version_str: &str) -> crate::Result<String> {
|
||||
let version = semver::Version::parse(version_str)
|
||||
.map_err(|error| Error::GenericError(format!("invalid app version: {error}")))?;
|
||||
fn try_add_numeric_build_number(version_str: &str) -> anyhow::Result<String> {
|
||||
let version = semver::Version::parse(version_str).context("invalid app version")?;
|
||||
if !version.build.is_empty() {
|
||||
let build = version.build.parse::<u64>();
|
||||
if build.is_ok() {
|
||||
@@ -183,7 +177,8 @@ fn build_nsis_app_installer(
|
||||
Arch::AArch64 => "arm64",
|
||||
target => {
|
||||
return Err(crate::Error::ArchError(format!(
|
||||
"unsupported architecture: {target:?}"
|
||||
"unsupported architecture: {:?}",
|
||||
target
|
||||
)))
|
||||
}
|
||||
};
|
||||
@@ -198,46 +193,38 @@ fn build_nsis_app_installer(
|
||||
|
||||
// we make a copy of the NSIS directory if we're going to sign its DLLs
|
||||
// because we don't want to change the DLL hashes so the cache can reuse it
|
||||
let maybe_plugin_copy_path = if settings.windows().can_sign() {
|
||||
let maybe_plugin_copy_path = if settings.can_sign() {
|
||||
// find nsis path
|
||||
#[cfg(target_os = "linux")]
|
||||
let system_nsis_toolset_path = std::env::var_os("NSIS_PATH")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("/usr/share/nsis"));
|
||||
#[cfg(target_os = "macos")]
|
||||
let system_nsis_toolset_path = std::env::var_os("NSIS_PATH")
|
||||
.map(PathBuf::from)
|
||||
.context("failed to resolve NSIS path")
|
||||
.or_else(|_| {
|
||||
let mut makensis_path = which::which("makensis").map_err(|error| Error::CommandFailed {
|
||||
command: "makensis".to_string(),
|
||||
error: std::io::Error::other(format!("failed to find makensis: {error}")),
|
||||
let system_nsis_toolset_path = std::env::var_os("NSIS_PATH")
|
||||
.map(PathBuf::from)
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to resolve NSIS path"))
|
||||
.or_else(|_| {
|
||||
let mut makensis_path =
|
||||
which::which("makensis").context("failed to resolve `makensis`; did you install nsis? See https://tauri.app/distribute/windows-installer/#install-nsis for more information")?;
|
||||
// homebrew installs it as a symlink
|
||||
if makensis_path.is_symlink() {
|
||||
// read_link might return a path relative to makensis_path so we must use join() and canonicalize
|
||||
makensis_path = makensis_path
|
||||
.parent()
|
||||
.context("missing makensis parent")?
|
||||
.join(std::fs::read_link(&makensis_path).context("failed to resolve makensis symlink")?)
|
||||
.canonicalize()
|
||||
.context("failed to resolve makensis path")?;
|
||||
}
|
||||
// file structure:
|
||||
// ├── bin
|
||||
// │ ├── makensis
|
||||
// ├── share
|
||||
// │ ├── nsis
|
||||
let bin_folder = makensis_path.parent().context("missing makensis parent")?;
|
||||
let root_folder = bin_folder.parent().context("missing makensis root")?;
|
||||
crate::Result::Ok(root_folder.join("share").join("nsis"))
|
||||
})?;
|
||||
// homebrew installs it as a symlink
|
||||
if makensis_path.is_symlink() {
|
||||
// read_link might return a path relative to makensis_path so we must use join() and canonicalize
|
||||
makensis_path = makensis_path
|
||||
.parent()
|
||||
.context("missing makensis parent")?
|
||||
.join(
|
||||
std::fs::read_link(&makensis_path)
|
||||
.fs_context("failed to resolve makensis symlink", makensis_path.clone())?,
|
||||
)
|
||||
.canonicalize()
|
||||
.fs_context(
|
||||
"failed to canonicalize makensis path",
|
||||
makensis_path.clone(),
|
||||
)?;
|
||||
}
|
||||
// file structure:
|
||||
// ├── bin
|
||||
// │ ├── makensis
|
||||
// ├── share
|
||||
// │ ├── nsis
|
||||
let bin_folder = makensis_path.parent().context("missing makensis parent")?;
|
||||
let root_folder = bin_folder.parent().context("missing makensis root")?;
|
||||
crate::Result::Ok(root_folder.join("share").join("nsis"))
|
||||
})?;
|
||||
#[cfg(windows)]
|
||||
let system_nsis_toolset_path = nsis_toolset_path.to_path_buf();
|
||||
|
||||
@@ -297,7 +284,7 @@ fn build_nsis_app_installer(
|
||||
);
|
||||
data.insert("copyright", to_json(settings.copyright_string()));
|
||||
|
||||
if settings.windows().can_sign() {
|
||||
if settings.can_sign() {
|
||||
let sign_cmd = format!("{:?}", sign_command("%1", &settings.sign_params())?);
|
||||
data.insert("uninstaller_sign_cmd", to_json(sign_cmd));
|
||||
}
|
||||
@@ -498,9 +485,7 @@ fn build_nsis_app_installer(
|
||||
.iter()
|
||||
.flat_map(|p| &p.schemes)
|
||||
.collect::<Vec<_>>();
|
||||
if !schemes.is_empty() {
|
||||
data.insert("deep_link_protocols", to_json(schemes));
|
||||
}
|
||||
data.insert("deep_link_protocols", to_json(schemes));
|
||||
}
|
||||
|
||||
let silent_webview2_install = if let WebviewInstallMode::DownloadBootstrapper { silent }
|
||||
@@ -616,7 +601,7 @@ fn build_nsis_app_installer(
|
||||
));
|
||||
fs::create_dir_all(nsis_installer_path.parent().unwrap())?;
|
||||
|
||||
if settings.windows().can_sign() {
|
||||
if settings.can_sign() {
|
||||
log::info!("Signing NSIS plugins");
|
||||
for dll in NSIS_PLUGIN_FILES {
|
||||
let path = additional_plugins_path.join(dll);
|
||||
@@ -652,14 +637,11 @@ fn build_nsis_app_installer(
|
||||
.env_remove("NSISCONFDIR")
|
||||
.current_dir(output_path)
|
||||
.piped()
|
||||
.map_err(|error| Error::CommandFailed {
|
||||
command: "makensis.exe".to_string(),
|
||||
error,
|
||||
})?;
|
||||
.context("error running makensis.exe")?;
|
||||
|
||||
fs::rename(nsis_output_path, &nsis_installer_path)?;
|
||||
|
||||
if settings.windows().can_sign() {
|
||||
if settings.can_sign() {
|
||||
try_sign(&nsis_installer_path, settings)?;
|
||||
} else {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
@@ -737,7 +719,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {
|
||||
let loader_path =
|
||||
dunce::simplified(&settings.project_out_directory().join("WebView2Loader.dll")).to_path_buf();
|
||||
if loader_path.exists() {
|
||||
if settings.windows().can_sign() {
|
||||
if settings.can_sign() {
|
||||
try_sign(&loader_path, settings)?;
|
||||
}
|
||||
added_resources.push(loader_path.clone());
|
||||
@@ -762,7 +744,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {
|
||||
}
|
||||
added_resources.push(resource_path.clone());
|
||||
|
||||
if settings.windows().can_sign() && should_sign(&resource_path)? {
|
||||
if settings.can_sign() {
|
||||
try_sign(&resource_path, settings)?;
|
||||
}
|
||||
|
||||
@@ -827,11 +809,7 @@ fn generate_estimated_size(
|
||||
.chain(resources.keys())
|
||||
{
|
||||
size += std::fs::metadata(k)
|
||||
.map_err(|error| Error::Fs {
|
||||
context: "when getting size of",
|
||||
path: k.to_path_buf(),
|
||||
error,
|
||||
})?
|
||||
.with_context(|| format!("when getting size of {}", k.display()))?
|
||||
.len();
|
||||
}
|
||||
Ok(size / 1024)
|
||||
|
||||
@@ -19,55 +19,43 @@
|
||||
!macroend
|
||||
|
||||
; Checks whether app is running or not and prompts to kill it.
|
||||
!macro CheckIfAppIsRunning executableName productName
|
||||
!define UniqueID ${__LINE__}
|
||||
|
||||
; Replace {{product_name}} placeholder in the messages with the passed product name
|
||||
nsis_tauri_utils::StrReplace "$(appRunning)" "{{product_name}}" "${productName}"
|
||||
Pop $R1
|
||||
nsis_tauri_utils::StrReplace "$(appRunningOkKill)" "{{product_name}}" "${productName}"
|
||||
Pop $R2
|
||||
nsis_tauri_utils::StrReplace "$(failedToKillApp)" "{{product_name}}" "${productName}"
|
||||
Pop $R3
|
||||
|
||||
!macro CheckIfAppIsRunning
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::FindProcessCurrentUser "${executableName}"
|
||||
nsis_tauri_utils::FindProcessCurrentUser "${MAINBINARYNAME}.exe"
|
||||
!else
|
||||
nsis_tauri_utils::FindProcess "${executableName}"
|
||||
nsis_tauri_utils::FindProcess "${MAINBINARYNAME}.exe"
|
||||
!endif
|
||||
Pop $R0
|
||||
${If} $R0 = 0
|
||||
IfSilent kill_${UniqueID} 0
|
||||
${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL $R2 IDOK kill_${UniqueID} IDCANCEL cancel_${UniqueID} ${|}
|
||||
kill_${UniqueID}:
|
||||
IfSilent kill 0
|
||||
${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL "$(appRunningOkKill)" IDOK kill IDCANCEL cancel ${|}
|
||||
kill:
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "${executableName}"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "${MAINBINARYNAME}.exe"
|
||||
!else
|
||||
nsis_tauri_utils::KillProcess "${executableName}"
|
||||
nsis_tauri_utils::KillProcess "${MAINBINARYNAME}.exe"
|
||||
!endif
|
||||
Pop $R0
|
||||
Sleep 500
|
||||
${If} $R0 = 0
|
||||
${OrIf} $R0 = 2
|
||||
Goto app_check_done_${UniqueID}
|
||||
Goto app_check_done
|
||||
${Else}
|
||||
IfSilent silent_${UniqueID} ui_${UniqueID}
|
||||
silent_${UniqueID}:
|
||||
IfSilent silent ui
|
||||
silent:
|
||||
System::Call 'kernel32::AttachConsole(i -1)i.r0'
|
||||
${If} $0 != 0
|
||||
System::Call 'kernel32::GetStdHandle(i -11)i.r0'
|
||||
System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color
|
||||
FileWrite $0 "$R1$\n"
|
||||
FileWrite $0 "$(appRunning)$\n"
|
||||
${EndIf}
|
||||
Abort
|
||||
ui_${UniqueID}:
|
||||
Abort $R3
|
||||
ui:
|
||||
Abort "$(failedToKillApp)"
|
||||
${EndIf}
|
||||
cancel_${UniqueID}:
|
||||
Abort $R1
|
||||
cancel:
|
||||
Abort "$(appRunning)"
|
||||
${EndIf}
|
||||
app_check_done_${UniqueID}:
|
||||
!undef UniqueID
|
||||
app_check_done:
|
||||
!macroend
|
||||
|
||||
; Sets AppUserModelId on a shortcut
|
||||
|
||||
@@ -14,6 +14,10 @@ use std::sync::OnceLock;
|
||||
use std::{path::Path, process::Command};
|
||||
|
||||
impl Settings {
|
||||
pub(crate) fn can_sign(&self) -> bool {
|
||||
self.windows().sign_command.is_some() || self.windows().certificate_thumbprint.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn sign_params(&self) -> SignParams {
|
||||
SignParams {
|
||||
product_name: self.product_name().into(),
|
||||
@@ -210,7 +214,7 @@ pub fn sign_custom<P: AsRef<Path>>(
|
||||
let output = cmd.output_ok()?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(output.stdout.as_slice()).into_owned();
|
||||
log::info!(action = "Signing";"Output of signing command:\n{}", stdout.trim());
|
||||
log::info!("{:?}", stdout);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -229,7 +233,7 @@ pub fn sign_default<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Resu
|
||||
let output = cmd.output_ok()?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(output.stdout.as_slice()).into_owned();
|
||||
log::info!(action = "Signing";"Output of signing command:\n{}", stdout.trim());
|
||||
log::info!("{:?}", stdout);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -247,39 +251,9 @@ pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
|
||||
}
|
||||
|
||||
pub fn try_sign<P: AsRef<Path>>(file_path: P, settings: &Settings) -> crate::Result<()> {
|
||||
if settings.no_sign() {
|
||||
log::warn!(
|
||||
"Skipping signing for {} due to --no-sign flag.",
|
||||
tauri_utils::display_path(file_path.as_ref())
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
if settings.windows().can_sign() {
|
||||
if settings.can_sign() {
|
||||
log::info!(action = "Signing"; "{}", tauri_utils::display_path(file_path.as_ref()));
|
||||
sign(file_path, &settings.sign_params())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If the file is signable (is a binary file) and not signed already
|
||||
/// (will skip the verification if not on Windows since we can't verify it)
|
||||
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"));
|
||||
if !is_binary {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let already_signed = verify(file_path)?;
|
||||
Ok(!already_signed)
|
||||
}
|
||||
// Skip verification if not on Windows since we can't verify it
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ use std::{
|
||||
fs::create_dir_all,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use ureq::ResponseExt;
|
||||
|
||||
use crate::utils::http_utils::{base_ureq_agent, download};
|
||||
use crate::utils::http_utils::download;
|
||||
|
||||
pub const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703";
|
||||
pub const WEBVIEW2_OFFLINE_INSTALLER_X86_URL: &str =
|
||||
@@ -23,18 +24,24 @@ pub const WIX_OUTPUT_FOLDER_NAME: &str = "msi";
|
||||
pub const WIX_UPDATER_OUTPUT_FOLDER_NAME: &str = "msi-updater";
|
||||
|
||||
pub fn webview2_guid_path(url: &str) -> crate::Result<(String, String)> {
|
||||
let agent = base_ureq_agent();
|
||||
let agent: ureq::Agent = ureq::Agent::config_builder()
|
||||
.proxy(ureq::Proxy::try_from_env())
|
||||
.build()
|
||||
.into();
|
||||
let response = agent.head(url).call().map_err(Box::new)?;
|
||||
let final_url = response.get_uri().to_string();
|
||||
let remaining_url = final_url.strip_prefix(WEBVIEW2_URL_PREFIX).ok_or_else(|| {
|
||||
crate::Error::GenericError(format!(
|
||||
"WebView2 URL prefix mismatch. Expected `{WEBVIEW2_URL_PREFIX}`, found `{final_url}`."
|
||||
))
|
||||
anyhow::anyhow!(
|
||||
"WebView2 URL prefix mismatch. Expected `{}`, found `{}`.",
|
||||
WEBVIEW2_URL_PREFIX,
|
||||
final_url
|
||||
)
|
||||
})?;
|
||||
let (guid, filename) = remaining_url.split_once('/').ok_or_else(|| {
|
||||
crate::Error::GenericError(format!(
|
||||
"WebView2 URL format mismatch. Expected `<GUID>/<FILENAME>`, found `{remaining_url}`."
|
||||
))
|
||||
anyhow::anyhow!(
|
||||
"WebView2 URL format mismatch. Expected `<GUID>/<FILENAME>`, found `{}`.",
|
||||
remaining_url
|
||||
)
|
||||
})?;
|
||||
Ok((guid.into(), filename.into()))
|
||||
}
|
||||
@@ -77,75 +84,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(())
|
||||
}
|
||||
|
||||
@@ -3,45 +3,17 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
fmt::Display,
|
||||
io, num,
|
||||
path::{self, PathBuf},
|
||||
};
|
||||
use std::{io, num, path};
|
||||
use thiserror::Error as DeriveError;
|
||||
|
||||
/// Errors returned by the bundler.
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// Error with context. Created by the [`Context`] trait.
|
||||
#[error("{0}: {1}")]
|
||||
Context(String, Box<Self>),
|
||||
/// File system error.
|
||||
#[error("{context} {path}: {error}")]
|
||||
Fs {
|
||||
/// Context of the error.
|
||||
context: &'static str,
|
||||
/// Path that was accessed.
|
||||
path: PathBuf,
|
||||
/// Error that occurred.
|
||||
error: io::Error,
|
||||
},
|
||||
/// Child process error.
|
||||
#[error("failed to run command {command}: {error}")]
|
||||
CommandFailed {
|
||||
/// Command that failed.
|
||||
command: String,
|
||||
/// Error that occurred.
|
||||
error: io::Error,
|
||||
},
|
||||
/// Error running tauri_utils API.
|
||||
#[error("{0}")]
|
||||
Resource(#[from] tauri_utils::Error),
|
||||
/// Bundler error.
|
||||
///
|
||||
/// This variant is no longer used as this crate no longer uses anyhow.
|
||||
// TODO(v3): remove this variant
|
||||
#[error("{0:#}")]
|
||||
BundlerError(#[from] anyhow::Error),
|
||||
/// I/O error.
|
||||
@@ -92,28 +64,6 @@ pub enum Error {
|
||||
/// Failed to validate downloaded file hash.
|
||||
#[error("hash mismatch of downloaded file")]
|
||||
HashError,
|
||||
/// Failed to parse binary
|
||||
#[error("Binary parse error: `{0}`")]
|
||||
BinaryParseError(#[from] goblin::error::Error),
|
||||
/// Package type is not supported by target platform
|
||||
#[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")
|
||||
)]
|
||||
MissingBundleTypeVar,
|
||||
/// Failed to write binary file changed
|
||||
#[error("Failed to write binary file changes: `{0}`")]
|
||||
BinaryWriteError(String),
|
||||
/// Invalid offset while patching binary file
|
||||
#[error("Invalid offset while patching binary file")]
|
||||
BinaryOffsetOutOfRange,
|
||||
/// Unsupported architecture.
|
||||
#[error("Architecture Error: `{0}`")]
|
||||
ArchError(String),
|
||||
@@ -168,110 +118,7 @@ pub enum Error {
|
||||
#[cfg(target_os = "linux")]
|
||||
#[error("{0}")]
|
||||
RpmError(#[from] rpm::Error),
|
||||
/// Failed to notarize application.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[error("failed to notarize app: {0}")]
|
||||
AppleNotarization(#[from] NotarizeAuthError),
|
||||
/// Failed to codesign application.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[error("failed codesign application: {0}")]
|
||||
AppleCodesign(#[from] Box<tauri_macos_sign::Error>),
|
||||
/// Handlebars template error.
|
||||
#[error(transparent)]
|
||||
Template(#[from] handlebars::TemplateError),
|
||||
/// Semver error.
|
||||
#[error("`{0}`")]
|
||||
SemverError(#[from] semver::Error),
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum NotarizeAuthError {
|
||||
#[error(
|
||||
"The team ID is now required for notarization with app-specific password as authentication. Please set the `APPLE_TEAM_ID` environment variable. You can find the team ID in https://developer.apple.com/account#MembershipDetailsCard."
|
||||
)]
|
||||
MissingTeamId,
|
||||
#[error("could not find API key file. Please set the APPLE_API_KEY_PATH environment variables to the path to the {file_name} file")]
|
||||
MissingApiKey { file_name: String },
|
||||
#[error("no APPLE_ID & APPLE_PASSWORD & APPLE_TEAM_ID or APPLE_API_KEY & APPLE_API_ISSUER & APPLE_API_KEY_PATH environment variables found")]
|
||||
MissingCredentials,
|
||||
}
|
||||
|
||||
/// Convenient type alias of Result type.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub trait Context<T> {
|
||||
// Required methods
|
||||
fn context<C>(self, context: C) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static;
|
||||
fn with_context<C, F>(self, f: F) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C;
|
||||
}
|
||||
|
||||
impl<T> Context<T> for Result<T> {
|
||||
fn context<C>(self, context: C) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.map_err(|e| Error::Context(context.to_string(), Box::new(e)))
|
||||
}
|
||||
|
||||
fn with_context<C, F>(self, f: F) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C,
|
||||
{
|
||||
self.map_err(|e| Error::Context(f().to_string(), Box::new(e)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Context<T> for Option<T> {
|
||||
fn context<C>(self, context: C) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.ok_or_else(|| Error::GenericError(context.to_string()))
|
||||
}
|
||||
|
||||
fn with_context<C, F>(self, f: F) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C,
|
||||
{
|
||||
self.ok_or_else(|| Error::GenericError(f().to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ErrorExt<T> {
|
||||
fn fs_context(self, context: &'static str, path: impl Into<PathBuf>) -> Result<T>;
|
||||
}
|
||||
|
||||
impl<T> ErrorExt<T> for std::result::Result<T, std::io::Error> {
|
||||
fn fs_context(self, context: &'static str, path: impl Into<PathBuf>) -> Result<T> {
|
||||
self.map_err(|error| Error::Fs {
|
||||
context,
|
||||
path: path.into(),
|
||||
error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
macro_rules! bail {
|
||||
($msg:literal $(,)?) => {
|
||||
return Err(crate::Error::GenericError($msg.into()))
|
||||
};
|
||||
($err:expr $(,)?) => {
|
||||
return Err(crate::Error::GenericError($err))
|
||||
};
|
||||
($fmt:expr, $($arg:tt)*) => {
|
||||
return Err(crate::Error::GenericError(format!($fmt, $($arg)*)))
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) use bail;
|
||||
|
||||
@@ -14,8 +14,6 @@ use sha2::Digest;
|
||||
use url::Url;
|
||||
use zip::ZipArchive;
|
||||
|
||||
const BUNDLER_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
|
||||
|
||||
fn generate_github_mirror_url_from_template(github_url: &str) -> Option<String> {
|
||||
std::env::var("TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE")
|
||||
.ok()
|
||||
@@ -49,37 +47,17 @@ fn generate_github_alternative_url(url: &str) -> Option<(ureq::Agent, String)> {
|
||||
|
||||
generate_github_mirror_url_from_template(url)
|
||||
.or_else(|| generate_github_mirror_url_from_base(url))
|
||||
.map(|alt_url| {
|
||||
(
|
||||
ureq::Agent::config_builder()
|
||||
.user_agent(BUNDLER_USER_AGENT)
|
||||
.build()
|
||||
.into(),
|
||||
alt_url,
|
||||
)
|
||||
})
|
||||
.map(|alt_url| (ureq::agent(), alt_url))
|
||||
}
|
||||
|
||||
fn create_agent_and_url(url: &str) -> (ureq::Agent, String) {
|
||||
generate_github_alternative_url(url).unwrap_or((base_ureq_agent(), url.to_owned()))
|
||||
}
|
||||
|
||||
pub(crate) fn base_ureq_agent() -> ureq::Agent {
|
||||
#[allow(unused_mut)]
|
||||
let mut config_builder = ureq::Agent::config_builder()
|
||||
.user_agent(BUNDLER_USER_AGENT)
|
||||
.proxy(ureq::Proxy::try_from_env());
|
||||
|
||||
#[cfg(feature = "platform-certs")]
|
||||
{
|
||||
config_builder = config_builder.tls_config(
|
||||
ureq::tls::TlsConfig::builder()
|
||||
.root_certs(ureq::tls::RootCerts::PlatformVerifier)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
config_builder.build().into()
|
||||
generate_github_alternative_url(url).unwrap_or((
|
||||
ureq::Agent::config_builder()
|
||||
.proxy(ureq::Proxy::try_from_env())
|
||||
.build()
|
||||
.into(),
|
||||
url.to_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -1,225 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.9.5]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`f022b2d1a`](https://www.github.com/tauri-apps/tauri/commit/f022b2d1ae57612e39c75782926f2f341d9034a8) ([#14582](https://www.github.com/tauri-apps/tauri/pull/14582) by [@hrzlgnm](https://www.github.com/tauri-apps/tauri/../../hrzlgnm)) Fixed an issue that caused the cli to error out with missing private key, in case the option `--no-sign` was requested and the `tauri.config` has signing key set and the plugin `tauri-plugin-updater` is used.
|
||||
- [`f855caf8a`](https://www.github.com/tauri-apps/tauri/commit/f855caf8a3830aa5dd6d0b039312866a5d9c3606) ([#14481](https://www.github.com/tauri-apps/tauri/pull/14481) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fixed the mismatched tauri package versions check didn't work for pnpm
|
||||
- [`79a7d9ec0`](https://www.github.com/tauri-apps/tauri/commit/79a7d9ec01be1a371b8e923848140fea75e9caed) ([#14468](https://www.github.com/tauri-apps/tauri/pull/14468) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the cli to print errors like `Error Failed to parse version 2 for crate tauri` when there was no `Cargo.lock` file present yet. This will still be logged in `--verbose` mode.
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
- [`ce98d87ce`](https://www.github.com/tauri-apps/tauri/commit/ce98d87ce0aaa907285852eb80691197424e03c3) ([#14474](https://www.github.com/tauri-apps/tauri/pull/14474) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) refactor: remove needless collect. No user facing changes.
|
||||
- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-bundler@2.7.4`
|
||||
- Upgraded to `tauri-macos-sign@2.3.1`
|
||||
- Upgraded to `tauri-utils@2.8.1`
|
||||
|
||||
## \[2.9.4]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`b586ecf1f`](https://www.github.com/tauri-apps/tauri/commit/b586ecf1f4b3b087f9aa6c4668c2c18b1b7925f4) ([#14416](https://www.github.com/tauri-apps/tauri/pull/14416) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons for svg images.
|
||||
|
||||
## \[2.9.3]
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`22edc65aa`](https://www.github.com/tauri-apps/tauri/commit/22edc65aad0b3e45515008e8e0866112da70c8a1) ([#14408](https://www.github.com/tauri-apps/tauri/pull/14408) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Set user-agent in bundler and cli http requests when fetching build tools.
|
||||
- [`779612ac8`](https://www.github.com/tauri-apps/tauri/commit/779612ac8425a787626da4cefdb9eaf7d63bea18) ([#14379](https://www.github.com/tauri-apps/tauri/pull/14379) by [@moubctez](https://www.github.com/tauri-apps/tauri/../../moubctez)) Properly read the `required-features` field of binaries in Cargo.toml to prevent bundling issues when the features weren't enabled.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`fd8c30b4f`](https://www.github.com/tauri-apps/tauri/commit/fd8c30b4f1bca8dd7165c5c0ebe7fbfd17662153) ([#14353](https://www.github.com/tauri-apps/tauri/pull/14353) by [@ChaseKnowlden](https://www.github.com/tauri-apps/tauri/../../ChaseKnowlden)) Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-bundler@2.7.3`
|
||||
|
||||
## \[2.9.2]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-bundler@2.7.2`
|
||||
|
||||
## \[2.9.1]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-macos-sign@2.3.0`
|
||||
- Upgraded to `tauri-bundler@2.7.1`
|
||||
|
||||
## \[2.9.0]
|
||||
|
||||
### New Features
|
||||
|
||||
- [`f5851ee00`](https://www.github.com/tauri-apps/tauri/commit/f5851ee00d6d1f4d560a220ca5a728fedd525092) ([#14089](https://www.github.com/tauri-apps/tauri/pull/14089)) Adds the `scrollBarStyle` option to the window configuration.
|
||||
- [`2a06d1006`](https://www.github.com/tauri-apps/tauri/commit/2a06d10066a806e392efe8bfb16d943ee0b0b61d) ([#14052](https://www.github.com/tauri-apps/tauri/pull/14052)) Add a `--no-sign` flag to the `tauri build` and `tauri bundle` commands to skip the code signing step, improving the developer experience for local testing and development without requiring code signing keys.
|
||||
- [`3b4fac201`](https://www.github.com/tauri-apps/tauri/commit/3b4fac2017832d426dd07c5e24e26684eda57f7b) ([#14194](https://www.github.com/tauri-apps/tauri/pull/14194)) Add `tauri.conf.json > bundle > android > autoIncrementVersionCode` config option to automatically increment the Android version code.
|
||||
- [`673867aa0`](https://www.github.com/tauri-apps/tauri/commit/673867aa0e1ccd766ee879ffe96aba58c758613c) ([#14094](https://www.github.com/tauri-apps/tauri/pull/14094)) Try to detect ANDROID_HOME and NDK_HOME environment variables from default system locations and install them if needed using the Android Studio command line tools.
|
||||
- [`3d6868d09`](https://www.github.com/tauri-apps/tauri/commit/3d6868d09c323d68a152f3c3f8c7256311bd020a) ([#14128](https://www.github.com/tauri-apps/tauri/pull/14128)) Added support to defining the content type of the declared file association on macOS (maps to LSItemContentTypes property).
|
||||
- [`3d6868d09`](https://www.github.com/tauri-apps/tauri/commit/3d6868d09c323d68a152f3c3f8c7256311bd020a) ([#14128](https://www.github.com/tauri-apps/tauri/pull/14128)) Added support to defining the metadata for custom types declared in `tauri.conf.json > bundle > fileAssociations > exportedType` via the `UTExportedTypeDeclarations` Info.plist property.
|
||||
- [`ed7c9a410`](https://www.github.com/tauri-apps/tauri/commit/ed7c9a4100e08c002212265549d12130d021ad1e) ([#14108](https://www.github.com/tauri-apps/tauri/pull/14108)) Added `bundle > macOS > infoPlist` and `bundle > iOS > infoPlist` configurations to allow defining custom Info.plist extensions.
|
||||
- [`75082cc5b`](https://www.github.com/tauri-apps/tauri/commit/75082cc5b340e30e2c4b4cd4bd6a1fe5382164aa) ([#14120](https://www.github.com/tauri-apps/tauri/pull/14120)) Added `ios run` and `android run` commands to run the app in production mode.
|
||||
- [`cc8c0b531`](https://www.github.com/tauri-apps/tauri/commit/cc8c0b53171173dbd1d01781a50de1a3ea159031) ([#14031](https://www.github.com/tauri-apps/tauri/pull/14031)) Added support to universal app links on macOS with the `plugins > deep-link > desktop > domains` configuration.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`94cbd40fc`](https://www.github.com/tauri-apps/tauri/commit/94cbd40fc733e08c0bccd48149d22a0e9c2f1e5c) ([#14223](https://www.github.com/tauri-apps/tauri/pull/14223)) Add support for Android's adaptive and themed icons.
|
||||
- [`b5aa01870`](https://www.github.com/tauri-apps/tauri/commit/b5aa018702bf45dc98297698f9b7d238705865a6) ([#14268](https://www.github.com/tauri-apps/tauri/pull/14268)) Update cargo-mobile2 to 0.21, enhancing error messages and opening Xcode when multiple apps are installed.
|
||||
- [`55453e845`](https://www.github.com/tauri-apps/tauri/commit/55453e8453d927b8197f1ba9f26fd944482938f7) ([#14262](https://www.github.com/tauri-apps/tauri/pull/14262)) Check mismatched versions in `tauri info`
|
||||
- [`1a6627ee7`](https://www.github.com/tauri-apps/tauri/commit/1a6627ee7d085a4e66784e2705254714d68c7244) ([#14122](https://www.github.com/tauri-apps/tauri/pull/14122)) Set a default log level filter when running `tauri add log`.
|
||||
- [`b06b3bd09`](https://www.github.com/tauri-apps/tauri/commit/b06b3bd091b0fed26cdcfb23cacb0462a7a9cc2d) ([#14126](https://www.github.com/tauri-apps/tauri/pull/14126)) Improve error messages with more context.
|
||||
- [`f6622a3e3`](https://www.github.com/tauri-apps/tauri/commit/f6622a3e342f5dd5fb3cf6e0f79fb309a10e9b3d) ([#14129](https://www.github.com/tauri-apps/tauri/pull/14129)) Prompt to install the iOS platform if it isn't installed yet.
|
||||
- [`6bbb530fd`](https://www.github.com/tauri-apps/tauri/commit/6bbb530fd5edfc07b180a4f3782b8566872ca3b1) ([#14105](https://www.github.com/tauri-apps/tauri/pull/14105)) Warn if productName is empty when initializing mobile project.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`19fb6f7cb`](https://www.github.com/tauri-apps/tauri/commit/19fb6f7cb0d702cb2f25f6f2d1e11014d9dada5d) ([#14146](https://www.github.com/tauri-apps/tauri/pull/14146)) Strip Windows-only extensions from the binary path so an Android project initialized on Windows can be used on UNIX systems.
|
||||
- [`19fb6f7cb`](https://www.github.com/tauri-apps/tauri/commit/19fb6f7cb0d702cb2f25f6f2d1e11014d9dada5d) ([#14146](https://www.github.com/tauri-apps/tauri/pull/14146)) Enhance Android build script usage on Windows by attempting to run cmd, bat and exe formats.
|
||||
- [`28a2f9bc5`](https://www.github.com/tauri-apps/tauri/commit/28a2f9bc55f658eb71ef1a970ff9f791346f7682) ([#14101](https://www.github.com/tauri-apps/tauri/pull/14101)) Fix iOS CLI usage after modifying the package name.
|
||||
- [`d2938486e`](https://www.github.com/tauri-apps/tauri/commit/d2938486e9d974debd90c15d7160b8a17bf4d763) ([#14261](https://www.github.com/tauri-apps/tauri/pull/14261)) Replaced the non-standard nerd font character with ` ⱼₛ ` in `tarui info`
|
||||
- [`25e920e16`](https://www.github.com/tauri-apps/tauri/commit/25e920e169db900ca4f07c2bb9eb290e9f9f2c7d) ([#14298](https://www.github.com/tauri-apps/tauri/pull/14298)) Wait for dev server to exit before exiting the CLI when the app is closed on `tauri dev --no-watch`.
|
||||
- [`b0012424c`](https://www.github.com/tauri-apps/tauri/commit/b0012424c5f432debfa42ba145e2672966d5f6d5) ([#14115](https://www.github.com/tauri-apps/tauri/pull/14115)) Resolve local IP address when `tauri.conf.json > build > devUrl` host is `0.0.0.0`.
|
||||
- [`abf7e8850`](https://www.github.com/tauri-apps/tauri/commit/abf7e8850ba41e7173e9e9a3fdd6dfb8f357d72d) ([#14118](https://www.github.com/tauri-apps/tauri/pull/14118)) Fixes mobile project initialization when using `pnpx` or `pnpm dlx`.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.8.0`
|
||||
- Upgraded to `tauri-bundler@2.7.0`
|
||||
|
||||
## \[2.8.4]
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`f70b28529`](https://www.github.com/tauri-apps/tauri/commit/f70b28529d226a2dec2f41709d8934f8f5adab25) ([#14093](https://www.github.com/tauri-apps/tauri/pull/14093) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ensure Rust targets for mobile are installed when running the dev and build commands (previously only checked on init).
|
||||
- [`a9b342125`](https://www.github.com/tauri-apps/tauri/commit/a9b342125d5ac1bc9a4b2e8b5f73e8ca3cbcb8b2) ([#14114](https://www.github.com/tauri-apps/tauri/pull/14114) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS dev and build targeting the simulator on Intel machines.
|
||||
- [`61b9b681e`](https://www.github.com/tauri-apps/tauri/commit/61b9b681e88067a53b79d2318ae005dc25addcd6) ([#14111](https://www.github.com/tauri-apps/tauri/pull/14111) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Retain `RUST_*` environment variables when running the mobile commands.
|
||||
- [`c23bec62d`](https://www.github.com/tauri-apps/tauri/commit/c23bec62d6d5724798869681aa1534423aae28e2) ([#14083](https://www.github.com/tauri-apps/tauri/pull/14083) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Tauri now ignores `macOS.minimumSystemVersion` in `tauri dev` to prevent forced rebuilds of macOS specific dependencies when using something like `rust-analyzer` at the same time as `tauri dev`.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`c37a29833`](https://www.github.com/tauri-apps/tauri/commit/c37a298331d6d744b15d32d55a2db83c884a3d6a) ([#14112](https://www.github.com/tauri-apps/tauri/pull/14112) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix usage with Deno failing with `ReferenceError: require is not defined`.
|
||||
- [`bcf000c0a`](https://www.github.com/tauri-apps/tauri/commit/bcf000c0a8607eedf488fb949b982f519abda43d) ([#14110](https://www.github.com/tauri-apps/tauri/pull/14110) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `ios` commands with `deno` crashing due to incorrect current working directory resolution.
|
||||
- [`7db7142f9`](https://www.github.com/tauri-apps/tauri/commit/7db7142f9ff7dc2f5719602e199b77129ceb19d3) ([#14119](https://www.github.com/tauri-apps/tauri/pull/14119) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes empty device name when using an Android emulator causing the emulator to never be detected as running.
|
||||
- [`956b4fd6f`](https://www.github.com/tauri-apps/tauri/commit/956b4fd6ffbb4312123b107ca96c87a001359b9d) ([#14106](https://www.github.com/tauri-apps/tauri/pull/14106) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Use the correct export method on Xcode < 15.4.
|
||||
|
||||
## \[2.8.3]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`0ac89d3b6`](https://www.github.com/tauri-apps/tauri/commit/0ac89d3b6c8c4a4826a4c42726e4f4a8941b3fde) ([#14078](https://www.github.com/tauri-apps/tauri/pull/14078) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Updated `cargo-mobile2` to allow running on iOS simulators that have a higher version than the XCode SDK. This fixes compatiblity issues with Apple's recent "iOS 18.5 + iOS 18.6 Simulator" platform support component.
|
||||
|
||||
## \[2.8.1]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-bundler@2.6.1`
|
||||
|
||||
## \[2.8.0]
|
||||
|
||||
### New Features
|
||||
|
||||
- [`91508c0b8`](https://www.github.com/tauri-apps/tauri/commit/91508c0b8d16ec61c7706e93b711c5a85aaffb4a) ([#13881](https://www.github.com/tauri-apps/tauri/pull/13881) by [@pepperoni505](https://www.github.com/tauri-apps/tauri/../../pepperoni505)) Introduces a new configuration option that allows you to specify custom folders to watch for changes when running `tauri dev`.
|
||||
- [`bc4afe7dd`](https://www.github.com/tauri-apps/tauri/commit/bc4afe7dd4780f02c2d4b1f07d97185fbc5d2bba) ([#13993](https://www.github.com/tauri-apps/tauri/pull/13993) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Check installed plugin NPM/crate versions for incompatible releases.
|
||||
- [`a9ec12843`](https://www.github.com/tauri-apps/tauri/commit/a9ec12843aa7d0eb774bd3a53e2e63da12cfa77b) ([#13521](https://www.github.com/tauri-apps/tauri/pull/13521) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added a `--skip-stapling` option to make `tauri build|bundle` *not* wait for notarization to finish on macOS.
|
||||
- [`0c402bfb6`](https://www.github.com/tauri-apps/tauri/commit/0c402bfb6bd0bec24d928fcabe2ffef1f5cff19a) ([#13997](https://www.github.com/tauri-apps/tauri/pull/13997) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Increase default iOS deployment target iOS to 14.0.
|
||||
- [`d6d5f3707`](https://www.github.com/tauri-apps/tauri/commit/d6d5f3707768a094ff7e961ae75ba0398d772655) ([#13358](https://www.github.com/tauri-apps/tauri/pull/13358) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `--root-certificate-path` option to `android dev` and `ios dev` to be able to connect to HTTPS dev servers.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`8b465a12b`](https://www.github.com/tauri-apps/tauri/commit/8b465a12ba73e94d7a3995defd9cc362d15eeebe) ([#13913](https://www.github.com/tauri-apps/tauri/pull/13913) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler now pulls the latest AppImage linuxdeploy plugin instead of using the built-in one. This should remove the libfuse requirement.
|
||||
- [`390cb9c36`](https://www.github.com/tauri-apps/tauri/commit/390cb9c36a4e2416891b64514e7ad5fc0a85ccf2) ([#13953](https://www.github.com/tauri-apps/tauri/pull/13953) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Reduced the log level of the binary patcher crate `goblin` to only show its debug logs in `-vv` and above.
|
||||
- [`4475e93e1`](https://www.github.com/tauri-apps/tauri/commit/4475e93e136e9e2bd5f3c7817fa2040924f630f6) ([#13824](https://www.github.com/tauri-apps/tauri/pull/13824) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler and cli will now read TLS Certificates installed on the system when downloading tools and checking versions.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`f0dcf9637`](https://www.github.com/tauri-apps/tauri/commit/f0dcf9637cc0d42eda05fed7dd6c5ff98bbf19ae) ([#13980](https://www.github.com/tauri-apps/tauri/pull/13980) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix the generated plugin init code of `tauri add` for `tauri-plugin-autostart` and `tauri-plugin-single-instance`
|
||||
- [`4d270a96a`](https://www.github.com/tauri-apps/tauri/commit/4d270a96a891ae83f7df751abcbe12b7072212d5) ([#13943](https://www.github.com/tauri-apps/tauri/pull/13943) by [@acx0](https://www.github.com/tauri-apps/tauri/../../acx0)) Fix codesigning verification failures caused by binary-patching during bundling
|
||||
- [`b21d86a8a`](https://www.github.com/tauri-apps/tauri/commit/b21d86a8a3ef29f16628b7d4de17ce1214e9bf49) ([#13981](https://www.github.com/tauri-apps/tauri/pull/13981) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `tauri permission add` could add duplicated permissions to the capability files
|
||||
- [`9c938be45`](https://www.github.com/tauri-apps/tauri/commit/9c938be4520fce9204361f3b59439844bc5c91e8) ([#13912](https://www.github.com/tauri-apps/tauri/pull/13912) by [@takecchi](https://www.github.com/tauri-apps/tauri/../../takecchi)) Properly migrate svelte to v5 in the plugin example template
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-utils@2.7.0`
|
||||
- Upgraded to `tauri-bundler@2.6.0`
|
||||
- Upgraded to `tauri-macos-sign@2.2.0`
|
||||
|
||||
## \[2.7.1]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-bundler@2.5.2`
|
||||
|
||||
## \[2.7.0]
|
||||
|
||||
### New Features
|
||||
|
||||
- [`33d079392`](https://www.github.com/tauri-apps/tauri/commit/33d079392ac4a5a153b7d8a6d82fefd6f54a2bdf) ([#13811](https://www.github.com/tauri-apps/tauri/pull/13811) by [@mhbagheri-99](https://www.github.com/tauri-apps/tauri/../../mhbagheri-99)) Allow runner configuration to be an object with cmd, cwd, and args properties. The runner can now be configured as `{ "cmd": "my_runner", "cwd": "/path", "args": ["--quiet"] }` while maintaining backwards compatibility with the existing string format.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`232265c70`](https://www.github.com/tauri-apps/tauri/commit/232265c70e1c213bbb3f84b5541ddc07d330fce1) ([#13209](https://www.github.com/tauri-apps/tauri/pull/13209) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Binaries are patched before bundling to add the type of a bundle they will placed in. This information will be used during update process to select the correct target.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`916aeaa48`](https://www.github.com/tauri-apps/tauri/commit/916aeaa48646a483a78e51cfe1633800ee62c37c) ([#13781](https://www.github.com/tauri-apps/tauri/pull/13781) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes Android dev and build commands reading `tauri.ios.conf.json` instead of `tauri.android.conf.json` to merge platform-specific configuration.
|
||||
- [`acd757428`](https://www.github.com/tauri-apps/tauri/commit/acd7574284056f9c00894cdce6c07f948fd80c87) ([#13743](https://www.github.com/tauri-apps/tauri/pull/13743) by [@owjs3901](https://www.github.com/tauri-apps/tauri/../../owjs3901)) Fix type of CFBundleVersion generated by `tauri ios init` when `bundleVersion` is a single number (for example `1` instead of `1.0.0`).
|
||||
- [`0f248b111`](https://www.github.com/tauri-apps/tauri/commit/0f248b111ffb8af934eaf64bd8f4591e628da786) ([#13799](https://www.github.com/tauri-apps/tauri/pull/13799) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Install iOS dependencies when needed.
|
||||
- [`7a6fd5b75`](https://www.github.com/tauri-apps/tauri/commit/7a6fd5b75d61071e2771f6277c0376ec206d302a) ([#13863](https://www.github.com/tauri-apps/tauri/pull/13863) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The AppImage bundler now pulls the AppRun binaries from our GitHub mirror, fixing 404 errors.
|
||||
- [`bda830410`](https://www.github.com/tauri-apps/tauri/commit/bda8304107da7ca60caaba5674faa793491898c6) ([#13833](https://www.github.com/tauri-apps/tauri/pull/13833) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fail with an error when trying to migrate from v2 alpha
|
||||
- [`bda830410`](https://www.github.com/tauri-apps/tauri/commit/bda8304107da7ca60caaba5674faa793491898c6) ([#13833](https://www.github.com/tauri-apps/tauri/pull/13833) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Use v2 stable instead of v2-rc when migrating from v2-beta
|
||||
|
||||
### What's Changed
|
||||
|
||||
- [`cfc5bb819`](https://www.github.com/tauri-apps/tauri/commit/cfc5bb819637a97141cbda3285c5d772cfc0ebca) ([#13780](https://www.github.com/tauri-apps/tauri/pull/13780) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Enable edge to edge in `tauri android init` template
|
||||
- [`12e359061`](https://www.github.com/tauri-apps/tauri/commit/12e3590613c7b4370aeddc16db7e29335e3a7684) ([#13759](https://www.github.com/tauri-apps/tauri/pull/13759) by [@owjs3901](https://www.github.com/tauri-apps/tauri/../../owjs3901)) Update compileSdk, targetSdk in android template to 36
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-bundler@2.5.1`
|
||||
- Upgraded to `tauri-utils@2.6.0`
|
||||
|
||||
## \[2.6.2]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`cbd962972`](https://www.github.com/tauri-apps/tauri/commit/cbd9629729ed6eb208ba2234d014c11c4e9f1c8c) ([#13730](https://www.github.com/tauri-apps/tauri/pull/13730) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Load `--config` arguments when running the Xcode and Android Studio build scripts.
|
||||
|
||||
## \[2.6.1]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`4b7370e9e`](https://www.github.com/tauri-apps/tauri/commit/4b7370e9e0ba299361c8ad96c08882337e44f091) ([#13710](https://www.github.com/tauri-apps/tauri/pull/13710) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue in the plugin template (`tauri plugin init`) that prevented the js build command to fail.
|
||||
|
||||
## \[2.6.0]
|
||||
|
||||
### New Features
|
||||
|
||||
- [`414619c36`](https://www.github.com/tauri-apps/tauri/commit/414619c36e94e21939534dd72c0438b93da75546) ([#13536](https://www.github.com/tauri-apps/tauri/pull/13536) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) Added support for the `bundleName` property in the macOS bundler configuration. This allows specifying the `CFBundleName` value for generated macOS bundles.
|
||||
- [`3242e1c94`](https://www.github.com/tauri-apps/tauri/commit/3242e1c946c441b58665ba5d612f3a3f1eafe0b6) ([#13659](https://www.github.com/tauri-apps/tauri/pull/13659) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow passing Cargo arguments to mobile dev and build commands.
|
||||
- [`d1ce9af62`](https://www.github.com/tauri-apps/tauri/commit/d1ce9af62881e3f7d86a495c9c40df5b7f9d1c04) ([#13660](https://www.github.com/tauri-apps/tauri/pull/13660) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow passing `--config` arguments to the `ios init` and `android init` commands to tweak the configuration used to initialize the mobile projects.
|
||||
- [`7322f0579`](https://www.github.com/tauri-apps/tauri/commit/7322f057923aaec88960ad5556776774b745762f) ([#13502](https://www.github.com/tauri-apps/tauri/pull/13502) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Allow using `CheckIfAppIsRunning` macro inside NSIS hooks, for example `!insertmacro CheckIfAppIsRunning "another-executable.exe" "Another Executable"`.
|
||||
- [`4a880ca69`](https://www.github.com/tauri-apps/tauri/commit/4a880ca697bab6d63a2a51ea94e1988cc8c4ea4a) ([#13658](https://www.github.com/tauri-apps/tauri/pull/13658) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Synchronize Tauri config productName changes with the iOS Xcode project.
|
||||
- [`8ee14a864`](https://www.github.com/tauri-apps/tauri/commit/8ee14a86480510c15823586cf28084e615cb7a9c) ([#13618](https://www.github.com/tauri-apps/tauri/pull/13618) by [@Sky-walkerX](https://www.github.com/tauri-apps/tauri/../../Sky-walkerX)) Warn the user that the app id shouldn't end in `.app` because it conflicts with the application bundle extension on macOS
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`574a4d4d3`](https://www.github.com/tauri-apps/tauri/commit/574a4d4d36762b5b09dc3fcfcbcae3a0df0b6d89) ([#13426](https://www.github.com/tauri-apps/tauri/pull/13426) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `dev`, `build` and `bundle` commands always take 2 seconds to start
|
||||
- [`35aa7e121`](https://www.github.com/tauri-apps/tauri/commit/35aa7e1218f34d0805e280e3ec32529d0cb0d733) ([#13294](https://www.github.com/tauri-apps/tauri/pull/13294) by [@kingsword09](https://www.github.com/tauri-apps/tauri/../../kingsword09)) fix: allow the target directory to be inside frontendDir as long as it is not the Rust target directory inside frontendDir.
|
||||
- [`ec6065fa4`](https://www.github.com/tauri-apps/tauri/commit/ec6065fa4a6427266ecfb0c0f62f008574bb7880) ([#13625](https://www.github.com/tauri-apps/tauri/pull/13625) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes Android and iOS dev/build commands not working when the app identifier is being modified by the `--config` option.
|
||||
- [`5a5291d66`](https://www.github.com/tauri-apps/tauri/commit/5a5291d66cb8a955c9d4f8e975782646ac0cc6e7) ([#13483](https://www.github.com/tauri-apps/tauri/pull/13483) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix simulator build detection on Xcode.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-bundler@2.5.0`
|
||||
- Upgraded to `tauri-utils@2.5.0`
|
||||
- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Update html5ever to 0.29 and kuchikiki to version 0.8.8-speedreader.
|
||||
|
||||
## \[2.5.0]
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-cli"
|
||||
version = "2.9.5"
|
||||
version = "2.5.0"
|
||||
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.20", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
jsonrpsee = { version = "0.24", features = ["server"] }
|
||||
@@ -46,31 +46,35 @@ jsonrpsee-ws-client = { version = "0.24", default-features = false }
|
||||
sublime_fuzzy = "0.7"
|
||||
clap_complete = "4"
|
||||
clap = { version = "4", features = ["derive", "env"] }
|
||||
thiserror = "2"
|
||||
tauri-bundler = { version = "2.7.4", default-features = false, path = "../tauri-bundler" }
|
||||
anyhow = "1"
|
||||
tauri-bundler = { version = "2.4.0", default-features = false, path = "../tauri-bundler" }
|
||||
colored = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1", features = ["preserve_order"] }
|
||||
json5 = "0.4"
|
||||
notify = "8"
|
||||
notify-debouncer-full = "0.6"
|
||||
notify-debouncer-full = "0.5"
|
||||
shared_child = "1"
|
||||
duct = "1.0"
|
||||
toml_edit = { version = "0.23", features = ["serde"] }
|
||||
duct = "0.13"
|
||||
toml_edit = { version = "0.22", features = ["serde"] }
|
||||
json-patch = "3"
|
||||
tauri-utils = { version = "2.8.1", path = "../tauri-utils", features = [
|
||||
tauri-utils = { version = "2.4.0", path = "../tauri-utils", features = [
|
||||
"isolation",
|
||||
"schema",
|
||||
"config-json5",
|
||||
"config-toml",
|
||||
"html-manipulation",
|
||||
] }
|
||||
toml = "0.9"
|
||||
jsonschema = "0.33"
|
||||
tauri-utils-v1 = { version = "1", package = "tauri-utils", features = [
|
||||
"isolation",
|
||||
"schema",
|
||||
"config-json5",
|
||||
"config-toml",
|
||||
] }
|
||||
toml = "0.8"
|
||||
jsonschema = "0.30"
|
||||
handlebars = "6"
|
||||
include_dir = "0.7"
|
||||
dirs = "6"
|
||||
minisign = "0.8"
|
||||
minisign = "=0.7.3"
|
||||
base64 = "0.22"
|
||||
ureq = { version = "3", default-features = false, features = ["gzip"] }
|
||||
os_info = "3"
|
||||
@@ -86,9 +90,9 @@ log = { version = "0.4.21", features = ["kv", "kv_std"] }
|
||||
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" }
|
||||
axum = { version = "0.7", features = ["ws"] }
|
||||
html5ever = "0.26"
|
||||
kuchiki = { package = "kuchikiki", version = "0.8" }
|
||||
tokio = { version = "1", features = ["macros", "sync"] }
|
||||
common-path = "1"
|
||||
serde-value = "0.7"
|
||||
@@ -110,19 +114,15 @@ elf = "0.7"
|
||||
memchr = "2"
|
||||
tempfile = "3"
|
||||
uuid = { version = "1", features = ["v5"] }
|
||||
rand = "0.9"
|
||||
zip = { version = "4", default-features = false, features = ["deflate"] }
|
||||
which = "8"
|
||||
rayon = "1.10"
|
||||
rand = "0.8"
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "1"
|
||||
pretty_assertions = "1"
|
||||
|
||||
[target."cfg(windows)".dependencies.windows-sys]
|
||||
version = "0.60"
|
||||
version = "0.59"
|
||||
features = [
|
||||
"Win32_Security",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_IO",
|
||||
"Win32_System_Console",
|
||||
@@ -133,7 +133,7 @@ libc = "0.2"
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
plist = "1"
|
||||
tauri-macos-sign = { version = "2.3.1", path = "../tauri-macos-sign" }
|
||||
tauri-macos-sign = { version = "2.1.0", path = "../tauri-macos-sign" }
|
||||
object = { version = "0.36", default-features = false, features = [
|
||||
"macho",
|
||||
"read_core",
|
||||
@@ -142,7 +142,7 @@ object = { version = "0.36", default-features = false, features = [
|
||||
ar = "0.9"
|
||||
|
||||
[features]
|
||||
default = ["rustls", "platform-certs"]
|
||||
default = ["rustls"]
|
||||
native-tls = [
|
||||
"tauri-bundler/native-tls",
|
||||
"cargo-mobile2/native-tls",
|
||||
@@ -150,4 +150,3 @@ native-tls = [
|
||||
]
|
||||
native-tls-vendored = ["native-tls", "tauri-bundler/native-tls-vendored"]
|
||||
rustls = ["tauri-bundler/rustls", "cargo-mobile2/rustls", "ureq/rustls"]
|
||||
platform-certs = ["tauri-bundler/platform-certs", "ureq/platform-verifier"]
|
||||
|
||||
@@ -33,7 +33,7 @@ These environment variables are inputs to the CLI which may have an equivalent C
|
||||
- See [creating API keys](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) for more information.
|
||||
- `API_PRIVATE_KEYS_DIR` — Specify the directory where your AuthKey file is located. See `APPLE_API_KEY`.
|
||||
- `APPLE_API_ISSUER` — Issuer ID. Required if `APPLE_API_KEY` is specified.
|
||||
- `APPLE_API_KEY_PATH` - path to the API key `.p8` file. If not specified, for macOS apps the bundler searches the following directories in sequence for a private key file with the name of `AuthKey\_<api_key>.p8`: `./private_keys`, `~/private_keys`, `~/.private_keys`, and `~/.appstoreconnect/private_keys`. **For iOS this variable is required**.
|
||||
- `APPLE_API_KEY_PATH` - path to the API key `.p8` file. If not specified, for macOS apps the bundler searches the following directories in sequence for a private key file with the name of 'AuthKey\_<api_key>.p8': './private_keys', '~/private_keys', '~/.private_keys', and '~/.appstoreconnect/private_keys'. **For iOS this variable is required**.
|
||||
- `APPLE_SIGNING_IDENTITY` — The identity used to code sign. Overwrites `tauri.conf.json > bundle > macOS > signingIdentity`. If neither are set, it is inferred from `APPLE_CERTIFICATE` when provided.
|
||||
- `APPLE_PROVIDER_SHORT_NAME` — If your Apple ID is connected to multiple teams, you have to specify the provider short name of the team you want to use to notarize your app. Overwrites `tauri.conf.json > bundle > macOS > providerShortName`.
|
||||
- `APPLE_DEVELOPMENT_TEAM` — The team ID used to code sign on iOS. Overwrites `tauri.conf.json > bundle > iOS > developmentTeam`. Can be found in https://developer.apple.com/account#MembershipDetailsCard.
|
||||
|
||||
@@ -40,3 +40,28 @@ MIT or MIT/Apache 2.0 where applicable.
|
||||
Logo: CC-BY-NC-ND
|
||||
|
||||
- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)
|
||||
|
||||
## Licensing Errata:
|
||||
|
||||
Because of publishing issues upstream, we soft-forked (and patched) both [`console`](https://github.com/mitsuhiko/console/blob/278de9dc2bf0fa28db69adee351072f668beec8f/Cargo.toml#L7) and [`dialoguer`](https://github.com/mitsuhiko/dialoguer/blob/2c3fe6b64641cfb57eb0e1d428274f63976ec150/Cargo.toml#L12) crates because of untenable issues surrounding expected use on Windows.
|
||||
|
||||
This soft fork was introduced to the Tauri Codebase [here](https://github.com/tauri-apps/tauri/pull/1610).
|
||||
|
||||
`console`
|
||||
|
||||
```
|
||||
license = "MIT"
|
||||
authors = [
|
||||
"Armin Ronacher <armin.ronacher@active-4.com>"
|
||||
]
|
||||
```
|
||||
|
||||
`dialoguer`
|
||||
|
||||
```
|
||||
license = "MIT"
|
||||
authors = [
|
||||
"Armin Ronacher <armin.ronacher@active-4.com>",
|
||||
"Pavan Kumar Sunkara <pavan.sss1991@gmail.com>"
|
||||
]
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://schema.tauri.app/config/2.9.4",
|
||||
"$id": "https://schema.tauri.app/config/2.5.1",
|
||||
"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",
|
||||
@@ -24,7 +24,7 @@
|
||||
"pattern": "^[^/\\:*?\"<>|]+$"
|
||||
},
|
||||
"mainBinaryName": {
|
||||
"description": "Overrides app's main binary filename.\n\n By default, Tauri uses the output binary from `cargo`, by setting this, we will rename that binary in `tauri-cli`'s\n `tauri build` command, and target `tauri bundle` to it\n\n If possible, change the [`package name`] or set the [`name field`] instead,\n and if that's not enough and you're using nightly, consider using the [`different-binary-name`] feature instead\n\n Note: this config should not include the binary extension (e.g. `.exe`), we'll add that for you\n\n [`package name`]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field\n [`name field`]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-name-field\n [`different-binary-name`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#different-binary-name",
|
||||
"description": "App main binary filename. Defaults to the name of your cargo crate.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -70,7 +70,6 @@
|
||||
"build": {
|
||||
"description": "The build configuration.",
|
||||
"default": {
|
||||
"additionalWatchFolders": [],
|
||||
"removeUnusedCommands": false
|
||||
},
|
||||
"allOf": [
|
||||
@@ -84,12 +83,11 @@
|
||||
"default": {
|
||||
"active": false,
|
||||
"android": {
|
||||
"autoIncrementVersionCode": false,
|
||||
"minSdkVersion": 24
|
||||
},
|
||||
"createUpdaterArtifacts": false,
|
||||
"iOS": {
|
||||
"minimumSystemVersion": "14.0"
|
||||
"minimumSystemVersion": "13.0"
|
||||
},
|
||||
"icon": [],
|
||||
"linux": {
|
||||
@@ -165,7 +163,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.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -231,7 +229,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).",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -367,11 +365,6 @@
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"focusable": {
|
||||
"description": "Whether the window will be focusable or not.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"transparent": {
|
||||
"description": "Whether the window is transparent or not.\n\n Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`.\n WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.",
|
||||
"default": false,
|
||||
@@ -496,7 +489,7 @@
|
||||
]
|
||||
},
|
||||
"incognito": {
|
||||
"description": "Whether or not the webview should be launched in incognito mode.\n\n ## Platform-specific:\n\n - **Android**: Unsupported.",
|
||||
"description": "Whether or not the webview should be launched in incognito mode.\n\n ## Platform-specific:\n\n - **Android**: Unsupported.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -573,36 +566,6 @@
|
||||
"description": "Allows disabling the input accessory view on iOS.\n\n The accessory view is the view that appears above the keyboard when a text input element is focused.\n It usually displays a view with \"Done\", \"Next\" buttons.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"dataDirectory": {
|
||||
"description": "Set a custom path for the webview's data directory (localStorage, cache, etc.) **relative to [`appDataDir()`]/${label}**.\n\n To set absolute paths, use [`WebviewWindowBuilder::data_directory`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.data_directory)\n\n #### Platform-specific:\n\n - **Windows**: WebViews with different values for settings like `additionalBrowserArgs`, `browserExtensionsEnabled` or `scrollBarStyle` must have different data directories.\n - **macOS / iOS**: Unsupported, use `dataStoreIdentifier` instead.\n - **Android**: Unsupported.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dataStoreIdentifier": {
|
||||
"description": "Initialize the WebView with a custom data store identifier. This can be seen as a replacement for `dataDirectory` which is unavailable in WKWebView.\n See https://developer.apple.com/documentation/webkit/wkwebsitedatastore/init(foridentifier:)?language=objc\n\n The array must contain 16 u8 numbers.\n\n #### Platform-specific:\n\n - **iOS**: Supported since version 17.0+.\n - **macOS**: Supported since version 14.0+.\n - **Windows / Linux / Android**: Unsupported.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 16,
|
||||
"minItems": 16
|
||||
},
|
||||
"scrollBarStyle": {
|
||||
"description": "Specifies the native scrollbar style to use with the webview.\n CSS styles that modify the scrollbar are applied on top of the native appearance configured here.\n\n Defaults to `default`, which is the browser default.\n\n ## Platform-specific\n\n - **Windows**:\n - `fluentOverlay` requires WebView2 Runtime version 125.0.2535.41 or higher,\n and does nothing on older versions.\n - This option must be given the same value for all webviews that target the same data directory.\n - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.",
|
||||
"default": "default",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ScrollBarStyle"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -921,7 +884,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Mica effect that matches the system dark preference **Windows 11 Only**",
|
||||
"description": "Mica effect that matches the system dark perefence **Windows 11 Only**",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"mica"
|
||||
@@ -942,7 +905,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Tabbed effect that matches the system dark preference **Windows 11 Only**",
|
||||
"description": "Tabbed effect that matches the system dark perefence **Windows 11 Only**",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"tabbed"
|
||||
@@ -1107,14 +1070,14 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A policy where a web view that's not in a window fully suspends tasks. This is usually the default behavior in case no policy is set.",
|
||||
"description": "A policy where a web view that’s not in a window fully suspends tasks. This is usually the default behavior in case no policy is set.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"suspend"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A policy where a web view that's not in a window limits processing, but does not fully suspend tasks.",
|
||||
"description": "A policy where a web view that’s not in a window limits processing, but does not fully suspend tasks.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"throttle"
|
||||
@@ -1122,25 +1085,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ScrollBarStyle": {
|
||||
"description": "The scrollbar style to use in the webview.\n\n ## Platform-specific\n\n - **Windows**: This option must be given the same value for all webviews that target the same data directory.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The scrollbar style to use in the webview.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Fluent UI style overlay scrollbars. **Windows Only**\n\n Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions,\n see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/?tabs=dotnetcsharp#10253541",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"fluentOverlay"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"SecurityConfig": {
|
||||
"description": "Security configuration.\n\n See more: <https://v2.tauri.app/reference/config/#securityconfig>",
|
||||
"type": "object",
|
||||
@@ -1205,7 +1149,7 @@
|
||||
]
|
||||
},
|
||||
"capabilities": {
|
||||
"description": "List of capabilities that are enabled on the application.\n\n By default (not set or empty list), all capability files from `./capabilities/` are included,\n by setting values in this entry, you have fine grained control over which capabilities are included\n\n You can either reference a capability file defined in `./capabilities/` with its identifier or inline a [`Capability`]\n\n ### Example\n\n ```json\n {\n \"app\": {\n \"capabilities\": [\n \"main-window\",\n {\n \"identifier\": \"drag-window\",\n \"permissions\": [\"core:window:allow-start-dragging\"]\n }\n ]\n }\n }\n ```",
|
||||
"description": "List of capabilities that are enabled on the application.\n\n If the list is empty, all capabilities are included.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -1402,7 +1346,7 @@
|
||||
]
|
||||
},
|
||||
"Capability": {
|
||||
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\n It controls application windows' and webviews' fine grained access\n to the Tauri core, application, or plugin commands.\n If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\n This can be done to create groups of windows, based on their required system access, which can reduce\n impact of frontend vulnerabilities in less privileged windows.\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\n A Window can have none, one, or multiple associated capabilities.\n\n ## Example\n\n ```json\n {\n \"identifier\": \"main-user-files-write\",\n \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\",\n \"windows\": [\n \"main\"\n ],\n \"permissions\": [\n \"core:default\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n },\n ],\n \"platforms\": [\"macOS\",\"windows\"]\n }\n ```",
|
||||
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\n It controls application windows' and webviews' fine grained access\n to the Tauri core, application, or plugin commands.\n If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\n This can be done to create groups of windows, based on their required system access, which can reduce\n impact of frontend vulnerabilities in less privileged windows.\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\n A Window can have none, one, or multiple associated capabilities.\n\n ## Example\n\n ```json\n {\n \"identifier\": \"main-user-files-write\",\n \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\",\n \"windows\": [\n \"main\"\n ],\n \"permissions\": [\n \"core:default\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n },\n ],\n \"platforms\": [\"macOS\",\"windows\"]\n }\n ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identifier",
|
||||
@@ -1414,7 +1358,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description of what the capability is intended to allow on associated windows.\n\n It should contain a description of what the grouped permissions should allow.\n\n ## Example\n\n This capability allows the `main` window access to `filesystem` write related\n commands and `dialog` commands to enable programmatic access to files selected by the user.",
|
||||
"description": "Description of what the capability is intended to allow on associated windows.\n\n It should contain a description of what the grouped permissions should allow.\n\n ## Example\n\n This capability allows the `main` window access to `filesystem` write related\n commands and `dialog` commands to enable programatic access to files selected by the user.",
|
||||
"default": "",
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1861,13 +1805,9 @@
|
||||
"properties": {
|
||||
"runner": {
|
||||
"description": "The binary used to build and run the application.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RunnerConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"devUrl": {
|
||||
@@ -1933,60 +1873,13 @@
|
||||
}
|
||||
},
|
||||
"removeUnusedCommands": {
|
||||
"description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4",
|
||||
"description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"additionalWatchFolders": {
|
||||
"description": "Additional paths to watch for changes when running `tauri dev`.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RunnerConfig": {
|
||||
"description": "The runner configuration.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A string specifying the binary to run.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "An object with advanced configuration options.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cmd"
|
||||
],
|
||||
"properties": {
|
||||
"cmd": {
|
||||
"description": "The binary to run.",
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"description": "The current working directory to run the command from.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"args": {
|
||||
"description": "Arguments to pass to the command.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"FrontendDist": {
|
||||
"description": "Defines the URL or assets to embed in the application.",
|
||||
"anyOf": [
|
||||
@@ -2121,7 +2014,7 @@
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"description": "App resources to bundle.\n Each resource is a path to a file or directory.\n Glob patterns are supported.\n\n ## Examples\n\n To include a list of files:\n\n ```json\n {\n \"bundle\": {\n \"resources\": [\n \"./path/to/some-file.txt\",\n \"/absolute/path/to/textfile.txt\",\n \"../relative/path/to/jsonfile.json\",\n \"some-folder/\",\n \"resources/**/*.md\"\n ]\n }\n }\n ```\n\n The bundled files will be in `$RESOURCES/` with the original directory structure preserved,\n for example: `./path/to/some-file.txt` -> `$RESOURCE/path/to/some-file.txt`\n\n To fine control where the files will get copied to, use a map instead\n\n ```json\n {\n \"bundle\": {\n \"resources\": {\n \"/absolute/path/to/textfile.txt\": \"resources/textfile.txt\",\n \"relative/path/to/jsonfile.json\": \"resources/jsonfile.json\",\n \"resources/\": \"\",\n \"docs/**/*md\": \"website-docs/\"\n }\n }\n }\n ```\n\n Note that when using glob pattern in this case, the original directory structure is not preserved,\n everything gets copied to the target directory directly\n\n See more: <https://v2.tauri.app/develop/resources/>",
|
||||
"description": "App resources to bundle.\n Each resource is a path to a file or directory.\n Glob patterns are supported.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/BundleResources"
|
||||
@@ -2160,7 +2053,7 @@
|
||||
]
|
||||
},
|
||||
"fileAssociations": {
|
||||
"description": "File types to associate with the application.",
|
||||
"description": "File associations to application.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
@@ -2272,7 +2165,7 @@
|
||||
"iOS": {
|
||||
"description": "iOS configuration.",
|
||||
"default": {
|
||||
"minimumSystemVersion": "14.0"
|
||||
"minimumSystemVersion": "13.0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
@@ -2283,7 +2176,6 @@
|
||||
"android": {
|
||||
"description": "Android configuration.",
|
||||
"default": {
|
||||
"autoIncrementVersionCode": false,
|
||||
"minSdkVersion": 24
|
||||
},
|
||||
"allOf": [
|
||||
@@ -2377,7 +2269,7 @@
|
||||
"description": "Updater type",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Generates legacy zipped v1 compatible updaters",
|
||||
"description": "Generates lagacy zipped v1 compatible updaters",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/V1Compatible"
|
||||
@@ -2391,10 +2283,10 @@
|
||||
]
|
||||
},
|
||||
"V1Compatible": {
|
||||
"description": "Generates legacy zipped v1 compatible updaters",
|
||||
"description": "Generates lagacy zipped v1 compatible updaters",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Generates legacy zipped v1 compatible updaters",
|
||||
"description": "Generates lagacy zipped v1 compatible updaters",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"v1Compatible"
|
||||
@@ -2435,16 +2327,6 @@
|
||||
"$ref": "#/definitions/AssociationExt"
|
||||
}
|
||||
},
|
||||
"contentTypes": {
|
||||
"description": "Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.\n\n This allows supporting any file format declared by another application that conforms to this type.\n Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"description": "The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`",
|
||||
"type": [
|
||||
@@ -2483,17 +2365,6 @@
|
||||
"$ref": "#/definitions/HandlerRank"
|
||||
}
|
||||
]
|
||||
},
|
||||
"exportedType": {
|
||||
"description": "The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\n\n You should define this if the associated file is a custom file type defined by your application.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ExportedFileAssociation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -2575,30 +2446,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ExportedFileAssociation": {
|
||||
"description": "The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identifier"
|
||||
],
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "The unique identifier for the exported type. Maps to `UTTypeIdentifier`.",
|
||||
"type": "string"
|
||||
},
|
||||
"conformsTo": {
|
||||
"description": "The types that this type conforms to. Maps to `UTTypeConformsTo`.\n\n Examples are `public.data`, `public.image`, `public.json` and `public.database`.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"WindowsConfig": {
|
||||
"description": "Windows bundler configuration.\n\n See more: <https://v2.tauri.app/reference/config/#windowsconfig>",
|
||||
"type": "object",
|
||||
@@ -2888,11 +2735,6 @@
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"fipsCompliant": {
|
||||
"description": "Enables FIPS compliant algorithms.\n Can also be enabled via the `TAURI_BUNDLER_WIX_FIPS_COMPLIANT` env var.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -3017,7 +2859,7 @@
|
||||
]
|
||||
},
|
||||
"installerHooks": {
|
||||
"description": "A path to a `.nsh` file that contains special NSIS macros to be hooked into the\n main installer.nsi script.\n\n Supported hooks are:\n\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\n\n ### Example\n\n ```nsh\n !macro NSIS_HOOK_PREINSTALL\n MessageBox MB_OK \"PreInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTINSTALL\n MessageBox MB_OK \"PostInstall\"\n !macroend\n\n !macro NSIS_HOOK_PREUNINSTALL\n MessageBox MB_OK \"PreUnInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTUNINSTALL\n MessageBox MB_OK \"PostUninstall\"\n !macroend\n ```",
|
||||
"description": "A path to a `.nsh` file that contains special NSIS macros to be hooked into the\n main installer.nsi script.\n\n Supported hooks are:\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\n\n\n ### Example\n\n ```nsh\n !macro NSIS_HOOK_PREINSTALL\n MessageBox MB_OK \"PreInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTINSTALL\n MessageBox MB_OK \"PostInstall\"\n !macroend\n\n !macro NSIS_HOOK_PREUNINSTALL\n MessageBox MB_OK \"PreUnInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTUNINSTALL\n MessageBox MB_OK \"PostUninstall\"\n !macroend\n\n ```",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -3571,15 +3413,8 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"bundleName": {
|
||||
"description": "The name of the builder that built the bundle.\n\n Translates to the bundle's CFBundleName property.\n\n If not set, defaults to the package's product name.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"minimumSystemVersion": {
|
||||
"description": "A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\n\n Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\n and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\n\n Ignored in `tauri dev`.\n\n An empty string is considered an invalid value so the default value is used.",
|
||||
"description": "A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\n\n Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\n and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\n\n An empty string is considered an invalid value so the default value is used.",
|
||||
"default": "10.13",
|
||||
"type": [
|
||||
"string",
|
||||
@@ -3601,7 +3436,7 @@
|
||||
]
|
||||
},
|
||||
"hardenedRuntime": {
|
||||
"description": "Whether the codesign should enable [hardened runtime](https://developer.apple.com/documentation/security/hardened_runtime) (for executables) or not.",
|
||||
"description": "Whether the codesign should enable [hardened runtime] (for executables) or not.\n\n [hardened runtime]: <https://developer.apple.com/documentation/security/hardened_runtime>",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -3619,13 +3454,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"infoPlist": {
|
||||
"description": "Path to a Info.plist file to merge with the default Info.plist.\n\n Note that Tauri also looks for a `Info.plist` file in the same directory as the Tauri configuration file.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dmg": {
|
||||
"description": "DMG-specific settings.",
|
||||
"default": {
|
||||
@@ -3795,15 +3623,8 @@
|
||||
},
|
||||
"minimumSystemVersion": {
|
||||
"description": "A version string indicating the minimum iOS version that the bundled application supports. Defaults to `13.0`.\n\n Maps to the IPHONEOS_DEPLOYMENT_TARGET value.",
|
||||
"default": "14.0",
|
||||
"default": "13.0",
|
||||
"type": "string"
|
||||
},
|
||||
"infoPlist": {
|
||||
"description": "Path to a Info.plist file to merge with the default Info.plist.\n\n Note that Tauri also looks for a `Info.plist` and `Info.ios.plist` file in the same directory as the Tauri configuration file.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -3828,11 +3649,6 @@
|
||||
"format": "uint32",
|
||||
"maximum": 2100000000.0,
|
||||
"minimum": 1.0
|
||||
},
|
||||
"autoIncrementVersionCode": {
|
||||
"description": "Whether to automatically increment the `versionCode` on each build.\n\n - If `true`, the generator will try to read the last `versionCode` from\n `tauri.properties` and increment it by 1 for every build.\n - If `false` or not set, it falls back to `version_code` or semver-derived logic.\n\n Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"cli.js": {
|
||||
"version": "2.9.5",
|
||||
"version": "2.5.0",
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"tauri": "2.9.4",
|
||||
"tauri-build": "2.5.3",
|
||||
"tauri-plugin": "2.5.2"
|
||||
"tauri": "2.5.1",
|
||||
"tauri-build": "2.2.0",
|
||||
"tauri-plugin": "2.2.0"
|
||||
}
|
||||
|
||||
@@ -2330,14 +2330,6 @@
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"fipsCompliant": {
|
||||
"description": "Enables FIPS compliant algorithms.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -9,7 +9,6 @@ use tauri_utils::acl::capability::{Capability, PermissionEntry};
|
||||
|
||||
use crate::{
|
||||
acl::FileFormat,
|
||||
error::ErrorExt,
|
||||
helpers::{app_paths::tauri_dir, prompts},
|
||||
Result,
|
||||
};
|
||||
@@ -99,7 +98,7 @@ pub fn command(options: Options) -> Result<()> {
|
||||
PermissionEntry::PermissionRef(
|
||||
p.clone()
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("invalid permission {p}")),
|
||||
.unwrap_or_else(|_| panic!("invalid permission {}", p)),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
@@ -107,9 +106,7 @@ pub fn command(options: Options) -> Result<()> {
|
||||
};
|
||||
|
||||
let path = match options.out {
|
||||
Some(o) => o
|
||||
.canonicalize()
|
||||
.fs_context("failed to canonicalize capability file path", o.clone())?,
|
||||
Some(o) => o.canonicalize()?,
|
||||
None => {
|
||||
let dir = tauri_dir();
|
||||
let capabilities_dir = dir.join("capabilities");
|
||||
@@ -128,21 +125,17 @@ pub fn command(options: Options) -> Result<()> {
|
||||
);
|
||||
let overwrite = prompts::confirm(&format!("{msg}, overwrite?"), Some(false))?;
|
||||
if overwrite {
|
||||
std::fs::remove_file(&path).fs_context("failed to remove capability file", path.clone())?;
|
||||
std::fs::remove_file(&path)?;
|
||||
} else {
|
||||
crate::error::bail!(msg);
|
||||
anyhow::bail!(msg);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).fs_context(
|
||||
"failed to create capability directory",
|
||||
parent.to_path_buf(),
|
||||
)?;
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
std::fs::write(&path, options.format.serialize(&capability)?)
|
||||
.fs_context("failed to write capability file", path.clone())?;
|
||||
std::fs::write(&path, options.format.serialize(&capability)?)?;
|
||||
|
||||
log::info!(action = "Created"; "capability at {}", dunce::simplified(&path).display());
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::error::Context;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
|
||||
@@ -34,8 +33,8 @@ impl FileFormat {
|
||||
|
||||
pub fn serialize<S: Serialize>(&self, s: &S) -> crate::Result<String> {
|
||||
let contents = match self {
|
||||
Self::Json => serde_json::to_string_pretty(s).context("failed to serialize JSON")?,
|
||||
Self::Toml => toml_edit::ser::to_string_pretty(s).context("failed to serialize TOML")?,
|
||||
Self::Json => serde_json::to_string_pretty(s)?,
|
||||
Self::Toml => toml_edit::ser::to_string_pretty(s)?,
|
||||
};
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::path::Path;
|
||||
use clap::Parser;
|
||||
|
||||
use crate::{
|
||||
error::{Context, ErrorExt},
|
||||
helpers::{app_paths::resolve_tauri_dir, prompts},
|
||||
Result,
|
||||
};
|
||||
@@ -78,32 +77,10 @@ impl TomlOrJson {
|
||||
};
|
||||
}
|
||||
|
||||
fn has_permission(&self, identifier: &str) -> bool {
|
||||
(|| {
|
||||
Some(match self {
|
||||
TomlOrJson::Toml(t) => t
|
||||
.get("permissions")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.any(|value| value.as_str() == Some(identifier)),
|
||||
|
||||
TomlOrJson::Json(j) => j
|
||||
.as_object()?
|
||||
.get("permissions")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.any(|value| value.as_str() == Some(identifier)),
|
||||
})
|
||||
})()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn to_string(&self) -> Result<String> {
|
||||
Ok(match self {
|
||||
TomlOrJson::Toml(t) => t.to_string(),
|
||||
TomlOrJson::Json(j) => {
|
||||
serde_json::to_string_pretty(&j).context("failed to serialize JSON")?
|
||||
}
|
||||
TomlOrJson::Json(j) => serde_json::to_string_pretty(&j)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -134,12 +111,12 @@ pub struct Options {
|
||||
pub fn command(options: Options) -> Result<()> {
|
||||
let dir = match resolve_tauri_dir() {
|
||||
Some(t) => t,
|
||||
None => std::env::current_dir().context("failed to resolve current directory")?,
|
||||
None => std::env::current_dir()?,
|
||||
};
|
||||
|
||||
let capabilities_dir = dir.join("capabilities");
|
||||
if !capabilities_dir.exists() {
|
||||
crate::error::bail!(
|
||||
anyhow::bail!(
|
||||
"Couldn't find capabilities directory at {}",
|
||||
dunce::simplified(&capabilities_dir).display()
|
||||
);
|
||||
@@ -151,11 +128,7 @@ pub fn command(options: Options) -> Result<()> {
|
||||
.split_once(':')
|
||||
.and_then(|(plugin, _permission)| known_plugins.get(&plugin));
|
||||
|
||||
let capabilities_iter = std::fs::read_dir(&capabilities_dir)
|
||||
.fs_context(
|
||||
"failed to read capabilities directory",
|
||||
capabilities_dir.clone(),
|
||||
)?
|
||||
let capabilities_iter = std::fs::read_dir(&capabilities_dir)?
|
||||
.flatten()
|
||||
.filter(|e| e.file_type().map(|e| e.is_file()).unwrap_or_default())
|
||||
.filter_map(|e| {
|
||||
@@ -247,7 +220,7 @@ pub fn command(options: Options) -> Result<()> {
|
||||
)?;
|
||||
|
||||
if selections.is_empty() {
|
||||
crate::error::bail!("You did not select any capabilities to update");
|
||||
anyhow::bail!("You did not select any capabilities to update");
|
||||
}
|
||||
|
||||
selections
|
||||
@@ -259,23 +232,13 @@ pub fn command(options: Options) -> Result<()> {
|
||||
};
|
||||
|
||||
if capabilities.is_empty() {
|
||||
crate::error::bail!("Could not find a capability to update");
|
||||
anyhow::bail!("Could not find a capability to update");
|
||||
}
|
||||
|
||||
for (capability, path) in &mut capabilities {
|
||||
if capability.has_permission(&options.identifier) {
|
||||
log::info!(
|
||||
"Permission `{}` already found in `{}` at {}",
|
||||
options.identifier,
|
||||
capability.identifier(),
|
||||
dunce::simplified(path).display()
|
||||
);
|
||||
} else {
|
||||
capability.insert_permission(options.identifier.clone());
|
||||
std::fs::write(&*path, capability.to_string()?)
|
||||
.fs_context("failed to write capability file", path.clone())?;
|
||||
log::info!(action = "Added"; "permission `{}` to `{}` at {}", options.identifier, capability.identifier(), dunce::simplified(path).display());
|
||||
}
|
||||
capability.insert_permission(options.identifier.clone());
|
||||
std::fs::write(&*path, capability.to_string()?)?;
|
||||
log::info!(action = "Added"; "permission `{}` to `{}` at {}", options.identifier, capability.identifier(), dunce::simplified(path).display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -4,11 +4,7 @@
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::{
|
||||
error::{Context, ErrorExt},
|
||||
helpers::app_paths::tauri_dir,
|
||||
Result,
|
||||
};
|
||||
use crate::{helpers::app_paths::tauri_dir, Result};
|
||||
use colored::Colorize;
|
||||
use tauri_utils::acl::{manifest::Manifest, APP_ACL_KEY};
|
||||
|
||||
@@ -33,10 +29,8 @@ pub fn command(options: Options) -> Result<()> {
|
||||
.join("acl-manifests.json");
|
||||
|
||||
if acl_manifests_path.exists() {
|
||||
let plugin_manifest_json = read_to_string(&acl_manifests_path)
|
||||
.fs_context("failed to read plugin manifest", acl_manifests_path)?;
|
||||
let acl = serde_json::from_str::<BTreeMap<String, Manifest>>(&plugin_manifest_json)
|
||||
.context("failed to parse plugin manifest as JSON")?;
|
||||
let plugin_manifest_json = read_to_string(&acl_manifests_path)?;
|
||||
let acl = serde_json::from_str::<BTreeMap<String, Manifest>>(&plugin_manifest_json)?;
|
||||
|
||||
for (key, manifest) in acl {
|
||||
if options
|
||||
@@ -153,6 +147,6 @@ pub fn command(options: Options) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
crate::error::bail!("permission file not found, please build your application once first")
|
||||
anyhow::bail!("permission file not found, please build your application once first")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use clap::Parser;
|
||||
|
||||
use crate::{
|
||||
acl::FileFormat,
|
||||
error::{Context, ErrorExt},
|
||||
helpers::{app_paths::resolve_tauri_dir, prompts},
|
||||
Result,
|
||||
};
|
||||
@@ -68,13 +67,11 @@ pub fn command(options: Options) -> Result<()> {
|
||||
};
|
||||
|
||||
let path = match options.out {
|
||||
Some(o) => o
|
||||
.canonicalize()
|
||||
.fs_context("failed to canonicalize permission file path", o.clone())?,
|
||||
Some(o) => o.canonicalize()?,
|
||||
None => {
|
||||
let dir = match resolve_tauri_dir() {
|
||||
Some(t) => t,
|
||||
None => std::env::current_dir().context("failed to resolve current directory")?,
|
||||
None => std::env::current_dir()?,
|
||||
};
|
||||
let permissions_dir = dir.join("permissions");
|
||||
permissions_dir.join(format!(
|
||||
@@ -92,31 +89,24 @@ pub fn command(options: Options) -> Result<()> {
|
||||
);
|
||||
let overwrite = prompts::confirm(&format!("{msg}, overwrite?"), Some(false))?;
|
||||
if overwrite {
|
||||
std::fs::remove_file(&path).fs_context("failed to remove permission file", path.clone())?;
|
||||
std::fs::remove_file(&path)?;
|
||||
} else {
|
||||
crate::error::bail!(msg);
|
||||
anyhow::bail!(msg);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).fs_context(
|
||||
"failed to create permission directory",
|
||||
parent.to_path_buf(),
|
||||
)?;
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
std::fs::write(
|
||||
&path,
|
||||
options
|
||||
.format
|
||||
.serialize(&PermissionFile {
|
||||
default: None,
|
||||
set: Vec::new(),
|
||||
permission: vec![permission],
|
||||
})
|
||||
.context("failed to serialize permission")?,
|
||||
)
|
||||
.fs_context("failed to write permission file", path.clone())?;
|
||||
options.format.serialize(&PermissionFile {
|
||||
default: None,
|
||||
set: Vec::new(),
|
||||
permission: vec![permission],
|
||||
})?,
|
||||
)?;
|
||||
|
||||
log::info!(action = "Created"; "permission at {}", dunce::simplified(&path).display());
|
||||
|
||||
|
||||
@@ -7,21 +7,11 @@ use std::path::Path;
|
||||
use clap::Parser;
|
||||
use tauri_utils::acl::{manifest::PermissionFile, PERMISSION_SCHEMA_FILE_NAME};
|
||||
|
||||
use crate::{
|
||||
acl::FileFormat,
|
||||
error::{Context, ErrorExt},
|
||||
helpers::app_paths::resolve_tauri_dir,
|
||||
Result,
|
||||
};
|
||||
use crate::{acl::FileFormat, helpers::app_paths::resolve_tauri_dir, Result};
|
||||
|
||||
fn rm_permission_files(identifier: &str, dir: &Path) -> Result<()> {
|
||||
for entry in std::fs::read_dir(dir)
|
||||
.fs_context("failed to read permissions directory", dir.to_path_buf())?
|
||||
.flatten()
|
||||
{
|
||||
let file_type = entry
|
||||
.file_type()
|
||||
.fs_context("failed to get permission file type", entry.path())?;
|
||||
for entry in std::fs::read_dir(dir)?.flatten() {
|
||||
let file_type = entry.file_type()?;
|
||||
let path = entry.path();
|
||||
if file_type.is_dir() {
|
||||
rm_permission_files(identifier, &path)?;
|
||||
@@ -37,21 +27,12 @@ fn rm_permission_files(identifier: &str, dir: &Path) -> Result<()> {
|
||||
let (mut permission_file, format): (PermissionFile, FileFormat) =
|
||||
match path.extension().and_then(|o| o.to_str()) {
|
||||
Some("toml") => {
|
||||
let content = std::fs::read_to_string(&path)
|
||||
.fs_context("failed to read permission file", path.clone())?;
|
||||
(
|
||||
toml::from_str(&content).context("failed to deserialize permission file")?,
|
||||
FileFormat::Toml,
|
||||
)
|
||||
let content = std::fs::read_to_string(&path)?;
|
||||
(toml::from_str(&content)?, FileFormat::Toml)
|
||||
}
|
||||
Some("json") => {
|
||||
let content =
|
||||
std::fs::read(&path).fs_context("failed to read permission file", path.clone())?;
|
||||
(
|
||||
serde_json::from_slice(&content)
|
||||
.context("failed to parse permission file as JSON")?,
|
||||
FileFormat::Json,
|
||||
)
|
||||
let content = std::fs::read(&path)?;
|
||||
(serde_json::from_slice(&content)?, FileFormat::Json)
|
||||
}
|
||||
_ => {
|
||||
continue;
|
||||
@@ -82,16 +63,10 @@ fn rm_permission_files(identifier: &str, dir: &Path) -> Result<()> {
|
||||
&& permission_file.set.is_empty()
|
||||
&& permission_file.permission.is_empty()
|
||||
{
|
||||
std::fs::remove_file(&path).fs_context("failed to remove permission file", path.clone())?;
|
||||
std::fs::remove_file(&path)?;
|
||||
log::info!(action = "Removed"; "file {}", dunce::simplified(&path).display());
|
||||
} else if updated {
|
||||
std::fs::write(
|
||||
&path,
|
||||
format
|
||||
.serialize(&permission_file)
|
||||
.context("failed to serialize permission")?,
|
||||
)
|
||||
.fs_context("failed to write permission file", path.clone())?;
|
||||
std::fs::write(&path, format.serialize(&permission_file)?)?;
|
||||
log::info!(action = "Removed"; "permission {identifier} from {}", dunce::simplified(&path).display());
|
||||
}
|
||||
}
|
||||
@@ -101,19 +76,13 @@ fn rm_permission_files(identifier: &str, dir: &Path) -> Result<()> {
|
||||
}
|
||||
|
||||
fn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> {
|
||||
for entry in std::fs::read_dir(dir)
|
||||
.fs_context("failed to read capabilities directory", dir.to_path_buf())?
|
||||
.flatten()
|
||||
{
|
||||
let file_type = entry
|
||||
.file_type()
|
||||
.fs_context("failed to get capability file type", entry.path())?;
|
||||
for entry in std::fs::read_dir(dir)?.flatten() {
|
||||
let file_type = entry.file_type()?;
|
||||
if file_type.is_file() {
|
||||
let path = entry.path();
|
||||
match path.extension().and_then(|o| o.to_str()) {
|
||||
Some("toml") => {
|
||||
let content = std::fs::read_to_string(&path)
|
||||
.fs_context("failed to read capability file", path.clone())?;
|
||||
let content = std::fs::read_to_string(&path)?;
|
||||
if let Ok(mut value) = content.parse::<toml_edit::DocumentMut>() {
|
||||
if let Some(permissions) = value.get_mut("permissions").and_then(|p| p.as_array_mut()) {
|
||||
let prev_len = permissions.len();
|
||||
@@ -129,16 +98,14 @@ fn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> {
|
||||
_ => false,
|
||||
});
|
||||
if prev_len != permissions.len() {
|
||||
std::fs::write(&path, value.to_string())
|
||||
.fs_context("failed to write capability file", path.clone())?;
|
||||
std::fs::write(&path, value.to_string())?;
|
||||
log::info!(action = "Removed"; "permission from capability at {}", dunce::simplified(&path).display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some("json") => {
|
||||
let content =
|
||||
std::fs::read(&path).fs_context("failed to read capability file", path.clone())?;
|
||||
let content = std::fs::read(&path)?;
|
||||
if let Ok(mut value) = serde_json::from_slice::<serde_json::Value>(&content) {
|
||||
if let Some(permissions) = value.get_mut("permissions").and_then(|p| p.as_array_mut()) {
|
||||
let prev_len = permissions.len();
|
||||
@@ -154,12 +121,7 @@ fn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> {
|
||||
_ => false,
|
||||
});
|
||||
if prev_len != permissions.len() {
|
||||
std::fs::write(
|
||||
&path,
|
||||
serde_json::to_vec_pretty(&value)
|
||||
.context("failed to serialize capability JSON")?,
|
||||
)
|
||||
.fs_context("failed to write capability file", path.clone())?;
|
||||
std::fs::write(&path, serde_json::to_vec_pretty(&value)?)?;
|
||||
log::info!(action = "Removed"; "permission from capability at {}", dunce::simplified(&path).display());
|
||||
}
|
||||
}
|
||||
@@ -190,9 +152,7 @@ pub struct Options {
|
||||
}
|
||||
|
||||
pub fn command(options: Options) -> Result<()> {
|
||||
let permissions_dir = std::env::current_dir()
|
||||
.context("failed to resolve current directory")?
|
||||
.join("permissions");
|
||||
let permissions_dir = std::env::current_dir()?.join("permissions");
|
||||
if permissions_dir.exists() {
|
||||
rm_permission_files(&options.identifier, &permissions_dir)?;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
acl,
|
||||
error::ErrorExt,
|
||||
helpers::{
|
||||
app_paths::{resolve_frontend_dir, tauri_dir},
|
||||
cargo,
|
||||
@@ -65,7 +64,7 @@ pub fn run(options: Options) -> Result<()> {
|
||||
};
|
||||
|
||||
if !is_known && (options.tag.is_some() || options.rev.is_some() || options.branch.is_some()) {
|
||||
crate::error::bail!(
|
||||
anyhow::bail!(
|
||||
"Git options --tag, --rev and --branch can only be used with official Tauri plugins"
|
||||
);
|
||||
}
|
||||
@@ -115,7 +114,7 @@ pub fn run(options: Options) -> Result<()> {
|
||||
format!("tauri-apps/tauri-plugin-{plugin}#{branch}")
|
||||
}
|
||||
(None, None, None, None) => npm_name,
|
||||
_ => crate::error::bail!("Only one of --tag, --rev and --branch can be specified"),
|
||||
_ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"),
|
||||
};
|
||||
manager.install(&[npm_spec], tauri_dir)?;
|
||||
}
|
||||
@@ -131,10 +130,6 @@ pub fn run(options: Options) -> Result<()> {
|
||||
"Builder::new(|pass| todo!()).build()"
|
||||
} else if plugin == "localhost" {
|
||||
"Builder::new(todo!()).build()"
|
||||
} else if plugin == "single-instance" {
|
||||
"init(|app, args, cwd| {})"
|
||||
} else if plugin == "log" {
|
||||
"Builder::new().level(tauri_plugin_log::log::LevelFilter::Info).build()"
|
||||
} else if metadata.builder {
|
||||
"Builder::new().build()"
|
||||
} else {
|
||||
@@ -142,10 +137,9 @@ 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();
|
||||
let re = Regex::new(r"(tauri\s*::\s*Builder\s*::\s*default\(\))(\s*)")?;
|
||||
for file in [tauri_dir.join("src/main.rs"), tauri_dir.join("src/lib.rs")] {
|
||||
let contents =
|
||||
std::fs::read_to_string(&file).fs_context("failed to read Rust entry point", file.clone())?;
|
||||
let contents = std::fs::read_to_string(&file)?;
|
||||
|
||||
if contents.contains(&plugin_init) {
|
||||
log::info!(
|
||||
@@ -159,7 +153,7 @@ pub fn run(options: Options) -> Result<()> {
|
||||
let out = re.replace(&contents, format!("$1$2{plugin_init}$2"));
|
||||
|
||||
log::info!("Adding plugin to {}", file.display());
|
||||
std::fs::write(&file, out.as_bytes()).fs_context("failed to write plugin init code", file)?;
|
||||
std::fs::write(file, out.as_bytes())?;
|
||||
|
||||
if !options.no_fmt {
|
||||
// reformat code with rustfmt
|
||||
|
||||
@@ -4,19 +4,17 @@
|
||||
|
||||
use crate::{
|
||||
bundle::BundleFormat,
|
||||
error::{Context, ErrorExt},
|
||||
helpers::{
|
||||
self,
|
||||
app_paths::{frontend_dir, tauri_dir},
|
||||
app_paths::tauri_dir,
|
||||
config::{get as get_config, ConfigHandle, FrontendDist},
|
||||
},
|
||||
info::plugins::check_mismatched_packages,
|
||||
interface::{rust::get_cargo_target_dir, AppInterface, Interface},
|
||||
ConfigValue, Result,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use clap::{ArgAction, Parser};
|
||||
use std::env::set_current_dir;
|
||||
use tauri_utils::config::RunnerConfig;
|
||||
use std::{env::set_current_dir, fs};
|
||||
use tauri_utils::platform::Target;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
@@ -27,7 +25,7 @@ use tauri_utils::platform::Target;
|
||||
pub struct Options {
|
||||
/// Binary to use to build the application, defaults to `cargo`
|
||||
#[clap(short, long)]
|
||||
pub runner: Option<RunnerConfig>,
|
||||
pub runner: Option<String>,
|
||||
/// Builds with the debug flag
|
||||
#[clap(short, long)]
|
||||
pub debug: bool,
|
||||
@@ -61,33 +59,11 @@ pub struct Options {
|
||||
/// Skip prompting for values
|
||||
#[clap(long, env = "CI")]
|
||||
pub ci: bool,
|
||||
/// Whether to wait for notarization to finish and `staple` the ticket onto the app.
|
||||
///
|
||||
/// Gatekeeper will look for stapled tickets to tell whether your app was notarized without
|
||||
/// reaching out to Apple's servers which is helpful in offline environments.
|
||||
///
|
||||
/// Enabling this option will also result in `tauri build` not waiting for notarization to finish
|
||||
/// which is helpful for the very first time your app is notarized as this can take multiple hours.
|
||||
/// On subsequent runs, it's recommended to disable this setting again.
|
||||
#[clap(long)]
|
||||
pub skip_stapling: bool,
|
||||
/// Do not error out if a version mismatch is detected on a Tauri package.
|
||||
///
|
||||
/// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior.
|
||||
#[clap(long)]
|
||||
pub ignore_version_mismatches: bool,
|
||||
/// Skip code signing when bundling the app
|
||||
#[clap(long)]
|
||||
pub no_sign: bool,
|
||||
}
|
||||
|
||||
pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
|
||||
crate::helpers::app_paths::resolve();
|
||||
|
||||
if options.no_sign {
|
||||
log::warn!("--no-sign flag detected: Signing will be skipped.");
|
||||
}
|
||||
|
||||
let ci = options.ci;
|
||||
|
||||
let target = options
|
||||
@@ -111,10 +87,6 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
|
||||
let config_guard = config.lock().unwrap();
|
||||
let config_ = config_guard.as_ref().unwrap();
|
||||
|
||||
if let Some(minimum_system_version) = &config_.bundle.macos.minimum_system_version {
|
||||
std::env::set_var("MACOSX_DEPLOYMENT_TARGET", minimum_system_version);
|
||||
}
|
||||
|
||||
let app_settings = interface.app_settings();
|
||||
let interface_options = options.clone().into();
|
||||
|
||||
@@ -148,19 +120,7 @@ pub fn setup(
|
||||
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 options.ignore_version_mismatches {
|
||||
log::error!("{error}");
|
||||
} else {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
set_current_dir(tauri_path).context("failed to set current directory")?;
|
||||
set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
|
||||
|
||||
let config_guard = config.lock().unwrap();
|
||||
let config_ = config_guard.as_ref().unwrap();
|
||||
@@ -170,9 +130,11 @@ pub fn setup(
|
||||
.unwrap_or_else(|| "tauri.conf.json".into());
|
||||
|
||||
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.",
|
||||
log::error!(
|
||||
"You must change the bundle identifier in `{} identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.",
|
||||
bundle_identifier_source
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if config_
|
||||
@@ -180,19 +142,12 @@ pub fn setup(
|
||||
.chars()
|
||||
.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))
|
||||
{
|
||||
crate::error::bail!(
|
||||
log::error!(
|
||||
"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
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Some(before_build) = config_.build.before_build_command.clone() {
|
||||
@@ -206,27 +161,22 @@ pub fn setup(
|
||||
.and_then(|p| p.canonicalize().ok())
|
||||
.map(|p| p.join(web_asset_path.file_name().unwrap()))
|
||||
.unwrap_or_else(|| std::env::current_dir().unwrap().join(web_asset_path));
|
||||
crate::error::bail!(
|
||||
"Unable to find your web assets, did you forget to build your web app? Your frontendDist is set to \"{}\" (which is `{}`).",
|
||||
web_asset_path.display(), absolute_path.display(),
|
||||
);
|
||||
return Err(anyhow::anyhow!(
|
||||
"Unable to find your web assets, did you forget to build your web app? Your frontendDist is set to \"{}\" (which is `{}`).",
|
||||
web_asset_path.display(), absolute_path.display(),
|
||||
));
|
||||
}
|
||||
if web_asset_path
|
||||
.canonicalize()
|
||||
.fs_context("failed to canonicalize path", web_asset_path.to_path_buf())?
|
||||
.file_name()
|
||||
== Some(std::ffi::OsStr::new("src-tauri"))
|
||||
{
|
||||
crate::error::bail!(
|
||||
if web_asset_path.canonicalize()?.file_name() == Some(std::ffi::OsStr::new("src-tauri")) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"The configured frontendDist is the `src-tauri` folder. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.",
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
// 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 = fs::canonicalize(get_cargo_target_dir(&options.args)?)?;
|
||||
let mut out_folders = Vec::new();
|
||||
if let Ok(web_asset_canonical) = dunce::canonicalize(web_asset_path) {
|
||||
if let Ok(web_asset_canonical) = web_asset_path.canonicalize() {
|
||||
if let Ok(relative_path) = target_path.strip_prefix(&web_asset_canonical) {
|
||||
let relative_str = relative_path.to_string_lossy();
|
||||
if !relative_str.is_empty() {
|
||||
@@ -243,16 +193,16 @@ pub fn setup(
|
||||
}
|
||||
|
||||
if !out_folders.is_empty() {
|
||||
crate::error::bail!(
|
||||
return Err(anyhow::anyhow!(
|
||||
"The configured frontendDist includes the `{:?}` {}. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.",
|
||||
out_folders,
|
||||
if out_folders.len() == 1 { "folder" } else { "folders" }
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if options.runner.is_none() {
|
||||
options.runner = config_.build.runner.clone();
|
||||
options.runner.clone_from(&config_.build.runner);
|
||||
}
|
||||
|
||||
options
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user