Skip to content

Commit

Permalink
feat(codegen): allow defining additional capabilities, closes #8798 (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
lucasfernog authored Feb 19, 2024
1 parent 770051a commit 8d16a80
Show file tree
Hide file tree
Showing 22 changed files with 452 additions and 240 deletions.
6 changes: 6 additions & 0 deletions .changes/codegen-capabilities-attribute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-macros": patch:enhance
"tauri-codegen": patch:enhance
---

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.
5 changes: 5 additions & 0 deletions .changes/context-runtime-authority.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-utils": patch:enhance
---

The `Context` struct now includes the runtime authority instead of the resolved ACL. This does not impact most applications.
5 changes: 5 additions & 0 deletions .changes/tauri-build-codegen-capabilities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-build": patch:enhance
---

Added `CodegenContext::capability` to include a capability file dynamically.
5 changes: 5 additions & 0 deletions .changes/tauri-utils-capability-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-utils": patch:enhance
---

Refactored the capability types and resolution algorithm.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion core/tauri-build/src/acl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ use schemars::{
schema_for,
};
use tauri_utils::{
acl::{build::CapabilityFile, capability::Capability, plugin::Manifest},
acl::{
capability::{Capability, CapabilityFile},
plugin::Manifest,
},
platform::Target,
};

Expand Down
15 changes: 14 additions & 1 deletion core/tauri-build/src/codegen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{
env::var,
fs::{create_dir_all, File},
io::{BufWriter, Write},
path::PathBuf,
path::{Path, PathBuf},
};
use tauri_codegen::{context_codegen, ContextData};
use tauri_utils::config::FrontendDist;
Expand All @@ -20,6 +20,7 @@ pub struct CodegenContext {
dev: bool,
config_path: PathBuf,
out_file: PathBuf,
capabilities: Option<Vec<PathBuf>>,
}

impl Default for CodegenContext {
Expand All @@ -28,6 +29,7 @@ impl Default for CodegenContext {
dev: false,
config_path: PathBuf::from("tauri.conf.json"),
out_file: PathBuf::from("tauri-build-context.rs"),
capabilities: None,
}
}
}
Expand Down Expand Up @@ -74,6 +76,16 @@ impl CodegenContext {
self
}

/// Adds a capability file to the generated context.
#[must_use]
pub fn capability<P: AsRef<Path>>(mut self, path: P) -> Self {
self
.capabilities
.get_or_insert_with(Default::default)
.push(path.as_ref().to_path_buf());
self
}

/// Generate the code and write it to the output file - returning the path it was saved to.
///
/// Unless you are doing something special with this builder, you don't need to do anything with
Expand Down Expand Up @@ -125,6 +137,7 @@ impl CodegenContext {
// it's very hard to have a build script for unit tests, so assume this is always called from
// outside the tauri crate, making the ::tauri root valid.
root: quote::quote!(::tauri),
capabilities: self.capabilities,
})?;

// get the full output file path
Expand Down
3 changes: 2 additions & 1 deletion core/tauri-codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ sha2 = "0.10"
base64 = "0.21"
proc-macro2 = "1"
quote = "1"
serde = { version = "1", features = ["derive"] }
syn = "2"
serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
tauri-utils = { version = "2.0.0-beta.1", path = "../tauri-utils", features = [ "build" ] }
thiserror = "1"
Expand Down
42 changes: 38 additions & 4 deletions core/tauri-codegen/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT

use std::collections::BTreeMap;
use std::convert::identity;
use std::path::{Path, PathBuf};
use std::{ffi::OsStr, str::FromStr};

Expand All @@ -11,7 +12,7 @@ use proc_macro2::TokenStream;
use quote::quote;
use sha2::{Digest, Sha256};

use tauri_utils::acl::capability::Capability;
use tauri_utils::acl::capability::{Capability, CapabilityFile};
use tauri_utils::acl::plugin::Manifest;
use tauri_utils::acl::resolved::Resolved;
use tauri_utils::assets::AssetKey;
Expand All @@ -20,6 +21,7 @@ use tauri_utils::html::{
inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node,
};
use tauri_utils::platform::Target;
use tauri_utils::tokens::{map_lit, str_lit};

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

Expand All @@ -32,6 +34,8 @@ pub struct ContextData {
pub config: Config,
pub config_parent: PathBuf,
pub root: TokenStream,
/// Additional capabilities to include.
pub capabilities: Option<Vec<PathBuf>>,
}

fn map_core_assets(
Expand Down Expand Up @@ -126,6 +130,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
config,
config_parent,
root,
capabilities: additional_capabilities,
} = data;

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

let capabilities = if config.app.security.capabilities.is_empty() {
let mut capabilities = if config.app.security.capabilities.is_empty() {
capabilities_from_files
} else {
let mut capabilities = BTreeMap::new();
Expand All @@ -410,7 +415,36 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
capabilities
};

let resolved_acl = Resolved::resolve(acl, capabilities, target).expect("failed to resolve ACL");
let acl_tokens = map_lit(
quote! { ::std::collections::BTreeMap },
&acl,
str_lit,
identity,
);

if let Some(paths) = additional_capabilities {
for path in paths {
let capability = CapabilityFile::load(&path)
.unwrap_or_else(|e| panic!("failed to read capability {}: {e}", path.display()));
match capability {
CapabilityFile::Capability(c) => {
capabilities.insert(c.identifier.clone(), c);
}
CapabilityFile::List {
capabilities: capabilities_list,
} => {
capabilities.extend(
capabilities_list
.into_iter()
.map(|c| (c.identifier.clone(), c)),
);
}
}
}
}

let resolved = Resolved::resolve(&acl, capabilities, target).expect("failed to resolve ACL");
let runtime_authority = quote!(#root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved));

Ok(quote!({
#[allow(unused_mut, clippy::let_and_return)]
Expand All @@ -422,7 +456,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
#package_info,
#info_plist,
#pattern,
#resolved_acl
#runtime_authority
);
#with_tray_icon_code
context
Expand Down
119 changes: 81 additions & 38 deletions core/tauri-macros/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@

use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use std::{env::VarError, path::PathBuf};
use std::path::PathBuf;
use syn::{
parse::{Parse, ParseBuffer},
punctuated::Punctuated,
LitStr, PathArguments, PathSegment, Token,
Expr, ExprLit, Lit, LitStr, Meta, PathArguments, PathSegment, Token,
};
use tauri_codegen::{context_codegen, get_config, ContextData};
use tauri_utils::{config::parse::does_supported_file_name_exist, platform::Target};

pub(crate) struct ContextItems {
config_file: PathBuf,
root: syn::Path,
capabilities: Option<Vec<PathBuf>>,
}

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

let config_file = if input.is_empty() {
std::env::var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join("tauri.conf.json"))
} else {
let raw: LitStr = input.parse()?;
let mut root = None;
let mut capabilities = None;
let config_file = input.parse::<LitStr>().ok().map(|raw| {
let _ = input.parse::<Token![,]>();
let path = PathBuf::from(raw.value());
if path.is_relative() {
std::env::var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join(path))
std::env::var("CARGO_MANIFEST_DIR")
.map(|m| PathBuf::from(m).join(path))
.map_err(|e| e.to_string())
} else {
Ok(path)
}
}
.map_err(|error| match error {
VarError::NotPresent => "no CARGO_MANIFEST_DIR env var, this should be set by cargo".into(),
VarError::NotUnicode(_) => "CARGO_MANIFEST_DIR env var contained invalid utf8".into(),
})
.and_then(|path| {
if does_supported_file_name_exist(target, &path) {
Ok(path)
} else {
Err(format!(
"no file at path {} exists, expected tauri config file",
path.display()
))
}
})
.map_err(|e| input.error(e))?;
.and_then(|path| {
if does_supported_file_name_exist(target, &path) {
Ok(path)
} else {
Err(format!(
"no file at path {} exists, expected tauri config file",
path.display()
))
}
})
});

let context_path = if input.is_empty() {
let mut segments = Punctuated::new();
segments.push(PathSegment {
ident: Ident::new("tauri", Span::call_site()),
arguments: PathArguments::None,
});
syn::Path {
leading_colon: Some(Token![::](Span::call_site())),
segments,
while let Ok(meta) = input.parse::<Meta>() {
match meta {
Meta::Path(p) => {
root.replace(p);
}
Meta::NameValue(v) => {
if *v.path.require_ident()? == "capabilities" {
if let Expr::Array(array) = v.value {
capabilities.replace(
array
.elems
.into_iter()
.map(|e| {
if let Expr::Lit(ExprLit {
attrs: _,
lit: Lit::Str(s),
}) = e
{
Ok(s.value().into())
} else {
Err(syn::Error::new(
input.span(),
"unexpected expression for capability",
))
}
})
.collect::<Result<Vec<_>, syn::Error>>()?,
);
} else {
return Err(syn::Error::new(
input.span(),
"unexpected value for capabilities",
));
}
}
}
Meta::List(_) => {
return Err(syn::Error::new(input.span(), "unexpected list input"));
}
}
} else {
let _: Token![,] = input.parse()?;
input.call(syn::Path::parse_mod_style)?
};
}

Ok(Self {
config_file,
root: context_path,
config_file: config_file
.unwrap_or_else(|| {
std::env::var("CARGO_MANIFEST_DIR")
.map(|m| PathBuf::from(m).join("tauri.conf.json"))
.map_err(|e| e.to_string())
})
.map_err(|e| input.error(e))?,
root: root.unwrap_or_else(|| {
let mut segments = Punctuated::new();
segments.push(PathSegment {
ident: Ident::new("tauri", Span::call_site()),
arguments: PathArguments::None,
});
syn::Path {
leading_colon: Some(Token![::](Span::call_site())),
segments,
}
}),
capabilities,
})
}
}
Expand All @@ -83,6 +125,7 @@ pub(crate) fn generate_context(context: ContextItems) -> TokenStream {
config,
config_parent,
root: context.root.to_token_stream(),
capabilities: context.capabilities,
})
.and_then(|data| context_codegen(data).map_err(|e| e.to_string()));

Expand Down
Loading

0 comments on commit 8d16a80

Please sign in to comment.