Skip to content

Commit 8d16a80

Browse files
authored
feat(codegen): allow defining additional capabilities, closes #8798 (#8802)
* refactor(core): capabilities must be referenced on the Tauri config file * add all capabilities by default * feat(codegen): allow defining additional capabilities, closes #8798 * undo example * lint * move add_capability to runtime authority * add change files * go through code review * fix tests * remove tokens option
1 parent 770051a commit 8d16a80

22 files changed

Lines changed: 452 additions & 240 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-macros": patch:enhance
3+
"tauri-codegen": patch:enhance
4+
---
5+
6+
The `generate_context` proc macro now accepts a `capabilities` attribute where the value is an array of file paths that can be [conditionally compiled](https://doc.rust-lang.org/reference/conditional-compilation.html). These capabilities are added to the application along the capabilities defined in the Tauri configuration file.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri-utils": patch:enhance
3+
---
4+
5+
The `Context` struct now includes the runtime authority instead of the resolved ACL. This does not impact most applications.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri-build": patch:enhance
3+
---
4+
5+
Added `CodegenContext::capability` to include a capability file dynamically.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri-utils": patch:enhance
3+
---
4+
5+
Refactored the capability types and resolution algorithm.

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.

core/tauri-build/src/acl.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ use schemars::{
1717
schema_for,
1818
};
1919
use tauri_utils::{
20-
acl::{build::CapabilityFile, capability::Capability, plugin::Manifest},
20+
acl::{
21+
capability::{Capability, CapabilityFile},
22+
plugin::Manifest,
23+
},
2124
platform::Target,
2225
};
2326

core/tauri-build/src/codegen/context.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{
77
env::var,
88
fs::{create_dir_all, File},
99
io::{BufWriter, Write},
10-
path::PathBuf,
10+
path::{Path, PathBuf},
1111
};
1212
use tauri_codegen::{context_codegen, ContextData};
1313
use tauri_utils::config::FrontendDist;
@@ -20,6 +20,7 @@ pub struct CodegenContext {
2020
dev: bool,
2121
config_path: PathBuf,
2222
out_file: PathBuf,
23+
capabilities: Option<Vec<PathBuf>>,
2324
}
2425

2526
impl Default for CodegenContext {
@@ -28,6 +29,7 @@ impl Default for CodegenContext {
2829
dev: false,
2930
config_path: PathBuf::from("tauri.conf.json"),
3031
out_file: PathBuf::from("tauri-build-context.rs"),
32+
capabilities: None,
3133
}
3234
}
3335
}
@@ -74,6 +76,16 @@ impl CodegenContext {
7476
self
7577
}
7678

79+
/// Adds a capability file to the generated context.
80+
#[must_use]
81+
pub fn capability<P: AsRef<Path>>(mut self, path: P) -> Self {
82+
self
83+
.capabilities
84+
.get_or_insert_with(Default::default)
85+
.push(path.as_ref().to_path_buf());
86+
self
87+
}
88+
7789
/// Generate the code and write it to the output file - returning the path it was saved to.
7890
///
7991
/// Unless you are doing something special with this builder, you don't need to do anything with
@@ -125,6 +137,7 @@ impl CodegenContext {
125137
// it's very hard to have a build script for unit tests, so assume this is always called from
126138
// outside the tauri crate, making the ::tauri root valid.
127139
root: quote::quote!(::tauri),
140+
capabilities: self.capabilities,
128141
})?;
129142

130143
// get the full output file path

core/tauri-codegen/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ sha2 = "0.10"
1717
base64 = "0.21"
1818
proc-macro2 = "1"
1919
quote = "1"
20-
serde = { version = "1", features = ["derive"] }
20+
syn = "2"
21+
serde = { version = "1", features = [ "derive" ] }
2122
serde_json = "1"
2223
tauri-utils = { version = "2.0.0-beta.1", path = "../tauri-utils", features = [ "build" ] }
2324
thiserror = "1"

core/tauri-codegen/src/context.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// SPDX-License-Identifier: MIT
44

55
use std::collections::BTreeMap;
6+
use std::convert::identity;
67
use std::path::{Path, PathBuf};
78
use std::{ffi::OsStr, str::FromStr};
89

@@ -11,7 +12,7 @@ use proc_macro2::TokenStream;
1112
use quote::quote;
1213
use sha2::{Digest, Sha256};
1314

14-
use tauri_utils::acl::capability::Capability;
15+
use tauri_utils::acl::capability::{Capability, CapabilityFile};
1516
use tauri_utils::acl::plugin::Manifest;
1617
use tauri_utils::acl::resolved::Resolved;
1718
use tauri_utils::assets::AssetKey;
@@ -20,6 +21,7 @@ use tauri_utils::html::{
2021
inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node,
2122
};
2223
use tauri_utils::platform::Target;
24+
use tauri_utils::tokens::{map_lit, str_lit};
2325

2426
use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError};
2527

@@ -32,6 +34,8 @@ pub struct ContextData {
3234
pub config: Config,
3335
pub config_parent: PathBuf,
3436
pub root: TokenStream,
37+
/// Additional capabilities to include.
38+
pub capabilities: Option<Vec<PathBuf>>,
3539
}
3640

3741
fn map_core_assets(
@@ -126,6 +130,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
126130
config,
127131
config_parent,
128132
root,
133+
capabilities: additional_capabilities,
129134
} = data;
130135

131136
let target = std::env::var("TARGET")
@@ -390,7 +395,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
390395
Default::default()
391396
};
392397

393-
let capabilities = if config.app.security.capabilities.is_empty() {
398+
let mut capabilities = if config.app.security.capabilities.is_empty() {
394399
capabilities_from_files
395400
} else {
396401
let mut capabilities = BTreeMap::new();
@@ -410,7 +415,36 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
410415
capabilities
411416
};
412417

413-
let resolved_acl = Resolved::resolve(acl, capabilities, target).expect("failed to resolve ACL");
418+
let acl_tokens = map_lit(
419+
quote! { ::std::collections::BTreeMap },
420+
&acl,
421+
str_lit,
422+
identity,
423+
);
424+
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 {
434+
capabilities: capabilities_list,
435+
} => {
436+
capabilities.extend(
437+
capabilities_list
438+
.into_iter()
439+
.map(|c| (c.identifier.clone(), c)),
440+
);
441+
}
442+
}
443+
}
444+
}
445+
446+
let resolved = Resolved::resolve(&acl, capabilities, target).expect("failed to resolve ACL");
447+
let runtime_authority = quote!(#root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved));
414448

415449
Ok(quote!({
416450
#[allow(unused_mut, clippy::let_and_return)]
@@ -422,7 +456,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
422456
#package_info,
423457
#info_plist,
424458
#pattern,
425-
#resolved_acl
459+
#runtime_authority
426460
);
427461
#with_tray_icon_code
428462
context

core/tauri-macros/src/context.rs

Lines changed: 81 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44

55
use proc_macro2::{Ident, Span, TokenStream};
66
use quote::{quote, ToTokens};
7-
use std::{env::VarError, path::PathBuf};
7+
use std::path::PathBuf;
88
use syn::{
99
parse::{Parse, ParseBuffer},
1010
punctuated::Punctuated,
11-
LitStr, PathArguments, PathSegment, Token,
11+
Expr, ExprLit, Lit, LitStr, Meta, PathArguments, PathSegment, Token,
1212
};
1313
use tauri_codegen::{context_codegen, get_config, ContextData};
1414
use tauri_utils::{config::parse::does_supported_file_name_exist, platform::Target};
1515

1616
pub(crate) struct ContextItems {
1717
config_file: PathBuf,
1818
root: syn::Path,
19+
capabilities: Option<Vec<PathBuf>>,
1920
}
2021

2122
impl Parse for ContextItems {
@@ -26,51 +27,92 @@ impl Parse for ContextItems {
2627
.map(Target::from_triple)
2728
.unwrap_or_else(|_| Target::current());
2829

29-
let config_file = if input.is_empty() {
30-
std::env::var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join("tauri.conf.json"))
31-
} else {
32-
let raw: LitStr = input.parse()?;
30+
let mut root = None;
31+
let mut capabilities = None;
32+
let config_file = input.parse::<LitStr>().ok().map(|raw| {
33+
let _ = input.parse::<Token![,]>();
3334
let path = PathBuf::from(raw.value());
3435
if path.is_relative() {
35-
std::env::var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join(path))
36+
std::env::var("CARGO_MANIFEST_DIR")
37+
.map(|m| PathBuf::from(m).join(path))
38+
.map_err(|e| e.to_string())
3639
} else {
3740
Ok(path)
3841
}
39-
}
40-
.map_err(|error| match error {
41-
VarError::NotPresent => "no CARGO_MANIFEST_DIR env var, this should be set by cargo".into(),
42-
VarError::NotUnicode(_) => "CARGO_MANIFEST_DIR env var contained invalid utf8".into(),
43-
})
44-
.and_then(|path| {
45-
if does_supported_file_name_exist(target, &path) {
46-
Ok(path)
47-
} else {
48-
Err(format!(
49-
"no file at path {} exists, expected tauri config file",
50-
path.display()
51-
))
52-
}
53-
})
54-
.map_err(|e| input.error(e))?;
42+
.and_then(|path| {
43+
if does_supported_file_name_exist(target, &path) {
44+
Ok(path)
45+
} else {
46+
Err(format!(
47+
"no file at path {} exists, expected tauri config file",
48+
path.display()
49+
))
50+
}
51+
})
52+
});
5553

56-
let context_path = if input.is_empty() {
57-
let mut segments = Punctuated::new();
58-
segments.push(PathSegment {
59-
ident: Ident::new("tauri", Span::call_site()),
60-
arguments: PathArguments::None,
61-
});
62-
syn::Path {
63-
leading_colon: Some(Token![::](Span::call_site())),
64-
segments,
54+
while let Ok(meta) = input.parse::<Meta>() {
55+
match meta {
56+
Meta::Path(p) => {
57+
root.replace(p);
58+
}
59+
Meta::NameValue(v) => {
60+
if *v.path.require_ident()? == "capabilities" {
61+
if let Expr::Array(array) = v.value {
62+
capabilities.replace(
63+
array
64+
.elems
65+
.into_iter()
66+
.map(|e| {
67+
if let Expr::Lit(ExprLit {
68+
attrs: _,
69+
lit: Lit::Str(s),
70+
}) = e
71+
{
72+
Ok(s.value().into())
73+
} else {
74+
Err(syn::Error::new(
75+
input.span(),
76+
"unexpected expression for capability",
77+
))
78+
}
79+
})
80+
.collect::<Result<Vec<_>, syn::Error>>()?,
81+
);
82+
} else {
83+
return Err(syn::Error::new(
84+
input.span(),
85+
"unexpected value for capabilities",
86+
));
87+
}
88+
}
89+
}
90+
Meta::List(_) => {
91+
return Err(syn::Error::new(input.span(), "unexpected list input"));
92+
}
6593
}
66-
} else {
67-
let _: Token![,] = input.parse()?;
68-
input.call(syn::Path::parse_mod_style)?
69-
};
94+
}
7095

7196
Ok(Self {
72-
config_file,
73-
root: context_path,
97+
config_file: config_file
98+
.unwrap_or_else(|| {
99+
std::env::var("CARGO_MANIFEST_DIR")
100+
.map(|m| PathBuf::from(m).join("tauri.conf.json"))
101+
.map_err(|e| e.to_string())
102+
})
103+
.map_err(|e| input.error(e))?,
104+
root: root.unwrap_or_else(|| {
105+
let mut segments = Punctuated::new();
106+
segments.push(PathSegment {
107+
ident: Ident::new("tauri", Span::call_site()),
108+
arguments: PathArguments::None,
109+
});
110+
syn::Path {
111+
leading_colon: Some(Token![::](Span::call_site())),
112+
segments,
113+
}
114+
}),
115+
capabilities,
74116
})
75117
}
76118
}
@@ -83,6 +125,7 @@ pub(crate) fn generate_context(context: ContextItems) -> TokenStream {
83125
config,
84126
config_parent,
85127
root: context.root.to_token_stream(),
128+
capabilities: context.capabilities,
86129
})
87130
.and_then(|data| context_codegen(data).map_err(|e| e.to_string()));
88131

0 commit comments

Comments
 (0)