Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(codegen): allow defining additional capabilities, closes #8798 #8802

Merged
merged 12 commits into from
Feb 19, 2024
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