mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-08-17 03:40:23 +03:00
feat(build): support plugins that are defined in app crate (#8781)
* feat(build): support plugins that are defined in app crate * dx
This commit is contained in:
parent
052e8b4311
commit
edb11c138d
5
.changes/inline-plugins.md
Normal file
5
.changes/inline-plugins.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'tauri-build': patch:enhance
|
||||||
|
---
|
||||||
|
|
||||||
|
Added `Attributes::plugin()` to register a plugin that is inlined in the application crate.
|
@ -24,6 +24,7 @@ use tauri_utils::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
env::var_os,
|
env::var_os,
|
||||||
fs::copy,
|
fs::copy,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -331,6 +332,41 @@ impl WindowsAttributes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Definition of a plugin that is part of the Tauri application instead of having its own crate.
|
||||||
|
///
|
||||||
|
/// By default it generates a plugin manifest that parses permissions from the `permissions/$plugin-name` directory.
|
||||||
|
/// To change the glob pattern that is used to find permissions, use [`Self::permissions_path_pattern`].
|
||||||
|
///
|
||||||
|
/// To autogenerate permissions for each of the plugin commands, see [`Self::commands`].
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct InlinedPlugin {
|
||||||
|
commands: &'static [&'static str],
|
||||||
|
permissions_path_pattern: Option<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlinedPlugin {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define a list of commands that gets permissions autogenerated in the format of `allow-$command` and `deny-$command`
|
||||||
|
/// where $command is the command in kebab-case.
|
||||||
|
pub fn commands(mut self, commands: &'static [&'static str]) -> Self {
|
||||||
|
self.commands = commands;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a glob pattern that is used to find the permissions of this inlined plugin.
|
||||||
|
///
|
||||||
|
/// **Note:** You must emit [rerun-if-changed] instructions for the plugin permissions directory.
|
||||||
|
///
|
||||||
|
/// By default it is `./permissions/$plugin-name/**/*`
|
||||||
|
pub fn permissions_path_pattern(mut self, pattern: &'static str) -> Self {
|
||||||
|
self.permissions_path_pattern.replace(pattern);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The attributes used on the build.
|
/// The attributes used on the build.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Attributes {
|
pub struct Attributes {
|
||||||
@ -339,6 +375,7 @@ pub struct Attributes {
|
|||||||
capabilities_path_pattern: Option<&'static str>,
|
capabilities_path_pattern: Option<&'static str>,
|
||||||
#[cfg(feature = "codegen")]
|
#[cfg(feature = "codegen")]
|
||||||
codegen: Option<codegen::context::CodegenContext>,
|
codegen: Option<codegen::context::CodegenContext>,
|
||||||
|
inlined_plugins: HashMap<&'static str, InlinedPlugin>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Attributes {
|
impl Attributes {
|
||||||
@ -365,6 +402,14 @@ impl Attributes {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds the given plugin to the list of inlined plugins (a plugin that is part of your application).
|
||||||
|
///
|
||||||
|
/// See [`InlinedPlugin`] for more information.
|
||||||
|
pub fn plugin(mut self, name: &'static str, plugin: InlinedPlugin) -> Self {
|
||||||
|
self.inlined_plugins.insert(name, plugin);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "codegen")]
|
#[cfg(feature = "codegen")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -473,7 +518,51 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
|||||||
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||||
|
|
||||||
manifest::check(&config, &mut manifest)?;
|
manifest::check(&config, &mut manifest)?;
|
||||||
let plugin_manifests = acl::get_plugin_manifests()?;
|
let mut plugin_manifests = acl::get_plugin_manifests()?;
|
||||||
|
for (name, plugin) in attributes.inlined_plugins {
|
||||||
|
let plugin_out_dir = out_dir.join("plugins").join(name);
|
||||||
|
|
||||||
|
let mut permission_files = if plugin.commands.is_empty() {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
tauri_utils::acl::build::autogenerate_command_permissions(
|
||||||
|
&plugin_out_dir,
|
||||||
|
plugin.commands,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
tauri_utils::acl::build::define_permissions(
|
||||||
|
&plugin_out_dir.join("*").to_string_lossy(),
|
||||||
|
name,
|
||||||
|
&plugin_out_dir,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(pattern) = plugin.permissions_path_pattern {
|
||||||
|
permission_files.extend(tauri_utils::acl::build::define_permissions(
|
||||||
|
pattern,
|
||||||
|
name,
|
||||||
|
&plugin_out_dir,
|
||||||
|
)?);
|
||||||
|
} else {
|
||||||
|
let default_permissions_path = Path::new("permissions").join(name);
|
||||||
|
println!(
|
||||||
|
"cargo:rerun-if-changed={}",
|
||||||
|
default_permissions_path.display()
|
||||||
|
);
|
||||||
|
permission_files.extend(tauri_utils::acl::build::define_permissions(
|
||||||
|
&default_permissions_path
|
||||||
|
.join("**")
|
||||||
|
.join("*")
|
||||||
|
.to_string_lossy(),
|
||||||
|
name,
|
||||||
|
&plugin_out_dir,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifest = tauri_utils::acl::plugin::Manifest::new(permission_files, None);
|
||||||
|
plugin_manifests.insert(name.into(), manifest);
|
||||||
|
}
|
||||||
|
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
out_dir.join(PLUGIN_MANIFESTS_FILE_NAME),
|
out_dir.join(PLUGIN_MANIFESTS_FILE_NAME),
|
||||||
serde_json::to_string(&plugin_manifests)?,
|
serde_json::to_string(&plugin_manifests)?,
|
||||||
|
@ -8,6 +8,9 @@ fn main() {
|
|||||||
codegen = codegen.dev();
|
codegen = codegen.dev();
|
||||||
}
|
}
|
||||||
|
|
||||||
tauri_build::try_build(tauri_build::Attributes::new().codegen(codegen))
|
tauri_build::try_build(tauri_build::Attributes::new().codegen(codegen).plugin(
|
||||||
.expect("failed to run tauri-build");
|
"app-menu",
|
||||||
|
tauri_build::InlinedPlugin::new().commands(&["toggle", "popup"]),
|
||||||
|
))
|
||||||
|
.expect("failed to run tauri-build");
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"description": "permissions to run the app",
|
"description": "permissions to run the app",
|
||||||
"windows": ["main", "main-*"],
|
"windows": ["main", "main-*"],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
"app-menu:default",
|
||||||
"sample:allow-ping-scoped",
|
"sample:allow-ping-scoped",
|
||||||
"sample:global-scope",
|
"sample:global-scope",
|
||||||
"path:default",
|
"path:default",
|
||||||
|
3
examples/api/src-tauri/permissions/app-menu/default.toml
Normal file
3
examples/api/src-tauri/permissions/app-menu/default.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[default]
|
||||||
|
description = "Default permissions for the plugin"
|
||||||
|
permissions = ["allow-toggle", "allow-popup"]
|
@ -29,37 +29,3 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {
|
|||||||
message: "message response".into(),
|
message: "message response".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(desktop, not(target_os = "macos")))]
|
|
||||||
#[command]
|
|
||||||
pub fn toggle_menu<R: tauri::Runtime>(window: tauri::Window<R>) {
|
|
||||||
if window.is_menu_visible().unwrap_or_default() {
|
|
||||||
let _ = window.hide_menu();
|
|
||||||
} else {
|
|
||||||
let _ = window.show_menu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
#[command]
|
|
||||||
pub fn toggle_menu<R: tauri::Runtime>(
|
|
||||||
app: tauri::AppHandle<R>,
|
|
||||||
app_menu: tauri::State<'_, crate::AppMenu<R>>,
|
|
||||||
) {
|
|
||||||
if let Some(menu) = app.remove_menu().unwrap() {
|
|
||||||
app_menu.0.lock().unwrap().replace(menu);
|
|
||||||
} else {
|
|
||||||
app
|
|
||||||
.set_menu(app_menu.0.lock().unwrap().clone().expect("no app menu"))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(desktop)]
|
|
||||||
#[command]
|
|
||||||
pub fn popup_context_menu<R: tauri::Runtime>(
|
|
||||||
window: tauri::Window<R>,
|
|
||||||
popup_menu: tauri::State<'_, crate::PopupMenu<R>>,
|
|
||||||
) {
|
|
||||||
window.popup_menu(&popup_menu.0).unwrap();
|
|
||||||
}
|
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
mod cmd;
|
mod cmd;
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
|
mod menu_plugin;
|
||||||
|
#[cfg(desktop)]
|
||||||
mod tray;
|
mod tray;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@ -46,6 +48,7 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
|||||||
let handle = app.handle();
|
let handle = app.handle();
|
||||||
tray::create_tray(handle)?;
|
tray::create_tray(handle)?;
|
||||||
handle.plugin(tauri_plugin_cli::init())?;
|
handle.plugin(tauri_plugin_cli::init())?;
|
||||||
|
handle.plugin(menu_plugin::init())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@ -140,10 +143,6 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
cmd::log_operation,
|
cmd::log_operation,
|
||||||
cmd::perform_request,
|
cmd::perform_request,
|
||||||
#[cfg(desktop)]
|
|
||||||
cmd::toggle_menu,
|
|
||||||
#[cfg(desktop)]
|
|
||||||
cmd::popup_context_menu
|
|
||||||
])
|
])
|
||||||
.build(tauri::tauri_build_context!())
|
.build(tauri::tauri_build_context!())
|
||||||
.expect("error while building tauri application");
|
.expect("error while building tauri application");
|
||||||
|
48
examples/api/src-tauri/src/menu_plugin.rs
Normal file
48
examples/api/src-tauri/src/menu_plugin.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use tauri::{
|
||||||
|
command,
|
||||||
|
plugin::{Builder, TauriPlugin},
|
||||||
|
Runtime,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
#[command]
|
||||||
|
pub fn toggle<R: tauri::Runtime>(window: tauri::Window<R>) {
|
||||||
|
if window.is_menu_visible().unwrap_or_default() {
|
||||||
|
let _ = window.hide_menu();
|
||||||
|
} else {
|
||||||
|
let _ = window.show_menu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[command]
|
||||||
|
pub fn toggle<R: tauri::Runtime>(
|
||||||
|
app: tauri::AppHandle<R>,
|
||||||
|
app_menu: tauri::State<'_, crate::AppMenu<R>>,
|
||||||
|
) {
|
||||||
|
if let Some(menu) = app.remove_menu().unwrap() {
|
||||||
|
app_menu.0.lock().unwrap().replace(menu);
|
||||||
|
} else {
|
||||||
|
app
|
||||||
|
.set_menu(app_menu.0.lock().unwrap().clone().expect("no app menu"))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
pub fn popup<R: tauri::Runtime>(
|
||||||
|
window: tauri::Window<R>,
|
||||||
|
popup_menu: tauri::State<'_, crate::PopupMenu<R>>,
|
||||||
|
) {
|
||||||
|
window.popup_menu(&popup_menu.0).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||||
|
Builder::new("app-menu")
|
||||||
|
.invoke_handler(tauri::generate_handler![popup, toggle])
|
||||||
|
.build()
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount, tick } from "svelte";
|
import { onMount, tick } from 'svelte'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
|
||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
if (event.ctrlKey && event.key === 'b') {
|
if (event.ctrlKey && event.key === 'b') {
|
||||||
invoke('toggle_menu')
|
invoke('plugin:app-menu|toggle')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -81,7 +81,7 @@
|
|||||||
|
|
||||||
// Console
|
// Console
|
||||||
let messages = writable([])
|
let messages = writable([])
|
||||||
let consoleTextEl;
|
let consoleTextEl
|
||||||
async function onMessage(value) {
|
async function onMessage(value) {
|
||||||
messages.update((r) => [
|
messages.update((r) => [
|
||||||
...r,
|
...r,
|
||||||
@ -90,10 +90,10 @@
|
|||||||
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
|
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
|
||||||
(typeof value === 'string' ? value : JSON.stringify(value, null, 1)) +
|
(typeof value === 'string' ? value : JSON.stringify(value, null, 1)) +
|
||||||
'</pre>'
|
'</pre>'
|
||||||
},
|
}
|
||||||
])
|
])
|
||||||
await tick();
|
await tick()
|
||||||
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight;
|
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// this function is renders HTML without sanitizing it so it's insecure
|
// this function is renders HTML without sanitizing it so it's insecure
|
||||||
@ -106,10 +106,10 @@
|
|||||||
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
|
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
|
||||||
html +
|
html +
|
||||||
'</pre>'
|
'</pre>'
|
||||||
},
|
}
|
||||||
])
|
])
|
||||||
await tick();
|
await tick()
|
||||||
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight;
|
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
@ -329,13 +329,16 @@
|
|||||||
hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
|
hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
|
||||||
active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25
|
active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25
|
||||||
"
|
"
|
||||||
on:keypress={(e) => e.key === "Enter"? clear() : {} }
|
on:keypress={(e) => (e.key === 'Enter' ? clear() : {})}
|
||||||
on:click={clear}
|
on:click={clear}
|
||||||
>
|
>
|
||||||
<div class="i-codicon-clear-all" />
|
<div class="i-codicon-clear-all" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div bind:this={consoleTextEl} class="px-2 overflow-y-auto all:font-mono code-block all:text-xs select-text mr-2">
|
<div
|
||||||
|
bind:this={consoleTextEl}
|
||||||
|
class="px-2 overflow-y-auto all:font-mono code-block all:text-xs select-text mr-2"
|
||||||
|
>
|
||||||
{#each $messages as r}
|
{#each $messages as r}
|
||||||
{@html r.html}
|
{@html r.html}
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
function contextMenu() {
|
function contextMenu() {
|
||||||
invoke('popup_context_menu')
|
invoke('plugin:app-menu|popup')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user