tauri_plugin/build/
mobile.rs1use std::{
8 env::var_os,
9 fs::{copy, create_dir, create_dir_all, read_to_string, remove_dir_all, write},
10 path::{Path, PathBuf},
11};
12
13use anyhow::{Context, Result};
14
15use super::{build_var, cfg_alias};
16
17#[cfg(target_os = "macos")]
18pub fn update_entitlements<F: FnOnce(&mut plist::Dictionary)>(f: F) -> Result<()> {
19 if let (Some(project_path), Ok(app_name)) = (
20 var_os("TAURI_IOS_PROJECT_PATH").map(PathBuf::from),
21 std::env::var("TAURI_IOS_APP_NAME"),
22 ) {
23 update_plist_file(
24 project_path
25 .join(format!("{app_name}_iOS"))
26 .join(format!("{app_name}_iOS.entitlements")),
27 f,
28 )?;
29 }
30
31 Ok(())
32}
33
34pub fn update_android_manifest(block_identifier: &str, parent: &str, insert: String) -> Result<()> {
35 if let Some(project_path) = var_os("TAURI_ANDROID_PROJECT_PATH").map(PathBuf::from) {
36 let manifest_path = project_path.join("app/src/main/AndroidManifest.xml");
37 let manifest = read_to_string(&manifest_path)?;
38 let rewritten = insert_into_xml(&manifest, block_identifier, parent, &insert);
39 if rewritten != manifest {
40 write(manifest_path, rewritten)?;
41 }
42 }
43 Ok(())
44}
45
46pub(crate) fn setup(
47 android_path: Option<PathBuf>,
48 #[allow(unused_variables)] ios_path: Option<PathBuf>,
49) -> Result<()> {
50 let target_os = build_var("CARGO_CFG_TARGET_OS")?;
51 let mobile = target_os == "android" || target_os == "ios";
52 cfg_alias("mobile", mobile);
53 cfg_alias("desktop", !mobile);
54
55 match target_os.as_str() {
56 "android" => {
57 if let Some(path) = android_path {
58 let manifest_dir = build_var("CARGO_MANIFEST_DIR").map(PathBuf::from)?;
59 let source = manifest_dir.join(path);
60
61 let tauri_library_path = std::env::var("DEP_TAURI_ANDROID_LIBRARY_PATH")
62 .expect("missing `DEP_TAURI_ANDROID_LIBRARY_PATH` environment variable. Make sure `tauri` is a dependency of the plugin.");
63 println!("cargo:rerun-if-env-changed=DEP_TAURI_ANDROID_LIBRARY_PATH");
64
65 create_dir_all(source.join(".tauri")).context("failed to create .tauri directory")?;
66 copy_folder(
67 Path::new(&tauri_library_path),
68 &source.join(".tauri").join("tauri-api"),
69 &[],
70 )
71 .context("failed to copy tauri-api to the plugin project")?;
72
73 println!("cargo:android_library_path={}", source.display());
74 }
75 }
76 #[cfg(target_os = "macos")]
77 "ios" => {
78 if let Some(path) = ios_path {
79 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
80 .map(PathBuf::from)
81 .unwrap();
82 let tauri_library_path = std::env::var("DEP_TAURI_IOS_LIBRARY_PATH")
83 .expect("missing `DEP_TAURI_IOS_LIBRARY_PATH` environment variable. Make sure `tauri` is a dependency of the plugin.");
84
85 let tauri_dep_path = path.parent().unwrap().join(".tauri");
86 create_dir_all(&tauri_dep_path).context("failed to create .tauri directory")?;
87 copy_folder(
88 Path::new(&tauri_library_path),
89 &tauri_dep_path.join("tauri-api"),
90 &[".build", "Package.resolved", "Tests"],
91 )
92 .context("failed to copy tauri-api to the plugin project")?;
93 tauri_utils::build::link_apple_library(
94 &std::env::var("CARGO_PKG_NAME").unwrap(),
95 manifest_dir.join(path),
96 );
97 }
98 }
99 _ => (),
100 }
101
102 Ok(())
103}
104
105fn copy_folder(source: &Path, target: &Path, ignore_paths: &[&str]) -> Result<()> {
106 let _ = remove_dir_all(target);
107
108 for entry in walkdir::WalkDir::new(source) {
109 let entry = entry?;
110 let rel_path = entry.path().strip_prefix(source)?;
111 let rel_path_str = rel_path.to_string_lossy();
112 if ignore_paths
113 .iter()
114 .any(|path| rel_path_str.starts_with(path))
115 {
116 continue;
117 }
118 let dest_path = target.join(rel_path);
119
120 if entry.file_type().is_dir() {
121 create_dir(&dest_path)
122 .with_context(|| format!("failed to create directory {}", dest_path.display()))?;
123 } else {
124 copy(entry.path(), &dest_path).with_context(|| {
125 format!(
126 "failed to copy {} to {}",
127 entry.path().display(),
128 dest_path.display()
129 )
130 })?;
131 println!("cargo:rerun-if-changed={}", entry.path().display());
132 }
133 }
134
135 Ok(())
136}
137
138#[cfg(target_os = "macos")]
139fn update_plist_file<P: AsRef<Path>, F: FnOnce(&mut plist::Dictionary)>(
140 path: P,
141 f: F,
142) -> Result<()> {
143 use std::io::Cursor;
144
145 let path = path.as_ref();
146 if path.exists() {
147 let plist_str = read_to_string(path)?;
148 let mut plist = plist::Value::from_reader(Cursor::new(&plist_str))?;
149 if let Some(dict) = plist.as_dictionary_mut() {
150 f(dict);
151 let mut plist_buf = Vec::new();
152 let writer = Cursor::new(&mut plist_buf);
153 plist::to_writer_xml(writer, &plist)?;
154 let new_plist_str = String::from_utf8(plist_buf)?;
155 if new_plist_str != plist_str {
156 write(path, new_plist_str)?;
157 }
158 }
159 }
160
161 Ok(())
162}
163
164fn xml_block_comment(id: &str) -> String {
165 format!("<!-- {id}. AUTO-GENERATED. DO NOT REMOVE. -->")
166}
167
168fn insert_into_xml(xml: &str, block_identifier: &str, parent_tag: &str, contents: &str) -> String {
169 let block_comment = xml_block_comment(block_identifier);
170
171 let mut rewritten = Vec::new();
172 let mut found_block = false;
173 let parent_closing_tag = format!("</{parent_tag}>");
174 for line in xml.split('\n') {
175 if line.contains(&block_comment) {
176 found_block = !found_block;
177 continue;
178 }
179
180 if found_block {
182 continue;
183 }
184
185 if let Some(index) = line.find(&parent_closing_tag) {
186 let indentation = " ".repeat(index + 4);
187 rewritten.push(format!("{}{}", indentation, block_comment));
188 for l in contents.split('\n') {
189 rewritten.push(format!("{}{}", indentation, l));
190 }
191 rewritten.push(format!("{}{}", indentation, block_comment));
192 }
193
194 rewritten.push(line.to_string());
195 }
196
197 rewritten.join("\n")
198}
199
200#[cfg(test)]
201mod tests {
202 #[test]
203 fn insert_into_xml() {
204 let manifest = r#"<manifest>
205 <application>
206 <intent-filter>
207 </intent-filter>
208 </application>
209</manifest>"#;
210 let id = "tauritest";
211 let new = super::insert_into_xml(manifest, id, "application", "<something></something>");
212
213 let block_id_comment = super::xml_block_comment(id);
214 let expected = format!(
215 r#"<manifest>
216 <application>
217 <intent-filter>
218 </intent-filter>
219 {block_id_comment}
220 <something></something>
221 {block_id_comment}
222 </application>
223</manifest>"#
224 );
225
226 assert_eq!(new, expected);
227
228 let new = super::insert_into_xml(&expected, id, "application", "<something></something>");
230 assert_eq!(new, expected);
231 }
232}