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