tauri_plugin/build/
mod.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use std::{
6  collections::BTreeMap,
7  path::{Path, PathBuf},
8};
9
10use anyhow::Result;
11use tauri_utils::acl::{self, Error};
12
13pub mod mobile;
14
15use serde::de::DeserializeOwned;
16
17use std::{env, io::Cursor};
18
19const RESERVED_PLUGIN_NAMES: &[&str] = &["core", "tauri"];
20
21pub fn plugin_config<T: DeserializeOwned>(name: &str) -> Option<T> {
22  let config_env_var_name = format!(
23    "TAURI_{}_PLUGIN_CONFIG",
24    name.to_uppercase().replace('-', "_")
25  );
26  if let Ok(config_str) = env::var(&config_env_var_name) {
27    println!("cargo:rerun-if-env-changed={config_env_var_name}");
28    serde_json::from_reader(Cursor::new(config_str))
29      .map(Some)
30      .expect("failed to parse configuration")
31  } else {
32    None
33  }
34}
35
36pub struct Builder<'a> {
37  commands: &'a [&'static str],
38  global_scope_schema: Option<schemars::schema::RootSchema>,
39  global_api_script_path: Option<PathBuf>,
40  android_path: Option<PathBuf>,
41  ios_path: Option<PathBuf>,
42}
43
44impl<'a> Builder<'a> {
45  pub fn new(commands: &'a [&'static str]) -> Self {
46    Self {
47      commands,
48      global_scope_schema: None,
49      global_api_script_path: None,
50      android_path: None,
51      ios_path: None,
52    }
53  }
54
55  /// Sets the global scope JSON schema.
56  pub fn global_scope_schema(mut self, schema: schemars::schema::RootSchema) -> Self {
57    self.global_scope_schema.replace(schema);
58    self
59  }
60
61  /// Sets the path to the script that is injected in the webview when the `withGlobalTauri` configuration is set to true.
62  ///
63  /// This is usually an IIFE that injects the plugin API JavaScript bindings to `window.__TAURI__`.
64  pub fn global_api_script_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
65    self.global_api_script_path.replace(path.into());
66    self
67  }
68
69  /// Sets the Android project path.
70  pub fn android_path<P: Into<PathBuf>>(mut self, android_path: P) -> Self {
71    self.android_path.replace(android_path.into());
72    self
73  }
74
75  /// Sets the iOS project path.
76  pub fn ios_path<P: Into<PathBuf>>(mut self, ios_path: P) -> Self {
77    self.ios_path.replace(ios_path.into());
78    self
79  }
80
81  /// [`Self::try_build`] but will exit automatically if an error is found.
82  pub fn build(self) {
83    if let Err(error) = self.try_build() {
84      println!("{}: {error:#}", env!("CARGO_PKG_NAME"));
85      std::process::exit(1);
86    }
87  }
88
89  /// Ensure this crate is properly configured to be a Tauri plugin.
90  ///
91  /// # Errors
92  ///
93  /// Errors will occur if environmental variables expected to be set inside of [build scripts]
94  /// are not found, or if the crate violates Tauri plugin conventions.
95  pub fn try_build(self) -> Result<()> {
96    // convention: plugin names should not use underscores
97    let name = build_var("CARGO_PKG_NAME")?;
98    if name.contains('_') {
99      anyhow::bail!("plugin names cannot contain underscores");
100    }
101    if RESERVED_PLUGIN_NAMES.contains(&name.as_str()) {
102      anyhow::bail!("plugin name `{name}` is reserved");
103    }
104
105    let out_dir = PathBuf::from(build_var("OUT_DIR")?);
106
107    // requirement: links MUST be set and MUST match the name
108    let _links = std::env::var("CARGO_MANIFEST_LINKS").map_err(|_| Error::LinksMissing)?;
109
110    let autogenerated = Path::new("permissions").join(acl::build::AUTOGENERATED_FOLDER_NAME);
111    std::fs::create_dir_all(&autogenerated).expect("unable to create permissions dir");
112
113    let commands_dir = autogenerated.join("commands");
114    if !self.commands.is_empty() {
115      acl::build::autogenerate_command_permissions(&commands_dir, self.commands, "", true);
116    }
117
118    println!("cargo:rerun-if-changed=permissions");
119    let permissions =
120      acl::build::define_permissions("./permissions/**/*.*", &name, &out_dir, |_| true)?;
121
122    if permissions.is_empty() {
123      let _ = std::fs::remove_file(format!(
124        "./permissions/{}/{}",
125        acl::PERMISSION_SCHEMAS_FOLDER_NAME,
126        acl::PERMISSION_SCHEMA_FILE_NAME
127      ));
128      let _ = std::fs::remove_file(autogenerated.join(acl::build::PERMISSION_DOCS_FILE_NAME));
129    } else {
130      acl::schema::generate_permissions_schema(&permissions, "./permissions")?;
131      acl::build::generate_docs(
132        &permissions,
133        &autogenerated,
134        name.strip_prefix("tauri-plugin-").unwrap_or(&name),
135      )?;
136    }
137
138    let mut permissions_map = BTreeMap::new();
139    permissions_map.insert(name.clone(), permissions);
140    tauri_utils::acl::build::generate_allowed_commands(&out_dir, None, permissions_map)?;
141
142    if let Some(global_scope_schema) = self.global_scope_schema {
143      acl::build::define_global_scope_schema(global_scope_schema, &name, &out_dir)?;
144    }
145
146    if let Some(path) = self.global_api_script_path {
147      tauri_utils::plugin::define_global_api_script_path(&path);
148    }
149
150    mobile::setup(self.android_path, self.ios_path)?;
151
152    Ok(())
153  }
154}
155
156fn cfg_alias(alias: &str, has_feature: bool) {
157  println!("cargo:rustc-check-cfg=cfg({alias})");
158  if has_feature {
159    println!("cargo:rustc-cfg={alias}");
160  }
161}
162
163/// Grab an env var that is expected to be set inside of build scripts.
164fn build_var(key: &'static str) -> Result<String, Error> {
165  std::env::var(key).map_err(|_| Error::BuildVar(key))
166}