mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-11 10:43:31 +02:00
Compare commits
85 Commits
feat/cli/k
...
feat/ci-io
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b8443ffa3 | ||
|
|
be808a9f5c | ||
|
|
6dcb7fbb81 | ||
|
|
f6f9192aa5 | ||
|
|
03d6c6a68f | ||
|
|
538e21e2e7 | ||
|
|
658bb1165e | ||
|
|
634c6b832c | ||
|
|
8c576222ba | ||
|
|
169d2434ff | ||
|
|
f9b529a96e | ||
|
|
7c26514340 | ||
|
|
69414a487f | ||
|
|
602d3ed3f6 | ||
|
|
348b26ffed | ||
|
|
d0ad0d2f48 | ||
|
|
10ab3e2f5e | ||
|
|
8ea87e9c9c | ||
|
|
c9ad2a73af | ||
|
|
bc1622c5ab | ||
|
|
e1b8ee2b7a | ||
|
|
f333656876 | ||
|
|
4c1c78ebf0 | ||
|
|
610aab7045 | ||
|
|
30d0e190bb | ||
|
|
5643ece77c | ||
|
|
aae91a9b53 | ||
|
|
349895b296 | ||
|
|
5d82357166 | ||
|
|
8f3a9c5cf6 | ||
|
|
6593f267b3 | ||
|
|
5d3242c496 | ||
|
|
68e80ffaa9 | ||
|
|
9f9e3ae54d | ||
|
|
82e8751ae8 | ||
|
|
e22d21beaf | ||
|
|
b36ccb7e99 | ||
|
|
65e7085d2f | ||
|
|
c4d323b70f | ||
|
|
914c5f299f | ||
|
|
1d9226b28c | ||
|
|
8cf9af93eb | ||
|
|
0a203a13ae | ||
|
|
dee19784f9 | ||
|
|
53a3398beb | ||
|
|
69aaff5507 | ||
|
|
80a301ea63 | ||
|
|
badad2b9a1 | ||
|
|
927ccc465d | ||
|
|
4a5f2ec1ae | ||
|
|
752ad3b203 | ||
|
|
ff4cb56b2e | ||
|
|
641d56dcb3 | ||
|
|
0500d3b4b1 | ||
|
|
1f84385e8a | ||
|
|
3668a1fdc8 | ||
|
|
2b846f413c | ||
|
|
4e26b05d20 | ||
|
|
a70f7b26bc | ||
|
|
e56a9dd729 | ||
|
|
403859d47e | ||
|
|
4c9ea450c3 | ||
|
|
b3a3afc7de | ||
|
|
3d992a8899 | ||
|
|
9bb81bc328 | ||
|
|
9890486321 | ||
|
|
16360aed33 | ||
|
|
e55d96acc1 | ||
|
|
8aad710064 | ||
|
|
6f0615044d | ||
|
|
f445f374a3 | ||
|
|
a3680ef2bc | ||
|
|
e20145cccc | ||
|
|
c2b120be51 | ||
|
|
3f655d6280 | ||
|
|
a9c8e565c6 | ||
|
|
a9f8ac7f96 | ||
|
|
d44f67f7af | ||
|
|
d3179b84b5 | ||
|
|
4c9c64c429 | ||
|
|
d14322de68 | ||
|
|
899f9b917a | ||
|
|
11d50e8474 | ||
|
|
6aee91a181 | ||
|
|
b4622ea4d3 |
5
.changes/build-android-env-vars.md
Normal file
5
.changes/build-android-env-vars.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-build": patch
|
||||
---
|
||||
|
||||
Set environment variables used by `tauri::mobile_entry_point`.
|
||||
6
.changes/cli-android-build.md
Normal file
6
.changes/cli-android-build.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"cli.rs": minor
|
||||
"cli.js": minor
|
||||
---
|
||||
|
||||
Added `android build` command.
|
||||
6
.changes/cli-ios-build.md
Normal file
6
.changes/cli-ios-build.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"cli.rs": patch
|
||||
"cli.js": patch
|
||||
---
|
||||
|
||||
Added `ios build` command.
|
||||
6
.changes/cli-mobile-dev.md
Normal file
6
.changes/cli-mobile-dev.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"cli.rs": minor
|
||||
"cli.js": minor
|
||||
---
|
||||
|
||||
Added `android dev` and `ios dev` commands.
|
||||
5
.changes/codegen-mobile-devurl.md
Normal file
5
.changes/codegen-mobile-devurl.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-codegen": patch
|
||||
---
|
||||
|
||||
Change `devPath` URL to use the local IP address on iOS and Android.
|
||||
@@ -3,7 +3,6 @@
|
||||
"timeout": 3600000,
|
||||
"pkgManagers": {
|
||||
"rust": {
|
||||
"errorOnVersionRange": "^2.0.0-0",
|
||||
"version": true,
|
||||
"getPublishedVersion": "cargo search ${ pkgFile.pkg.package.name } --limit 1 | sed -nE \"s/^[^\\\"]*\\\"//; s/\\\".*//1p\"",
|
||||
"prepublish": [
|
||||
@@ -68,7 +67,6 @@
|
||||
]
|
||||
},
|
||||
"javascript": {
|
||||
"errorOnVersionRange": "^2.0.0-0",
|
||||
"version": true,
|
||||
"getPublishedVersion": "npm view ${ pkgFile.pkg.name } version",
|
||||
"prepublish": [
|
||||
|
||||
5
.changes/default-tls-features.md
Normal file
5
.changes/default-tls-features.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": major
|
||||
---
|
||||
|
||||
Added the `default-tls` and `reqwest-default-tls` Cargo features for enabling TLS suppport to connect over HTTPS.
|
||||
5
.changes/dev-proxy.md
Normal file
5
.changes/dev-proxy.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": major
|
||||
---
|
||||
|
||||
**Breaking change:** Use the custom protocol as a proxy to the development server on all platforms except Linux.
|
||||
5
.changes/mobile-config.md
Normal file
5
.changes/mobile-config.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-utils": minor
|
||||
---
|
||||
|
||||
Parse `android` and `ios` Tauri configuration files.
|
||||
5
.changes/mobile-entry-point-macro.md
Normal file
5
.changes/mobile-entry-point-macro.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-macros": minor
|
||||
---
|
||||
|
||||
Added the `mobile_entry_point` macro.
|
||||
6
.changes/mobile-init.md
Normal file
6
.changes/mobile-init.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"cli.rs": minor
|
||||
"cli.js": minor
|
||||
---
|
||||
|
||||
Added `android init` and `ios init` commands.
|
||||
6
.changes/mobile-open.md
Normal file
6
.changes/mobile-open.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"cli.rs": minor
|
||||
"cli.js": minor
|
||||
---
|
||||
|
||||
Added `android open` and `ios open` commands.
|
||||
6
.changes/mobile-webview-access.md
Normal file
6
.changes/mobile-webview-access.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-runtime-wry": minor
|
||||
"tauri": minor
|
||||
---
|
||||
|
||||
Support `with_webview` for Android platform alowing execution of JNI code in context.
|
||||
5
.changes/refactor-setup.md
Normal file
5
.changes/refactor-setup.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": major
|
||||
---
|
||||
|
||||
**Breaking change:** The window creation and setup hook are now called when the event loop is ready.
|
||||
5
.changes/tauri-mobile-entry-point.md
Normal file
5
.changes/tauri-mobile-entry-point.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": minor
|
||||
---
|
||||
|
||||
Export types required by the `mobile_entry_point` macro.
|
||||
38
.github/workflows/test-core.yml
vendored
38
.github/workflows/test-core.yml
vendored
@@ -34,29 +34,49 @@ jobs:
|
||||
- {
|
||||
target: x86_64-pc-windows-msvc,
|
||||
os: windows-latest,
|
||||
toolchain: '1.61.0'
|
||||
toolchain: '1.61.0',
|
||||
cross: false,
|
||||
command: 'test'
|
||||
}
|
||||
- {
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
os: ubuntu-latest,
|
||||
toolchain: '1.59.0'
|
||||
toolchain: '1.59.0',
|
||||
cross: false,
|
||||
command: 'test'
|
||||
}
|
||||
- {
|
||||
target: x86_64-apple-darwin,
|
||||
os: macos-latest,
|
||||
toolchain: '1.59.0'
|
||||
toolchain: '1.59.0',
|
||||
cross: false,
|
||||
command: 'test'
|
||||
}
|
||||
- {
|
||||
target: aarch64-apple-ios,
|
||||
os: macos-latest,
|
||||
toolchain: '1.59.0',
|
||||
cross: false,
|
||||
command: 'build'
|
||||
}
|
||||
- {
|
||||
target: aarch64-linux-android,
|
||||
os: ubuntu-latest,
|
||||
toolchain: '1.59.0',
|
||||
cross: true,
|
||||
command: 'build'
|
||||
}
|
||||
features:
|
||||
- {
|
||||
args: --no-default-features,
|
||||
args: --no-default-features --features native-tls-vendored,
|
||||
key: no-default
|
||||
}
|
||||
- {
|
||||
args: --features api-all,
|
||||
args: --features native-tls-vendored,api-all,
|
||||
key: api-all
|
||||
}
|
||||
- {
|
||||
args: --features compression,wry,isolation,custom-protocol,api-all,cli,updater,system-tray,windows7-compat,http-multipart,
|
||||
args: --features native-tls-vendored,compression,wry,isolation,custom-protocol,api-all,cli,updater,system-tray,windows7-compat,http-multipart,
|
||||
key: all
|
||||
}
|
||||
|
||||
@@ -83,4 +103,8 @@ jobs:
|
||||
save-if: ${{ matrix.features.key == 'all' }}
|
||||
|
||||
- name: test
|
||||
run: cargo test --target ${{ matrix.platform.target }} ${{ matrix.features.args }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.platform.cross }}
|
||||
command: ${{ matrix.platform.command }}
|
||||
args: --target ${{ matrix.platform.target }} ${{ matrix.features.args }}
|
||||
|
||||
106
.github/workflows/test-mobile.yml
vendored
Normal file
106
.github/workflows/test-mobile.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
name: test mobile
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/test-mobile.yml'
|
||||
- 'tooling/cli/templates/mobile/**'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: install Linux dependencies
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: yarn
|
||||
cache-dependency-path: |
|
||||
tooling/api/yarn.lock
|
||||
examples/api/yarn.lock
|
||||
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
cache: gradle
|
||||
|
||||
- name: Setup NDK
|
||||
uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r25b
|
||||
local-cache: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: |
|
||||
tooling/cli
|
||||
examples/api/src-tauri
|
||||
|
||||
- name: build CLI
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path ./tooling/cli/Cargo.toml
|
||||
|
||||
- name: build Tauri API
|
||||
working-directory: ./tooling/api
|
||||
run: yarn && yarn build
|
||||
|
||||
- name: install API example dependencies
|
||||
working-directory: ./examples/api
|
||||
run: yarn
|
||||
|
||||
- name: init Android Studio project
|
||||
working-directory: ./examples/api
|
||||
run: ../../tooling/cli/target/debug/cargo-tauri android init
|
||||
env:
|
||||
NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
|
||||
- name: build APK
|
||||
working-directory: ./examples/api
|
||||
run: ../../tooling/cli/target/debug/cargo-tauri android build
|
||||
env:
|
||||
NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
|
||||
- name: setup iOS code signing certificate
|
||||
if: matrix.platform == 'macos-latest'
|
||||
run: ./tooling/cli/target/debug/cargo-tauri ios certificate setup
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: init XCode project
|
||||
if: matrix.platform == 'macos-latest'
|
||||
working-directory: ./examples/api
|
||||
run: ../../tooling/cli/target/debug/cargo-tauri ios init
|
||||
|
||||
- name: build IPA
|
||||
if: matrix.platform == 'macos-latest'
|
||||
run: ../../tooling/cli/target/debug/cargo-tauri ios build
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Config",
|
||||
"description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler, enable the app updater, define a system tray, enable APIs via the allowlist and more.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, and `tauri.macos.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml` and `Tauri.macos.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"allowlist\": { \"all\": true }, \"bundle\": {}, \"security\": { \"csp\": null }, \"updater\": { \"active\": false }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```",
|
||||
"description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler, enable the app updater, define a system tray, enable APIs via the allowlist and more.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"allowlist\": { \"all\": true }, \"bundle\": {}, \"security\": { \"csp\": null }, \"updater\": { \"active\": false }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
@@ -135,6 +135,7 @@
|
||||
"deb": {
|
||||
"files": {}
|
||||
},
|
||||
"iOS": {},
|
||||
"icon": [],
|
||||
"identifier": "",
|
||||
"macOS": {
|
||||
@@ -271,6 +272,7 @@
|
||||
"deb": {
|
||||
"files": {}
|
||||
},
|
||||
"iOS": {},
|
||||
"icon": [],
|
||||
"identifier": "",
|
||||
"macOS": {
|
||||
@@ -1132,6 +1134,15 @@
|
||||
"$ref": "#/definitions/WindowsConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"iOS": {
|
||||
"description": "iOS configuration.",
|
||||
"default": {},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/IosConfig"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -1618,6 +1629,20 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"IosConfig": {
|
||||
"description": "General configuration for the iOS target.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"developmentTeam": {
|
||||
"description": "The development team. This value is required for iOS development because code signing is enforced. The `TAURI_APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"AllowlistConfig": {
|
||||
"description": "Allowlist configuration.",
|
||||
"type": "object",
|
||||
|
||||
@@ -219,6 +219,18 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
}
|
||||
let config: Config = serde_json::from_value(config)?;
|
||||
|
||||
let s = config.tauri.bundle.identifier.split('.');
|
||||
let last = s.clone().count() - 1;
|
||||
let mut domain = String::new();
|
||||
for (i, w) in s.enumerate() {
|
||||
if i != last {
|
||||
domain.push_str(w);
|
||||
domain.push('_');
|
||||
}
|
||||
}
|
||||
domain.pop();
|
||||
println!("cargo:rustc-env=TAURI_ANDROID_DOMAIN={}", domain);
|
||||
|
||||
cfg_alias("dev", !has_feature("custom-protocol"));
|
||||
|
||||
let mut manifest = Manifest::from_path("Cargo.toml")?;
|
||||
@@ -275,6 +287,9 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
}
|
||||
|
||||
let target_triple = std::env::var("TARGET").unwrap();
|
||||
|
||||
println!("cargo:rustc-env=TAURI_TARGET_TRIPLE={}", target_triple);
|
||||
|
||||
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
// TODO: far from ideal, but there's no other way to get the target dir, see <https://github.com/rust-lang/cargo/issues/5457>
|
||||
let target_dir = out_dir
|
||||
@@ -333,11 +348,12 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
.window_icon_path
|
||||
.unwrap_or_else(|| find_icon(&config, |i| i.ends_with(".ico"), "icons/icon.ico"));
|
||||
|
||||
if window_icon_path.exists() {
|
||||
let mut res = WindowsResource::new();
|
||||
if target_triple.contains("windows") {
|
||||
if window_icon_path.exists() {
|
||||
let mut res = WindowsResource::new();
|
||||
|
||||
res.set_manifest(
|
||||
r#"
|
||||
res.set_manifest(
|
||||
r#"
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
@@ -353,42 +369,43 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
</dependency>
|
||||
</assembly>
|
||||
"#,
|
||||
);
|
||||
);
|
||||
|
||||
if let Some(sdk_dir) = &attributes.windows_attributes.sdk_dir {
|
||||
if let Some(sdk_dir_str) = sdk_dir.to_str() {
|
||||
res.set_toolkit_path(sdk_dir_str);
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"sdk_dir path is not valid; only UTF-8 characters are allowed"
|
||||
));
|
||||
if let Some(sdk_dir) = &attributes.windows_attributes.sdk_dir {
|
||||
if let Some(sdk_dir_str) = sdk_dir.to_str() {
|
||||
res.set_toolkit_path(sdk_dir_str);
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"sdk_dir path is not valid; only UTF-8 characters are allowed"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(version) = &config.package.version {
|
||||
if let Ok(v) = Version::parse(version) {
|
||||
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);
|
||||
if let Some(version) = &config.package.version {
|
||||
if let Ok(v) = Version::parse(version) {
|
||||
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);
|
||||
}
|
||||
res.set("FileVersion", version);
|
||||
res.set("ProductVersion", version);
|
||||
}
|
||||
res.set("FileVersion", version);
|
||||
res.set("ProductVersion", version);
|
||||
}
|
||||
if let Some(product_name) = &config.package.product_name {
|
||||
res.set("ProductName", product_name);
|
||||
res.set("FileDescription", product_name);
|
||||
}
|
||||
res.set_icon_with_id(&window_icon_path.display().to_string(), "32512");
|
||||
res.compile().with_context(|| {
|
||||
format!(
|
||||
"failed to compile `{}` into a Windows Resource file during tauri-build",
|
||||
if let Some(product_name) = &config.package.product_name {
|
||||
res.set("ProductName", product_name);
|
||||
res.set("FileDescription", product_name);
|
||||
}
|
||||
res.set_icon_with_id(&window_icon_path.display().to_string(), "32512");
|
||||
res.compile().with_context(|| {
|
||||
format!(
|
||||
"failed to compile `{}` into a Windows Resource file during tauri-build",
|
||||
window_icon_path.display()
|
||||
)
|
||||
})?;
|
||||
} else {
|
||||
return Err(anyhow!(format!(
|
||||
"`{}` not found; required for generating a Windows Resource file during tauri-build",
|
||||
window_icon_path.display()
|
||||
)
|
||||
})?;
|
||||
} else {
|
||||
return Err(anyhow!(format!(
|
||||
"`{}` not found; required for generating a Windows Resource file during tauri-build",
|
||||
window_icon_path.display()
|
||||
)));
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
|
||||
|
||||
@@ -29,6 +29,10 @@ semver = "1"
|
||||
ico = "0.1"
|
||||
png = "0.17"
|
||||
json-patch = "0.2"
|
||||
url = "2"
|
||||
|
||||
[target."cfg(any(windows, target_os = \"linux\", target_os = \"macos\"))".dependencies]
|
||||
local-ip-address = "0.4"
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
plist = "1"
|
||||
|
||||
@@ -124,38 +124,56 @@ enum Target {
|
||||
pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsError> {
|
||||
let ContextData {
|
||||
dev,
|
||||
config,
|
||||
mut config,
|
||||
config_parent,
|
||||
root,
|
||||
} = data;
|
||||
|
||||
let target = if let Ok(target) = std::env::var("TARGET") {
|
||||
if target.contains("unknown-linux") {
|
||||
let target =
|
||||
if let Ok(target) = std::env::var("TARGET").or_else(|_| std::env::var("TAURI_TARGET_TRIPLE")) {
|
||||
if target.contains("unknown-linux") {
|
||||
Target::Linux
|
||||
} else if target.contains("pc-windows") {
|
||||
Target::Windows
|
||||
} else if target.contains("apple-darwin") {
|
||||
Target::Darwin
|
||||
} else if target.contains("android") {
|
||||
Target::Android
|
||||
} else if target.contains("apple-ios") {
|
||||
Target::Ios
|
||||
} else {
|
||||
panic!("unknown codegen target {}", target);
|
||||
}
|
||||
} else if cfg!(target_os = "linux") {
|
||||
Target::Linux
|
||||
} else if target.contains("pc-windows") {
|
||||
} else if cfg!(windows) {
|
||||
Target::Windows
|
||||
} else if target.contains("apple-darwin") {
|
||||
} else if cfg!(target_os = "macos") {
|
||||
Target::Darwin
|
||||
} else if target.contains("android") {
|
||||
} else if cfg!(target_os = "android") {
|
||||
Target::Android
|
||||
} else if target.contains("apple-ios") {
|
||||
} else if cfg!(target_os = "ios") {
|
||||
Target::Ios
|
||||
} else {
|
||||
panic!("unknown codegen target {}", target);
|
||||
panic!("unknown codegen target")
|
||||
};
|
||||
|
||||
#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
|
||||
if dev && (target == Target::Ios || target == Target::Android) {
|
||||
if let AppUrl::Url(WindowUrl::External(url)) = &mut config.build.dev_path {
|
||||
let localhost = match url.host() {
|
||||
Some(url::Host::Domain(d)) => d == "localhost",
|
||||
Some(url::Host::Ipv4(i)) => {
|
||||
i == std::net::Ipv4Addr::LOCALHOST || i == std::net::Ipv4Addr::UNSPECIFIED
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if localhost {
|
||||
let ip = local_ip_address::local_ip().expect("failed to resolve local IP address");
|
||||
url.set_host(Some(&ip.to_string())).unwrap();
|
||||
}
|
||||
}
|
||||
} else if cfg!(target_os = "linux") {
|
||||
Target::Linux
|
||||
} else if cfg!(windows) {
|
||||
Target::Windows
|
||||
} else if cfg!(target_os = "macos") {
|
||||
Target::Darwin
|
||||
} else if cfg!(target_os = "android") {
|
||||
Target::Android
|
||||
} else if cfg!(target_os = "ios") {
|
||||
Target::Ios
|
||||
} else {
|
||||
panic!("unknown codegen target");
|
||||
};
|
||||
}
|
||||
|
||||
let mut options = AssetOptions::new(config.tauri.pattern.clone())
|
||||
.freeze_prototype(config.tauri.security.freeze_prototype)
|
||||
|
||||
@@ -431,6 +431,7 @@ impl ToTokens for EmbeddedAssets {
|
||||
|
||||
// we expect phf related items to be in path when generating the path code
|
||||
tokens.append_all(quote! {{
|
||||
#[allow(unused_imports)]
|
||||
use ::tauri::utils::assets::{CspHash, EmbeddedAssets, phf, phf::phf_map};
|
||||
EmbeddedAssets::new(phf_map! { #assets }, &[#global_hashes], phf_map! { #html_hashes })
|
||||
}});
|
||||
|
||||
@@ -8,6 +8,7 @@ use syn::{parse_macro_input, DeriveInput, ItemFn};
|
||||
|
||||
mod command;
|
||||
mod command_module;
|
||||
mod mobile;
|
||||
mod runtime;
|
||||
|
||||
#[macro_use]
|
||||
@@ -24,6 +25,11 @@ pub fn command(attributes: TokenStream, item: TokenStream) -> TokenStream {
|
||||
command::wrapper(attributes, item)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn mobile_entry_point(attributes: TokenStream, item: TokenStream) -> TokenStream {
|
||||
mobile::entry_point(attributes, item)
|
||||
}
|
||||
|
||||
/// Accepts a list of commands functions. Creates a handler that allows commands to be called from JS with invoke().
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
83
core/tauri-macros/src/mobile.rs
Normal file
83
core/tauri-macros/src/mobile.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use std::env::var;
|
||||
use syn::{parse_macro_input, spanned::Spanned, ItemFn};
|
||||
|
||||
fn get_env_var<R: FnOnce(String) -> String>(
|
||||
name: &str,
|
||||
replacer: R,
|
||||
error: &mut Option<TokenStream2>,
|
||||
function: &ItemFn,
|
||||
) -> TokenStream2 {
|
||||
match var(name) {
|
||||
Ok(value) => {
|
||||
let ident = format_ident!("{}", replacer(value));
|
||||
quote!(#ident)
|
||||
}
|
||||
Err(_) => {
|
||||
error.replace(
|
||||
syn::Error::new(
|
||||
function.span(),
|
||||
format!(
|
||||
"`{}` env var not set, do you have a build script with tauri-build?",
|
||||
name,
|
||||
),
|
||||
)
|
||||
.into_compile_error(),
|
||||
);
|
||||
quote!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry_point(_attributes: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let function = parse_macro_input!(item as ItemFn);
|
||||
let function_name = function.sig.ident.clone();
|
||||
|
||||
let mut error = None;
|
||||
let domain = get_env_var("TAURI_ANDROID_DOMAIN", |r| r, &mut error, &function);
|
||||
let app_name = get_env_var(
|
||||
"CARGO_PKG_NAME",
|
||||
|r| r.replace('_', "_1"),
|
||||
&mut error,
|
||||
&function,
|
||||
);
|
||||
let app_name_str = var("CARGO_PKG_NAME").unwrap();
|
||||
|
||||
if let Some(e) = error {
|
||||
quote!(#e).into()
|
||||
} else {
|
||||
quote!(
|
||||
fn stop_unwind<F: FnOnce() -> T, T>(f: F) -> T {
|
||||
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
|
||||
Ok(t) => t,
|
||||
Err(err) => {
|
||||
eprintln!("attempt to unwind out of `rust` with err: {:?}", err);
|
||||
std::process::abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#function
|
||||
|
||||
fn _start_app() {
|
||||
::tauri::init_logging(#app_name_str);
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
use ::tauri::paste;
|
||||
::tauri::wry_android_binding!(#domain, #app_name, _start_app, ::tauri::wry);
|
||||
}
|
||||
stop_unwind(#function_name);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[no_mangle]
|
||||
#[inline(never)]
|
||||
pub extern "C" fn start_app() {
|
||||
_start_app()
|
||||
}
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
@@ -100,9 +100,9 @@ type FileDropHandler = dyn Fn(&Window, WryFileDropEvent) -> bool + 'static;
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
pub use tauri_runtime::TrayId;
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[cfg(any(desktop, target_os = "android"))]
|
||||
mod webview;
|
||||
#[cfg(desktop)]
|
||||
#[cfg(any(desktop, target_os = "android"))]
|
||||
pub use webview::Webview;
|
||||
|
||||
#[cfg(all(desktop, feature = "system-tray"))]
|
||||
@@ -1005,7 +1005,7 @@ pub enum ApplicationMessage {
|
||||
}
|
||||
|
||||
pub enum WindowMessage {
|
||||
#[cfg(desktop)]
|
||||
#[cfg(any(desktop, target_os = "android"))]
|
||||
WithWebview(Box<dyn FnOnce(Webview) + Send>),
|
||||
AddEventListener(Uuid, Box<dyn Fn(&WindowEvent) + Send>),
|
||||
AddMenuEventListener(Uuid, Box<dyn Fn(&MenuEvent) + Send>),
|
||||
@@ -1156,7 +1156,7 @@ pub struct WryDispatcher<T: UserEvent> {
|
||||
unsafe impl<T: UserEvent> Sync for WryDispatcher<T> {}
|
||||
|
||||
impl<T: UserEvent> WryDispatcher<T> {
|
||||
#[cfg(desktop)]
|
||||
#[cfg(any(desktop, target_os = "android"))]
|
||||
pub fn with_webview<F: FnOnce(Webview) + Send + 'static>(&self, f: F) -> Result<()> {
|
||||
send_user_message(
|
||||
&self.context,
|
||||
@@ -2253,7 +2253,7 @@ fn handle_user_message<T: UserEvent>(
|
||||
});
|
||||
if let Some((Some(window), window_event_listeners, menu_event_listeners)) = w {
|
||||
match window_message {
|
||||
#[cfg(desktop)]
|
||||
#[cfg(any(target_os = "android", desktop))]
|
||||
WindowMessage::WithWebview(f) => {
|
||||
if let WindowHandle::Webview { inner: w, .. } = &window {
|
||||
#[cfg(any(
|
||||
@@ -2276,13 +2276,17 @@ fn handle_user_message<T: UserEvent>(
|
||||
ns_window: w.ns_window(),
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
f(Webview {
|
||||
controller: w.controller(),
|
||||
});
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
use wry::webview::WebviewExtAndroid;
|
||||
f(w.handle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,4 +34,10 @@ mod imp {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
mod imp {
|
||||
use wry::webview::JniHandle;
|
||||
pub type Webview = JniHandle;
|
||||
}
|
||||
|
||||
pub use imp::*;
|
||||
|
||||
@@ -599,6 +599,9 @@ pub struct BundleConfig {
|
||||
/// Configuration for the Windows bundle.
|
||||
#[serde(default)]
|
||||
pub windows: WindowsConfig,
|
||||
/// iOS configuration.
|
||||
#[serde(rename = "iOS", default)]
|
||||
pub ios: IosConfig,
|
||||
}
|
||||
|
||||
/// A CLI argument definition.
|
||||
@@ -2475,6 +2478,18 @@ fn default_dialog() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// General configuration for the iOS target.
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct IosConfig {
|
||||
/// The development team. This value is required for iOS development because code signing is enforced.
|
||||
/// The `TAURI_APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.
|
||||
#[serde(alias = "development-team")]
|
||||
pub development_team: Option<String>,
|
||||
}
|
||||
|
||||
/// Defines the URL or assets to embed in the application.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||
@@ -2723,8 +2738,8 @@ impl PackageConfig {
|
||||
///
|
||||
/// In addition to the default configuration file, Tauri can
|
||||
/// read a platform-specific configuration from `tauri.linux.conf.json`,
|
||||
/// `tauri.windows.conf.json`, and `tauri.macos.conf.json`
|
||||
/// (or `Tauri.linux.toml`, `Tauri.windows.toml` and `Tauri.macos.toml` if the `Tauri.toml` format is used),
|
||||
/// `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`
|
||||
/// (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),
|
||||
/// which gets merged with the main configuration object.
|
||||
///
|
||||
/// ## Configuration Structure
|
||||
@@ -3252,6 +3267,7 @@ mod build {
|
||||
let macos = quote!(Default::default());
|
||||
let external_bin = opt_vec_str_lit(self.external_bin.as_ref());
|
||||
let windows = &self.windows;
|
||||
let ios = quote!(Default::default());
|
||||
|
||||
literal_struct!(
|
||||
tokens,
|
||||
@@ -3270,7 +3286,8 @@ mod build {
|
||||
deb,
|
||||
macos,
|
||||
external_bin,
|
||||
windows
|
||||
windows,
|
||||
ios
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3686,6 +3703,7 @@ mod test {
|
||||
macos: Default::default(),
|
||||
external_bin: None,
|
||||
windows: Default::default(),
|
||||
ios: Default::default(),
|
||||
},
|
||||
cli: None,
|
||||
updater: UpdaterConfig {
|
||||
|
||||
@@ -54,6 +54,10 @@ impl ConfigFormat {
|
||||
"tauri.macos.conf.json"
|
||||
} else if cfg!(windows) {
|
||||
"tauri.windows.conf.json"
|
||||
} else if cfg!(target_os = "android") {
|
||||
"tauri.android.conf.json"
|
||||
} else if cfg!(target_os = "ios") {
|
||||
"tauri.ios.conf.json"
|
||||
} else {
|
||||
"tauri.linux.conf.json"
|
||||
}
|
||||
@@ -63,6 +67,10 @@ impl ConfigFormat {
|
||||
"tauri.macos.conf.json5"
|
||||
} else if cfg!(windows) {
|
||||
"tauri.windows.conf.json5"
|
||||
} else if cfg!(target_os = "android") {
|
||||
"tauri.android.conf.json"
|
||||
} else if cfg!(target_os = "ios") {
|
||||
"tauri.ios.conf.json"
|
||||
} else {
|
||||
"tauri.linux.conf.json5"
|
||||
}
|
||||
@@ -72,6 +80,10 @@ impl ConfigFormat {
|
||||
"Tauri.macos.toml"
|
||||
} else if cfg!(windows) {
|
||||
"Tauri.windows.toml"
|
||||
} else if cfg!(target_os = "android") {
|
||||
"tauri.android.toml"
|
||||
} else if cfg!(target_os = "ios") {
|
||||
"tauri.ios.toml"
|
||||
} else {
|
||||
"Tauri.linux.toml"
|
||||
}
|
||||
@@ -170,11 +182,13 @@ pub fn is_configuration_file(path: &Path) -> bool {
|
||||
|
||||
/// Reads the configuration from the given root directory.
|
||||
///
|
||||
/// It first looks for a `tauri.conf.json[5]` file on the given directory. The file must exist.
|
||||
/// It first looks for a `tauri.conf.json[5]` or `Tauri.toml` file on the given directory. The file must exist.
|
||||
/// Then it looks for a platform-specific configuration file:
|
||||
/// - `tauri.macos.conf.json[5]` on macOS
|
||||
/// - `tauri.linux.conf.json[5]` on Linux
|
||||
/// - `tauri.windows.conf.json[5]` on Windows
|
||||
/// - `tauri.macos.conf.json[5]` or `Tauri.macos.toml` on macOS
|
||||
/// - `tauri.linux.conf.json[5]` or `Tauri.linux.toml` on Linux
|
||||
/// - `tauri.windows.conf.json[5]` or `Tauri.windows.toml` on Windows
|
||||
/// - `tauri.android.conf.json[5]` or `Tauri.android.toml` on Android
|
||||
/// - `tauri.ios.conf.json[5]` or `Tauri.ios.toml` on iOS
|
||||
/// Merging the configurations using [JSON Merge Patch (RFC 7396)].
|
||||
///
|
||||
/// [JSON Merge Patch (RFC 7396)]: https://datatracker.ietf.org/doc/html/rfc7396.
|
||||
|
||||
@@ -67,9 +67,9 @@ dirs-next = "2.0"
|
||||
percent-encoding = "2.2"
|
||||
base64 = { version = "0.13", optional = true }
|
||||
clap = { version = "3", optional = true }
|
||||
reqwest = { version = "0.11", features = [ "json", "stream" ], optional = true }
|
||||
reqwest = { version = "0.11", default-features = false, features = [ "json", "stream" ], optional = true }
|
||||
bytes = { version = "1", features = [ "serde" ], optional = true }
|
||||
attohttpc = { version = "0.22", features = [ "compress", "json", "form" ], optional = true }
|
||||
attohttpc = { version = "0.24", default-features = false, features = [ "compress", "json", "form" ] }
|
||||
open = { version = "3.0", optional = true }
|
||||
shared_child = { version = "1.0", optional = true }
|
||||
os_pipe = { version = "1.0", optional = true }
|
||||
@@ -104,9 +104,17 @@ objc = "0.2"
|
||||
webview2-com = "0.19.1"
|
||||
win7-notifications = { version = "0.3.1", optional = true }
|
||||
|
||||
[target."cfg(windows)".dependencies.windows]
|
||||
version = "0.39.0"
|
||||
features = [ "Win32_Foundation" ]
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
paste = "1.0"
|
||||
android_logger = "0.9"
|
||||
log = "0.4"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
env_logger = "0.9.0"
|
||||
|
||||
[target."cfg(windows)".dependencies.windows]
|
||||
version = "0.39.0"
|
||||
features = [ "Win32_Foundation" ]
|
||||
|
||||
[build-dependencies]
|
||||
heck = "0.4"
|
||||
@@ -139,11 +147,13 @@ updater = [
|
||||
"dialog-ask",
|
||||
"fs-extract-api"
|
||||
]
|
||||
http-api = [ "attohttpc" ]
|
||||
http-api = [ ]
|
||||
http-multipart = [ "attohttpc/multipart-form", "reqwest/multipart" ]
|
||||
shell-open-api = [ "open", "regex", "tauri-macros/shell-scope" ]
|
||||
fs-extract-api = [ "zip" ]
|
||||
reqwest-client = [ "reqwest", "bytes" ]
|
||||
reqwest-default-tls = [ "reqwest-client", "reqwest/default-tls" ]
|
||||
default-tls = [ "attohttpc/tls-native" ]
|
||||
reqwest-native-tls-vendored = [ "reqwest-client", "reqwest/native-tls-vendored" ]
|
||||
native-tls-vendored = [ "attohttpc/tls-vendored" ]
|
||||
process-command-api = [ "shared_child", "os_pipe" ]
|
||||
|
||||
@@ -245,41 +245,4 @@
|
||||
setNotificationPermission(response ? 'granted' : 'denied')
|
||||
}
|
||||
})
|
||||
|
||||
window.alert = function (message) {
|
||||
window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Dialog',
|
||||
message: {
|
||||
cmd: 'messageDialog',
|
||||
message: message.toString()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.confirm = function (message) {
|
||||
return window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Dialog',
|
||||
message: {
|
||||
cmd: 'confirmDialog',
|
||||
message: message.toString()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// window.print works on Linux/Windows; need to use the API on macOS
|
||||
if (navigator.userAgent.includes('Mac')) {
|
||||
window.print = function () {
|
||||
return window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Window',
|
||||
message: {
|
||||
cmd: 'manage',
|
||||
data: {
|
||||
cmd: {
|
||||
type: 'print'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
|
||||
__RAW_core_script__
|
||||
|
||||
__RAW_window_dialogs_script__
|
||||
|
||||
__RAW_window_print_script__
|
||||
|
||||
__RAW_event_initialization_script__
|
||||
|
||||
if (window.ipc) {
|
||||
|
||||
19
core/tauri/scripts/window_dialogs.js
Normal file
19
core/tauri/scripts/window_dialogs.js
Normal file
@@ -0,0 +1,19 @@
|
||||
window.alert = function (message) {
|
||||
window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Dialog',
|
||||
message: {
|
||||
cmd: 'messageDialog',
|
||||
message: message.toString()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.confirm = function (message) {
|
||||
return window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Dialog',
|
||||
message: {
|
||||
cmd: 'confirmDialog',
|
||||
message: message.toString()
|
||||
}
|
||||
})
|
||||
}
|
||||
13
core/tauri/scripts/window_print.js
Normal file
13
core/tauri/scripts/window_print.js
Normal file
@@ -0,0 +1,13 @@
|
||||
window.print = function () {
|
||||
return window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Window',
|
||||
message: {
|
||||
cmd: 'manage',
|
||||
data: {
|
||||
cmd: {
|
||||
type: 'print'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -40,6 +40,7 @@ use tauri_utils::PackageInfo;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
path::{Path, PathBuf},
|
||||
sync::{mpsc::Sender, Arc, Weak},
|
||||
};
|
||||
@@ -559,9 +560,10 @@ impl<R: Runtime> ManagerBase<R> for AppHandle<R> {
|
||||
///
|
||||
/// This type implements [`Manager`] which allows for manipulation of global application items.
|
||||
#[default_runtime(crate::Wry, wry)]
|
||||
#[derive(Debug)]
|
||||
pub struct App<R: Runtime> {
|
||||
runtime: Option<R>,
|
||||
pending_windows: Option<Vec<PendingWindow<EventLoopMessage, R>>>,
|
||||
setup: Option<SetupHook<R>>,
|
||||
manager: WindowManager<R>,
|
||||
#[cfg(all(desktop, feature = "global-shortcut"))]
|
||||
global_shortcut_manager: R::GlobalShortcutManager,
|
||||
@@ -570,6 +572,22 @@ pub struct App<R: Runtime> {
|
||||
handle: AppHandle<R>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> fmt::Debug for App<R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut d = f.debug_struct("App");
|
||||
d.field("runtime", &self.runtime)
|
||||
.field("manager", &self.manager)
|
||||
.field("handle", &self.handle);
|
||||
|
||||
#[cfg(all(desktop, feature = "global-shortcut"))]
|
||||
d.field("global_shortcut_manager", &self.global_shortcut_manager);
|
||||
#[cfg(feature = "clipboard")]
|
||||
d.field("clipboard_manager", &self.clipboard_manager);
|
||||
|
||||
d.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Manager<R> for App<R> {}
|
||||
impl<R: Runtime> ManagerBase<R> for App<R> {
|
||||
fn manager(&self) -> &WindowManager<R> {
|
||||
@@ -577,7 +595,11 @@ impl<R: Runtime> ManagerBase<R> for App<R> {
|
||||
}
|
||||
|
||||
fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
|
||||
RuntimeOrDispatch::Runtime(self.runtime.as_ref().unwrap())
|
||||
if let Some(runtime) = self.runtime.as_ref() {
|
||||
RuntimeOrDispatch::Runtime(runtime)
|
||||
} else {
|
||||
self.handle.runtime()
|
||||
}
|
||||
}
|
||||
|
||||
fn managed_app_handle(&self) -> AppHandle<R> {
|
||||
@@ -830,6 +852,17 @@ impl<R: Runtime> App<R> {
|
||||
let app_handle = self.handle();
|
||||
let manager = self.manager.clone();
|
||||
self.runtime.take().unwrap().run(move |event| match event {
|
||||
RuntimeRunEvent::Ready => {
|
||||
if let Err(e) = setup(&mut self) {
|
||||
panic!("Failed to setup app: {}", e);
|
||||
}
|
||||
on_event_loop_event(
|
||||
&app_handle,
|
||||
RuntimeRunEvent::Ready,
|
||||
&manager,
|
||||
Some(&mut callback),
|
||||
);
|
||||
}
|
||||
RuntimeRunEvent::Exit => {
|
||||
on_event_loop_event(
|
||||
&app_handle,
|
||||
@@ -1518,8 +1551,11 @@ impl<R: Runtime> Builder<R> {
|
||||
#[cfg(feature = "clipboard")]
|
||||
let clipboard_manager = runtime.clipboard_manager();
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut app = App {
|
||||
runtime: Some(runtime),
|
||||
pending_windows: Some(self.pending_windows),
|
||||
setup: Some(self.setup),
|
||||
manager: manager.clone(),
|
||||
#[cfg(all(desktop, feature = "global-shortcut"))]
|
||||
global_shortcut_manager: global_shortcut_manager.clone(),
|
||||
@@ -1609,26 +1645,6 @@ impl<R: Runtime> Builder<R> {
|
||||
|
||||
app.manager.initialize_plugins(&app.handle())?;
|
||||
|
||||
let window_labels = self
|
||||
.pending_windows
|
||||
.iter()
|
||||
.map(|p| p.label.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for pending in self.pending_windows {
|
||||
let pending =
|
||||
app
|
||||
.manager
|
||||
.prepare_window(app.handle.clone(), pending, &window_labels, None)?;
|
||||
let detached = app.runtime.as_ref().unwrap().create_window(pending)?;
|
||||
let _window = app.manager.attach_window(app.handle(), detached);
|
||||
}
|
||||
|
||||
(self.setup)(&mut app).map_err(|e| crate::Error::Setup(e.into()))?;
|
||||
|
||||
#[cfg(updater)]
|
||||
app.run_updater();
|
||||
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
@@ -1651,6 +1667,38 @@ unsafe impl HasRawDisplayHandle for App {
|
||||
}
|
||||
}
|
||||
|
||||
fn setup<R: Runtime>(app: &mut App<R>) -> crate::Result<()> {
|
||||
let pending_windows = app.pending_windows.take();
|
||||
if let Some(pending_windows) = pending_windows {
|
||||
let window_labels = pending_windows
|
||||
.iter()
|
||||
.map(|p| p.label.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for pending in pending_windows {
|
||||
let pending =
|
||||
app
|
||||
.manager
|
||||
.prepare_window(app.handle.clone(), pending, &window_labels, None)?;
|
||||
let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app.handle().runtime() {
|
||||
runtime.create_window(pending)?
|
||||
} else {
|
||||
// the AppHandle's runtime is always RuntimeOrDispatch::RuntimeHandle
|
||||
unreachable!()
|
||||
};
|
||||
let _window = app.manager.attach_window(app.handle(), detached);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(setup) = app.setup.take() {
|
||||
(setup)(app).map_err(|e| crate::Error::Setup(e.into()))?;
|
||||
}
|
||||
|
||||
#[cfg(updater)]
|
||||
app.run_updater();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_event_loop_event<R: Runtime, F: FnMut(&AppHandle<R>, RunEvent) + 'static>(
|
||||
app_handle: &AppHandle<R>,
|
||||
event: RuntimeRunEvent<EventLoopMessage>,
|
||||
|
||||
@@ -78,6 +78,8 @@ fn os_type() -> &'static str {
|
||||
return "Darwin";
|
||||
#[cfg(target_os = "ios")]
|
||||
return "iOS";
|
||||
#[cfg(target_os = "android")]
|
||||
return "Android";
|
||||
}
|
||||
|
||||
#[cfg(os_all)]
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
//! - **http-api**: Enables the [`api::http`] module.
|
||||
//! - **http-multipart**: Adds support to `multipart/form-data` requests.
|
||||
//! - **reqwest-client**: Uses `reqwest` as HTTP client on the `http` APIs. Improves performance, but increases the bundle size.
|
||||
//! - **default-tls**: Provides TLS support to connect over HTTPS (applies to the default HTTP client).
|
||||
//! - **reqwest-default-tls**: Provides TLS support to connect over HTTPS (applies to the `reqwest` HTTP client).
|
||||
//! - **native-tls-vendored**: Compile and statically link to a vendored copy of OpenSSL (applies to the default HTTP client).
|
||||
//! - **reqwest-native-tls-vendored**: Compile and statically link to a vendored copy of OpenSSL (applies to the `reqwest` HTTP client).
|
||||
//! - **process-command-api**: Enables the [`api::process::Command`] APIs.
|
||||
@@ -166,6 +168,8 @@ pub use error::Error;
|
||||
#[cfg(shell_scope)]
|
||||
#[doc(hidden)]
|
||||
pub use regex;
|
||||
#[cfg(mobile)]
|
||||
pub use tauri_macros::mobile_entry_point;
|
||||
pub use tauri_macros::{command, generate_handler};
|
||||
|
||||
pub mod api;
|
||||
@@ -196,6 +200,17 @@ pub use tauri_utils as utils;
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "wry")))]
|
||||
pub type Wry = tauri_runtime_wry::Wry<EventLoopMessage>;
|
||||
|
||||
#[cfg(all(feature = "wry", target_os = "android"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "wry", target_os = "android"))))]
|
||||
pub use tauri_runtime_wry::wry::android_binding as wry_android_binding;
|
||||
|
||||
#[cfg(all(feature = "wry", target_os = "android"))]
|
||||
#[doc(hidden)]
|
||||
pub use paste;
|
||||
#[cfg(all(feature = "wry", target_os = "android"))]
|
||||
#[doc(hidden)]
|
||||
pub use tauri_runtime_wry::wry;
|
||||
|
||||
/// `Result<T, ::tauri::Error>`
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -262,6 +277,22 @@ pub use self::runtime::ClipboardManager;
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "global-shortcut")))]
|
||||
pub use self::runtime::GlobalShortcutManager;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[doc(hidden)]
|
||||
pub fn init_logging(tag: &str) {
|
||||
android_logger::init_once(
|
||||
android_logger::Config::default()
|
||||
.with_min_level(log::Level::Trace)
|
||||
.with_tag(tag),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
#[doc(hidden)]
|
||||
pub fn init_logging(_tag: &str) {
|
||||
env_logger::init();
|
||||
}
|
||||
|
||||
/// Updater events.
|
||||
#[cfg(updater)]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
|
||||
|
||||
@@ -145,11 +145,6 @@ fn set_csp<R: Runtime>(
|
||||
Csp::DirectiveMap(csp).to_string()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn set_html_csp(html: &str, csp: &str) -> String {
|
||||
html.replacen(tauri_utils::html::CSP_TOKEN, csp, 1)
|
||||
}
|
||||
|
||||
// inspired by https://github.com/rust-lang/rust/blob/1be5c8f90912c446ecbdc405cbc4a89f9acd20fd/library/alloc/src/str.rs#L260-L297
|
||||
fn replace_with_callback<F: FnMut() -> String>(
|
||||
original: &str,
|
||||
@@ -377,7 +372,13 @@ impl<R: Runtime> WindowManager<R> {
|
||||
/// Get the origin as it will be seen in the webview.
|
||||
fn get_browser_origin(&self) -> String {
|
||||
match self.base_path() {
|
||||
AppUrl::Url(WindowUrl::External(url)) => url.origin().ascii_serialization(),
|
||||
AppUrl::Url(WindowUrl::External(url)) => {
|
||||
if cfg!(dev) && !cfg!(target_os = "linux") {
|
||||
format_real_schema("tauri")
|
||||
} else {
|
||||
url.origin().ascii_serialization()
|
||||
}
|
||||
}
|
||||
_ => format_real_schema("tauri"),
|
||||
}
|
||||
}
|
||||
@@ -884,8 +885,12 @@ impl<R: Runtime> WindowManager<R> {
|
||||
>,
|
||||
) -> Box<dyn Fn(&HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> + Send + Sync>
|
||||
{
|
||||
#[cfg(dev)]
|
||||
let url = self.get_url().into_owned();
|
||||
#[cfg(not(dev))]
|
||||
let manager = self.clone();
|
||||
let window_origin = window_origin.to_string();
|
||||
|
||||
Box::new(move |request| {
|
||||
let path = request
|
||||
.uri()
|
||||
@@ -898,32 +903,53 @@ impl<R: Runtime> WindowManager<R> {
|
||||
// the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows
|
||||
// where `$P` is not `localhost/*`
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
let asset = manager.get_asset(path)?;
|
||||
let mut builder = HttpResponseBuilder::new()
|
||||
.header("Access-Control-Allow-Origin", &window_origin)
|
||||
.mimetype(&asset.mime_type);
|
||||
if let Some(csp) = &asset.csp_header {
|
||||
builder = builder.header("Content-Security-Policy", csp);
|
||||
}
|
||||
let mut response = builder.body(asset.bytes)?;
|
||||
if let Some(handler) = &web_resource_request_handler {
|
||||
handler(request, &mut response);
|
||||
|
||||
// if it's an HTML file, we need to set the CSP meta tag on Linux
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Some(response_csp) = response.headers().get("Content-Security-Policy") {
|
||||
let response_csp = String::from_utf8_lossy(response_csp.as_bytes());
|
||||
let body = set_html_csp(&String::from_utf8_lossy(response.body()), &response_csp);
|
||||
*response.body_mut() = body.as_bytes().to_vec();
|
||||
}
|
||||
} else {
|
||||
#[cfg(target_os = "linux")]
|
||||
let mut builder =
|
||||
HttpResponseBuilder::new().header("Access-Control-Allow-Origin", &window_origin);
|
||||
|
||||
#[cfg(dev)]
|
||||
let mut response = {
|
||||
let mut url = url.clone();
|
||||
url.set_path(&path);
|
||||
match attohttpc::get(url.as_str())
|
||||
.danger_accept_invalid_certs(true)
|
||||
.send()
|
||||
{
|
||||
if let Some(csp) = &asset.csp_header {
|
||||
let body = set_html_csp(&String::from_utf8_lossy(response.body()), csp);
|
||||
*response.body_mut() = body.as_bytes().to_vec();
|
||||
Ok(r) => {
|
||||
for (name, value) in r.headers() {
|
||||
if name == "Content-Type" {
|
||||
builder = builder.mimetype(value.to_str().unwrap());
|
||||
}
|
||||
builder = builder.header(name, value);
|
||||
}
|
||||
builder.status(r.status()).body(r.bytes()?)?
|
||||
}
|
||||
Err(e) => {
|
||||
debug_eprintln!("Failed to request {}: {}", url.as_str(), e);
|
||||
return Err(Box::new(e));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(dev))]
|
||||
let mut response = {
|
||||
let asset = manager.get_asset(path)?;
|
||||
builder = builder.mimetype(&asset.mime_type);
|
||||
if let Some(csp) = &asset.csp_header {
|
||||
builder = builder.header("Content-Security-Policy", csp);
|
||||
}
|
||||
builder.body(asset.bytes)?
|
||||
};
|
||||
if let Some(handler) = &web_resource_request_handler {
|
||||
handler(request, &mut response);
|
||||
}
|
||||
// if it's an HTML file, we need to set the CSP meta tag on Linux
|
||||
#[cfg(all(not(dev), target_os = "linux"))]
|
||||
if let Some(response_csp) = response.headers().get("Content-Security-Policy") {
|
||||
let response_csp = String::from_utf8_lossy(response_csp.as_bytes());
|
||||
let html = String::from_utf8_lossy(response.body());
|
||||
let body = html.replacen(tauri_utils::html::CSP_TOKEN, &response_csp, 1);
|
||||
*response.body_mut() = body.as_bytes().to_vec();
|
||||
}
|
||||
Ok(response)
|
||||
})
|
||||
@@ -952,6 +978,10 @@ impl<R: Runtime> WindowManager<R> {
|
||||
#[raw]
|
||||
core_script: &'a str,
|
||||
#[raw]
|
||||
window_dialogs_script: &'a str,
|
||||
#[raw]
|
||||
window_print_script: &'a str,
|
||||
#[raw]
|
||||
event_initialization_script: &'a str,
|
||||
#[raw]
|
||||
plugin_initialization_script: &'a str,
|
||||
@@ -1017,6 +1047,19 @@ impl<R: Runtime> WindowManager<R> {
|
||||
)
|
||||
),
|
||||
core_script: include_str!("../scripts/core.js"),
|
||||
|
||||
// window.print works on Linux/Windows; need to use the API on macOS
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
window_print_script: include_str!("../scripts/window_print.js"),
|
||||
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
|
||||
window_print_script: "",
|
||||
|
||||
// dialogs are implemented natively on Android
|
||||
#[cfg(not(target_os = "android"))]
|
||||
window_dialogs_script: include_str!("../scripts/window_dialogs.js"),
|
||||
#[cfg(target_os = "android")]
|
||||
window_dialogs_script: "",
|
||||
|
||||
event_initialization_script: &self.event_initialization_script(),
|
||||
plugin_initialization_script,
|
||||
freeze_prototype,
|
||||
@@ -1125,7 +1168,10 @@ impl<R: Runtime> WindowManager<R> {
|
||||
#[allow(unused_mut)] // mut url only for the data-url parsing
|
||||
let (is_local, mut url) = match &pending.webview_attributes.url {
|
||||
WindowUrl::App(path) => {
|
||||
#[cfg(target_os = "linux")]
|
||||
let url = self.get_url();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let url: Cow<'_, Url> = Cow::Owned(Url::parse("tauri://localhost").unwrap());
|
||||
(
|
||||
true,
|
||||
// ignore "index.html" just to simplify the url
|
||||
@@ -1142,7 +1188,13 @@ impl<R: Runtime> WindowManager<R> {
|
||||
}
|
||||
WindowUrl::External(url) => {
|
||||
let config_url = self.get_url();
|
||||
(config_url.make_relative(url).is_some(), url.clone())
|
||||
let is_local = config_url.make_relative(url).is_some();
|
||||
let mut url = url.clone();
|
||||
if is_local && !cfg!(target_os = "linux") {
|
||||
url.set_scheme("tauri").unwrap();
|
||||
url.set_host(Some("localhost")).unwrap();
|
||||
}
|
||||
(is_local, url)
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
@@ -86,7 +86,7 @@ pub(crate) struct PatternJavascript {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn format_real_schema(schema: &str) -> String {
|
||||
if cfg!(windows) {
|
||||
if cfg!(windows) || cfg!(target_os = "android") {
|
||||
format!("https://{}.localhost", schema)
|
||||
} else {
|
||||
format!("{}://localhost", schema)
|
||||
|
||||
@@ -13,10 +13,10 @@ use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
#[cfg(shell_scope)]
|
||||
use crate::ShellScopeConfig;
|
||||
use crate::{Manager, Pattern};
|
||||
use crate::{Manager, Pattern, WindowBuilder};
|
||||
use tauri_utils::{
|
||||
assets::{AssetKey, Assets, CspHash},
|
||||
config::{CliConfig, Config, PatternKind, TauriConfig},
|
||||
config::{CliConfig, Config, PatternKind, TauriConfig, WindowUrl},
|
||||
};
|
||||
|
||||
pub struct NoopAsset {
|
||||
@@ -46,7 +46,7 @@ pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> {
|
||||
package: Default::default(),
|
||||
tauri: TauriConfig {
|
||||
pattern: PatternKind::Brownfield,
|
||||
windows: vec![Default::default()],
|
||||
windows: Vec::new(),
|
||||
cli: Some(CliConfig {
|
||||
description: None,
|
||||
long_description: None,
|
||||
@@ -86,9 +86,15 @@ pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> {
|
||||
}
|
||||
|
||||
pub fn mock_app() -> crate::App<MockRuntime> {
|
||||
crate::Builder::<MockRuntime>::new()
|
||||
let app = crate::Builder::<MockRuntime>::new()
|
||||
.build(mock_context(noop_assets()))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
WindowBuilder::new(&app, "main", WindowUrl::App("index.html".into()))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
app
|
||||
}
|
||||
|
||||
pub(crate) fn mock_invoke_context() -> crate::endpoints::InvokeContext<MockRuntime> {
|
||||
|
||||
@@ -643,11 +643,14 @@ impl<'de, R: Runtime> CommandArg<'de, R> for Window<R> {
|
||||
}
|
||||
|
||||
/// The platform webview handle. Accessed with [`Window#method.with_webview`];
|
||||
#[cfg(all(desktop, feature = "wry"))]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "wry"))))]
|
||||
#[cfg(all(any(desktop, target_os = "android"), feature = "wry"))]
|
||||
#[cfg_attr(
|
||||
doc_cfg,
|
||||
doc(cfg(all(any(desktop, target_os = "android"), feature = "wry")))
|
||||
)]
|
||||
pub struct PlatformWebview(tauri_runtime_wry::Webview);
|
||||
|
||||
#[cfg(all(desktop, feature = "wry"))]
|
||||
#[cfg(all(any(desktop, target_os = "android"), feature = "wry"))]
|
||||
impl PlatformWebview {
|
||||
/// Returns [`webkit2gtk::WebView`] handle.
|
||||
#[cfg(any(
|
||||
@@ -706,6 +709,12 @@ impl PlatformWebview {
|
||||
pub fn ns_window(&self) -> cocoa::base::id {
|
||||
self.0.ns_window
|
||||
}
|
||||
|
||||
/// Returns handle for JNI execution.
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn jni_handle(&self) -> tauri_runtime_wry::wry::webview::JniHandle {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// APIs specific to the wry runtime.
|
||||
@@ -749,13 +758,24 @@ impl Window<crate::Wry> {
|
||||
/// let bg_color: cocoa::base::id = msg_send![class!(NSColor), colorWithDeviceRed:0.5 green:0.2 blue:0.4 alpha:1.];
|
||||
/// let () = msg_send![webview.ns_window(), setBackgroundColor: bg_color];
|
||||
/// }
|
||||
///
|
||||
/// #[cfg(target_os = "android")]
|
||||
/// {
|
||||
/// use jni::objects::JValue;
|
||||
/// webview.jni_handle().exec(|env, _, webview| {
|
||||
/// env.call_method(webview, "zoomBy", "(F)V", &[JValue::Float(4.)]).unwrap();
|
||||
/// })
|
||||
/// }
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(desktop)]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "wry", desktop))))]
|
||||
#[cfg(any(desktop, target_os = "android"))]
|
||||
#[cfg_attr(
|
||||
doc_cfg,
|
||||
doc(cfg(all(feature = "wry", any(desktop, target_os = "android"))))
|
||||
)]
|
||||
pub fn with_webview<F: FnOnce(PlatformWebview) + Send + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
|
||||
@@ -100,7 +100,12 @@ fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf {
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf {
|
||||
root_dir.join(format!("target/debug/bundle/ios/app-updater.app"))
|
||||
root_dir.join(format!("target/debug/bundle/ios/app-updater.ipa"))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf {
|
||||
root_dir.join(format!("target/debug/bundle/android/app-updater.apk"))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --clearScreen false --port 5173",
|
||||
"dev": "vite --clearScreen false",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"tauri": "node ../../tooling/cli/node/tauri.js"
|
||||
@@ -15,9 +15,10 @@
|
||||
"devDependencies": {
|
||||
"@iconify-json/codicon": "^1.1.10",
|
||||
"@iconify-json/ph": "^1.1.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.49",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"internal-ip": "^7.0.0",
|
||||
"svelte": "^3.49.0",
|
||||
"unocss": "^0.39.3",
|
||||
"vite": "^2.9.13"
|
||||
"vite": "^3.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
4
examples/api/src-tauri/.gitignore
vendored
4
examples/api/src-tauri/.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# cargo-mobile
|
||||
.cargo/
|
||||
/gen
|
||||
|
||||
194
examples/api/src-tauri/Cargo.lock
generated
194
examples/api/src-tauri/Cargo.lock
generated
@@ -101,17 +101,11 @@ checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
||||
name = "api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"env_logger 0.9.3",
|
||||
"jni 0.19.0",
|
||||
"log",
|
||||
"mobile-entry-point",
|
||||
"paste",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-runtime-wry",
|
||||
"tiny_http",
|
||||
"window-shadows",
|
||||
"window-vibrancy",
|
||||
@@ -149,16 +143,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "attohttpc"
|
||||
version = "0.22.0"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7"
|
||||
checksum = "b85f766c20e6ae766956f7a2fcc4e0931e79a7e1f48b29132b5d647021114914"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"http",
|
||||
"log",
|
||||
"mime",
|
||||
"multipart",
|
||||
"native-tls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
@@ -1333,19 +1326,6 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ico"
|
||||
version = "0.1.0"
|
||||
@@ -1490,20 +1470,6 @@ dependencies = [
|
||||
"system-deps 5.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"combine",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.20.0"
|
||||
@@ -1620,6 +1586,18 @@ dependencies = [
|
||||
"safemem",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "local-ip-address"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74d6f43d42d775afa8073bfa6aba569b340a3635efa8c9f7702c9c6ed209692f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"neli",
|
||||
"thiserror",
|
||||
"windows-sys 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
@@ -1778,17 +1756,6 @@ dependencies = [
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mobile-entry-point"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bef5a90018326583471cccca10424d7b3e770397b02f03276543cbb9b6a1a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multipart"
|
||||
version = "0.18.0"
|
||||
@@ -1802,24 +1769,6 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.6.0"
|
||||
@@ -1848,6 +1797,16 @@ dependencies = [
|
||||
"jni-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "neli"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9053554eb5dcb7e10d9cdab1206965bde870eed5d0d341532ca035e3ba221508"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
@@ -2014,51 +1973,6 @@ dependencies = [
|
||||
"windows-sys 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.5.1"
|
||||
@@ -2574,13 +2488,11 @@ dependencies = [
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
@@ -2588,7 +2500,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"url",
|
||||
@@ -2667,16 +2578,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
@@ -2689,29 +2590,6 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.22.0"
|
||||
@@ -3092,7 +2970,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"image",
|
||||
"instant",
|
||||
"jni 0.20.0",
|
||||
"jni",
|
||||
"lazy_static",
|
||||
"libappindicator",
|
||||
"libc",
|
||||
@@ -3130,6 +3008,7 @@ dependencies = [
|
||||
name = "tauri"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"anyhow",
|
||||
"attohttpc",
|
||||
"base64",
|
||||
@@ -3139,6 +3018,7 @@ dependencies = [
|
||||
"dirs-next",
|
||||
"embed_plist",
|
||||
"encoding_rs",
|
||||
"env_logger 0.9.3",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"glib",
|
||||
@@ -3149,6 +3029,7 @@ dependencies = [
|
||||
"ico",
|
||||
"ignore",
|
||||
"infer 0.9.0",
|
||||
"log",
|
||||
"minisign-verify",
|
||||
"notify-rust",
|
||||
"objc",
|
||||
@@ -3156,6 +3037,7 @@ dependencies = [
|
||||
"open",
|
||||
"os_info",
|
||||
"os_pipe",
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"png 0.17.7",
|
||||
"rand 0.8.5",
|
||||
@@ -3212,6 +3094,7 @@ dependencies = [
|
||||
"brotli",
|
||||
"ico",
|
||||
"json-patch",
|
||||
"local-ip-address",
|
||||
"plist",
|
||||
"png 0.17.7",
|
||||
"proc-macro2",
|
||||
@@ -3224,6 +3107,7 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"time",
|
||||
"url",
|
||||
"uuid 1.2.1",
|
||||
"walkdir",
|
||||
]
|
||||
@@ -3463,16 +3347,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.4"
|
||||
@@ -3676,12 +3550,6 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.0.11"
|
||||
|
||||
@@ -6,6 +6,9 @@ edition = "2021"
|
||||
rust-version = "1.59"
|
||||
license = "Apache-2.0 OR MIT"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { path = "../../../core/tauri-build", features = ["codegen", "isolation"] }
|
||||
|
||||
@@ -35,19 +38,6 @@ features = [
|
||||
window-vibrancy = "0.2"
|
||||
window-shadows= "0.2"
|
||||
|
||||
[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies]
|
||||
log = "0.4"
|
||||
tauri-runtime-wry = { path = "../../../core/tauri-runtime-wry/" }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.9.0"
|
||||
jni = "0.19.0"
|
||||
paste = "1.0"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
mobile-entry-point = "0.1.0"
|
||||
env_logger = "0.9.0"
|
||||
|
||||
[features]
|
||||
default = [ "custom-protocol" ]
|
||||
custom-protocol = [ "tauri/custom-protocol" ]
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[app]
|
||||
name = "api"
|
||||
stylized-name = "Tauri API"
|
||||
domain = "com.tauri"
|
||||
template-pack = "tauri"
|
||||
|
||||
[apple]
|
||||
development-team = "0"
|
||||
@@ -9,6 +9,11 @@
|
||||
|
||||
mod cmd;
|
||||
|
||||
#[cfg(mobile)]
|
||||
mod mobile;
|
||||
#[cfg(mobile)]
|
||||
pub use mobile::*;
|
||||
|
||||
use serde::Serialize;
|
||||
use tauri::{window::WindowBuilder, App, AppHandle, RunEvent, WindowUrl};
|
||||
|
||||
|
||||
4
examples/api/src-tauri/src/mobile.rs
Normal file
4
examples/api/src-tauri/src/mobile.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[tauri::mobile_entry_point]
|
||||
fn main() {
|
||||
super::AppBuilder::new().run();
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
"devPath": "http://localhost:5173",
|
||||
"beforeDevCommand": "yarn dev",
|
||||
"beforeDevCommand": "yarn dev --host",
|
||||
"beforeBuildCommand": "yarn build"
|
||||
},
|
||||
"package": {
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import Unocss from 'unocss/vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
import { internalIpV4 } from 'internal-ip'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [Unocss(), svelte()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: `assets/[name].js`,
|
||||
chunkFileNames: `assets/[name].js`,
|
||||
assetFileNames: `assets/[name].[ext]`
|
||||
export default defineConfig(async ({ command, mode }) => {
|
||||
const host = await internalIpV4()
|
||||
return {
|
||||
plugins: [Unocss(), svelte()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: `assets/[name].js`,
|
||||
chunkFileNames: `assets/[name].js`,
|
||||
assetFileNames: `assets/[name].[ext]`
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
hmr: {
|
||||
protocol: 'ws',
|
||||
host,
|
||||
port: 5183
|
||||
},
|
||||
fs: {
|
||||
allow: ['.', '../../tooling/api/dist']
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
allow: ['.', '../../tooling/api/dist']
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.5.2.tgz#8c2d931ff927be0ebe740169874a3d4004ab414b"
|
||||
integrity sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==
|
||||
|
||||
"@esbuild/linux-loong64@0.14.54":
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
|
||||
integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
|
||||
|
||||
"@iconify-json/codicon@^1.1.10":
|
||||
version "1.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@iconify-json/codicon/-/codicon-1.1.10.tgz#22fee909be51afebfbcc6cd57209064b5363f202"
|
||||
@@ -80,15 +85,15 @@
|
||||
estree-walker "^2.0.1"
|
||||
picomatch "^2.2.2"
|
||||
|
||||
"@sveltejs/vite-plugin-svelte@^1.0.0-next.49":
|
||||
version "1.0.0-next.49"
|
||||
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.49.tgz#44cc00a19c6c23002516b66c5ab90ee66720df57"
|
||||
integrity sha512-AKh0Ka8EDgidnxWUs8Hh2iZLZovkETkefO99XxZ4sW4WGJ7VFeBx5kH/NIIGlaNHLcrIvK3CK0HkZwC3Cici0A==
|
||||
"@sveltejs/vite-plugin-svelte@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.1.tgz#7f468f03c933fcdfc60d4773671c73f33b9ef4d6"
|
||||
integrity sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^4.2.1"
|
||||
debug "^4.3.4"
|
||||
deepmerge "^4.2.2"
|
||||
kleur "^4.1.4"
|
||||
kleur "^4.1.5"
|
||||
magic-string "^0.26.2"
|
||||
svelte-hmr "^0.14.12"
|
||||
|
||||
@@ -323,6 +328,13 @@ deepmerge@^4.2.2:
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||
|
||||
default-gateway@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71"
|
||||
integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==
|
||||
dependencies:
|
||||
execa "^5.0.0"
|
||||
|
||||
defu@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/defu/-/defu-6.0.0.tgz#b397a6709a2f3202747a3d9daf9446e41ad0c5fc"
|
||||
@@ -338,138 +350,139 @@ duplexer@^0.1.2:
|
||||
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
|
||||
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
|
||||
|
||||
esbuild-android-64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.47.tgz#ef95b42c67bcf4268c869153fa3ad1466c4cea6b"
|
||||
integrity sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==
|
||||
esbuild-android-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
|
||||
integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==
|
||||
|
||||
esbuild-android-arm64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.47.tgz#4ebd7ce9fb250b4695faa3ee46fd3b0754ecd9e6"
|
||||
integrity sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==
|
||||
esbuild-android-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771"
|
||||
integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==
|
||||
|
||||
esbuild-darwin-64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.47.tgz#e0da6c244f497192f951807f003f6a423ed23188"
|
||||
integrity sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==
|
||||
esbuild-darwin-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25"
|
||||
integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==
|
||||
|
||||
esbuild-darwin-arm64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.47.tgz#cd40fd49a672fca581ed202834239dfe540a9028"
|
||||
integrity sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==
|
||||
esbuild-darwin-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73"
|
||||
integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==
|
||||
|
||||
esbuild-freebsd-64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.47.tgz#8da6a14c095b29c01fc8087a16cb7906debc2d67"
|
||||
integrity sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==
|
||||
esbuild-freebsd-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d"
|
||||
integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==
|
||||
|
||||
esbuild-freebsd-arm64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.47.tgz#ad31f9c92817ff8f33fd253af7ab5122dc1b83f6"
|
||||
integrity sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==
|
||||
esbuild-freebsd-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48"
|
||||
integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==
|
||||
|
||||
esbuild-linux-32@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.47.tgz#de085e4db2e692ea30c71208ccc23fdcf5196c58"
|
||||
integrity sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==
|
||||
esbuild-linux-32@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5"
|
||||
integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==
|
||||
|
||||
esbuild-linux-64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.47.tgz#2a9321bbccb01f01b04cebfcfccbabeba3658ba1"
|
||||
integrity sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==
|
||||
esbuild-linux-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652"
|
||||
integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
|
||||
|
||||
esbuild-linux-arm64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.47.tgz#b9da7b6fc4b0ca7a13363a0c5b7bb927e4bc535a"
|
||||
integrity sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==
|
||||
esbuild-linux-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b"
|
||||
integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==
|
||||
|
||||
esbuild-linux-arm@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.47.tgz#56fec2a09b9561c337059d4af53625142aded853"
|
||||
integrity sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==
|
||||
esbuild-linux-arm@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59"
|
||||
integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==
|
||||
|
||||
esbuild-linux-mips64le@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.47.tgz#9db21561f8f22ed79ef2aedb7bbef082b46cf823"
|
||||
integrity sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==
|
||||
esbuild-linux-mips64le@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34"
|
||||
integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==
|
||||
|
||||
esbuild-linux-ppc64le@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.47.tgz#dc3a3da321222b11e96e50efafec9d2de408198b"
|
||||
integrity sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==
|
||||
esbuild-linux-ppc64le@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e"
|
||||
integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==
|
||||
|
||||
esbuild-linux-riscv64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.47.tgz#9bd6dcd3dca6c0357084ecd06e1d2d4bf105335f"
|
||||
integrity sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==
|
||||
esbuild-linux-riscv64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8"
|
||||
integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==
|
||||
|
||||
esbuild-linux-s390x@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.47.tgz#a458af939b52f2cd32fc561410d441a51f69d41f"
|
||||
integrity sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==
|
||||
esbuild-linux-s390x@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6"
|
||||
integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==
|
||||
|
||||
esbuild-netbsd-64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.47.tgz#6388e785d7e7e4420cb01348d7483ab511b16aa8"
|
||||
integrity sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==
|
||||
esbuild-netbsd-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81"
|
||||
integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==
|
||||
|
||||
esbuild-openbsd-64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.47.tgz#309af806db561aa886c445344d1aacab850dbdc5"
|
||||
integrity sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==
|
||||
esbuild-openbsd-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b"
|
||||
integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==
|
||||
|
||||
esbuild-sunos-64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.47.tgz#3f19612dcdb89ba6c65283a7ff6e16f8afbf8aaa"
|
||||
integrity sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==
|
||||
esbuild-sunos-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da"
|
||||
integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==
|
||||
|
||||
esbuild-windows-32@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.47.tgz#a92d279c8458d5dc319abcfeb30aa49e8f2e6f7f"
|
||||
integrity sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==
|
||||
esbuild-windows-32@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31"
|
||||
integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==
|
||||
|
||||
esbuild-windows-64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.47.tgz#2564c3fcf0c23d701edb71af8c52d3be4cec5f8a"
|
||||
integrity sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==
|
||||
esbuild-windows-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4"
|
||||
integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
|
||||
|
||||
esbuild-windows-arm64@0.14.47:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.47.tgz#86d9db1a22d83360f726ac5fba41c2f625db6878"
|
||||
integrity sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==
|
||||
esbuild-windows-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982"
|
||||
integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==
|
||||
|
||||
esbuild@^0.14.27:
|
||||
version "0.14.47"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.47.tgz#0d6415f6bd8eb9e73a58f7f9ae04c5276cda0e4d"
|
||||
integrity sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==
|
||||
esbuild@^0.14.47:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2"
|
||||
integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
|
||||
optionalDependencies:
|
||||
esbuild-android-64 "0.14.47"
|
||||
esbuild-android-arm64 "0.14.47"
|
||||
esbuild-darwin-64 "0.14.47"
|
||||
esbuild-darwin-arm64 "0.14.47"
|
||||
esbuild-freebsd-64 "0.14.47"
|
||||
esbuild-freebsd-arm64 "0.14.47"
|
||||
esbuild-linux-32 "0.14.47"
|
||||
esbuild-linux-64 "0.14.47"
|
||||
esbuild-linux-arm "0.14.47"
|
||||
esbuild-linux-arm64 "0.14.47"
|
||||
esbuild-linux-mips64le "0.14.47"
|
||||
esbuild-linux-ppc64le "0.14.47"
|
||||
esbuild-linux-riscv64 "0.14.47"
|
||||
esbuild-linux-s390x "0.14.47"
|
||||
esbuild-netbsd-64 "0.14.47"
|
||||
esbuild-openbsd-64 "0.14.47"
|
||||
esbuild-sunos-64 "0.14.47"
|
||||
esbuild-windows-32 "0.14.47"
|
||||
esbuild-windows-64 "0.14.47"
|
||||
esbuild-windows-arm64 "0.14.47"
|
||||
"@esbuild/linux-loong64" "0.14.54"
|
||||
esbuild-android-64 "0.14.54"
|
||||
esbuild-android-arm64 "0.14.54"
|
||||
esbuild-darwin-64 "0.14.54"
|
||||
esbuild-darwin-arm64 "0.14.54"
|
||||
esbuild-freebsd-64 "0.14.54"
|
||||
esbuild-freebsd-arm64 "0.14.54"
|
||||
esbuild-linux-32 "0.14.54"
|
||||
esbuild-linux-64 "0.14.54"
|
||||
esbuild-linux-arm "0.14.54"
|
||||
esbuild-linux-arm64 "0.14.54"
|
||||
esbuild-linux-mips64le "0.14.54"
|
||||
esbuild-linux-ppc64le "0.14.54"
|
||||
esbuild-linux-riscv64 "0.14.54"
|
||||
esbuild-linux-s390x "0.14.54"
|
||||
esbuild-netbsd-64 "0.14.54"
|
||||
esbuild-openbsd-64 "0.14.54"
|
||||
esbuild-sunos-64 "0.14.54"
|
||||
esbuild-windows-32 "0.14.54"
|
||||
esbuild-windows-64 "0.14.54"
|
||||
esbuild-windows-arm64 "0.14.54"
|
||||
|
||||
estree-walker@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||
|
||||
execa@^5.1.1:
|
||||
execa@^5.0.0, execa@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
|
||||
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
|
||||
@@ -558,6 +571,26 @@ human-signals@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||
|
||||
internal-ip@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-7.0.0.tgz#5b1c6a9d7e188aa73a1b69717daf50c8d8ed774f"
|
||||
integrity sha512-qE4TeD4brqC45Vq/+VASeMiS1KRyfBkR6HT2sh9pZVVCzSjPkaCEfKFU+dL0PRv7NHJtvoKN2r82G6wTfzorkw==
|
||||
dependencies:
|
||||
default-gateway "^6.0.3"
|
||||
ipaddr.js "^2.0.1"
|
||||
is-ip "^3.1.0"
|
||||
p-event "^4.2.0"
|
||||
|
||||
ip-regex@^4.0.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5"
|
||||
integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==
|
||||
|
||||
ipaddr.js@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
|
||||
integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
|
||||
|
||||
is-binary-path@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||
@@ -584,6 +617,13 @@ is-glob@^4.0.1, is-glob@~4.0.1:
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-ip@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-3.1.0.tgz#2ae5ddfafaf05cb8008a62093cf29734f657c5d8"
|
||||
integrity sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==
|
||||
dependencies:
|
||||
ip-regex "^4.0.0"
|
||||
|
||||
is-number@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
@@ -604,10 +644,10 @@ jiti@^1.13.0:
|
||||
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.14.0.tgz#5350fff532a4d891ca4bcd700c47c1f40e6ee326"
|
||||
integrity sha512-4IwstlaKQc9vCTC+qUXLM1hajy2ImiL9KnLvVYiaHOtS/v3wRjhLlGl121AmgDgx/O43uKmxownJghS5XMya2A==
|
||||
|
||||
kleur@^4.1.4:
|
||||
version "4.1.4"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d"
|
||||
integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==
|
||||
kleur@^4.1.5:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
|
||||
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
|
||||
|
||||
kolorist@^1.5.1:
|
||||
version "1.5.1"
|
||||
@@ -710,6 +750,18 @@ onetime@^5.1.2:
|
||||
dependencies:
|
||||
mimic-fn "^2.1.0"
|
||||
|
||||
p-event@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5"
|
||||
integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==
|
||||
dependencies:
|
||||
p-timeout "^3.1.0"
|
||||
|
||||
p-finally@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
|
||||
integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
|
||||
|
||||
p-limit@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
|
||||
@@ -724,6 +776,13 @@ p-locate@^5.0.0:
|
||||
dependencies:
|
||||
p-limit "^3.0.2"
|
||||
|
||||
p-timeout@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
|
||||
integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==
|
||||
dependencies:
|
||||
p-finally "^1.0.0"
|
||||
|
||||
path-exists@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
||||
@@ -764,10 +823,10 @@ picomatch@^2.2.2:
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
||||
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
|
||||
|
||||
postcss@^8.4.13:
|
||||
version "8.4.14"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
|
||||
integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
|
||||
postcss@^8.4.16:
|
||||
version "8.4.16"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c"
|
||||
integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==
|
||||
dependencies:
|
||||
nanoid "^3.3.4"
|
||||
picocolors "^1.0.0"
|
||||
@@ -785,7 +844,7 @@ readdirp@~3.6.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
resolve@^1.22.0:
|
||||
resolve@^1.22.1:
|
||||
version "1.22.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
|
||||
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
|
||||
@@ -799,10 +858,10 @@ reusify@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||
|
||||
rollup@^2.59.0:
|
||||
version "2.75.7"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.75.7.tgz#221ff11887ae271e37dcc649ba32ce1590aaa0b9"
|
||||
integrity sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==
|
||||
"rollup@>=2.75.6 <2.77.0 || ~2.77.0":
|
||||
version "2.77.3"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12"
|
||||
integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
@@ -926,15 +985,15 @@ unocss@^0.39.3:
|
||||
"@unocss/transformer-variant-group" "0.39.3"
|
||||
"@unocss/vite" "0.39.3"
|
||||
|
||||
vite@^2.9.13:
|
||||
version "2.9.13"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.13.tgz#859cb5d4c316c0d8c6ec9866045c0f7858ca6abc"
|
||||
integrity sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw==
|
||||
vite@^3.0.9:
|
||||
version "3.0.9"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.9.tgz#45fac22c2a5290a970f23d66c1aef56a04be8a30"
|
||||
integrity sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==
|
||||
dependencies:
|
||||
esbuild "^0.14.27"
|
||||
postcss "^8.4.13"
|
||||
resolve "^1.22.0"
|
||||
rollup "^2.59.0"
|
||||
esbuild "^0.14.47"
|
||||
postcss "^8.4.16"
|
||||
resolve "^1.22.1"
|
||||
rollup ">=2.75.6 <2.77.0 || ~2.77.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ mod updater_bundle;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::sign as macos_sign;
|
||||
|
||||
pub use self::{
|
||||
category::AppCategory,
|
||||
settings::{
|
||||
|
||||
@@ -7,4 +7,5 @@ pub mod app;
|
||||
pub mod dmg;
|
||||
pub mod icon;
|
||||
pub mod ios;
|
||||
/// Code signing and notarization utilities.
|
||||
pub mod sign;
|
||||
|
||||
@@ -14,12 +14,12 @@ use regex::Regex;
|
||||
const KEYCHAIN_ID: &str = "tauri-build.keychain";
|
||||
const KEYCHAIN_PWD: &str = "tauri-build";
|
||||
|
||||
// Import certificate from ENV variables.
|
||||
// APPLE_CERTIFICATE is the p12 certificate base64 encoded.
|
||||
// By example you can use; openssl base64 -in MyCertificate.p12 -out MyCertificate-base64.txt
|
||||
// Then use the value of the base64 in APPLE_CERTIFICATE env variable.
|
||||
// You need to set APPLE_CERTIFICATE_PASSWORD to the password you set when you exported your certificate.
|
||||
// https://help.apple.com/xcode/mac/current/#/dev154b28f09 see: `Export a signing certificate`
|
||||
/// Import certificate from ENV variables.
|
||||
/// APPLE_CERTIFICATE is the p12 certificate base64 encoded.
|
||||
/// By example you can use; openssl base64 -in MyCertificate.p12 -out MyCertificate-base64.txt
|
||||
/// Then use the value of the base64 in APPLE_CERTIFICATE env variable.
|
||||
/// You need to set APPLE_CERTIFICATE_PASSWORD to the password you set when you exported your certificate.
|
||||
/// https://help.apple.com/xcode/mac/current/#/dev154b28f09 see: `Export a signing certificate`
|
||||
pub fn setup_keychain(
|
||||
certificate_encoded: OsString,
|
||||
certificate_password: OsString,
|
||||
@@ -130,6 +130,7 @@ pub fn setup_keychain(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes the keychain we setup from the environment variables.
|
||||
pub fn delete_keychain() {
|
||||
// delete keychain if needed and skip any error
|
||||
let _ = Command::new("security")
|
||||
@@ -138,6 +139,7 @@ pub fn delete_keychain() {
|
||||
.output_ok();
|
||||
}
|
||||
|
||||
/// Sign the application.
|
||||
pub fn sign(
|
||||
path_to_sign: PathBuf,
|
||||
identity: &str,
|
||||
@@ -212,6 +214,7 @@ fn try_sign(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Notarize the application.
|
||||
pub fn notarize(
|
||||
app_bundle_path: PathBuf,
|
||||
auth_args: Vec<String>,
|
||||
@@ -373,6 +376,7 @@ fn get_notarization_status(
|
||||
}
|
||||
}
|
||||
|
||||
/// Authorization arguments for the notarization process. Read from environment variables.
|
||||
pub fn notarize_auth_args() -> crate::Result<Vec<String>> {
|
||||
match (
|
||||
std::env::var_os("APPLE_ID"),
|
||||
|
||||
845
tooling/cli/Cargo.lock
generated
845
tooling/cli/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -39,6 +39,12 @@ name = "cargo-tauri"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# cargo-mobile = { path = "../../../cargo-mobile/", default-features = false }
|
||||
cargo-mobile = { git = "https://github.com/tauri-apps/cargo-mobile", branch = "dev", default-features = false }
|
||||
textwrap = { version = "0.11.0", features = ["term_size"] }
|
||||
jsonrpsee = { version = "0.16", features = [ "client", "server" ]}
|
||||
thiserror = "1"
|
||||
sublime_fuzzy = "0.7"
|
||||
clap = { version = "4.0", features = [ "derive" ] }
|
||||
anyhow = "1.0"
|
||||
tauri-bundler = { version = "1.1.0", path = "../bundler" }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Config",
|
||||
"description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler, enable the app updater, define a system tray, enable APIs via the allowlist and more.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, and `tauri.macos.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml` and `Tauri.macos.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"allowlist\": { \"all\": true }, \"bundle\": {}, \"security\": { \"csp\": null }, \"updater\": { \"active\": false }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```",
|
||||
"description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler, enable the app updater, define a system tray, enable APIs via the allowlist and more.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"allowlist\": { \"all\": true }, \"bundle\": {}, \"security\": { \"csp\": null }, \"updater\": { \"active\": false }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
@@ -135,6 +135,7 @@
|
||||
"deb": {
|
||||
"files": {}
|
||||
},
|
||||
"iOS": {},
|
||||
"icon": [],
|
||||
"identifier": "",
|
||||
"macOS": {
|
||||
@@ -271,6 +272,7 @@
|
||||
"deb": {
|
||||
"files": {}
|
||||
},
|
||||
"iOS": {},
|
||||
"icon": [],
|
||||
"identifier": "",
|
||||
"macOS": {
|
||||
@@ -1132,6 +1134,15 @@
|
||||
"$ref": "#/definitions/WindowsConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"iOS": {
|
||||
"description": "iOS configuration.",
|
||||
"default": {},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/IosConfig"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -1618,6 +1629,20 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"IosConfig": {
|
||||
"description": "General configuration for the iOS target.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"developmentTeam": {
|
||||
"description": "The development team. This value is required for iOS development because code signing is enforced. The `TAURI_APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"AllowlistConfig": {
|
||||
"description": "Allowlist configuration.",
|
||||
"type": "object",
|
||||
|
||||
@@ -57,110 +57,15 @@ pub struct Options {
|
||||
}
|
||||
|
||||
pub fn command(mut options: Options) -> Result<()> {
|
||||
let (merge_config, merge_config_path) = if let Some(config) = &options.config {
|
||||
if config.starts_with('{') {
|
||||
(Some(config.to_string()), None)
|
||||
} else {
|
||||
(
|
||||
Some(
|
||||
std::fs::read_to_string(config).with_context(|| "failed to read custom configuration")?,
|
||||
),
|
||||
Some(config.clone()),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
options.config = merge_config;
|
||||
|
||||
let tauri_path = tauri_dir();
|
||||
set_current_dir(&tauri_path).with_context(|| "failed to change current working directory")?;
|
||||
let mut interface = setup(&mut options, false)?;
|
||||
|
||||
let config = get_config(options.config.as_deref())?;
|
||||
|
||||
let config_guard = config.lock().unwrap();
|
||||
let config_ = config_guard.as_ref().unwrap();
|
||||
|
||||
let bundle_identifier_source = match config_.find_bundle_identifier_overwriter() {
|
||||
Some(source) if source == MERGE_CONFIG_EXTENSION_NAME => merge_config_path.unwrap_or(source),
|
||||
Some(source) => source,
|
||||
None => "tauri.conf.json".into(),
|
||||
};
|
||||
|
||||
if config_.tauri.bundle.identifier == "com.tauri.dev" {
|
||||
error!(
|
||||
"You must change the bundle identifier in `{} > tauri > bundle > identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.",
|
||||
bundle_identifier_source
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if config_
|
||||
.tauri
|
||||
.bundle
|
||||
.identifier
|
||||
.chars()
|
||||
.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))
|
||||
{
|
||||
error!(
|
||||
"The bundle identifier \"{}\" set in `{} > tauri > bundle > identifier`. The bundle identifier string must contain only alphanumeric characters (A–Z, a–z, and 0–9), hyphens (-), and periods (.).",
|
||||
config_.tauri.bundle.identifier,
|
||||
bundle_identifier_source
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut interface = AppInterface::new(config_, options.target.clone())?;
|
||||
let app_settings = interface.app_settings();
|
||||
let interface_options = options.clone().into();
|
||||
|
||||
if let Some(before_build) = config_.build.before_build_command.clone() {
|
||||
run_hook(
|
||||
"beforeBuildCommand",
|
||||
before_build,
|
||||
&interface,
|
||||
options.debug,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let AppUrl::Url(WindowUrl::App(web_asset_path)) = &config_.build.dist_dir {
|
||||
if !web_asset_path.exists() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Unable to find your web assets, did you forget to build your web app? Your distDir is set to \"{:?}\".",
|
||||
web_asset_path
|
||||
));
|
||||
}
|
||||
if web_asset_path.canonicalize()?.file_name() == Some(std::ffi::OsStr::new("src-tauri")) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"The configured distDir is the `src-tauri` folder.
|
||||
Please isolate your web assets on a separate folder and update `tauri.conf.json > build > distDir`.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut out_folders = Vec::new();
|
||||
for folder in &["node_modules", "src-tauri", "target"] {
|
||||
if web_asset_path.join(folder).is_dir() {
|
||||
out_folders.push(folder.to_string());
|
||||
}
|
||||
}
|
||||
if !out_folders.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"The configured distDir includes the `{:?}` {}. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > distDir`.",
|
||||
out_folders,
|
||||
if out_folders.len() == 1 { "folder" }else { "folders" }
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if options.runner.is_none() {
|
||||
options.runner = config_.build.runner.clone();
|
||||
}
|
||||
|
||||
if let Some(list) = options.features.as_mut() {
|
||||
list.extend(config_.build.features.clone().unwrap_or_default());
|
||||
}
|
||||
|
||||
let bin_path = app_settings.app_binary_path(&interface_options)?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
|
||||
@@ -317,6 +222,114 @@ pub fn command(mut options: Options) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup(options: &mut Options, mobile: bool) -> Result<AppInterface> {
|
||||
let (merge_config, merge_config_path) = if let Some(config) = &options.config {
|
||||
if config.starts_with('{') {
|
||||
(Some(config.to_string()), None)
|
||||
} else {
|
||||
(
|
||||
Some(
|
||||
std::fs::read_to_string(config).with_context(|| "failed to read custom configuration")?,
|
||||
),
|
||||
Some(config.clone()),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
options.config = merge_config;
|
||||
|
||||
let tauri_path = tauri_dir();
|
||||
set_current_dir(&tauri_path).with_context(|| "failed to change current working directory")?;
|
||||
|
||||
let config = get_config(options.config.as_deref())?;
|
||||
|
||||
let config_guard = config.lock().unwrap();
|
||||
let config_ = config_guard.as_ref().unwrap();
|
||||
|
||||
let interface = AppInterface::new(config_, options.target.clone())?;
|
||||
|
||||
let bundle_identifier_source = match config_.find_bundle_identifier_overwriter() {
|
||||
Some(source) if source == MERGE_CONFIG_EXTENSION_NAME => merge_config_path.unwrap_or(source),
|
||||
Some(source) => source,
|
||||
None => "tauri.conf.json".into(),
|
||||
};
|
||||
|
||||
if config_.tauri.bundle.identifier == "com.tauri.dev" {
|
||||
error!(
|
||||
"You must change the bundle identifier in `{} > tauri > bundle > identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.",
|
||||
bundle_identifier_source
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if config_
|
||||
.tauri
|
||||
.bundle
|
||||
.identifier
|
||||
.chars()
|
||||
.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))
|
||||
{
|
||||
error!(
|
||||
"The bundle identifier \"{}\" set in `{} > tauri > bundle > identifier`. The bundle identifier string must contain only alphanumeric characters (A–Z, a–z, and 0–9), hyphens (-), and periods (.).",
|
||||
config_.tauri.bundle.identifier,
|
||||
bundle_identifier_source
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Some(before_build) = config_.build.before_build_command.clone() {
|
||||
run_hook(
|
||||
"beforeBuildCommand",
|
||||
before_build,
|
||||
&interface,
|
||||
options.debug,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let AppUrl::Url(WindowUrl::App(web_asset_path)) = &config_.build.dist_dir {
|
||||
if !web_asset_path.exists() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Unable to find your web assets, did you forget to build your web app? Your distDir is set to \"{:?}\".",
|
||||
web_asset_path
|
||||
));
|
||||
}
|
||||
if web_asset_path.canonicalize()?.file_name() == Some(std::ffi::OsStr::new("src-tauri")) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"The configured distDir is the `src-tauri` folder.
|
||||
Please isolate your web assets on a separate folder and update `tauri.conf.json > build > distDir`.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut out_folders = Vec::new();
|
||||
for folder in &["node_modules", "src-tauri", "target"] {
|
||||
if web_asset_path.join(folder).is_dir() {
|
||||
out_folders.push(folder.to_string());
|
||||
}
|
||||
}
|
||||
if !out_folders.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"The configured distDir includes the `{:?}` {}. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > distDir`.",
|
||||
out_folders,
|
||||
if out_folders.len() == 1 { "folder" }else { "folders" }
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if options.runner.is_none() {
|
||||
options.runner = config_.build.runner.clone();
|
||||
}
|
||||
|
||||
options
|
||||
.features
|
||||
.get_or_insert(Vec::new())
|
||||
.extend(config_.build.features.clone().unwrap_or_default());
|
||||
interface.build_options(&mut options.features, mobile);
|
||||
|
||||
Ok(interface)
|
||||
}
|
||||
|
||||
fn run_hook(name: &str, hook: HookCommand, interface: &AppInterface, debug: bool) -> Result<()> {
|
||||
let (script, script_cwd) = match hook {
|
||||
HookCommand::Script(s) if s.is_empty() => (None, None),
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
command_env,
|
||||
config::{get as get_config, reload as reload_config, AppUrl, BeforeDevCommand, WindowUrl},
|
||||
},
|
||||
interface::{AppInterface, ExitReason, Interface},
|
||||
interface::{AppInterface, DevProcess, ExitReason, Interface},
|
||||
CommandExt, Result,
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
@@ -49,7 +49,7 @@ pub struct Options {
|
||||
pub features: Option<Vec<String>>,
|
||||
/// Exit on panic
|
||||
#[clap(short, long)]
|
||||
exit_on_panic: bool,
|
||||
pub exit_on_panic: bool,
|
||||
/// JSON string or path to JSON file to merge with tauri.conf.json
|
||||
#[clap(short, long)]
|
||||
pub config: Option<String>,
|
||||
@@ -74,6 +74,15 @@ pub fn command(options: Options) -> Result<()> {
|
||||
}
|
||||
|
||||
fn command_internal(mut options: Options) -> Result<()> {
|
||||
let mut interface = setup(&mut options)?;
|
||||
let exit_on_panic = options.exit_on_panic;
|
||||
let no_watch = options.no_watch;
|
||||
interface.dev(options.into(), move |status, reason| {
|
||||
on_app_exit(status, reason, exit_on_panic, no_watch)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn setup(options: &mut Options) -> Result<AppInterface> {
|
||||
let tauri_path = tauri_dir();
|
||||
options.config = if let Some(config) = &options.config {
|
||||
Some(if config.starts_with('{') {
|
||||
@@ -89,7 +98,7 @@ fn command_internal(mut options: Options) -> Result<()> {
|
||||
|
||||
let config = get_config(options.config.as_deref())?;
|
||||
|
||||
let mut interface = AppInterface::new(
|
||||
let interface = AppInterface::new(
|
||||
config.lock().unwrap().as_ref().unwrap(),
|
||||
options.target.clone(),
|
||||
)?;
|
||||
@@ -229,8 +238,8 @@ fn command_internal(mut options: Options) -> Result<()> {
|
||||
// or better separate the config passed from the cli internally and
|
||||
// config passed by the user in `--config` into to separate env vars
|
||||
// and the context merges, the user first, then the internal cli config
|
||||
if let Some(c) = options.config {
|
||||
let mut c: tauri_utils::config::Config = serde_json::from_str(&c)?;
|
||||
if let Some(c) = &options.config {
|
||||
let mut c: tauri_utils::config::Config = serde_json::from_str(c)?;
|
||||
c.build.dev_path = dev_path.clone();
|
||||
options.config = Some(serde_json::to_string(&c).unwrap());
|
||||
} else {
|
||||
@@ -295,14 +304,30 @@ fn command_internal(mut options: Options) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let exit_on_panic = options.exit_on_panic;
|
||||
let no_watch = options.no_watch;
|
||||
interface.dev(options.into(), move |status, reason| {
|
||||
on_dev_exit(status, reason, exit_on_panic, no_watch)
|
||||
})
|
||||
Ok(interface)
|
||||
}
|
||||
|
||||
fn on_dev_exit(status: ExitStatus, reason: ExitReason, exit_on_panic: bool, no_watch: bool) {
|
||||
pub fn wait_dev_process<
|
||||
C: DevProcess + Send + 'static,
|
||||
F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static,
|
||||
>(
|
||||
child: C,
|
||||
on_exit: F,
|
||||
) {
|
||||
std::thread::spawn(move || {
|
||||
let status = child.wait().expect("failed to wait on app");
|
||||
on_exit(
|
||||
status,
|
||||
if child.manually_killed_process() {
|
||||
ExitReason::TriggeredKill
|
||||
} else {
|
||||
ExitReason::NormalExit
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn on_app_exit(status: ExitStatus, reason: ExitReason, exit_on_panic: bool, no_watch: bool) {
|
||||
if no_watch
|
||||
|| (!matches!(reason, ExitReason::TriggeredKill)
|
||||
&& (exit_on_panic || matches!(reason, ExitReason::NormalExit)))
|
||||
@@ -332,7 +357,7 @@ fn check_for_updates() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn kill_before_dev_process() {
|
||||
pub fn kill_before_dev_process() {
|
||||
if let Some(child) = BEFORE_DEV.get() {
|
||||
let child = child.lock().unwrap();
|
||||
KILL_BEFORE_DEV_FLAG
|
||||
|
||||
363
tooling/cli/src/helpers/flock.rs
Normal file
363
tooling/cli/src/helpers/flock.rs
Normal file
@@ -0,0 +1,363 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// taken from https://github.com/rust-lang/cargo/blob/b0c9586f4cbf426914df47c65de38ea323772c74/src/cargo/util/flock.rs
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::fs::{create_dir_all, File, OpenOptions};
|
||||
use std::io;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::Result;
|
||||
use anyhow::Context as _;
|
||||
use sys::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileLock {
|
||||
f: Option<File>,
|
||||
path: PathBuf,
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum State {
|
||||
Unlocked,
|
||||
Shared,
|
||||
Exclusive,
|
||||
}
|
||||
|
||||
impl FileLock {
|
||||
/// Returns the underlying file handle of this lock.
|
||||
pub fn file(&self) -> &File {
|
||||
self.f.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Returns the underlying path that this lock points to.
|
||||
///
|
||||
/// Note that special care must be taken to ensure that the path is not
|
||||
/// referenced outside the lifetime of this lock.
|
||||
pub fn path(&self) -> &Path {
|
||||
assert_ne!(self.state, State::Unlocked);
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Returns the parent path containing this file
|
||||
pub fn parent(&self) -> &Path {
|
||||
assert_ne!(self.state, State::Unlocked);
|
||||
self.path.parent().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for FileLock {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.file().read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for FileLock {
|
||||
fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
|
||||
self.file().seek(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for FileLock {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.file().write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.file().flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileLock {
|
||||
fn drop(&mut self) {
|
||||
if self.state != State::Unlocked {
|
||||
if let Some(f) = self.f.take() {
|
||||
let _ = unlock(&f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens exclusive access to a file, returning the locked version of a
|
||||
/// file.
|
||||
///
|
||||
/// This function will create a file at `path` if it doesn't already exist
|
||||
/// (including intermediate directories), and then it will acquire an
|
||||
/// exclusive lock on `path`. If the process must block waiting for the
|
||||
/// lock, the `msg` is logged.
|
||||
///
|
||||
/// The returned file can be accessed to look at the path and also has
|
||||
/// read/write access to the underlying file.
|
||||
pub fn open_rw<P>(path: P, msg: &str) -> Result<FileLock>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
open(
|
||||
path.as_ref(),
|
||||
OpenOptions::new().read(true).write(true).create(true),
|
||||
State::Exclusive,
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
/// Opens shared access to a file, returning the locked version of a file.
|
||||
///
|
||||
/// This function will fail if `path` doesn't already exist, but if it does
|
||||
/// then it will acquire a shared lock on `path`. If the process must block
|
||||
/// waiting for the lock, the `msg` is logged.
|
||||
///
|
||||
/// The returned file can be accessed to look at the path and also has read
|
||||
/// access to the underlying file. Any writes to the file will return an
|
||||
/// error.
|
||||
pub fn open_ro<P>(path: P, msg: &str) -> Result<FileLock>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
open(
|
||||
path.as_ref(),
|
||||
OpenOptions::new().read(true),
|
||||
State::Shared,
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
fn open(path: &Path, opts: &OpenOptions, state: State, msg: &str) -> Result<FileLock> {
|
||||
// If we want an exclusive lock then if we fail because of NotFound it's
|
||||
// likely because an intermediate directory didn't exist, so try to
|
||||
// create the directory and then continue.
|
||||
let f = opts
|
||||
.open(path)
|
||||
.or_else(|e| {
|
||||
if e.kind() == io::ErrorKind::NotFound && state == State::Exclusive {
|
||||
create_dir_all(path.parent().unwrap())?;
|
||||
Ok(opts.open(path)?)
|
||||
} else {
|
||||
Err(anyhow::Error::from(e))
|
||||
}
|
||||
})
|
||||
.with_context(|| format!("failed to open: {}", path.display()))?;
|
||||
match state {
|
||||
State::Exclusive => {
|
||||
acquire(msg, path, &|| try_lock_exclusive(&f), &|| {
|
||||
lock_exclusive(&f)
|
||||
})?;
|
||||
}
|
||||
State::Shared => {
|
||||
acquire(msg, path, &|| try_lock_shared(&f), &|| lock_shared(&f))?;
|
||||
}
|
||||
State::Unlocked => {}
|
||||
}
|
||||
Ok(FileLock {
|
||||
f: Some(f),
|
||||
path: path.to_path_buf(),
|
||||
state,
|
||||
})
|
||||
}
|
||||
|
||||
/// Acquires a lock on a file in a "nice" manner.
|
||||
///
|
||||
/// Almost all long-running blocking actions in Cargo have a status message
|
||||
/// associated with them as we're not sure how long they'll take. Whenever a
|
||||
/// conflicted file lock happens, this is the case (we're not sure when the lock
|
||||
/// will be released).
|
||||
///
|
||||
/// This function will acquire the lock on a `path`, printing out a nice message
|
||||
/// to the console if we have to wait for it. It will first attempt to use `try`
|
||||
/// to acquire a lock on the crate, and in the case of contention it will emit a
|
||||
/// status message based on `msg` to `config`'s shell, and then use `block` to
|
||||
/// block waiting to acquire a lock.
|
||||
///
|
||||
/// Returns an error if the lock could not be acquired or if any error other
|
||||
/// than a contention error happens.
|
||||
fn acquire(
|
||||
msg: &str,
|
||||
path: &Path,
|
||||
lock_try: &dyn Fn() -> io::Result<()>,
|
||||
lock_block: &dyn Fn() -> io::Result<()>,
|
||||
) -> Result<()> {
|
||||
// File locking on Unix is currently implemented via `flock`, which is known
|
||||
// to be broken on NFS. We could in theory just ignore errors that happen on
|
||||
// NFS, but apparently the failure mode [1] for `flock` on NFS is **blocking
|
||||
// forever**, even if the "non-blocking" flag is passed!
|
||||
//
|
||||
// As a result, we just skip all file locks entirely on NFS mounts. That
|
||||
// should avoid calling any `flock` functions at all, and it wouldn't work
|
||||
// there anyway.
|
||||
//
|
||||
// [1]: https://github.com/rust-lang/cargo/issues/2615
|
||||
if is_on_nfs_mount(path) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match lock_try() {
|
||||
Ok(()) => return Ok(()),
|
||||
|
||||
// In addition to ignoring NFS which is commonly not working we also
|
||||
// just ignore locking on filesystems that look like they don't
|
||||
// implement file locking.
|
||||
Err(e) if error_unsupported(&e) => return Ok(()),
|
||||
|
||||
Err(e) => {
|
||||
if !error_contended(&e) {
|
||||
let e = anyhow::Error::from(e);
|
||||
let cx = format!("failed to lock file: {}", path.display());
|
||||
return Err(e.context(cx));
|
||||
}
|
||||
}
|
||||
}
|
||||
let msg = format!("waiting for file lock on {}", msg);
|
||||
log::info!(action = "Blocking"; "{}", &msg);
|
||||
|
||||
lock_block().with_context(|| format!("failed to lock file: {}", path.display()))?;
|
||||
return Ok(());
|
||||
|
||||
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
|
||||
fn is_on_nfs_mount(path: &Path) -> bool {
|
||||
use std::ffi::CString;
|
||||
use std::mem;
|
||||
use std::os::unix::prelude::*;
|
||||
|
||||
let path = match CString::new(path.as_os_str().as_bytes()) {
|
||||
Ok(path) => path,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let mut buf: libc::statfs = mem::zeroed();
|
||||
let r = libc::statfs(path.as_ptr(), &mut buf);
|
||||
|
||||
r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(not(target_os = "linux"), target_env = "musl"))]
|
||||
fn is_on_nfs_mount(_path: &Path) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod sys {
|
||||
use std::fs::File;
|
||||
use std::io::{Error, Result};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
pub(super) fn lock_shared(file: &File) -> Result<()> {
|
||||
flock(file, libc::LOCK_SH)
|
||||
}
|
||||
|
||||
pub(super) fn lock_exclusive(file: &File) -> Result<()> {
|
||||
flock(file, libc::LOCK_EX)
|
||||
}
|
||||
|
||||
pub(super) fn try_lock_shared(file: &File) -> Result<()> {
|
||||
flock(file, libc::LOCK_SH | libc::LOCK_NB)
|
||||
}
|
||||
|
||||
pub(super) fn try_lock_exclusive(file: &File) -> Result<()> {
|
||||
flock(file, libc::LOCK_EX | libc::LOCK_NB)
|
||||
}
|
||||
|
||||
pub(super) fn unlock(file: &File) -> Result<()> {
|
||||
flock(file, libc::LOCK_UN)
|
||||
}
|
||||
|
||||
pub(super) fn error_contended(err: &Error) -> bool {
|
||||
err.raw_os_error().map_or(false, |x| x == libc::EWOULDBLOCK)
|
||||
}
|
||||
|
||||
pub(super) fn error_unsupported(err: &Error) -> bool {
|
||||
match err.raw_os_error() {
|
||||
// Unfortunately, depending on the target, these may or may not be the same.
|
||||
// For targets in which they are the same, the duplicate pattern causes a warning.
|
||||
#[allow(unreachable_patterns)]
|
||||
Some(libc::ENOTSUP | libc::EOPNOTSUPP) => true,
|
||||
Some(libc::ENOSYS) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solaris"))]
|
||||
fn flock(file: &File, flag: libc::c_int) -> Result<()> {
|
||||
let ret = unsafe { libc::flock(file.as_raw_fd(), flag) };
|
||||
if ret < 0 {
|
||||
Err(Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "solaris")]
|
||||
fn flock(file: &File, flag: libc::c_int) -> Result<()> {
|
||||
// Solaris lacks flock(), so simply succeed with a no-op
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod sys {
|
||||
use std::fs::File;
|
||||
use std::io::{Error, Result};
|
||||
use std::mem;
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
|
||||
use winapi::shared::minwindef::DWORD;
|
||||
use winapi::shared::winerror::{ERROR_INVALID_FUNCTION, ERROR_LOCK_VIOLATION};
|
||||
use winapi::um::fileapi::{LockFileEx, UnlockFile};
|
||||
use winapi::um::minwinbase::{LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY};
|
||||
|
||||
pub(super) fn lock_shared(file: &File) -> Result<()> {
|
||||
lock_file(file, 0)
|
||||
}
|
||||
|
||||
pub(super) fn lock_exclusive(file: &File) -> Result<()> {
|
||||
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK)
|
||||
}
|
||||
|
||||
pub(super) fn try_lock_shared(file: &File) -> Result<()> {
|
||||
lock_file(file, LOCKFILE_FAIL_IMMEDIATELY)
|
||||
}
|
||||
|
||||
pub(super) fn try_lock_exclusive(file: &File) -> Result<()> {
|
||||
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY)
|
||||
}
|
||||
|
||||
pub(super) fn error_contended(err: &Error) -> bool {
|
||||
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()
|
||||
.map_or(false, |x| x == ERROR_INVALID_FUNCTION as i32)
|
||||
}
|
||||
|
||||
pub(super) fn unlock(file: &File) -> Result<()> {
|
||||
unsafe {
|
||||
let ret = UnlockFile(file.as_raw_handle(), 0, 0, !0, !0);
|
||||
if ret == 0 {
|
||||
Err(Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lock_file(file: &File, flags: DWORD) -> Result<()> {
|
||||
unsafe {
|
||||
let mut overlapped = mem::zeroed();
|
||||
let ret = LockFileEx(file.as_raw_handle(), flags, 0, !0, !0, &mut overlapped);
|
||||
if ret == 0 {
|
||||
Err(Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
pub mod app_paths;
|
||||
pub mod config;
|
||||
pub mod flock;
|
||||
pub mod framework;
|
||||
pub mod template;
|
||||
pub mod updater_signature;
|
||||
|
||||
@@ -3,22 +3,74 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs::{create_dir_all, File},
|
||||
io::Write,
|
||||
path::Path,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use handlebars::Handlebars;
|
||||
use handlebars::{to_json, Handlebars};
|
||||
use include_dir::Dir;
|
||||
use serde::Serialize;
|
||||
use serde_json::value::{Map, Value as JsonValue};
|
||||
|
||||
pub fn render<P: AsRef<Path>>(
|
||||
/// Map of template variable names and values.
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct JsonMap(Map<String, JsonValue>);
|
||||
|
||||
impl Default for JsonMap {
|
||||
fn default() -> Self {
|
||||
Self(Map::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonMap {
|
||||
pub fn insert(&mut self, name: &str, value: impl Serialize) {
|
||||
self.0.insert(name.to_owned(), to_json(value));
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &Map<String, JsonValue> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<P: AsRef<Path>, D: Serialize>(
|
||||
handlebars: &Handlebars<'_>,
|
||||
data: &BTreeMap<&str, serde_json::Value>,
|
||||
data: &D,
|
||||
dir: &Dir<'_>,
|
||||
out_dir: P,
|
||||
) -> crate::Result<()> {
|
||||
create_dir_all(out_dir.as_ref().join(dir.path()))?;
|
||||
let out_dir = out_dir.as_ref();
|
||||
let mut created_dirs = Vec::new();
|
||||
render_with_generator(
|
||||
handlebars,
|
||||
data,
|
||||
dir,
|
||||
out_dir,
|
||||
&mut |file_path: &PathBuf| {
|
||||
let path = out_dir.join(file_path);
|
||||
let parent = path.parent().unwrap().to_path_buf();
|
||||
if !created_dirs.contains(&parent) {
|
||||
create_dir_all(&parent)?;
|
||||
created_dirs.push(parent);
|
||||
}
|
||||
File::create(path).map(Some)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_with_generator<
|
||||
P: AsRef<Path>,
|
||||
D: Serialize,
|
||||
F: FnMut(&PathBuf) -> std::io::Result<Option<File>>,
|
||||
>(
|
||||
handlebars: &Handlebars<'_>,
|
||||
data: &D,
|
||||
dir: &Dir<'_>,
|
||||
out_dir: P,
|
||||
out_file_generator: &mut F,
|
||||
) -> crate::Result<()> {
|
||||
let out_dir = out_dir.as_ref();
|
||||
for file in dir.files() {
|
||||
let mut file_path = file.path().to_path_buf();
|
||||
// cargo for some reason ignores the /templates folder packaging when it has a Cargo.toml file inside
|
||||
@@ -28,17 +80,18 @@ pub fn render<P: AsRef<Path>>(
|
||||
file_path.set_extension("toml");
|
||||
}
|
||||
}
|
||||
let mut output_file = File::create(out_dir.as_ref().join(file_path))?;
|
||||
if let Some(utf8) = file.contents_utf8() {
|
||||
handlebars
|
||||
.render_template_to_write(utf8, &data, &mut output_file)
|
||||
.expect("Failed to render template");
|
||||
} else {
|
||||
output_file.write_all(file.contents())?;
|
||||
if let Some(mut output_file) = out_file_generator(&file_path)? {
|
||||
if let Some(utf8) = file.contents_utf8() {
|
||||
handlebars
|
||||
.render_template_to_write(utf8, &data, &mut output_file)
|
||||
.expect("Failed to render template");
|
||||
} else {
|
||||
output_file.write_all(file.contents())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
for dir in dir.dirs() {
|
||||
render(handlebars, data, dir, out_dir.as_ref())?;
|
||||
render_with_generator(handlebars, data, dir, out_dir, out_file_generator)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -636,7 +636,7 @@ pub fn command(_options: Options) -> Result<()> {
|
||||
InfoBlock::new("MSVC", "").display();
|
||||
for i in build_tools {
|
||||
indent(6);
|
||||
println!("{}", format!("{} {}", "-".cyan(), i));
|
||||
println!("{} {}", "-".cyan(), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -885,6 +885,29 @@ pub fn command(_options: Options) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if tauri_dir.is_some() {
|
||||
let p = tauri_dir.as_ref().unwrap();
|
||||
if p.join("gen/apple").exists() {
|
||||
let teams = cargo_mobile::apple::teams::find_development_teams().unwrap_or_default();
|
||||
Section("iOS").display();
|
||||
InfoBlock::new(
|
||||
"Teams",
|
||||
if teams.is_empty() {
|
||||
"None".red().to_string()
|
||||
} else {
|
||||
teams
|
||||
.iter()
|
||||
.map(|t| format!("{} (ID: {})", t.name, t.id))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
.to_string()
|
||||
},
|
||||
)
|
||||
.display();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,15 @@ use std::{
|
||||
use crate::helpers::config::Config;
|
||||
use tauri_bundler::bundle::{PackageType, Settings, SettingsBuilder};
|
||||
|
||||
pub use rust::{Options, Rust as AppInterface};
|
||||
pub use rust::{manifest, MobileOptions, Options, Rust as AppInterface};
|
||||
|
||||
pub trait DevProcess {
|
||||
fn kill(&self) -> std::io::Result<()>;
|
||||
fn try_wait(&self) -> std::io::Result<Option<ExitStatus>>;
|
||||
fn wait(&self) -> std::io::Result<ExitStatus>;
|
||||
fn manually_killed_process(&self) -> bool;
|
||||
fn is_building_app(&self) -> bool;
|
||||
}
|
||||
|
||||
pub trait AppSettings {
|
||||
fn get_package_settings(&self) -> tauri_bundler::PackageSettings;
|
||||
@@ -85,4 +93,9 @@ pub trait Interface: Sized {
|
||||
options: Options,
|
||||
on_exit: F,
|
||||
) -> crate::Result<()>;
|
||||
fn mobile_dev<R: Fn(MobileOptions) -> crate::Result<Box<dyn DevProcess>>>(
|
||||
&mut self,
|
||||
options: MobileOptions,
|
||||
runner: R,
|
||||
) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
@@ -10,11 +10,7 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, ExitStatus},
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::sync_channel,
|
||||
Arc, Mutex,
|
||||
},
|
||||
sync::{mpsc::sync_channel, Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
@@ -26,14 +22,13 @@ use log::{debug, error, info};
|
||||
use notify::RecursiveMode;
|
||||
use notify_debouncer_mini::new_debouncer;
|
||||
use serde::Deserialize;
|
||||
use shared_child::SharedChild;
|
||||
use tauri_bundler::{
|
||||
AppCategory, BundleBinary, BundleSettings, DebianSettings, MacOsSettings, PackageSettings,
|
||||
UpdaterSettings, WindowsSettings,
|
||||
};
|
||||
use tauri_utils::config::parse::is_configuration_file;
|
||||
|
||||
use super::{AppSettings, ExitReason, Interface};
|
||||
use super::{AppSettings, DevProcess, ExitReason, Interface};
|
||||
use crate::helpers::{
|
||||
app_paths::{app_dir, tauri_dir},
|
||||
config::{reload as reload_config, wix_settings, Config},
|
||||
@@ -41,11 +36,11 @@ use crate::helpers::{
|
||||
|
||||
mod cargo_config;
|
||||
mod desktop;
|
||||
mod manifest;
|
||||
pub mod manifest;
|
||||
use cargo_config::Config as CargoConfig;
|
||||
use manifest::{rewrite_manifest, Manifest};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Options {
|
||||
pub runner: Option<String>,
|
||||
pub debug: bool,
|
||||
@@ -84,30 +79,13 @@ impl From<crate::dev::Options> for Options {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DevChild {
|
||||
manually_killed_app: Arc<AtomicBool>,
|
||||
build_child: Arc<SharedChild>,
|
||||
app_child: Arc<Mutex<Option<Arc<SharedChild>>>>,
|
||||
}
|
||||
|
||||
impl DevChild {
|
||||
fn kill(&self) -> std::io::Result<()> {
|
||||
if let Some(child) = &*self.app_child.lock().unwrap() {
|
||||
child.kill()?;
|
||||
} else {
|
||||
self.build_child.kill()?;
|
||||
}
|
||||
self.manually_killed_app.store(true, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
|
||||
if let Some(child) = &*self.app_child.lock().unwrap() {
|
||||
child.try_wait()
|
||||
} else {
|
||||
self.build_child.try_wait()
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MobileOptions {
|
||||
pub debug: bool,
|
||||
pub features: Option<Vec<String>>,
|
||||
pub args: Vec<String>,
|
||||
pub config: Option<String>,
|
||||
pub no_watch: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -156,8 +134,10 @@ impl Interface for Rust {
|
||||
std::env::set_var("MACOSX_DEPLOYMENT_TARGET", minimum_system_version);
|
||||
}
|
||||
|
||||
let app_settings = RustAppSettings::new(config, manifest, target)?;
|
||||
|
||||
Ok(Self {
|
||||
app_settings: RustAppSettings::new(config, manifest, target)?,
|
||||
app_settings,
|
||||
config_features: config.build.features.clone().unwrap_or_default(),
|
||||
product_name: config.package.product_name.clone(),
|
||||
available_targets: None,
|
||||
@@ -168,11 +148,7 @@ impl Interface for Rust {
|
||||
&self.app_settings
|
||||
}
|
||||
|
||||
fn build(&mut self, mut options: Options) -> crate::Result<()> {
|
||||
options
|
||||
.features
|
||||
.get_or_insert(Vec::new())
|
||||
.push("custom-protocol".into());
|
||||
fn build(&mut self, options: Options) -> crate::Result<()> {
|
||||
desktop::build(
|
||||
options,
|
||||
&self.app_settings,
|
||||
@@ -185,28 +161,62 @@ impl Interface for Rust {
|
||||
|
||||
fn dev<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
options: Options,
|
||||
mut options: Options,
|
||||
on_exit: F,
|
||||
) -> crate::Result<()> {
|
||||
let on_exit = Arc::new(on_exit);
|
||||
|
||||
let on_exit_ = on_exit.clone();
|
||||
let mut run_args = Vec::new();
|
||||
dev_options(
|
||||
false,
|
||||
&mut options.args,
|
||||
&mut run_args,
|
||||
&mut options.features,
|
||||
&self.app_settings,
|
||||
);
|
||||
|
||||
if options.no_watch {
|
||||
let (tx, rx) = sync_channel(1);
|
||||
self.run_dev(options, move |status, reason| {
|
||||
self.run_dev(options, run_args, move |status, reason| {
|
||||
tx.send(()).unwrap();
|
||||
on_exit_(status, reason)
|
||||
on_exit(status, reason)
|
||||
})?;
|
||||
|
||||
rx.recv().unwrap();
|
||||
Ok(())
|
||||
} else {
|
||||
let child = self.run_dev(options.clone(), move |status, reason| {
|
||||
on_exit_(status, reason)
|
||||
})?;
|
||||
let config = options.config.clone();
|
||||
let run = Arc::new(|rust: &mut Rust| {
|
||||
let on_exit = on_exit.clone();
|
||||
rust.run_dev(options.clone(), run_args.clone(), move |status, reason| {
|
||||
on_exit(status, reason)
|
||||
})
|
||||
});
|
||||
self.run_dev_watcher(config, run)
|
||||
}
|
||||
}
|
||||
|
||||
self.run_dev_watcher(child, options, on_exit)
|
||||
fn mobile_dev<R: Fn(MobileOptions) -> crate::Result<Box<dyn DevProcess>>>(
|
||||
&mut self,
|
||||
mut options: MobileOptions,
|
||||
runner: R,
|
||||
) -> crate::Result<()> {
|
||||
let mut run_args = Vec::new();
|
||||
dev_options(
|
||||
true,
|
||||
&mut options.args,
|
||||
&mut run_args,
|
||||
&mut options.features,
|
||||
&self.app_settings,
|
||||
);
|
||||
|
||||
if options.no_watch {
|
||||
runner(options)?;
|
||||
Ok(())
|
||||
} else {
|
||||
let config = options.config.clone();
|
||||
let run = Arc::new(|_rust: &mut Rust| runner(options.clone()));
|
||||
self.run_dev_watcher(config, run)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,51 +344,89 @@ fn lookup<F: FnMut(FileType, PathBuf)>(dir: &Path, mut f: F) {
|
||||
}
|
||||
}
|
||||
|
||||
fn shared_options(
|
||||
mobile: bool,
|
||||
features: &mut Option<Vec<String>>,
|
||||
app_settings: &RustAppSettings,
|
||||
) {
|
||||
if mobile {
|
||||
let all_features = app_settings
|
||||
.manifest
|
||||
.all_enabled_features(if let Some(f) = features { f } else { &[] });
|
||||
if all_features.contains(&"tauri/default-tls".into())
|
||||
|| all_features.contains(&"tauri/reqwest-default-tls".into())
|
||||
{
|
||||
if all_features.contains(&"tauri/reqwest-client".into()) {
|
||||
features
|
||||
.get_or_insert(Vec::new())
|
||||
.push("tauri/reqwest-native-tls-vendored".into());
|
||||
} else {
|
||||
features
|
||||
.get_or_insert(Vec::new())
|
||||
.push("tauri/native-tls-vendored".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dev_options(
|
||||
mobile: bool,
|
||||
args: &mut Vec<String>,
|
||||
run_args: &mut Vec<String>,
|
||||
features: &mut Option<Vec<String>>,
|
||||
app_settings: &RustAppSettings,
|
||||
) {
|
||||
let mut dev_args = Vec::new();
|
||||
let mut reached_run_args = false;
|
||||
for arg in args.clone() {
|
||||
if reached_run_args {
|
||||
run_args.push(arg);
|
||||
} else if arg == "--" {
|
||||
reached_run_args = true;
|
||||
} else {
|
||||
dev_args.push(arg);
|
||||
}
|
||||
}
|
||||
*args = dev_args;
|
||||
|
||||
shared_options(mobile, features, app_settings);
|
||||
|
||||
if !args.contains(&"--no-default-features".into()) {
|
||||
let manifest_features = app_settings.manifest.features();
|
||||
let enable_features: Vec<String> = manifest_features
|
||||
.get("default")
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter(|feature| {
|
||||
if let Some(manifest_feature) = manifest_features.get(feature) {
|
||||
!manifest_feature.contains(&"tauri/custom-protocol".into())
|
||||
} else {
|
||||
feature != "tauri/custom-protocol"
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
args.push("--no-default-features".into());
|
||||
if !enable_features.is_empty() {
|
||||
features.get_or_insert(Vec::new()).extend(enable_features);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rust {
|
||||
pub fn build_options(&self, features: &mut Option<Vec<String>>, mobile: bool) {
|
||||
features
|
||||
.get_or_insert(Vec::new())
|
||||
.push("custom-protocol".into());
|
||||
shared_options(mobile, features, &self.app_settings);
|
||||
}
|
||||
|
||||
fn run_dev<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
mut options: Options,
|
||||
options: Options,
|
||||
run_args: Vec<String>,
|
||||
on_exit: F,
|
||||
) -> crate::Result<DevChild> {
|
||||
let mut args = Vec::new();
|
||||
let mut run_args = Vec::new();
|
||||
let mut reached_run_args = false;
|
||||
for arg in options.args.clone() {
|
||||
if reached_run_args {
|
||||
run_args.push(arg);
|
||||
} else if arg == "--" {
|
||||
reached_run_args = true;
|
||||
} else {
|
||||
args.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
if !args.contains(&"--no-default-features".into()) {
|
||||
let manifest_features = self.app_settings.manifest.features();
|
||||
let enable_features: Vec<String> = manifest_features
|
||||
.get("default")
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter(|feature| {
|
||||
if let Some(manifest_feature) = manifest_features.get(feature) {
|
||||
!manifest_feature.contains(&"tauri/custom-protocol".into())
|
||||
} else {
|
||||
feature != "tauri/custom-protocol"
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
args.push("--no-default-features".into());
|
||||
if !enable_features.is_empty() {
|
||||
options
|
||||
.features
|
||||
.get_or_insert(Vec::new())
|
||||
.extend(enable_features);
|
||||
}
|
||||
}
|
||||
|
||||
options.args = args;
|
||||
|
||||
) -> crate::Result<Box<dyn DevProcess>> {
|
||||
desktop::run_dev(
|
||||
options,
|
||||
run_args,
|
||||
@@ -388,14 +436,16 @@ impl Rust {
|
||||
self.product_name.clone(),
|
||||
on_exit,
|
||||
)
|
||||
.map(|c| Box::new(c) as Box<dyn DevProcess>)
|
||||
}
|
||||
|
||||
fn run_dev_watcher<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
|
||||
fn run_dev_watcher<F: Fn(&mut Rust) -> crate::Result<Box<dyn DevProcess>>>(
|
||||
&mut self,
|
||||
child: DevChild,
|
||||
options: Options,
|
||||
on_exit: Arc<F>,
|
||||
config: Option<String>,
|
||||
run: Arc<F>,
|
||||
) -> crate::Result<()> {
|
||||
let child = run(self)?;
|
||||
|
||||
let process = Arc::new(Mutex::new(child));
|
||||
let (tx, rx) = sync_channel(1);
|
||||
let app_path = app_dir();
|
||||
@@ -452,12 +502,11 @@ impl Rust {
|
||||
loop {
|
||||
if let Ok(events) = rx.recv() {
|
||||
for event in events {
|
||||
let on_exit = on_exit.clone();
|
||||
let event_path = event.path;
|
||||
|
||||
if !ignore_matcher.is_ignore(&event_path, event_path.is_dir()) {
|
||||
if is_configuration_file(&event_path) {
|
||||
match reload_config(options.config.as_deref()) {
|
||||
match reload_config(config.as_deref()) {
|
||||
Ok(config) => {
|
||||
info!("Tauri configuration changed. Rewriting manifest...");
|
||||
self.app_settings.manifest =
|
||||
@@ -465,8 +514,7 @@ impl Rust {
|
||||
}
|
||||
Err(err) => {
|
||||
let p = process.lock().unwrap();
|
||||
let is_building_app = p.app_child.lock().unwrap().is_none();
|
||||
if is_building_app {
|
||||
if p.is_building_app() {
|
||||
p.kill().with_context(|| "failed to kill app process")?;
|
||||
}
|
||||
error!("{}", err);
|
||||
@@ -491,9 +539,7 @@ impl Rust {
|
||||
break;
|
||||
}
|
||||
}
|
||||
*p = self.run_dev(options.clone(), move |status, reason| {
|
||||
on_exit(status, reason)
|
||||
})?;
|
||||
*p = run(self)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{AppSettings, DevChild, ExitReason, Options, RustAppSettings, Target};
|
||||
use super::{AppSettings, DevProcess, ExitReason, Options, RustAppSettings, Target};
|
||||
use crate::CommandExt;
|
||||
|
||||
use anyhow::Context;
|
||||
@@ -16,6 +16,52 @@ use std::{
|
||||
},
|
||||
};
|
||||
|
||||
pub struct DevChild {
|
||||
manually_killed_app: Arc<AtomicBool>,
|
||||
build_child: Option<Arc<SharedChild>>,
|
||||
app_child: Arc<Mutex<Option<Arc<SharedChild>>>>,
|
||||
}
|
||||
|
||||
impl DevProcess for DevChild {
|
||||
fn kill(&self) -> std::io::Result<()> {
|
||||
if let Some(child) = &*self.app_child.lock().unwrap() {
|
||||
child.kill()?;
|
||||
} else if let Some(child) = &self.build_child {
|
||||
child.kill()?;
|
||||
}
|
||||
self.manually_killed_app.store(true, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
|
||||
if let Some(child) = &*self.app_child.lock().unwrap() {
|
||||
child.try_wait()
|
||||
} else if let Some(child) = &self.build_child {
|
||||
child.try_wait()
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn wait(&self) -> std::io::Result<ExitStatus> {
|
||||
if let Some(child) = &*self.app_child.lock().unwrap() {
|
||||
child.wait()
|
||||
} else if let Some(child) = &self.build_child {
|
||||
child.wait()
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn manually_killed_process(&self) -> bool {
|
||||
self.manually_killed_app.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn is_building_app(&self) -> bool {
|
||||
self.app_child.lock().unwrap().is_none()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_dev<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
|
||||
options: Options,
|
||||
run_args: Vec<String>,
|
||||
@@ -24,7 +70,7 @@ pub fn run_dev<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
|
||||
app_settings: &RustAppSettings,
|
||||
product_name: Option<String>,
|
||||
on_exit: F,
|
||||
) -> crate::Result<DevChild> {
|
||||
) -> crate::Result<impl DevProcess> {
|
||||
let bin_path = app_settings.app_binary_path(&options)?;
|
||||
|
||||
let manually_killed_app = Arc::new(AtomicBool::default());
|
||||
@@ -45,18 +91,14 @@ pub fn run_dev<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
|
||||
app.stderr(os_pipe::dup_stderr().unwrap());
|
||||
app.args(run_args);
|
||||
let app_child = Arc::new(SharedChild::spawn(&mut app).unwrap());
|
||||
let app_child_t = app_child.clone();
|
||||
std::thread::spawn(move || {
|
||||
let status = app_child_t.wait().expect("failed to wait on app");
|
||||
on_exit(
|
||||
status,
|
||||
if manually_killed_app_.load(Ordering::Relaxed) {
|
||||
ExitReason::TriggeredKill
|
||||
} else {
|
||||
ExitReason::NormalExit
|
||||
},
|
||||
);
|
||||
});
|
||||
crate::dev::wait_dev_process(
|
||||
DevChild {
|
||||
manually_killed_app: manually_killed_app_,
|
||||
build_child: None,
|
||||
app_child: Arc::new(Mutex::new(Some(app_child.clone()))),
|
||||
},
|
||||
on_exit,
|
||||
);
|
||||
|
||||
app_child_.lock().unwrap().replace(app_child);
|
||||
} else {
|
||||
@@ -74,7 +116,7 @@ pub fn run_dev<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
|
||||
|
||||
Ok(DevChild {
|
||||
manually_killed_app,
|
||||
build_child,
|
||||
build_child: Some(build_child),
|
||||
app_child,
|
||||
})
|
||||
}
|
||||
@@ -341,7 +383,7 @@ fn rename_app(bin_path: &Path, product_name: Option<&str>) -> crate::Result<Path
|
||||
let product_path = bin_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(&product_name)
|
||||
.join(product_name)
|
||||
.with_extension(bin_path.extension().unwrap_or_default());
|
||||
|
||||
rename(bin_path, &product_path).with_context(|| {
|
||||
|
||||
@@ -82,7 +82,7 @@ fn get_enabled_features(list: &HashMap<String, Vec<String>>, feature: &str) -> V
|
||||
f
|
||||
}
|
||||
|
||||
fn read_manifest(manifest_path: &Path) -> crate::Result<Document> {
|
||||
pub fn read_manifest(manifest_path: &Path) -> crate::Result<Document> {
|
||||
let mut manifest_str = String::new();
|
||||
|
||||
let mut manifest_file = File::open(manifest_path)
|
||||
|
||||
@@ -11,10 +11,11 @@ mod icon;
|
||||
mod info;
|
||||
mod init;
|
||||
mod interface;
|
||||
mod mobile;
|
||||
mod plugin;
|
||||
mod signer;
|
||||
|
||||
use clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand};
|
||||
use clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum};
|
||||
use env_logger::fmt::Color;
|
||||
use env_logger::Builder;
|
||||
use log::{debug, log_enabled, Level};
|
||||
@@ -23,9 +24,33 @@ use std::io::{BufReader, Write};
|
||||
use std::process::{exit, Command, ExitStatus, Output, Stdio};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fmt::Display,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
pub enum RunMode {
|
||||
Desktop,
|
||||
#[cfg(target_os = "macos")]
|
||||
Ios,
|
||||
Android,
|
||||
}
|
||||
|
||||
impl Display for RunMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Desktop => "desktop",
|
||||
#[cfg(target_os = "macos")]
|
||||
Self::Ios => "iOS",
|
||||
Self::Android => "android",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct VersionMetadata {
|
||||
tauri: String,
|
||||
@@ -68,6 +93,9 @@ enum Commands {
|
||||
Init(init::Options),
|
||||
Plugin(plugin::Cli),
|
||||
Signer(signer::Cli),
|
||||
Android(mobile::android::Cli),
|
||||
#[cfg(target_os = "macos")]
|
||||
Ios(mobile::ios::Cli),
|
||||
}
|
||||
|
||||
fn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error {
|
||||
@@ -167,6 +195,9 @@ where
|
||||
Commands::Init(options) => init::command(options)?,
|
||||
Commands::Plugin(cli) => plugin::command(cli)?,
|
||||
Commands::Signer(cli) => signer::command(cli)?,
|
||||
Commands::Android(c) => mobile::android::command(c, cli.verbose)?,
|
||||
#[cfg(target_os = "macos")]
|
||||
Commands::Ios(c) => mobile::ios::command(c, cli.verbose)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
274
tooling/cli/src/mobile/android.rs
Normal file
274
tooling/cli/src/mobile/android.rs
Normal file
@@ -0,0 +1,274 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use cargo_mobile::{
|
||||
android::{
|
||||
adb,
|
||||
config::{Config as AndroidConfig, Metadata as AndroidMetadata, Raw as RawAndroidConfig},
|
||||
device::Device,
|
||||
emulator,
|
||||
env::Env,
|
||||
target::Target,
|
||||
},
|
||||
config::app::App,
|
||||
opts::NoiseLevel,
|
||||
os,
|
||||
util::prompt,
|
||||
};
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::{
|
||||
env::set_var,
|
||||
thread::{sleep, spawn},
|
||||
time::Duration,
|
||||
};
|
||||
use sublime_fuzzy::best_match;
|
||||
|
||||
use super::{
|
||||
ensure_init, get_app,
|
||||
init::{command as init_command, init_dot_cargo},
|
||||
log_finished, read_options, CliOptions, Target as MobileTarget, MIN_DEVICE_MATCH_SCORE,
|
||||
};
|
||||
use crate::{
|
||||
helpers::config::{get as get_tauri_config, Config as TauriConfig},
|
||||
Result,
|
||||
};
|
||||
|
||||
mod android_studio_script;
|
||||
mod build;
|
||||
mod dev;
|
||||
mod open;
|
||||
pub(crate) mod project;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(
|
||||
author,
|
||||
version,
|
||||
about = "Android commands",
|
||||
subcommand_required(true),
|
||||
arg_required_else_help(true)
|
||||
)]
|
||||
pub struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(about = "Initializes a Tauri Android project")]
|
||||
pub struct InitOptions {
|
||||
/// Skip prompting for values
|
||||
#[clap(long)]
|
||||
ci: bool,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
Init(InitOptions),
|
||||
/// Open project in Android Studio
|
||||
Open,
|
||||
Dev(dev::Options),
|
||||
Build(build::Options),
|
||||
#[clap(hide(true))]
|
||||
AndroidStudioScript(android_studio_script::Options),
|
||||
}
|
||||
|
||||
pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
|
||||
let noise_level = NoiseLevel::from_occurrences(verbosity as u64);
|
||||
match cli.command {
|
||||
Commands::Init(options) => init_command(MobileTarget::Android, options.ci, false)?,
|
||||
Commands::Open => open::command()?,
|
||||
Commands::Dev(options) => dev::command(options, noise_level)?,
|
||||
Commands::Build(options) => build::command(options, noise_level)?,
|
||||
Commands::AndroidStudioScript(options) => android_studio_script::command(options)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_config(
|
||||
app: Option<App>,
|
||||
config: &TauriConfig,
|
||||
cli_options: &CliOptions,
|
||||
) -> (App, AndroidConfig, AndroidMetadata) {
|
||||
let app = app.unwrap_or_else(|| get_app(config));
|
||||
let android_options = cli_options.clone();
|
||||
|
||||
let raw = RawAndroidConfig {
|
||||
features: android_options.features.clone(),
|
||||
logcat_filter_specs: vec!["RustStdoutStderr".into()],
|
||||
..Default::default()
|
||||
};
|
||||
let config = AndroidConfig::from_raw(app.clone(), Some(raw)).unwrap();
|
||||
|
||||
let metadata = AndroidMetadata {
|
||||
supported: true,
|
||||
cargo_args: Some(android_options.args),
|
||||
features: android_options.features,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
set_var("WRY_ANDROID_REVERSED_DOMAIN", app.reverse_domain());
|
||||
set_var("WRY_ANDROID_APP_NAME_SNAKE_CASE", app.name());
|
||||
set_var(
|
||||
"WRY_ANDROID_KOTLIN_FILES_OUT_DIR",
|
||||
config
|
||||
.project_dir()
|
||||
.join("app/src/main")
|
||||
.join(format!(
|
||||
"java/{}/{}",
|
||||
app.reverse_domain().replace('.', "/"),
|
||||
app.name()
|
||||
))
|
||||
.join("generated"),
|
||||
);
|
||||
|
||||
(app, config, metadata)
|
||||
}
|
||||
|
||||
fn with_config<T>(
|
||||
cli_options: Option<CliOptions>,
|
||||
f: impl FnOnce(&App, &AndroidConfig, &AndroidMetadata, CliOptions) -> Result<T>,
|
||||
) -> Result<T> {
|
||||
let (app, config, metadata, cli_options) = {
|
||||
let tauri_config = get_tauri_config(None)?;
|
||||
let tauri_config_guard = tauri_config.lock().unwrap();
|
||||
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
|
||||
let cli_options = cli_options.unwrap_or_else(read_options);
|
||||
let (app, config, metadata) = get_config(None, tauri_config_, &cli_options);
|
||||
(app, config, metadata, cli_options)
|
||||
};
|
||||
f(&app, &config, &metadata, cli_options)
|
||||
}
|
||||
|
||||
fn env() -> Result<Env> {
|
||||
let env = super::env()?;
|
||||
cargo_mobile::android::env::Env::from_env(env).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn delete_codegen_vars() {
|
||||
for (k, _) in std::env::vars() {
|
||||
if k.starts_with("WRY_") && (k.ends_with("CLASS_EXTENSION") || k.ends_with("CLASS_INIT")) {
|
||||
std::env::remove_var(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn adb_device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
|
||||
let device_list = adb::device_list(env)
|
||||
.map_err(|cause| anyhow::anyhow!("Failed to detect connected Android devices: {cause}"))?;
|
||||
if !device_list.is_empty() {
|
||||
let device = if let Some(t) = target {
|
||||
let (device, score) = device_list
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|d| {
|
||||
let score = best_match(t, d.name()).map_or(0, |m| m.score());
|
||||
(d, score)
|
||||
})
|
||||
.max_by_key(|(_, score)| *score)
|
||||
// we already checked the list is not empty
|
||||
.unwrap();
|
||||
if score > MIN_DEVICE_MATCH_SCORE {
|
||||
device
|
||||
} else {
|
||||
anyhow::bail!("Could not find an Android device matching {t}")
|
||||
}
|
||||
} else if device_list.len() > 1 {
|
||||
let index = prompt::list(
|
||||
concat!("Detected ", "Android", " devices"),
|
||||
device_list.iter(),
|
||||
"device",
|
||||
None,
|
||||
"Device",
|
||||
)
|
||||
.map_err(|cause| anyhow::anyhow!("Failed to prompt for Android device: {cause}"))?;
|
||||
device_list.into_iter().nth(index).unwrap()
|
||||
} else {
|
||||
device_list.into_iter().next().unwrap()
|
||||
};
|
||||
println!(
|
||||
"Detected connected device: {} with target {:?}",
|
||||
device,
|
||||
device.target().triple,
|
||||
);
|
||||
Ok(device)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("No connected Android devices detected"))
|
||||
}
|
||||
}
|
||||
|
||||
fn emulator_prompt(env: &'_ Env, target: Option<&str>) -> Result<emulator::Emulator> {
|
||||
let emulator_list = emulator::avd_list(env).unwrap_or_default();
|
||||
if !emulator_list.is_empty() {
|
||||
let emulator = if let Some(t) = target {
|
||||
let (device, score) = emulator_list
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|d| {
|
||||
let score = best_match(t, d.name()).map_or(0, |m| m.score());
|
||||
(d, score)
|
||||
})
|
||||
.max_by_key(|(_, score)| *score)
|
||||
// we already checked the list is not empty
|
||||
.unwrap();
|
||||
if score > MIN_DEVICE_MATCH_SCORE {
|
||||
device
|
||||
} else {
|
||||
anyhow::bail!("Could not find an Android Emulator matching {t}")
|
||||
}
|
||||
} else if emulator_list.len() > 1 {
|
||||
let index = prompt::list(
|
||||
concat!("Detected ", "Android", " emulators"),
|
||||
emulator_list.iter(),
|
||||
"emulator",
|
||||
None,
|
||||
"Emulator",
|
||||
)
|
||||
.map_err(|cause| anyhow::anyhow!("Failed to prompt for Android Emulator device: {cause}"))?;
|
||||
emulator_list.into_iter().nth(index).unwrap()
|
||||
} else {
|
||||
emulator_list.into_iter().next().unwrap()
|
||||
};
|
||||
|
||||
let handle = emulator.start(env)?;
|
||||
spawn(move || {
|
||||
let _ = handle.wait();
|
||||
});
|
||||
|
||||
Ok(emulator)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("No available Android Emulator detected"))
|
||||
}
|
||||
}
|
||||
|
||||
fn device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
|
||||
if let Ok(device) = adb_device_prompt(env, target) {
|
||||
Ok(device)
|
||||
} else {
|
||||
let emulator = emulator_prompt(env, target)?;
|
||||
let handle = emulator.start(env)?;
|
||||
spawn(move || {
|
||||
let _ = handle.wait();
|
||||
});
|
||||
loop {
|
||||
sleep(Duration::from_secs(2));
|
||||
if let Ok(device) = adb_device_prompt(env, Some(emulator.name())) {
|
||||
return Ok(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
|
||||
device_prompt(env, None).map(|device| device.target()).ok()
|
||||
}
|
||||
|
||||
fn open_and_wait(config: &AndroidConfig, env: &Env) -> ! {
|
||||
log::info!("Opening Android Studio");
|
||||
if let Err(e) = os::open_file_with("Android Studio", config.project_dir(), &env.base) {
|
||||
log::error!("{}", e);
|
||||
}
|
||||
loop {
|
||||
sleep(Duration::from_secs(24 * 60 * 60));
|
||||
}
|
||||
}
|
||||
59
tooling/cli/src/mobile/android/android_studio_script.rs
Normal file
59
tooling/cli/src/mobile/android/android_studio_script.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use super::{detect_target_ok, ensure_init, env, with_config, MobileTarget};
|
||||
use crate::Result;
|
||||
use clap::{ArgAction, Parser};
|
||||
|
||||
use cargo_mobile::{
|
||||
android::target::Target,
|
||||
opts::Profile,
|
||||
target::{call_for_targets_with_fallback, TargetTrait},
|
||||
};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Options {
|
||||
/// Targets to build.
|
||||
#[clap(
|
||||
short,
|
||||
long = "target",
|
||||
action = ArgAction::Append,
|
||||
num_args(0..),
|
||||
default_value = Target::DEFAULT_KEY,
|
||||
value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))
|
||||
)]
|
||||
targets: Option<Vec<String>>,
|
||||
/// Builds with the release flag
|
||||
#[clap(short, long)]
|
||||
release: bool,
|
||||
}
|
||||
|
||||
pub fn command(options: Options) -> Result<()> {
|
||||
let profile = if options.release {
|
||||
Profile::Release
|
||||
} else {
|
||||
Profile::Debug
|
||||
};
|
||||
|
||||
with_config(None, |_app, config, metadata, cli_options| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Android)?;
|
||||
|
||||
let env = env()?;
|
||||
|
||||
call_for_targets_with_fallback(
|
||||
options.targets.unwrap_or_default().iter(),
|
||||
&detect_target_ok,
|
||||
&env,
|
||||
|target: &Target| {
|
||||
target
|
||||
.build(
|
||||
config,
|
||||
metadata,
|
||||
&env,
|
||||
cli_options.noise_level,
|
||||
true,
|
||||
profile,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
},
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!(e.to_string()))?
|
||||
})
|
||||
}
|
||||
194
tooling/cli/src/mobile/android/build.rs
Normal file
194
tooling/cli/src/mobile/android/build.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
use super::{
|
||||
delete_codegen_vars, ensure_init, env, init_dot_cargo, log_finished, open_and_wait, with_config,
|
||||
MobileTarget,
|
||||
};
|
||||
use crate::{
|
||||
helpers::flock,
|
||||
interface::{AppSettings, Interface, Options as InterfaceOptions},
|
||||
mobile::{write_options, CliOptions},
|
||||
Result,
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
|
||||
use cargo_mobile::{
|
||||
android::{aab, apk, config::Config as AndroidConfig, env::Env, target::Target},
|
||||
opts::{NoiseLevel, Profile},
|
||||
target::TargetTrait,
|
||||
};
|
||||
|
||||
use std::env::set_var;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(about = "Android build")]
|
||||
pub struct Options {
|
||||
/// Builds with the debug flag
|
||||
#[clap(short, long)]
|
||||
pub debug: bool,
|
||||
/// Which targets to build (all by default).
|
||||
#[clap(
|
||||
short,
|
||||
long = "target",
|
||||
action = ArgAction::Append,
|
||||
num_args(0..),
|
||||
value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))
|
||||
)]
|
||||
pub targets: Option<Vec<String>>,
|
||||
/// List of cargo features to activate
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
|
||||
pub features: Option<Vec<String>>,
|
||||
/// JSON string or path to JSON file to merge with tauri.conf.json
|
||||
#[clap(short, long)]
|
||||
pub config: Option<String>,
|
||||
/// Whether to split the APKs and AABs per ABIs.
|
||||
#[clap(long)]
|
||||
pub split_per_abi: bool,
|
||||
/// Build APKs.
|
||||
#[clap(long)]
|
||||
pub apk: bool,
|
||||
/// Build AABs.
|
||||
#[clap(long)]
|
||||
pub aab: bool,
|
||||
/// Open Android Studio
|
||||
#[clap(short, long)]
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl From<Options> for crate::build::Options {
|
||||
fn from(options: Options) -> Self {
|
||||
Self {
|
||||
runner: None,
|
||||
debug: options.debug,
|
||||
target: None,
|
||||
features: options.features,
|
||||
bundles: None,
|
||||
config: options.config,
|
||||
args: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
delete_codegen_vars();
|
||||
with_config(
|
||||
Some(Default::default()),
|
||||
|app, config, _metadata, _cli_options| {
|
||||
set_var("WRY_RUSTWEBVIEWCLIENT_CLASS_EXTENSION", "");
|
||||
set_var("WRY_RUSTWEBVIEW_CLASS_INIT", "");
|
||||
|
||||
ensure_init(config.project_dir(), MobileTarget::Android)?;
|
||||
|
||||
let mut env = env()?;
|
||||
init_dot_cargo(app, Some((&env, config)))?;
|
||||
|
||||
let open = options.open;
|
||||
run_build(options, config, &mut env, noise_level)?;
|
||||
|
||||
if open {
|
||||
open_and_wait(config, &env);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn run_build(
|
||||
mut options: Options,
|
||||
config: &AndroidConfig,
|
||||
env: &mut Env,
|
||||
noise_level: NoiseLevel,
|
||||
) -> Result<()> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
|
||||
if !(options.apk || options.aab) {
|
||||
// if the user didn't specify the format to build, we'll do both
|
||||
options.apk = true;
|
||||
options.aab = true;
|
||||
}
|
||||
|
||||
let mut build_options = options.clone().into();
|
||||
let interface = crate::build::setup(&mut build_options, true)?;
|
||||
|
||||
let app_settings = interface.app_settings();
|
||||
let bin_path = app_settings.app_binary_path(&InterfaceOptions {
|
||||
debug: build_options.debug,
|
||||
..Default::default()
|
||||
})?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
let _lock = flock::open_rw(&out_dir.join("lock").with_extension("android"), "Android")?;
|
||||
|
||||
let cli_options = CliOptions {
|
||||
features: build_options.features.clone(),
|
||||
args: build_options.args.clone(),
|
||||
noise_level,
|
||||
vars: Default::default(),
|
||||
};
|
||||
let _handle = write_options(cli_options, &mut env.base)?;
|
||||
|
||||
options
|
||||
.features
|
||||
.get_or_insert(Vec::new())
|
||||
.push("custom-protocol".into());
|
||||
|
||||
let apk_outputs = if options.apk {
|
||||
apk::build(
|
||||
config,
|
||||
env,
|
||||
noise_level,
|
||||
profile,
|
||||
get_targets_or_all(Vec::new())?,
|
||||
options.split_per_abi,
|
||||
)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let aab_outputs = if options.aab {
|
||||
aab::build(
|
||||
config,
|
||||
env,
|
||||
noise_level,
|
||||
profile,
|
||||
get_targets_or_all(Vec::new())?,
|
||||
options.split_per_abi,
|
||||
)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
log_finished(apk_outputs, "APK");
|
||||
log_finished(aab_outputs, "AAB");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_targets_or_all<'a>(targets: Vec<String>) -> Result<Vec<&'a Target<'a>>> {
|
||||
if targets.is_empty() {
|
||||
Ok(Target::all().iter().map(|t| t.1).collect())
|
||||
} else {
|
||||
let mut outs = Vec::new();
|
||||
|
||||
let possible_targets = Target::all()
|
||||
.keys()
|
||||
.map(|key| key.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
for t in targets {
|
||||
let target = Target::for_name(&t).ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Target {} is invalid; the possible targets are {}",
|
||||
t,
|
||||
possible_targets
|
||||
)
|
||||
})?;
|
||||
outs.push(target);
|
||||
}
|
||||
Ok(outs)
|
||||
}
|
||||
}
|
||||
203
tooling/cli/src/mobile/android/dev.rs
Normal file
203
tooling/cli/src/mobile/android/dev.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
use super::{
|
||||
delete_codegen_vars, device_prompt, ensure_init, env, init_dot_cargo, open_and_wait, with_config,
|
||||
MobileTarget,
|
||||
};
|
||||
use crate::{
|
||||
helpers::flock,
|
||||
interface::{AppSettings, Interface, MobileOptions, Options as InterfaceOptions},
|
||||
mobile::{write_options, CliOptions, DevChild, DevProcess},
|
||||
Result,
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
|
||||
use cargo_mobile::{
|
||||
android::{
|
||||
config::{Config as AndroidConfig, Metadata as AndroidMetadata},
|
||||
env::Env,
|
||||
},
|
||||
config::app::App,
|
||||
opts::{FilterLevel, NoiseLevel, Profile},
|
||||
};
|
||||
|
||||
use std::env::set_var;
|
||||
|
||||
const WEBVIEW_CLIENT_CLASS_EXTENSION: &str = "
|
||||
@android.annotation.SuppressLint(\"WebViewClientOnReceivedSslError\")
|
||||
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler, error: android.net.http.SslError) {
|
||||
handler.proceed()
|
||||
}
|
||||
";
|
||||
const WEBVIEW_CLASS_INIT: &str =
|
||||
"this.settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW";
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(about = "Android dev")]
|
||||
pub struct Options {
|
||||
/// List of cargo features to activate
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
|
||||
pub features: Option<Vec<String>>,
|
||||
/// Exit on panic
|
||||
#[clap(short, long)]
|
||||
exit_on_panic: bool,
|
||||
/// JSON string or path to JSON file to merge with tauri.conf.json
|
||||
#[clap(short, long)]
|
||||
pub config: Option<String>,
|
||||
/// Disable the file watcher
|
||||
#[clap(long)]
|
||||
pub no_watch: bool,
|
||||
/// Open Android Studio instead of trying to run on a connected device
|
||||
#[clap(short, long)]
|
||||
pub open: bool,
|
||||
/// Runs on the given device name
|
||||
pub device: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Options> for crate::dev::Options {
|
||||
fn from(options: Options) -> Self {
|
||||
Self {
|
||||
runner: None,
|
||||
target: None,
|
||||
features: options.features,
|
||||
exit_on_panic: options.exit_on_panic,
|
||||
config: options.config,
|
||||
release_mode: false,
|
||||
args: Vec::new(),
|
||||
no_watch: options.no_watch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
delete_codegen_vars();
|
||||
with_config(
|
||||
Some(Default::default()),
|
||||
|app, config, metadata, _cli_options| {
|
||||
set_var(
|
||||
"WRY_RUSTWEBVIEWCLIENT_CLASS_EXTENSION",
|
||||
WEBVIEW_CLIENT_CLASS_EXTENSION,
|
||||
);
|
||||
set_var("WRY_RUSTWEBVIEW_CLASS_INIT", WEBVIEW_CLASS_INIT);
|
||||
ensure_init(config.project_dir(), MobileTarget::Android)?;
|
||||
run_dev(options, app, config, metadata, noise_level).map_err(Into::into)
|
||||
},
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn run_dev(
|
||||
options: Options,
|
||||
app: &App,
|
||||
config: &AndroidConfig,
|
||||
metadata: &AndroidMetadata,
|
||||
noise_level: NoiseLevel,
|
||||
) -> Result<()> {
|
||||
let mut dev_options = options.clone().into();
|
||||
let mut interface = crate::dev::setup(&mut dev_options)?;
|
||||
|
||||
let app_settings = interface.app_settings();
|
||||
let bin_path = app_settings.app_binary_path(&InterfaceOptions {
|
||||
debug: !dev_options.release_mode,
|
||||
..Default::default()
|
||||
})?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
let _lock = flock::open_rw(&out_dir.join("lock").with_extension("android"), "Android")?;
|
||||
|
||||
let env = env()?;
|
||||
init_dot_cargo(app, Some((&env, config)))?;
|
||||
|
||||
let open = options.open;
|
||||
let exit_on_panic = options.exit_on_panic;
|
||||
let no_watch = options.no_watch;
|
||||
let device = options.device;
|
||||
interface.mobile_dev(
|
||||
MobileOptions {
|
||||
debug: true,
|
||||
features: options.features,
|
||||
args: Vec::new(),
|
||||
config: options.config,
|
||||
no_watch: options.no_watch,
|
||||
},
|
||||
|options| {
|
||||
let mut env = env.clone();
|
||||
let cli_options = CliOptions {
|
||||
features: options.features.clone(),
|
||||
args: options.args.clone(),
|
||||
noise_level,
|
||||
vars: Default::default(),
|
||||
};
|
||||
let _handle = write_options(cli_options, &mut env.base)?;
|
||||
|
||||
if open {
|
||||
open_and_wait(config, &env)
|
||||
} else {
|
||||
match run(
|
||||
device.as_deref(),
|
||||
options,
|
||||
config,
|
||||
&env,
|
||||
metadata,
|
||||
noise_level,
|
||||
) {
|
||||
Ok(c) => {
|
||||
crate::dev::wait_dev_process(c.clone(), move |status, reason| {
|
||||
crate::dev::on_app_exit(status, reason, exit_on_panic, no_watch)
|
||||
});
|
||||
Ok(Box::new(c) as Box<dyn DevProcess>)
|
||||
}
|
||||
Err(RunError::FailedToPromptForDevice(e)) => {
|
||||
log::error!("{}", e);
|
||||
open_and_wait(config, &env)
|
||||
}
|
||||
Err(e) => {
|
||||
crate::dev::kill_before_dev_process();
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum RunError {
|
||||
#[error("{0}")]
|
||||
FailedToPromptForDevice(String),
|
||||
#[error("{0}")]
|
||||
RunFailed(String),
|
||||
}
|
||||
|
||||
fn run(
|
||||
device: Option<&str>,
|
||||
options: MobileOptions,
|
||||
config: &AndroidConfig,
|
||||
env: &Env,
|
||||
metadata: &AndroidMetadata,
|
||||
noise_level: NoiseLevel,
|
||||
) -> Result<DevChild, RunError> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
|
||||
let build_app_bundle = metadata.asset_packs().is_some();
|
||||
|
||||
device_prompt(env, device)
|
||||
.map_err(|e| RunError::FailedToPromptForDevice(e.to_string()))?
|
||||
.run(
|
||||
config,
|
||||
env,
|
||||
noise_level,
|
||||
profile,
|
||||
Some(match noise_level {
|
||||
NoiseLevel::Polite => FilterLevel::Info,
|
||||
NoiseLevel::LoudAndProud => FilterLevel::Debug,
|
||||
NoiseLevel::FranklyQuitePedantic => FilterLevel::Verbose,
|
||||
}),
|
||||
build_app_bundle,
|
||||
false,
|
||||
".MainActivity".into(),
|
||||
)
|
||||
.map(DevChild::new)
|
||||
.map_err(|e| RunError::RunFailed(e.to_string()))
|
||||
}
|
||||
14
tooling/cli/src/mobile/android/open.rs
Normal file
14
tooling/cli/src/mobile/android/open.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use super::{ensure_init, env, with_config, MobileTarget};
|
||||
use crate::Result;
|
||||
use cargo_mobile::os;
|
||||
|
||||
pub fn command() -> Result<()> {
|
||||
with_config(
|
||||
Some(Default::default()),
|
||||
|_root_conf, config, _metadata, _cli_options| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Android)?;
|
||||
let env = env()?;
|
||||
os::open_file_with("Android Studio", config.project_dir(), &env.base).map_err(Into::into)
|
||||
},
|
||||
)
|
||||
}
|
||||
185
tooling/cli/src/mobile/android/project.rs
Normal file
185
tooling/cli/src/mobile/android/project.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{helpers::template, Result};
|
||||
use anyhow::Context;
|
||||
use cargo_mobile::{
|
||||
android::{
|
||||
config::{Config, Metadata},
|
||||
target::Target,
|
||||
},
|
||||
config::app::DEFAULT_ASSET_DIR,
|
||||
os,
|
||||
target::TargetTrait as _,
|
||||
util::{
|
||||
self,
|
||||
cli::{Report, TextWrapper},
|
||||
prefix_path,
|
||||
},
|
||||
};
|
||||
use handlebars::Handlebars;
|
||||
use include_dir::{include_dir, Dir};
|
||||
|
||||
use std::{ffi::OsStr, fs, path::Path};
|
||||
|
||||
const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/mobile/android");
|
||||
|
||||
pub fn gen(
|
||||
config: &Config,
|
||||
metadata: &Metadata,
|
||||
(handlebars, mut map): (Handlebars, template::JsonMap),
|
||||
wrapper: &TextWrapper,
|
||||
) -> Result<()> {
|
||||
println!("Installing Android toolchains...");
|
||||
Target::install_all().with_context(|| "failed to run rustup")?;
|
||||
println!("Generating Android Studio project...");
|
||||
let dest = config.project_dir();
|
||||
let asset_packs = metadata.asset_packs().unwrap_or_default();
|
||||
|
||||
map.insert(
|
||||
"root-dir-rel",
|
||||
Path::new(&os::replace_path_separator(
|
||||
util::relativize_path(
|
||||
config.app().root_dir(),
|
||||
config.project_dir().join(config.app().name()),
|
||||
)
|
||||
.into_os_string(),
|
||||
)),
|
||||
);
|
||||
map.insert("root-dir", config.app().root_dir());
|
||||
map.insert("targets", Target::all().values().collect::<Vec<_>>());
|
||||
map.insert("target-names", Target::all().keys().collect::<Vec<_>>());
|
||||
map.insert(
|
||||
"arches",
|
||||
Target::all()
|
||||
.values()
|
||||
.map(|target| target.arch)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
map.insert("android-app-plugins", metadata.app_plugins());
|
||||
map.insert(
|
||||
"android-project-dependencies",
|
||||
metadata.project_dependencies(),
|
||||
);
|
||||
map.insert("android-app-dependencies", metadata.app_dependencies());
|
||||
map.insert(
|
||||
"android-app-dependencies-platform",
|
||||
metadata.app_dependencies_platform(),
|
||||
);
|
||||
map.insert(
|
||||
"has-code",
|
||||
metadata.project_dependencies().is_some()
|
||||
|| metadata.app_dependencies().is_some()
|
||||
|| metadata.app_dependencies_platform().is_some(),
|
||||
);
|
||||
map.insert(
|
||||
"asset-packs",
|
||||
asset_packs
|
||||
.iter()
|
||||
.map(|p| p.name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
map.insert("windows", cfg!(windows));
|
||||
|
||||
let domain = config.app().reverse_domain().replace('.', "/");
|
||||
let package_path = format!("java/{}/{}", domain, config.app().name());
|
||||
|
||||
map.insert("package-path", &package_path);
|
||||
|
||||
let mut created_dirs = Vec::new();
|
||||
template::render_with_generator(
|
||||
&handlebars,
|
||||
map.inner(),
|
||||
&TEMPLATE_DIR,
|
||||
&dest,
|
||||
&mut |path| {
|
||||
let mut iter = path.iter();
|
||||
let root = iter.next().unwrap().to_str().unwrap();
|
||||
let path_without_root: std::path::PathBuf = iter.collect();
|
||||
let path = match (
|
||||
root,
|
||||
path.extension().and_then(|o| o.to_str()),
|
||||
path_without_root.strip_prefix("src/main"),
|
||||
) {
|
||||
("app" | "buildSrc", Some("kt"), Ok(path)) => {
|
||||
let parent = path.parent().unwrap();
|
||||
let file_name = path.file_name().unwrap();
|
||||
let out_dir = dest
|
||||
.join(root)
|
||||
.join("src/main")
|
||||
.join(&package_path)
|
||||
.join(parent);
|
||||
out_dir.join(file_name)
|
||||
}
|
||||
_ => dest.join(path),
|
||||
};
|
||||
|
||||
let parent = path.parent().unwrap().to_path_buf();
|
||||
if !created_dirs.contains(&parent) {
|
||||
fs::create_dir_all(&parent)?;
|
||||
created_dirs.push(parent);
|
||||
}
|
||||
|
||||
let mut options = fs::OpenOptions::new();
|
||||
options.write(true);
|
||||
|
||||
#[cfg(unix)]
|
||||
if path.file_name().unwrap() == OsStr::new("gradlew") {
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
options.mode(0o755);
|
||||
}
|
||||
|
||||
if path.file_name().unwrap() == OsStr::new("BuildTask.kt") || !path.exists() {
|
||||
options.create(true).open(path).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
)
|
||||
.with_context(|| "failed to process template")?;
|
||||
|
||||
if !asset_packs.is_empty() {
|
||||
Report::action_request(
|
||||
"When running from Android Studio, you must first set your deployment option to \"APK from app bundle\".",
|
||||
"Android Studio will not be able to find your asset packs otherwise. The option can be found under \"Run > Edit Configurations > Deploy\"."
|
||||
).print(wrapper);
|
||||
}
|
||||
|
||||
let source_dest = dest.join("app");
|
||||
for source in metadata.app_sources() {
|
||||
let source_src = config.app().root_dir().join(source);
|
||||
let source_file = source_src
|
||||
.file_name()
|
||||
.ok_or_else(|| anyhow::anyhow!("asset source {} is invalid", source_src.display()))?;
|
||||
fs::copy(&source_src, source_dest.join(source_file)).map_err(|cause| {
|
||||
anyhow::anyhow!(
|
||||
"failed to copy {} to {}: {}",
|
||||
source_src.display(),
|
||||
source_dest.display(),
|
||||
cause
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
let dest = prefix_path(dest, "app/src/main/");
|
||||
fs::create_dir_all(&dest).map_err(|cause| {
|
||||
anyhow::anyhow!(
|
||||
"failed to create directory at {}: {}",
|
||||
dest.display(),
|
||||
cause
|
||||
)
|
||||
})?;
|
||||
|
||||
let asset_dir = dest.join(DEFAULT_ASSET_DIR);
|
||||
if !asset_dir.is_dir() {
|
||||
fs::create_dir_all(&asset_dir).map_err(|cause| {
|
||||
anyhow::anyhow!(
|
||||
"failed to create asset dir {path}: {cause}",
|
||||
path = asset_dir.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
368
tooling/cli/src/mobile/init.rs
Normal file
368
tooling/cli/src/mobile/init.rs
Normal file
@@ -0,0 +1,368 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::{get_app, Target};
|
||||
use crate::helpers::{config::get as get_tauri_config, template::JsonMap};
|
||||
use crate::Result;
|
||||
use cargo_mobile::{
|
||||
android::{
|
||||
config::Config as AndroidConfig, env::Env as AndroidEnv, target::Target as AndroidTarget,
|
||||
},
|
||||
config::app::App,
|
||||
dot_cargo,
|
||||
target::TargetTrait as _,
|
||||
util::{
|
||||
self,
|
||||
cli::{Report, TextWrapper},
|
||||
},
|
||||
};
|
||||
use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError};
|
||||
|
||||
use std::{env::current_dir, path::PathBuf};
|
||||
|
||||
pub fn command(target: Target, ci: bool, reinstall_deps: bool) -> Result<()> {
|
||||
let wrapper = TextWrapper::with_splitter(textwrap::termwidth(), textwrap::NoHyphenation);
|
||||
exec(
|
||||
target,
|
||||
&wrapper,
|
||||
ci || std::env::var("CI").is_ok(),
|
||||
reinstall_deps,
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("{:#}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init_dot_cargo(app: &App, android: Option<(&AndroidEnv, &AndroidConfig)>) -> Result<()> {
|
||||
let mut dot_cargo = dot_cargo::DotCargo::load(app)?;
|
||||
// Mysteriously, builds that don't specify `--target` seem to fight over
|
||||
// the build cache with builds that use `--target`! This means that
|
||||
// alternating between i.e. `cargo run` and `cargo apple run` would
|
||||
// result in clean builds being made each time you switched... which is
|
||||
// pretty nightmarish. Specifying `build.target` in `.cargo/config`
|
||||
// fortunately has the same effect as specifying `--target`, so now we can
|
||||
// `cargo run` with peace of mind!
|
||||
//
|
||||
// This behavior could be explained here:
|
||||
// https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags
|
||||
dot_cargo.set_default_target(util::host_target_triple()?);
|
||||
|
||||
if let Some((env, config)) = android {
|
||||
for target in AndroidTarget::all().values() {
|
||||
dot_cargo.insert_target(
|
||||
target.triple.to_owned(),
|
||||
target.generate_cargo_config(config, env)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
dot_cargo.write(app).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
target: Target,
|
||||
wrapper: &TextWrapper,
|
||||
#[allow(unused_variables)] non_interactive: bool,
|
||||
#[allow(unused_variables)] reinstall_deps: bool,
|
||||
) -> Result<App> {
|
||||
let tauri_config = get_tauri_config(None)?;
|
||||
let tauri_config_guard = tauri_config.lock().unwrap();
|
||||
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
|
||||
|
||||
let app = get_app(tauri_config_);
|
||||
|
||||
let (handlebars, mut map) = handlebars(&app);
|
||||
|
||||
let mut args = std::env::args_os();
|
||||
let tauri_binary = args
|
||||
.next()
|
||||
.map(|bin| {
|
||||
let path = PathBuf::from(&bin);
|
||||
if path.exists() {
|
||||
if let Ok(dir) = current_dir() {
|
||||
let absolute_path = util::prefix_path(dir, path);
|
||||
return absolute_path.into();
|
||||
}
|
||||
}
|
||||
bin
|
||||
})
|
||||
.unwrap_or_else(|| std::ffi::OsString::from("cargo"));
|
||||
let mut build_args = Vec::new();
|
||||
for arg in args {
|
||||
let path = PathBuf::from(&arg);
|
||||
if path.exists() {
|
||||
if let Ok(dir) = current_dir() {
|
||||
let absolute_path = util::prefix_path(dir, path);
|
||||
build_args.push(absolute_path.to_string_lossy().into_owned());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
build_args.push(arg.to_string_lossy().into_owned());
|
||||
if arg == "android" || arg == "ios" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
build_args.push(target.ide_build_script_name().into());
|
||||
map.insert("tauri-binary", tauri_binary.to_string_lossy());
|
||||
map.insert("tauri-binary-args", &build_args);
|
||||
map.insert("tauri-binary-args-str", build_args.join(" "));
|
||||
|
||||
let app = match target {
|
||||
// Generate Android Studio project
|
||||
Target::Android => match AndroidEnv::new() {
|
||||
Ok(env) => {
|
||||
let (app, config, metadata) =
|
||||
super::android::get_config(Some(app), tauri_config_, &Default::default());
|
||||
map.insert("android", &config);
|
||||
super::android::project::gen(&config, &metadata, (handlebars, map), wrapper)?;
|
||||
init_dot_cargo(&app, Some((&env, &config)))?;
|
||||
app
|
||||
}
|
||||
Err(err) => {
|
||||
if err.sdk_or_ndk_issue() {
|
||||
Report::action_request(
|
||||
" to initialize Android environment; Android support won't be usable until you fix the issue below and re-run `tauri android init`!",
|
||||
err,
|
||||
)
|
||||
.print(wrapper);
|
||||
init_dot_cargo(&app, None)?;
|
||||
app
|
||||
} else {
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
},
|
||||
#[cfg(target_os = "macos")]
|
||||
// Generate Xcode project
|
||||
Target::Ios => {
|
||||
let (app, config, metadata) =
|
||||
super::ios::get_config(Some(app), tauri_config_, &Default::default());
|
||||
map.insert("apple", &config);
|
||||
super::ios::project::gen(
|
||||
&config,
|
||||
&metadata,
|
||||
(handlebars, map),
|
||||
wrapper,
|
||||
non_interactive,
|
||||
reinstall_deps,
|
||||
)?;
|
||||
init_dot_cargo(&app, None)?;
|
||||
app
|
||||
}
|
||||
};
|
||||
|
||||
Report::victory(
|
||||
"Project generated successfully!",
|
||||
"Make cool apps! 🌻 🐕 🎉",
|
||||
)
|
||||
.print(wrapper);
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
fn handlebars(app: &App) -> (Handlebars<'static>, JsonMap) {
|
||||
let mut h = Handlebars::new();
|
||||
h.register_escape_fn(handlebars::no_escape);
|
||||
|
||||
h.register_helper("html-escape", Box::new(html_escape));
|
||||
h.register_helper("join", Box::new(join));
|
||||
h.register_helper("quote-and-join", Box::new(quote_and_join));
|
||||
h.register_helper(
|
||||
"quote-and-join-colon-prefix",
|
||||
Box::new(quote_and_join_colon_prefix),
|
||||
);
|
||||
h.register_helper("snake-case", Box::new(snake_case));
|
||||
h.register_helper("reverse-domain", Box::new(reverse_domain));
|
||||
h.register_helper(
|
||||
"reverse-domain-snake-case",
|
||||
Box::new(reverse_domain_snake_case),
|
||||
);
|
||||
// don't mix these up or very bad things will happen to all of us
|
||||
h.register_helper("prefix-path", Box::new(prefix_path));
|
||||
h.register_helper("unprefix-path", Box::new(unprefix_path));
|
||||
|
||||
let mut map = JsonMap::default();
|
||||
map.insert("app", app);
|
||||
|
||||
(h, map)
|
||||
}
|
||||
|
||||
fn get_str<'a>(helper: &'a Helper) -> &'a str {
|
||||
helper
|
||||
.param(0)
|
||||
.and_then(|v| v.value().as_str())
|
||||
.unwrap_or("")
|
||||
}
|
||||
|
||||
fn get_str_array<'a>(
|
||||
helper: &'a Helper,
|
||||
formatter: impl Fn(&str) -> String,
|
||||
) -> Option<Vec<String>> {
|
||||
helper.param(0).and_then(|v| {
|
||||
v.value().as_array().and_then(|arr| {
|
||||
arr
|
||||
.iter()
|
||||
.map(|val| {
|
||||
val.as_str().map(
|
||||
#[allow(clippy::redundant_closure)]
|
||||
|s| formatter(s),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn html_escape(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_ctx: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(&handlebars::html_escape(get_str(helper)))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn join(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(
|
||||
&get_str_array(helper, |s| s.to_string())
|
||||
.ok_or_else(|| RenderError::new("`join` helper wasn't given an array"))?
|
||||
.join(", "),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn quote_and_join(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(
|
||||
&get_str_array(helper, |s| format!("{:?}", s))
|
||||
.ok_or_else(|| RenderError::new("`quote-and-join` helper wasn't given an array"))?
|
||||
.join(", "),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn quote_and_join_colon_prefix(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(
|
||||
&get_str_array(helper, |s| format!("{:?}", format!(":{}", s)))
|
||||
.ok_or_else(|| {
|
||||
RenderError::new("`quote-and-join-colon-prefix` helper wasn't given an array")
|
||||
})?
|
||||
.join(", "),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn snake_case(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
use heck::ToSnekCase as _;
|
||||
out
|
||||
.write(&get_str(helper).to_snek_case())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn reverse_domain(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(&util::reverse_domain(get_str(helper)))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn reverse_domain_snake_case(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
use heck::ToSnekCase as _;
|
||||
out
|
||||
.write(&util::reverse_domain(get_str(helper)).to_snek_case())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn app_root(ctx: &Context) -> Result<&str, RenderError> {
|
||||
let app_root = ctx
|
||||
.data()
|
||||
.get("app")
|
||||
.ok_or_else(|| RenderError::new("`app` missing from template data."))?
|
||||
.get("root-dir")
|
||||
.ok_or_else(|| RenderError::new("`app.root-dir` missing from template data."))?;
|
||||
app_root
|
||||
.as_str()
|
||||
.ok_or_else(|| RenderError::new("`app.root-dir` contained invalid UTF-8."))
|
||||
}
|
||||
|
||||
fn prefix_path(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
ctx: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(
|
||||
util::prefix_path(app_root(ctx)?, get_str(helper))
|
||||
.to_str()
|
||||
.ok_or_else(|| {
|
||||
RenderError::new(
|
||||
"Either the `app.root-dir` or the specified path contained invalid UTF-8.",
|
||||
)
|
||||
})?,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn unprefix_path(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
ctx: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(
|
||||
util::unprefix_path(app_root(ctx)?, get_str(helper))
|
||||
.map_err(|_| {
|
||||
RenderError::new("Attempted to unprefix a path that wasn't in the app root dir.")
|
||||
})?
|
||||
.to_str()
|
||||
.ok_or_else(|| {
|
||||
RenderError::new(
|
||||
"Either the `app.root-dir` or the specified path contained invalid UTF-8.",
|
||||
)
|
||||
})?,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
286
tooling/cli/src/mobile/ios.rs
Normal file
286
tooling/cli/src/mobile/ios.rs
Normal file
@@ -0,0 +1,286 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use cargo_mobile::{
|
||||
apple::{
|
||||
config::{
|
||||
Config as AppleConfig, Metadata as AppleMetadata, Platform as ApplePlatform,
|
||||
Raw as RawAppleConfig,
|
||||
},
|
||||
device::Device,
|
||||
ios_deploy, simctl,
|
||||
target::Target,
|
||||
teams::find_development_teams,
|
||||
},
|
||||
config::app::App,
|
||||
env::Env,
|
||||
opts::NoiseLevel,
|
||||
os,
|
||||
util::prompt,
|
||||
};
|
||||
use clap::{Parser, Subcommand};
|
||||
use sublime_fuzzy::best_match;
|
||||
|
||||
use super::{
|
||||
ensure_init, env, get_app,
|
||||
init::{command as init_command, init_dot_cargo},
|
||||
log_finished, read_options, CliOptions, Target as MobileTarget, MIN_DEVICE_MATCH_SCORE,
|
||||
};
|
||||
use crate::{
|
||||
helpers::config::{get as get_tauri_config, Config as TauriConfig},
|
||||
Result,
|
||||
};
|
||||
|
||||
use std::{
|
||||
process::exit,
|
||||
thread::{sleep, spawn},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
mod build;
|
||||
mod certificate;
|
||||
mod dev;
|
||||
mod open;
|
||||
pub(crate) mod project;
|
||||
mod xcode_script;
|
||||
|
||||
pub const APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME: &str = "TAURI_APPLE_DEVELOPMENT_TEAM";
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(
|
||||
author,
|
||||
version,
|
||||
about = "iOS commands",
|
||||
subcommand_required(true),
|
||||
arg_required_else_help(true)
|
||||
)]
|
||||
pub struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(about = "Initializes a Tauri iOS project")]
|
||||
pub struct InitOptions {
|
||||
/// Skip prompting for values
|
||||
#[clap(long)]
|
||||
ci: bool,
|
||||
/// Reinstall dependencies
|
||||
#[clap(short, long)]
|
||||
reinstall_deps: bool,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
Init(InitOptions),
|
||||
Open,
|
||||
Dev(dev::Options),
|
||||
Build(build::Options),
|
||||
#[clap(hide(true))]
|
||||
XcodeScript(xcode_script::Options),
|
||||
#[clap(subcommand)]
|
||||
Certificate(CertificateCommands),
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum CertificateCommands {
|
||||
Setup,
|
||||
Delete,
|
||||
}
|
||||
|
||||
pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
|
||||
let noise_level = NoiseLevel::from_occurrences(verbosity as u64);
|
||||
match cli.command {
|
||||
Commands::Init(options) => init_command(MobileTarget::Ios, options.ci, options.reinstall_deps)?,
|
||||
Commands::Open => open::command()?,
|
||||
Commands::Dev(options) => dev::command(options, noise_level)?,
|
||||
Commands::Build(options) => build::command(options, noise_level)?,
|
||||
Commands::XcodeScript(options) => xcode_script::command(options)?,
|
||||
Commands::Certificate(CertificateCommands::Setup) => certificate::setup()?,
|
||||
Commands::Certificate(CertificateCommands::Delete) => certificate::delete(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_config(
|
||||
app: Option<App>,
|
||||
config: &TauriConfig,
|
||||
cli_options: &CliOptions,
|
||||
) -> (App, AppleConfig, AppleMetadata) {
|
||||
let app = app.unwrap_or_else(|| get_app(config));
|
||||
let ios_options = cli_options.clone();
|
||||
|
||||
let raw = RawAppleConfig {
|
||||
development_team: std::env::var(APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME)
|
||||
.ok()
|
||||
.or_else(|| config.tauri.bundle.ios.development_team.clone())
|
||||
.unwrap_or_else(|| {
|
||||
let teams = find_development_teams().unwrap_or_default();
|
||||
match teams.len() {
|
||||
0 => {
|
||||
log::error!("No code signing certificates found. You must add one and set the certificate development team ID on the `tauri > iOS > developmentTeam` config value or the `{APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME}` environment variable. To list the available certificates, run `tauri info`.");
|
||||
exit(1);
|
||||
}
|
||||
1 => teams.first().unwrap().id.clone(),
|
||||
_ => {
|
||||
log::error!("You must set the code signing certificate development team ID on the `tauri > iOS > developmentTeam` config value or the `{APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME}` environment variable. Available certificates: {}", teams.iter().map(|t| format!("{} (ID: {})", t.name, t.id)).collect::<Vec<String>>().join(", "));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}),
|
||||
ios_features: ios_options.features.clone(),
|
||||
bundle_version: config.package.version.clone(),
|
||||
bundle_version_short: config.package.version.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let config = AppleConfig::from_raw(app.clone(), Some(raw)).unwrap();
|
||||
|
||||
let metadata = AppleMetadata {
|
||||
supported: true,
|
||||
ios: ApplePlatform {
|
||||
cargo_args: Some(ios_options.args),
|
||||
features: ios_options.features,
|
||||
..Default::default()
|
||||
},
|
||||
macos: Default::default(),
|
||||
};
|
||||
|
||||
(app, config, metadata)
|
||||
}
|
||||
|
||||
fn with_config<T>(
|
||||
cli_options: Option<CliOptions>,
|
||||
f: impl FnOnce(&App, &AppleConfig, &AppleMetadata, CliOptions) -> Result<T>,
|
||||
) -> Result<T> {
|
||||
let (app, config, metadata, cli_options) = {
|
||||
let tauri_config = get_tauri_config(None)?;
|
||||
let tauri_config_guard = tauri_config.lock().unwrap();
|
||||
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
|
||||
let cli_options = cli_options.unwrap_or_else(read_options);
|
||||
let (app, config, metadata) = get_config(None, tauri_config_, &cli_options);
|
||||
(app, config, metadata, cli_options)
|
||||
};
|
||||
f(&app, &config, &metadata, cli_options)
|
||||
}
|
||||
|
||||
fn ios_deploy_device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
|
||||
let device_list = ios_deploy::device_list(env)
|
||||
.map_err(|cause| anyhow::anyhow!("Failed to detect connected iOS devices: {cause}"))?;
|
||||
if !device_list.is_empty() {
|
||||
let device = if let Some(t) = target {
|
||||
let (device, score) = device_list
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|d| {
|
||||
let score = best_match(t, d.name()).map_or(0, |m| m.score());
|
||||
(d, score)
|
||||
})
|
||||
.max_by_key(|(_, score)| *score)
|
||||
// we already checked the list is not empty
|
||||
.unwrap();
|
||||
if score > MIN_DEVICE_MATCH_SCORE {
|
||||
device
|
||||
} else {
|
||||
anyhow::bail!("Could not find an iOS device matching {t}")
|
||||
}
|
||||
} else {
|
||||
let index = if device_list.len() > 1 {
|
||||
prompt::list(
|
||||
concat!("Detected ", "iOS", " devices"),
|
||||
device_list.iter(),
|
||||
"device",
|
||||
None,
|
||||
"Device",
|
||||
)
|
||||
.map_err(|cause| anyhow::anyhow!("Failed to prompt for iOS device: {cause}"))?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
device_list.into_iter().nth(index).unwrap()
|
||||
};
|
||||
println!(
|
||||
"Detected connected device: {} with target {:?}",
|
||||
device,
|
||||
device.target().triple,
|
||||
);
|
||||
Ok(device)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("No connected iOS devices detected"))
|
||||
}
|
||||
}
|
||||
|
||||
fn simulator_prompt(env: &'_ Env, target: Option<&str>) -> Result<simctl::Device> {
|
||||
let simulator_list = simctl::device_list(env).map_err(|cause| {
|
||||
anyhow::anyhow!("Failed to detect connected iOS Simulator devices: {cause}")
|
||||
})?;
|
||||
if !simulator_list.is_empty() {
|
||||
let device = if let Some(t) = target {
|
||||
let (device, score) = simulator_list
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|d| {
|
||||
let score = best_match(t, d.name()).map_or(0, |m| m.score());
|
||||
(d, score)
|
||||
})
|
||||
.max_by_key(|(_, score)| *score)
|
||||
// we already checked the list is not empty
|
||||
.unwrap();
|
||||
if score > MIN_DEVICE_MATCH_SCORE {
|
||||
device
|
||||
} else {
|
||||
anyhow::bail!("Could not find an iOS Simulator matching {t}")
|
||||
}
|
||||
} else if simulator_list.len() > 1 {
|
||||
let index = prompt::list(
|
||||
concat!("Detected ", "iOS", " simulators"),
|
||||
simulator_list.iter(),
|
||||
"simulator",
|
||||
None,
|
||||
"Simulator",
|
||||
)
|
||||
.map_err(|cause| anyhow::anyhow!("Failed to prompt for iOS Simulator device: {cause}"))?;
|
||||
simulator_list.into_iter().nth(index).unwrap()
|
||||
} else {
|
||||
simulator_list.into_iter().next().unwrap()
|
||||
};
|
||||
|
||||
log::info!("Starting simulator {}", device.name());
|
||||
let handle = device.start(env)?;
|
||||
spawn(move || {
|
||||
let _ = handle.wait();
|
||||
});
|
||||
|
||||
Ok(device)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("No available iOS Simulator detected"))
|
||||
}
|
||||
}
|
||||
|
||||
fn device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
|
||||
if let Ok(device) = ios_deploy_device_prompt(env, target) {
|
||||
Ok(device)
|
||||
} else {
|
||||
let simulator = simulator_prompt(env, target)?;
|
||||
let handle = simulator.start(env)?;
|
||||
spawn(move || {
|
||||
let _ = handle.wait();
|
||||
});
|
||||
Ok(simulator.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
|
||||
device_prompt(env, None).map(|device| device.target()).ok()
|
||||
}
|
||||
|
||||
fn open_and_wait(config: &AppleConfig, env: &Env) -> ! {
|
||||
log::info!("Opening Xcode");
|
||||
if let Err(e) = os::open_file_with("Xcode", config.project_dir(), env) {
|
||||
log::error!("{}", e);
|
||||
}
|
||||
loop {
|
||||
sleep(Duration::from_secs(24 * 60 * 60));
|
||||
}
|
||||
}
|
||||
156
tooling/cli/src/mobile/ios/build.rs
Normal file
156
tooling/cli/src/mobile/ios/build.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use super::{
|
||||
detect_target_ok, ensure_init, env, init_dot_cargo, log_finished, open_and_wait, with_config,
|
||||
MobileTarget,
|
||||
};
|
||||
use crate::{
|
||||
helpers::flock,
|
||||
interface::{AppSettings, Interface, Options as InterfaceOptions},
|
||||
mobile::{write_options, CliOptions},
|
||||
Result,
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
|
||||
use cargo_mobile::{
|
||||
apple::{config::Config as AppleConfig, target::Target},
|
||||
env::Env,
|
||||
opts::{NoiseLevel, Profile},
|
||||
target::{call_for_targets_with_fallback, TargetInvalid, TargetTrait},
|
||||
};
|
||||
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(about = "Android build")]
|
||||
pub struct Options {
|
||||
/// Builds with the debug flag
|
||||
#[clap(short, long)]
|
||||
pub debug: bool,
|
||||
/// Which targets to build.
|
||||
#[clap(
|
||||
short,
|
||||
long = "target",
|
||||
action = ArgAction::Append,
|
||||
num_args(0..),
|
||||
default_value = Target::DEFAULT_KEY,
|
||||
value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))
|
||||
)]
|
||||
pub targets: Vec<String>,
|
||||
/// List of cargo features to activate
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
|
||||
pub features: Option<Vec<String>>,
|
||||
/// JSON string or path to JSON file to merge with tauri.conf.json
|
||||
#[clap(short, long)]
|
||||
pub config: Option<String>,
|
||||
/// Build number to append to the app version.
|
||||
#[clap(long)]
|
||||
pub build_number: Option<u32>,
|
||||
/// Open Xcode
|
||||
#[clap(short, long)]
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl From<Options> for crate::build::Options {
|
||||
fn from(options: Options) -> Self {
|
||||
Self {
|
||||
runner: None,
|
||||
debug: options.debug,
|
||||
target: None,
|
||||
features: options.features,
|
||||
bundles: None,
|
||||
config: options.config,
|
||||
args: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
with_config(
|
||||
Some(Default::default()),
|
||||
|app, config, _metadata, _cli_options| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Ios)?;
|
||||
|
||||
let mut env = env()?;
|
||||
init_dot_cargo(app, None)?;
|
||||
|
||||
let open = options.open;
|
||||
run_build(options, config, &mut env, noise_level)?;
|
||||
|
||||
if open {
|
||||
open_and_wait(config, &env);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn run_build(
|
||||
mut options: Options,
|
||||
config: &AppleConfig,
|
||||
env: &mut Env,
|
||||
noise_level: NoiseLevel,
|
||||
) -> Result<()> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
|
||||
let mut build_options = options.clone().into();
|
||||
let interface = crate::build::setup(&mut build_options, true)?;
|
||||
|
||||
let app_settings = interface.app_settings();
|
||||
let bin_path = app_settings.app_binary_path(&InterfaceOptions {
|
||||
debug: build_options.debug,
|
||||
..Default::default()
|
||||
})?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
let _lock = flock::open_rw(&out_dir.join("lock").with_extension("ios"), "iOS")?;
|
||||
|
||||
let cli_options = CliOptions {
|
||||
features: build_options.features.clone(),
|
||||
args: build_options.args.clone(),
|
||||
noise_level,
|
||||
vars: Default::default(),
|
||||
};
|
||||
let _handle = write_options(cli_options, env)?;
|
||||
|
||||
options
|
||||
.features
|
||||
.get_or_insert(Vec::new())
|
||||
.push("custom-protocol".into());
|
||||
|
||||
let mut out_files = Vec::new();
|
||||
|
||||
call_for_targets_with_fallback(
|
||||
options.targets.iter(),
|
||||
&detect_target_ok,
|
||||
env,
|
||||
|target: &Target| -> Result<()> {
|
||||
let mut app_version = config.bundle_version().clone();
|
||||
if let Some(build_number) = options.build_number {
|
||||
app_version.push_extra(build_number);
|
||||
}
|
||||
|
||||
target.build(config, env, noise_level, profile)?;
|
||||
target.archive(config, env, noise_level, profile, Some(app_version))?;
|
||||
target.export(config, env, noise_level)?;
|
||||
|
||||
if let Ok(ipa_path) = config.ipa_path() {
|
||||
let out_dir = config.export_dir().join(target.arch);
|
||||
fs::create_dir_all(&out_dir)?;
|
||||
let path = out_dir.join(ipa_path.file_name().unwrap());
|
||||
fs::rename(&ipa_path, &path)?;
|
||||
out_files.push(path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.map_err(|e: TargetInvalid| anyhow::anyhow!(e.to_string()))??;
|
||||
|
||||
log_finished(out_files, "IPA");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
22
tooling/cli/src/mobile/ios/certificate.rs
Normal file
22
tooling/cli/src/mobile/ios/certificate.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::Result;
|
||||
use tauri_bundler::bundle::macos_sign::{delete_keychain, setup_keychain};
|
||||
|
||||
pub fn setup() -> Result<()> {
|
||||
if let (Some(certificate_encoded), Some(certificate_password)) = (
|
||||
std::env::var_os("APPLE_CERTIFICATE"),
|
||||
std::env::var_os("APPLE_CERTIFICATE_PASSWORD"),
|
||||
) {
|
||||
// setup keychain allow you to import your certificate
|
||||
// for CI build
|
||||
setup_keychain(certificate_encoded, certificate_password)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"Missing APPLE_CERTIFICATE and APPLE_CERTIFICATE_PASSWORD environment variables"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete() {
|
||||
delete_keychain();
|
||||
}
|
||||
196
tooling/cli/src/mobile/ios/dev.rs
Normal file
196
tooling/cli/src/mobile/ios/dev.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
use super::{
|
||||
device_prompt, ensure_init, env, init_dot_cargo, open_and_wait, with_config, MobileTarget,
|
||||
APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME,
|
||||
};
|
||||
use crate::{
|
||||
helpers::flock,
|
||||
interface::{AppSettings, Interface, MobileOptions, Options as InterfaceOptions},
|
||||
mobile::{write_options, CliOptions, DevChild, DevProcess},
|
||||
Result,
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
|
||||
use cargo_mobile::{
|
||||
apple::{config::Config as AppleConfig, teams::find_development_teams},
|
||||
config::app::App,
|
||||
env::Env,
|
||||
opts::{NoiseLevel, Profile},
|
||||
};
|
||||
use dialoguer::{theme::ColorfulTheme, Select};
|
||||
|
||||
use std::env::{set_var, var_os};
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(about = "iOS dev")]
|
||||
pub struct Options {
|
||||
/// List of cargo features to activate
|
||||
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
|
||||
pub features: Option<Vec<String>>,
|
||||
/// Exit on panic
|
||||
#[clap(short, long)]
|
||||
exit_on_panic: bool,
|
||||
/// JSON string or path to JSON file to merge with tauri.conf.json
|
||||
#[clap(short, long)]
|
||||
pub config: Option<String>,
|
||||
/// Run the code in release mode
|
||||
#[clap(long = "release")]
|
||||
pub release_mode: bool,
|
||||
/// Disable the file watcher
|
||||
#[clap(long)]
|
||||
pub no_watch: bool,
|
||||
/// Open Xcode instead of trying to run on a connected device
|
||||
#[clap(short, long)]
|
||||
pub open: bool,
|
||||
/// Runs on the given device name
|
||||
pub device: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Options> for crate::dev::Options {
|
||||
fn from(options: Options) -> Self {
|
||||
Self {
|
||||
runner: None,
|
||||
target: None,
|
||||
features: options.features,
|
||||
exit_on_panic: options.exit_on_panic,
|
||||
config: options.config,
|
||||
release_mode: options.release_mode,
|
||||
args: Vec::new(),
|
||||
no_watch: options.no_watch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
if var_os(APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME).is_none() {
|
||||
if let Ok(teams) = find_development_teams() {
|
||||
let index = match teams.len() {
|
||||
0 => None,
|
||||
1 => Some(0),
|
||||
_ => {
|
||||
let index = Select::with_theme(&ColorfulTheme::default())
|
||||
.items(
|
||||
&teams
|
||||
.iter()
|
||||
.map(|t| format!("{} (ID: {})", t.name, t.id))
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.default(0)
|
||||
.interact()?;
|
||||
Some(index)
|
||||
}
|
||||
};
|
||||
if let Some(index) = index {
|
||||
let team = teams.get(index).unwrap();
|
||||
log::info!(
|
||||
"Using development team `{}`. To make this permanent, set the `{}` environment variable to `{}`",
|
||||
team.name,
|
||||
APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME,
|
||||
team.id
|
||||
);
|
||||
set_var(APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME, &team.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
with_config(
|
||||
Some(Default::default()),
|
||||
|app, config, _metadata, _cli_options| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Ios)?;
|
||||
run_dev(options, app, config, noise_level).map_err(Into::into)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn run_dev(
|
||||
options: Options,
|
||||
app: &App,
|
||||
config: &AppleConfig,
|
||||
noise_level: NoiseLevel,
|
||||
) -> Result<()> {
|
||||
let mut dev_options = options.clone().into();
|
||||
let mut interface = crate::dev::setup(&mut dev_options)?;
|
||||
|
||||
let app_settings = interface.app_settings();
|
||||
let bin_path = app_settings.app_binary_path(&InterfaceOptions {
|
||||
debug: !dev_options.release_mode,
|
||||
..Default::default()
|
||||
})?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
let _lock = flock::open_rw(&out_dir.join("lock").with_extension("ios"), "iOS")?;
|
||||
|
||||
let env = env()?;
|
||||
init_dot_cargo(app, None)?;
|
||||
|
||||
let open = options.open;
|
||||
let exit_on_panic = options.exit_on_panic;
|
||||
let no_watch = options.no_watch;
|
||||
let device = options.device;
|
||||
interface.mobile_dev(
|
||||
MobileOptions {
|
||||
debug: true,
|
||||
features: options.features,
|
||||
args: Vec::new(),
|
||||
config: options.config,
|
||||
no_watch: options.no_watch,
|
||||
},
|
||||
|options| {
|
||||
let mut env = env.clone();
|
||||
let cli_options = CliOptions {
|
||||
features: options.features.clone(),
|
||||
args: options.args.clone(),
|
||||
noise_level,
|
||||
vars: Default::default(),
|
||||
};
|
||||
let _handle = write_options(cli_options, &mut env)?;
|
||||
|
||||
if open {
|
||||
open_and_wait(config, &env)
|
||||
} else {
|
||||
match run(device.as_deref(), options, config, &env, noise_level) {
|
||||
Ok(c) => {
|
||||
crate::dev::wait_dev_process(c.clone(), move |status, reason| {
|
||||
crate::dev::on_app_exit(status, reason, exit_on_panic, no_watch)
|
||||
});
|
||||
Ok(Box::new(c) as Box<dyn DevProcess>)
|
||||
}
|
||||
Err(RunError::FailedToPromptForDevice(e)) => {
|
||||
log::error!("{}", e);
|
||||
open_and_wait(config, &env)
|
||||
}
|
||||
Err(e) => {
|
||||
crate::dev::kill_before_dev_process();
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum RunError {
|
||||
#[error("{0}")]
|
||||
FailedToPromptForDevice(String),
|
||||
#[error("{0}")]
|
||||
RunFailed(String),
|
||||
}
|
||||
fn run(
|
||||
device: Option<&str>,
|
||||
options: MobileOptions,
|
||||
config: &AppleConfig,
|
||||
env: &Env,
|
||||
noise_level: NoiseLevel,
|
||||
) -> Result<DevChild, RunError> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
|
||||
let non_interactive = true; // ios-deploy --noninteractive (quit when app crashes or exits)
|
||||
|
||||
device_prompt(env, device)
|
||||
.map_err(|e| RunError::FailedToPromptForDevice(e.to_string()))?
|
||||
.run(config, env, noise_level, non_interactive, profile)
|
||||
.map(DevChild::new)
|
||||
.map_err(|e| RunError::RunFailed(e.to_string()))
|
||||
}
|
||||
14
tooling/cli/src/mobile/ios/open.rs
Normal file
14
tooling/cli/src/mobile/ios/open.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use super::{ensure_init, env, with_config, MobileTarget};
|
||||
use crate::Result;
|
||||
use cargo_mobile::os;
|
||||
|
||||
pub fn command() -> Result<()> {
|
||||
with_config(
|
||||
Some(Default::default()),
|
||||
|_root_conf, config, _metadata, _cli_options| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Ios)?;
|
||||
let env = env()?;
|
||||
os::open_file_with("Xcode", config.project_dir(), &env).map_err(Into::into)
|
||||
},
|
||||
)
|
||||
}
|
||||
187
tooling/cli/src/mobile/ios/project.rs
Normal file
187
tooling/cli/src/mobile/ios/project.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{helpers::template, Result};
|
||||
use anyhow::Context;
|
||||
use cargo_mobile::{
|
||||
apple::{
|
||||
config::{Config, Metadata},
|
||||
deps, rust_version_check,
|
||||
target::Target,
|
||||
},
|
||||
bossy,
|
||||
config::app::DEFAULT_ASSET_DIR,
|
||||
target::TargetTrait as _,
|
||||
util::{self, cli::TextWrapper},
|
||||
};
|
||||
use handlebars::Handlebars;
|
||||
use include_dir::{include_dir, Dir};
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
fs::{create_dir_all, OpenOptions},
|
||||
path::{Component, PathBuf},
|
||||
};
|
||||
|
||||
const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/mobile/ios");
|
||||
|
||||
// unprefixed app_root seems pretty dangerous!!
|
||||
// TODO: figure out what cargo-mobile meant by that
|
||||
pub fn gen(
|
||||
config: &Config,
|
||||
metadata: &Metadata,
|
||||
(handlebars, mut map): (Handlebars, template::JsonMap),
|
||||
wrapper: &TextWrapper,
|
||||
non_interactive: bool,
|
||||
reinstall_deps: bool,
|
||||
) -> Result<()> {
|
||||
println!("Installing iOS toolchains...");
|
||||
Target::install_all()?;
|
||||
rust_version_check(wrapper)?;
|
||||
|
||||
deps::install_all(wrapper, non_interactive, true, reinstall_deps)
|
||||
.with_context(|| "failed to install Apple dependencies")?;
|
||||
|
||||
let dest = config.project_dir();
|
||||
let rel_prefix = util::relativize_path(config.app().root_dir(), &dest);
|
||||
let source_dirs = vec![rel_prefix.join("src")];
|
||||
|
||||
let asset_catalogs = metadata.ios().asset_catalogs().unwrap_or_default();
|
||||
let ios_pods = metadata.ios().pods().unwrap_or_default();
|
||||
let macos_pods = metadata.macos().pods().unwrap_or_default();
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
let default_archs = ["arm64", "arm64-sim"];
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
let default_archs = ["arm64", "x86_64"];
|
||||
|
||||
map.insert("file-groups", &source_dirs);
|
||||
map.insert("ios-frameworks", metadata.ios().frameworks());
|
||||
map.insert("ios-valid-archs", default_archs);
|
||||
map.insert("ios-vendor-frameworks", metadata.ios().vendor_frameworks());
|
||||
map.insert("ios-vendor-sdks", metadata.ios().vendor_sdks());
|
||||
map.insert("macos-frameworks", metadata.macos().frameworks());
|
||||
map.insert(
|
||||
"macos-vendor-frameworks",
|
||||
metadata.macos().vendor_frameworks(),
|
||||
);
|
||||
map.insert("macos-vendor-sdks", metadata.macos().vendor_frameworks());
|
||||
map.insert("asset-catalogs", asset_catalogs);
|
||||
map.insert("ios-pods", ios_pods);
|
||||
map.insert("macos-pods", macos_pods);
|
||||
map.insert(
|
||||
"ios-additional-targets",
|
||||
metadata.ios().additional_targets(),
|
||||
);
|
||||
map.insert(
|
||||
"macos-additional-targets",
|
||||
metadata.macos().additional_targets(),
|
||||
);
|
||||
map.insert("ios-pre-build-scripts", metadata.ios().pre_build_scripts());
|
||||
map.insert(
|
||||
"ios-post-compile-scripts",
|
||||
metadata.ios().post_compile_scripts(),
|
||||
);
|
||||
map.insert(
|
||||
"ios-post-build-scripts",
|
||||
metadata.ios().post_build_scripts(),
|
||||
);
|
||||
map.insert(
|
||||
"macos-pre-build-scripts",
|
||||
metadata.macos().pre_build_scripts(),
|
||||
);
|
||||
map.insert(
|
||||
"macos-post-compile-scripts",
|
||||
metadata.macos().post_compile_scripts(),
|
||||
);
|
||||
map.insert(
|
||||
"macos-post-build-scripts",
|
||||
metadata.macos().post_build_scripts(),
|
||||
);
|
||||
map.insert(
|
||||
"ios-command-line-arguments",
|
||||
metadata.ios().command_line_arguments(),
|
||||
);
|
||||
map.insert(
|
||||
"macos-command-line-arguments",
|
||||
metadata.macos().command_line_arguments(),
|
||||
);
|
||||
|
||||
let mut created_dirs = Vec::new();
|
||||
template::render_with_generator(
|
||||
&handlebars,
|
||||
map.inner(),
|
||||
&TEMPLATE_DIR,
|
||||
&dest,
|
||||
&mut |path| {
|
||||
let mut components: Vec<_> = path.components().collect();
|
||||
let mut new_component = None;
|
||||
for component in &mut components {
|
||||
if let Component::Normal(c) = component {
|
||||
let c = c.to_string_lossy();
|
||||
if c.contains("{{app.name}}") {
|
||||
new_component.replace(OsString::from(
|
||||
&c.replace("{{app.name}}", config.app().name()),
|
||||
));
|
||||
*component = Component::Normal(new_component.as_ref().unwrap());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let path = dest.join(components.iter().collect::<PathBuf>());
|
||||
|
||||
let parent = path.parent().unwrap().to_path_buf();
|
||||
if !created_dirs.contains(&parent) {
|
||||
create_dir_all(&parent)?;
|
||||
created_dirs.push(parent);
|
||||
}
|
||||
|
||||
let mut options = OpenOptions::new();
|
||||
options.write(true);
|
||||
|
||||
if path.file_name().unwrap() == OsStr::new("BuildTask.kt") || !path.exists() {
|
||||
options.create(true).open(path).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
)
|
||||
.with_context(|| "failed to process template")?;
|
||||
|
||||
let asset_dir = dest.join(DEFAULT_ASSET_DIR);
|
||||
if !asset_dir.is_dir() {
|
||||
create_dir_all(&asset_dir).map_err(|cause| {
|
||||
anyhow::anyhow!(
|
||||
"failed to create asset dir {path}: {cause}",
|
||||
path = asset_dir.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
// Create all asset catalog directories if they don't already exist
|
||||
for dir in asset_catalogs {
|
||||
std::fs::create_dir_all(dir).map_err(|cause| {
|
||||
anyhow::anyhow!(
|
||||
"failed to create directory at {path}: {cause}",
|
||||
path = dir.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
// Note that Xcode doesn't always reload the project nicely; reopening is
|
||||
// often necessary.
|
||||
println!("Generating Xcode project...");
|
||||
bossy::Command::impure("xcodegen")
|
||||
.with_args(["generate", "--spec"])
|
||||
.with_arg(dest.join("project.yml"))
|
||||
.run_and_wait()
|
||||
.with_context(|| "failed to run `xcodegen`")?;
|
||||
|
||||
if !ios_pods.is_empty() || !macos_pods.is_empty() {
|
||||
bossy::Command::impure_parse("pod install")
|
||||
.with_arg(format!("--project-directory={}", dest.display()))
|
||||
.run_and_wait()
|
||||
.with_context(|| "failed to run `pod install`")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
172
tooling/cli/src/mobile/ios/xcode_script.rs
Normal file
172
tooling/cli/src/mobile/ios/xcode_script.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use super::{env, with_config};
|
||||
use crate::Result;
|
||||
use clap::Parser;
|
||||
|
||||
use cargo_mobile::{apple::target::Target, opts::Profile, util};
|
||||
|
||||
use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Options {
|
||||
/// Value of `PLATFORM_DISPLAY_NAME` env var
|
||||
#[clap(long)]
|
||||
platform: String,
|
||||
/// Value of `SDKROOT` env var
|
||||
#[clap(long)]
|
||||
sdk_root: PathBuf,
|
||||
/// Value of `FRAMEWORK_SEARCH_PATHS` env var
|
||||
#[clap(long)]
|
||||
framework_search_paths: String,
|
||||
/// Value of `GCC_PREPROCESSOR_DEFINITIONS` env var
|
||||
#[clap(long)]
|
||||
gcc_preprocessor_definitions: String,
|
||||
/// Value of `HEADER_SEARCH_PATHS` env var
|
||||
#[clap(long)]
|
||||
header_search_paths: String,
|
||||
/// Value of `CONFIGURATION` env var
|
||||
#[clap(long)]
|
||||
configuration: String,
|
||||
/// Value of `FORCE_COLOR` env var
|
||||
#[clap(long)]
|
||||
force_color: bool,
|
||||
/// Value of `ARCHS` env var
|
||||
#[clap(index = 1, required = true)]
|
||||
arches: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn command(options: Options) -> Result<()> {
|
||||
fn macos_from_platform(platform: &str) -> bool {
|
||||
platform == "macOS"
|
||||
}
|
||||
|
||||
fn profile_from_configuration(configuration: &str) -> Profile {
|
||||
if configuration == "release" {
|
||||
Profile::Release
|
||||
} else {
|
||||
Profile::Debug
|
||||
}
|
||||
}
|
||||
|
||||
// `xcode-script` is ran from the `gen/apple` folder.
|
||||
std::env::set_current_dir(
|
||||
std::env::current_dir()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let profile = profile_from_configuration(&options.configuration);
|
||||
let macos = macos_from_platform(&options.platform);
|
||||
|
||||
with_config(None, |_root_conf, config, metadata, cli_options| {
|
||||
let env = env()?;
|
||||
// The `PATH` env var Xcode gives us is missing any additions
|
||||
// made by the user's profile, so we'll manually add cargo's
|
||||
// `PATH`.
|
||||
let env = env
|
||||
.explicit_env_vars(cli_options.vars)
|
||||
.prepend_to_path(util::home_dir()?.join(".cargo/bin"));
|
||||
|
||||
if !options.sdk_root.is_dir() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"SDK root provided by Xcode was invalid. {} doesn't exist or isn't a directory",
|
||||
options.sdk_root.display(),
|
||||
));
|
||||
}
|
||||
let include_dir = options.sdk_root.join("usr/include");
|
||||
if !include_dir.is_dir() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Include dir was invalid. {} doesn't exist or isn't a directory",
|
||||
include_dir.display()
|
||||
));
|
||||
}
|
||||
|
||||
// Host flags that are used by build scripts
|
||||
let macos_isysroot = {
|
||||
let macos_sdk_root = options
|
||||
.sdk_root
|
||||
.join("../../../../MacOSX.platform/Developer/SDKs/MacOSX.sdk");
|
||||
if !macos_sdk_root.is_dir() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid SDK root {}",
|
||||
macos_sdk_root.display()
|
||||
));
|
||||
}
|
||||
format!("-isysroot {}", macos_sdk_root.display())
|
||||
};
|
||||
|
||||
let mut host_env = HashMap::<&str, &OsStr>::new();
|
||||
|
||||
host_env.insert("RUST_BACKTRACE", "1".as_ref());
|
||||
|
||||
host_env.insert("CFLAGS_x86_64_apple_darwin", macos_isysroot.as_ref());
|
||||
host_env.insert("CXXFLAGS_x86_64_apple_darwin", macos_isysroot.as_ref());
|
||||
|
||||
host_env.insert(
|
||||
"OBJC_INCLUDE_PATH_x86_64_apple_darwin",
|
||||
include_dir.as_os_str(),
|
||||
);
|
||||
|
||||
host_env.insert(
|
||||
"FRAMEWORK_SEARCH_PATHS",
|
||||
options.framework_search_paths.as_ref(),
|
||||
);
|
||||
host_env.insert(
|
||||
"GCC_PREPROCESSOR_DEFINITIONS",
|
||||
options.gcc_preprocessor_definitions.as_ref(),
|
||||
);
|
||||
host_env.insert("HEADER_SEARCH_PATHS", options.header_search_paths.as_ref());
|
||||
|
||||
let macos_target = Target::macos();
|
||||
|
||||
let isysroot = format!("-isysroot {}", options.sdk_root.display());
|
||||
|
||||
for arch in options.arches {
|
||||
// Set target-specific flags
|
||||
let triple = match arch.as_str() {
|
||||
"arm64" => "aarch64_apple_ios",
|
||||
"arm64-sim" => "aarch64_apple_ios_sim",
|
||||
"x86_64" => "x86_64_apple_ios",
|
||||
"Simulator" => continue,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Arch specified by Xcode was invalid. {} isn't a known arch",
|
||||
arch
|
||||
))
|
||||
}
|
||||
};
|
||||
let cflags = format!("CFLAGS_{}", triple);
|
||||
let cxxflags = format!("CFLAGS_{}", triple);
|
||||
let objc_include_path = format!("OBJC_INCLUDE_PATH_{}", triple);
|
||||
let mut target_env = host_env.clone();
|
||||
target_env.insert(cflags.as_ref(), isysroot.as_ref());
|
||||
target_env.insert(cxxflags.as_ref(), isysroot.as_ref());
|
||||
target_env.insert(objc_include_path.as_ref(), include_dir.as_ref());
|
||||
|
||||
let target = if macos {
|
||||
&macos_target
|
||||
} else {
|
||||
Target::for_arch(&arch).ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Arch specified by Xcode was invalid. {} isn't a known arch",
|
||||
arch
|
||||
)
|
||||
})?
|
||||
};
|
||||
target.compile_lib(
|
||||
config,
|
||||
metadata,
|
||||
cli_options.noise_level,
|
||||
true,
|
||||
profile,
|
||||
&env,
|
||||
target_env,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
269
tooling/cli/src/mobile/mod.rs
Normal file
269
tooling/cli/src/mobile/mod.rs
Normal file
@@ -0,0 +1,269 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{
|
||||
helpers::{app_paths::tauri_dir, config::Config as TauriConfig},
|
||||
interface::DevProcess,
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use cargo_mobile::{
|
||||
bossy,
|
||||
config::app::{App, Raw as RawAppConfig},
|
||||
env::Error as EnvError,
|
||||
opts::NoiseLevel,
|
||||
};
|
||||
use jsonrpsee::client_transport::ws::WsTransportClientBuilder;
|
||||
use jsonrpsee::core::client::{Client, ClientBuilder, ClientT};
|
||||
use jsonrpsee::rpc_params;
|
||||
use jsonrpsee::server::{RpcModule, ServerBuilder, ServerHandle};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared_child::SharedChild;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env::set_var,
|
||||
env::var,
|
||||
ffi::OsString,
|
||||
fmt::Write,
|
||||
net::SocketAddr,
|
||||
path::PathBuf,
|
||||
process::ExitStatus,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use cargo_mobile::env::Env;
|
||||
#[cfg(windows)]
|
||||
use cargo_mobile::os::Env;
|
||||
|
||||
pub mod android;
|
||||
mod init;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod ios;
|
||||
|
||||
const MIN_DEVICE_MATCH_SCORE: isize = 0;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DevChild {
|
||||
child: Arc<SharedChild>,
|
||||
manually_killed_process: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl DevChild {
|
||||
fn new(handle: bossy::Handle) -> Self {
|
||||
Self {
|
||||
child: Arc::new(SharedChild::new(handle.into()).unwrap()),
|
||||
manually_killed_process: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DevProcess for DevChild {
|
||||
fn kill(&self) -> std::io::Result<()> {
|
||||
self.child.kill()?;
|
||||
self.manually_killed_process.store(true, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
|
||||
self.child.try_wait()
|
||||
}
|
||||
|
||||
fn wait(&self) -> std::io::Result<ExitStatus> {
|
||||
self.child.wait()
|
||||
}
|
||||
|
||||
fn manually_killed_process(&self) -> bool {
|
||||
self.manually_killed_process.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn is_building_app(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum Target {
|
||||
Android,
|
||||
#[cfg(target_os = "macos")]
|
||||
Ios,
|
||||
}
|
||||
|
||||
impl Target {
|
||||
fn ide_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Android => "Android Studio",
|
||||
#[cfg(target_os = "macos")]
|
||||
Self::Ios => "Xcode",
|
||||
}
|
||||
}
|
||||
|
||||
fn command_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Android => "android",
|
||||
#[cfg(target_os = "macos")]
|
||||
Self::Ios => "ios",
|
||||
}
|
||||
}
|
||||
|
||||
fn ide_build_script_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Android => "android-studio-script",
|
||||
#[cfg(target_os = "macos")]
|
||||
Self::Ios => "xcode-script",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct CliOptions {
|
||||
pub features: Option<Vec<String>>,
|
||||
pub args: Vec<String>,
|
||||
pub noise_level: NoiseLevel,
|
||||
pub vars: HashMap<String, OsString>,
|
||||
}
|
||||
|
||||
fn env_vars() -> HashMap<String, OsString> {
|
||||
let mut vars = HashMap::new();
|
||||
for (k, v) in std::env::vars_os() {
|
||||
let k = k.to_string_lossy();
|
||||
if (k.starts_with("TAURI") && k != "TAURI_PRIVATE_KEY" && k != "TAURI_KEY_PASSWORD")
|
||||
|| k.starts_with("WRY")
|
||||
|| k == "TMPDIR"
|
||||
|| k == "PATH"
|
||||
{
|
||||
vars.insert(k.into_owned(), v);
|
||||
}
|
||||
}
|
||||
vars
|
||||
}
|
||||
|
||||
fn env() -> Result<Env, EnvError> {
|
||||
let env = Env::new()?.explicit_env_vars(env_vars());
|
||||
Ok(env)
|
||||
}
|
||||
|
||||
/// Writes CLI options to be used later on the Xcode and Android Studio build commands
|
||||
pub fn write_options(
|
||||
mut options: CliOptions,
|
||||
env: &mut Env,
|
||||
) -> crate::Result<(Runtime, ServerHandle)> {
|
||||
options.vars.extend(env_vars());
|
||||
|
||||
let runtime = Runtime::new().unwrap();
|
||||
let r: anyhow::Result<(ServerHandle, SocketAddr)> = runtime.block_on(async move {
|
||||
let server = ServerBuilder::default().build("127.0.0.1:0").await?;
|
||||
let addr = server.local_addr()?;
|
||||
|
||||
let mut module = RpcModule::new(());
|
||||
module.register_method("options", move |_, _| Ok(options.clone()))?;
|
||||
|
||||
let handle = server.start(module)?;
|
||||
|
||||
Ok((handle, addr))
|
||||
});
|
||||
let (handle, addr) = r?;
|
||||
|
||||
env.insert_env_var("TAURI_OPTIONS_SERVER_ADDR".into(), addr.to_string().into());
|
||||
|
||||
Ok((runtime, handle))
|
||||
}
|
||||
|
||||
fn read_options() -> CliOptions {
|
||||
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
let options = runtime
|
||||
.block_on(async move {
|
||||
let (tx, rx) = WsTransportClientBuilder::default()
|
||||
.build(
|
||||
format!(
|
||||
"ws://{}",
|
||||
var("TAURI_OPTIONS_SERVER_ADDR").expect("missing addr environment variable")
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.await?;
|
||||
let client: Client = ClientBuilder::default().build_with_tokio(tx, rx);
|
||||
let options: CliOptions = client.request("options", rpc_params![]).await?;
|
||||
Ok::<CliOptions, anyhow::Error>(options)
|
||||
})
|
||||
.expect("failed to read CLI options");
|
||||
|
||||
for (k, v) in &options.vars {
|
||||
set_var(k, v);
|
||||
}
|
||||
options
|
||||
}
|
||||
|
||||
fn get_app(config: &TauriConfig) -> App {
|
||||
let mut s = config.tauri.bundle.identifier.rsplit('.');
|
||||
let app_name = s.next().unwrap_or("app").to_string();
|
||||
let mut domain = String::new();
|
||||
for w in s {
|
||||
domain.push_str(w);
|
||||
domain.push('.');
|
||||
}
|
||||
domain.pop();
|
||||
|
||||
let s = config.tauri.bundle.identifier.split('.');
|
||||
let last = s.clone().count() - 1;
|
||||
let mut reverse_domain = String::new();
|
||||
for (i, w) in s.enumerate() {
|
||||
if i != last {
|
||||
reverse_domain.push_str(w);
|
||||
reverse_domain.push('.');
|
||||
}
|
||||
}
|
||||
reverse_domain.pop();
|
||||
|
||||
let manifest_path = tauri_dir().join("Cargo.toml");
|
||||
let app_name = if let Ok(manifest) = crate::interface::manifest::read_manifest(&manifest_path) {
|
||||
manifest
|
||||
.as_table()
|
||||
.get("package")
|
||||
.and_then(|p| p.as_table())
|
||||
.and_then(|p| p.get("name"))
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|n| n.to_string())
|
||||
.unwrap_or(app_name)
|
||||
} else {
|
||||
app_name
|
||||
};
|
||||
|
||||
let raw = RawAppConfig {
|
||||
name: app_name,
|
||||
stylized_name: config.package.product_name.clone(),
|
||||
domain,
|
||||
asset_dir: None,
|
||||
template_pack: None,
|
||||
};
|
||||
App::from_raw(tauri_dir(), raw).unwrap()
|
||||
}
|
||||
|
||||
fn ensure_init(project_dir: PathBuf, target: Target) -> Result<()> {
|
||||
if !project_dir.exists() {
|
||||
bail!(
|
||||
"{} project directory {} doesn't exist. Please run `tauri {} init` and try again.",
|
||||
target.ide_name(),
|
||||
project_dir.display(),
|
||||
target.command_name(),
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn log_finished(outputs: Vec<PathBuf>, kind: &str) {
|
||||
if !outputs.is_empty() {
|
||||
let mut printable_paths = String::new();
|
||||
for path in &outputs {
|
||||
writeln!(printable_paths, " {}", path.display()).unwrap();
|
||||
}
|
||||
|
||||
log::info!(action = "Finished"; "{} {}{} at:\n{}", outputs.len(), kind, if outputs.len() == 1 { "" } else { "s" }, printable_paths);
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,14 @@ description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
default-run = "app"
|
||||
edition = "2021"
|
||||
rust-version = "1.59"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = {{{ tauri_build_dep }}}
|
||||
|
||||
|
||||
8
tooling/cli/templates/app/src-tauri/src/desktop.rs
Normal file
8
tooling/cli/templates/app/src-tauri/src/desktop.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
pub fn main() {
|
||||
app::AppBuilder::new().run();
|
||||
}
|
||||
41
tooling/cli/templates/app/src-tauri/src/lib.rs
Normal file
41
tooling/cli/templates/app/src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use tauri::App;
|
||||
|
||||
#[cfg(mobile)]
|
||||
mod mobile;
|
||||
#[cfg(mobile)]
|
||||
pub use mobile::*;
|
||||
|
||||
pub type SetupHook = Box<dyn FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AppBuilder {
|
||||
setup: Option<SetupHook>,
|
||||
}
|
||||
|
||||
impl AppBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn setup<F>(mut self, setup: F) -> Self
|
||||
where
|
||||
F: FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send + 'static,
|
||||
{
|
||||
self.setup.replace(Box::new(setup));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(self) {
|
||||
let setup = self.setup;
|
||||
tauri::Builder::default()
|
||||
.setup(move |app| {
|
||||
if let Some(setup) = setup {
|
||||
(setup)(app)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
#[cfg(desktop)]
|
||||
mod desktop;
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
#[cfg(desktop)]
|
||||
desktop::main();
|
||||
}
|
||||
|
||||
4
tooling/cli/templates/app/src-tauri/src/mobile.rs
Normal file
4
tooling/cli/templates/app/src-tauri/src/mobile.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[tauri::mobile_entry_point]
|
||||
fn main() {
|
||||
super::AppBuilder::new().run()
|
||||
}
|
||||
12
tooling/cli/templates/mobile/android/.editorconfig
Normal file
12
tooling/cli/templates/mobile/android/.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
17
tooling/cli/templates/mobile/android/.gitignore
vendored
Normal file
17
tooling/cli/templates/mobile/android/.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
build
|
||||
/buildSrc/src/main/{{package-path}}/kotlin/BuildTask.kt
|
||||
/buildSrc/src/main/{{package-path}}/kotlin/RustPlugin.kt
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
1
tooling/cli/templates/mobile/android/app/.gitignore
vendored
Normal file
1
tooling/cli/templates/mobile/android/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/src/main/{{package-path}}/generated
|
||||
99
tooling/cli/templates/mobile/android/app/build.gradle.kts
Normal file
99
tooling/cli/templates/mobile/android/app/build.gradle.kts
Normal file
@@ -0,0 +1,99 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("rustPlugin")
|
||||
{{~#each android-app-plugins}}
|
||||
id("{{this}}"){{/each}}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 33
|
||||
defaultConfig {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||
applicationId = "{{reverse-domain app.domain}}.{{snake-case app.name}}"
|
||||
minSdk = {{android.min-sdk-version}}
|
||||
targetSdk = 33
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
sourceSets.getByName("main") {
|
||||
{{#if android.vulkan-validation}}// Vulkan validation layers
|
||||
val ndkHome = System.getenv("NDK_HOME")
|
||||
jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs")
|
||||
{{/if}}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
packagingOptions {
|
||||
{{~#each targets}}
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/{{this.abi}}/*.so")
|
||||
{{/each}}
|
||||
}
|
||||
}
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
flavorDimensions.add("abi")
|
||||
productFlavors {
|
||||
create("universal") {
|
||||
val abiList = findProperty("abiList") as? String
|
||||
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += abiList?.split(",")?.map { it.trim() } ?: listOf(
|
||||
{{~#each targets}}
|
||||
"{{this.abi}}",{{/each}}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
{{~#each targets}}
|
||||
|
||||
create("{{this.arch}}") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("{{this.abi}}")
|
||||
}
|
||||
}
|
||||
{{/each}}
|
||||
}
|
||||
|
||||
assetPacks += mutableSetOf({{quote-and-join-colon-prefix asset-packs}})
|
||||
}
|
||||
|
||||
rust {
|
||||
rootDirRel = "{{root-dir-rel}}"
|
||||
targets = listOf({{quote-and-join target-names}})
|
||||
arches = listOf({{quote-and-join arches}})
|
||||
}
|
||||
|
||||
dependencies {
|
||||
{{~#each android-app-dependencies-platform}}
|
||||
implementation(platform("{{this}}")){{/each}}
|
||||
{{~#each android-app-dependencies}}
|
||||
implementation("{{this}}"){{/each}}
|
||||
implementation("androidx.webkit:webkit:1.4.0")
|
||||
implementation("androidx.appcompat:appcompat:1.5.0")
|
||||
implementation("com.google.android.material:material:1.6.1")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
android.applicationVariants.all {
|
||||
tasks["mergeUniversalReleaseJniLibFolders"].dependsOn(tasks["rustBuildRelease"])
|
||||
tasks["mergeUniversalDebugJniLibFolders"].dependsOn(tasks["rustBuildDebug"])
|
||||
productFlavors.filter{ it.name != "universal" }.forEach { _ ->
|
||||
val archAndBuildType = name.capitalize()
|
||||
tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"])
|
||||
}
|
||||
}
|
||||
}
|
||||
21
tooling/cli/templates/mobile/android/app/proguard-rules.pro
vendored
Normal file
21
tooling/cli/templates/mobile/android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="{{reverse-domain app.domain}}.{{snake-case app.name}}">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.{{snake-case app.name}}"
|
||||
android:usesCleartextTraffic="${usesCleartextTraffic}">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,3 @@
|
||||
package {{reverse-domain app.domain}}.{{snake-case app.name}}
|
||||
|
||||
class MainActivity : TauriActivity()
|
||||
@@ -0,0 +1,5 @@
|
||||
package {{reverse-domain app.domain}}.{{snake-case app.name}}
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
abstract class TauriActivity : AppCompatActivity()
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user