From edb11c138def2e317099db432479e3ca5dbf803f Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Fri, 16 Feb 2024 08:24:40 -0300 Subject: [PATCH] feat(build): support plugins that are defined in app crate (#8781) * feat(build): support plugins that are defined in app crate * dx --- .changes/inline-plugins.md | 5 + core/tauri-build/src/lib.rs | 91 ++++++++++++++++++- examples/api/src-tauri/build.rs | 7 +- .../api/src-tauri/capabilities/run-app.json | 1 + .../permissions/app-menu/default.toml | 3 + examples/api/src-tauri/src/cmd.rs | 34 ------- examples/api/src-tauri/src/lib.rs | 7 +- examples/api/src-tauri/src/menu_plugin.rs | 48 ++++++++++ examples/api/src/App.svelte | 25 ++--- examples/api/src/views/Welcome.svelte | 2 +- 10 files changed, 170 insertions(+), 53 deletions(-) create mode 100644 .changes/inline-plugins.md create mode 100644 examples/api/src-tauri/permissions/app-menu/default.toml create mode 100644 examples/api/src-tauri/src/menu_plugin.rs diff --git a/.changes/inline-plugins.md b/.changes/inline-plugins.md new file mode 100644 index 000000000..b2d7f52cc --- /dev/null +++ b/.changes/inline-plugins.md @@ -0,0 +1,5 @@ +--- +'tauri-build': patch:enhance +--- + +Added `Attributes::plugin()` to register a plugin that is inlined in the application crate. diff --git a/core/tauri-build/src/lib.rs b/core/tauri-build/src/lib.rs index 7d892ecb4..a86a7b95a 100644 --- a/core/tauri-build/src/lib.rs +++ b/core/tauri-build/src/lib.rs @@ -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, + 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)?, diff --git a/examples/api/src-tauri/build.rs b/examples/api/src-tauri/build.rs index e7b35e0b3..da2b155ed 100644 --- a/examples/api/src-tauri/build.rs +++ b/examples/api/src-tauri/build.rs @@ -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"); } diff --git a/examples/api/src-tauri/capabilities/run-app.json b/examples/api/src-tauri/capabilities/run-app.json index a95e15871..9aa2e3306 100644 --- a/examples/api/src-tauri/capabilities/run-app.json +++ b/examples/api/src-tauri/capabilities/run-app.json @@ -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", diff --git a/examples/api/src-tauri/permissions/app-menu/default.toml b/examples/api/src-tauri/permissions/app-menu/default.toml new file mode 100644 index 000000000..b17fbd19a --- /dev/null +++ b/examples/api/src-tauri/permissions/app-menu/default.toml @@ -0,0 +1,3 @@ +[default] +description = "Default permissions for the plugin" +permissions = ["allow-toggle", "allow-popup"] diff --git a/examples/api/src-tauri/src/cmd.rs b/examples/api/src-tauri/src/cmd.rs index 2c92decf7..e013e50a3 100644 --- a/examples/api/src-tauri/src/cmd.rs +++ b/examples/api/src-tauri/src/cmd.rs @@ -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(window: tauri::Window) { - 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( - app: tauri::AppHandle, - app_menu: tauri::State<'_, crate::AppMenu>, -) { - 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( - window: tauri::Window, - popup_menu: tauri::State<'_, crate::PopupMenu>, -) { - window.popup_menu(&popup_menu.0).unwrap(); -} diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index ba7960dfb..b219762af 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -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) + 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) + 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"); diff --git a/examples/api/src-tauri/src/menu_plugin.rs b/examples/api/src-tauri/src/menu_plugin.rs new file mode 100644 index 000000000..59e2101ad --- /dev/null +++ b/examples/api/src-tauri/src/menu_plugin.rs @@ -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(window: tauri::Window) { + 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( + app: tauri::AppHandle, + app_menu: tauri::State<'_, crate::AppMenu>, +) { + 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( + window: tauri::Window, + popup_menu: tauri::State<'_, crate::PopupMenu>, +) { + window.popup_menu(&popup_menu.0).unwrap(); +} + +pub fn init() -> TauriPlugin { + Builder::new("app-menu") + .invoke_handler(tauri::generate_handler![popup, toggle]) + .build() +} diff --git a/examples/api/src/App.svelte b/examples/api/src/App.svelte index b0288f2b0..3b077748b 100644 --- a/examples/api/src/App.svelte +++ b/examples/api/src/App.svelte @@ -1,5 +1,5 @@