Skip to content

Commit 013f8f6

Browse files
Legend-MasterlucasfernogFabianLars
authored
feat: add a new option to remove unused commands (#12890)
* Add a new option to remove unused commands * Fix compile * Add markers to all core plugins * Clippy * Add allow unused when running with this * Use build script to generate allowed-commands.json * Clean up and add proper reruns * Wrong path * Revert to #[cfg_attr(not(debug_assertions), allow(unused))] * Add change files * Some more docs * Add version requirement note * Avoid rerun if no capabilities folder * Remove unused box * small cleanup * fix channel * implement for app handler too * rely on core:default for channel perms * Move this feature to config * Docs change * Forget one last remove_unused_commands * Remove removeUnusedCommands from helloworld * tell handler that the app ACL manifest exists * update change file * update doc * update change file * Use a struct to pass the data instead of env var * Clippy * Fix can't exclude inlined plugins on Windows due to UNC paths... * Apply suggestion from code review * Remove remove on empty to tauri-build * Revert "Remove remove on empty to tauri-build" This reverts commit b727dd6. * Centralize remove_file(allowed_commands_file_path) * Escape glob pattern * update change file * remove unused commands for dev too * Update crates/tauri-utils/src/config.rs Co-authored-by: Fabian-Lars <github@fabianlars.de> * regen schema --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app> Co-authored-by: Fabian-Lars <github@fabianlars.de>
1 parent 5591a4f commit 013f8f6

File tree

40 files changed

+695
-274
lines changed

40 files changed

+695
-274
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
tauri-cli: 'minor:feat'
3+
---
4+
5+
Reads `build > removeUnusedCommands` from the config file and pass in the environment variables on the build command to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. For this to work on inlined plugins you must add a `#![plugin(<insert_plugin_name>)]` inside the `tauri::generate_handler![]` usage and the app manifest must be set.

.changes/remove-unused-commands.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
tauri: 'minor:feat'
3+
tauri-build: 'minor:feat'
4+
tauri-codegen: 'minor:feat'
5+
tauri-macros: 'minor:feat'
6+
tauri-plugin: 'minor:feat'
7+
tauri-utils: 'minor:feat'
8+
---
9+
10+
Added `build > removeUnusedCommands` to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. Note this won't be accounting for dynamically added ACLs so make sure to check it when using this.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,4 @@ opt-level = "s"
7070
[patch.crates-io]
7171
schemars_derive = { git = 'https://github.com/tauri-apps/schemars.git', branch = 'feat/preserve-description-newlines' }
7272
tauri = { path = "./crates/tauri" }
73+
tauri-plugin = { path = "./crates/tauri-plugin" }

crates/tauri-build/src/acl.rs

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use std::{
1111
use anyhow::{Context, Result};
1212
use tauri_utils::{
1313
acl::{
14-
capability::Capability, manifest::Manifest, schema::CAPABILITIES_SCHEMA_FOLDER_PATH,
14+
capability::Capability,
15+
manifest::{Manifest, PermissionFile},
16+
schema::CAPABILITIES_SCHEMA_FOLDER_PATH,
1517
ACL_MANIFESTS_FILE_NAME, APP_ACL_KEY, CAPABILITIES_FILE_NAME,
1618
},
1719
platform::Target,
@@ -155,11 +157,17 @@ fn read_plugins_manifests() -> Result<BTreeMap<String, Manifest>> {
155157
Ok(manifests)
156158
}
157159

160+
struct InlinedPuginsAcl {
161+
manifests: BTreeMap<String, Manifest>,
162+
permission_files: BTreeMap<String, Vec<PermissionFile>>,
163+
}
164+
158165
fn inline_plugins(
159166
out_dir: &Path,
160167
inlined_plugins: HashMap<&'static str, InlinedPlugin>,
161-
) -> Result<BTreeMap<String, Manifest>> {
168+
) -> Result<InlinedPuginsAcl> {
162169
let mut acl_manifests = BTreeMap::new();
170+
let mut permission_files_map = BTreeMap::new();
163171

164172
for (name, plugin) in inlined_plugins {
165173
let plugin_out_dir = out_dir.join("plugins").join(name);
@@ -236,18 +244,29 @@ permissions = [{default_permissions}]
236244
)?);
237245
}
238246

247+
permission_files_map.insert(name.into(), permission_files.clone());
248+
239249
let manifest = tauri_utils::acl::manifest::Manifest::new(permission_files, None);
240250
acl_manifests.insert(name.into(), manifest);
241251
}
242252

243-
Ok(acl_manifests)
253+
Ok(InlinedPuginsAcl {
254+
manifests: acl_manifests,
255+
permission_files: permission_files_map,
256+
})
257+
}
258+
259+
#[derive(Debug)]
260+
struct AppManifestAcl {
261+
manifest: Manifest,
262+
permission_files: Vec<PermissionFile>,
244263
}
245264

246265
fn app_manifest_permissions(
247266
out_dir: &Path,
248267
manifest: AppManifest,
249268
inlined_plugins: &HashMap<&'static str, InlinedPlugin>,
250-
) -> Result<Manifest> {
269+
) -> Result<AppManifestAcl> {
251270
let app_out_dir = out_dir.join("app-manifest");
252271
fs::create_dir_all(&app_out_dir)?;
253272
let pkg_name = "__app__";
@@ -290,6 +309,7 @@ fn app_manifest_permissions(
290309
let inlined_plugins_permissions: Vec<_> = inlined_plugins
291310
.keys()
292311
.map(|name| permissions_root.join(name))
312+
.flat_map(|p| p.canonicalize())
293313
.collect();
294314

295315
permission_files.extend(tauri_utils::acl::build::define_permissions(
@@ -308,10 +328,10 @@ fn app_manifest_permissions(
308328
)?);
309329
}
310330

311-
Ok(tauri_utils::acl::manifest::Manifest::new(
312-
permission_files,
313-
None,
314-
))
331+
Ok(AppManifestAcl {
332+
permission_files: permission_files.clone(),
333+
manifest: tauri_utils::acl::manifest::Manifest::new(permission_files, None),
334+
})
315335
}
316336

317337
fn validate_capabilities(
@@ -380,19 +400,21 @@ fn validate_capabilities(
380400
pub fn build(out_dir: &Path, target: Target, attributes: &Attributes) -> super::Result<()> {
381401
let mut acl_manifests = read_plugins_manifests()?;
382402

383-
let app_manifest = app_manifest_permissions(
403+
let app_acl = app_manifest_permissions(
384404
out_dir,
385405
attributes.app_manifest,
386406
&attributes.inlined_plugins,
387407
)?;
388-
if app_manifest.default_permission.is_some()
389-
|| !app_manifest.permission_sets.is_empty()
390-
|| !app_manifest.permissions.is_empty()
391-
{
392-
acl_manifests.insert(APP_ACL_KEY.into(), app_manifest);
408+
let has_app_manifest = app_acl.manifest.default_permission.is_some()
409+
|| !app_acl.manifest.permission_sets.is_empty()
410+
|| !app_acl.manifest.permissions.is_empty();
411+
if has_app_manifest {
412+
acl_manifests.insert(APP_ACL_KEY.into(), app_acl.manifest);
393413
}
394414

395-
acl_manifests.extend(inline_plugins(out_dir, attributes.inlined_plugins.clone())?);
415+
let inline_plugins_acl = inline_plugins(out_dir, attributes.inlined_plugins.clone())?;
416+
417+
acl_manifests.extend(inline_plugins_acl.manifests);
396418

397419
let acl_manifests_path = save_acl_manifests(&acl_manifests)?;
398420
fs::copy(acl_manifests_path, out_dir.join(ACL_MANIFESTS_FILE_NAME))?;
@@ -412,5 +434,12 @@ pub fn build(out_dir: &Path, target: Target, attributes: &Attributes) -> super::
412434

413435
tauri_utils::plugin::save_global_api_scripts_paths(out_dir);
414436

437+
let mut permissions_map = inline_plugins_acl.permission_files;
438+
if has_app_manifest {
439+
permissions_map.insert(APP_ACL_KEY.to_string(), app_acl.permission_files);
440+
}
441+
442+
tauri_utils::acl::build::generate_allowed_commands(out_dir, permissions_map)?;
443+
415444
Ok(())
416445
}

crates/tauri-build/src/lib.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -456,12 +456,6 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
456456
use anyhow::anyhow;
457457

458458
println!("cargo:rerun-if-env-changed=TAURI_CONFIG");
459-
#[cfg(feature = "config-json")]
460-
println!("cargo:rerun-if-changed=tauri.conf.json");
461-
#[cfg(feature = "config-json5")]
462-
println!("cargo:rerun-if-changed=tauri.conf.json5");
463-
#[cfg(feature = "config-toml")]
464-
println!("cargo:rerun-if-changed=Tauri.toml");
465459

466460
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
467461
let mobile = target_os == "ios" || target_os == "android";
@@ -471,12 +465,11 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
471465
let target_triple = env::var("TARGET").unwrap();
472466
let target = tauri_utils::platform::Target::from_triple(&target_triple);
473467

474-
let (config, merged_config_path) =
475-
tauri_utils::config::parse::read_from(target, env::current_dir().unwrap())?;
476-
if let Some(merged_config_path) = merged_config_path {
477-
println!("cargo:rerun-if-changed={}", merged_config_path.display());
468+
let (mut config, config_paths) =
469+
tauri_utils::config::parse::read_from(target, &env::current_dir().unwrap())?;
470+
for config_file_path in config_paths {
471+
println!("cargo:rerun-if-changed={}", config_file_path.display());
478472
}
479-
let mut config = serde_json::from_value(config)?;
480473
if let Ok(env) = env::var("TAURI_CONFIG") {
481474
let merge_config: serde_json::Value = serde_json::from_str(&env)?;
482475
json_patch::merge(&mut config, &merge_config);

crates/tauri-cli/config.schema.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@
6969
},
7070
"build": {
7171
"description": "The build configuration.",
72-
"default": {},
72+
"default": {
73+
"removeUnusedCommands": false
74+
},
7375
"allOf": [
7476
{
7577
"$ref": "#/definitions/BuildConfig"
@@ -1797,6 +1799,11 @@
17971799
"items": {
17981800
"type": "string"
17991801
}
1802+
},
1803+
"removeUnusedCommands": {
1804+
"description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4",
1805+
"default": false,
1806+
"type": "boolean"
18001807
}
18011808
},
18021809
"additionalProperties": false

crates/tauri-cli/src/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ pub fn setup(
182182
return Err(anyhow::anyhow!(
183183
"The configured frontendDist includes the `{:?}` {}. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.",
184184
out_folders,
185-
if out_folders.len() == 1 { "folder" }else { "folders" }
185+
if out_folders.len() == 1 { "folder" } else { "folders" }
186186
)
187187
);
188188
}

crates/tauri-cli/src/helpers/config.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use itertools::Itertools;
66
use json_patch::merge;
77
use serde_json::Value as JsonValue;
88

9+
use tauri_utils::acl::REMOVE_UNUSED_COMMANDS_ENV_VAR;
910
pub use tauri_utils::{config::*, platform::Target};
1011

1112
use std::{
@@ -153,7 +154,7 @@ fn get_internal(
153154
let mut extensions = HashMap::new();
154155

155156
if let Some((platform_config, config_path)) =
156-
tauri_utils::config::parse::read_platform(target, tauri_dir.to_path_buf())?
157+
tauri_utils::config::parse::read_platform(target, tauri_dir)?
157158
{
158159
merge(&mut config, &platform_config);
159160
extensions.insert(
@@ -213,6 +214,10 @@ fn get_internal(
213214
);
214215
}
215216

217+
if config.build.remove_unused_commands {
218+
std::env::set_var(REMOVE_UNUSED_COMMANDS_ENV_VAR, tauri_dir);
219+
}
220+
216221
*config_handle().lock().unwrap() = Some(ConfigMetadata {
217222
target,
218223
inner: config,

crates/tauri-codegen/src/context.rs

Lines changed: 12 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ use proc_macro2::TokenStream;
1818
use quote::quote;
1919
use sha2::{Digest, Sha256};
2020
use syn::Expr;
21-
use tauri_utils::acl::{ACL_MANIFESTS_FILE_NAME, CAPABILITIES_FILE_NAME};
2221
use tauri_utils::{
23-
acl::capability::{Capability, CapabilityFile},
24-
acl::manifest::Manifest,
25-
acl::resolved::Resolved,
22+
acl::{
23+
get_capabilities, manifest::Manifest, resolved::Resolved, ACL_MANIFESTS_FILE_NAME,
24+
CAPABILITIES_FILE_NAME,
25+
},
2626
assets::AssetKey,
27-
config::{CapabilityEntry, Config, FrontendDist, PatternKind},
27+
config::{Config, FrontendDist, PatternKind},
2828
html::{inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, NodeRef},
2929
platform::Target,
3030
tokens::{map_lit, str_lit},
@@ -386,34 +386,14 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
386386
};
387387

388388
let capabilities_file_path = out_dir.join(CAPABILITIES_FILE_NAME);
389-
let mut capabilities_from_files: BTreeMap<String, Capability> = if capabilities_file_path.exists()
390-
{
391-
let capabilities_file =
392-
std::fs::read_to_string(capabilities_file_path).expect("failed to read capabilities");
393-
serde_json::from_str(&capabilities_file).expect("failed to parse capabilities")
394-
} else {
395-
Default::default()
396-
};
389+
let capabilities = get_capabilities(
390+
&config,
391+
Some(&capabilities_file_path),
392+
additional_capabilities.as_deref(),
393+
)
394+
.unwrap();
397395

398-
let mut capabilities = if config.app.security.capabilities.is_empty() {
399-
capabilities_from_files
400-
} else {
401-
let mut capabilities = BTreeMap::new();
402-
for capability_entry in &config.app.security.capabilities {
403-
match capability_entry {
404-
CapabilityEntry::Inlined(capability) => {
405-
capabilities.insert(capability.identifier.clone(), capability.clone());
406-
}
407-
CapabilityEntry::Reference(id) => {
408-
let capability = capabilities_from_files
409-
.remove(id)
410-
.unwrap_or_else(|| panic!("capability with identifier {id} not found"));
411-
capabilities.insert(id.clone(), capability);
412-
}
413-
}
414-
}
415-
capabilities
416-
};
396+
let resolved = Resolved::resolve(&acl, capabilities, target).expect("failed to resolve ACL");
417397

418398
let acl_tokens = map_lit(
419399
quote! { ::std::collections::BTreeMap },
@@ -422,29 +402,6 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
422402
identity,
423403
);
424404

425-
if let Some(paths) = additional_capabilities {
426-
for path in paths {
427-
let capability = CapabilityFile::load(&path)
428-
.unwrap_or_else(|e| panic!("failed to read capability {}: {e}", path.display()));
429-
match capability {
430-
CapabilityFile::Capability(c) => {
431-
capabilities.insert(c.identifier.clone(), c);
432-
}
433-
CapabilityFile::List(capabilities_list)
434-
| CapabilityFile::NamedList {
435-
capabilities: capabilities_list,
436-
} => {
437-
capabilities.extend(
438-
capabilities_list
439-
.into_iter()
440-
.map(|c| (c.identifier.clone(), c)),
441-
);
442-
}
443-
}
444-
}
445-
}
446-
447-
let resolved = Resolved::resolve(&acl, capabilities, target).expect("failed to resolve ACL");
448405
let runtime_authority = quote!(#root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved));
449406

450407
let plugin_global_api_scripts = if config.app.with_global_tauri {

0 commit comments

Comments
 (0)