Skip to content

Commit

Permalink
feat(build): support plugins that are defined in app crate (#8781)
Browse files Browse the repository at this point in the history
* feat(build): support plugins that are defined in app crate

* dx
  • Loading branch information
lucasfernog committed Feb 16, 2024
1 parent 052e8b4 commit edb11c1
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 53 deletions.
5 changes: 5 additions & 0 deletions .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.
91 changes: 90 additions & 1 deletion core/tauri-build/src/lib.rs
Expand Up @@ -24,6 +24,7 @@ use tauri_utils::{
};

use std::{
collections::HashMap,
env::var_os,
fs::copy,
path::{Path, PathBuf},
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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]
Expand Down Expand Up @@ -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)?,
Expand Down
7 changes: 5 additions & 2 deletions examples/api/src-tauri/build.rs
Expand Up @@ -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");
}
1 change: 1 addition & 0 deletions examples/api/src-tauri/capabilities/run-app.json
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions 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"]
34 changes: 0 additions & 34 deletions examples/api/src-tauri/src/cmd.rs
Expand Up @@ -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();
}
7 changes: 3 additions & 4 deletions examples/api/src-tauri/src/lib.rs
Expand Up @@ -4,6 +4,8 @@

mod cmd;
#[cfg(desktop)]
mod menu_plugin;
#[cfg(desktop)]
mod tray;

use serde::Serialize;
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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");
Expand Down
48 changes: 48 additions & 0 deletions 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<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()
}
25 changes: 14 additions & 11 deletions examples/api/src/App.svelte
@@ -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'
Expand All @@ -13,7 +13,7 @@
document.addEventListener('keydown', (event) => {
if (event.ctrlKey && event.key === 'b') {
invoke('toggle_menu')
invoke('plugin:app-menu|toggle')
}
})
Expand Down Expand Up @@ -81,7 +81,7 @@
// Console
let messages = writable([])
let consoleTextEl;
let consoleTextEl
async function onMessage(value) {
messages.update((r) => [
...r,
Expand All @@ -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
Expand All @@ -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() {
Expand Down Expand Up @@ -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}
Expand Down
2 changes: 1 addition & 1 deletion examples/api/src/views/Welcome.svelte
Expand Up @@ -17,7 +17,7 @@
})
function contextMenu() {
invoke('popup_context_menu')
invoke('plugin:app-menu|popup')
}
</script>

Expand Down

0 comments on commit edb11c1

Please sign in to comment.