lib.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! [![](https://github.com/tauri-apps/tauri/raw/dev/.github/splash.png)](https://tauri.app)
  5. //!
  6. //! This Rust executable provides the full interface to all of the required activities for which the CLI is required. It will run on macOS, Windows, and Linux.
  7. #![doc(
  8. html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png",
  9. html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
  10. )]
  11. use anyhow::Context;
  12. pub use anyhow::Result;
  13. mod acl;
  14. mod add;
  15. mod build;
  16. mod bundle;
  17. mod completions;
  18. mod dev;
  19. mod helpers;
  20. mod icon;
  21. mod info;
  22. mod init;
  23. mod interface;
  24. mod migrate;
  25. mod mobile;
  26. mod plugin;
  27. mod signer;
  28. use clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum};
  29. use env_logger::fmt::style::{AnsiColor, Style};
  30. use env_logger::Builder;
  31. use log::Level;
  32. use serde::{Deserialize, Serialize};
  33. use std::io::{BufReader, Write};
  34. use std::process::{exit, Command, ExitStatus, Output, Stdio};
  35. use std::{
  36. ffi::OsString,
  37. fmt::Display,
  38. fs::read_to_string,
  39. io::BufRead,
  40. path::PathBuf,
  41. str::FromStr,
  42. sync::{Arc, Mutex},
  43. };
  44. /// Tauri configuration argument option.
  45. #[derive(Debug, Clone, Serialize, Deserialize)]
  46. pub struct ConfigValue(pub(crate) serde_json::Value);
  47. impl FromStr for ConfigValue {
  48. type Err = anyhow::Error;
  49. fn from_str(config: &str) -> std::result::Result<Self, Self::Err> {
  50. if config.starts_with('{') {
  51. Ok(Self(
  52. serde_json::from_str(config).context("invalid configuration JSON")?,
  53. ))
  54. } else {
  55. let path = PathBuf::from(config);
  56. if path.exists() {
  57. Ok(Self(serde_json::from_str(
  58. &read_to_string(&path)
  59. .with_context(|| format!("invalid configuration at file {config}"))?,
  60. )?))
  61. } else {
  62. anyhow::bail!("provided configuration path does not exist")
  63. }
  64. }
  65. }
  66. }
  67. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
  68. pub enum RunMode {
  69. Desktop,
  70. #[cfg(target_os = "macos")]
  71. Ios,
  72. Android,
  73. }
  74. impl Display for RunMode {
  75. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  76. write!(
  77. f,
  78. "{}",
  79. match self {
  80. Self::Desktop => "desktop",
  81. #[cfg(target_os = "macos")]
  82. Self::Ios => "iOS",
  83. Self::Android => "android",
  84. }
  85. )
  86. }
  87. }
  88. #[derive(Deserialize)]
  89. pub struct VersionMetadata {
  90. tauri: String,
  91. #[serde(rename = "tauri-build")]
  92. tauri_build: String,
  93. #[serde(rename = "tauri-plugin")]
  94. tauri_plugin: String,
  95. }
  96. #[derive(Deserialize)]
  97. pub struct PackageJson {
  98. name: Option<String>,
  99. version: Option<String>,
  100. product_name: Option<String>,
  101. }
  102. #[derive(Parser)]
  103. #[clap(
  104. author,
  105. version,
  106. about,
  107. bin_name("cargo-tauri"),
  108. subcommand_required(true),
  109. arg_required_else_help(true),
  110. propagate_version(true),
  111. no_binary_name(true)
  112. )]
  113. pub(crate) struct Cli {
  114. /// Enables verbose logging
  115. #[clap(short, long, global = true, action = ArgAction::Count)]
  116. verbose: u8,
  117. #[clap(subcommand)]
  118. command: Commands,
  119. }
  120. #[derive(Subcommand)]
  121. enum Commands {
  122. Init(init::Options),
  123. Dev(dev::Options),
  124. Build(build::Options),
  125. Bundle(bundle::Options),
  126. Android(mobile::android::Cli),
  127. #[cfg(target_os = "macos")]
  128. Ios(mobile::ios::Cli),
  129. /// Migrate from v1 to v2
  130. Migrate,
  131. Info(info::Options),
  132. Add(add::Options),
  133. Plugin(plugin::Cli),
  134. Icon(icon::Options),
  135. Signer(signer::Cli),
  136. Completions(completions::Options),
  137. Permission(acl::permission::Cli),
  138. Capability(acl::capability::Cli),
  139. }
  140. fn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error {
  141. let mut app = I::command();
  142. err.format(&mut app)
  143. }
  144. /// Run the Tauri CLI with the passed arguments, exiting if an error occurs.
  145. ///
  146. /// The passed arguments should have the binary argument(s) stripped out before being passed.
  147. ///
  148. /// e.g.
  149. /// 1. `tauri-cli 1 2 3` -> `1 2 3`
  150. /// 2. `cargo tauri 1 2 3` -> `1 2 3`
  151. /// 3. `node tauri.js 1 2 3` -> `1 2 3`
  152. ///
  153. /// The passed `bin_name` parameter should be how you want the help messages to display the command.
  154. /// This defaults to `cargo-tauri`, but should be set to how the program was called, such as
  155. /// `cargo tauri`.
  156. pub fn run<I, A>(args: I, bin_name: Option<String>)
  157. where
  158. I: IntoIterator<Item = A>,
  159. A: Into<OsString> + Clone,
  160. {
  161. if let Err(e) = try_run(args, bin_name) {
  162. let mut message = e.to_string();
  163. if e.chain().count() > 1 {
  164. message.push(':');
  165. }
  166. e.chain().skip(1).for_each(|cause| {
  167. let m = cause.to_string();
  168. if !message.contains(&m) {
  169. message.push('\n');
  170. message.push_str(" - ");
  171. message.push_str(&m);
  172. }
  173. });
  174. log::error!("{message}");
  175. exit(1);
  176. }
  177. }
  178. /// Run the Tauri CLI with the passed arguments.
  179. ///
  180. /// It is similar to [`run`], but instead of exiting on an error, it returns a result.
  181. pub fn try_run<I, A>(args: I, bin_name: Option<String>) -> Result<()>
  182. where
  183. I: IntoIterator<Item = A>,
  184. A: Into<OsString> + Clone,
  185. {
  186. let cli = match bin_name {
  187. Some(bin_name) => Cli::command().bin_name(bin_name),
  188. None => Cli::command(),
  189. };
  190. let cli_ = cli.clone();
  191. let matches = cli.get_matches_from(args);
  192. let res = Cli::from_arg_matches(&matches).map_err(format_error::<Cli>);
  193. let cli = match res {
  194. Ok(s) => s,
  195. Err(e) => e.exit(),
  196. };
  197. let mut builder = Builder::from_default_env();
  198. let init_res = builder
  199. .format_indent(Some(12))
  200. .filter(None, verbosity_level(cli.verbose).to_level_filter())
  201. .format(|f, record| {
  202. let mut is_command_output = false;
  203. if let Some(action) = record.key_values().get("action".into()) {
  204. let action = action.to_cow_str().unwrap();
  205. is_command_output = action == "stdout" || action == "stderr";
  206. if !is_command_output {
  207. let style = Style::new().fg_color(Some(AnsiColor::Green.into())).bold();
  208. write!(f, " {style}{}{style:#} ", action)?;
  209. }
  210. } else {
  211. let style = f.default_level_style(record.level()).bold();
  212. write!(
  213. f,
  214. " {style}{}{style:#} ",
  215. prettyprint_level(record.level())
  216. )?;
  217. }
  218. if !is_command_output && log::log_enabled!(Level::Debug) {
  219. let style = Style::new().fg_color(Some(AnsiColor::Black.into()));
  220. write!(f, "[{style}{}{style:#}] ", record.target())?;
  221. }
  222. writeln!(f, "{}", record.args())
  223. })
  224. .try_init();
  225. if let Err(err) = init_res {
  226. eprintln!("Failed to attach logger: {err}");
  227. }
  228. match cli.command {
  229. Commands::Build(options) => build::command(options, cli.verbose)?,
  230. Commands::Bundle(options) => bundle::command(options, cli.verbose)?,
  231. Commands::Dev(options) => dev::command(options)?,
  232. Commands::Add(options) => add::command(options)?,
  233. Commands::Icon(options) => icon::command(options)?,
  234. Commands::Info(options) => info::command(options)?,
  235. Commands::Init(options) => init::command(options)?,
  236. Commands::Plugin(cli) => plugin::command(cli)?,
  237. Commands::Signer(cli) => signer::command(cli)?,
  238. Commands::Completions(options) => completions::command(options, cli_)?,
  239. Commands::Permission(options) => acl::permission::command(options)?,
  240. Commands::Capability(options) => acl::capability::command(options)?,
  241. Commands::Android(c) => mobile::android::command(c, cli.verbose)?,
  242. #[cfg(target_os = "macos")]
  243. Commands::Ios(c) => mobile::ios::command(c, cli.verbose)?,
  244. Commands::Migrate => migrate::command()?,
  245. }
  246. Ok(())
  247. }
  248. /// This maps the occurrence of `--verbose` flags to the correct log level
  249. fn verbosity_level(num: u8) -> Level {
  250. match num {
  251. 0 => Level::Info,
  252. 1 => Level::Debug,
  253. 2.. => Level::Trace,
  254. }
  255. }
  256. /// The default string representation for `Level` is all uppercaps which doesn't mix well with the other printed actions.
  257. fn prettyprint_level(lvl: Level) -> &'static str {
  258. match lvl {
  259. Level::Error => "Error",
  260. Level::Warn => "Warn",
  261. Level::Info => "Info",
  262. Level::Debug => "Debug",
  263. Level::Trace => "Trace",
  264. }
  265. }
  266. pub trait CommandExt {
  267. // The `pipe` function sets the stdout and stderr to properly
  268. // show the command output in the Node.js wrapper.
  269. fn piped(&mut self) -> std::io::Result<ExitStatus>;
  270. fn output_ok(&mut self) -> crate::Result<Output>;
  271. }
  272. impl CommandExt for Command {
  273. fn piped(&mut self) -> std::io::Result<ExitStatus> {
  274. self.stdout(os_pipe::dup_stdout()?);
  275. self.stderr(os_pipe::dup_stderr()?);
  276. let program = self.get_program().to_string_lossy().into_owned();
  277. log::debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
  278. self.status().map_err(Into::into)
  279. }
  280. fn output_ok(&mut self) -> crate::Result<Output> {
  281. let program = self.get_program().to_string_lossy().into_owned();
  282. log::debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
  283. self.stdout(Stdio::piped());
  284. self.stderr(Stdio::piped());
  285. let mut child = self.spawn()?;
  286. let mut stdout = child.stdout.take().map(BufReader::new).unwrap();
  287. let stdout_lines = Arc::new(Mutex::new(Vec::new()));
  288. let stdout_lines_ = stdout_lines.clone();
  289. std::thread::spawn(move || {
  290. let mut line = String::new();
  291. let mut lines = stdout_lines_.lock().unwrap();
  292. loop {
  293. line.clear();
  294. match stdout.read_line(&mut line) {
  295. Ok(0) => break,
  296. Ok(_) => {
  297. log::debug!(action = "stdout"; "{}", line.trim_end());
  298. lines.extend(line.as_bytes().to_vec());
  299. }
  300. Err(_) => (),
  301. }
  302. }
  303. });
  304. let mut stderr = child.stderr.take().map(BufReader::new).unwrap();
  305. let stderr_lines = Arc::new(Mutex::new(Vec::new()));
  306. let stderr_lines_ = stderr_lines.clone();
  307. std::thread::spawn(move || {
  308. let mut line = String::new();
  309. let mut lines = stderr_lines_.lock().unwrap();
  310. loop {
  311. line.clear();
  312. match stderr.read_line(&mut line) {
  313. Ok(0) => break,
  314. Ok(_) => {
  315. log::debug!(action = "stderr"; "{}", line.trim_end());
  316. lines.extend(line.as_bytes().to_vec());
  317. }
  318. Err(_) => (),
  319. }
  320. }
  321. });
  322. let status = child.wait()?;
  323. let output = Output {
  324. status,
  325. stdout: std::mem::take(&mut *stdout_lines.lock().unwrap()),
  326. stderr: std::mem::take(&mut *stderr_lines.lock().unwrap()),
  327. };
  328. if output.status.success() {
  329. Ok(output)
  330. } else {
  331. Err(anyhow::anyhow!("failed to run {}", program))
  332. }
  333. }
  334. }