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