Skip to main content

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>,
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) -> 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    // The autogenerated permissions are written to `OUT_DIR` so they don't have to be committed
111    // at the crate level. Only the human-readable documentation is kept in the crate's
112    // `permissions/autogenerated` folder so it stays recoverable by the user.
113    let docs_dir = Path::new("permissions").join(acl::build::AUTOGENERATED_FOLDER_NAME);
114
115    // remove the autogenerated permissions that older Tauri versions wrote into the crate's source
116    // tree; they now live in `OUT_DIR`.
117    let _ = std::fs::remove_dir_all(docs_dir.join("commands"));
118
119    println!("cargo:rerun-if-changed=permissions");
120
121    let mut permission_files = Vec::new();
122
123    if !self.commands.is_empty() {
124      let commands_dir = out_dir
125        .join(acl::build::AUTOGENERATED_FOLDER_NAME)
126        .join("commands");
127      acl::build::autogenerate_command_permissions(&commands_dir, self.commands, "", false);
128      permission_files.extend(acl::build::collect_permission_files(
129        &commands_dir.join("*").to_string_lossy(),
130        |_| true,
131      )?);
132    }
133
134    permission_files.extend(acl::build::collect_permission_files(
135      "./permissions/**/*.*",
136      |_| true,
137    )?);
138
139    let permissions = acl::build::define_permissions_from_files(permission_files, &name, &out_dir)?;
140
141    if permissions.is_empty() {
142      let _ = std::fs::remove_file(format!(
143        "./permissions/{}/{}",
144        acl::PERMISSION_SCHEMAS_FOLDER_NAME,
145        acl::PERMISSION_SCHEMA_FILE_NAME
146      ));
147      let _ = std::fs::remove_file(docs_dir.join(acl::build::PERMISSION_DOCS_FILE_NAME));
148    } else {
149      acl::schema::generate_permissions_schema(&permissions, "./permissions")?;
150      std::fs::create_dir_all(&docs_dir).expect("unable to create permissions docs dir");
151      acl::build::generate_docs(
152        &permissions,
153        &docs_dir,
154        name.strip_prefix("tauri-plugin-").unwrap_or(&name),
155      )?;
156    }
157
158    let mut permissions_map = BTreeMap::new();
159    permissions_map.insert(name.clone(), permissions);
160    tauri_utils::acl::build::generate_allowed_commands(&out_dir, None, permissions_map)?;
161
162    if let Some(global_scope_schema) = self.global_scope_schema {
163      acl::build::define_global_scope_schema(global_scope_schema, &name, &out_dir)?;
164    }
165
166    if let Some(path) = self.global_api_script_path {
167      tauri_utils::plugin::define_global_api_script_path(&path);
168    }
169
170    mobile::setup(self.android_path, self.ios_path)?;
171
172    Ok(())
173  }
174}
175
176fn cfg_alias(alias: &str, has_feature: bool) {
177  println!("cargo:rustc-check-cfg=cfg({alias})");
178  if has_feature {
179    println!("cargo:rustc-cfg={alias}");
180  }
181}
182
183/// Grab an env var that is expected to be set inside of build scripts.
184fn build_var(key: &'static str) -> Result<String, Error> {
185  std::env::var(key).map_err(|_| Error::BuildVar(key))
186}