Compare commits

..

1 Commits

Author SHA1 Message Date
Lucas Nogueira
7c6bb5dfd7 refactor: do not proxy dev server when using localhost on iOS/Android 2024-10-03 08:53:40 -03:00
328 changed files with 9909 additions and 16799 deletions

View File

@@ -4,7 +4,7 @@
"feat": "New Features",
"enhance": "Enhancements",
"bug": "Bug Fixes",
"perf": "Performance Improvements",
"pref": "Performance Improvements",
"changes": "What's Changed",
"sec": "Security fixes",
"deps": "Dependencies",

16
.changes/msrv-1.77.2.md Normal file
View File

@@ -0,0 +1,16 @@
---
"tauri": patch:changes
"tauri-utils": patch:changes
"tauri-runtime": patch:changes
"tauri-runtime-wry": patch:changes
"tauri-codegen": patch:changes
"tauri-macros": patch:changes
"tauri-plugin": patch:changes
"tauri-build": patch:changes
"tauri-cli": patch:changes
"tauri-macos-sign": patch:changes
"tauri-bundler": patch:changes
"tauri-driver": patch:changes
---
Downgrade MSRV to 1.77.2 to support Windows.

View File

@@ -53,12 +53,7 @@ First, [join our Discord server](https://discord.gg/SpmNs4S) and let us know tha
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:
```bash
pnpm install
pnpm build
```
Some Tauri packages will be automatically built when running one of the examples. Others, however, will need to be built beforehand. To build these automatically, run the `.scripts/setup.sh` (Linux and macOS) or `.scripts/setup.ps1` (Windows) script. This will install the Rust and Node.js CLI and build the JS API. After that, you should be able to run all the examples. Note that the setup script should be executed from the root folder of the repository in order to run correctly.
### Overview

View File

@@ -34,9 +34,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'

View File

@@ -44,9 +44,7 @@ jobs:
if: needs.changes.outputs.api == 'true'
steps:
- uses: actions/checkout@v4
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'

View File

@@ -23,9 +23,7 @@ jobs:
with:
fetch-depth: 0
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 20
@@ -38,7 +36,7 @@ jobs:
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev libfuse2 librsvg2-dev
sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev libfuse2
- uses: Swatinem/rust-cache@v2
@@ -75,9 +73,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 20
@@ -94,7 +90,7 @@ jobs:
- name: install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev librsvg2-dev
sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev
- name: covector version or publish (publish when no change files present)
uses: jbolda/covector/packages/action@covector-v0
@@ -115,7 +111,7 @@ jobs:
- name: Create Pull Request With Versions Bumped
if: steps.covector.outputs.commandRan == 'version'
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # 7.0.6
uses: tauri-apps/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: release/version-updates
@@ -128,7 +124,7 @@ jobs:
if: |
steps.covector.outputs.successfulPublish == 'true' &&
steps.covector.outputs.packagesPublished != ''
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # 3.0.0
uses: peter-evans/repository-dispatch@v1
with:
token: ${{ secrets.ORG_TAURI_BOT_PAT }}
repository: tauri-apps/tauri-docs

View File

@@ -66,9 +66,7 @@ jobs:
with:
targets: ${{ matrix.target.name }}
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- name: Setup node
uses: actions/setup-node@v4
with:

View File

@@ -26,9 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'

View File

@@ -19,9 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
@@ -33,9 +31,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'

View File

@@ -43,13 +43,11 @@ jobs:
- host: windows-latest
architecture: x64
target: aarch64-pc-windows-msvc
build: pnpm build --target aarch64-pc-windows-msvc
build: pnpm build --target aarch64-pc-windows-msvc --features native-tls-vendored --cargo-flags="--no-default-features"
- host: ubuntu-20.04
target: x86_64-unknown-linux-gnu
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
build: |
npm i -g --force corepack
corepack enable
cd packages/cli
pnpm build --target x86_64-unknown-linux-gnu
strip *.node
@@ -69,8 +67,6 @@ jobs:
target: aarch64-unknown-linux-gnu
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
build: |
npm i -g --force corepack
corepack enable
cd packages/cli
pnpm build --target aarch64-unknown-linux-gnu
aarch64-unknown-linux-gnu-strip *.node
@@ -92,22 +88,11 @@ jobs:
rustup target add aarch64-unknown-linux-musl
pnpm build --target aarch64-unknown-linux-musl
/aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node
- host: ubuntu-22.04
architecture: x64
target: riscv64gc-unknown-linux-gnu
setup: |
sudo apt-get update
sudo apt-get install gcc-riscv64-linux-gnu g++-riscv64-linux-gnu -y
build: |
pnpm build --target=riscv64gc-unknown-linux-gnu
riscv64-linux-gnu-strip *.node
name: stable - ${{ matrix.settings.target }} - node@20
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v4
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- name: Setup node
uses: actions/setup-node@v4
if: ${{ !matrix.settings.docker }}
@@ -123,6 +108,7 @@ jobs:
- uses: Swatinem/rust-cache@v1
with:
key: ${{ matrix.settings.target }}
working-directory: 'crates/tauri-cli/'
if: ${{ matrix.settings.docker }}
- name: Setup toolchain
run: ${{ matrix.settings.setup }}
@@ -217,9 +203,7 @@ jobs:
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v4
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- name: Setup node
uses: actions/setup-node@v4
with:
@@ -250,9 +234,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- name: Setup node
uses: actions/setup-node@v4
with:
@@ -271,7 +253,7 @@ jobs:
- name: install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev librsvg2-dev
sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev
- name: Test bindings
run: pnpm test
test-linux-x64-musl-binding:
@@ -289,9 +271,7 @@ jobs:
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
steps:
- uses: actions/checkout@v4
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- name: Setup node
uses: actions/setup-node@v4
with:
@@ -382,9 +362,7 @@ jobs:
id-token: write # npm provenance
steps:
- uses: actions/checkout@v4
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- name: Setup node
uses: actions/setup-node@v4
with:

View File

@@ -37,7 +37,7 @@ jobs:
- os: windows-latest
rust_target: aarch64-pc-windows-msvc
ext: '.exe'
args: ''
args: '--no-default-features --features native-tls-vendored'
steps:
- uses: actions/checkout@v4

View File

@@ -25,7 +25,7 @@ jobs:
- name: Install Rust
run: rustup update stable && rustup default stable
- uses: actions/cache@v4
- uses: actions/cache@v2
with:
path: ${{ runner.tool_cache }}/cargo-vet
key: cargo-vet-bin-${{ env.CARGO_VET_VERSION }}

View File

@@ -33,8 +33,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: install Rust 1.77.2
uses: dtolnay/rust-toolchain@1.77.2
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: install Linux dependencies
if: matrix.platform == 'ubuntu-latest'
@@ -42,9 +42,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.1
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- name: setup node
uses: actions/setup-node@v4
with:

View File

@@ -11,7 +11,6 @@ on:
pull_request:
paths:
- '.github/workflows/test-cli-js.yml'
- 'packages/cli/**'
# currently` @tauri-apps/cli` only tests the template
- 'crates/tauri-cli/templates/app/**'
@@ -38,9 +37,7 @@ jobs:
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
- run: |
npm i -g --force corepack
corepack enable
- run: corepack enable
- name: setup node
uses: actions/setup-node@v4
with:
@@ -51,7 +48,7 @@ jobs:
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev librsvg2-dev
sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev
- uses: Swatinem/rust-cache@v2

View File

@@ -44,7 +44,7 @@ jobs:
- uses: actions/checkout@v4
- name: 'Setup Rust'
uses: dtolnay/rust-toolchain@1.77.2
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform.target }}
@@ -52,7 +52,7 @@ jobs:
if: matrix.platform.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.1 libayatana-appindicator3-dev librsvg2-dev
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.1 libayatana-appindicator3-dev
- uses: Swatinem/rust-cache@v2

View File

@@ -88,6 +88,16 @@ jobs:
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.1 libxdo-dev libayatana-appindicator3-dev
- name: downgrade crates with MSRV conflict
run: |
cargo update -p ravif --precise 0.11.5
cargo update -p aws-config --precise 1.5.5
cargo update -p aws-sdk-ssooidc --precise 1.40.0
cargo update -p aws-sdk-s3 --precise 1.46.0
cargo update -p aws-sdk-sts --precise 1.39.0
cargo update -p aws-sdk-sso --precise 1.39.0
cargo update -p bitstream-io --precise 2.3.0
- uses: Swatinem/rust-cache@v2
with:
prefix-key: v2

2
.gitignore vendored
View File

@@ -13,8 +13,6 @@ node_modules/
# .vscode workspace settings file
.vscode/settings.json
.vscode/launch.json
.vscode/tasks.json
# npm, yarn and bun lock files
package-lock.json

View File

@@ -1,6 +1,5 @@
{
"singleQuote": true,
"semi": false,
"trailingComma": "none",
"experimentalOperatorPosition": "start"
"trailingComma": "none"
}

View File

@@ -28,8 +28,8 @@ const ignore = [
async function checkFile(file) {
if (
extensions.some((e) => file.endsWith(e))
&& !ignore.some((i) => file.includes(`/${i}/`) || path.basename(file) == i)
extensions.some((e) => file.endsWith(e)) &&
!ignore.some((i) => file.includes(`/${i}/`) || path.basename(file) == i)
) {
const fileStream = fs.createReadStream(file)
const rl = readline.createInterface({
@@ -42,11 +42,11 @@ async function checkFile(file) {
for await (let line of rl) {
// ignore empty lines, allow shebang and bundler license
if (
line.length === 0
|| line.startsWith('#!')
|| line.startsWith('// swift-tools-version:')
|| line === bundlerLicense
|| line === denoLicense
line.length === 0 ||
line.startsWith('#!') ||
line.startsWith('// swift-tools-version:') ||
line === bundlerLicense ||
line === denoLicense
) {
continue
}

3799
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -70,4 +70,3 @@ opt-level = "s"
[patch.crates-io]
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" }

View File

@@ -6,7 +6,7 @@ PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy
PackageHomePage: https://tauri.app
PackageLicenseDeclared: Apache-2.0
PackageLicenseDeclared: MIT
PackageCopyrightText: 2019-2025, The Tauri Programme in the Commons Conservancy
PackageCopyrightText: 2019-2024, The Tauri Programme in the Commons Conservancy
PackageSummary: <text>Tauri is a rust project that enables developers to make secure
and small desktop applications using a web frontend.
</text>

View File

@@ -35,7 +35,7 @@ The list of Tauri's features includes, but is not limited to:
- Built-in self updater (desktop only)
- System tray icons
- Native notifications
- Native WebView Protocol (tauri doesn't create a localhost http(s) server to serve the WebView contents)
- [Localhost free (🔥)](https://github.com/tauri-apps/tauri/issues/10510)
- GitHub action for streamlined CI
- VS Code extension
@@ -43,13 +43,13 @@ The list of Tauri's features includes, but is not limited to:
Tauri currently supports development and distribution on the following platforms:
| Platform | Versions |
| :--------- | :-------------------------------------------------------------------------------------------------------------- |
| Windows | 7 and above |
| macOS | 10.15 and above |
| Linux | webkit2gtk 4.0 for Tauri v1 (for example Ubuntu 18.04). webkit2gtk 4.1 for Tauri v2 (for example Ubuntu 22.04). |
| iOS/iPadOS | 9 and above |
| Android | 7 and above (currently 8 and above) |
| Platform | Versions |
| :---------------- | :-------------------------------------------------------------------------------------------------------------- |
| Windows | 7 and above |
| macOS | 10.15 and above |
| Linux | webkit2gtk 4.0 for Tauri v1 (for example Ubuntu 18.04). webkit2gtk 4.1 for Tauri v2 (for example Ubuntu 22.04). |
| iOS/iPadOS (beta) | 9 and above |
| Android (beta) | 7 and above |
## Contributing

View File

@@ -9,11 +9,11 @@ description = "Cross-platform WebView rendering library"
repository = "https://github.com/tauri-apps/wry"
[dependencies]
anyhow = "1"
anyhow = "1.0.40"
time = { version = "0.3", features = ["formatting"] }
tempfile = "3"
serde_json = "1"
serde = { version = "1", features = ["derive"] }
tempfile = "3.2.0"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
[[bin]]
name = "run_benchmark"

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! [![](https://github.com/tauri-apps/tauri/raw/dev/.github/splash.png)](https://tauri.app)
//!
//! 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_**

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! [![](https://github.com/tauri-apps/tauri/raw/dev/.github/splash.png)](https://tauri.app)
//!
//! 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_**

View File

@@ -1,7 +1,6 @@
body {
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
Arial, sans-serif;
margin: auto;
max-width: 38rem;
padding: 2rem;

View File

@@ -11,6 +11,6 @@ tauri-build = { path = "../../../../crates/tauri-build", features = [
] }
[dependencies]
serde_json = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { path = "../../../../crates/tauri", features = [] }

View File

@@ -11,6 +11,6 @@ tauri-build = { path = "../../../../crates/tauri-build", features = [
] }
[dependencies]
serde_json = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { path = "../../../../crates/tauri", features = [] }

View File

@@ -18,7 +18,7 @@ async fn read_file<R: Runtime>(app: AppHandle<R>) -> Result<Response, String> {
.path()
.resolve(".tauri_3mb.json", BaseDirectory::Home)
.map_err(|e| e.to_string())?;
let contents = read(path).map_err(|e| e.to_string())?;
let contents = read(&path).map_err(|e| e.to_string())?;
Ok(Response::new(contents))
}

View File

@@ -11,6 +11,6 @@ tauri-build = { path = "../../../../crates/tauri-build", features = [
] }
[dependencies]
serde_json = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { path = "../../../../crates/tauri", features = [] }

View File

@@ -1,72 +1,5 @@
# Changelog
## \[2.1.1]
### Dependencies
- Upgraded to `tauri-utils@2.3.1`
- Upgraded to `tauri-codegen@2.1.1`
## \[2.1.0]
### New Features
- [`013f8f652`](https://www.github.com/tauri-apps/tauri/commit/013f8f652302f2d49c5ec0a075582033d8b074fb) ([#12890](https://www.github.com/tauri-apps/tauri/pull/12890) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Added `build > removeUnusedCommands` to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. Note this won't be accounting for dynamically added ACLs so make sure to check it when using this.
### Performance Improvements
- [`1cd8f55ee`](https://www.github.com/tauri-apps/tauri/commit/1cd8f55eed326d61860fee62ba2d2f4464bdcfcc) ([#13033](https://www.github.com/tauri-apps/tauri/pull/13033) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Don't ship global `bundle.global.js` if `app > withGlobalTauri` is set to false
### Dependencies
- Upgraded to `tauri-utils@2.3.0`
- Upgraded to `tauri-codegen@2.1.0`
## \[2.0.6]
### Dependencies
- Upgraded to `tauri-utils@2.2.0`
- Upgraded to `tauri-codegen@2.0.5`
## \[2.0.5]
### Bug Fixes
- [`848d0e060`](https://www.github.com/tauri-apps/tauri/commit/848d0e060e6eb3c8e9e8175adc7896587b5a947d) ([#12270](https://www.github.com/tauri-apps/tauri/pull/12270) by [@aurelj](https://www.github.com/tauri-apps/tauri/../../aurelj)) Update `cargo_toml` to `0.21.0`. This adds compatibility with Rust's 2024 Edition.
- [`cd1d026f9`](https://www.github.com/tauri-apps/tauri/commit/cd1d026f9799c26b04acb64f49e7ee0a8b193049) ([#11961](https://www.github.com/tauri-apps/tauri/pull/11961) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix tauri fails to build if the project path contains glob characters
## \[2.0.4]
### Dependencies
- Upgraded to `tauri-utils@2.1.1`
- Upgraded to `tauri-codegen@2.0.4`
## \[2.0.3]
### Dependencies
- Upgraded to `tauri-utils@2.1.0`
- Upgraded to `tauri-codegen@2.0.3`
## \[2.0.2]
### Dependencies
- Upgraded to `tauri-utils@2.0.2`
## \[2.0.1]
### What's Changed
- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
### Dependencies
- Upgraded to `tauri-utils@2.0.1`
- Upgraded to `tauri-codegen@2.0.1`
## \[2.0.0]
### What's Changed

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-build"
version = "2.1.1"
version = "2.0.0"
description = "build time code to pair with https://crates.io/crates/tauri"
exclude = ["CHANGELOG.md", "/target"]
readme = "README.md"
@@ -28,23 +28,22 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
anyhow = "1"
quote = { version = "1", optional = true }
tauri-codegen = { version = "2.1.1", path = "../tauri-codegen", optional = true }
tauri-utils = { version = "2.3.1", path = "../tauri-utils", features = [
tauri-codegen = { version = "2.0.0", path = "../tauri-codegen", optional = true }
tauri-utils = { version = "2.0.0", path = "../tauri-utils", features = [
"build",
"resources",
] }
cargo_toml = "0.22"
cargo_toml = "0.17"
serde = "1"
serde_json = "1"
heck = "0.5"
json-patch = "3"
json-patch = "2.0"
walkdir = "2"
tauri-winres = "0.3"
tauri-winres = "0.1"
semver = "1"
dirs = "6"
dirs = "5"
glob = "0.3"
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]

View File

@@ -11,9 +11,7 @@ use std::{
use anyhow::{Context, Result};
use tauri_utils::{
acl::{
capability::Capability,
manifest::{Manifest, PermissionFile},
schema::CAPABILITIES_SCHEMA_FOLDER_PATH,
capability::Capability, manifest::Manifest, schema::CAPABILITIES_SCHEMA_FOLDER_PATH,
ACL_MANIFESTS_FILE_NAME, APP_ACL_KEY, CAPABILITIES_FILE_NAME,
},
platform::Target,
@@ -157,17 +155,11 @@ fn read_plugins_manifests() -> Result<BTreeMap<String, Manifest>> {
Ok(manifests)
}
struct InlinedPuginsAcl {
manifests: BTreeMap<String, Manifest>,
permission_files: BTreeMap<String, Vec<PermissionFile>>,
}
fn inline_plugins(
out_dir: &Path,
inlined_plugins: HashMap<&'static str, InlinedPlugin>,
) -> Result<InlinedPuginsAcl> {
) -> Result<BTreeMap<String, Manifest>> {
let mut acl_manifests = BTreeMap::new();
let mut permission_files_map = BTreeMap::new();
for (name, plugin) in inlined_plugins {
let plugin_out_dir = out_dir.join("plugins").join(name);
@@ -207,9 +199,7 @@ permissions = [{default_permissions}]
}
tauri_utils::acl::build::define_permissions(
&PathBuf::from(glob::Pattern::escape(&plugin_out_dir.to_string_lossy()))
.join("*")
.to_string_lossy(),
&plugin_out_dir.join("*").to_string_lossy(),
name,
&plugin_out_dir,
|_| true,
@@ -232,41 +222,28 @@ permissions = [{default_permissions}]
);
}
permission_files.extend(tauri_utils::acl::build::define_permissions(
&PathBuf::from(glob::Pattern::escape(
&default_permissions_path.to_string_lossy(),
))
.join("**")
.join("*")
.to_string_lossy(),
&default_permissions_path
.join("**")
.join("*")
.to_string_lossy(),
name,
&plugin_out_dir,
|_| true,
)?);
}
permission_files_map.insert(name.into(), permission_files.clone());
let manifest = tauri_utils::acl::manifest::Manifest::new(permission_files, None);
acl_manifests.insert(name.into(), manifest);
}
Ok(InlinedPuginsAcl {
manifests: acl_manifests,
permission_files: permission_files_map,
})
}
#[derive(Debug)]
struct AppManifestAcl {
manifest: Manifest,
permission_files: Vec<PermissionFile>,
Ok(acl_manifests)
}
fn app_manifest_permissions(
out_dir: &Path,
manifest: AppManifest,
inlined_plugins: &HashMap<&'static str, InlinedPlugin>,
) -> Result<AppManifestAcl> {
) -> Result<Manifest> {
let app_out_dir = out_dir.join("app-manifest");
fs::create_dir_all(&app_out_dir)?;
let pkg_name = "__app__";
@@ -309,7 +286,6 @@ fn app_manifest_permissions(
let inlined_plugins_permissions: Vec<_> = inlined_plugins
.keys()
.map(|name| permissions_root.join(name))
.flat_map(|p| p.canonicalize())
.collect();
permission_files.extend(tauri_utils::acl::build::define_permissions(
@@ -328,10 +304,10 @@ fn app_manifest_permissions(
)?);
}
Ok(AppManifestAcl {
permission_files: permission_files.clone(),
manifest: tauri_utils::acl::manifest::Manifest::new(permission_files, None),
})
Ok(tauri_utils::acl::manifest::Manifest::new(
permission_files,
None,
))
}
fn validate_capabilities(
@@ -400,21 +376,19 @@ fn validate_capabilities(
pub fn build(out_dir: &Path, target: Target, attributes: &Attributes) -> super::Result<()> {
let mut acl_manifests = read_plugins_manifests()?;
let app_acl = app_manifest_permissions(
let app_manifest = app_manifest_permissions(
out_dir,
attributes.app_manifest,
&attributes.inlined_plugins,
)?;
let has_app_manifest = app_acl.manifest.default_permission.is_some()
|| !app_acl.manifest.permission_sets.is_empty()
|| !app_acl.manifest.permissions.is_empty();
if has_app_manifest {
acl_manifests.insert(APP_ACL_KEY.into(), app_acl.manifest);
if app_manifest.default_permission.is_some()
|| !app_manifest.permission_sets.is_empty()
|| !app_manifest.permissions.is_empty()
{
acl_manifests.insert(APP_ACL_KEY.into(), app_manifest);
}
let inline_plugins_acl = inline_plugins(out_dir, attributes.inlined_plugins.clone())?;
acl_manifests.extend(inline_plugins_acl.manifests);
acl_manifests.extend(inline_plugins(out_dir, attributes.inlined_plugins.clone())?);
let acl_manifests_path = save_acl_manifests(&acl_manifests)?;
fs::copy(acl_manifests_path, out_dir.join(ACL_MANIFESTS_FILE_NAME))?;
@@ -432,12 +406,7 @@ pub fn build(out_dir: &Path, target: Target, attributes: &Attributes) -> super::
let capabilities_path = save_capabilities(&capabilities)?;
fs::copy(capabilities_path, out_dir.join(CAPABILITIES_FILE_NAME))?;
let mut permissions_map = inline_plugins_acl.permission_files;
if has_app_manifest {
permissions_map.insert(APP_ACL_KEY.to_string(), app_acl.permission_files);
}
tauri_utils::acl::build::generate_allowed_commands(out_dir, permissions_map)?;
tauri_utils::plugin::save_global_api_scripts_paths(out_dir);
Ok(())
}

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! [![](https://github.com/tauri-apps/tauri/raw/dev/.github/splash.png)](https://tauri.app)
//!
//! This applies the macros at build-time in order to rig some special features needed by `cargo`.
#![doc(
@@ -68,7 +70,7 @@ fn copy_binaries(
.to_string_lossy()
.replace(&format!("-{target_triple}"), "");
if package_name == Some(&file_name) {
if package_name.map_or(false, |n| n == &file_name) {
return Err(anyhow::anyhow!(
"Cannot define a sidecar with the same name as the Cargo package name `{}`. Please change the sidecar name in the filesystem and the Tauri configuration.",
file_name
@@ -419,26 +421,23 @@ pub fn is_dev() -> bool {
/// Run all build time helpers for your Tauri Application.
///
/// To provide extra configuration, such as [`AppManifest::commands`]
/// for fine-grained control over command permissions, see [`try_build`].
/// See [`Attributes`] for the complete list of configuration options.
/// The current helpers include the following:
/// * Generates a Windows Resource file when targeting Windows.
///
/// # Platforms
///
/// [`build()`] should be called inside of `build.rs` regardless of the platform, so **DO NOT** use a [conditional compilation]
/// check that prevents it from running on any of your targets.
/// [`build()`] should be called inside of `build.rs` regardless of the platform:
/// * New helpers may target more platforms in the future.
/// * Platform specific code is handled by the helpers automatically.
/// * A build script is required in order to activate some cargo environmental variables that are
/// used when generating code and embedding assets - so [`build()`] may as well be called.
///
/// Platform specific code is handled by the helpers automatically.
///
/// A build script is required in order to activate some cargo environmental variables that are
/// used when generating code and embedding assets.
/// In short, this is saying don't put the call to [`build()`] behind a `#[cfg(windows)]`.
///
/// # Panics
///
/// If any of the build time helpers fail, they will [`std::panic!`] with the related error message.
/// This is typically desirable when running inside a build script; see [`try_build`] for no panics.
///
/// [conditional compilation]: https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/conditional-compilation.html
pub fn build() {
if let Err(error) = try_build(Attributes::default()) {
let error = format!("{error:#}");
@@ -453,12 +452,18 @@ pub fn build() {
}
}
/// Same as [`build()`], but takes an extra configuration argument, and does not panic.
/// Non-panicking [`build()`].
#[allow(unused_variables)]
pub fn try_build(attributes: Attributes) -> Result<()> {
use anyhow::anyhow;
println!("cargo:rerun-if-env-changed=TAURI_CONFIG");
#[cfg(feature = "config-json")]
println!("cargo:rerun-if-changed=tauri.conf.json");
#[cfg(feature = "config-json5")]
println!("cargo:rerun-if-changed=tauri.conf.json5");
#[cfg(feature = "config-toml")]
println!("cargo:rerun-if-changed=Tauri.toml");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let mobile = target_os == "ios" || target_os == "android";
@@ -468,11 +473,12 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
let target_triple = env::var("TARGET").unwrap();
let target = tauri_utils::platform::Target::from_triple(&target_triple);
let (mut config, config_paths) =
tauri_utils::config::parse::read_from(target, &env::current_dir().unwrap())?;
for config_file_path in config_paths {
println!("cargo:rerun-if-changed={}", config_file_path.display());
let (config, merged_config_path) =
tauri_utils::config::parse::read_from(target, env::current_dir().unwrap())?;
if let Some(merged_config_path) = merged_config_path {
println!("cargo:rerun-if-changed={}", merged_config_path.display());
}
let mut config = serde_json::from_value(config)?;
if let Ok(env) = env::var("TAURI_CONFIG") {
let merge_config: serde_json::Value = serde_json::from_str(&env)?;
json_patch::merge(&mut config, &merge_config);
@@ -502,8 +508,19 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
cfg_alias("dev", is_dev());
let cargo_toml_path = Path::new("Cargo.toml").canonicalize()?;
let mut manifest = Manifest::<cargo_toml::Value>::from_path_with_metadata(cargo_toml_path)?;
let ws_path = get_workspace_dir()?;
let mut manifest =
Manifest::<cargo_toml::Value>::from_slice_with_metadata(&fs::read("Cargo.toml")?)?;
if let Ok(ws_manifest) = Manifest::from_path(ws_path.join("Cargo.toml")) {
Manifest::complete_from_path_and_workspace(
&mut manifest,
Path::new("Cargo.toml"),
Some((&ws_manifest, ws_path.as_path())),
)?;
} else {
Manifest::complete_from_path(&mut manifest, Path::new("Cargo.toml"))?;
}
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
@@ -511,8 +528,6 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
acl::build(&out_dir, target, &attributes)?;
tauri_utils::plugin::save_global_api_scripts_paths(&out_dir, None);
println!("cargo:rustc-env=TAURI_ENV_TARGET_TRIPLE={target_triple}");
// when running codegen in this build script, we need to access the env var directly
env::set_var("TAURI_ENV_TARGET_TRIPLE", &target_triple);
@@ -611,7 +626,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
if let Some(version_str) = &config.version {
if let Ok(v) = Version::parse(version_str) {
let version = (v.major << 48) | (v.minor << 32) | (v.patch << 16);
let version = v.major << 48 | v.minor << 32 | v.patch << 16;
res.set_version_info(VersionInfo::FILEVERSION, version);
res.set_version_info(VersionInfo::PRODUCTVERSION, version);
}
@@ -621,17 +636,6 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
res.set("ProductName", product_name);
}
let company_name = config.bundle.publisher.unwrap_or_else(|| {
config
.identifier
.split('.')
.nth(1)
.unwrap_or(&config.identifier)
.to_string()
});
res.set("CompanyName", &company_name);
let file_description = config
.product_name
.or_else(|| manifest.package.as_ref().map(|p| p.name.clone()))
@@ -684,7 +688,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
}
}
"msvc" => {
if env::var("STATIC_VCRUNTIME").is_ok_and(|v| v == "true") {
if env::var("STATIC_VCRUNTIME").map_or(false, |v| v == "true") {
static_vcruntime::build();
}
}
@@ -699,3 +703,23 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
Ok(())
}
#[derive(serde::Deserialize)]
struct CargoMetadata {
workspace_root: PathBuf,
}
fn get_workspace_dir() -> Result<PathBuf> {
let output = std::process::Command::new("cargo")
.args(["metadata", "--no-deps", "--format-version", "1"])
.output()?;
if !output.status.success() {
return Err(anyhow::anyhow!(
"cargo metadata command exited with a non zero exit code: {}",
String::from_utf8(output.stderr)?
));
}
Ok(serde_json::from_slice::<CargoMetadata>(&output.stdout)?.workspace_root)
}

View File

@@ -1,133 +1,5 @@
# Changelog
## \[2.3.1]
### Bug Fixes
- [`2138bbc21`](https://www.github.com/tauri-apps/tauri/commit/2138bbc21294785df5f4144670104387289f79c1) ([#13087](https://www.github.com/tauri-apps/tauri/pull/13087) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix NSIS installer displaying in wrong language if `SpanishInternational` is included
### Dependencies
- Upgraded to `tauri-utils@2.3.1`
## \[2.3.0]
### Enhancements
- [`f981a5ee8`](https://www.github.com/tauri-apps/tauri/commit/f981a5ee8b292b9ea09329f60cecc7f688dda734) ([#12602](https://www.github.com/tauri-apps/tauri/pull/12602) by [@kxxt](https://www.github.com/tauri-apps/tauri/../../kxxt)) Add basic support for linux riscv64 platform.
### Bug Fixes
- [`3626b7a92`](https://www.github.com/tauri-apps/tauri/commit/3626b7a92be2890a82e8d5bd00d13887e199ea4a) ([#12759](https://www.github.com/tauri-apps/tauri/pull/12759) by [@ninjadev64](https://www.github.com/tauri-apps/tauri/../../ninjadev64)) Fix resources being bundled to the wrong path during RPM bundling when resources are specified as a map.
- [`2b960dfd9`](https://www.github.com/tauri-apps/tauri/commit/2b960dfd9fdc995bd6474958c05783ff53b64b7e) ([#12643](https://www.github.com/tauri-apps/tauri/pull/12643) by [@animeshchaudhri](https://www.github.com/tauri-apps/tauri/../../animeshchaudhri)) Remove the autostart plugin registry entry when the app is uninstalled (NSIS only).
### Dependencies
- Upgraded to `tauri-utils@2.3.0`
## \[2.2.4]
### Enhancements
- [`5eba0785c`](https://www.github.com/tauri-apps/tauri/commit/5eba0785c461a0d0bec47653eaf6ccdf5f05d347) ([#12605](https://www.github.com/tauri-apps/tauri/pull/12605) by [@niusia-ua](https://www.github.com/tauri-apps/tauri/../../niusia-ua)) Added Ukrainian translation for the custom tauri messages in the nsis bundle
### Dependencies
- Upgraded to `tauri-utils@2.2.0`
- Upgraded to `tauri-macos-sign@2.1.0`
## \[2.2.3]
### Bug Fixes
- [`de8600b4d`](https://www.github.com/tauri-apps/tauri/commit/de8600b4d9a04e809e078c8aea61825d1328201f) ([#12471](https://www.github.com/tauri-apps/tauri/pull/12471) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) Bumped `nsis-tauri-utils` to `0.4.2` which fixes the following bugs:
- Fixed launch on start checkbox in nsis installer does not work well with applications that require elevated permissions
- Fixed nsis installer may fail to install if launched by updater plugin
- [`fbe7c9ead`](https://www.github.com/tauri-apps/tauri/commit/fbe7c9ead76e71ca258c6f48bbb62185fcc37b1c) ([#12466](https://www.github.com/tauri-apps/tauri/pull/12466) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the compiled AppImage to miss webkitgtk's internal `libwebkit2gtkinjectedbundle.so` file.
- [`f5a59b93b`](https://www.github.com/tauri-apps/tauri/commit/f5a59b93bfefb43ff131a7870b3c5d5e48c1ca1e) ([#12136](https://www.github.com/tauri-apps/tauri/pull/12136) by [@unknovvn](https://www.github.com/tauri-apps/tauri/../../unknovvn)) The NSIS bundler will now replace non-numeric build metadata with `0` instead of returning an error.
- [`9dac2863a`](https://www.github.com/tauri-apps/tauri/commit/9dac2863afa70fb0bcddf859b284afba917f28ae) ([#12323](https://www.github.com/tauri-apps/tauri/pull/12323) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Skip signing the .dmg if self signing via `"signingIdentity": "-"` is used.
- [`b8eb28877`](https://www.github.com/tauri-apps/tauri/commit/b8eb28877fe822dbe17999fc8af98ed7d0983679) ([#12427](https://www.github.com/tauri-apps/tauri/pull/12427) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Clean up `Software\${MANUFACTURER}\${PRODUCTNAME}` registry key in the NSIS uninstaller if "Delete application data" option is checked when uninstalling.
## \[2.2.2]
### Bug Fixes
- [`72748cc45`](https://www.github.com/tauri-apps/tauri/commit/72748cc45cf670dd03c86c8deceb5942598f5ad9) ([#12365](https://www.github.com/tauri-apps/tauri/pull/12365) by [@don41382](https://www.github.com/tauri-apps/tauri/../../don41382)) Fixed an issue that caused the `.msi` installer not to lookup the `INSTALLDIR` set in the `nsis` installer.
- [`cf771bf69`](https://www.github.com/tauri-apps/tauri/commit/cf771bf69aa26b62d11a54a69131c631505d8c55) ([#12402](https://www.github.com/tauri-apps/tauri/pull/12402) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the .msi installer to not contain root resources when there were .dll files present in the target directory.
- [`07ccdc499`](https://www.github.com/tauri-apps/tauri/commit/07ccdc499c3240e7240be3abf95ef2d7d00b2dc7) ([#12324](https://www.github.com/tauri-apps/tauri/pull/12324) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue leading to NSIS based installers to not contain the `WebView2Loader.dll` file when targetting `windows-gnu`.
## \[2.2.1]
### Bug Fixes
- [`cd1d026f9`](https://www.github.com/tauri-apps/tauri/commit/cd1d026f9799c26b04acb64f49e7ee0a8b193049) ([#11961](https://www.github.com/tauri-apps/tauri/pull/11961) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix tauri fails to build if the project path contains glob characters
## \[2.2.0]
### New Features
- [`cccb308c7`](https://www.github.com/tauri-apps/tauri/commit/cccb308c7b559b0838138d6cea280665f060c925) ([#11562](https://www.github.com/tauri-apps/tauri/pull/11562) by [@jLynx](https://www.github.com/tauri-apps/tauri/../../jLynx)) Generate signature for `.deb` packages when `createUpdaterArtifacts` option is enabled.
### Enhancements
- [`93a3a043d`](https://www.github.com/tauri-apps/tauri/commit/93a3a043d39cc96515d51d98beeb14261d3a246b) ([#11727](https://www.github.com/tauri-apps/tauri/pull/11727) by [@Kiyozz](https://www.github.com/tauri-apps/tauri/../../Kiyozz)) Add support for `Portuguese` language for NSIS windows installer.
- [`53f808674`](https://www.github.com/tauri-apps/tauri/commit/53f808674b2c0012bc44a41ced90e742afbb41e8) ([#11799](https://www.github.com/tauri-apps/tauri/pull/11799) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler now reads the `TAURI_BUNDLER_DMG_IGNORE_CI` env var to decide whether to check for `CI: true` when building DMG files.
### Dependencies
- Upgraded to `tauri-utils@2.1.1`
## \[2.1.0]
### New Features
- [`1b6b2cfaa`](https://www.github.com/tauri-apps/tauri/commit/1b6b2cfaa14ab1d418c676cedbf942a812377a30) ([#11521](https://www.github.com/tauri-apps/tauri/pull/11521) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Process `bundle > windows > wix > fragmentPaths` with Handlebars to interpolate expressions within it.
- [`6dea12a06`](https://www.github.com/tauri-apps/tauri/commit/6dea12a0677a905cb1f14969fe05c53e7cd717c6) ([#11402](https://www.github.com/tauri-apps/tauri/pull/11402) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > deb > recommends` and `bundle > linux > rpm > recommends` fields to declare a strong, but not absolute, dependency for your `.deb` and `.rpm` packages.
- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level.
### Dependencies
- Upgraded to `tauri-utils@2.1.0`
## \[2.0.4]
### New Features
- [`c8f55b615`](https://www.github.com/tauri-apps/tauri/commit/c8f55b615d2d98ade5c0f1896139dc283382a176) ([#11388](https://www.github.com/tauri-apps/tauri/pull/11388) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundler > windows > wix > version` to manually specify a wix-compatible version.
## \[2.0.3]
### New Features
- [`eda5713ea`](https://www.github.com/tauri-apps/tauri/commit/eda5713eab78d28182071ea25ceca5f1994f37ea) ([#11242](https://www.github.com/tauri-apps/tauri/pull/11242) by [@alex-sandri](https://www.github.com/tauri-apps/tauri/../../alex-sandri)) Add `Italian` to supported NSIS installer languages
### Enhancements
- [`504bb8ec8`](https://www.github.com/tauri-apps/tauri/commit/504bb8ec8cb294c5067357e18328580dd2b950c9) ([#11287](https://www.github.com/tauri-apps/tauri/pull/11287) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Pull upstream changes for the DMG creation script.
### Bug Fixes
- [`069c05e44`](https://www.github.com/tauri-apps/tauri/commit/069c05e44fd6f30083fdc00dd6c0001278898592) ([#11315](https://www.github.com/tauri-apps/tauri/pull/11315) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix CLI crashing and failing to find a `.ico` file when `bundle > icon` option is using globs and doesn't have a string that ends with `.ico`.
## \[2.0.2]
### Bug Fixes
- [`858b3516a`](https://www.github.com/tauri-apps/tauri/commit/858b3516a008ae5e6f2af489805896e2c142be10) ([#11217](https://www.github.com/tauri-apps/tauri/pull/11217) by [@kittuov](https://www.github.com/tauri-apps/tauri/../../kittuov)) On Windows, fixed command arguments for `bundle -> windows -> msi -> elevatedUpdateTask`. to work with spaces in `productName`
- [`a49a19ffa`](https://www.github.com/tauri-apps/tauri/commit/a49a19ffa304f031fb1a04d31a567cc7f42a380a) ([#11218](https://www.github.com/tauri-apps/tauri/pull/11218)) Fix bundling `appimage`, `deb` and `rpm` bundles failing to open when using `mainBinaryName` with spaces.
## \[2.0.1]
### What's Changed
- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
### Dependencies
- Upgraded to `tauri-utils@2.0.1`
- Upgraded to `tauri-macos-sign@2.0.1`
## \[2.0.0]
### What's Changed

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-bundler"
version = "2.3.1"
version = "2.0.0"
authors = [
"George Burton <burtonageo@gmail.com>",
"Tauri Programme within The Commons Conservancy",
@@ -15,30 +15,32 @@ rust-version = "1.77.2"
exclude = ["CHANGELOG.md", "/target", "rustfmt.toml"]
[dependencies]
tauri-utils = { version = "2.3.1", path = "../tauri-utils", features = [
tauri-utils = { version = "2.0.0", path = "../tauri-utils", features = [
"resources",
] }
image = "0.25"
flate2 = "1"
anyhow = "1"
thiserror = "2"
serde_json = "1"
serde = { version = "1", features = ["derive"] }
strsim = "0.11"
tar = "0.4"
image = "0.25.0"
flate2 = "1.0"
anyhow = "1.0"
thiserror = "1.0"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
strsim = "0.11.0"
tar = "0.4.40"
walkdir = "2"
handlebars = "6"
tempfile = "3"
tempfile = "3.10.1"
log = { version = "0.4.21", features = ["kv"] }
dirs = "6"
dirs = "5"
os_pipe = "1"
ureq = { version = "3", default-features = false, features = ["socks-proxy"] }
ureq = { version = "2.9.6", default-features = false, features = [
"socks-proxy",
] }
native-tls = { version = "0.2", optional = true }
hex = "0.4"
semver = "1"
sha1 = "0.10"
sha2 = "0.10"
zip = { version = "2", default-features = false, features = ["deflate"] }
zip = { version = "2.0", default-features = false, features = ["deflate"] }
dunce = "1"
url = "2"
uuid = { version = "1", features = ["v4", "v5"] }
@@ -46,7 +48,7 @@ regex = "1"
[target."cfg(target_os = \"windows\")".dependencies]
bitness = "0.4"
windows-registry = "0.5"
windows-registry = "0.2.0"
glob = "0.3"
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
@@ -57,13 +59,13 @@ features = ["Win32_System_SystemInformation", "Win32_System_Diagnostics_Debug"]
icns = { package = "tauri-icns", version = "0.1" }
time = { version = "0.3", features = ["formatting"] }
plist = "1"
tauri-macos-sign = { version = "2.1.0", path = "../tauri-macos-sign" }
tauri-macos-sign = { version = "2.0.0", path = "../tauri-macos-sign" }
[target."cfg(target_os = \"linux\")".dependencies]
heck = "0.5"
ar = "0.9"
md5 = "0.7"
rpm = { version = "0.16", features = ["bzip2-compression"] }
ar = "0.9.0"
md5 = "0.7.0"
rpm = "0.15.0"
[lib]
name = "tauri_bundler"
@@ -73,4 +75,4 @@ path = "src/lib.rs"
default = ["rustls"]
native-tls = ["ureq/native-tls"]
native-tls-vendored = ["native-tls", "native-tls/vendored"]
rustls = ["ureq/rustls"]
rustls = ["ureq/tls"]

View File

@@ -4,10 +4,12 @@
// SPDX-License-Identifier: MIT
mod category;
mod common;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
mod path_utils;
mod platform;
mod settings;
mod updater_bundle;
@@ -70,7 +72,8 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
// 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");
let skip =
std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").map_or(false, |v| v == "true");
if skip {
continue;
}
@@ -148,7 +151,6 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
| PackageType::MacOsBundle
| PackageType::Nsis
| PackageType::WindowsMsi
| PackageType::Deb
)
} else {
matches!(package_type, PackageType::MacOsBundle)
@@ -164,7 +166,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
// Self contained updater, no need to zip
matches!(
package_type,
PackageType::AppImage | PackageType::Nsis | PackageType::WindowsMsi | PackageType::Deb
PackageType::AppImage | PackageType::Nsis | PackageType::WindowsMsi
)
})
{

View File

@@ -249,7 +249,7 @@ struct AppCategoryVisitor {
did_you_mean: Option<&'static str>,
}
impl serde::de::Visitor<'_> for AppCategoryVisitor {
impl<'d> serde::de::Visitor<'d> for AppCategoryVisitor {
type Value = AppCategory;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@@ -4,11 +4,28 @@
// SPDX-License-Identifier: MIT
use std::{
ffi::OsStr,
fs::{self, File},
io::{self, BufWriter},
io::{self, BufRead, BufReader, BufWriter},
path::Path,
process::{Command, ExitStatus, Output, Stdio},
sync::{Arc, Mutex},
};
/// Returns true if the path has a filename indicating that it is a high-density
/// "retina" icon. Specifically, returns true the file stem ends with
/// "@2x" (a convention specified by the [Apple developer docs](
/// <https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html>)).
#[allow(dead_code)]
pub fn is_retina<P: AsRef<Path>>(path: P) -> bool {
path
.as_ref()
.file_stem()
.and_then(OsStr::to_str)
.map(|stem| stem.ends_with("@2x"))
.unwrap_or(false)
}
/// Creates a new file at the given path, creating any parent directories as
/// needed.
pub fn create_file(path: &Path) -> crate::Result<BufWriter<File>> {
@@ -19,36 +36,6 @@ pub fn create_file(path: &Path) -> crate::Result<BufWriter<File>> {
Ok(BufWriter::new(file))
}
/// Creates the given directory path,
/// erasing it first if specified.
#[allow(dead_code)]
pub fn create_dir(path: &Path, erase: bool) -> crate::Result<()> {
if erase && path.exists() {
remove_dir_all(path)?;
}
Ok(fs::create_dir(path)?)
}
/// Creates all of the directories of the specified path,
/// erasing it first if specified.
#[allow(dead_code)]
pub fn create_dir_all(path: &Path, erase: bool) -> crate::Result<()> {
if erase && path.exists() {
remove_dir_all(path)?;
}
Ok(fs::create_dir_all(path)?)
}
/// Removes the directory and its contents if it exists.
#[allow(dead_code)]
pub fn remove_dir_all(path: &Path) -> crate::Result<()> {
if path.exists() {
Ok(fs::remove_dir_all(path)?)
} else {
Ok(())
}
}
/// Makes a symbolic link to a directory.
#[cfg(unix)]
#[allow(dead_code)]
@@ -76,9 +63,11 @@ fn symlink_file(src: &Path, dst: &Path) -> io::Result<()> {
}
/// Copies a regular file from one path to another, creating any parent
/// directories of the destination path as necessary. Fails if the source path
/// directories of the destination path as necessary. Fails if the source path
/// is a directory or doesn't exist.
pub fn copy_file(from: &Path, to: &Path) -> crate::Result<()> {
pub fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> crate::Result<()> {
let from = from.as_ref();
let to = to.as_ref();
if !from.exists() {
return Err(crate::Error::GenericError(format!(
"{from:?} does not exist"
@@ -162,7 +151,7 @@ pub fn copy_custom_files(
pkg_path
};
if path.is_file() {
copy_file(path, &data_dir.join(pkg_path))?;
copy_file(path, data_dir.join(pkg_path))?;
} else {
copy_dir(path, &data_dir.join(pkg_path))?;
}
@@ -170,10 +159,93 @@ pub fn copy_custom_files(
Ok(())
}
pub trait CommandExt {
// The `pipe` function sets the stdout and stderr to properly
// show the command output in the Node.js wrapper.
fn piped(&mut self) -> std::io::Result<ExitStatus>;
fn output_ok(&mut self) -> crate::Result<Output>;
}
impl CommandExt for Command {
fn piped(&mut self) -> std::io::Result<ExitStatus> {
self.stdin(os_pipe::dup_stdin()?);
self.stdout(os_pipe::dup_stdout()?);
self.stderr(os_pipe::dup_stderr()?);
let program = self.get_program().to_string_lossy().into_owned();
log::debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
self.status().map_err(Into::into)
}
fn output_ok(&mut self) -> crate::Result<Output> {
let program = self.get_program().to_string_lossy().into_owned();
log::debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
self.stdout(Stdio::piped());
self.stderr(Stdio::piped());
let mut child = self.spawn()?;
let mut stdout = child.stdout.take().map(BufReader::new).unwrap();
let stdout_lines = Arc::new(Mutex::new(Vec::new()));
let stdout_lines_ = stdout_lines.clone();
std::thread::spawn(move || {
let mut line = String::new();
let mut lines = stdout_lines_.lock().unwrap();
loop {
line.clear();
match stdout.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stdout"; "{}", line.trim_end());
lines.extend(line.as_bytes().to_vec());
}
Err(_) => (),
}
}
});
let mut stderr = child.stderr.take().map(BufReader::new).unwrap();
let stderr_lines = Arc::new(Mutex::new(Vec::new()));
let stderr_lines_ = stderr_lines.clone();
std::thread::spawn(move || {
let mut line = String::new();
let mut lines = stderr_lines_.lock().unwrap();
loop {
line.clear();
match stderr.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stderr"; "{}", line.trim_end());
lines.extend(line.as_bytes().to_vec());
}
Err(_) => (),
}
}
});
let status = child.wait()?;
let output = Output {
status,
stdout: std::mem::take(&mut *stdout_lines.lock().unwrap()),
stderr: std::mem::take(&mut *stderr_lines.lock().unwrap()),
};
if output.status.success() {
Ok(output)
} else {
Err(crate::Error::GenericError(format!(
"failed to run {program}"
)))
}
}
}
#[cfg(test)]
mod tests {
use super::create_file;
use std::io::Write;
use super::{create_file, is_retina};
use std::{io::Write, path::PathBuf};
use tauri_utils::resources::resource_relpath;
#[test]
fn create_file_with_parent_dirs() {
@@ -191,8 +263,6 @@ mod tests {
#[cfg(not(windows))]
#[test]
fn copy_dir_with_symlinks() {
use std::path::PathBuf;
// Create a directory structure that looks like this:
// ${TMP}/orig/
// sub/
@@ -240,4 +310,26 @@ mod tests {
b"Hello, world!\n"
);
}
#[test]
fn retina_icon_paths() {
assert!(!is_retina("data/icons/512x512.png"));
assert!(is_retina("data/icons/512x512@2x.png"));
}
#[test]
fn resource_relative_paths() {
assert_eq!(
resource_relpath(&PathBuf::from("./data/images/button.png")),
PathBuf::from("data/images/button.png")
);
assert_eq!(
resource_relpath(&PathBuf::from("../../images/wheel.png")),
PathBuf::from("_up_/_up_/images/wheel.png")
);
assert_eq!(
resource_relpath(&PathBuf::from("/home/ferris/crab.png")),
PathBuf::from("_root_/home/ferris/crab.png")
);
}
}

View File

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

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# Copyright 2019-2024 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
set -euxo pipefail
export ARCH={{arch}}
APPIMAGE_BUNDLE_XDG_OPEN=${APPIMAGE_BUNDLE_XDG_OPEN-0}
APPIMAGE_BUNDLE_XDG_MIME=${APPIMAGE_BUNDLE_XDG_MIME-0}
APPIMAGE_BUNDLE_GSTREAMER=${APPIMAGE_BUNDLE_GSTREAMER-0}
TAURI_TRAY_LIBRARY_PATH=${TAURI_TRAY_LIBRARY_PATH-0}
if [ "$ARCH" == "i686" ]; then
linuxdeploy_arch="i386"
else
linuxdeploy_arch="$ARCH"
fi
mkdir -p "{{product_name}}.AppDir"
cp -r ../appimage_deb/data/usr "{{product_name}}.AppDir"
cd "{{product_name}}.AppDir"
mkdir -p "usr/bin"
mkdir -p "usr/lib"
if [[ "$APPIMAGE_BUNDLE_XDG_OPEN" != "0" ]] && [[ -f "/usr/bin/xdg-open" ]]; then
echo "Copying /usr/bin/xdg-open"
cp /usr/bin/xdg-open usr/bin
fi
if [[ "$APPIMAGE_BUNDLE_XDG_MIME" != "0" ]] && [[ -f "/usr/bin/xdg-mime" ]]; then
echo "Copying /usr/bin/xdg-mime"
cp /usr/bin/xdg-mime usr/bin
fi
if [[ "$TAURI_TRAY_LIBRARY_PATH" != "0" ]]; then
echo "Copying appindicator library ${TAURI_TRAY_LIBRARY_PATH}"
cp ${TAURI_TRAY_LIBRARY_PATH} usr/lib
# It looks like we're practicing good hygiene by adding the ABI version.
# But for compatibility we'll symlink this file to what we did before.
# Specifically this prevents breaking libappindicator-sys v0.7.1 and v0.7.2.
if [[ "$TAURI_TRAY_LIBRARY_PATH" == *.so.1 ]]; then
readonly soname=$(basename "$TAURI_TRAY_LIBRARY_PATH")
readonly old_name=$(basename "$TAURI_TRAY_LIBRARY_PATH" .1)
echo "Adding compatibility symlink ${old_name} -> ${soname}"
ln -s ${soname} usr/lib/${old_name}
fi
fi
# Copy WebKit files. Follow symlinks in case `/usr/lib64` is a symlink to `/usr/lib`
find -L /usr/lib* -name WebKitNetworkProcess -exec mkdir -p "$(dirname '{}')" \; -exec cp --parents '{}' "." \; || true
find -L /usr/lib* -name WebKitWebProcess -exec mkdir -p "$(dirname '{}')" \; -exec cp --parents '{}' "." \; || true
find -L /usr/lib* -name libwebkit2gtkinjectedbundle.so -exec mkdir -p "$(dirname '{}')" \; -exec cp --parents '{}' "." \; || true
( cd "{{tauri_tools_path}}" && ( wget -q -4 -N https://github.com/AppImage/AppImageKit/releases/download/continuous/AppRun-${ARCH} || wget -q -4 -N https://github.com/AppImage/AppImageKit/releases/download/12/AppRun-${ARCH} ) )
chmod +x "{{tauri_tools_path}}/AppRun-${ARCH}"
# We need AppRun to be installed as {{product_name}}.AppDir/AppRun.
# Otherwise the linuxdeploy scripts will default to symlinking our main bin instead and will crash on trying to launch.
cp "{{tauri_tools_path}}/AppRun-${ARCH}" AppRun
cp "{{icon_path}}" .DirIcon
ln -sf "{{icon_path}}" "{{product_name}}.png"
ln -sf "usr/share/applications/{{product_name}}.desktop" "{{product_name}}.desktop"
cd ..
if [[ "$APPIMAGE_BUNDLE_GSTREAMER" != "0" ]]; then
gst_plugin="--plugin gstreamer"
wget -q -4 -N "https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh"
chmod +x linuxdeploy-plugin-gstreamer.sh
else
gst_plugin=""
fi
( cd "{{tauri_tools_path}}" && wget -q -4 -N https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh )
( cd "{{tauri_tools_path}}" && wget -q -4 -N https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-${linuxdeploy_arch}.AppImage )
chmod +x "{{tauri_tools_path}}/linuxdeploy-plugin-gtk.sh"
chmod +x "{{tauri_tools_path}}/linuxdeploy-${linuxdeploy_arch}.AppImage"
dd if=/dev/zero bs=1 count=3 seek=8 conv=notrunc of="{{tauri_tools_path}}/linuxdeploy-${linuxdeploy_arch}.AppImage"
OUTPUT="{{appimage_filename}}" "{{tauri_tools_path}}/linuxdeploy-${linuxdeploy_arch}.AppImage" --appimage-extract-and-run --appdir "{{product_name}}.AppDir" --plugin gtk ${gst_plugin} --output appimage

View File

@@ -0,0 +1,124 @@
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{
super::{
common::{self, CommandExt},
path_utils,
},
debian,
};
use crate::{bundle::settings::Arch, Settings};
use anyhow::Context;
use handlebars::Handlebars;
use std::{
collections::BTreeMap,
fs::{remove_dir_all, write},
path::PathBuf,
process::{Command, Stdio},
};
/// Bundles the project.
/// Returns a vector of PathBuf that shows where the AppImage was created.
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
// generate the deb binary name
let arch: &str = match settings.binary_arch() {
Arch::X86_64 => "amd64",
Arch::X86 => "i386",
Arch::AArch64 => "aarch64",
Arch::Armhf => "armhf",
target => {
return Err(crate::Error::ArchError(format!(
"Unsupported architecture: {:?}",
target
)));
}
};
let package_dir = settings.project_out_directory().join("bundle/appimage_deb");
// generate deb_folder structure
let (data_dir, icons) = debian::generate_data(settings, &package_dir)
.with_context(|| "Failed to build data folders and files")?;
common::copy_custom_files(&settings.appimage().files, &data_dir)
.with_context(|| "Failed to copy custom files")?;
let output_path = settings.project_out_directory().join("bundle/appimage");
if output_path.exists() {
remove_dir_all(&output_path)?;
}
std::fs::create_dir_all(output_path.clone())?;
let app_dir_path = output_path.join(format!("{}.AppDir", settings.product_name()));
let appimage_filename = format!(
"{}_{}_{}.AppImage",
settings.product_name(),
settings.version_string(),
arch
);
let appimage_path = output_path.join(&appimage_filename);
path_utils::create(app_dir_path, true)?;
// setup data to insert into shell script
let mut sh_map = BTreeMap::new();
sh_map.insert("arch", settings.target().split('-').next().unwrap());
sh_map.insert("product_name", settings.product_name());
sh_map.insert("appimage_filename", &appimage_filename);
let tauri_tools_path = settings
.local_tools_directory()
.map(|d| d.join(".tauri"))
.unwrap_or_else(|| {
dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join("tauri"))
});
std::fs::create_dir_all(&tauri_tools_path)?;
let tauri_tools_path_str = tauri_tools_path.to_string_lossy();
sh_map.insert("tauri_tools_path", &tauri_tools_path_str);
let larger_icon = icons
.iter()
.filter(|i| i.width == i.height)
.max_by_key(|i| i.width)
.expect("couldn't find a square icon to use as AppImage icon");
let larger_icon_path = larger_icon
.path
.strip_prefix(package_dir.join("data"))
.unwrap()
.to_string_lossy()
.to_string();
sh_map.insert("icon_path", &larger_icon_path);
// initialize shell script template.
let mut handlebars = Handlebars::new();
handlebars.register_escape_fn(handlebars::no_escape);
handlebars
.register_template_string("appimage", include_str!("./appimage"))
.expect("Failed to register template for handlebars");
let temp = handlebars.render("appimage", &sh_map)?;
// create the shell script file in the target/ folder.
let sh_file = output_path.join("build_appimage.sh");
log::info!(action = "Bundling"; "{} ({})", appimage_filename, appimage_path.display());
write(&sh_file, temp)?;
// chmod script for execution
Command::new("chmod")
.arg("777")
.arg(&sh_file)
.current_dir(output_path.clone())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("Failed to chmod script");
// execute the shell script to build the appimage.
Command::new(&sh_file)
.current_dir(output_path)
.output_ok()
.context("error running build_appimage.sh")?;
remove_dir_all(&package_dir)?;
Ok(vec![appimage_path])
}

View File

@@ -23,8 +23,8 @@
// metadata, as well as generating the md5sums file. Currently we do not
// generate postinst or prerm files.
use super::freedesktop;
use crate::{bundle::settings::Arch, utils::fs_utils, Settings};
use super::{super::common, freedesktop};
use crate::{bundle::settings::Arch, Settings};
use anyhow::Context;
use flate2::{write::GzEncoder, Compression};
use tar::HeaderMode;
@@ -46,7 +46,6 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
Arch::AArch64 => "arm64",
Arch::Armhf => "armhf",
Arch::Armel => "armel",
Arch::Riscv64 => "riscv64",
target => {
return Err(crate::Error::ArchError(format!(
"Unsupported architecture: {:?}",
@@ -74,7 +73,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
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)
common::copy_custom_files(&settings.deb().files, &data_dir)
.with_context(|| "Failed to copy custom files")?;
// Generate control files.
@@ -114,7 +113,7 @@ pub fn generate_data(
for bin in settings.binaries() {
let bin_path = settings.binary_path(bin);
fs_utils::copy_file(&bin_path, &bin_dir.join(bin.name()))
common::copy_file(&bin_path, bin_dir.join(bin.name()))
.with_context(|| format!("Failed to copy binary from {bin_path:?}"))?;
}
@@ -142,7 +141,7 @@ fn generate_changelog_file(settings: &Settings, data_dir: &Path) -> crate::Resul
let product_name = settings.product_name();
let dest_path = data_dir.join(format!("usr/share/doc/{product_name}/changelog.gz"));
let changelog_file = fs_utils::create_file(&dest_path)?;
let changelog_file = common::create_file(&dest_path)?;
let mut gzip_encoder = GzEncoder::new(changelog_file, Compression::new(9));
io::copy(&mut src_file, &mut gzip_encoder)?;
@@ -162,7 +161,7 @@ fn generate_control_file(
// For more information about the format of this file, see
// https://www.debian.org/doc/debian-policy/ch-controlfields.html
let dest_path = control_dir.join("control");
let mut file = fs_utils::create_file(&dest_path)?;
let mut file = common::create_file(&dest_path)?;
let package = heck::AsKebabCase(settings.product_name());
writeln!(file, "Package: {}", package)?;
writeln!(file, "Version: {}", settings.version_string())?;
@@ -199,15 +198,6 @@ fn generate_control_file(
if !dependencies.is_empty() {
writeln!(file, "Depends: {}", dependencies.join(", "))?;
}
let dependencies = settings
.deb()
.recommends
.as_ref()
.cloned()
.unwrap_or_default();
if !dependencies.is_empty() {
writeln!(file, "Recommends: {}", dependencies.join(", "))?;
}
let provides = settings
.deb()
.provides
@@ -295,7 +285,7 @@ fn create_script_file_from_path(from: &PathBuf, to: &PathBuf) -> crate::Result<(
/// for each file within the `data_dir`.
fn generate_md5sums(control_dir: &Path, data_dir: &Path) -> crate::Result<()> {
let md5sums_path = control_dir.join("md5sums");
let mut md5sums_file = fs_utils::create_file(&md5sums_path)?;
let mut md5sums_file = common::create_file(&md5sums_path)?;
for entry in WalkDir::new(data_dir) {
let entry = entry?;
let path = entry.path();
@@ -328,7 +318,7 @@ fn copy_resource_files(settings: &Settings, data_dir: &Path) -> crate::Result<()
/// Create an empty file at the given path, creating any parent directories as
/// needed, then write `data` into the file.
fn create_file_with_data<P: AsRef<Path>>(path: P, data: &str) -> crate::Result<()> {
let mut file = fs_utils::create_file(path.as_ref())?;
let mut file = common::create_file(path.as_ref())?;
file.write_all(data.as_bytes())?;
file.flush()?;
Ok(())
@@ -377,7 +367,7 @@ fn create_tar_from_dir<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> cr
fn tar_and_gzip_dir<P: AsRef<Path>>(src_dir: P) -> crate::Result<PathBuf> {
let src_dir = src_dir.as_ref();
let dest_path = src_dir.with_extension("tar.gz");
let dest_file = fs_utils::create_file(&dest_path)?;
let dest_file = common::create_file(&dest_path)?;
let gzip_encoder = GzEncoder::new(dest_file, Compression::default());
let gzip_encoder = create_tar_from_dir(src_dir, gzip_encoder)?;
let mut dest_file = gzip_encoder.finish()?;
@@ -388,7 +378,7 @@ fn tar_and_gzip_dir<P: AsRef<Path>>(src_dir: P) -> crate::Result<PathBuf> {
/// Creates an `ar` archive from the given source files and writes it to the
/// given destination path.
fn create_archive(srcs: Vec<PathBuf>, dest: &Path) -> crate::Result<()> {
let mut builder = ar::Builder::new(fs_utils::create_file(dest)?);
let mut builder = ar::Builder::new(common::create_file(dest)?);
for path in &srcs {
builder.append_path(path)?;
}

View File

@@ -26,10 +26,8 @@ use handlebars::Handlebars;
use image::{self, codecs::png::PngDecoder, ImageDecoder};
use serde::Serialize;
use crate::{
utils::{self, fs_utils},
Settings,
};
use crate::bundle::common;
use crate::Settings;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct Icon {
@@ -67,7 +65,7 @@ pub fn list_icon_files(
let decoder = PngDecoder::new(BufReader::new(File::open(&icon_path)?))?;
let width = decoder.dimensions().0;
let height = decoder.dimensions().1;
let is_high_density = utils::is_retina(&icon_path);
let is_high_density = common::is_retina(&icon_path);
let dest_path = get_dest_path(width, height, is_high_density);
Icon {
width,
@@ -86,7 +84,7 @@ pub fn list_icon_files(
pub fn copy_icon_files(settings: &Settings, data_dir: &Path) -> crate::Result<Vec<Icon>> {
let icons = list_icon_files(settings, data_dir)?;
for (icon, src) in &icons {
fs_utils::copy_file(src, &icon.path)?;
common::copy_file(src, &icon.path)?;
}
Ok(icons.into_keys().collect())
@@ -101,13 +99,12 @@ pub fn generate_desktop_file(
data_dir: &Path,
) -> crate::Result<(PathBuf, PathBuf)> {
let bin_name = settings.main_binary_name()?;
let product_name = settings.product_name();
let desktop_file_name = format!("{product_name}.desktop");
let path = PathBuf::from("usr/share/applications").join(desktop_file_name);
let dest_path = PathBuf::from("/").join(&path);
let file_path = data_dir.join(&path);
let file = &mut fs_utils::create_file(&file_path)?;
let file = &mut common::create_file(&file_path)?;
let mut handlebars = Handlebars::new();
handlebars.register_escape_fn(handlebars::no_escape);
@@ -153,12 +150,6 @@ pub fn generate_desktop_file(
let mime_type = (!mime_type.is_empty()).then_some(mime_type.join(";"));
let bin_name_exec = if bin_name.contains(' ') {
format!("\"{bin_name}\"")
} else {
bin_name.to_string()
};
handlebars.render_to_write(
"main.desktop",
&DesktopTemplateParams {
@@ -171,7 +162,7 @@ pub fn generate_desktop_file(
} else {
None
},
exec: &bin_name_exec,
exec: bin_name,
icon: bin_name,
name: settings.product_name(),
mime_type,

View File

@@ -12,7 +12,6 @@ use std::{
fs::{self, File},
path::{Path, PathBuf},
};
use tauri_utils::config::RpmCompression;
use super::freedesktop;
@@ -29,7 +28,6 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
Arch::AArch64 => "aarch64",
Arch::Armhf => "armhfp",
Arch::Armel => "armel",
Arch::Riscv64 => "riscv64",
target => {
return Err(crate::Error::ArchError(format!(
"Unsupported architecture: {:?}",
@@ -56,25 +54,11 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
let license = settings.license().unwrap_or_default();
let name = heck::AsKebabCase(settings.product_name()).to_string();
let compression = settings
.rpm()
.compression
.map(|c| match c {
RpmCompression::Gzip { level } => rpm::CompressionWithLevel::Gzip(level),
RpmCompression::Zstd { level } => rpm::CompressionWithLevel::Zstd(level),
RpmCompression::Xz { level } => rpm::CompressionWithLevel::Xz(level),
RpmCompression::Bzip2 { level } => rpm::CompressionWithLevel::Bzip2(level),
_ => rpm::CompressionWithLevel::None,
})
// This matches .deb compression. On a 240MB source binary the bundle will be 100KB larger than rpm's default while reducing build times by ~25%.
// TODO: Default to Zstd in v3 to match rpm-rs new default in 0.16
.unwrap_or(rpm::CompressionWithLevel::Gzip(6));
let mut builder = rpm::PackageBuilder::new(&name, version, &license, arch, summary)
.epoch(epoch)
.release(release)
.compression(compression);
// This matches .deb compression. On a 240MB source binary the bundle will be 100KB larger than rpm's default while reducing build times by ~25%.
.compression(rpm::CompressionWithLevel::Gzip(6));
if let Some(description) = settings.long_description() {
builder = builder.description(description);
@@ -100,17 +84,6 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
builder = builder.provides(Dependency::any(dep));
}
// Add recommends
for dep in settings
.rpm()
.recommends
.as_ref()
.cloned()
.unwrap_or_default()
{
builder = builder.recommends(Dependency::any(dep));
}
// Add conflicts
for dep in settings
.rpm()
@@ -187,10 +160,10 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
FileOptions::new(resource_dir.to_string_lossy()).mode(FileMode::Dir { permissions: 0o755 }),
)?;
// Then add the resources files in that directory
for resource in settings.resource_files().iter() {
let resource = resource?;
let dest = resource_dir.join(resource.target());
builder = builder.with_file(resource.path(), FileOptions::new(dest.to_string_lossy()))?;
for src in settings.resource_files() {
let src = src?;
let dest = resource_dir.join(tauri_utils::resources::resource_relpath(&src));
builder = builder.with_file(&src, FileOptions::new(dest.to_string_lossy()))?;
}
}

View File

@@ -23,13 +23,11 @@
// files into the `Contents` directory of the bundle.
use super::{
super::common::{self, CommandExt},
icon::create_icns_file,
sign::{notarize, notarize_auth, sign, NotarizeAuthError, SignTarget},
};
use crate::{
utils::{fs_utils, CommandExt},
Settings,
};
use crate::Settings;
use anyhow::Context;
@@ -159,7 +157,7 @@ fn copy_binaries_to_bundle(
for bin in settings.binaries() {
let bin_path = settings.binary_path(bin);
let dest_path = dest_dir.join(bin.name());
fs_utils::copy_file(&bin_path, &dest_path)
common::copy_file(&bin_path, &dest_path)
.with_context(|| format!("Failed to copy binary from {:?}", bin_path))?;
paths.push(dest_path);
}
@@ -175,10 +173,10 @@ fn copy_custom_files_to_bundle(bundle_directory: &Path, settings: &Settings) ->
contents_path
};
if path.is_file() {
fs_utils::copy_file(path, &bundle_directory.join(contents_path))
common::copy_file(path, bundle_directory.join(contents_path))
.with_context(|| format!("Failed to copy file {:?} to {:?}", path, contents_path))?;
} else {
fs_utils::copy_dir(path, &bundle_directory.join(contents_path))
common::copy_dir(path, &bundle_directory.join(contents_path))
.with_context(|| format!("Failed to copy directory {:?} to {:?}", path, contents_path))?;
}
}
@@ -351,7 +349,7 @@ fn copy_framework_from(dest_dir: &Path, framework: &str, src_dir: &Path) -> crat
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))?;
common::copy_dir(&src_path, &dest_dir.join(&src_name))?;
Ok(true)
} else {
Ok(false)
@@ -384,7 +382,7 @@ fn copy_frameworks_to_bundle(
.file_name()
.expect("Couldn't get framework filename");
let dest_path = dest_dir.join(src_name);
fs_utils::copy_dir(&src_path, &dest_path)?;
common::copy_dir(&src_path, &dest_path)?;
add_framework_sign_path(&src_path, &dest_path, &mut paths);
continue;
} else if framework.ends_with(".dylib") {
@@ -397,7 +395,7 @@ fn copy_frameworks_to_bundle(
}
let src_name = src_path.file_name().expect("Couldn't get library filename");
let dest_path = dest_dir.join(src_name);
fs_utils::copy_file(&src_path, &dest_path)?;
common::copy_file(&src_path, &dest_path)?;
paths.push(SignTarget {
path: dest_path,
is_an_executable: false,

View File

@@ -4,63 +4,13 @@
# SPDX-License-Identifier: MIT
# Create a read-only disk image of the contents of a folder
# forked from https://github.com/create-dmg/create-dmg
# forked from https://github.com/andreyvit/create-dmg
# Bail out on any unhandled errors
set -e;
# Any command that exits with non-zero code will cause the pipeline to fail
set -o pipefail;
CDMG_VERSION='1.2.1'
# The full path to the "support/" directory this script is using
# (This will be set up by code later in the script.)
CDMG_SUPPORT_DIR=""
OS_FULL_VERSION="$(sw_vers | sed -n 2p | cut -d : -f 2 | tr -d '[:space:]' | cut -c1-)"
OS_MAJOR_VERSION="$(echo $OS_FULL_VERSION | cut -d . -f 1)"
OS_MINOR_VERSION="$(echo $OS_FULL_VERSION | cut -d . -f 2)"
WINX=10
WINY=60
WINW=500
WINH=350
ICON_SIZE=128
TEXT_SIZE=16
FORMAT="UDZO"
FILESYSTEM="HFS+"
ADD_FILE_SOURCES=()
ADD_FILE_TARGETS=()
IMAGEKEY=""
HDIUTIL_VERBOSITY=""
SANDBOX_SAFE=0
BLESS=0
SKIP_JENKINS=0
MAXIMUM_UNMOUNTING_ATTEMPTS=3
SIGNATURE=""
NOTARIZE=""
set -ex;
function pure_version() {
echo "$CDMG_VERSION"
}
function hdiutil_detach_retry() {
# Unmount
unmounting_attempts=0
until
echo "Unmounting disk image..."
(( unmounting_attempts++ ))
hdiutil detach "$1"
exit_code=$?
(( exit_code == 0 )) && break # nothing goes wrong
(( exit_code != 16 )) && exit $exit_code # exit with the original exit code
# The above statement returns 1 if test failed (exit_code == 16).
# It can make the code in the {do... done} block to be executed
do
(( unmounting_attempts == MAXIMUM_UNMOUNTING_ATTEMPTS )) && exit 16 # patience exhausted, exit with code EBUSY
echo "Wait a moment..."
sleep $(( 1 * (2 ** unmounting_attempts) ))
done
unset unmounting_attempts
echo '1.0.0.6'
}
function version() {
@@ -69,226 +19,180 @@ function version() {
function usage() {
version
cat <<EOHELP
Creates a fancy DMG file.
Usage: $(basename $0) [options] <output_name.dmg> <source_folder>
All contents of <source_folder> will be copied into the disk image.
Options:
--volname <name>
set volume name (displayed in the Finder sidebar and window title)
--volicon <icon.icns>
set volume icon
--background <pic.png>
set folder background image (provide png, gif, or jpg)
--window-pos <x> <y>
set position the folder window
--window-size <width> <height>
set size of the folder window
--text-size <text_size>
set window text size (10-16)
--icon-size <icon_size>
set window icons size (up to 128)
--icon file_name <x> <y>
set position of the file's icon
--hide-extension <file_name>
hide the extension of file
--app-drop-link <x> <y>
make a drop link to Applications, at location x,y
--ql-drop-link <x> <y>
make a drop link to user QuickLook install dir, at location x,y
--eula <eula_file>
attach a license file to the dmg (plain text or RTF)
--no-internet-enable
disable automatic mount & copy
--format <format>
specify the final disk image format (UDZO|UDBZ|ULFO|ULMO) (default is UDZO)
--filesystem <filesystem>
specify the disk image filesystem (HFS+|APFS) (default is HFS+, APFS supports macOS 10.13 or newer)
--encrypt
enable encryption for the resulting disk image (AES-256 - you will be prompted for password)
--encrypt-aes128
enable encryption for the resulting disk image (AES-128 - you will be prompted for password)
--add-file <target_name> <file>|<folder> <x> <y>
add additional file or folder (can be used multiple times)
--disk-image-size <x>
set the disk image size manually to x MB
--hdiutil-verbose
execute hdiutil in verbose mode
--hdiutil-quiet
execute hdiutil in quiet mode
--bless
bless the mount folder (deprecated, needs macOS 12.2.1 or older)
--codesign <signature>
codesign the disk image with the specified signature
--notarize <credentials>
notarize the disk image (waits and staples) with the keychain stored credentials
--sandbox-safe
execute hdiutil with sandbox compatibility and do not bless (not supported for APFS disk images)
--skip-jenkins
skip Finder-prettifying AppleScript, useful in Sandbox and non-GUI environments
--version
show create-dmg version number
-h, --help
display this help screen
EOHELP
echo "Creates a fancy DMG file."
echo "Usage: $(basename "$0") [options] <output_name.dmg> <source_folder>"
echo "All contents of <source_folder> will be copied into the disk image."
echo "Options:"
echo " --volname name"
echo " set volume name (displayed in the Finder sidebar and window title)"
echo " --volicon icon.icns"
echo " set volume icon"
echo " --background pic.png"
echo " set folder background image (provide png, gif, jpg)"
echo " --window-pos x y"
echo " set position the folder window"
echo " --window-size width height"
echo " set size of the folder window"
echo " --text-size text_size"
echo " set window text size (10-16)"
echo " --icon-size icon_size"
echo " set window icons size (up to 128)"
echo " --icon file_name x y"
echo " set position of the file's icon"
echo " --hide-extension file_name"
echo " hide the extension of file"
echo " --app-drop-link x y"
echo " make a drop link to Applications, at location x,y"
echo " --ql-drop-link x y"
echo " make a drop link to user QuickLook install dir, at location x,y"
echo " --eula eula_file"
echo " attach a license file to the dmg"
echo " --no-internet-enable"
echo " disable automatic mount&copy"
echo " --format"
echo " specify the final image format (default is UDZO)"
echo " --add-file target_name file|folder x y"
echo " add additional file or folder (can be used multiple times)"
echo " --disk-image-size x"
echo " set the disk image size manually to x MB"
echo " --hdiutil-verbose"
echo " execute hdiutil in verbose mode"
echo " --hdiutil-quiet"
echo " execute hdiutil in quiet mode"
echo " --bless"
echo " bless the mount folder (deprecated, needs macOS 12.2.1 or older)"
echo " --sandbox-safe"
echo " execute hdiutil with sandbox compatibility, do not bless and do not execute the cosmetic AppleScript"
echo " --version show tool version number"
echo " -h, --help display this help"
exit 0
}
# factors can cause interstitial disk images to contain more than a single
# partition - expand the hunt for the temporary disk image by checking for
# the path of the volume, versus assuming its the first result (as in pr/152).
function find_mount_dir() {
local dev_name="${1}"
>&2 echo "Searching for mounted interstitial disk image using ${dev_name}... "
# enumerate up to 9 partitions
for i in {1..9}; do
# attempt to find the partition
local found_dir
found_dir=$(hdiutil info | grep -E --color=never "${dev_name}" | head -${i} | awk '{print $3}' | xargs)
if [[ -n "${found_dir}" ]]; then
echo "${found_dir}"
return 0
fi
done
}
# Argument parsing
WINX=10
WINY=60
WINW=500
WINH=350
ICON_SIZE=128
TEXT_SIZE=16
FORMAT="UDZO"
ADD_FILE_SOURCES=()
ADD_FILE_TARGETS=()
IMAGEKEY=""
HDIUTIL_VERBOSITY=""
SANDBOX_SAFE=0
BLESS=0
SKIP_JENKINS=0
MAXIMUM_UNMOUNTING_ATTEMPTS=3
POSITION_CLAUSE=""
HIDING_CLAUSE=""
while [[ "${1:0:1}" = "-" ]]; do
case $1 in
--volname)
VOLUME_NAME="$2"
shift; shift;;
--volicon)
VOLUME_ICON_FILE="$2"
shift; shift;;
--background)
BACKGROUND_FILE="$2"
BACKGROUND_FILE_NAME="$(basename "$BACKGROUND_FILE")"
BACKGROUND_CLAUSE="set background picture of opts to file \".background:$BACKGROUND_FILE_NAME\""
REPOSITION_HIDDEN_FILES_CLAUSE="set position of every item to {theBottomRightX + 100, 100}"
shift; shift;;
--icon-size)
ICON_SIZE="$2"
shift; shift;;
--text-size)
TEXT_SIZE="$2"
shift; shift;;
--window-pos)
WINX=$2; WINY=$3
shift; shift; shift;;
--window-size)
WINW=$2; WINH=$3
shift; shift; shift;;
--icon)
POSITION_CLAUSE="${POSITION_CLAUSE}set position of item \"$2\" to {$3, $4}
"
shift; shift; shift; shift;;
--hide-extension)
HIDING_CLAUSE="${HIDING_CLAUSE}set the extension hidden of item \"$2\" to true
"
shift; shift;;
-h | --help)
usage;;
--version)
version; exit 0;;
--pure-version)
pure_version; exit 0;;
--ql-drop-link)
QL_LINK=$2
QL_CLAUSE="set position of item \"QuickLook\" to {$2, $3}
"
shift; shift; shift;;
--app-drop-link)
APPLICATION_LINK=$2
APPLICATION_CLAUSE="set position of item \"Applications\" to {$2, $3}
"
shift; shift; shift;;
--eula)
EULA_RSRC=$2
shift; shift;;
--no-internet-enable)
NOINTERNET=1
shift;;
--format)
FORMAT="$2"
shift; shift;;
--filesystem)
FILESYSTEM="$2"
shift; shift;;
--encrypt)
ENABLE_ENCRYPTION=1
AESBITS=256
shift;;
--encrypt-aes128)
ENABLE_ENCRYPTION=1
AESBITS=128
shift;;
--add-file | --add-folder)
ADD_FILE_TARGETS+=("$2")
ADD_FILE_SOURCES+=("$3")
POSITION_CLAUSE="${POSITION_CLAUSE}
set position of item \"$2\" to {$4, $5}
"
shift; shift; shift; shift; shift;;
--disk-image-size)
DISK_IMAGE_SIZE="$2"
shift; shift;;
--hdiutil-verbose)
HDIUTIL_VERBOSITY='-verbose'
shift;;
--hdiutil-quiet)
HDIUTIL_VERBOSITY='-quiet'
shift;;
--codesign)
SIGNATURE="$2"
shift; shift;;
--notarize)
NOTARIZE="$2"
shift; shift;;
--sandbox-safe)
SANDBOX_SAFE=1
shift;;
--bless)
BLESS=1
shift;;
--rez)
echo "REZ is no more directly used. You can remove the --rez argument."
shift; shift;;
--skip-jenkins)
SKIP_JENKINS=1
shift;;
-*)
echo "Unknown option: $1. Run 'create-dmg --help' for help."
exit 1;;
--volname)
VOLUME_NAME="$2"
shift; shift;;
--volicon)
VOLUME_ICON_FILE="$2"
shift; shift;;
--background)
BACKGROUND_FILE="$2"
BACKGROUND_FILE_NAME="$(basename "$BACKGROUND_FILE")"
BACKGROUND_CLAUSE="set background picture of opts to file \".background:$BACKGROUND_FILE_NAME\""
REPOSITION_HIDDEN_FILES_CLAUSE="set position of every item to {theBottomRightX + 100, 100}"
shift; shift;;
--icon-size)
ICON_SIZE="$2"
shift; shift;;
--text-size)
TEXT_SIZE="$2"
shift; shift;;
--window-pos)
WINX=$2; WINY=$3
shift; shift; shift;;
--window-size)
WINW=$2; WINH=$3
shift; shift; shift;;
--icon)
POSITION_CLAUSE="${POSITION_CLAUSE}set position of item \"$2\" to {$3, $4}
"
shift; shift; shift; shift;;
--hide-extension)
HIDING_CLAUSE="${HIDING_CLAUSE}set the extension hidden of item \"$2\" to true
"
shift; shift;;
-h | --help)
usage;;
--version)
version; exit 0;;
--pure-version)
pure_version; exit 0;;
--ql-drop-link)
QL_LINK=$2
QL_CLAUSE="set position of item \"QuickLook\" to {$2, $3}
"
shift; shift; shift;;
--app-drop-link)
APPLICATION_LINK=$2
APPLICATION_CLAUSE="set position of item \"Applications\" to {$2, $3}
"
shift; shift; shift;;
--eula)
EULA_RSRC=$2
shift; shift;;
--no-internet-enable)
NOINTERNET=1
shift;;
--format)
FORMAT="$2"
shift; shift;;
--add-file | --add-folder)
ADD_FILE_TARGETS+=("$2")
ADD_FILE_SOURCES+=("$3")
POSITION_CLAUSE="${POSITION_CLAUSE}
set position of item \"$2\" to {$4, $5}
"
shift; shift; shift; shift; shift;;
--disk-image-size)
DISK_IMAGE_SIZE="$2"
shift; shift;;
--hdiutil-verbose)
HDIUTIL_VERBOSITY='-verbose'
shift;;
--hdiutil-quiet)
HDIUTIL_VERBOSITY='-quiet'
shift;;
--sandbox-safe)
SANDBOX_SAFE=1
shift;;
--bless)
BLESS=1
shift;;
--skip-jenkins)
SKIP_JENKINS=1
shift;;
-*)
echo "Unknown option: $1. Run with --help for help."
exit 1;;
esac
case $FORMAT in
UDZO)
IMAGEKEY="-imagekey zlib-level=9";;
UDBZ)
IMAGEKEY="-imagekey bzip2-level=9";;
ULFO)
;;
ULMO)
;;
*)
echo >&2 "Unknown disk image format: $FORMAT"
exit 1;;
UDZO)
IMAGEKEY="-imagekey zlib-level=9";;
UDBZ)
IMAGEKEY="-imagekey bzip2-level=9";;
esac
done
if [[ -z "$2" ]]; then
echo "Not enough arguments. Run 'create-dmg --help' for help."
echo "Not enough arguments. Invoke with --help for help."
exit 1
fi
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DMG_PATH="$1"
DMG_DIRNAME="$(dirname "$DMG_PATH")"
DMG_DIR="$(cd "$DMG_DIRNAME" > /dev/null; pwd)"
DMG_NAME="$(basename "$DMG_PATH")"
DMG_TEMP_NAME="$DMG_DIR/rw.${DMG_NAME}"
SRC_FOLDER="$(cd "$2" > /dev/null; pwd)"
# Argument validation checks
@@ -298,48 +202,24 @@ if [[ "${DMG_PATH: -4}" != ".dmg" ]]; then
exit 1
fi
if [[ "${FILESYSTEM}" != "HFS+" ]] && [[ "${FILESYSTEM}" != "APFS" ]]; then
echo "Unknown disk image filesystem: ${FILESYSTEM}. Run 'create-dmg --help' for help."
exit 1
fi
if [[ "${FILESYSTEM}" == "APFS" ]] && [[ ${SANDBOX_SAFE} -eq 1 ]]; then
echo "Creating an APFS disk image that is sandbox safe is not supported."
exit 1
fi
# Main script logic
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DMG_DIRNAME="$(dirname "$DMG_PATH")"
DMG_DIR="$(cd "$DMG_DIRNAME" > /dev/null; pwd)"
DMG_NAME="$(basename "$DMG_PATH")"
DMG_TEMP_NAME="$DMG_DIR/rw.$$.${DMG_NAME}"
# Detect where we're running from
sentinel_file="$SCRIPT_DIR/.this-is-the-create-dmg-repo"
if [[ -f "$sentinel_file" ]]; then
# We're running from inside a repo
CDMG_SUPPORT_DIR="$SCRIPT_DIR/support"
else
# We're running inside an installed location
bin_dir="$SCRIPT_DIR"
prefix_dir=$(dirname "$bin_dir")
CDMG_SUPPORT_DIR="$prefix_dir/share/create-dmg/support"
fi
if [[ -z "$VOLUME_NAME" ]]; then
VOLUME_NAME="$(basename "$DMG_PATH" .dmg)"
fi
if [[ ! -d "$CDMG_SUPPORT_DIR" ]]; then
echo >&2 "Cannot find support/ directory: expected at: $CDMG_SUPPORT_DIR"
exit 1
# brew formula will set this as 1 and embed the support scripts
BREW_INSTALL=0
AUX_PATH="$SCRIPT_DIR/support"
if [ $BREW_INSTALL -eq 0 ]; then
test -d "$AUX_PATH" || {
echo "Cannot find support directory: $AUX_PATH"
exit 1
}
fi
if [[ -f "$SRC_FOLDER/.DS_Store" ]]; then
echo "Deleting .DS_Store found in source folder"
echo "Deleting any .DS_Store in source folder"
rm "$SRC_FOLDER/.DS_Store"
fi
@@ -349,7 +229,7 @@ if [[ -f "${DMG_TEMP_NAME}" ]]; then
rm -f "${DMG_TEMP_NAME}"
fi
# Use Megabytes since hdiutil fails with very large byte numbers
# Using Megabytes since hdiutil fails with very large Byte numbers
function blocks_to_megabytes() {
# Add 1 extra MB, since there's no decimal retention here
MB_SIZE=$((($1 * 512 / 1000 / 1000) + 1))
@@ -358,13 +238,8 @@ function blocks_to_megabytes() {
function get_size() {
# Get block size in disk
if [[ $OS_MAJOR_VERSION -ge 12 ]]; then
bytes_size=$(du -B 512 -s "$1")
else
bytes_size=$(du -s "$1")
fi
bytes_size=$(echo $bytes_size | sed -e 's/ .*//g')
echo $(blocks_to_megabytes $bytes_size)
bytes_size=$(du -s "$1" | sed -e 's/ .*//g')
echo $(blocks_to_megabytes "$bytes_size")
}
# Create the DMG with the specified size or the hdiutil estimation
@@ -373,14 +248,8 @@ if [[ -n "$DISK_IMAGE_SIZE" ]]; then
CUSTOM_SIZE="-size ${DISK_IMAGE_SIZE}m"
fi
if [[ $SANDBOX_SAFE -eq 0 ]]; then
if [[ "$FILESYSTEM" == "APFS" ]]; then
FILESYSTEM_ARGUMENTS=""
else
FILESYSTEM_ARGUMENTS="-c c=64,a=16,e=16"
fi
hdiutil create ${HDIUTIL_VERBOSITY} -srcfolder "$SRC_FOLDER" -volname "${VOLUME_NAME}" \
-fs "${FILESYSTEM}" -fsargs "${FILESYSTEM_ARGUMENTS}" -format UDRW ${CUSTOM_SIZE} "${DMG_TEMP_NAME}"
if [ $SANDBOX_SAFE -eq 0 ]; then
hdiutil create ${HDIUTIL_VERBOSITY} -srcfolder "$SRC_FOLDER" -volname "${VOLUME_NAME}" -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW ${CUSTOM_SIZE} "${DMG_TEMP_NAME}"
else
hdiutil makehybrid ${HDIUTIL_VERBOSITY} -default-volume-name "${VOLUME_NAME}" -hfs -o "${DMG_TEMP_NAME}" "$SRC_FOLDER"
hdiutil convert -format UDRW -ov -o "${DMG_TEMP_NAME}" "${DMG_TEMP_NAME}"
@@ -391,7 +260,7 @@ fi
DISK_IMAGE_SIZE=$(get_size "${DMG_TEMP_NAME}")
# Use the custom size if bigger
if [[ $SANDBOX_SAFE -eq 1 ]] && [[ ! -z "$DISK_IMAGE_SIZE_CUSTOM" ]] && [[ $DISK_IMAGE_SIZE_CUSTOM -gt $DISK_IMAGE_SIZE ]]; then
if [[ $SANDBOX_SAFE -eq 1 ]] && [[ -n "$DISK_IMAGE_SIZE_CUSTOM" ]] && [[ $DISK_IMAGE_SIZE_CUSTOM -gt $DISK_IMAGE_SIZE ]]; then
DISK_IMAGE_SIZE=$DISK_IMAGE_SIZE_CUSTOM
fi
@@ -409,39 +278,28 @@ DISK_IMAGE_SIZE=$(expr $DISK_IMAGE_SIZE + 20)
# Make sure target image size is within limits
MIN_DISK_IMAGE_SIZE=$(hdiutil resize -limits "${DMG_TEMP_NAME}" | awk 'NR=1{print int($1/2048+1)}')
if [ $MIN_DISK_IMAGE_SIZE -gt $DISK_IMAGE_SIZE ]; then
DISK_IMAGE_SIZE=$MIN_DISK_IMAGE_SIZE
DISK_IMAGE_SIZE=$MIN_DISK_IMAGE_SIZE
fi
# Resize the image for the extra stuff
hdiutil resize ${HDIUTIL_VERBOSITY} -size ${DISK_IMAGE_SIZE}m "${DMG_TEMP_NAME}"
# Mount the new DMG
# mount the new DMG
echo "Mounting disk image..."
MOUNT_DIR="/Volumes/${VOLUME_NAME}"
# Unmount leftover dmg if it was mounted previously (e.g. developer mounted dmg, installed app and forgot to unmount it)
if [[ -d "${MOUNT_DIR}" ]]; then
echo "Unmounting previously mounted disk image..."
DEV_NAME=$(hdiutil info | grep -E --color=never '^/dev/' | sed 1q | awk '{print $1}')
hdiutil detach "${DEV_NAME}"
fi
echo "Mounting disk image..."
MOUNT_RANDOM_PATH="/Volumes"
if [[ $SANDBOX_SAFE -eq 1 ]]; then
MOUNT_RANDOM_PATH="/tmp"
fi
if [[ "$FILESYSTEM" == "APFS" ]]; then
HDIUTIL_FILTER="tail -n 1"
else
HDIUTIL_FILTER="sed 1q"
fi
DEV_NAME=$(hdiutil attach -mountrandom ${MOUNT_RANDOM_PATH} -readwrite -noverify -noautoopen -nobrowse "${DMG_TEMP_NAME}" | grep -E --color=never '^/dev/' | ${HDIUTIL_FILTER} | awk '{print $1}')
echo "Mount directory: $MOUNT_DIR"
DEV_NAME=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_TEMP_NAME}" | grep -E --color=never '^/dev/' | sed 1q | awk '{print $1}')
echo "Device name: $DEV_NAME"
if [[ "$FILESYSTEM" == "APFS" ]]; then
MOUNT_DIR=$(find_mount_dir "${DEV_NAME}")
else
MOUNT_DIR=$(find_mount_dir "${DEV_NAME}s")
fi
if [[ -z "${MOUNT_DIR}" ]]; then
>&2 echo "ERROR: unable to proceed with final disk image creation because the interstitial disk image was not found."
>&2 echo "The interstitial disk image will likely be mounted and will need to be cleaned up manually."
exit 1
fi
echo "Mount dir: $MOUNT_DIR"
if [[ -n "$BACKGROUND_FILE" ]]; then
echo "Copying background file '$BACKGROUND_FILE'..."
@@ -450,14 +308,12 @@ if [[ -n "$BACKGROUND_FILE" ]]; then
fi
if [[ -n "$APPLICATION_LINK" ]]; then
echo "Making link to Applications dir..."
echo $MOUNT_DIR
ln -s /Applications "$MOUNT_DIR/Applications"
echo "making link to Applications dir"
test -d "$MOUNT_DIR/Applications" || ln -s /Applications "$MOUNT_DIR/Applications"
fi
if [[ -n "$QL_LINK" ]]; then
echo "Making link to QuickLook install dir..."
echo $MOUNT_DIR
echo "making link to QuickLook install dir"
ln -s "/Library/QuickLook" "$MOUNT_DIR/QuickLook"
fi
@@ -475,15 +331,24 @@ if [[ -n "$ADD_FILE_SOURCES" ]]; then
done
fi
VOLUME_NAME=$(basename $MOUNT_DIR)
# Run AppleScript to do all the Finder cosmetic stuff
# run AppleScript to do all the Finder cosmetic stuff
APPLESCRIPT_FILE=$(mktemp -t createdmg.tmp.XXXXXXXXXX)
function applescript_source() {
if [ $BREW_INSTALL -eq 0 ]; then
cat "$AUX_PATH/template.applescript"
else
cat << 'EOS'
# BREW_INLINE_APPLESCRIPT_PLACEHOLDER
EOS
fi
}
if [[ $SANDBOX_SAFE -eq 1 ]]; then
echo "Skipping Finder-prettifying AppleScript because we are in Sandbox..."
else
if [[ $SKIP_JENKINS -eq 0 ]]; then
cat "$CDMG_SUPPORT_DIR/template.applescript" \
applescript_source \
| sed -e "s/WINX/$WINX/g" -e "s/WINY/$WINY/g" -e "s/WINW/$WINW/g" \
-e "s/WINH/$WINH/g" -e "s/BACKGROUND_CLAUSE/$BACKGROUND_CLAUSE/g" \
-e "s/REPOSITION_HIDDEN_FILES_CLAUSE/$REPOSITION_HIDDEN_FILES_CLAUSE/g" \
@@ -493,76 +358,71 @@ else
| perl -pe "s/APPLICATION_CLAUSE/$APPLICATION_CLAUSE/g" \
| perl -pe "s/HIDING_CLAUSE/$HIDING_CLAUSE/" \
> "$APPLESCRIPT_FILE"
# pause to workaround occasional "Cant get disk" (-1728) issues
ERROR_1728_WORKAROUND_SLEEP_INTERVAL=2
echo "Will sleep for $ERROR_1728_WORKAROUND_SLEEP_INTERVAL seconds to workaround occasions \"Can't get disk (-1728)\" issues..."
sleep $ERROR_1728_WORKAROUND_SLEEP_INTERVAL
sleep 2 # pause to workaround occasional "Can't get disk" (-1728) issues
echo "Running AppleScript to make Finder stuff pretty: /usr/bin/osascript \"${APPLESCRIPT_FILE}\" \"${VOLUME_NAME}\""
if /usr/bin/osascript "${APPLESCRIPT_FILE}" "${VOLUME_NAME}"; then
# Okay, we're cool
true
else
echo >&2 "Failed running AppleScript"
hdiutil_detach_retry "${DEV_NAME}"
hdiutil detach "${DEV_NAME}"
exit 64
fi
echo "Done running the AppleScript..."
sleep 4
rm "$APPLESCRIPT_FILE"
else
echo ''
echo "Will skip running AppleScript to configure DMG aesthetics because of --skip-jenkins option."
echo "This will result in a DMG without any custom background or icons positioning."
echo "More info at https://github.com/create-dmg/create-dmg/issues/72"
echo ''
fi
fi
# Make sure it's not world writeable
# make sure it's not world writeable
echo "Fixing permissions..."
chmod -Rf go-w "${MOUNT_DIR}" &> /dev/null || true
echo "Done fixing permissions"
echo "Done fixing permissions."
# Make the top window open itself on mount:
# make the top window open itself on mount:
if [[ $BLESS -eq 1 && $SANDBOX_SAFE -eq 0 ]]; then
echo "Blessing started"
if [ $(uname -m) == "arm64" ]; then
bless --folder "${MOUNT_DIR}"
else
bless --folder "${MOUNT_DIR}" --openfolder "${MOUNT_DIR}"
fi
bless --folder "${MOUNT_DIR}" --openfolder "${MOUNT_DIR}"
echo "Blessing finished"
else
echo "Skipping blessing on sandbox"
fi
if [[ -n "$VOLUME_ICON_FILE" ]]; then
# Tell the volume that it has a special file attribute
# tell the volume that it has a special file attribute
SetFile -a C "$MOUNT_DIR"
fi
# Delete unnecessary file system events log if possible
# Delete unnecessary file system events log
echo "Deleting .fseventsd"
rm -rf "${MOUNT_DIR}/.fseventsd" || true
rm -rf "${MOUNT_DIR}/.fseventsd"
hdiutil_detach_retry "${DEV_NAME}"
# unmount
unmounting_attempts=0
until
echo "Unmounting disk image..."
(( unmounting_attempts++ ))
hdiutil detach "${DEV_NAME}"
exit_code=$?
(( exit_code == 0 )) && break # nothing goes wrong
(( exit_code != 16 )) && exit $exit_code # exit with the original exit code
# The above statement returns 1 if test failed (exit_code == 16).
# It can make the code in the {do... done} block to be executed
do
(( unmounting_attempts == MAXIMUM_UNMOUNTING_ATTEMPTS )) && exit 16 # patience exhausted, exit with code EBUSY
echo "Wait a moment..."
sleep $(( 1 * (2 ** unmounting_attempts) ))
done
unset unmounting_attempts
# Compress image and optionally encrypt
if [[ $ENABLE_ENCRYPTION -eq 0 ]]; then
echo "Compressing disk image..."
hdiutil convert ${HDIUTIL_VERBOSITY} "${DMG_TEMP_NAME}" -format ${FORMAT} ${IMAGEKEY} -o "${DMG_DIR}/${DMG_NAME}"
else
echo "Compressing and encrypting disk image..."
echo "NOTE: hdiutil will only prompt a single time for a password - ensure entry is correct."
hdiutil convert ${HDIUTIL_VERBOSITY} "${DMG_TEMP_NAME}" -format ${FORMAT} ${IMAGEKEY} -encryption AES-${AESBITS} -stdinpass -o "${DMG_DIR}/${DMG_NAME}"
fi
# compress image
echo "Compressing disk image..."
hdiutil convert ${HDIUTIL_VERBOSITY} "${DMG_TEMP_NAME}" -format "${FORMAT}" ${IMAGEKEY} -o "${DMG_DIR}/${DMG_NAME}"
rm -f "${DMG_TEMP_NAME}"
# Adding EULA resources
# adding EULA resources
if [[ -n "${EULA_RSRC}" && "${EULA_RSRC}" != "-null-" ]]; then
echo "Adding EULA resources..."
echo "adding EULA resources"
#
# Use udifrez instead flatten/rez/unflatten
# https://github.com/create-dmg/create-dmg/issues/109
@@ -571,19 +431,20 @@ if [[ -n "${EULA_RSRC}" && "${EULA_RSRC}" != "-null-" ]]; then
# https://developer.apple.com/forums/thread/668084
#
EULA_RESOURCES_FILE=$(mktemp -t createdmg.tmp.XXXXXXXXXX)
EULA_FORMAT=$(file -b ${EULA_RSRC})
EULA_FORMAT=$(file -b "${EULA_RSRC}")
if [[ ${EULA_FORMAT} == 'Rich Text Format data'* ]] ; then
EULA_FORMAT='RTF '
else
EULA_FORMAT='TEXT'
fi
# Encode the EULA to base64
# Replace 'openssl base64' with 'base64' if Mac OS X 10.6 support is no more needed
# EULA_DATA="$(base64 -b 52 "${EULA_RSRC}" | sed s$'/^\(.*\)$/\t\t\t\\1/')"
EULA_DATA="$(openssl base64 -in "${EULA_RSRC}" | tr -d '\n' | awk '{gsub(/.{52}/,"&\n")}1' | sed s$'/^\(.*\)$/\t\t\t\\1/')"
# Fill the template with the custom EULA contents
eval "cat > \"${EULA_RESOURCES_FILE}\" <<EOF
$(<${CDMG_SUPPORT_DIR}/eula-resources-template.xml)
$(<${AUX_PATH}/eula-resources-template.xml)
EOF
"
# Apply the resources
@@ -594,45 +455,19 @@ if [[ -n "${EULA_RSRC}" && "${EULA_RSRC}" != "-null-" ]]; then
echo "Successfully added the EULA license"
fi
# Enable "internet", whatever that is
if [[ ! -z "${NOINTERNET}" && "${NOINTERNET}" == 1 ]]; then
echo "Not setting 'internet-enable' on the dmg, per caller request"
if [[ -n "${NOINTERNET}" && "${NOINTERNET}" == 1 ]]; then
echo "not setting 'internet-enable' on the dmg"
else
# Check if hdiutil supports internet-enable
# Support was removed in macOS 10.15. See https://github.com/andreyvit/create-dmg/issues/76
if hdiutil internet-enable -help >/dev/null 2>/dev/null; then
# check if hdiutil supports internet-enable
# support was removed in macOS 10.15
# https://github.com/andreyvit/create-dmg/issues/76
if hdiutil internet-enable -help >/dev/null 2>/dev/null
then
hdiutil internet-enable -yes "${DMG_DIR}/${DMG_NAME}"
else
echo "hdiutil does not support internet-enable. Note it was removed in macOS 10.15."
fi
fi
if [[ -n "${SIGNATURE}" && "${SIGNATURE}" != "-null-" ]]; then
echo "Codesign started"
codesign -s "${SIGNATURE}" "${DMG_DIR}/${DMG_NAME}"
dmgsignaturecheck="$(codesign --verify --deep --verbose=2 --strict "${DMG_DIR}/${DMG_NAME}" 2>&1 >/dev/null)"
if [ $? -eq 0 ]; then
echo "The disk image is now codesigned"
else
echo "The signature seems invalid${NC}"
exit 1
fi
fi
if [[ -n "${NOTARIZE}" && "${NOTARIZE}" != "-null-" ]]; then
echo "Notarization started"
xcrun notarytool submit "${DMG_DIR}/${DMG_NAME}" --keychain-profile "${NOTARIZE}" --wait
echo "Stapling the notarization ticket"
staple="$(xcrun stapler staple "${DMG_DIR}/${DMG_NAME}")"
if [ $? -eq 0 ]; then
echo "The disk image is now notarized"
else
echo "$staple"
echo "The notarization failed with error $?"
exit 1
fi
fi
# All done!
echo "Disk image done"
exit 0

View File

@@ -5,8 +5,7 @@
use super::{app, icon::create_icns_file};
use crate::{
bundle::{settings::Arch, Bundle},
utils::CommandExt,
bundle::{common::CommandExt, settings::Arch, Bundle},
PackageType, Settings,
};
@@ -62,18 +61,17 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
let bundle_file_name = format!("{}.app", product_name);
let bundle_dir = settings.project_out_directory().join("bundle/macos");
let support_directory_path = output_path
.parent()
.unwrap()
.join("share/create-dmg/support");
for path in &[&support_directory_path, &output_path] {
if path.exists() {
fs::remove_dir_all(path).with_context(|| format!("Failed to remove old {}", dmg_name))?;
}
fs::create_dir_all(path)
.with_context(|| format!("Failed to create output directory at {:?}", path))?;
let support_directory_path = output_path.join("support");
if output_path.exists() {
fs::remove_dir_all(&output_path)
.with_context(|| format!("Failed to remove old {}", dmg_name))?;
}
fs::create_dir_all(&support_directory_path).with_context(|| {
format!(
"Failed to create output directory at {:?}",
support_directory_path
)
})?;
// create paths for script
let bundle_script_path = output_path.join("bundle_dmg.sh");
@@ -174,11 +172,9 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
// Issue #592 - Building MacOS dmg files on CI
// https://github.com/tauri-apps/tauri/issues/592
if env::var_os("TAURI_BUNDLER_DMG_IGNORE_CI").unwrap_or_default() != "true" {
if let Some(value) = env::var_os("CI") {
if value == "true" {
bundle_dmg_cmd.arg("--skip-jenkins");
}
if let Some(value) = env::var_os("CI") {
if value == "true" {
bundle_dmg_cmd.arg("--skip-jenkins");
}
}
@@ -194,19 +190,16 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
fs::rename(bundle_dir.join(dmg_name), dmg_path.clone())?;
// 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 identity != Some("-") {
if let Some(keychain) = super::sign::keychain(identity)? {
super::sign::sign(
&keychain,
vec![super::sign::SignTarget {
path: dmg_path.clone(),
is_an_executable: false,
}],
settings,
)?;
}
if let Some(keychain) = super::sign::keychain(settings.macos().signing_identity.as_deref())? {
super::sign::sign(
&keychain,
vec![super::sign::SignTarget {
path: dmg_path.clone(),
is_an_executable: false,
}],
settings,
)?;
}
Ok(Bundled {

View File

@@ -3,8 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::bundle::Settings;
use crate::utils::{self, fs_utils};
use crate::bundle::{common, Settings};
use std::{
cmp::min,
ffi::OsStr,
@@ -29,7 +28,7 @@ pub fn create_icns_file(out_dir: &Path, settings: &Settings) -> crate::Result<Op
if icon_path.extension() == Some(OsStr::new("icns")) {
let mut dest_path = out_dir.to_path_buf();
dest_path.push(icon_path.file_name().expect("Could not get icon filename"));
fs_utils::copy_file(&icon_path, &dest_path)?;
common::copy_file(&icon_path, &dest_path)?;
return Ok(Some(dest_path));
}
}
@@ -64,7 +63,7 @@ pub fn create_icns_file(out_dir: &Path, settings: &Settings) -> crate::Result<Op
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
let icon = image::open(&icon_path)?;
let density = if utils::is_retina(&icon_path) { 2 } else { 1 };
let density = if common::is_retina(&icon_path) { 2 } else { 1 };
let (w, h) = icon.dimensions();
let orig_size = min(w, h);
let next_size_down = 2f32.powf((orig_size as f32).log2().floor()) as u32;

View File

@@ -13,10 +13,7 @@
// See https://developer.apple.com/go/?id=bundle-structure for a full
// explanation.
use crate::{
utils::{self, fs_utils},
Settings,
};
use crate::{bundle::common, Settings};
use anyhow::Context;
use image::{codecs::png::PngDecoder, GenericImageView, ImageDecoder};
@@ -53,7 +50,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
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)
common::copy_file(&src, &dest)
.with_context(|| format!("Failed to copy resource file {:?}", src))?;
}
@@ -64,7 +61,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()))
common::copy_file(&bin_path, app_bundle_path.join(bin.name()))
.with_context(|| format!("Failed to copy binary from {:?}", bin_path))?;
}
@@ -96,11 +93,11 @@ fn generate_icon_files(bundle_dir: &Path, settings: &Settings) -> crate::Result<
let decoder = PngDecoder::new(BufReader::new(File::open(&icon_path)?))?;
let width = decoder.dimensions().0;
let height = decoder.dimensions().1;
let is_retina = utils::is_retina(&icon_path);
let is_retina = common::is_retina(&icon_path);
if !sizes.contains(&(width, height, is_retina)) {
sizes.insert((width, height, is_retina));
let dest_path = get_dest_path(width, height, is_retina);
fs_utils::copy_file(&icon_path, &dest_path)?;
common::copy_file(&icon_path, &dest_path)?;
}
}
// Fall back to non-PNG files for any missing sizes.
@@ -124,12 +121,12 @@ fn generate_icon_files(bundle_dir: &Path, settings: &Settings) -> crate::Result<
} else {
let icon = image::open(&icon_path)?;
let (width, height) = icon.dimensions();
let is_retina = utils::is_retina(&icon_path);
let is_retina = common::is_retina(&icon_path);
if !sizes.contains(&(width, height, is_retina)) {
sizes.insert((width, height, is_retina));
let dest_path = get_dest_path(width, height, is_retina);
icon.write_to(
&mut fs_utils::create_file(&dest_path)?,
&mut common::create_file(&dest_path)?,
image::ImageFormat::Png,
)?;
}
@@ -145,7 +142,7 @@ fn generate_info_plist(
settings: &Settings,
icon_filenames: &[String],
) -> crate::Result<()> {
let file = &mut fs_utils::create_file(&bundle_dir.join("Info.plist"))?;
let file = &mut common::create_file(&bundle_dir.join("Info.plist"))?;
writeln!(
file,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\

View File

@@ -48,14 +48,9 @@ pub fn sign(
log::info!(action = "Signing"; "with identity \"{}\"", keychain.signing_identity());
for target in targets {
let entitlements_path = if target.is_an_executable {
settings.macos().entitlements.as_ref().map(Path::new)
} else {
None
};
keychain.sign(
&target.path,
entitlements_path,
settings.macos().entitlements.as_ref().map(Path::new),
target.is_an_executable && settings.macos().hardened_runtime,
)?;
}

View File

@@ -0,0 +1,287 @@
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
fs::{create_dir, create_dir_all, read_dir, remove_dir_all},
path::{Path, PathBuf},
};
/// Directory options.
#[derive(Default, Clone)]
pub struct DirOpts {
pub depth: u64,
}
/// File options.
pub struct FileOpts {
pub overwrite: bool,
pub skip: bool,
#[allow(dead_code)]
pub buffer_size: usize,
}
/// Copy options.
#[derive(Clone)]
pub struct Options {
pub overwrite: bool,
pub skip: bool,
pub buffer_size: usize,
pub copy_files: bool,
pub content_only: bool,
pub depth: u64,
}
/// Directory information descriptor
pub struct DirInfo {
pub size: u64,
pub files: Vec<String>,
pub directories: Vec<String>,
}
impl Default for Options {
fn default() -> Options {
Options {
overwrite: false,
skip: false,
buffer_size: 64000,
copy_files: false,
content_only: false,
depth: 0,
}
}
}
impl Default for FileOpts {
fn default() -> FileOpts {
FileOpts {
overwrite: false,
skip: false,
buffer_size: 64000,
}
}
}
/// Creates the given directory path,
/// erasing it first if specified.
pub fn create<P>(path: P, erase: bool) -> crate::Result<()>
where
P: AsRef<Path>,
{
if erase && path.as_ref().exists() {
remove(&path)?;
}
Ok(create_dir(&path)?)
}
/// Creates all of the directories of the specified path,
/// erasing it first if specified.
pub fn create_all<P>(path: P, erase: bool) -> crate::Result<()>
where
P: AsRef<Path>,
{
if erase && path.as_ref().exists() {
remove(&path)?;
}
Ok(create_dir_all(&path)?)
}
/// Removes the directory if it exists.
pub fn remove<P: AsRef<Path>>(path: P) -> crate::Result<()> {
if path.as_ref().exists() {
Ok(remove_dir_all(path)?)
} else {
Ok(())
}
}
/// Copy file with the given options.
pub fn copy_file<P, Q>(from: P, to: Q, options: &FileOpts) -> crate::Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let from = from.as_ref();
if !from.exists() {
if let Some(msg) = from.to_str() {
let msg = format!("Path \"{msg}\" does not exist or you don't have access");
return Err(crate::Error::PathUtilError(msg));
}
return Err(crate::Error::PathUtilError(
"Path does not exist or you don't have access!".to_owned(),
));
}
if !from.is_file() {
if let Some(msg) = from.to_str() {
let msg = format!("Path \"{msg}\" is not a file!");
return Err(crate::Error::PathUtilError(msg));
}
return Err(crate::Error::PathUtilError(
"Path is not a file!".to_owned(),
));
}
if !options.overwrite && to.as_ref().exists() {
if options.skip {
return Ok(0);
}
if let Some(msg) = to.as_ref().to_str() {
let msg = format!("Path \"{msg}\" is exist");
return Err(crate::Error::PathUtilError(msg));
}
}
Ok(std::fs::copy(from, to)?)
}
/// Copies the directory with the given options.
#[allow(dead_code)]
pub fn copy<P, Q>(from: P, to: Q, options: &Options) -> crate::Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let from = from.as_ref();
if !from.exists() {
if let Some(msg) = from.to_str() {
let msg = format!("Path \"{msg}\" does not exist or you don't have access!");
return Err(crate::Error::PathUtilError(msg));
}
return Err(crate::Error::PathUtilError(
"Path does not exist or you don't have access".to_owned(),
));
}
if !from.is_dir() {
if let Some(msg) = from.to_str() {
let msg = format!("Path \"{msg}\" is not a directory!");
return Err(crate::Error::PathUtilError(msg));
}
return Err(crate::Error::PathUtilError(
"Path is not a directory".to_owned(),
));
}
let dir_name = if let Some(val) = from.components().last() {
val.as_os_str()
} else {
return Err(crate::Error::PathUtilError(
"Invalid Folder form".to_owned(),
));
};
let mut to: PathBuf = to.as_ref().to_path_buf();
if !options.content_only && (!options.copy_files || to.exists()) {
to.push(dir_name);
}
let mut read_options = DirOpts::default();
if options.depth > 0 {
read_options.depth = options.depth;
}
let dir_content = get_dir_info(from, &read_options)?;
for directory in dir_content.directories {
let tmp_to = Path::new(&directory).strip_prefix(from)?;
let dir = to.join(tmp_to);
if !dir.exists() {
if options.copy_files {
create_all(dir, false)?;
} else {
create(dir, false)?;
}
}
}
let mut result: u64 = 0;
for file in dir_content.files {
let to = to.to_path_buf();
let tp = Path::new(&file).strip_prefix(from)?;
let path = to.join(tp);
let file_options = FileOpts {
overwrite: options.overwrite,
skip: options.skip,
buffer_size: options.buffer_size,
};
let mut result_copy: crate::Result<u64>;
let mut work = true;
while work {
#[allow(clippy::needless_borrow)]
{
result_copy = copy_file(&file, &path, &file_options);
}
match result_copy {
Ok(val) => {
result += val;
work = false;
}
Err(err) => {
let err_msg = err.to_string();
return Err(crate::Error::PathUtilError(err_msg));
}
}
}
}
Ok(result)
}
/// Gets the DirInfo from the directory path with the given options.
pub fn get_dir_info<P>(path: P, options: &DirOpts) -> crate::Result<DirInfo>
where
P: AsRef<Path>,
{
let depth = if options.depth == 0 {
0
} else {
options.depth + 1
};
_get_dir_info(path, depth)
}
/// Gets the DirInfo from the directory with the given depth.
fn _get_dir_info<P>(path: P, mut depth: u64) -> crate::Result<DirInfo>
where
P: AsRef<Path>,
{
let mut directories = Vec::new();
let mut files = Vec::new();
let mut size = 0;
let item = path.as_ref().to_str();
if item.is_none() {
return Err(crate::Error::PathUtilError("Invalid Path".to_owned()));
}
let item = item.expect("Item had no data").to_string();
if path.as_ref().is_dir() {
directories.push(item);
if depth == 0 || depth > 1 {
if depth > 1 {
depth -= 1;
}
for entry in read_dir(&path)? {
let _path = entry?.path();
match _get_dir_info(_path, depth) {
Ok(items) => {
let mut _files = items.files;
let mut _directories = items.directories;
size += items.size;
files.append(&mut _files);
directories.append(&mut _directories);
}
Err(err) => return Err(err),
}
}
}
} else {
size = path.as_ref().metadata()?.len();
files.push(item);
}
Ok(DirInfo {
size,
files,
directories,
})
}

View File

@@ -3,7 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::utils::CommandExt;
use super::common::CommandExt;
use std::process::Command;
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
@@ -57,8 +57,6 @@ pub fn target_triple() -> Result<String, crate::Error> {
"armv7".into()
} else if cfg!(target_arch = "aarch64") {
"aarch64".into()
} else if cfg!(target_arch = "riscv64") {
"riscv64".into()
} else {
return Err(crate::Error::ArchError(String::from(
"Unable to determine target-architecture",

View File

@@ -4,14 +4,11 @@
// SPDX-License-Identifier: MIT
use super::category::AppCategory;
use crate::{bundle::platform::target_triple, utils::fs_utils};
use crate::bundle::{common, platform::target_triple};
use anyhow::Context;
pub use tauri_utils::config::WebviewInstallMode;
use tauri_utils::{
config::{
BundleType, DeepLinkProtocol, FileAssociation, NSISInstallerMode, NsisCompression,
RpmCompression,
},
config::{BundleType, DeepLinkProtocol, FileAssociation, NSISInstallerMode, NsisCompression},
resources::{external_binaries, ResourcePaths},
};
@@ -173,8 +170,6 @@ pub struct DebianSettings {
// OS-specific settings:
/// the list of debian dependencies.
pub depends: Option<Vec<String>>,
/// the list of debian dependencies recommendations.
pub recommends: Option<Vec<String>>,
/// the list of dependencies the package provides.
pub provides: Option<Vec<String>>,
/// the list of package conflicts.
@@ -220,10 +215,6 @@ pub struct DebianSettings {
pub struct AppImageSettings {
/// The files to include in the Appimage Binary.
pub files: HashMap<PathBuf, PathBuf>,
/// Whether to include gstreamer plugins for audio/media support.
pub bundle_media_framework: bool,
/// Whether to include the `xdg-open` binary.
pub bundle_xdg_open: bool,
}
/// The RPM bundle settings.
@@ -231,15 +222,13 @@ pub struct AppImageSettings {
pub struct RpmSettings {
/// The list of RPM dependencies your application relies on.
pub depends: Option<Vec<String>>,
/// the list of of RPM dependencies your application recommends.
pub recommends: Option<Vec<String>>,
/// The list of RPM dependencies your application provides.
pub provides: Option<Vec<String>>,
/// The list of RPM dependencies your application conflicts with. They must not be present
/// in order for the package to be installed.
pub conflicts: Option<Vec<String>>,
/// The list of RPM dependencies your application supersedes - if this package is installed,
/// packages listed as "obsoletes" will be automatically removed (if they are present).
/// packages listed as obsoletes will be automatically removed (if they are present).
pub obsoletes: Option<Vec<String>>,
/// The RPM release tag.
pub release: String,
@@ -269,8 +258,6 @@ pub struct RpmSettings {
/// Path to script that will be executed after the package is removed. See
/// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
pub post_remove_script: Option<PathBuf>,
/// Compression algorithm and level. Defaults to `Gzip` with level 6.
pub compression: Option<RpmCompression>,
}
/// Position coordinates struct.
@@ -364,15 +351,6 @@ impl Default for WixLanguage {
/// Settings specific to the WiX implementation.
#[derive(Clone, Debug, Default)]
pub struct WixSettings {
/// MSI installer version in the format `major.minor.patch.build` (build is optional).
///
/// Because a valid version is required for MSI installer, it will be derived from [`PackageSettings::version`] if this field is not set.
///
/// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.
/// The third and fourth fields have a maximum value of 65,535.
///
/// See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.
pub version: Option<String>,
/// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,
/// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.
///
@@ -408,7 +386,7 @@ pub struct WixSettings {
pub banner_path: Option<PathBuf>,
/// Path to a bitmap file to use on the installation user interface dialogs.
/// It is used on the welcome and completion dialogs.
///
/// The required dimensions are 493px × 312px.
pub dialog_image_path: Option<PathBuf>,
/// Enables FIPS compliant algorithms.
@@ -523,7 +501,6 @@ pub struct WindowsSettings {
/// Nsis configuration.
pub nsis: Option<NsisSettings>,
/// The path to the application icon. Defaults to `./icons/icon.ico`.
#[deprecated = "This is used for the MSI installer and will be removed in 3.0.0, use `BundleSettings::icon` field and make sure a `.ico` icon exists instead."]
pub icon_path: PathBuf,
/// The installation mode for the Webview2 runtime.
pub webview_install_mode: WebviewInstallMode,
@@ -549,24 +526,19 @@ pub struct WindowsSettings {
pub sign_command: Option<CustomSignCommandSettings>,
}
#[allow(deprecated)]
mod _default {
use super::*;
impl Default for WindowsSettings {
fn default() -> Self {
Self {
digest_algorithm: None,
certificate_thumbprint: None,
timestamp_url: None,
tsp: false,
wix: None,
nsis: None,
icon_path: PathBuf::from("icons/icon.ico"),
webview_install_mode: Default::default(),
allow_downgrades: true,
sign_command: None,
}
impl Default for WindowsSettings {
fn default() -> Self {
Self {
digest_algorithm: None,
certificate_thumbprint: None,
timestamp_url: None,
tsp: false,
wix: None,
nsis: None,
icon_path: PathBuf::from("icons/icon.ico"),
webview_install_mode: Default::default(),
allow_downgrades: true,
sign_command: None,
}
}
}
@@ -723,8 +695,6 @@ pub enum Arch {
Armhf,
/// For the AArch32 / ARM32 instruction sets with soft-float (32 bits).
Armel,
/// For the RISC-V instruction sets (64 bits).
Riscv64,
/// For universal macOS applications.
Universal,
}
@@ -902,8 +872,6 @@ impl Settings {
Arch::Armel
} else if self.target.starts_with("aarch64") {
Arch::AArch64
} else if self.target.starts_with("riscv64") {
Arch::Riscv64
} else if self.target.starts_with("universal") {
Arch::Universal
} else {
@@ -921,16 +889,6 @@ impl Settings {
.map_err(Into::into)
}
/// Returns the file name of the binary being bundled.
pub fn main_binary_mut(&mut self) -> crate::Result<&mut BundleBinary> {
self
.binaries
.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.
pub fn main_binary_name(&self) -> crate::Result<&str> {
self
@@ -1072,7 +1030,7 @@ impl Settings {
.to_string_lossy()
.replace(&format!("-{}", self.target), ""),
);
fs_utils::copy_file(&src, &dest)?;
common::copy_file(&src, &dest)?;
paths.push(dest);
}
Ok(paths)
@@ -1083,7 +1041,7 @@ impl Settings {
for resource in self.resource_files().iter() {
let resource = resource?;
let dest = path.join(resource.target());
fs_utils::copy_file(resource.path(), &dest)?;
common::copy_file(resource.path(), dest)?;
}
Ok(())
}

View File

@@ -3,6 +3,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::common;
use crate::{
bundle::{
windows::{
@@ -11,7 +13,6 @@ use crate::{
},
Bundle,
},
utils::fs_utils,
Settings,
};
use tauri_utils::display_path;
@@ -209,7 +210,7 @@ fn bundle_update_windows(settings: &Settings, bundles: &[Bundle]) -> crate::Resu
pub fn create_zip(src_file: &Path, dst_file: &Path) -> crate::Result<PathBuf> {
let parent_dir = dst_file.parent().expect("No data in parent");
fs::create_dir_all(parent_dir)?;
let writer = fs_utils::create_file(dst_file)?;
let writer = common::create_file(dst_file)?;
let file_name = src_file
.file_name()
@@ -234,7 +235,7 @@ pub fn create_zip(src_file: &Path, dst_file: &Path) -> crate::Result<PathBuf> {
fn create_tar(src_dir: &Path, dest_path: &Path) -> crate::Result<PathBuf> {
use flate2::{write::GzEncoder, Compression};
let dest_file = fs_utils::create_file(dest_path)?;
let dest_file = common::create_file(dest_path)?;
let gzip_encoder = GzEncoder::new(dest_file, Compression::default());
let gzip_encoder = create_tar_from_src(src_dir, gzip_encoder)?;

View File

@@ -70,12 +70,9 @@
<Property Id="ARPURLUPDATEINFO" Value="{{homepage}}"/>
{{/if}}
<!-- initialize with previous InstallDir -->
<Property Id="INSTALLDIR">
<!-- First attempt: Search for "InstallDir" -->
<RegistrySearch Id="PrevInstallDirWithName" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw" />
<!-- Second attempt: If the first fails, search for the default key value (this is how the nsis installer currently stores the path) -->
<RegistrySearch Id="PrevInstallDirNoName" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Type="raw" />
<RegistrySearch Id="PrevInstallDirReg" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw"/>
</Property>
<!-- launch app checkbox -->

View File

@@ -3,22 +3,17 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{
bundle::{
settings::{Arch, Settings},
windows::{
sign::try_sign,
util::{
download_webview2_bootstrapper, download_webview2_offline_installer,
WIX_OUTPUT_FOLDER_NAME, WIX_UPDATER_OUTPUT_FOLDER_NAME,
},
use crate::bundle::{
common::CommandExt,
path_utils::{copy_file, FileOpts},
settings::{Arch, Settings},
windows::{
sign::try_sign,
util::{
download_and_verify, download_webview2_bootstrapper, download_webview2_offline_installer,
extract_zip, HashAlgorithm, WIX_OUTPUT_FOLDER_NAME, WIX_UPDATER_OUTPUT_FOLDER_NAME,
},
},
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};
@@ -26,7 +21,6 @@ use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, HashMap, HashSet},
ffi::OsStr,
fs::{self, File},
io::Write,
path::{Path, PathBuf},
@@ -203,7 +197,14 @@ fn copy_icon(settings: &Settings, filename: &str, path: &Path) -> crate::Result<
let icon_path = std::env::current_dir()?.join(path);
copy_file(&icon_path, &icon_target_path)?;
copy_file(
icon_path,
&icon_target_path,
&FileOpts {
overwrite: true,
..Default::default()
},
)?;
Ok(icon_target_path)
}
@@ -280,37 +281,19 @@ fn clear_env_for_wix(cmd: &mut Command) {
}
}
fn validate_wix_version(version_str: &str) -> anyhow::Result<()> {
let components = version_str
.split('.')
.flat_map(|c| c.parse::<u64>().ok())
.collect::<Vec<_>>();
anyhow::ensure!(
components.len() >= 3,
"app wix version should be in the format major.minor.patch.build (build is optional)"
);
if components[0] > 255 {
// WiX requires versions to be numeric only in a `major.minor.patch.build` format
pub fn convert_version(version_str: &str) -> anyhow::Result<String> {
let version = semver::Version::parse(version_str).context("invalid app version")?;
if version.major > 255 {
bail!("app version major number cannot be greater than 255");
}
if components[1] > 255 {
if version.minor > 255 {
bail!("app version minor number cannot be greater than 255");
}
if components[2] > 65535 {
if version.patch > 65535 {
bail!("app version patch number cannot be greater than 65535");
}
if components.len() == 4 && components[3] > 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) -> 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() {
@@ -449,18 +432,7 @@ pub fn build_wix_app_installer(
}
};
let app_version = if let Some(version) = settings
.windows()
.wix
.as_ref()
.and_then(|wix| wix.version.clone())
{
version
} else {
convert_version(settings.version_string())?
};
validate_wix_version(&app_version)?;
let app_version = convert_version(settings.version_string())?;
// target only supports x64.
log::info!("Target: {}", arch);
@@ -632,17 +604,7 @@ pub fn build_wix_app_installer(
data.insert("main_binary_path", to_json(main_binary_path));
// copy icon from `settings.windows().icon_path` folder to resource folder near msi
#[allow(deprecated)]
let icon_path = if !settings.windows().icon_path.as_os_str().is_empty() {
settings.windows().icon_path.clone()
} else {
settings
.icon_files()
.flatten()
.find(|i| i.extension() == Some(OsStr::new("ico")))
.context("Couldn't find a .ico icon")?
};
let icon_path = copy_icon(settings, "icon.ico", &icon_path)?;
let icon_path = copy_icon(settings, "icon.ico", &settings.windows().icon_path)?;
data.insert("icon_path", to_json(icon_path));
@@ -724,26 +686,38 @@ pub fn build_wix_app_installer(
);
// Create the update task XML
let skip_uac_task = Handlebars::new();
let mut skip_uac_task = Handlebars::new();
let xml = include_str!("./update-task.xml");
let update_content = skip_uac_task.render_template(xml, &data)?;
skip_uac_task
.register_template_string("update.xml", xml)
.map_err(|e| e.to_string())
.expect("Failed to setup Update Task handlebars");
let temp_xml_path = output_path.join("update.xml");
let update_content = skip_uac_task.render("update.xml", &data)?;
fs::write(temp_xml_path, update_content)?;
// Create the Powershell script to install the task
let mut skip_uac_task_installer = Handlebars::new();
skip_uac_task_installer.register_escape_fn(handlebars::no_escape);
let xml = include_str!("./install-task.ps1");
let install_script_content = skip_uac_task_installer.render_template(xml, &data)?;
skip_uac_task_installer
.register_template_string("install-task.ps1", xml)
.map_err(|e| e.to_string())
.expect("Failed to setup Update Task Installer handlebars");
let temp_ps1_path = output_path.join("install-task.ps1");
let install_script_content = skip_uac_task_installer.render("install-task.ps1", &data)?;
fs::write(temp_ps1_path, install_script_content)?;
// Create the Powershell script to uninstall the task
let mut skip_uac_task_uninstaller = Handlebars::new();
skip_uac_task_uninstaller.register_escape_fn(handlebars::no_escape);
let xml = include_str!("./uninstall-task.ps1");
let install_script_content = skip_uac_task_uninstaller.render_template(xml, &data)?;
skip_uac_task_uninstaller
.register_template_string("uninstall-task.ps1", xml)
.map_err(|e| e.to_string())
.expect("Failed to setup Update Task Uninstaller handlebars");
let temp_ps1_path = output_path.join("uninstall-task.ps1");
let install_script_content = skip_uac_task_uninstaller.render("uninstall-task.ps1", &data)?;
fs::write(temp_ps1_path, install_script_content)?;
data.insert("enable_elevated_update_task", to_json(true));
@@ -758,9 +732,7 @@ pub fn build_wix_app_installer(
let extension_regex = Regex::new("\"http://schemas.microsoft.com/wix/(\\w+)\"")?;
for fragment_path in fragment_paths {
let fragment_path = current_dir.join(fragment_path);
let fragment_content = fs::read_to_string(&fragment_path)?;
let fragment_handlebars = Handlebars::new();
let fragment = fragment_handlebars.render_template(&fragment_content, &data)?;
let fragment = fs::read_to_string(&fragment_path)?;
let mut extensions = Vec::new();
for cap in extension_regex.captures_iter(&fragment) {
extensions.push(wix_toolset_path.join(format!("Wix{}.dll", &cap[1])));
@@ -929,11 +901,12 @@ fn get_merge_modules(settings: &Settings) -> crate::Result<Vec<MergeModule>> {
let mut merge_modules = Vec::new();
let regex = Regex::new(r"[^\w\d\.]")?;
for msm in glob::glob(
&PathBuf::from(glob::Pattern::escape(
&settings.project_out_directory().to_string_lossy(),
))
.join("*.msm")
.to_string_lossy(),
settings
.project_out_directory()
.join("*.msm")
.to_string_lossy()
.to_string()
.as_str(),
)? {
let path = msm?;
let filename = path
@@ -1040,13 +1013,8 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
let mut dlls = Vec::new();
// TODO: The bundler should not include all DLLs it finds. Instead it should only include WebView2Loader.dll if present and leave the rest to the resources config.
let out_dir = settings.project_out_directory();
for dll in glob::glob(
&PathBuf::from(glob::Pattern::escape(&out_dir.to_string_lossy()))
.join("*.dll")
.to_string_lossy(),
)? {
for dll in glob::glob(out_dir.join("*.dll").to_string_lossy().to_string().as_str())? {
let path = dll?;
let resource_path = dunce::simplified(&path);
let relative_path = path
@@ -1064,48 +1032,16 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
}
if !dlls.is_empty() {
resources
.entry("".to_string())
.and_modify(|r| r.files.append(&mut dlls))
.or_insert(ResourceDirectory {
resources.insert(
"".to_string(),
ResourceDirectory {
path: "".to_string(),
name: "".to_string(),
directories: vec![],
files: dlls,
});
},
);
}
Ok(resources)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_wix_version() {
assert!(validate_wix_version("1.1.1").is_ok());
assert!(validate_wix_version("1.1.1.1").is_ok());
assert!(validate_wix_version("255.1.1.1").is_ok());
assert!(validate_wix_version("1.255.1.1").is_ok());
assert!(validate_wix_version("1.1.65535.1").is_ok());
assert!(validate_wix_version("1.1.1.65535").is_ok());
assert!(validate_wix_version("256.1.1.1").is_err());
assert!(validate_wix_version("1.256.1.1").is_err());
assert!(validate_wix_version("1.1.65536.1").is_err());
assert!(validate_wix_version("1.1.1.65536").is_err());
}
#[test]
fn converts_version_to_wix() {
assert_eq!(convert_version("1.1.2").unwrap(), "1.1.2");
assert_eq!(convert_version("1.1.2-4").unwrap(), "1.1.2.4");
assert_eq!(convert_version("1.1.2-65535").unwrap(), "1.1.2.65535");
assert_eq!(convert_version("1.1.2+2").unwrap(), "1.1.2.2");
assert!(convert_version("1.1.2-alpha").is_err());
assert!(convert_version("1.1.2-alpha.4").is_err());
assert!(convert_version("1.1.2+asd.3").is_err());
}
}

View File

@@ -37,7 +37,7 @@
<Actions Context="Author">
<Exec>
<Command>cmd.exe</Command>
<Arguments>/c ^"%SYSTEMROOT%\System32\msiexec.exe /i "%TEMP%\\{{product_name}}.msi" {{msiexec_args}} /promptrestart^"</Arguments>
<Arguments>/c "%SYSTEMROOT%\System32\msiexec.exe /i %TEMP%\\{{product_name}}.msi {{msiexec_args}} /promptrestart"</Arguments>
</Exec>
</Actions>
</Task>

View File

@@ -56,8 +56,7 @@ ${StrLoc}
!define WEBVIEW2INSTALLERPATH "{{webview2_installer_path}}"
!define MINIMUMWEBVIEW2VERSION "{{minimum_webview2_version}}"
!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}"
!define MANUKEY "Software\${MANUFACTURER}"
!define MANUPRODUCTKEY "${MANUKEY}\${PRODUCTNAME}"
!define MANUPRODUCTKEY "Software\${MANUFACTURER}\${PRODUCTNAME}"
!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}"
!define ESTIMATEDSIZE "{{estimated_size}}"
!define STARTMENUFOLDER "{{start_menu_folder}}"
@@ -835,27 +834,12 @@ Section Uninstall
DeleteRegKey HKCU "${UNINSTKEY}"
!endif
; Removes the Autostart entry for ${PRODUCTNAME} from the HKCU Run key if it exists.
; This ensures the program does not launch automatically after uninstallation if it exists.
; If it doesn't exist, it does nothing.
; We do this when not updating (to preserve the registry value on updates)
${If} $UpdateMode <> 1
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "${PRODUCTNAME}"
${EndIf}
DeleteRegValue HKCU "${MANUPRODUCTKEY}" "Installer Language"
; Delete app data if the checkbox is selected
; and if not updating
${If} $DeleteAppDataCheckboxState = 1
${AndIf} $UpdateMode <> 1
; Clear the install location $INSTDIR from registry
DeleteRegKey SHCTX "${MANUPRODUCTKEY}"
DeleteRegKey /ifempty SHCTX "${MANUKEY}"
; Clear the install language from registry
DeleteRegValue HKCU "${MANUPRODUCTKEY}" "Installer Language"
DeleteRegKey /ifempty HKCU "${MANUPRODUCTKEY}"
DeleteRegKey /ifempty HKCU "${MANUKEY}"
SetShellVarContext current
RmDir /r "$APPDATA\${BUNDLEID}"
RmDir /r "$LOCALAPPDATA\${BUNDLEID}"

View File

@@ -1,27 +0,0 @@
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} "${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 ${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"
LangString olderOrUnknownVersionInstalled ${LANG_ITALIAN} "Una versione $R4 di ${PRODUCTNAME} è installata nel tuo sistema. È consigliato che disinstalli la versione corrente prima di procedere all'installazione. Seleziona l'operazione che vuoi eseguire e clicca Avanti per continuare."
LangString silentDowngrades ${LANG_ITALIAN} "I downgrade sono disabilitati per questo installer, impossibile procedere con l'installer silenzioso, usa invece l'installer con interfaccia grafica.$\n"
LangString unableToUninstall ${LANG_ITALIAN} "Impossibile disinstallare!"
LangString uninstallApp ${LANG_ITALIAN} "Disinstalla ${PRODUCTNAME}"
LangString uninstallBeforeInstalling ${LANG_ITALIAN} "Disinstalla prima di installare"
LangString unknown ${LANG_ITALIAN} "sconosciuta"
LangString webview2AbortError ${LANG_ITALIAN} "Errore nell'installazione di WebView2! L'app non può funzionare senza. Prova a riavviare l'installer."
LangString webview2DownloadError ${LANG_ITALIAN} "Errore: Il download di WebView2 è fallito - $0"
LangString webview2DownloadSuccess ${LANG_ITALIAN} "Bootstrapper WebView2 scaricato con successo"
LangString webview2Downloading ${LANG_ITALIAN} "Scaricando il bootstrapper WebView2..."
LangString webview2InstallError ${LANG_ITALIAN} "Errore: L'installazione di WebView2 è fallita con il codice $1"
LangString webview2InstallSuccess ${LANG_ITALIAN} "WebView2 installato correttamente"
LangString deleteAppData ${LANG_ITALIAN} "Cancella i dati dell'applicazione"

View File

@@ -1,27 +0,0 @@
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} "${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 ${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"
LangString olderOrUnknownVersionInstalled ${LANG_PORTUGUESE} "Uma versão $R4 do ${PRODUCTNAME} está instalada no sistema. Recomenda-se desinstalar a versão atual antes de instalar. Selecione a operação que deseja realizar e clique em Seguinte para continuar."
LangString silentDowngrades ${LANG_PORTUGUESE} "Rebaixamentos estão desativados neste instalador, não é possível prosseguir com a instalação silenciosa. Por favor, utilize o instalador com interface gráfica.$\n"
LangString unableToUninstall ${LANG_PORTUGUESE} "Não foi possível desinstalar!"
LangString uninstallApp ${LANG_PORTUGUESE} "Desinstalar ${PRODUCTNAME}"
LangString uninstallBeforeInstalling ${LANG_PORTUGUESE} "Desinstalar antes de instalar"
LangString unknown ${LANG_PORTUGUESE} "desconhecida"
LangString webview2AbortError ${LANG_PORTUGUESE} "Falha ao instalar o WebView2! A aplicação não pode ser executada sem ele. Tente reiniciar o instalador."
LangString webview2DownloadError ${LANG_PORTUGUESE} "Erro: Falha ao transferir o WebView2 - $0"
LangString webview2DownloadSuccess ${LANG_PORTUGUESE} "Bootstrapper do WebView2 transferido com sucesso"
LangString webview2Downloading ${LANG_PORTUGUESE} "A transferir o Bootstrapper do WebView2..."
LangString webview2InstallError ${LANG_PORTUGUESE} "Erro: Instalação do WebView2 falhou com o código $1"
LangString webview2InstallSuccess ${LANG_PORTUGUESE} "WebView2 instalado com sucesso"
LangString deleteAppData ${LANG_PORTUGUESE} "Eliminar os dados da aplicação"

View File

@@ -1,27 +1,27 @@
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} "¡${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 ${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"
LangString olderOrUnknownVersionInstalled ${LANG_SPANISHINTERNATIONAL} "Una versión $R4 de ${PRODUCTNAME} está instalada en su sistema. Es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar."
LangString silentDowngrades ${LANG_SPANISHINTERNATIONAL} "Disminuir la versión está deshabilitado para este instalador. No se puede continuar con el instalador silencioso, por favor use el instalador de interfaz gráfica.$\n"
LangString unableToUninstall ${LANG_SPANISHINTERNATIONAL} "No se ha podido desinstalar."
LangString uninstallApp ${LANG_SPANISHINTERNATIONAL} "Desinstalar ${PRODUCTNAME}"
LangString uninstallBeforeInstalling ${LANG_SPANISHINTERNATIONAL} "Desinstalar antes de instalar"
LangString unknown ${LANG_SPANISHINTERNATIONAL} "desconocida"
LangString webview2AbortError ${LANG_SPANISHINTERNATIONAL} "No se ha podido instalar WebView2. Intente reiniciar el instalador."
LangString webview2DownloadError ${LANG_SPANISHINTERNATIONAL} "Error: No se ha podido descargar WebView2 - $0"
LangString webview2DownloadSuccess ${LANG_SPANISHINTERNATIONAL} "El bootstrapper de WebView2 fue descargado con éxito."
LangString webview2Downloading ${LANG_SPANISHINTERNATIONAL} "Descargando el bootstrapper de WebView2..."
LangString webview2InstallError ${LANG_SPANISHINTERNATIONAL} "Error: La instalación de WebView2 falló con el código $1."
LangString webview2InstallSuccess ${LANG_SPANISHINTERNATIONAL} "WebView2 fue instalado con éxito."
LangString deleteAppData ${LANG_SPANISHINTERNATIONAL} "Eliminar los datos de aplicación"
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} "¡${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 ${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"
LangString olderOrUnknownVersionInstalled ${LANG_SPANISH} "Una versión $R4 de ${PRODUCTNAME} está instalada en su sistema. Es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar."
LangString silentDowngrades ${LANG_SPANISH} "Disminuir la versión está deshabilitado para este instalador. No se puede continuar con el instalador silencioso, por favor use el instalador de interfaz gráfica.$\n"
LangString unableToUninstall ${LANG_SPANISH} "No se ha podido desinstalar."
LangString uninstallApp ${LANG_SPANISH} "Desinstalar ${PRODUCTNAME}"
LangString uninstallBeforeInstalling ${LANG_SPANISH} "Desinstalar antes de instalar"
LangString unknown ${LANG_SPANISH} "desconocida"
LangString webview2AbortError ${LANG_SPANISH} "No se ha podido instalar WebView2. Intente reiniciar el instalador."
LangString webview2DownloadError ${LANG_SPANISH} "Error: No se ha podido descargar WebView2 - $0"
LangString webview2DownloadSuccess ${LANG_SPANISH} "El bootstrapper de WebView2 fue descargado con éxito."
LangString webview2Downloading ${LANG_SPANISH} "Descargando el bootstrapper de WebView2..."
LangString webview2InstallError ${LANG_SPANISH} "Error: La instalación de WebView2 falló con el código $1."
LangString webview2InstallSuccess ${LANG_SPANISH} "WebView2 fue instalado con éxito."
LangString deleteAppData ${LANG_SPANISH} "Eliminar los datos de aplicación"

View File

@@ -1,27 +0,0 @@
LangString addOrReinstall ${LANG_UKRAINIAN} "Додати/Перевстановити компоненти"
LangString alreadyInstalled ${LANG_UKRAINIAN} "Вже встановлено"
LangString alreadyInstalledLong ${LANG_UKRAINIAN} "${PRODUCTNAME} ${VERSION} вже встановлено. Виберіть дію, яку ви хочете виконати, і натисніть Далі, щоб продовжити."
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} "Не вдалося примусово закрити ${PRODUCTNAME}. Будь ласка, спочатку закрийте його, а потім спробуйте ще раз"
LangString installingWebview2 ${LANG_UKRAINIAN} "Встановлення WebView2..."
LangString newerVersionInstalled ${LANG_UKRAINIAN} "Новіша версія ${PRODUCTNAME} вже встановлена! Встановлювати старішу версію не рекомендується. Якщо ви дійсно хочете встановити цю версію, краще спочатку видаліть поточну. Виберіть дію, яку ви хочете виконати, і натисніть Далі, щоб продовжити."
LangString older ${LANG_UKRAINIAN} "старішу"
LangString olderOrUnknownVersionInstalled ${LANG_UKRAINIAN} "У вашій системі вже встановлено $R4 версію ${PRODUCTNAME}. Рекомендується видалити поточну версію перед встановленням. Виберіть дію, яку ви хочете виконати, і натисніть Далі, щоб продовжити."
LangString silentDowngrades ${LANG_UKRAINIAN} "Для цього встановлювача вимкнено зниження версій. Неможливо продовжити роботу з фоновим встановлювачем. Будь ласка, скористайтеся встановлювачем з графічним інтерфейсом.$\n"
LangString unableToUninstall ${LANG_UKRAINIAN} "Не вдалося видалити!"
LangString uninstallApp ${LANG_UKRAINIAN} "Видалити ${PRODUCTNAME}"
LangString uninstallBeforeInstalling ${LANG_UKRAINIAN} "Видалити перед встановленням"
LangString unknown ${LANG_UKRAINIAN} "невідому"
LangString webview2AbortError ${LANG_UKRAINIAN} "Не вдалося встановити WebView2! Без нього програма не може працювати. Спробуйте перезапустити встановлювач."
LangString webview2DownloadError ${LANG_UKRAINIAN} "Помилка: не вдалося завантажити WebView2 - $0"
LangString webview2DownloadSuccess ${LANG_UKRAINIAN} "WebView2 успішно завантажено"
LangString webview2Downloading ${LANG_UKRAINIAN} "Завантаження WebView2..."
LangString webview2InstallError ${LANG_UKRAINIAN} "Помилка: не вдалося встановити WebView2, код виходу - $1"
LangString webview2InstallSuccess ${LANG_UKRAINIAN} "WebView2 успішно встановлено "
LangString deleteAppData ${LANG_UKRAINIAN} "Видалити дані програми"

View File

@@ -2,21 +2,17 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::bundle::settings::Arch;
use crate::bundle::windows::sign::{sign_command, try_sign};
use crate::{
bundle::{
settings::Arch,
windows::{
sign::{sign_command, try_sign},
util::{
download_webview2_bootstrapper, download_webview2_offline_installer,
NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME,
},
common::CommandExt,
windows::util::{
download_and_verify, download_webview2_bootstrapper, download_webview2_offline_installer,
verify_file_hash, HashAlgorithm, NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME,
},
},
utils::{
http_utils::{download_and_verify, verify_file_hash, HashAlgorithm},
CommandExt,
},
Settings,
};
use tauri_utils::display_path;
@@ -39,8 +35,8 @@ const NSIS_URL: &str =
#[cfg(target_os = "windows")]
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.4.2/nsis_tauri_utils.dll";
const NSIS_TAURI_UTILS_SHA1: &str = "6532DA4545864C6EC95F62F27F2199BFD668560B";
"https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.4.1/nsis_tauri_utils.dll";
const NSIS_TAURI_UTILS_SHA1: &str = "F99A50209A345185A84D34D0E5F66D04C75FF52F";
#[cfg(target_os = "windows")]
const NSIS_REQUIRED_FILES: &[&str] = &[
@@ -94,8 +90,8 @@ pub fn bundle_project(settings: &Settings, updater: bool) -> crate::Result<Vec<P
if !mismatched.is_empty() {
log::warn!("NSIS directory contains mis-hashed files. Redownloading them.");
for (path, url, hash, hash_algorithm) in mismatched {
let data = download_and_verify(url, hash, *hash_algorithm)?;
for (path, url, hash, hash_algorithim) in mismatched {
let data = download_and_verify(url, hash, *hash_algorithim)?;
fs::write(nsis_toolset_path.join(path), data)?;
}
}
@@ -112,7 +108,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)?;
crate::bundle::windows::util::extract_zip(&data, _tauri_tools_path)?;
fs::rename(_tauri_tools_path.join("nsis-3.08"), nsis_toolset_path)?;
}
@@ -131,7 +127,7 @@ fn get_and_extract_nsis(nsis_toolset_path: &Path, _tauri_tools_path: &Path) -> c
Ok(())
}
fn try_add_numeric_build_number(version_str: &str) -> anyhow::Result<String> {
fn add_build_number_if_needed(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>();
@@ -141,10 +137,7 @@ fn try_add_numeric_build_number(version_str: &str) -> anyhow::Result<String> {
version.major, version.minor, version.patch, version.build
));
} else {
log::warn!(
"Unable to parse version build metadata. Numeric value expected, received: `{}`. This will be replaced with `0` in `VIProductVersion` because Windows requires this field to be numeric.",
version.build
);
anyhow::bail!("optional build metadata in app version must be numeric-only");
}
}
@@ -153,7 +146,6 @@ fn try_add_numeric_build_number(version_str: &str) -> anyhow::Result<String> {
version.major, version.minor, version.patch,
))
}
fn build_nsis_app_installer(
settings: &Settings,
_nsis_toolset_path: &Path,
@@ -218,7 +210,7 @@ fn build_nsis_app_installer(
data.insert("version", to_json(version));
data.insert(
"version_with_build",
to_json(try_add_numeric_build_number(version)?),
to_json(add_build_number_if_needed(version)?),
);
data.insert(
@@ -599,24 +591,10 @@ fn association_description(
type ResourcesMap = BTreeMap<PathBuf, (PathBuf, PathBuf)>;
fn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {
let mut resources = ResourcesMap::new();
let cwd = std::env::current_dir()?;
let mut added_resources = Vec::new();
// Adding WebViewer2Loader.dll in case windows-gnu toolchain is used
if settings.target().ends_with("-gnu") {
let loader_path =
dunce::simplified(&settings.project_out_directory().join("WebView2Loader.dll")).to_path_buf();
if loader_path.exists() {
added_resources.push(loader_path.clone());
resources.insert(
loader_path,
(PathBuf::new(), PathBuf::from("WebView2Loader.dll")),
);
}
}
for resource in settings.resource_files().iter() {
let resource = resource?;
@@ -706,7 +684,6 @@ fn get_lang_data(lang: &str) -> Option<(String, &[u8])> {
"dutch" => include_bytes!("./languages/Dutch.nsh"),
"english" => include_bytes!("./languages/English.nsh"),
"german" => include_bytes!("./languages/German.nsh"),
"italian" => include_bytes!("./languages/Italian.nsh"),
"japanese" => include_bytes!("./languages/Japanese.nsh"),
"korean" => include_bytes!("./languages/Korean.nsh"),
"portuguesebr" => include_bytes!("./languages/PortugueseBR.nsh"),
@@ -719,8 +696,6 @@ fn get_lang_data(lang: &str) -> Option<(String, &[u8])> {
"persian" => include_bytes!("./languages/Persian.nsh"),
"turkish" => include_bytes!("./languages/Turkish.nsh"),
"swedish" => include_bytes!("./languages/Swedish.nsh"),
"portuguese" => include_bytes!("./languages/Portuguese.nsh"),
"ukrainian" => include_bytes!("./languages/Ukrainian.nsh"),
_ => return None,
};
Some((path, content))

View File

@@ -6,7 +6,7 @@
use crate::bundle::settings::CustomSignCommandSettings;
#[cfg(windows)]
use crate::bundle::windows::util;
use crate::{utils::CommandExt, Settings};
use crate::{bundle::common::CommandExt, Settings};
#[cfg(windows)]
use std::path::PathBuf;
#[cfg(windows)]

View File

@@ -3,13 +3,15 @@
// SPDX-License-Identifier: MIT
use std::{
fs::create_dir_all,
fs::{create_dir_all, File},
io::{Cursor, Read, Write},
path::{Path, PathBuf},
};
use ureq::ResponseExt;
use crate::utils::http_utils::download;
use regex::Regex;
use sha2::Digest;
use url::Url;
use zip::ZipArchive;
pub const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703";
pub const WEBVIEW2_OFFLINE_INSTALLER_X86_URL: &str =
@@ -24,12 +26,9 @@ 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: ureq::Agent = ureq::Agent::config_builder()
.proxy(ureq::Proxy::try_from_env())
.build()
.into();
let agent = ureq::AgentBuilder::new().try_proxy_from_env(true).build();
let response = agent.head(url).call().map_err(Box::new)?;
let final_url = response.get_uri().to_string();
let final_url = response.get_url();
let remaining_url = final_url.strip_prefix(WEBVIEW2_URL_PREFIX).ok_or_else(|| {
anyhow::anyhow!(
"WebView2 URL prefix mismatch. Expected `{}`, found `{}`.",
@@ -70,6 +69,148 @@ pub fn download_webview2_offline_installer(base_path: &Path, arch: &str) -> crat
Ok(file_path)
}
fn generate_github_mirror_url_from_template(github_url: &str) -> Option<String> {
std::env::var("TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE")
.ok()
.and_then(|template| {
let re =
Regex::new(r"https://github.com/([^/]+)/([^/]+)/releases/download/([^/]+)/(.*)").unwrap();
re.captures(github_url).map(|caps| {
template
.replace("<owner>", &caps[1])
.replace("<repo>", &caps[2])
.replace("<version>", &caps[3])
.replace("<asset>", &caps[4])
})
})
}
fn generate_github_mirror_url_from_base(github_url: &str) -> Option<String> {
std::env::var("TAURI_BUNDLER_TOOLS_GITHUB_MIRROR")
.ok()
.and_then(|cdn| Url::parse(&cdn).ok())
.map(|mut cdn| {
cdn.set_path(github_url);
cdn.to_string()
})
}
fn generate_github_alternative_url(url: &str) -> Option<(ureq::Agent, String)> {
if !url.starts_with("https://github.com/") {
return None;
}
generate_github_mirror_url_from_template(url)
.or_else(|| generate_github_mirror_url_from_base(url))
.map(|alt_url| (ureq::AgentBuilder::new().build(), alt_url))
}
fn create_agent_and_url(url: &str) -> (ureq::Agent, String) {
generate_github_alternative_url(url).unwrap_or((
ureq::AgentBuilder::new().try_proxy_from_env(true).build(),
url.to_owned(),
))
}
pub fn download(url: &str) -> crate::Result<Vec<u8>> {
let (agent, final_url) = create_agent_and_url(url);
log::info!(action = "Downloading"; "{}", final_url);
let response = agent.get(&final_url).call().map_err(Box::new)?;
let mut bytes = Vec::new();
response.into_reader().read_to_end(&mut bytes)?;
Ok(bytes)
}
#[derive(Clone, Copy)]
pub enum HashAlgorithm {
#[cfg(target_os = "windows")]
Sha256,
Sha1,
}
/// Function used to download a file and checks SHA256 to verify the download.
pub fn download_and_verify(
url: &str,
hash: &str,
hash_algorithm: HashAlgorithm,
) -> crate::Result<Vec<u8>> {
let data = download(url)?;
log::info!("validating hash");
verify_hash(&data, hash, hash_algorithm)?;
Ok(data)
}
pub fn verify_hash(data: &[u8], hash: &str, hash_algorithm: HashAlgorithm) -> crate::Result<()> {
match hash_algorithm {
#[cfg(target_os = "windows")]
HashAlgorithm::Sha256 => {
let hasher = sha2::Sha256::new();
verify_data_with_hasher(data, hash, hasher)
}
HashAlgorithm::Sha1 => {
let hasher = sha1::Sha1::new();
verify_data_with_hasher(data, hash, hasher)
}
}
}
fn verify_data_with_hasher(data: &[u8], hash: &str, mut hasher: impl Digest) -> crate::Result<()> {
hasher.update(data);
let url_hash = hasher.finalize().to_vec();
let expected_hash = hex::decode(hash)?;
if expected_hash == url_hash {
Ok(())
} else {
Err(crate::Error::HashError)
}
}
pub fn verify_file_hash<P: AsRef<Path>>(
path: P,
hash: &str,
hash_algorithm: HashAlgorithm,
) -> crate::Result<()> {
let data = std::fs::read(path)?;
verify_hash(&data, hash, hash_algorithm)
}
/// Extracts the zips from memory into a usable path.
#[allow(dead_code)]
pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> {
let cursor = Cursor::new(data);
let mut zipa = ZipArchive::new(cursor)?;
for i in 0..zipa.len() {
let mut file = zipa.by_index(i)?;
if let Some(name) = file.enclosed_name() {
let dest_path = path.join(name);
if file.is_dir() {
create_dir_all(&dest_path)?;
continue;
}
let parent = dest_path.parent().expect("Failed to get parent");
if !parent.exists() {
create_dir_all(parent)?;
}
let mut buff: Vec<u8> = Vec::new();
file.read_to_end(&mut buff)?;
let mut fileout = File::create(dest_path).expect("Failed to open file");
fileout.write_all(&buff)?;
}
}
Ok(())
}
#[cfg(target_os = "windows")]
pub fn os_bitness<'a>() -> Option<&'a str> {
use windows_sys::Win32::System::SystemInformation::{
@@ -84,3 +225,57 @@ pub fn os_bitness<'a>() -> Option<&'a str> {
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::generate_github_mirror_url_from_template;
use std::env;
const GITHUB_ASSET_URL: &str =
"https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip";
const NON_GITHUB_ASSET_URL: &str = "https://someotherwebsite.com/somefile.zip";
#[test]
fn test_generate_mirror_url_no_env_var() {
env::remove_var("TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE");
assert!(generate_github_mirror_url_from_template(GITHUB_ASSET_URL).is_none());
}
#[test]
fn test_generate_mirror_url_non_github_url() {
env::set_var(
"TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE",
"https://mirror.example.com/<owner>/<repo>/releases/download/<version>/<asset>",
);
assert!(generate_github_mirror_url_from_template(NON_GITHUB_ASSET_URL).is_none());
}
struct TestCase {
template: &'static str,
expected_url: &'static str,
}
#[test]
fn test_generate_mirror_url_correctly() {
let test_cases = vec![
TestCase {
template: "https://mirror.example.com/<owner>/<repo>/releases/download/<version>/<asset>",
expected_url: "https://mirror.example.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip",
},
TestCase {
template: "https://mirror.example.com/<asset>",
expected_url: "https://mirror.example.com/wix311-binaries.zip",
},
];
for case in test_cases {
env::set_var("TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE", case.template);
assert_eq!(
generate_github_mirror_url_from_template(GITHUB_ASSET_URL),
Some(case.expected_url.to_string())
);
}
}
}

View File

@@ -3,6 +3,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! [![](https://github.com/tauri-apps/tauri/raw/dev/.github/splash.png)](https://tauri.app)
//!
//! The Tauri bundler is a tool that generates installers or app bundles for executables.
//! It supports auto updating through [tauri](https://docs.rs/tauri).
//!
@@ -23,6 +25,5 @@
/// The bundle API.
pub mod bundle;
mod error;
mod utils;
pub use bundle::*;
pub use error::{Error, Result};

View File

@@ -1,219 +0,0 @@
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
fs::{create_dir_all, File},
io::{Cursor, Read, Write},
path::Path,
};
use regex::Regex;
use sha2::Digest;
use url::Url;
use zip::ZipArchive;
fn generate_github_mirror_url_from_template(github_url: &str) -> Option<String> {
std::env::var("TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE")
.ok()
.and_then(|template| {
let re =
Regex::new(r"https://github.com/([^/]+)/([^/]+)/releases/download/([^/]+)/(.*)").unwrap();
re.captures(github_url).map(|caps| {
template
.replace("<owner>", &caps[1])
.replace("<repo>", &caps[2])
.replace("<version>", &caps[3])
.replace("<asset>", &caps[4])
})
})
}
fn generate_github_mirror_url_from_base(github_url: &str) -> Option<String> {
std::env::var("TAURI_BUNDLER_TOOLS_GITHUB_MIRROR")
.ok()
.and_then(|cdn| Url::parse(&cdn).ok())
.map(|mut cdn| {
cdn.set_path(github_url);
cdn.to_string()
})
}
fn generate_github_alternative_url(url: &str) -> Option<(ureq::Agent, String)> {
if !url.starts_with("https://github.com/") {
return None;
}
generate_github_mirror_url_from_template(url)
.or_else(|| generate_github_mirror_url_from_base(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((
ureq::Agent::config_builder()
.proxy(ureq::Proxy::try_from_env())
.build()
.into(),
url.to_owned(),
))
}
#[allow(dead_code)]
pub fn download(url: &str) -> crate::Result<Vec<u8>> {
let (agent, final_url) = create_agent_and_url(url);
log::info!(action = "Downloading"; "{}", final_url);
let response = agent.get(&final_url).call().map_err(Box::new)?;
let mut bytes = Vec::new();
response.into_body().into_reader().read_to_end(&mut bytes)?;
Ok(bytes)
}
#[allow(dead_code)]
#[derive(Clone, Copy)]
pub enum HashAlgorithm {
#[cfg(target_os = "windows")]
Sha256,
Sha1,
}
/// Function used to download a file and checks SHA256 to verify the download.
#[allow(dead_code)]
pub fn download_and_verify(
url: &str,
hash: &str,
hash_algorithm: HashAlgorithm,
) -> crate::Result<Vec<u8>> {
let data = download(url)?;
log::info!("validating hash");
verify_hash(&data, hash, hash_algorithm)?;
Ok(data)
}
#[allow(dead_code)]
pub fn verify_hash(data: &[u8], hash: &str, hash_algorithm: HashAlgorithm) -> crate::Result<()> {
match hash_algorithm {
#[cfg(target_os = "windows")]
HashAlgorithm::Sha256 => {
let hasher = sha2::Sha256::new();
verify_data_with_hasher(data, hash, hasher)
}
HashAlgorithm::Sha1 => {
let hasher = sha1::Sha1::new();
verify_data_with_hasher(data, hash, hasher)
}
}
}
fn verify_data_with_hasher(data: &[u8], hash: &str, mut hasher: impl Digest) -> crate::Result<()> {
hasher.update(data);
let url_hash = hasher.finalize().to_vec();
let expected_hash = hex::decode(hash)?;
if expected_hash == url_hash {
Ok(())
} else {
Err(crate::Error::HashError)
}
}
#[allow(dead_code)]
pub fn verify_file_hash<P: AsRef<Path>>(
path: P,
hash: &str,
hash_algorithm: HashAlgorithm,
) -> crate::Result<()> {
let data = std::fs::read(path)?;
verify_hash(&data, hash, hash_algorithm)
}
/// Extracts the zips from memory into a usable path.
#[allow(dead_code)]
pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> {
let cursor = Cursor::new(data);
let mut zipa = ZipArchive::new(cursor)?;
for i in 0..zipa.len() {
let mut file = zipa.by_index(i)?;
if let Some(name) = file.enclosed_name() {
let dest_path = path.join(name);
if file.is_dir() {
create_dir_all(&dest_path)?;
continue;
}
let parent = dest_path.parent().expect("Failed to get parent");
if !parent.exists() {
create_dir_all(parent)?;
}
let mut buff: Vec<u8> = Vec::new();
file.read_to_end(&mut buff)?;
let mut fileout = File::create(dest_path).expect("Failed to open file");
fileout.write_all(&buff)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::generate_github_mirror_url_from_template;
use std::env;
const GITHUB_ASSET_URL: &str =
"https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip";
const NON_GITHUB_ASSET_URL: &str = "https://someotherwebsite.com/somefile.zip";
#[test]
fn test_generate_mirror_url_no_env_var() {
env::remove_var("TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE");
assert!(generate_github_mirror_url_from_template(GITHUB_ASSET_URL).is_none());
}
#[test]
fn test_generate_mirror_url_non_github_url() {
env::set_var(
"TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE",
"https://mirror.example.com/<owner>/<repo>/releases/download/<version>/<asset>",
);
assert!(generate_github_mirror_url_from_template(NON_GITHUB_ASSET_URL).is_none());
}
struct TestCase {
template: &'static str,
expected_url: &'static str,
}
#[test]
fn test_generate_mirror_url_correctly() {
let test_cases = vec![
TestCase {
template: "https://mirror.example.com/<owner>/<repo>/releases/download/<version>/<asset>",
expected_url: "https://mirror.example.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip",
},
TestCase {
template: "https://mirror.example.com/<asset>",
expected_url: "https://mirror.example.com/wix311-binaries.zip",
},
];
for case in test_cases {
env::set_var("TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE", case.template);
assert_eq!(
generate_github_mirror_url_from_template(GITHUB_ASSET_URL),
Some(case.expected_url.to_string())
);
}
}
}

View File

@@ -1,141 +0,0 @@
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
ffi::OsStr,
io::{BufRead, BufReader},
path::Path,
process::{Command, ExitStatus, Output, Stdio},
sync::{Arc, Mutex},
};
pub mod fs_utils;
pub mod http_utils;
/// Returns true if the path has a filename indicating that it is a high-density
/// "retina" icon. Specifically, returns true the file stem ends with
/// "@2x" (a convention specified by the [Apple developer docs](
/// <https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html>)).
#[allow(dead_code)]
pub fn is_retina(path: &Path) -> bool {
path
.file_stem()
.and_then(OsStr::to_str)
.map(|stem| stem.ends_with("@2x"))
.unwrap_or(false)
}
pub trait CommandExt {
// The `pipe` function sets the stdout and stderr to properly
// show the command output in the Node.js wrapper.
fn piped(&mut self) -> std::io::Result<ExitStatus>;
fn output_ok(&mut self) -> crate::Result<Output>;
}
impl CommandExt for Command {
fn piped(&mut self) -> std::io::Result<ExitStatus> {
self.stdin(os_pipe::dup_stdin()?);
self.stdout(os_pipe::dup_stdout()?);
self.stderr(os_pipe::dup_stderr()?);
let program = self.get_program().to_string_lossy().into_owned();
log::debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
self.status()
}
fn output_ok(&mut self) -> crate::Result<Output> {
let program = self.get_program().to_string_lossy().into_owned();
log::debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
self.stdout(Stdio::piped());
self.stderr(Stdio::piped());
let mut child = self.spawn()?;
let mut stdout = child.stdout.take().map(BufReader::new).unwrap();
let stdout_lines = Arc::new(Mutex::new(Vec::new()));
let stdout_lines_ = stdout_lines.clone();
std::thread::spawn(move || {
let mut line = String::new();
let mut lines = stdout_lines_.lock().unwrap();
loop {
line.clear();
match stdout.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stdout"; "{}", line.trim_end());
lines.extend(line.as_bytes().to_vec());
}
Err(_) => (),
}
}
});
let mut stderr = child.stderr.take().map(BufReader::new).unwrap();
let stderr_lines = Arc::new(Mutex::new(Vec::new()));
let stderr_lines_ = stderr_lines.clone();
std::thread::spawn(move || {
let mut line = String::new();
let mut lines = stderr_lines_.lock().unwrap();
loop {
line.clear();
match stderr.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stderr"; "{}", line.trim_end());
lines.extend(line.as_bytes().to_vec());
}
Err(_) => (),
}
}
});
let status = child.wait()?;
let output = Output {
status,
stdout: std::mem::take(&mut *stdout_lines.lock().unwrap()),
stderr: std::mem::take(&mut *stderr_lines.lock().unwrap()),
};
if output.status.success() {
Ok(output)
} else {
Err(crate::Error::GenericError(format!(
"failed to run {program}"
)))
}
}
}
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use tauri_utils::resources::resource_relpath;
use super::is_retina;
#[test]
fn retina_icon_paths() {
assert!(!is_retina(Path::new("data/icons/512x512.png")));
assert!(is_retina(Path::new("data/icons/512x512@2x.png")));
}
#[test]
fn resource_relative_paths() {
assert_eq!(
resource_relpath(Path::new("./data/images/button.png")),
PathBuf::from("data/images/button.png")
);
assert_eq!(
resource_relpath(Path::new("../../images/wheel.png")),
PathBuf::from("_up_/_up_/images/wheel.png")
);
assert_eq!(
resource_relpath(Path::new("/home/ferris/crab.png")),
PathBuf::from("_root_/home/ferris/crab.png")
);
}
}

View File

@@ -1,224 +1,5 @@
# Changelog
## \[2.4.1]
### Enhancements
- [`f805061d1`](https://www.github.com/tauri-apps/tauri/commit/f805061d1152bc4790dbdb9475a506afcdd1de75) ([#13079](https://www.github.com/tauri-apps/tauri/pull/13079) by [@Pietagorh](https://www.github.com/tauri-apps/tauri/../../Pietagorh)) Add support for passing TOML and JSON5 config files to `--config` arg
### Bug Fixes
- [`30beb6fee`](https://www.github.com/tauri-apps/tauri/commit/30beb6fee7b052e588ee72c238e315a557b5d6f2) ([#13096](https://www.github.com/tauri-apps/tauri/pull/13096) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `tauri info` can't find the latest version for rust crates
- [`30beb6fee`](https://www.github.com/tauri-apps/tauri/commit/30beb6fee7b052e588ee72c238e315a557b5d6f2) ([#13096](https://www.github.com/tauri-apps/tauri/pull/13096) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `tauri info` logging network operations in `info` level instead of `debug`
- [`794af778e`](https://www.github.com/tauri-apps/tauri/commit/794af778e4915ffb6a4fe9bae8fba04bc880503d) ([#13117](https://www.github.com/tauri-apps/tauri/pull/13117) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix setting merge config value to null with `--config` arg no longer works
### Dependencies
- Upgraded to `tauri-bundler@2.3.1`
- Upgraded to `tauri-utils@2.3.1`
## \[2.4.0]
### New Features
- [`d91bfa5cb`](https://www.github.com/tauri-apps/tauri/commit/d91bfa5cb921a078758edd45ef3eaff71358d1eb) ([#12970](https://www.github.com/tauri-apps/tauri/pull/12970) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow merging multiple configuration values on `tauri dev`, `tauri build`, `tauri bundle`, `tauri android dev`, `tauri android build`, `tauri ios dev` and `tauri ios build`.
- [`013f8f652`](https://www.github.com/tauri-apps/tauri/commit/013f8f652302f2d49c5ec0a075582033d8b074fb) ([#12890](https://www.github.com/tauri-apps/tauri/pull/12890) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Reads `build > removeUnusedCommands` from the config file and pass in the environment variables on the build command to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. For this to work on inlined plugins you must add a `#![plugin(<insert_plugin_name>)]` inside the `tauri::generate_handler![]` usage and the app manifest must be set.
- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS.
### Enhancements
- [`f981a5ee8`](https://www.github.com/tauri-apps/tauri/commit/f981a5ee8b292b9ea09329f60cecc7f688dda734) ([#12602](https://www.github.com/tauri-apps/tauri/pull/12602) by [@kxxt](https://www.github.com/tauri-apps/tauri/../../kxxt)) Add basic support for linux riscv64 platform.
### Bug Fixes
- [`0c4700e99`](https://www.github.com/tauri-apps/tauri/commit/0c4700e9907f242eabe579eb6149a1d75174185c) ([#12985](https://www.github.com/tauri-apps/tauri/pull/12985) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The cli will now accept `--bundles updater` again. It's still no-op as it has been for all v2 versions. If you want to build updater artifacts, enable `createUpdaterArtifacts` in `tauri.conf.json`.
- [`eec08a18b`](https://www.github.com/tauri-apps/tauri/commit/eec08a18b66525f5544cd30144d0553260ee3a70) ([#12998](https://www.github.com/tauri-apps/tauri/pull/12998) by [@jason89521](https://www.github.com/tauri-apps/tauri/../../jason89521)) For bun's lockfile, check both `bun.lock` and `bun.lockb`.
- [`b83921226`](https://www.github.com/tauri-apps/tauri/commit/b83921226cb3084992bb5357e7e39a09ea97843e) ([#12977](https://www.github.com/tauri-apps/tauri/pull/12977) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `tauri ios` commands using the wrong working directory with `bun@>1.2`.
- [`f268b3dbd`](https://www.github.com/tauri-apps/tauri/commit/f268b3dbdf313484c85b4a1f69cd7cec63049f35) ([#12871](https://www.github.com/tauri-apps/tauri/pull/12871) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ignore parent .gitignore files on the Tauri project path detection.
### Dependencies
- Upgraded to `tauri-utils@2.3.0`
- Upgraded to `tauri-bundler@2.3.0`
## \[2.3.1]
### Bug Fixes
- [`22e9bf74a`](https://www.github.com/tauri-apps/tauri/commit/22e9bf74a4684c827279a85bb66548e83c1ea5cf) ([#12538](https://www.github.com/tauri-apps/tauri/pull/12538) by [@DeTeam](https://www.github.com/tauri-apps/tauri/../../DeTeam)) Set initialViewController for LaunchScreen (iOS).
## \[2.3.0]
### Enhancements
- [`a2d36b8c3`](https://www.github.com/tauri-apps/tauri/commit/a2d36b8c34a8dcfc6736797ca5cd4665faf75e7e) ([#12181](https://www.github.com/tauri-apps/tauri/pull/12181) by [@bastiankistner](https://www.github.com/tauri-apps/tauri/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only).
- [`6e417c943`](https://www.github.com/tauri-apps/tauri/commit/6e417c9435d8fae0eca9e8d42d6215e887a28d98) ([#12786](https://www.github.com/tauri-apps/tauri/pull/12786) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Added RPM to the list of package types for which signature file will be generated.
### Dependencies
- Upgraded to `tauri-utils@2.2.0`
- Upgraded to `tauri-bundler@2.2.4`
- Upgraded to `tauri-macos-sign@2.1.0`
## \[2.2.7]
### Bug Fixes
- [`8e9134c4a`](https://www.github.com/tauri-apps/tauri/commit/8e9134c4a2047329be0dbb868b7ae061a9d3f190) ([#12511](https://www.github.com/tauri-apps/tauri/pull/12511) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused `tauri dev` to fail because of an incorrect `--bins` flag.
## \[2.2.6]
### Enhancements
- [`1a86974aa`](https://www.github.com/tauri-apps/tauri/commit/1a86974aa3d09957c6b1142a17bbfed9998798fd) ([#12406](https://www.github.com/tauri-apps/tauri/pull/12406) by [@bradleat](https://www.github.com/tauri-apps/tauri/../../bradleat)) `ios build --open` will now let xcode start the rust build process.
- [`9a30bed98`](https://www.github.com/tauri-apps/tauri/commit/9a30bed98c2d8501328006fad5840eb9d533e1c2) ([#12423](https://www.github.com/tauri-apps/tauri/pull/12423) by [@tr3ysmith](https://www.github.com/tauri-apps/tauri/../../tr3ysmith)) Added conditional logic to MacOS codesigning where only executables get the entitlements file when being signed. This solves an issue where the app may not launch when using 3rd party frameworks if certain entitlements are added. Ex: multicast support (must be applied for through apple developer, and the framework would not have that capability).
- [`0b79af711`](https://www.github.com/tauri-apps/tauri/commit/0b79af711430934362602fb950c3e4cb5b59cf9c) ([#12438](https://www.github.com/tauri-apps/tauri/pull/12438) by [@3lpsy](https://www.github.com/tauri-apps/tauri/../../3lpsy)) Log the command used to start the rust app in development.
### Bug Fixes
- [`bc43c738b`](https://www.github.com/tauri-apps/tauri/commit/bc43c738baf686353690d3d9259b4976881718c8) ([#12442](https://www.github.com/tauri-apps/tauri/pull/12442) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that prevented `tauri add` to work for the `clipboard-manager` plugin.
- [`27096cdc0`](https://www.github.com/tauri-apps/tauri/commit/27096cdc05d89b61b2372b4e4a3018c87f240ab8) ([#12445](https://www.github.com/tauri-apps/tauri/pull/12445) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused Tauri's CLI to enable tauri's `native-tls` feature even though it wasn't needed. Moved `reqwest` to a mobile-only dependency in `tauri` and enabled its `rustls-tls` feature flag.
### Dependencies
- Upgraded to `tauri-bundler@2.2.3`
## \[2.2.5]
### Dependencies
- Upgraded to `tauri-bundler@2.2.2`
## \[2.2.4]
### Bug Fixes
- [`cad550445`](https://www.github.com/tauri-apps/tauri/commit/cad5504455ffa53e297cebff473c113b1afa5d29) ([#12354](https://www.github.com/tauri-apps/tauri/pull/12354) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed and issue that caused `tauri add` to try to install incorrect npm packages.
## \[2.2.3]
### Enhancements
- [`a0f2c84d5`](https://www.github.com/tauri-apps/tauri/commit/a0f2c84d51f5086c5055867d6f61ea90c463a26c) ([#12204](https://www.github.com/tauri-apps/tauri/pull/12204) by [@pjf-dev](https://www.github.com/tauri-apps/tauri/../../pjf-dev)) Enhance `tauri icon` command by including 64x64 png size in default icon sizes.
### Bug Fixes
- [`98f62e65a`](https://www.github.com/tauri-apps/tauri/commit/98f62e65a27a375272c6b4d9f34c23e142b9d3a6) ([#12246](https://www.github.com/tauri-apps/tauri/pull/12246) by [@marcomq](https://www.github.com/tauri-apps/tauri/../../marcomq)) Properly add NPM packages for community plugins when using the `tauri add` command.
- [`b9a99a5c6`](https://www.github.com/tauri-apps/tauri/commit/b9a99a5c69d8a2a1a3ff30e500b46872258dca15) ([#12297](https://www.github.com/tauri-apps/tauri/pull/12297) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the built-in dev server to constantly refresh on Linux. This only affected users who do not have `devUrl` point to a URL.
- [`ef21ed9ac`](https://www.github.com/tauri-apps/tauri/commit/ef21ed9ac1c045c38b0c04e3d71a441694abc257) ([#12290](https://www.github.com/tauri-apps/tauri/pull/12290) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS build failing when the development team contains spaces.
### Dependencies
- Upgraded to `tauri-bundler@2.2.1`
## \[2.2.2]
### Bug Fixes
- [`26fc9558f`](https://www.github.com/tauri-apps/tauri/commit/26fc9558fe7b2fe649f61926da88f36110dd5707) ([#12178](https://www.github.com/tauri-apps/tauri/pull/12178) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the `tauri dev` file watcher to exit after detecting file changes.
## \[2.2.1]
### Bug Fixes
- [`881729448`](https://www.github.com/tauri-apps/tauri/commit/881729448c9abd0d0c7941a8a31c94119ce827af) ([#12164](https://www.github.com/tauri-apps/tauri/pull/12164) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused `tauri dev` to crash before showing the app on Linux.
## \[2.2.0]
### New Features
- [`cccb308c7`](https://www.github.com/tauri-apps/tauri/commit/cccb308c7b559b0838138d6cea280665f060c925) ([#11562](https://www.github.com/tauri-apps/tauri/pull/11562) by [@jLynx](https://www.github.com/tauri-apps/tauri/../../jLynx)) Generate signature for `.deb` packages when `createUpdaterArtifacts` option is enabled.
- [`74212d40d`](https://www.github.com/tauri-apps/tauri/commit/74212d40d80dba4501b3d4ae30104fa3d447bdf9) ([#11653](https://www.github.com/tauri-apps/tauri/pull/11653) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Include Linux destkop environment and session type in `tauri info` command.
### Enhancements
- [`93a3a043d`](https://www.github.com/tauri-apps/tauri/commit/93a3a043d39cc96515d51d98beeb14261d3a246b) ([#11727](https://www.github.com/tauri-apps/tauri/pull/11727) by [@Kiyozz](https://www.github.com/tauri-apps/tauri/../../Kiyozz)) Add support for `Portuguese` language for NSIS windows installer.
### Bug Fixes
- [`c8700656b`](https://www.github.com/tauri-apps/tauri/commit/c8700656be3001a0cc6e087f23aebd482430a85b) ([#11985](https://www.github.com/tauri-apps/tauri/pull/11985) by [@ShaunSHamilton](https://www.github.com/tauri-apps/tauri/../../ShaunSHamilton)) Fix `tauri remove` from removing object type (`{}`) permissions.
- [`0ae06c5ca`](https://www.github.com/tauri-apps/tauri/commit/0ae06c5ca89cecd24154affdc69668f5e1e67d85) ([#11914](https://www.github.com/tauri-apps/tauri/pull/11914) by [@wtto00](https://www.github.com/tauri-apps/tauri/../../wtto00)) Fix the exclude path in file `Cargo.toml` of plugin template generated by cli. Path changed in [#9346](https://github.com/tauri-apps/tauri/pull/9346)
### Dependencies
- Upgraded to `tauri-bundler@2.2.0`
- Upgraded to `tauri-utils@2.1.1`
## \[2.1.0]
### New Features
- [`1b6b2cfaa`](https://www.github.com/tauri-apps/tauri/commit/1b6b2cfaa14ab1d418c676cedbf942a812377a30) ([#11521](https://www.github.com/tauri-apps/tauri/pull/11521) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Process `bundle > windows > wix > fragmentPaths` with Handlebars to interpolate expressions within it.
- [`6bf917941`](https://www.github.com/tauri-apps/tauri/commit/6bf917941ff0fcc49e86b3ba427340b75f3ce49c) ([#11322](https://www.github.com/tauri-apps/tauri/pull/11322) by [@ShaunSHamilton](https://www.github.com/tauri-apps/tauri/../../ShaunSHamilton)) Add `tauri remove` to remove plugins from projects.
- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level.
### Enhancements
- [`1f311832a`](https://www.github.com/tauri-apps/tauri/commit/1f311832ab5b2d62a533dfcf9b1d78bddf249ae8) ([#11405](https://www.github.com/tauri-apps/tauri/pull/11405) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add more context for errors when decoding secret and public keys for signing updater artifacts.
- [`e0d1307d3`](https://www.github.com/tauri-apps/tauri/commit/e0d1307d3f78987d0059921a5ab01ea4b26e0ef1) ([#11414](https://www.github.com/tauri-apps/tauri/pull/11414) by [@Czxck001](https://www.github.com/tauri-apps/tauri/../../Czxck001)) Migrate the `$schema` Tauri configuration to the v2 format.
- [`c43d5df15`](https://www.github.com/tauri-apps/tauri/commit/c43d5df15828ecffa606482ea2b60350c488c981) ([#11512](https://www.github.com/tauri-apps/tauri/pull/11512) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Associate a newly created capability file with the `main` window on the `tauri add` and `tauri permission add` commands.
### Bug Fixes
- [`7af01ff2c`](https://www.github.com/tauri-apps/tauri/commit/7af01ff2ce623d727cd13a4c8a549c1c80031882) ([#11523](https://www.github.com/tauri-apps/tauri/pull/11523) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `tauri migrate` failing to install NPM depenencies when running from Deno.
- [`100a4455a`](https://www.github.com/tauri-apps/tauri/commit/100a4455aa48df508510bbc08273215bdf70c012) ([#11529](https://www.github.com/tauri-apps/tauri/pull/11529) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix detecting yarn berry (v2 and higher) in various tauri cli commands.
- [`60e86d5f6`](https://www.github.com/tauri-apps/tauri/commit/60e86d5f6e0f0c769d34ef368cd8801a918d796d) ([#11624](https://www.github.com/tauri-apps/tauri/pull/11624) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Use the public network IP address on `android dev` by default on Windows.
### Dependencies
- Upgraded to `tauri-utils@2.1.0`
- Upgraded to `tauri-bundler@2.1.0`
## \[2.0.4]
### Enhancements
- [`e4c9268b1`](https://www.github.com/tauri-apps/tauri/commit/e4c9268b19c614dc9ebb0895448fd16de7efee80) ([#11258](https://www.github.com/tauri-apps/tauri/pull/11258) by [@regexident](https://www.github.com/tauri-apps/tauri/../../regexident)) Support custom project directory structure where the Tauri app folder is not a subfolder of the frontend project.
The frontend and Tauri app project paths can be set with the `TAURI_FRONTEND_PATH` and the `TAURI_APP_PATH` environment variables respectively.
### Dependencies
- Upgraded to `tauri-bundler@2.0.4`
## \[2.0.3]
### New Features
- [`eda5713ea`](https://www.github.com/tauri-apps/tauri/commit/eda5713eab78d28182071ea25ceca5f1994f37ea) ([#11242](https://www.github.com/tauri-apps/tauri/pull/11242) by [@alex-sandri](https://www.github.com/tauri-apps/tauri/../../alex-sandri)) Add `Italian` to supported NSIS installer languages
- [`b3563e3d6`](https://www.github.com/tauri-apps/tauri/commit/b3563e3d6ae8dc90ee68f25f575cd5538ab1915b) ([#11304](https://www.github.com/tauri-apps/tauri/pull/11304) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add Deno support in tauri-cli operations.
### Bug Fixes
- [`d609bef9f`](https://www.github.com/tauri-apps/tauri/commit/d609bef9fd7cd6eeb2bd701558100bd9cfb6e6f6) ([#11314](https://www.github.com/tauri-apps/tauri/pull/11314) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix android invalid proguard file when using an `identifier` that contains a component that is a reserved kotlin keyword, like `in`, `class`, etc
- [`069c05e44`](https://www.github.com/tauri-apps/tauri/commit/069c05e44fd6f30083fdc00dd6c0001278898592) ([#11315](https://www.github.com/tauri-apps/tauri/pull/11315) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix CLI crashing and failing to find a `.ico` file when `bundle > icon` option is using globs and doesn't have a string that ends with `.ico`.
### Dependencies
- Upgraded to `tauri-bundler@2.0.3`
## \[2.0.2]
### What's Changed
- [`4475fbb50`](https://www.github.com/tauri-apps/tauri/commit/4475fbb502c5ffb3cea4de6bef1c7869be39bed6) ([#11208](https://www.github.com/tauri-apps/tauri/pull/11208) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Update cargo-mobile2 to 0.17.3, fixing lib name validation.
- [`a49a19ffa`](https://www.github.com/tauri-apps/tauri/commit/a49a19ffa304f031fb1a04d31a567cc7f42a380a) ([#11218](https://www.github.com/tauri-apps/tauri/pull/11218)) Fix bundling `appimage`, `deb` and `rpm` bundles failing to open when using `mainBinaryName` with spaces.
### Dependencies
- Upgraded to `tauri-bundler@2.0.2`
## \[2.0.1]
### What's Changed
- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
### Dependencies
- Upgraded to `tauri-utils@2.0.1`
- Upgraded to `tauri-macos-sign@2.0.1`
- Upgraded to `tauri-bundler@2.0.1`
## \[2.0.0]
### What's Changed

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-cli"
version = "2.4.1"
version = "2.0.0"
authors = ["Tauri Programme within The Commons Conservancy"]
edition = "2021"
rust-version = "1.77.2"
@@ -36,29 +36,28 @@ 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.17", default-features = false }
cargo-mobile2 = { version = "0.17.2", default-features = false }
[dependencies]
jsonrpsee = { version = "0.24", features = ["server"] }
jsonrpsee-core = "0.24"
jsonrpsee-client-transport = { version = "0.24", features = ["ws"] }
jsonrpsee-ws-client = { version = "0.24", default-features = false }
jsonrpsee = { version = "0.24.5", features = ["server"] }
jsonrpsee-core = "0.24.5"
jsonrpsee-client-transport = { version = "0.24.5", features = ["ws"] }
jsonrpsee-ws-client = { version = "0.24.5", default-features = false }
sublime_fuzzy = "0.7"
clap_complete = "4"
clap = { version = "4", features = ["derive", "env"] }
anyhow = "1"
tauri-bundler = { version = "2.3.1", 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.5"
shared_child = "1"
clap = { version = "4.5", features = ["derive", "env"] }
anyhow = "1.0"
tauri-bundler = { version = "2.0.0", default-features = false, path = "../tauri-bundler" }
colored = "2.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
notify = "6.1"
notify-debouncer-mini = "0.4"
shared_child = "1.0"
duct = "0.13"
toml_edit = { version = "0.22", features = ["serde"] }
json-patch = "3"
tauri-utils = { version = "2.3.1", path = "../tauri-utils", features = [
json-patch = "2.0"
tauri-utils = { version = "2.0.0", path = "../tauri-utils", features = [
"isolation",
"schema",
"config-json5",
@@ -71,42 +70,41 @@ tauri-utils-v1 = { version = "1", package = "tauri-utils", features = [
"config-toml",
] }
toml = "0.8"
jsonschema = "0.29"
handlebars = "6"
jsonschema = "0.18"
handlebars = "6.0"
include_dir = "0.7"
minisign = "=0.7.3"
base64 = "0.22"
ureq = { version = "3", default-features = false, features = ["gzip"] }
base64 = "0.22.0"
ureq = { version = "2.9.6", default-features = false, features = ["gzip"] }
os_info = "3"
semver = "1"
regex = "1"
semver = "1.0"
regex = "1.10.3"
heck = "0.5"
dialoguer = "0.11"
url = { version = "2", features = ["serde"] }
url = { version = "2.5", features = ["serde"] }
os_pipe = "1"
ignore = "0.4"
ctrlc = "3"
ctrlc = "3.4"
log = { version = "0.4.21", features = ["kv", "kv_std"] }
env_logger = "0.11"
env_logger = "0.11.5"
icns = { package = "tauri-icns", version = "0.1" }
image = { version = "0.25", default-features = false, features = ["ico"] }
axum = { version = "0.7", features = ["ws"] }
axum = { version = "0.7.4", features = ["ws"] }
html5ever = "0.26"
kuchiki = { package = "kuchikiki", version = "0.8" }
tokio = { version = "1", features = ["macros", "sync"] }
common-path = "1"
serde-value = "0.7"
serde-value = "0.7.0"
itertools = "0.13"
local-ip-address = "0.6"
css-color = "0.2"
resvg = "0.45.0"
resvg = "0.43.0"
dunce = "1"
glob = "0.3"
# 0.39 raised msrv to above 1.78 but 0.37+ can't compile on 1.77.2 either.
oxc_parser = "0.36"
oxc_span = "0.36"
oxc_allocator = "0.36"
oxc_ast = "0.36"
oxc_parser = "0.24"
oxc_span = "0.24"
oxc_allocator = "0.24"
oxc_ast = "0.24"
magic_string = "0.3"
phf = { version = "0.11", features = ["macros"] }
walkdir = "2"
@@ -133,7 +131,7 @@ libc = "0.2"
[target."cfg(target_os = \"macos\")".dependencies]
plist = "1"
tauri-macos-sign = { version = "2.1.0", path = "../tauri-macos-sign" }
tauri-macos-sign = { version = "2.0.0", path = "../tauri-macos-sign" }
object = { version = "0.36", default-features = false, features = [
"macho",
"read_core",
@@ -149,4 +147,4 @@ native-tls = [
"ureq/native-tls",
]
native-tls-vendored = ["native-tls", "tauri-bundler/native-tls-vendored"]
rustls = ["tauri-bundler/rustls", "cargo-mobile2/rustls", "ureq/rustls"]
rustls = ["tauri-bundler/rustls", "cargo-mobile2/rustls", "ureq/tls"]

View File

@@ -17,7 +17,6 @@ These environment variables are inputs to the CLI which may have an equivalent C
- `TAURI_BUNDLER_WIX_FIPS_COMPLIANT` — Specify the bundler's WiX `FipsCompliant` option.
- `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR` - Specify a GitHub mirror to download files and tools used by tauri bundler.
- `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE` - Specify a GitHub mirror template to download files and tools used by tauri bundler, for example: `https://mirror.example.com/<owner>/<repo>/releases/download/<version>/<asset>`.
- `TAURI_BUNDLER_DMG_IGNORE_CI` - Disable the check for `CI: true` in the `.dmg` bundler.
- `TAURI_SKIP_SIDECAR_SIGNATURE_CHECK` - Skip signing sidecars.
- `TAURI_SIGNING_PRIVATE_KEY` — Private key used to sign your app bundles, can be either a string or a path to the file.
- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` — The signing private key password, see `TAURI_SIGNING_PRIVATE_KEY`.

View File

@@ -1,8 +1,8 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://schema.tauri.app/config/2.4.1",
"$id": "https://schema.tauri.app/config/2.0.0",
"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 ```",
"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\": \"../dist\",\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",
"required": [
"identifier"
@@ -69,9 +69,7 @@
},
"build": {
"description": "The build configuration.",
"default": {
"removeUnusedCommands": false
},
"default": {},
"allOf": [
{
"$ref": "#/definitions/BuildConfig"
@@ -229,7 +227,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).",
"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.0.0-rc/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).",
"default": true,
"type": "boolean"
},
@@ -399,13 +397,6 @@
"default": false,
"type": "boolean"
},
"windowClassname": {
"description": "The name of the window class created on Windows to create the window. **Windows only**.",
"type": [
"string",
"null"
]
},
"theme": {
"description": "The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.",
"anyOf": [
@@ -426,17 +417,6 @@
}
]
},
"trafficLightPosition": {
"description": "The position of the window controls on macOS.\n\n Requires titleBarStyle: Overlay and decorations: true.",
"anyOf": [
{
"$ref": "#/definitions/LogicalPosition"
},
{
"type": "null"
}
]
},
"hiddenTitle": {
"description": "If `true`, sets the window title to be hidden on macOS.",
"default": false,
@@ -506,45 +486,6 @@
"description": "Whether browser extensions can be installed for the webview process\n\n ## Platform-specific:\n\n - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\n - **MacOS / Linux / iOS / Android** - Unsupported.",
"default": false,
"type": "boolean"
},
"useHttpsScheme": {
"description": "Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.\n\n ## Note\n\n Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\n\n ## Warning\n\n Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.",
"default": false,
"type": "boolean"
},
"devtools": {
"description": "Enable web inspector which is usually called browser devtools. Enabled by default.\n\n This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.\n\n ## Platform-specific\n\n - macOS: This will call private functions on **macOS**.\n - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.\n - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.",
"type": [
"boolean",
"null"
]
},
"backgroundColor": {
"description": "Set the window and webview background color.\n\n ## Platform-specific:\n\n - **Windows**: alpha channel is ignored for the window layer.\n - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.\n - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored for the webview layer.",
"anyOf": [
{
"$ref": "#/definitions/Color"
},
{
"type": "null"
}
]
},
"backgroundThrottling": {
"description": "Change the default background throttling behaviour.\n\n By default, browsers use a suspend policy that will throttle timers and even unload\n the whole tab (view) to free resources after roughly 5 minutes when a view became\n minimized or hidden. This will pause all tasks until the documents visibility state\n changes back from hidden to visible by bringing the view back to the foreground.\n\n ## Platform-specific\n\n - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\n - **iOS**: Supported since version 17.0+.\n - **macOS**: Supported since version 14.0+.\n\n see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578",
"anyOf": [
{
"$ref": "#/definitions/BackgroundThrottlingPolicy"
},
{
"type": "null"
}
]
},
"javascriptDisabled": {
"description": "Whether we should disable JavaScript code execution on the webview or not.",
"default": false,
"type": "boolean"
}
},
"additionalProperties": false
@@ -613,27 +554,6 @@
}
]
},
"LogicalPosition": {
"description": "Position coordinates struct.",
"type": "object",
"required": [
"x",
"y"
],
"properties": {
"x": {
"description": "X coordinate.",
"type": "number",
"format": "double"
},
"y": {
"description": "Y coordinate.",
"type": "number",
"format": "double"
}
},
"additionalProperties": false
},
"WindowEffectsConfig": {
"description": "The window effects configuration object",
"type": "object",
@@ -907,122 +827,32 @@
]
},
"Color": {
"anyOf": [
"description": "a tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255.",
"type": "array",
"items": [
{
"description": "Color hex string, for example: #fff, #ffffff, or #ffffffff.",
"type": "string",
"pattern": "^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$"
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
{
"description": "Array of RGB colors. Each value has minimum of 0 and maximum of 255.",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
{
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
{
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
],
"maxItems": 3,
"minItems": 3
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
{
"description": "Array of RGBA colors. Each value has minimum of 0 and maximum of 255.",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
{
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
{
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
{
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
],
"maxItems": 4,
"minItems": 4
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
{
"description": "Object of red, green, blue, alpha color values. Each value has minimum of 0 and maximum of 255.",
"type": "object",
"required": [
"blue",
"green",
"red"
],
"properties": {
"red": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"green": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"blue": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"alpha": {
"default": 255,
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
]
},
"BackgroundThrottlingPolicy": {
"description": "Background throttling policy.",
"oneOf": [
{
"description": "A policy where background throttling is disabled",
"type": "string",
"enum": [
"disabled"
]
},
{
"description": "A policy where a web view thats 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 thats not in a window limits processing, but does not fully suspend tasks.",
"type": "string",
"enum": [
"throttle"
]
}
]
],
"maxItems": 4,
"minItems": 4
},
"SecurityConfig": {
"description": "Security configuration.\n\n See more: <https://v2.tauri.app/reference/config/#securityconfig>",
@@ -1094,17 +924,6 @@
"items": {
"$ref": "#/definitions/CapabilityEntry"
}
},
"headers": {
"description": "The headers, which are added to every http response from tauri to the web view\n This doesn't include IPC Messages and error responses",
"anyOf": [
{
"$ref": "#/definitions/HeaderConfig"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
@@ -1285,7 +1104,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 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 ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\n It controls application windows fine grained access to the Tauri core, application, or plugin commands.\n If a 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 \"platforms\": [\"macOS\",\"windows\"]\n }\n ```",
"type": "object",
"required": [
"identifier",
@@ -1318,21 +1137,21 @@
"type": "boolean"
},
"windows": {
"description": "List of windows that are affected by this capability. Can be a glob pattern.\n\n If a window label matches any of the patterns in this list,\n the capability will be enabled on all the webviews of that window,\n regardless of the value of [`Self::webviews`].\n\n On multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`]\n for a fine grained access control.\n\n ## Example\n\n `[\"main\"]`",
"description": "List of windows that are affected by this capability. Can be a glob pattern.\n\n On multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n ## Example\n\n `[\"main\"]`",
"type": "array",
"items": {
"type": "string"
}
},
"webviews": {
"description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\n The capability will be enabled on all the webviews\n whose label matches any of the patterns in this list,\n regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n ## Example\n\n `[\"sub-webview-one\", \"sub-webview-two\"]`",
"description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\n This is only required when using on multiwebview contexts, by default\n all child webviews of a window that matches [`Self::windows`] are linked.\n\n ## Example\n\n `[\"sub-webview-one\", \"sub-webview-two\"]`",
"type": "array",
"items": {
"type": "string"
}
},
"permissions": {
"description": "List of permissions attached to this capability.\n\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\n For commands directly implemented in the application itself only `${permission-name}`\n is required.\n\n ## Example\n\n ```json\n [\n \"core:default\",\n \"shell:allow-open\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n }\n ]\n ```",
"description": "List of permissions attached to this capability.\n\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\n For commands directly implemented in the application itself only `${permission-name}`\n is required.\n\n ## Example\n\n ```json\n [\n \"core:default\",\n \"shell:allow-open\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n }\n ```",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionEntry"
@@ -1514,168 +1333,6 @@
}
]
},
"HeaderConfig": {
"description": "A struct, where the keys are some specific http header names.\n If the values to those keys are defined, then they will be send as part of a response message.\n This does not include error messages and ipc messages\n\n ## Example configuration\n ```javascript\n {\n //..\n app:{\n //..\n security: {\n headers: {\n \"Cross-Origin-Opener-Policy\": \"same-origin\",\n \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n \"Timing-Allow-Origin\": [\n \"https://developer.mozilla.org\",\n \"https://example.com\",\n ],\n \"Access-Control-Expose-Headers\": \"Tauri-Custom-Header\",\n \"Tauri-Custom-Header\": {\n \"key1\": \"'value1' 'value2'\",\n \"key2\": \"'value3'\"\n }\n },\n csp: \"default-src 'self'; connect-src ipc: http://ipc.localhost\",\n }\n //..\n }\n //..\n }\n ```\n In this example `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` are set to allow for the use of [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).\n The result is, that those headers are then set on every response sent via the `get_response` function in crates/tauri/src/protocol/tauri.rs.\n The Content-Security-Policy header is defined separately, because it is also handled separately.\n\n For the helloworld example, this config translates into those response headers:\n ```http\n access-control-allow-origin: http://tauri.localhost\n access-control-expose-headers: Tauri-Custom-Header\n content-security-policy: default-src 'self'; connect-src ipc: http://ipc.localhost; script-src 'self' 'sha256-Wjjrs6qinmnr+tOry8x8PPwI77eGpUFR3EEGZktjJNs='\n content-type: text/html\n cross-origin-embedder-policy: require-corp\n cross-origin-opener-policy: same-origin\n tauri-custom-header: key1 'value1' 'value2'; key2 'value3'\n timing-allow-origin: https://developer.mozilla.org, https://example.com\n ```\n Since the resulting header values are always 'string-like'. So depending on the what data type the HeaderSource is, they need to be converted.\n - `String`(JS/Rust): stay the same for the resulting header value\n - `Array`(JS)/`Vec\\<String\\>`(Rust): Item are joined by \", \" for the resulting header value\n - `Object`(JS)/ `Hashmap\\<String,String\\>`(Rust): Items are composed from: key + space + value. Item are then joined by \"; \" for the resulting header value",
"type": "object",
"properties": {
"Access-Control-Allow-Credentials": {
"description": "The Access-Control-Allow-Credentials response header tells browsers whether the\n server allows cross-origin HTTP requests to include credentials.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Access-Control-Allow-Headers": {
"description": "The Access-Control-Allow-Headers response header is used in response\n to a preflight request which includes the Access-Control-Request-Headers\n to indicate which HTTP headers can be used during the actual request.\n\n This header is required if the request has an Access-Control-Request-Headers header.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Access-Control-Allow-Methods": {
"description": "The Access-Control-Allow-Methods response header specifies one or more methods\n allowed when accessing a resource in response to a preflight request.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Access-Control-Expose-Headers": {
"description": "The Access-Control-Expose-Headers response header allows a server to indicate\n which response headers should be made available to scripts running in the browser,\n in response to a cross-origin request.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Access-Control-Max-Age": {
"description": "The Access-Control-Max-Age response header indicates how long the results of a\n preflight request (that is the information contained in the\n Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can\n be cached.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Cross-Origin-Embedder-Policy": {
"description": "The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding\n cross-origin resources into the document.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Cross-Origin-Opener-Policy": {
"description": "The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a\n top-level document does not share a browsing context group with cross-origin documents.\n COOP will process-isolate your document and potential attackers can't access your global\n object if they were to open it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Cross-Origin-Resource-Policy": {
"description": "The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the\n browser blocks no-cors cross-origin/cross-site requests to the given resource.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Permissions-Policy": {
"description": "The HTTP Permissions-Policy header provides a mechanism to allow and deny the\n use of browser features in a document or within any \\<iframe\\> elements in the document.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Timing-Allow-Origin": {
"description": "The Timing-Allow-Origin response header specifies origins that are allowed to see values\n of attributes retrieved via features of the Resource Timing API, which would otherwise be\n reported as zero due to cross-origin restrictions.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"X-Content-Type-Options": {
"description": "The X-Content-Type-Options response HTTP header is a marker used by the server to indicate\n that the MIME types advertised in the Content-Type headers should be followed and not be\n changed. The header allows you to avoid MIME type sniffing by saying that the MIME types\n are deliberately configured.\n\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options>",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Tauri-Custom-Header": {
"description": "A custom header field Tauri-Custom-Header, don't use it.\n Remember to set Access-Control-Expose-Headers accordingly\n\n **NOT INTENDED FOR PRODUCTION USE**",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
"HeaderSource": {
"description": "definition of a header source\n\n The header value to a header name",
"anyOf": [
{
"description": "string version of the header Value",
"type": "string"
},
{
"description": "list version of the header value. Item are joined by \",\" for the real header value",
"type": "array",
"items": {
"type": "string"
}
},
{
"description": "(Rust struct | Json | JavaScript Object) equivalent of the header value. Items are composed from: key + space + value. Item are then joined by \";\" for the real header value",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
]
},
"TrayIconConfig": {
"description": "Configuration for application tray icon.\n\n See more: <https://v2.tauri.app/reference/config/#trayiconconfig>",
"type": "object",
@@ -1700,13 +1357,7 @@
"type": "boolean"
},
"menuOnLeftClick": {
"description": "A Boolean value that determines whether the menu should appear when the tray icon receives a left click.\n\n ## Platform-specific:\n\n - **Linux**: Unsupported.",
"default": true,
"deprecated": true,
"type": "boolean"
},
"showMenuOnLeftClick": {
"description": "A Boolean value that determines whether the menu should appear when the tray icon receives a left click.\n\n ## Platform-specific:\n\n - **Linux**: Unsupported.",
"description": "A Boolean value that determines whether the menu should appear when the tray icon receives a left click on macOS.",
"default": true,
"type": "boolean"
},
@@ -1739,7 +1390,7 @@
]
},
"devUrl": {
"description": "The URL to load in development.\n\n This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.\n Most modern JavaScript bundlers like [Vite](https://vite.dev/guide/) provides a way to start a dev server by default.\n\n If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)\n and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.",
"description": "The URL to load in development.\n\n This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.\n Most modern JavaScript bundlers like [vite](https://vitejs.dev/guide/) provides a way to start a dev server by default.\n\n If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)\n and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.",
"type": [
"string",
"null"
@@ -1799,11 +1450,6 @@
"items": {
"type": "string"
}
},
"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 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"
}
},
"additionalProperties": false
@@ -2005,7 +1651,7 @@
]
},
"useLocalToolsDir": {
"description": "Whether to use the project's `target` directory, for caching build tools (e.g., Wix and NSIS) when building this application. Defaults to `false`.\n\n If true, tools will be cached in `target/.tauri/`.\n If false, tools will be cached in the current user's platform-specific cache directory.\n\n An example where it can be appropriate to set this to `true` is when building this application as a Windows System user (e.g., AWS EC2 workloads),\n because the Window system's app data directory is restricted.",
"description": "Whether to use the project's `target` directory, for caching build tools (e.g., Wix and NSIS) when building this application. Defaults to `false`.\n\n If true, tools will be cached in `target\\.tauri-tools`.\n If false, tools will be cached in the current user's platform-specific cache directory.\n\n An example where it can be appropriate to set this to `true` is when building this application as a Windows System user (e.g., AWS EC2 workloads),\n because the Window system's app data directory is restricted.",
"default": false,
"type": "boolean"
},
@@ -2524,13 +2170,6 @@
"description": "Configuration for the MSI bundle using WiX.\n\n See more: <https://v2.tauri.app/reference/config/#wixconfig>",
"type": "object",
"properties": {
"version": {
"description": "MSI installer version in the format `major.minor.patch.build` (build is optional).\n\n Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.\n\n The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n The third and foruth fields have a maximum value of 65,535.\n\n See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.",
"type": [
"string",
"null"
]
},
"upgradeCode": {
"description": "A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\n otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n\n By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.\n You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.\n\n It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\n whenever you want to change your product name.",
"type": [
@@ -2616,7 +2255,7 @@
]
},
"dialogImagePath": {
"description": "Path to a bitmap file to use on the installation user interface dialogs.\n It is used on the welcome and completion dialogs.\n\n The required dimensions are 493px × 312px.",
"description": "Path to a bitmap file to use on the installation user interface dialogs.\n It is used on the welcome and completion dialogs.\n The required dimensions are 493px × 312px.",
"type": [
"string",
"null"
@@ -2928,16 +2567,6 @@
"type": "string"
}
},
"recommends": {
"description": "The list of deb dependencies your application recommends.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"provides": {
"description": "The list of dependencies the package provides.",
"type": [
@@ -3049,16 +2678,6 @@
"type": "string"
}
},
"recommends": {
"description": "The list of RPM dependencies your application recommends.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"provides": {
"description": "The list of RPM dependencies your application provides.",
"type": [
@@ -3143,133 +2762,10 @@
"string",
"null"
]
},
"compression": {
"description": "Compression algorithm and level. Defaults to `Gzip` with level 6.",
"anyOf": [
{
"$ref": "#/definitions/RpmCompression"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
"RpmCompression": {
"description": "Compression algorithms used when bundling RPM packages.",
"oneOf": [
{
"description": "Gzip compression",
"type": "object",
"required": [
"level",
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"gzip"
]
},
"level": {
"description": "Gzip compression level",
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
},
{
"description": "Zstd compression",
"type": "object",
"required": [
"level",
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"zstd"
]
},
"level": {
"description": "Zstd compression level",
"type": "integer",
"format": "int32"
}
},
"additionalProperties": false
},
{
"description": "Xz compression",
"type": "object",
"required": [
"level",
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"xz"
]
},
"level": {
"description": "Xz compression level",
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
},
{
"description": "Bzip2 compression",
"type": "object",
"required": [
"level",
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"bzip2"
]
},
"level": {
"description": "Bzip2 compression level",
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
},
{
"description": "Disable compression",
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"none"
]
}
},
"additionalProperties": false
}
]
},
"MacConfig": {
"description": "Configuration for the macOS bundles.\n\n See more: <https://v2.tauri.app/reference/config/#macconfig>",
"type": "object",
@@ -3502,7 +2998,7 @@
"additionalProperties": false
},
"AndroidConfig": {
"description": "General configuration for the Android target.",
"description": "General configuration for the iOS target.",
"type": "object",
"properties": {
"minSdkVersion": {

View File

@@ -1,9 +1,9 @@
{
"cli.js": {
"version": "2.4.1",
"version": "2.0.0",
"node": ">= 10.0.0"
},
"tauri": "2.4.1",
"tauri-build": "2.1.1",
"tauri-plugin": "2.1.1"
"tauri": "2.0.0",
"tauri-build": "2.0.0",
"tauri-plugin": "2.0.0"
}

View File

@@ -465,15 +465,6 @@
"description": "Whether page zooming by hotkeys is enabled\n\n ## Platform-specific:\n\n - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.\n - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,\n 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission\n\n - **Android / iOS**: Unsupported.",
"default": false,
"type": "boolean"
},
"backgroundThrottling": {
"description": "Change the default background throttling behaviour.\n\n By default, browsers use a suspend policy that will throttle timers and even unload\n the whole tab (view) to free resources after roughly 5 minutes when a view became\n minimized or hidden. This will pause all tasks until the documents visibility state\n changes back from hidden to visible by bringing the view back to the foreground.\n\n ## Platform-specific\n\n - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\n - **iOS**: Supported since version 17.0+.\n - **macOS**: Supported since version 14.0+.\n\n see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578",
"type": "string",
"enum": [
"disabled",
"throttle",
"suspend"
]
}
},
"additionalProperties": false
@@ -1378,7 +1369,7 @@
]
},
"devUrl": {
"description": "The URL to load in development.\n\n This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.\n Most modern JavaScript bundlers like [Vite](https://vite.dev/guide/) provides a way to start a dev server by default.\n\n If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)\n and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.",
"description": "The URL to load in development.\n\n This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.\n Most modern JavaScript bundlers like [vite](https://vitejs.dev/guide/) provides a way to start a dev server by default.\n\n If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)\n and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.",
"type": [
"string",
"null"
@@ -3004,4 +2995,4 @@
"additionalProperties": true
}
}
}
}

View File

@@ -166,15 +166,16 @@ pub fn command(options: Options) -> Result<()> {
let capabilities = if let Some((expected_platforms, target_name)) = expected_capability_config {
let mut capabilities = capabilities_iter
.filter(|(capability, _path)| {
capability.platforms().is_some_and(|platforms| {
// all platforms must be in the expected platforms list
platforms
.iter()
.all(|p| expected_platforms.contains(&p.to_string()))
.filter(|(capability, _path)| {
capability.platforms().map_or(
false, /* allows any target, so we should skip it since we're adding a target-specific plugin */
|platforms| {
// all platforms must be in the expected platforms list
platforms.iter().all(|p| expected_platforms.contains(&p.to_string()))
},
)
})
})
.collect::<Vec<_>>();
.collect::<Vec<_>>();
if capabilities.is_empty() {
let identifier = format!("{target_name}-capability");
@@ -186,8 +187,7 @@ pub fn command(options: Options) -> Result<()> {
capabilities.push((
TomlOrJson::Json(serde_json::json!({
"identifier": identifier,
"platforms": expected_platforms,
"windows": ["main"]
"platforms": expected_platforms
})),
capability_path,
));

View File

@@ -23,7 +23,8 @@ pub struct Options {
pub fn command(options: Options) -> Result<()> {
crate::helpers::app_paths::resolve();
let acl_manifests_path = tauri_dir()
let tauri_dir = tauri_dir();
let acl_manifests_path = tauri_dir
.join("gen")
.join("schemas")
.join("acl-manifests.json");

View File

@@ -9,7 +9,7 @@ use crate::Result;
pub mod add;
mod ls;
mod new;
pub mod rm;
mod rm;
#[derive(Debug, Parser)]
#[clap(about = "Manage or create permissions for your app or plugin")]

View File

@@ -46,15 +46,13 @@ fn rm_permission_files(identifier: &str, dir: &Path) -> Result<()> {
permission_file.default = None;
} else {
let set_len = permission_file.set.len();
permission_file
.set
.retain(|s| !identifier_match(identifier, &s.identifier));
permission_file.set.retain(|s| s.identifier != identifier);
updated = permission_file.set.len() != set_len;
let permission_len = permission_file.permission.len();
permission_file
.permission
.retain(|s| !identifier_match(identifier, &s.identifier));
.retain(|s| s.identifier != identifier);
updated = updated || permission_file.permission.len() != permission_len;
}
@@ -86,17 +84,7 @@ fn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> {
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();
permissions.retain(|p| match p {
toml_edit::Value::String(s) => !identifier_match(identifier, s.value()),
toml_edit::Value::InlineTable(o) => {
if let Some(toml_edit::Value::String(permission_name)) = o.get("identifier") {
return !identifier_match(identifier, permission_name.value());
}
true
}
_ => false,
});
permissions.retain(|p| p.as_str().map(|p| p != identifier).unwrap_or(false));
if prev_len != permissions.len() {
std::fs::write(&path, value.to_string())?;
log::info!(action = "Removed"; "permission from capability at {}", dunce::simplified(&path).display());
@@ -109,17 +97,7 @@ fn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> {
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();
permissions.retain(|p| match p {
serde_json::Value::String(s) => !identifier_match(identifier, s),
serde_json::Value::Object(o) => {
if let Some(serde_json::Value::String(permission_name)) = o.get("identifier") {
return !identifier_match(identifier, permission_name);
}
true
}
_ => false,
});
permissions.retain(|p| p.as_str().map(|p| p != identifier).unwrap_or(false));
if prev_len != permissions.len() {
std::fs::write(&path, serde_json::to_vec_pretty(&value)?)?;
log::info!(action = "Removed"; "permission from capability at {}", dunce::simplified(&path).display());
@@ -135,20 +113,11 @@ fn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> {
Ok(())
}
fn identifier_match(identifier: &str, permission: &str) -> bool {
match identifier.split_once(':') {
Some((plugin_name, "*")) => permission.contains(plugin_name),
_ => permission == identifier,
}
}
#[derive(Debug, Parser)]
#[clap(about = "Remove a permission file, and its reference from any capability")]
pub struct Options {
/// Permission to remove.
///
/// To remove all permissions for a given plugin, provide `<plugin-name>:*`
pub identifier: String,
identifier: String,
}
pub fn command(options: Options) -> Result<()> {

View File

@@ -9,7 +9,7 @@ use regex::Regex;
use crate::{
acl,
helpers::{
app_paths::{resolve_frontend_dir, tauri_dir},
app_paths::{resolve_app_dir, tauri_dir},
cargo,
npm::PackageManager,
},
@@ -49,27 +49,14 @@ pub fn run(options: Options) -> Result<()> {
.map(|(p, v)| (p, Some(v)))
.unwrap_or((&options.plugin, None));
let mut plugins = crate::helpers::plugins::known_plugins();
let (metadata, is_known) = plugins
.remove(plugin)
.map(|metadata| (metadata, true))
.unwrap_or_default();
let plugin_snake_case = plugin.replace('-', "_");
let crate_name = format!("tauri-plugin-{plugin}");
let npm_name = if is_known {
format!("@tauri-apps/plugin-{plugin}")
} else {
format!("tauri-plugin-{plugin}-api")
};
let npm_name = format!("@tauri-apps/plugin-{plugin}");
if !is_known && (options.tag.is_some() || options.rev.is_some() || options.branch.is_some()) {
anyhow::bail!(
"Git options --tag, --rev and --branch can only be used with official Tauri plugins"
);
}
let mut plugins = crate::helpers::plugins::known_plugins();
let metadata = plugins.remove(plugin).unwrap_or_default();
let frontend_dir = resolve_frontend_dir();
let app_dir = resolve_app_dir();
let tauri_dir = tauri_dir();
let target_str = metadata
@@ -94,7 +81,10 @@ pub fn run(options: Options) -> Result<()> {
})?;
if !metadata.rust_only {
if let Some(manager) = frontend_dir.map(PackageManager::from_project) {
if let Some(manager) = app_dir
.map(PackageManager::from_project)
.and_then(|managers| managers.into_iter().next())
{
let npm_version_req = version
.map(ToString::to_string)
.or(metadata.version_req.as_ref().map(|v| match manager {
@@ -103,7 +93,9 @@ pub fn run(options: Options) -> Result<()> {
}));
let npm_spec = match (npm_version_req, options.tag, options.rev, options.branch) {
(Some(version_req), _, _, _) => format!("{npm_name}@{version_req}"),
(Some(version), _, _, _) => {
format!("{npm_name}@{version}")
}
(None, Some(tag), None, None) => {
format!("tauri-apps/tauri-plugin-{plugin}#{tag}")
}

View File

@@ -40,20 +40,16 @@ pub struct Options {
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
/// Space or comma separated list of bundles to package.
///
/// Note that the `updater` bundle is not automatically added so you must specify it if the updater is enabled.
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
pub bundles: Option<Vec<BundleFormat>>,
/// Skip the bundling step even if `bundle > active` is `true` in tauri config.
#[clap(long)]
pub no_bundle: bool,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
/// JSON string or path to JSON file to merge with tauri.conf.json
#[clap(short, long)]
pub config: Vec<ConfigValue>,
pub config: Option<ConfigValue>,
/// Command line arguments passed to the runner. Use `--` to explicitly mark the start of the arguments.
pub args: Vec<String>,
/// Skip prompting for values
@@ -72,10 +68,7 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
.map(Target::from_triple)
.unwrap_or_else(Target::current);
let config = get_config(
target,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
)?;
let config = get_config(target, options.config.as_ref().map(|c| &c.0))?;
let mut interface = AppInterface::new(
config.lock().unwrap().as_ref().unwrap(),
@@ -94,7 +87,7 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
let bin_path = interface.build(interface_options)?;
log::info!(action ="Built"; "application at: {}", tauri_utils::display_path(bin_path));
log::info!(action ="Built"; "application at: {}", tauri_utils::display_path(&bin_path));
let app_settings = interface.app_settings();
@@ -182,7 +175,7 @@ pub fn setup(
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 out_folders.len() == 1 { "folder" }else { "folders" }
)
);
}

View File

@@ -9,6 +9,7 @@ use std::{
};
use anyhow::Context;
use base64::Engine;
use clap::{builder::PossibleValue, ArgAction, Parser, ValueEnum};
use tauri_bundler::PackageType;
use tauri_utils::platform::Target;
@@ -43,8 +44,7 @@ impl ValueEnum for BundleFormat {
}
fn to_possible_value(&self) -> Option<PossibleValue> {
let hide = self.0 == PackageType::Updater;
Some(PossibleValue::new(self.0.short_name()).hide(hide))
Some(PossibleValue::new(self.0.short_name()))
}
}
@@ -58,17 +58,13 @@ pub struct Options {
#[clap(short, long)]
pub debug: bool,
/// Space or comma separated list of bundles to package.
///
/// Note that the `updater` bundle is not automatically added so you must specify it if the updater is enabled.
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
pub bundles: Option<Vec<BundleFormat>>,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
/// JSON string or path to JSON file to merge with tauri.conf.json
#[clap(short, long)]
pub config: Vec<ConfigValue>,
pub config: Option<ConfigValue>,
/// Space or comma separated list of features, should be the same features passed to `tauri build` if any.
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
@@ -108,10 +104,7 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> {
.map(Target::from_triple)
.unwrap_or_else(Target::current);
let config = get_config(
target,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
)?;
let config = get_config(target, options.config.as_ref().map(|c| &c.0))?;
let interface = AppInterface::new(
config.lock().unwrap().as_ref().unwrap(),
@@ -189,6 +182,24 @@ pub fn bundle<A: AppSettings>(
_ => log::Level::Trace,
});
// set env vars used by the bundler
#[cfg(target_os = "linux")]
{
if config.bundle.linux.appimage.bundle_media_framework {
std::env::set_var("APPIMAGE_BUNDLE_GSTREAMER", "1");
}
if let Some(open) = config.plugins.0.get("shell").and_then(|v| v.get("open")) {
if open.as_bool().is_some_and(|x| x) || open.is_string() {
std::env::set_var("APPIMAGE_BUNDLE_XDG_OPEN", "1");
}
}
if settings.deep_link_protocols().is_some() {
std::env::set_var("APPIMAGE_BUNDLE_XDG_MIME", "1");
}
}
let bundles = tauri_bundler::bundle_project(&settings)
.map_err(|e| match e {
tauri_bundler::Error::BundlerError(e) => e,
@@ -216,12 +227,7 @@ fn sign_updaters(
.filter(|bundle| {
matches!(
bundle.package_type,
PackageType::Updater
| PackageType::Nsis
| PackageType::WindowsMsi
| PackageType::AppImage
| PackageType::Deb
| PackageType::Rpm
PackageType::Updater | PackageType::Nsis | PackageType::WindowsMsi | PackageType::AppImage
)
})
.collect();
@@ -251,14 +257,15 @@ fn sign_updaters(
// check if private_key points to a file...
let maybe_path = Path::new(&private_key);
let private_key = if maybe_path.exists() {
std::fs::read_to_string(maybe_path)
.with_context(|| format!("faild to read {}", maybe_path.display()))?
std::fs::read_to_string(maybe_path)?
} else {
private_key
};
let secret_key =
updater_signature::secret_key(private_key, password).context("failed to decode secret key")?;
let public_key = updater_signature::pub_key(pubkey).context("failed to decode pubkey")?;
let secret_key = updater_signature::secret_key(private_key, password)?;
let pubkey = base64::engine::general_purpose::STANDARD.decode(pubkey)?;
let pub_key_decoded = String::from_utf8_lossy(&pubkey);
let public_key = minisign::PublicKeyBox::from_string(&pub_key_decoded)?.into_public_key()?;
let mut signed_paths = Vec::new();
for bundle in update_enabled_bundles {

View File

@@ -9,7 +9,7 @@ use clap_complete::{generate, Shell};
use std::{fs::write, path::PathBuf};
const PKG_MANAGERS: &[&str] = &["cargo", "pnpm", "npm", "yarn", "bun", "deno"];
const PKG_MANAGERS: &[&str] = &["cargo", "pnpm", "npm", "yarn", "bun"];
#[derive(Debug, Clone, Parser)]
#[clap(about = "Generate Tauri CLI shell completions for Bash, Zsh, PowerShell or Fish")]
@@ -28,10 +28,6 @@ fn completions_for(shell: Shell, manager: &'static str, cmd: Command) -> Vec<u8>
Command::new(manager)
.bin_name(manager)
.subcommand(Command::new("run").subcommand(tauri))
} else if manager == "deno" {
Command::new(manager)
.bin_name(manager)
.subcommand(Command::new("task").subcommand(tauri))
} else {
Command::new(manager).bin_name(manager).subcommand(tauri)
};
@@ -45,15 +41,13 @@ fn get_completions(shell: Shell, cmd: Command) -> Result<String> {
let completions = if shell == Shell::Bash {
let mut completions =
String::from_utf8_lossy(&completions_for(shell, "cargo", cmd)).into_owned();
for &manager in PKG_MANAGERS {
for manager in PKG_MANAGERS {
completions.push_str(&format!(
"complete -F _cargo -o bashdefault -o default {} tauri\n",
if manager == "npm" {
if manager == &"npm" {
"npm run"
} else if manager == "bun" {
} else if manager == &"bun" {
"bun run"
} else if manager == "deno" {
"deno task"
} else {
manager
}

View File

@@ -4,13 +4,13 @@
use crate::{
helpers::{
app_paths::{frontend_dir, tauri_dir},
app_paths::{app_dir, tauri_dir},
command_env,
config::{
get as get_config, reload as reload_config, BeforeDevCommand, ConfigHandle, FrontendDist,
},
},
interface::{AppInterface, ExitReason, Interface},
interface::{AppInterface, DevProcess, ExitReason, Interface},
CommandExt, ConfigValue, Result,
};
@@ -59,15 +59,9 @@ pub struct Options {
/// Exit on panic
#[clap(short, long)]
pub exit_on_panic: bool,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
/// JSON string or path to JSON file to merge with tauri.conf.json
#[clap(short, long)]
pub config: Vec<ConfigValue>,
pub config: Option<ConfigValue>,
/// Run the code in release mode
#[clap(long = "release")]
pub release_mode: bool,
@@ -110,10 +104,7 @@ fn command_internal(mut options: Options) -> Result<()> {
.map(Target::from_triple)
.unwrap_or_else(Target::current);
let config = get_config(
target,
&options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),
)?;
let config = get_config(target, options.config.as_ref().map(|c| &c.0))?;
let mut interface = AppInterface::new(
config.lock().unwrap().as_ref().unwrap(),
@@ -149,7 +140,7 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
(Some(script), cwd.map(Into::into), wait)
}
};
let cwd = script_cwd.unwrap_or_else(|| frontend_dir().clone());
let cwd = script_cwd.unwrap_or_else(|| app_dir().clone());
if let Some(before_dev) = script {
log::info!(action = "Running"; "BeforeDevCommand (`{}`)", before_dev);
let mut env = command_env(true);
@@ -271,13 +262,26 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
let server_url = format!("http://{server_url}");
dev_url = Some(server_url.parse().unwrap());
options.config.push(crate::ConfigValue(serde_json::json!({
"build": {
"devUrl": server_url
if let Some(c) = &mut options.config {
if let Some(build) = c
.0
.as_object_mut()
.and_then(|root| root.get_mut("build"))
.and_then(|build| build.as_object_mut())
{
build.insert("devUrl".into(), server_url.into());
}
})));
} else {
options
.config
.replace(crate::ConfigValue(serde_json::json!({
"build": {
"devUrl": server_url
}
})));
}
reload_config(&options.config.iter().map(|c| &c.0).collect::<Vec<_>>())?;
reload_config(options.config.as_ref().map(|c| &c.0))?;
}
}
}
@@ -334,6 +338,30 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
Ok(())
}
pub fn wait_dev_process<
C: DevProcess + Send + 'static,
F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static,
>(
child: C,
on_exit: F,
) {
std::thread::spawn(move || {
let code = child
.wait()
.ok()
.and_then(|status| status.code())
.or(Some(1));
on_exit(
code,
if child.manually_killed_process() {
ExitReason::TriggeredKill
} else {
ExitReason::NormalExit
},
);
});
}
pub fn on_app_exit(code: Option<i32>, reason: ExitReason, exit_on_panic: bool, no_watch: bool) {
if no_watch
|| (!matches!(reason, ExitReason::TriggeredKill)

View File

@@ -79,7 +79,7 @@ pub fn start<P: AsRef<Path>>(dir: P, ip: IpAddr, port: Option<u16>) -> crate::Re
}
async fn handler(uri: Uri, state: State<ServerState>) -> impl IntoResponse {
// Frontend files should not contain query parameters. This seems to be how Vite handles it.
// Frontend files should not contain query parameters. This seems to be how vite handles it.
let uri = uri.path();
let uri = if uri == "/" {
@@ -162,20 +162,17 @@ fn watch<F: Fn() + Send + 'static>(dir: PathBuf, handler: F) {
thread::spawn(move || {
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = notify_debouncer_full::new_debouncer(Duration::from_secs(1), None, tx)
let mut watcher = notify_debouncer_mini::new_debouncer(Duration::from_secs(1), tx)
.expect("failed to start builtin server fs watcher");
watcher
.watcher()
.watch(&dir, notify::RecursiveMode::Recursive)
.expect("builtin server failed to watch dir");
loop {
if let Ok(Ok(event)) = rx.recv() {
if let Some(event) = event.first() {
if !event.kind.is_access() {
handler();
}
}
if rx.recv().is_ok() {
handler();
}
}
});

View File

@@ -18,12 +18,7 @@ use tauri_utils::{
};
const TAURI_GITIGNORE: &[u8] = include_bytes!("../../tauri.gitignore");
// path to the Tauri app (Rust crate) directory, usually `<project>/src-tauri/`
const ENV_TAURI_APP_PATH: &str = "TAURI_APP_PATH";
// path to the frontend app directory, usually `<project>/`
const ENV_TAURI_FRONTEND_PATH: &str = "TAURI_FRONTEND_PATH";
static FRONTEND_DIR: OnceLock<PathBuf> = OnceLock::new();
static APP_DIR: OnceLock<PathBuf> = OnceLock::new();
static TAURI_DIR: OnceLock<PathBuf> = OnceLock::new();
pub fn walk_builder(path: &Path) -> WalkBuilder {
@@ -39,7 +34,6 @@ pub fn walk_builder(path: &Path) -> WalkBuilder {
let mut builder = WalkBuilder::new(path);
builder.add_custom_ignore_filename(".taurignore");
builder.git_global(false);
builder.parents(false);
let _ = builder.add_ignore(default_gitignore);
builder
}
@@ -74,74 +68,50 @@ fn lookup<F: Fn(&PathBuf) -> bool>(dir: &Path, checker: F) -> Option<PathBuf> {
None
}
fn env_tauri_app_path() -> Option<PathBuf> {
std::env::var(ENV_TAURI_APP_PATH)
.map(PathBuf::from)
.ok()?
.canonicalize()
.ok()
.map(|p| dunce::simplified(&p).to_path_buf())
}
fn env_tauri_frontend_path() -> Option<PathBuf> {
std::env::var(ENV_TAURI_FRONTEND_PATH)
.map(PathBuf::from)
.ok()?
.canonicalize()
.ok()
.map(|p| dunce::simplified(&p).to_path_buf())
}
pub fn resolve_tauri_dir() -> Option<PathBuf> {
let src_dir = env_tauri_app_path().or_else(|| current_dir().ok())?;
let Ok(cwd) = current_dir() else {
return None;
};
for standard_tauri_path in [src_dir.clone(), src_dir.join("src-tauri")] {
if standard_tauri_path
.join(ConfigFormat::Json.into_file_name())
.exists()
|| standard_tauri_path
.join(ConfigFormat::Json5.into_file_name())
.exists()
|| standard_tauri_path
.join(ConfigFormat::Toml.into_file_name())
.exists()
{
log::debug!(
"Found Tauri project inside {} on early lookup",
standard_tauri_path.display()
);
return Some(standard_tauri_path);
}
if cwd.join(ConfigFormat::Json.into_file_name()).exists()
|| cwd.join(ConfigFormat::Json5.into_file_name()).exists()
|| cwd.join(ConfigFormat::Toml.into_file_name()).exists()
{
return Some(cwd);
}
log::debug!("resolving Tauri directory from {}", src_dir.display());
let src_tauri = cwd.join("src-tauri");
if src_tauri.join(ConfigFormat::Json.into_file_name()).exists()
|| src_tauri
.join(ConfigFormat::Json5.into_file_name())
.exists()
|| src_tauri.join(ConfigFormat::Toml.into_file_name()).exists()
{
return Some(src_tauri);
}
lookup(&src_dir, |path| {
lookup(&cwd, |path| {
folder_has_configuration_file(Target::Linux, path) || is_configuration_file(Target::Linux, path)
})
.map(|p| {
if p.is_dir() {
log::debug!("Found Tauri project directory {}", p.display());
p
} else {
log::debug!("Found Tauri project configuration file {}", p.display());
p.parent().unwrap().to_path_buf()
}
})
}
pub fn resolve() {
TAURI_DIR.set(resolve_tauri_dir().unwrap_or_else(|| {
let env_var_name = env_tauri_app_path().is_some().then(|| format!("`{ENV_TAURI_APP_PATH}`"));
panic!("Couldn't recognize the {} folder as a Tauri project. It must contain a `{}`, `{}` or `{}` file in any subfolder.",
env_var_name.as_deref().unwrap_or("current"),
TAURI_DIR.set(resolve_tauri_dir().unwrap_or_else(||
panic!("Couldn't recognize the current folder as a Tauri project. It must contain a `{}`, `{}` or `{}` file in any subfolder.",
ConfigFormat::Json.into_file_name(),
ConfigFormat::Json5.into_file_name(),
ConfigFormat::Toml.into_file_name()
)
})).expect("tauri dir already resolved");
FRONTEND_DIR
.set(resolve_frontend_dir().unwrap_or_else(|| tauri_dir().parent().unwrap().to_path_buf()))
)).expect("tauri dir already resolved");
APP_DIR
.set(resolve_app_dir().unwrap_or_else(|| tauri_dir().parent().unwrap().to_path_buf()))
.expect("app dir already resolved");
}
@@ -151,20 +121,13 @@ pub fn tauri_dir() -> &'static PathBuf {
.expect("app paths not initialized, this is a Tauri CLI bug")
}
pub fn resolve_frontend_dir() -> Option<PathBuf> {
let frontend_dir =
env_tauri_frontend_path().unwrap_or_else(|| current_dir().expect("failed to read cwd"));
if frontend_dir.join("package.json").exists() {
return Some(frontend_dir);
pub fn resolve_app_dir() -> Option<PathBuf> {
let cwd = current_dir().expect("failed to read cwd");
if cwd.join("package.json").exists() {
return Some(cwd);
}
log::debug!(
"resolving frontend directory from {}",
frontend_dir.display()
);
lookup(&frontend_dir, |path| {
lookup(&cwd, |path| {
if let Some(file_name) = path.file_name() {
file_name == OsStr::new("package.json")
} else {
@@ -174,8 +137,8 @@ pub fn resolve_frontend_dir() -> Option<PathBuf> {
.map(|p| p.parent().unwrap().to_path_buf())
}
pub fn frontend_dir() -> &'static PathBuf {
FRONTEND_DIR
pub fn app_dir() -> &'static PathBuf {
APP_DIR
.get()
.expect("app paths not initialized, this is a Tauri CLI bug")
}

View File

@@ -61,33 +61,3 @@ pub fn install_one(options: CargoInstallOptions) -> crate::Result<()> {
Ok(())
}
#[derive(Debug, Default, Clone, Copy)]
pub struct CargoUninstallOptions<'a> {
pub name: &'a str,
pub cwd: Option<&'a std::path::Path>,
pub target: Option<&'a str>,
}
pub fn uninstall_one(options: CargoUninstallOptions) -> crate::Result<()> {
let mut cargo = Command::new("cargo");
cargo.arg("remove");
cargo.arg(options.name);
if let Some(target) = options.target {
cargo.args(["--target", target]);
}
if let Some(cwd) = options.cwd {
cargo.current_dir(cwd);
}
log::info!("Uninstalling Cargo dependency \"{}\"...", options.name);
let status = cargo.status().context("failed to run `cargo remove`")?;
if !status.success() {
anyhow::bail!("Failed to remove Cargo dependency");
}
Ok(())
}

View File

@@ -97,30 +97,15 @@ impl std::fmt::Display for CrateVersion {
}
}
// Reference: https://github.com/rust-lang/crates.io/blob/98c83c8231cbcd15d6b8f06d80a00ad462f71585/src/views.rs#L274
#[derive(serde::Deserialize)]
struct CrateMetadata {
/// The "default" version of this crate.
///
/// This version will be displayed by default on the crate's page.
pub default_version: Option<String>,
}
// Reference: https://github.com/rust-lang/crates.io/blob/98c83c8231cbcd15d6b8f06d80a00ad462f71585/src/controllers/krate/metadata.rs#L44
#[derive(serde::Deserialize)]
struct CrateIoGetResponse {
/// The crate metadata.
#[serde(rename = "crate")]
krate: CrateMetadata,
}
pub fn crate_latest_version(name: &str) -> Option<String> {
// Reference: https://github.com/rust-lang/crates.io/blob/98c83c8231cbcd15d6b8f06d80a00ad462f71585/src/controllers/krate/metadata.rs#L88
let url = format!("https://crates.io/api/v1/crates/{name}?include");
let mut response = ureq::get(&url).call().ok()?;
let metadata: CrateIoGetResponse =
serde_json::from_reader(response.body_mut().as_reader()).unwrap();
metadata.krate.default_version
let url = format!("https://docs.rs/crate/{name}/");
match ureq::get(&url).call() {
Ok(response) => match (response.status(), response.header("location")) {
(302, Some(location)) => Some(location.replace(&url, "")),
_ => None,
},
Err(_) => None,
}
}
pub fn crate_version(

View File

@@ -2,11 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use itertools::Itertools;
use json_patch::merge;
use serde_json::Value as JsonValue;
use tauri_utils::acl::REMOVE_UNUSED_COMMANDS_ENV_VAR;
pub use tauri_utils::{config::*, platform::Target};
use std::{
@@ -64,7 +62,6 @@ pub type ConfigHandle = Arc<Mutex<Option<ConfigMetadata>>>;
pub fn wix_settings(config: WixConfig) -> tauri_bundler::WixSettings {
tauri_bundler::WixSettings {
version: config.version,
upgrade_code: config.upgrade_code,
language: tauri_bundler::WixLanguage(match config.language {
WixLanguage::One(lang) => vec![(lang, Default::default())],
@@ -94,7 +91,7 @@ pub fn wix_settings(config: WixConfig) -> tauri_bundler::WixSettings {
enable_elevated_update_task: config.enable_elevated_update_task,
banner_path: config.banner_path,
dialog_image_path: config.dialog_image_path,
fips_compliant: var_os("TAURI_BUNDLER_WIX_FIPS_COMPLIANT").is_some_and(|v| v == "true"),
fips_compliant: var_os("TAURI_BUNDLER_WIX_FIPS_COMPLIANT").map_or(false, |v| v == "true"),
}
}
@@ -139,7 +136,7 @@ fn config_handle() -> &'static ConfigHandle {
/// Gets the static parsed config from `tauri.conf.json`.
fn get_internal(
merge_configs: &[&serde_json::Value],
merge_config: Option<&serde_json::Value>,
reload: bool,
target: Target,
) -> crate::Result<ConfigHandle> {
@@ -154,7 +151,7 @@ fn get_internal(
let mut extensions = HashMap::new();
if let Some((platform_config, config_path)) =
tauri_utils::config::parse::read_platform(target, tauri_dir)?
tauri_utils::config::parse::read_platform(target, tauri_dir.to_path_buf())?
{
merge(&mut config, &platform_config);
extensions.insert(
@@ -163,27 +160,22 @@ fn get_internal(
);
}
if !merge_configs.is_empty() {
let mut merge_config = serde_json::Value::Object(Default::default());
for conf in merge_configs {
merge_patches(&mut merge_config, conf);
}
if let Some(merge_config) = merge_config {
let merge_config_str = serde_json::to_string(&merge_config).unwrap();
set_var("TAURI_CONFIG", merge_config_str);
merge(&mut config, &merge_config);
extensions.insert(MERGE_CONFIG_EXTENSION_NAME.into(), merge_config);
}
merge(&mut config, merge_config);
extensions.insert(MERGE_CONFIG_EXTENSION_NAME.into(), merge_config.clone());
};
if config_path.extension() == Some(OsStr::new("json"))
|| config_path.extension() == Some(OsStr::new("json5"))
{
let schema: JsonValue = serde_json::from_str(include_str!("../../config.schema.json"))?;
let validator = jsonschema::validator_for(&schema).expect("Invalid schema");
let mut errors = validator.iter_errors(&config).peekable();
if errors.peek().is_some() {
let schema = jsonschema::JSONSchema::compile(&schema).unwrap();
let result = schema.validate(&config);
if let Err(errors) = result {
for error in errors {
let path = error.instance_path.into_iter().join(" > ");
let path = error.instance_path.clone().into_vec().join(" > ");
if path.is_empty() {
log::error!("`{}` error: {}", config_file_name, error);
} else {
@@ -214,10 +206,6 @@ fn get_internal(
);
}
if config.build.remove_unused_commands {
std::env::set_var(REMOVE_UNUSED_COMMANDS_ENV_VAR, tauri_dir);
}
*config_handle().lock().unwrap() = Some(ConfigMetadata {
target,
inner: config,
@@ -227,42 +215,35 @@ fn get_internal(
Ok(config_handle().clone())
}
pub fn get(target: Target, merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
get_internal(merge_configs, false, target)
pub fn get(
target: Target,
merge_config: Option<&serde_json::Value>,
) -> crate::Result<ConfigHandle> {
get_internal(merge_config, false, target)
}
pub fn reload(merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
pub fn reload(merge_config: Option<&serde_json::Value>) -> crate::Result<ConfigHandle> {
let target = config_handle()
.lock()
.unwrap()
.as_ref()
.map(|conf| conf.target);
if let Some(target) = target {
get_internal(merge_configs, true, target)
get_internal(merge_config, true, target)
} else {
Err(anyhow::anyhow!("config not loaded"))
}
}
/// merges the loaded config with the given value
pub fn merge_with(merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigHandle> {
pub fn merge_with(merge_config: &serde_json::Value) -> crate::Result<ConfigHandle> {
let handle = config_handle();
if merge_configs.is_empty() {
return Ok(handle.clone());
}
if let Some(config_metadata) = &mut *handle.lock().unwrap() {
let mut merge_config = serde_json::Value::Object(Default::default());
for conf in merge_configs {
merge_patches(&mut merge_config, conf);
}
let merge_config_str = serde_json::to_string(&merge_config).unwrap();
let merge_config_str = serde_json::to_string(merge_config).unwrap();
set_var("TAURI_CONFIG", merge_config_str);
let mut value = serde_json::to_value(config_metadata.inner.clone())?;
merge(&mut value, &merge_config);
merge(&mut value, merge_config);
config_metadata.inner = serde_json::from_value(value)?;
Ok(handle.clone())
@@ -270,87 +251,3 @@ pub fn merge_with(merge_configs: &[&serde_json::Value]) -> crate::Result<ConfigH
Err(anyhow::anyhow!("config not loaded"))
}
}
/// Same as [`json_patch::merge`] but doesn't delete the key when the patch's value is `null`
fn merge_patches(doc: &mut serde_json::Value, patch: &serde_json::Value) {
use serde_json::{Map, Value};
if !patch.is_object() {
*doc = patch.clone();
return;
}
if !doc.is_object() {
*doc = Value::Object(Map::new());
}
let map = doc.as_object_mut().unwrap();
for (key, value) in patch.as_object().unwrap() {
merge_patches(map.entry(key.as_str()).or_insert(Value::Null), value);
}
}
#[cfg(test)]
mod tests {
#[test]
fn merge_patches() {
let mut json = serde_json::Value::Object(Default::default());
super::merge_patches(
&mut json,
&serde_json::json!({
"app": {
"withGlobalTauri": true,
"windows": []
},
"plugins": {
"test": "tauri"
},
"build": {
"devUrl": "http://localhost:8080"
}
}),
);
super::merge_patches(
&mut json,
&serde_json::json!({
"app": { "withGlobalTauri": null }
}),
);
super::merge_patches(
&mut json,
&serde_json::json!({
"app": { "windows": null }
}),
);
super::merge_patches(
&mut json,
&serde_json::json!({
"plugins": { "updater": {
"endpoints": ["https://tauri.app"]
} }
}),
);
assert_eq!(
json,
serde_json::json!({
"app": {
"withGlobalTauri": null,
"windows": null
},
"plugins": {
"test": "tauri",
"updater": {
"endpoints": ["https://tauri.app"]
}
},
"build": {
"devUrl": "http://localhost:8080"
}
})
)
}
}

View File

@@ -267,7 +267,7 @@ mod sys {
}
pub(super) fn error_contended(err: &Error) -> bool {
err.raw_os_error() == Some(libc::EWOULDBLOCK)
err.raw_os_error().map_or(false, |x| x == libc::EWOULDBLOCK)
}
pub(super) fn error_unsupported(err: &Error) -> bool {
@@ -327,11 +327,15 @@ mod sys {
}
pub(super) fn error_contended(err: &Error) -> bool {
err.raw_os_error() == Some(ERROR_LOCK_VIOLATION as i32)
err
.raw_os_error()
.map_or(false, |x| x == ERROR_LOCK_VIOLATION as i32)
}
pub(super) fn error_unsupported(err: &Error) -> bool {
err.raw_os_error() == Some(ERROR_INVALID_FUNCTION as i32)
err
.raw_os_error()
.map_or(false, |x| x == ERROR_INVALID_FUNCTION as i32)
}
pub(super) fn unlock(file: &File) -> Result<()> {

View File

@@ -31,7 +31,7 @@ use crate::{
CommandExt,
};
use self::app_paths::frontend_dir;
use self::app_paths::app_dir;
pub fn command_env(debug: bool) -> HashMap<&'static str, String> {
let mut map = HashMap::new();
@@ -80,7 +80,7 @@ pub fn run_hook(
HookCommand::Script(s) => (Some(s), None),
HookCommand::ScriptWithOptions { script, cwd } => (Some(script), cwd.map(Into::into)),
};
let cwd = script_cwd.unwrap_or_else(|| frontend_dir().clone());
let cwd = script_cwd.unwrap_or_else(|| app_dir().clone());
if let Some(script) = script {
log::info!(action = "Running"; "{} `{}`", name, script);

View File

@@ -7,22 +7,6 @@ use anyhow::Context;
use crate::helpers::cross_command;
use std::{fmt::Display, path::Path, process::Command};
pub fn manager_version(package_manager: &str) -> Option<String> {
cross_command(package_manager)
.arg("-v")
.output()
.map(|o| {
if o.status.success() {
let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();
Some(v.split('\n').next().unwrap().to_string())
} else {
None
}
})
.ok()
.unwrap_or_default()
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum PackageManager {
Npm,
@@ -30,7 +14,6 @@ pub enum PackageManager {
Yarn,
YarnBerry,
Bun,
Deno,
}
impl Display for PackageManager {
@@ -44,50 +27,53 @@ impl Display for PackageManager {
PackageManager::Yarn => "yarn",
PackageManager::YarnBerry => "yarn berry",
PackageManager::Bun => "bun",
PackageManager::Deno => "deno",
}
)
}
}
impl PackageManager {
/// Detects package manager from the given directory, falls back to [`PackageManager::Npm`].
pub fn from_project<P: AsRef<Path>>(path: P) -> Self {
Self::all_from_project(path)
.first()
.copied()
.unwrap_or(Self::Npm)
}
/// Detects all possible package managers from the given directory.
pub fn all_from_project<P: AsRef<Path>>(path: P) -> Vec<Self> {
let mut found = Vec::new();
pub fn from_project<P: AsRef<Path>>(path: P) -> Vec<Self> {
let mut use_npm = false;
let mut use_pnpm = false;
let mut use_yarn = false;
let mut use_bun = false;
if let Ok(entries) = std::fs::read_dir(path) {
for entry in entries.flatten() {
let path = entry.path();
let name = path.file_name().unwrap().to_string_lossy();
match name.as_ref() {
"package-lock.json" => found.push(PackageManager::Npm),
"pnpm-lock.yaml" => found.push(PackageManager::Pnpm),
"yarn.lock" => {
let yarn = if manager_version("yarn")
.map(|v| v.chars().next().map(|c| c > '1').unwrap_or_default())
.unwrap_or(false)
{
PackageManager::YarnBerry
} else {
PackageManager::Yarn
};
found.push(yarn);
}
"bun.lock" | "bun.lockb" => found.push(PackageManager::Bun),
"deno.lock" => found.push(PackageManager::Deno),
_ => (),
if name.as_ref() == "package-lock.json" {
use_npm = true;
} else if name.as_ref() == "pnpm-lock.yaml" {
use_pnpm = true;
} else if name.as_ref() == "yarn.lock" {
use_yarn = true;
} else if name.as_ref() == "bun.lockb" {
use_bun = true;
}
}
}
if !use_npm && !use_pnpm && !use_yarn && !use_bun {
return Vec::new();
}
let mut found = Vec::new();
if use_npm {
found.push(PackageManager::Npm);
}
if use_pnpm {
found.push(PackageManager::Pnpm);
}
if use_yarn {
found.push(PackageManager::Yarn);
}
if use_bun {
found.push(PackageManager::Bun);
}
found
}
@@ -98,15 +84,10 @@ impl PackageManager {
PackageManager::Npm => cross_command("npm"),
PackageManager::Pnpm => cross_command("pnpm"),
PackageManager::Bun => cross_command("bun"),
PackageManager::Deno => cross_command("deno"),
}
}
pub fn install<P: AsRef<Path>>(
&self,
dependencies: &[String],
frontend_dir: P,
) -> crate::Result<()> {
pub fn install<P: AsRef<Path>>(&self, dependencies: &[String], app_dir: P) -> crate::Result<()> {
let dependencies_str = if dependencies.len() > 1 {
"dependencies"
} else {
@@ -121,16 +102,11 @@ impl PackageManager {
.join(", ")
);
let mut command = self.cross_command();
command.arg("add");
match self {
PackageManager::Deno => command.args(dependencies.iter().map(|d| format!("npm:{d}"))),
_ => command.args(dependencies),
};
let status = command
.current_dir(frontend_dir)
let status = self
.cross_command()
.arg("add")
.args(dependencies)
.current_dir(app_dir)
.status()
.with_context(|| format!("failed to run {self}"))?;
@@ -141,11 +117,7 @@ impl PackageManager {
Ok(())
}
pub fn remove<P: AsRef<Path>>(
&self,
dependencies: &[String],
frontend_dir: P,
) -> crate::Result<()> {
pub fn remove<P: AsRef<Path>>(&self, dependencies: &[String], app_dir: P) -> crate::Result<()> {
let dependencies_str = if dependencies.len() > 1 {
"dependencies"
} else {
@@ -168,7 +140,7 @@ impl PackageManager {
"remove"
})
.args(dependencies)
.current_dir(frontend_dir)
.current_dir(app_dir)
.status()
.with_context(|| format!("failed to run {self}"))?;
@@ -182,7 +154,7 @@ impl PackageManager {
pub fn current_package_version<P: AsRef<Path>>(
&self,
name: &str,
frontend_dir: P,
app_dir: P,
) -> crate::Result<Option<String>> {
let (output, regex) = match self {
PackageManager::Yarn => (
@@ -190,7 +162,7 @@ impl PackageManager {
.args(["list", "--pattern"])
.arg(name)
.args(["--depth", "0"])
.current_dir(frontend_dir)
.current_dir(app_dir)
.output()?,
None,
),
@@ -199,26 +171,35 @@ impl PackageManager {
.arg("info")
.arg(name)
.arg("--json")
.current_dir(frontend_dir)
.current_dir(app_dir)
.output()?,
Some(regex::Regex::new("\"Version\":\"([\\da-zA-Z\\-\\.]+)\"").unwrap()),
),
PackageManager::Npm => (
cross_command("npm")
.arg("list")
.arg(name)
.args(["version", "--depth", "0"])
.current_dir(app_dir)
.output()?,
None,
),
PackageManager::Pnpm => (
cross_command("pnpm")
.arg("list")
.arg(name)
.args(["--parseable", "--depth", "0"])
.current_dir(frontend_dir)
.current_dir(app_dir)
.output()?,
None,
),
// Bun and Deno don't support `list` command
PackageManager::Npm | PackageManager::Bun | PackageManager::Deno => (
// Bun doesn't support `list` command
PackageManager::Bun => (
cross_command("npm")
.arg("list")
.arg(name)
.args(["version", "--depth", "0"])
.current_dir(frontend_dir)
.current_dir(app_dir)
.output()?,
None,
),

View File

@@ -52,7 +52,7 @@ pub fn parse<P: AsRef<Path>>(path: P) -> crate::Result<Pbxproj> {
State::XCBuildConfigurationObject { id } => {
if line.contains("buildSettings") {
state = State::XCBuildConfigurationObjectBuildSettings { id: id.clone() };
} else if split_at_identation(line).is_some_and(|(_ident, token)| token == "};") {
} else if split_at_identation(line).map_or(false, |(_ident, token)| token == "};") {
state = State::XCBuildConfiguration;
}
}
@@ -122,7 +122,7 @@ pub fn parse<P: AsRef<Path>>(path: P) -> crate::Result<Pbxproj> {
State::XCConfigurationListObject { id } => {
if line.contains("buildConfigurations") {
state = State::XCConfigurationListObjectBuildConfigurations { id: id.clone() };
} else if split_at_identation(line).is_some_and(|(_ident, token)| token == "};") {
} else if split_at_identation(line).map_or(false, |(_ident, token)| token == "};") {
state = State::XCConfigurationList;
}
}

View File

@@ -69,8 +69,6 @@ pub fn known_plugins() -> HashMap<&'static str, PluginMetadata> {
"shell",
"upload",
"websocket",
"opener",
"clipboard-manager",
] {
plugins.entry(p).or_default();
}

View File

@@ -4,9 +4,7 @@
use anyhow::Context;
use base64::Engine;
use minisign::{
sign, KeyPair as KP, PublicKey, PublicKeyBox, SecretKey, SecretKeyBox, SignatureBox,
};
use minisign::{sign, KeyPair as KP, SecretKey, SecretKeyBox, SignatureBox};
use std::{
fs::{self, File, OpenOptions},
io::{BufReader, BufWriter, Write},
@@ -134,24 +132,15 @@ pub fn secret_key<S: AsRef<[u8]>>(
private_key: S,
password: Option<String>,
) -> crate::Result<SecretKey> {
let decoded_secret = decode_key(private_key).context("failed to decode base64 secret key")?;
let sk_box =
SecretKeyBox::from_string(&decoded_secret).context("failed to load updater private key")?;
let decoded_secret = decode_key(private_key)?;
let sk_box = SecretKeyBox::from_string(&decoded_secret)
.with_context(|| "failed to load updater private key")?;
let sk = sk_box
.into_secret_key(password)
.context("incorrect updater private key password")?;
.with_context(|| "incorrect updater private key password")?;
Ok(sk)
}
/// Gets the updater secret key from the given private key and password.
pub fn pub_key<S: AsRef<[u8]>>(public_key: S) -> crate::Result<PublicKey> {
let decoded_publick = decode_key(public_key).context("failed to decode base64 pubkey")?;
let pk_box =
PublicKeyBox::from_string(&decoded_publick).context("failed to load updater pubkey")?;
let pk = pk_box.into_public_key()?;
Ok(pk)
}
fn unix_timestamp() -> u64 {
let start = SystemTime::now();
let since_the_epoch = start

View File

@@ -267,13 +267,13 @@ fn ico(source: &Source, out_dir: &Path) -> Result<()> {
Ok(())
}
// Generate .png files in 32x32, 64x64, 128x128, 256x256, 512x512 (icon.png)
// Generate .png files in 32x32, 128x128, 256x256, 512x512 (icon.png)
// Main target: Linux
fn png(source: &Source, out_dir: &Path, ios_color: Rgba<u8>) -> Result<()> {
fn desktop_entries(out_dir: &Path) -> Vec<PngEntry> {
let mut entries = Vec::new();
for size in [32, 64, 128, 256, 512] {
for size in [32, 128, 256, 512] {
let file_name = match size {
256 => "128x128@2x.png".to_string(),
512 => "icon.png".to_string(),

View File

@@ -10,10 +10,10 @@ use std::{
};
use tauri_utils::platform::Target;
pub fn items(frontend_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<SectionItem> {
pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<SectionItem> {
let mut items = Vec::new();
if tauri_dir.is_some() {
if let Ok(config) = crate::helpers::config::get(Target::current(), &[]) {
if let Ok(config) = crate::helpers::config::get(Target::current(), None) {
let config_guard = config.lock().unwrap();
let config = config_guard.as_ref().unwrap();
@@ -41,8 +41,8 @@ pub fn items(frontend_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<Se
items.push(SectionItem::new().description(format!("devUrl: {dev_url}")));
}
if let Some(frontend_dir) = frontend_dir {
if let Ok(package_json) = read_to_string(frontend_dir.join("package.json")) {
if let Some(app_dir) = app_dir {
if let Ok(package_json) = read_to_string(app_dir.join("package.json")) {
let (framework, bundler) = framework::infer_from_package_json(&package_json);
if let Some(framework) = framework {

View File

@@ -5,7 +5,23 @@
use super::{ActionResult, SectionItem, VersionMetadata};
use colored::Colorize;
use crate::helpers::{cross_command, npm::manager_version};
use crate::helpers::cross_command;
pub fn manager_version(package_manager: &str) -> Option<String> {
cross_command(package_manager)
.arg("-v")
.output()
.map(|o| {
if o.status.success() {
let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();
Some(v.split('\n').next().unwrap().to_string())
} else {
None
}
})
.ok()
.unwrap_or_default()
}
pub fn items(metadata: &VersionMetadata) -> Vec<SectionItem> {
let node_target_ver = metadata.js_cli.node.replace(">= ", "");
@@ -46,10 +62,17 @@ pub fn items(metadata: &VersionMetadata) -> Vec<SectionItem> {
.ok()
.unwrap_or_default()
}),
SectionItem::new().action(|| manager_version("pnpm").map(|v| format!("pnpm: {v}")).into()),
SectionItem::new().action(|| manager_version("yarn").map(|v| format!("yarn: {v}")).into()),
SectionItem::new().action(|| manager_version("npm").map(|v| format!("npm: {v}")).into()),
SectionItem::new().action(|| manager_version("bun").map(|v| format!("bun: {v}")).into()),
SectionItem::new().action(|| manager_version("deno").map(|v| format!("deno: {v}")).into()),
SectionItem::new().action(|| {
manager_version("pnpm")
.map(|v| format!("pnpm: {}", v))
.into()
}),
SectionItem::new().action(|| {
manager_version("yarn")
.map(|v| format!("yarn: {}", v))
.into()
}),
SectionItem::new().action(|| manager_version("npm").map(|v| format!("npm: {}", v)).into()),
SectionItem::new().action(|| manager_version("bun").map(|v| format!("bun: {}", v)).into()),
]
}

View File

@@ -175,45 +175,17 @@ fn is_xcode_command_line_tools_installed() -> bool {
.map(|o| o.status.success())
.unwrap_or(false)
}
fn de_and_session() -> String {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
return {
let de = std::env::var("XDG_SESSION_DESKTOP");
let session = std::env::var("XDG_SESSION_TYPE");
format!(
" ({} on {})",
de.as_deref().unwrap_or("Unknown DE"),
session.as_deref().unwrap_or("Unknown Session")
)
};
#[cfg(not(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
)))]
String::new()
}
pub fn items() -> Vec<SectionItem> {
vec![
SectionItem::new().action(|| {
let os_info = os_info::get();
format!(
"OS: {} {} {} ({:?}){}",
"OS: {} {} {} ({:?})",
os_info.os_type(),
os_info.version(),
os_info.architecture().unwrap_or("Unknown Architecture"),
os_info.bitness(),
de_and_session(),
os_info.bitness()
).into()
}),
#[cfg(windows)]

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