add.rs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use anyhow::Context;
  5. use clap::Parser;
  6. use crate::{
  7. helpers::{
  8. app_paths::{app_dir, tauri_dir},
  9. cross_command,
  10. npm::PackageManager,
  11. },
  12. Result,
  13. };
  14. use std::{collections::HashMap, process::Command};
  15. #[derive(Debug, Parser)]
  16. #[clap(about = "Installs a plugin on the project")]
  17. pub struct Options {
  18. /// The plugin to add.
  19. plugin: String,
  20. /// Git tag to use.
  21. #[clap(short, long)]
  22. tag: Option<String>,
  23. /// Git rev to use.
  24. #[clap(short, long)]
  25. rev: Option<String>,
  26. /// Git branch to use.
  27. #[clap(short, long)]
  28. branch: Option<String>,
  29. }
  30. pub fn command(options: Options) -> Result<()> {
  31. let plugin = options.plugin;
  32. let crate_name = format!("tauri-plugin-{plugin}");
  33. let npm_name = format!("@tauri-apps/plugin-{plugin}");
  34. let mut plugins = plugins();
  35. let metadata = plugins.remove(plugin.as_str()).unwrap_or_default();
  36. let mut cargo = Command::new("cargo");
  37. cargo.current_dir(tauri_dir()).arg("add").arg(&crate_name);
  38. if options.tag.is_some() || options.rev.is_some() || options.branch.is_some() {
  39. cargo
  40. .arg("--git")
  41. .arg("https://github.com/tauri-apps/plugins-workspace");
  42. }
  43. if metadata.desktop_only {
  44. cargo
  45. .arg("--target")
  46. .arg(r#"cfg(not(any(target_os = "android", target_os = "ios")))"#);
  47. }
  48. let npm_spec = match (options.tag, options.rev, options.branch) {
  49. (Some(tag), None, None) => {
  50. cargo.args(["--tag", &tag]);
  51. format!("tauri-apps/tauri-plugin-{plugin}#{tag}")
  52. }
  53. (None, Some(rev), None) => {
  54. cargo.args(["--rev", &rev]);
  55. format!("tauri-apps/tauri-plugin-{plugin}#{rev}")
  56. }
  57. (None, None, Some(branch)) => {
  58. cargo.args(["--branch", &branch]);
  59. format!("tauri-apps/tauri-plugin-{plugin}#{branch}")
  60. }
  61. (None, None, None) => npm_name,
  62. _ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"),
  63. };
  64. log::info!("Installing Cargo dependency {crate_name}...");
  65. let status = cargo.status().context("failed to run `cargo add`")?;
  66. if !status.success() {
  67. anyhow::bail!("Failed to install Cargo dependency");
  68. }
  69. if !metadata.rust_only {
  70. if let Some(manager) = std::panic::catch_unwind(app_dir)
  71. .map(Some)
  72. .unwrap_or_default()
  73. .map(PackageManager::from_project)
  74. .and_then(|managers| managers.into_iter().next())
  75. {
  76. let mut cmd = match manager {
  77. PackageManager::Npm => cross_command("npm"),
  78. PackageManager::Pnpm => cross_command("pnpm"),
  79. PackageManager::Yarn => cross_command("yarn"),
  80. PackageManager::YarnBerry => cross_command("yarn"),
  81. };
  82. cmd.arg("add").arg(&npm_spec);
  83. log::info!("Installing NPM dependency {npm_spec}...");
  84. let status = cmd
  85. .status()
  86. .with_context(|| format!("failed to run {manager}"))?;
  87. if !status.success() {
  88. anyhow::bail!("Failed to install NPM dependency");
  89. }
  90. }
  91. }
  92. let rust_code = if metadata.builder {
  93. if metadata.desktop_only {
  94. format!(
  95. r#"tauri::Builder::default()
  96. .setup(|app| {{
  97. #[cfg(desktop)]
  98. app.handle().plugin(tauri_plugin_{plugin}::Builder::new().build());
  99. Ok(())
  100. }})
  101. "#,
  102. )
  103. } else {
  104. format!(
  105. r#"tauri::Builder::default()
  106. .setup(|app| {{
  107. app.handle().plugin(tauri_plugin_{plugin}::Builder::new().build());
  108. Ok(())
  109. }})
  110. "#,
  111. )
  112. }
  113. } else if metadata.desktop_only {
  114. format!(
  115. r#"tauri::Builder::default()
  116. .setup(|app| {{
  117. #[cfg(desktop)]
  118. app.handle().plugin(tauri_plugin_{plugin}::init());
  119. Ok(())
  120. }})
  121. "#,
  122. )
  123. } else {
  124. format!(
  125. r#"tauri::Builder::default().plugin(tauri_plugin_{plugin}::init())
  126. "#,
  127. )
  128. };
  129. println!("You must enable the plugin in your Rust code:\n\n{rust_code}");
  130. Ok(())
  131. }
  132. #[derive(Default)]
  133. struct PluginMetadata {
  134. desktop_only: bool,
  135. rust_only: bool,
  136. builder: bool,
  137. }
  138. // known plugins with particular cases
  139. fn plugins() -> HashMap<&'static str, PluginMetadata> {
  140. let mut plugins: HashMap<&'static str, PluginMetadata> = HashMap::new();
  141. // desktop-only
  142. for p in [
  143. "authenticator",
  144. "cli",
  145. "global-shortcut",
  146. "updater",
  147. "window-state",
  148. ] {
  149. plugins.entry(p).or_default().desktop_only = true;
  150. }
  151. // uses builder pattern
  152. for p in [
  153. "global-shortcut",
  154. "localhost",
  155. "log",
  156. "sql",
  157. "store",
  158. "stronghold",
  159. "updater",
  160. "window-state",
  161. ] {
  162. plugins.entry(p).or_default().builder = true;
  163. }
  164. // rust-only
  165. #[allow(clippy::single_element_loop)]
  166. for p in ["localhost"] {
  167. plugins.entry(p).or_default().rust_only = true;
  168. }
  169. plugins
  170. }