build.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use heck::AsShoutySnakeCase;
  5. use tauri_utils::write_if_changed;
  6. use std::{
  7. env, fs,
  8. path::{Path, PathBuf},
  9. sync::{Mutex, OnceLock},
  10. };
  11. static CHECKED_FEATURES: OnceLock<Mutex<Vec<String>>> = OnceLock::new();
  12. const PLUGINS: &[(&str, &[(&str, bool)])] = &[
  13. // (plugin_name, &[(command, enabled-by_default)])
  14. (
  15. "core:path",
  16. &[
  17. ("resolve_directory", true),
  18. ("resolve", true),
  19. ("normalize", true),
  20. ("join", true),
  21. ("dirname", true),
  22. ("extname", true),
  23. ("basename", true),
  24. ("is_absolute", true),
  25. ],
  26. ),
  27. (
  28. "core:event",
  29. &[
  30. ("listen", true),
  31. ("unlisten", true),
  32. ("emit", true),
  33. ("emit_to", true),
  34. ],
  35. ),
  36. (
  37. "core:window",
  38. &[
  39. ("create", false),
  40. // getters
  41. ("get_all_windows", true),
  42. ("scale_factor", true),
  43. ("inner_position", true),
  44. ("outer_position", true),
  45. ("inner_size", true),
  46. ("outer_size", true),
  47. ("is_fullscreen", true),
  48. ("is_minimized", true),
  49. ("is_maximized", true),
  50. ("is_focused", true),
  51. ("is_decorated", true),
  52. ("is_resizable", true),
  53. ("is_maximizable", true),
  54. ("is_minimizable", true),
  55. ("is_closable", true),
  56. ("is_visible", true),
  57. ("is_enabled", true),
  58. ("title", true),
  59. ("current_monitor", true),
  60. ("primary_monitor", true),
  61. ("monitor_from_point", true),
  62. ("available_monitors", true),
  63. ("cursor_position", true),
  64. ("theme", true),
  65. // setters
  66. ("center", false),
  67. ("request_user_attention", false),
  68. ("set_enabled", false),
  69. ("set_resizable", false),
  70. ("set_maximizable", false),
  71. ("set_minimizable", false),
  72. ("set_closable", false),
  73. ("set_title", false),
  74. ("maximize", false),
  75. ("unmaximize", false),
  76. ("minimize", false),
  77. ("unminimize", false),
  78. ("show", false),
  79. ("hide", false),
  80. ("close", false),
  81. ("destroy", false),
  82. ("set_decorations", false),
  83. ("set_shadow", false),
  84. ("set_effects", false),
  85. ("set_always_on_top", false),
  86. ("set_always_on_bottom", false),
  87. ("set_visible_on_all_workspaces", false),
  88. ("set_content_protected", false),
  89. ("set_size", false),
  90. ("set_min_size", false),
  91. ("set_size_constraints", false),
  92. ("set_max_size", false),
  93. ("set_position", false),
  94. ("set_fullscreen", false),
  95. ("set_focus", false),
  96. ("set_skip_taskbar", false),
  97. ("set_cursor_grab", false),
  98. ("set_cursor_visible", false),
  99. ("set_cursor_icon", false),
  100. ("set_cursor_position", false),
  101. ("set_ignore_cursor_events", false),
  102. ("start_dragging", false),
  103. ("start_resize_dragging", false),
  104. ("set_progress_bar", false),
  105. ("set_icon", false),
  106. ("set_title_bar_style", false),
  107. ("set_theme", false),
  108. ("toggle_maximize", false),
  109. // internal
  110. ("internal_toggle_maximize", true),
  111. ],
  112. ),
  113. (
  114. "core:webview",
  115. &[
  116. ("create_webview", false),
  117. ("create_webview_window", false),
  118. // getters
  119. ("get_all_webviews", true),
  120. ("webview_position", true),
  121. ("webview_size", true),
  122. // setters
  123. ("webview_close", false),
  124. ("set_webview_size", false),
  125. ("set_webview_position", false),
  126. ("set_webview_focus", false),
  127. ("set_webview_zoom", false),
  128. ("webview_hide", false),
  129. ("webview_show", false),
  130. ("print", false),
  131. ("reparent", false),
  132. ("clear_all_browsing_data", false),
  133. // internal
  134. ("internal_toggle_devtools", true),
  135. ],
  136. ),
  137. (
  138. "core:app",
  139. &[
  140. ("version", true),
  141. ("name", true),
  142. ("tauri_version", true),
  143. ("app_show", false),
  144. ("app_hide", false),
  145. ("default_window_icon", false),
  146. ("set_app_theme", false),
  147. ],
  148. ),
  149. (
  150. "core:image",
  151. &[
  152. ("new", true),
  153. ("from_bytes", true),
  154. ("from_path", true),
  155. ("rgba", true),
  156. ("size", true),
  157. ],
  158. ),
  159. ("core:resources", &[("close", true)]),
  160. (
  161. "core:menu",
  162. &[
  163. ("new", true),
  164. ("append", true),
  165. ("prepend", true),
  166. ("insert", true),
  167. ("remove", true),
  168. ("remove_at", true),
  169. ("items", true),
  170. ("get", true),
  171. ("popup", true),
  172. ("create_default", true),
  173. ("set_as_app_menu", true),
  174. ("set_as_window_menu", true),
  175. ("text", true),
  176. ("set_text", true),
  177. ("is_enabled", true),
  178. ("set_enabled", true),
  179. ("set_accelerator", true),
  180. ("set_as_windows_menu_for_nsapp", true),
  181. ("set_as_help_menu_for_nsapp", true),
  182. ("is_checked", true),
  183. ("set_checked", true),
  184. ("set_icon", true),
  185. ],
  186. ),
  187. (
  188. "core:tray",
  189. &[
  190. ("new", true),
  191. ("get_by_id", true),
  192. ("remove_by_id", true),
  193. ("set_icon", true),
  194. ("set_menu", true),
  195. ("set_tooltip", true),
  196. ("set_title", true),
  197. ("set_visible", true),
  198. ("set_temp_dir_path", true),
  199. ("set_icon_as_template", true),
  200. ("set_show_menu_on_left_click", true),
  201. ],
  202. ),
  203. ];
  204. // checks if the given Cargo feature is enabled.
  205. fn has_feature(feature: &str) -> bool {
  206. CHECKED_FEATURES
  207. .get_or_init(Default::default)
  208. .lock()
  209. .unwrap()
  210. .push(feature.to_string());
  211. // when a feature is enabled, Cargo sets the `CARGO_FEATURE_<name>` env var to 1
  212. // <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts>
  213. std::env::var(format!("CARGO_FEATURE_{}", AsShoutySnakeCase(feature)))
  214. .map(|x| x == "1")
  215. .unwrap_or(false)
  216. }
  217. // creates a cfg alias if `has_feature` is true.
  218. // `alias` must be a snake case string.
  219. fn alias(alias: &str, has_feature: bool) {
  220. println!("cargo:rustc-check-cfg=cfg({alias})");
  221. if has_feature {
  222. println!("cargo:rustc-cfg={alias}");
  223. }
  224. }
  225. fn main() {
  226. let custom_protocol = has_feature("custom-protocol");
  227. let dev = !custom_protocol;
  228. alias("custom_protocol", custom_protocol);
  229. alias("dev", dev);
  230. println!("cargo:dev={dev}");
  231. let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
  232. let mobile = target_os == "ios" || target_os == "android";
  233. alias("desktop", !mobile);
  234. alias("mobile", mobile);
  235. let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
  236. let checked_features_out_path = out_dir.join("checked_features");
  237. std::fs::write(
  238. checked_features_out_path,
  239. CHECKED_FEATURES.get().unwrap().lock().unwrap().join(","),
  240. )
  241. .expect("failed to write checked_features file");
  242. // workaround needed to prevent `STATUS_ENTRYPOINT_NOT_FOUND` error in tests
  243. // see https://github.com/tauri-apps/tauri/pull/4383#issuecomment-1212221864
  244. let target_env = std::env::var("CARGO_CFG_TARGET_ENV");
  245. let is_tauri_workspace = std::env::var("__TAURI_WORKSPACE__").map_or(false, |v| v == "true");
  246. if is_tauri_workspace && target_os == "windows" && Ok("msvc") == target_env.as_deref() {
  247. embed_manifest_for_tests();
  248. }
  249. if target_os == "android" {
  250. if let Ok(kotlin_out_dir) = std::env::var("WRY_ANDROID_KOTLIN_FILES_OUT_DIR") {
  251. fn env_var(var: &str) -> String {
  252. std::env::var(var).unwrap_or_else(|_| {
  253. panic!("`{var}` is not set, which is needed to generate the kotlin files for android.")
  254. })
  255. }
  256. let package = env_var("WRY_ANDROID_PACKAGE");
  257. let library = env_var("WRY_ANDROID_LIBRARY");
  258. let kotlin_out_dir = PathBuf::from(&kotlin_out_dir)
  259. .canonicalize()
  260. .unwrap_or_else(move |_| {
  261. panic!("Failed to canonicalize `WRY_ANDROID_KOTLIN_FILES_OUT_DIR` path {kotlin_out_dir}")
  262. });
  263. let kotlin_files_path =
  264. PathBuf::from(env_var("CARGO_MANIFEST_DIR")).join("mobile/android-codegen");
  265. println!("cargo:rerun-if-changed={}", kotlin_files_path.display());
  266. let kotlin_files =
  267. fs::read_dir(kotlin_files_path).expect("failed to read Android codegen directory");
  268. for file in kotlin_files {
  269. let file = file.unwrap();
  270. let content = fs::read_to_string(file.path())
  271. .expect("failed to read kotlin file as string")
  272. .replace("{{package}}", &package)
  273. .replace("{{library}}", &library);
  274. let out_path = kotlin_out_dir.join(file.file_name());
  275. // Overwrite only if changed to not trigger rebuilds
  276. write_if_changed(&out_path, &content).expect("Failed to write kotlin file");
  277. println!("cargo:rerun-if-changed={}", out_path.display());
  278. }
  279. }
  280. if let Some(project_dir) = env::var_os("TAURI_ANDROID_PROJECT_PATH").map(PathBuf::from) {
  281. let tauri_proguard = include_str!("./mobile/proguard-tauri.pro").replace(
  282. "$PACKAGE",
  283. &env::var("WRY_ANDROID_PACKAGE")
  284. .expect("missing `WRY_ANDROID_PACKAGE` environment variable"),
  285. );
  286. std::fs::write(
  287. project_dir.join("app").join("proguard-tauri.pro"),
  288. tauri_proguard,
  289. )
  290. .expect("failed to write proguard-tauri.pro");
  291. }
  292. let lib_path =
  293. PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("mobile/android");
  294. println!("cargo:android_library_path={}", lib_path.display());
  295. }
  296. #[cfg(target_os = "macos")]
  297. {
  298. if target_os == "ios" {
  299. let lib_path =
  300. PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("mobile/ios-api");
  301. tauri_utils::build::link_apple_library("Tauri", &lib_path);
  302. println!("cargo:ios_library_path={}", lib_path.display());
  303. }
  304. }
  305. define_permissions(&out_dir);
  306. }
  307. const LICENSE_HEADER: &str = r"# Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  308. # SPDX-License-Identifier: Apache-2.0
  309. # SPDX-License-Identifier: MIT
  310. ";
  311. fn define_permissions(out_dir: &Path) {
  312. for (plugin, commands) in PLUGINS {
  313. let plugin_directory_name = plugin.strip_prefix("core:").unwrap_or(plugin);
  314. let permissions_out_dir = out_dir.join("permissions").join(plugin_directory_name);
  315. let autogenerated =
  316. permissions_out_dir.join(tauri_utils::acl::build::AUTOGENERATED_FOLDER_NAME);
  317. let commands_dir = autogenerated.join("commands");
  318. tauri_utils::acl::build::autogenerate_command_permissions(
  319. &commands_dir,
  320. &commands.iter().map(|(cmd, _)| *cmd).collect::<Vec<_>>(),
  321. LICENSE_HEADER,
  322. false,
  323. );
  324. let default_permissions = commands
  325. .iter()
  326. .filter(|(_cmd, default)| *default)
  327. .map(|(cmd, _)| {
  328. let slugified_command = cmd.replace('_', "-");
  329. format!("\"allow-{slugified_command}\"")
  330. })
  331. .collect::<Vec<_>>()
  332. .join(", ");
  333. let default_toml = format!(
  334. r###"{LICENSE_HEADER}# Automatically generated - DO NOT EDIT!
  335. [default]
  336. description = "Default permissions for the plugin."
  337. permissions = [{default_permissions}]
  338. "###,
  339. );
  340. let out_path = autogenerated.join("default.toml");
  341. write_if_changed(out_path, default_toml)
  342. .unwrap_or_else(|_| panic!("unable to autogenerate default permissions"));
  343. let permissions = tauri_utils::acl::build::define_permissions(
  344. &permissions_out_dir
  345. .join("**")
  346. .join("*.toml")
  347. .to_string_lossy(),
  348. &format!("tauri:{plugin}"),
  349. out_dir,
  350. |_| true,
  351. )
  352. .unwrap_or_else(|e| panic!("failed to define permissions for {plugin}: {e}"));
  353. let docs_out_dir = Path::new("permissions")
  354. .join(plugin_directory_name)
  355. .join("autogenerated");
  356. fs::create_dir_all(&docs_out_dir).expect("failed to create plugin documentation directory");
  357. tauri_utils::acl::build::generate_docs(
  358. &permissions,
  359. &docs_out_dir,
  360. plugin.strip_prefix("tauri-plugin-").unwrap_or(plugin),
  361. )
  362. .expect("failed to generate plugin documentation page");
  363. }
  364. define_default_permission_set(out_dir);
  365. }
  366. fn define_default_permission_set(out_dir: &Path) {
  367. let permissions_out_dir = out_dir.join("permissions");
  368. fs::create_dir_all(&permissions_out_dir)
  369. .expect("failed to create core:default permissions directory");
  370. let default_toml = permissions_out_dir.join("default.toml");
  371. let toml_content = format!(
  372. r#"# {LICENSE_HEADER}
  373. [default]
  374. description = """Default core plugins set which includes:
  375. {}
  376. """
  377. permissions = [{}]
  378. "#,
  379. PLUGINS
  380. .iter()
  381. .map(|(k, _)| format!("- '{k}:default'"))
  382. .collect::<Vec<_>>()
  383. .join("\n"),
  384. PLUGINS
  385. .iter()
  386. .map(|(k, _)| format!("'{k}:default'"))
  387. .collect::<Vec<_>>()
  388. .join(",")
  389. );
  390. write_if_changed(&default_toml, toml_content)
  391. .unwrap_or_else(|_| panic!("unable to autogenerate core:default set"));
  392. let _ = tauri_utils::acl::build::define_permissions(
  393. &permissions_out_dir.join("*.toml").to_string_lossy(),
  394. "tauri:core",
  395. out_dir,
  396. |_| true,
  397. )
  398. .unwrap_or_else(|e| panic!("failed to define permissions for `core:default` : {e}"));
  399. }
  400. fn embed_manifest_for_tests() {
  401. static WINDOWS_MANIFEST_FILE: &str = "windows-app-manifest.xml";
  402. let manifest = std::env::current_dir()
  403. .unwrap()
  404. .join("../tauri-build/src")
  405. .join(WINDOWS_MANIFEST_FILE);
  406. println!("cargo:rerun-if-changed={}", manifest.display());
  407. // Embed the Windows application manifest file.
  408. println!("cargo:rustc-link-arg=/MANIFEST:EMBED");
  409. println!(
  410. "cargo:rustc-link-arg=/MANIFESTINPUT:{}",
  411. manifest.to_str().unwrap()
  412. );
  413. // Turn linker warnings into errors.
  414. println!("cargo:rustc-link-arg=/WX");
  415. }