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:
Lucas Fernandes Nogueira 2024-02-16 08:24:40 -03:00 committed by GitHub
parent 052e8b4311
commit edb11c138d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 170 additions and 53 deletions

View File

@ -0,0 +1,5 @@
---
'tauri-build': patch:enhance
---
Added `Attributes::plugin()` to register a plugin that is inlined in the application crate.

View File

@ -24,6 +24,7 @@ use tauri_utils::{
};
use std::{
collections::HashMap,
env::var_os,
fs::copy,
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.
#[derive(Debug, Default)]
pub struct Attributes {
@ -339,6 +375,7 @@ pub struct Attributes {
capabilities_path_pattern: Option<&'static str>,
#[cfg(feature = "codegen")]
codegen: Option<codegen::context::CodegenContext>,
inlined_plugins: HashMap<&'static str, InlinedPlugin>,
}
impl Attributes {
@ -365,6 +402,14 @@ impl Attributes {
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_attr(docsrs, doc(cfg(feature = "codegen")))]
#[must_use]
@ -473,7 +518,51 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
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(
out_dir.join(PLUGIN_MANIFESTS_FILE_NAME),
serde_json::to_string(&plugin_manifests)?,

View File

@ -8,6 +8,9 @@ fn main() {
codegen = codegen.dev();
}
tauri_build::try_build(tauri_build::Attributes::new().codegen(codegen))
.expect("failed to run tauri-build");
tauri_build::try_build(tauri_build::Attributes::new().codegen(codegen).plugin(
"app-menu",
tauri_build::InlinedPlugin::new().commands(&["toggle", "popup"]),
))
.expect("failed to run tauri-build");
}

View File

@ -4,6 +4,7 @@
"description": "permissions to run the app",
"windows": ["main", "main-*"],
"permissions": [
"app-menu:default",
"sample:allow-ping-scoped",
"sample:global-scope",
"path:default",

View File

@ -0,0 +1,3 @@
[default]
description = "Default permissions for the plugin"
permissions = ["allow-toggle", "allow-popup"]

View File

@ -29,37 +29,3 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {
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();
}

View File

@ -4,6 +4,8 @@
mod cmd;
#[cfg(desktop)]
mod menu_plugin;
#[cfg(desktop)]
mod tray;
use serde::Serialize;
@ -46,6 +48,7 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
let handle = app.handle();
tray::create_tray(handle)?;
handle.plugin(tauri_plugin_cli::init())?;
handle.plugin(menu_plugin::init())?;
}
#[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![
cmd::log_operation,
cmd::perform_request,
#[cfg(desktop)]
cmd::toggle_menu,
#[cfg(desktop)]
cmd::popup_context_menu
])
.build(tauri::tauri_build_context!())
.expect("error while building tauri application");

View 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()
}

View File

@ -1,5 +1,5 @@
<script>
import { onMount, tick } from "svelte";
import { onMount, tick } from 'svelte'
import { writable } from 'svelte/store'
import { invoke } from '@tauri-apps/api/core'
@ -13,7 +13,7 @@
document.addEventListener('keydown', (event) => {
if (event.ctrlKey && event.key === 'b') {
invoke('toggle_menu')
invoke('plugin:app-menu|toggle')
}
})
@ -81,7 +81,7 @@
// Console
let messages = writable([])
let consoleTextEl;
let consoleTextEl
async function onMessage(value) {
messages.update((r) => [
...r,
@ -90,10 +90,10 @@
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
(typeof value === 'string' ? value : JSON.stringify(value, null, 1)) +
'</pre>'
},
}
])
await tick();
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight;
await tick()
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight
}
// 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> ` +
html +
'</pre>'
},
}
])
await tick();
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight;
await tick()
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight
}
function clear() {
@ -329,13 +329,16 @@
hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
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}
>
<div class="i-codicon-clear-all" />
</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}
{@html r.html}
{/each}

View File

@ -17,7 +17,7 @@
})
function contextMenu() {
invoke('popup_context_menu')
invoke('plugin:app-menu|popup')
}
</script>