Skip to main content

tauri_build/codegen/
context.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 anyhow::{Context, Result};
6use std::{
7  env::var,
8  fs::{File, create_dir_all},
9  io::{BufWriter, Write},
10  path::{Path, PathBuf},
11};
12use tauri_codegen::{ContextData, context_codegen};
13use tauri_utils::config::FrontendDist;
14
15// TODO docs
16/// A builder for generating a Tauri application context during compile time.
17#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
18#[derive(Debug)]
19pub struct CodegenContext {
20  pub(crate) config_path: Option<PathBuf>,
21  out_file: PathBuf,
22  capabilities: Option<Vec<PathBuf>>,
23}
24
25impl Default for CodegenContext {
26  fn default() -> Self {
27    Self {
28      config_path: None,
29      out_file: PathBuf::from("tauri-build-context.rs"),
30      capabilities: None,
31    }
32  }
33}
34
35impl CodegenContext {
36  /// Create a new [`CodegenContext`] builder that is already filled with the default options.
37  pub fn new() -> Self {
38    Self::default()
39  }
40
41  /// Set the path to the `tauri.conf.json` (relative to the crate's directory).
42  ///
43  /// This defaults to a file called `tauri.conf.json` inside of the current working directory of
44  /// the crate compiling; does not need to be set manually if that config file is in the same
45  /// directory as your `Cargo.toml`.
46  #[must_use]
47  #[deprecated(since = "2.12.0", note = "Use `Attributes::config_path()` instead")]
48  pub fn config_path(mut self, config_path: impl Into<PathBuf>) -> Self {
49    self.config_path.replace(config_path.into());
50    self
51  }
52
53  /// Sets the output file's path.
54  ///
55  /// **Note:** This path should be relative to the `OUT_DIR`.
56  ///
57  /// Don't set this if you are using [`tauri::tauri_build_context!`] as that helper macro
58  /// expects the default value. This option can be useful if you are not using the helper and
59  /// instead using [`std::include!`] on the generated code yourself.
60  ///
61  /// Defaults to `tauri-build-context.rs`.
62  ///
63  /// [`tauri::tauri_build_context!`]: https://docs.rs/tauri/latest/tauri/macro.tauri_build_context.html
64  #[must_use]
65  pub fn out_file(mut self, filename: PathBuf) -> Self {
66    self.out_file = filename;
67    self
68  }
69
70  /// Adds a capability file to the generated context.
71  #[must_use]
72  pub fn capability<P: AsRef<Path>>(mut self, path: P) -> Self {
73    self
74      .capabilities
75      .get_or_insert_with(Default::default)
76      .push(path.as_ref().to_path_buf());
77    self
78  }
79
80  /// Generate the code and write it to the output file - returning the path it was saved to.
81  ///
82  /// Unless you are doing something special with this builder, you don't need to do anything with
83  /// the returned output path.
84  pub(crate) fn try_build(self) -> Result<PathBuf> {
85    let (config, config_parent) = tauri_codegen::get_config(
86      &self
87        .config_path
88        .unwrap_or_else(|| PathBuf::from("tauri.conf.json")),
89    )?;
90
91    // rerun if changed
92    match &config.build.frontend_dist {
93      Some(FrontendDist::Directory(p)) => {
94        let dist_path = config_parent.join(p);
95        if dist_path.exists() {
96          println!("cargo:rerun-if-changed={}", dist_path.display());
97        }
98      }
99      Some(FrontendDist::Files(files)) => {
100        for path in files {
101          println!(
102            "cargo:rerun-if-changed={}",
103            config_parent.join(path).display()
104          );
105        }
106      }
107      _ => (),
108    }
109    for icon in &config.bundle.icon {
110      println!(
111        "cargo:rerun-if-changed={}",
112        config_parent.join(icon).display()
113      );
114    }
115    if let Some(tray_icon) = config.app.tray_icon.as_ref().map(|t| &t.icon_path) {
116      println!(
117        "cargo:rerun-if-changed={}",
118        config_parent.join(tray_icon).display()
119      );
120    }
121
122    #[cfg(target_os = "macos")]
123    {
124      let info_plist_path = config_parent.join("Info.plist");
125      if info_plist_path.exists() {
126        println!("cargo:rerun-if-changed={}", info_plist_path.display());
127      }
128
129      if let Some(plist_path) = &config.bundle.macos.info_plist {
130        let info_plist_path = config_parent.join(plist_path);
131        if info_plist_path.exists() {
132          println!("cargo:rerun-if-changed={}", info_plist_path.display());
133        }
134      }
135    }
136
137    let code = context_codegen(ContextData {
138      dev: crate::is_dev(),
139      config,
140      config_parent,
141      // it's very hard to have a build script for unit tests, so assume this is always called from
142      // outside the tauri crate, making the ::tauri root valid.
143      root: quote::quote!(::tauri),
144      capabilities: self.capabilities,
145      assets: None,
146      test: false,
147    })?;
148
149    // get the full output file path
150    let out = var("OUT_DIR")
151      .map(PathBuf::from)
152      .map(|path| path.join(&self.out_file))
153      .with_context(|| "unable to find OUT_DIR during tauri-build")?;
154
155    // make sure any nested directories in OUT_DIR are created
156    let parent = out.parent().with_context(
157      || "`Codegen` could not find the parent to `out_file` while creating the file",
158    )?;
159    create_dir_all(parent)?;
160
161    let mut file = File::create(&out).map(BufWriter::new).with_context(|| {
162      format!(
163        "Unable to create output file during tauri-build {}",
164        out.display()
165      )
166    })?;
167
168    writeln!(file, "{code}").with_context(|| {
169      format!(
170        "Unable to write tokenstream to out file during tauri-build {}",
171        out.display()
172      )
173    })?;
174
175    Ok(out)
176  }
177}