lib.rs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // Copyright 2019-2023 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. pub use anyhow::Result;
  12. mod build;
  13. mod completions;
  14. mod dev;
  15. mod helpers;
  16. mod icon;
  17. mod info;
  18. mod init;
  19. mod interface;
  20. mod migrate;
  21. mod mobile;
  22. mod plugin;
  23. mod signer;
  24. use clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum};
  25. use env_logger::fmt::Color;
  26. use env_logger::Builder;
  27. use log::{debug, log_enabled, Level};
  28. use serde::Deserialize;
  29. use std::io::{BufReader, Write};
  30. use std::process::{exit, Command, ExitStatus, Output, Stdio};
  31. use std::{
  32. ffi::OsString,
  33. fmt::Display,
  34. sync::{Arc, Mutex},
  35. };
  36. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
  37. pub enum RunMode {
  38. Desktop,
  39. #[cfg(target_os = "macos")]
  40. Ios,
  41. Android,
  42. }
  43. impl Display for RunMode {
  44. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  45. write!(
  46. f,
  47. "{}",
  48. match self {
  49. Self::Desktop => "desktop",
  50. #[cfg(target_os = "macos")]
  51. Self::Ios => "iOS",
  52. Self::Android => "android",
  53. }
  54. )
  55. }
  56. }
  57. #[derive(Deserialize)]
  58. pub struct VersionMetadata {
  59. tauri: String,
  60. #[serde(rename = "tauri-build")]
  61. tauri_build: String,
  62. }
  63. #[derive(Deserialize)]
  64. pub struct PackageJson {
  65. name: Option<String>,
  66. version: Option<String>,
  67. product_name: Option<String>,
  68. }
  69. #[derive(Parser)]
  70. #[clap(
  71. author,
  72. version,
  73. about,
  74. bin_name("cargo-tauri"),
  75. subcommand_required(true),
  76. arg_required_else_help(true),
  77. propagate_version(true),
  78. no_binary_name(true)
  79. )]
  80. pub(crate) struct Cli {
  81. /// Enables verbose logging
  82. #[clap(short, long, global = true, action = ArgAction::Count)]
  83. verbose: u8,
  84. #[clap(subcommand)]
  85. command: Commands,
  86. }
  87. #[derive(Subcommand)]
  88. enum Commands {
  89. Build(build::Options),
  90. Dev(dev::Options),
  91. Icon(icon::Options),
  92. Info(info::Options),
  93. Init(init::Options),
  94. Plugin(plugin::Cli),
  95. Signer(signer::Cli),
  96. Completions(completions::Options),
  97. Android(mobile::android::Cli),
  98. #[cfg(target_os = "macos")]
  99. Ios(mobile::ios::Cli),
  100. /// Migrate from v1 to v2
  101. Migrate,
  102. }
  103. fn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error {
  104. let mut app = I::command();
  105. err.format(&mut app)
  106. }
  107. /// Run the Tauri CLI with the passed arguments, exiting if an error occurs.
  108. ///
  109. /// The passed arguments should have the binary argument(s) stripped out before being passed.
  110. ///
  111. /// e.g.
  112. /// 1. `tauri-cli 1 2 3` -> `1 2 3`
  113. /// 2. `cargo tauri 1 2 3` -> `1 2 3`
  114. /// 3. `node tauri.js 1 2 3` -> `1 2 3`
  115. ///
  116. /// The passed `bin_name` parameter should be how you want the help messages to display the command.
  117. /// This defaults to `cargo-tauri`, but should be set to how the program was called, such as
  118. /// `cargo tauri`.
  119. pub fn run<I, A>(args: I, bin_name: Option<String>)
  120. where
  121. I: IntoIterator<Item = A>,
  122. A: Into<OsString> + Clone,
  123. {
  124. if let Err(e) = try_run(args, bin_name) {
  125. log::error!("{:#}", e);
  126. exit(1);
  127. }
  128. }
  129. /// Run the Tauri CLI with the passed arguments.
  130. ///
  131. /// It is similar to [`run`], but instead of exiting on an error, it returns a result.
  132. pub fn try_run<I, A>(args: I, bin_name: Option<String>) -> Result<()>
  133. where
  134. I: IntoIterator<Item = A>,
  135. A: Into<OsString> + Clone,
  136. {
  137. let cli = match bin_name {
  138. Some(bin_name) => Cli::command().bin_name(bin_name),
  139. None => Cli::command(),
  140. };
  141. let cli_ = cli.clone();
  142. let matches = cli.get_matches_from(args);
  143. let res = Cli::from_arg_matches(&matches).map_err(format_error::<Cli>);
  144. let cli = match res {
  145. Ok(s) => s,
  146. Err(e) => e.exit(),
  147. };
  148. let mut builder = Builder::from_default_env();
  149. let init_res = builder
  150. .format_indent(Some(12))
  151. .filter(None, verbosity_level(cli.verbose).to_level_filter())
  152. .format(|f, record| {
  153. let mut is_command_output = false;
  154. if let Some(action) = record.key_values().get("action".into()) {
  155. let action = action.to_str().unwrap();
  156. is_command_output = action == "stdout" || action == "stderr";
  157. if !is_command_output {
  158. let mut action_style = f.style();
  159. action_style.set_color(Color::Green).set_bold(true);
  160. write!(f, "{:>12} ", action_style.value(action))?;
  161. }
  162. } else {
  163. let mut level_style = f.default_level_style(record.level());
  164. level_style.set_bold(true);
  165. write!(
  166. f,
  167. "{:>12} ",
  168. level_style.value(prettyprint_level(record.level()))
  169. )?;
  170. }
  171. if !is_command_output && log_enabled!(Level::Debug) {
  172. let mut target_style = f.style();
  173. target_style.set_color(Color::Black);
  174. write!(f, "[{}] ", target_style.value(record.target()))?;
  175. }
  176. writeln!(f, "{}", record.args())
  177. })
  178. .try_init();
  179. if let Err(err) = init_res {
  180. eprintln!("Failed to attach logger: {err}");
  181. }
  182. match cli.command {
  183. Commands::Build(options) => build::command(options, cli.verbose)?,
  184. Commands::Dev(options) => dev::command(options)?,
  185. Commands::Icon(options) => icon::command(options)?,
  186. Commands::Info(options) => info::command(options)?,
  187. Commands::Init(options) => init::command(options)?,
  188. Commands::Plugin(cli) => plugin::command(cli)?,
  189. Commands::Signer(cli) => signer::command(cli)?,
  190. Commands::Completions(options) => completions::command(options, cli_)?,
  191. Commands::Android(c) => mobile::android::command(c, cli.verbose)?,
  192. #[cfg(target_os = "macos")]
  193. Commands::Ios(c) => mobile::ios::command(c, cli.verbose)?,
  194. Commands::Migrate => migrate::command()?,
  195. }
  196. Ok(())
  197. }
  198. /// This maps the occurrence of `--verbose` flags to the correct log level
  199. fn verbosity_level(num: u8) -> Level {
  200. match num {
  201. 0 => Level::Info,
  202. 1 => Level::Debug,
  203. 2.. => Level::Trace,
  204. }
  205. }
  206. /// The default string representation for `Level` is all uppercaps which doesn't mix well with the other printed actions.
  207. fn prettyprint_level(lvl: Level) -> &'static str {
  208. match lvl {
  209. Level::Error => "Error",
  210. Level::Warn => "Warn",
  211. Level::Info => "Info",
  212. Level::Debug => "Debug",
  213. Level::Trace => "Trace",
  214. }
  215. }
  216. pub trait CommandExt {
  217. // The `pipe` function sets the stdout and stderr to properly
  218. // show the command output in the Node.js wrapper.
  219. fn piped(&mut self) -> std::io::Result<ExitStatus>;
  220. fn output_ok(&mut self) -> crate::Result<Output>;
  221. }
  222. impl CommandExt for Command {
  223. fn piped(&mut self) -> std::io::Result<ExitStatus> {
  224. self.stdout(os_pipe::dup_stdout()?);
  225. self.stderr(os_pipe::dup_stderr()?);
  226. let program = self.get_program().to_string_lossy().into_owned();
  227. debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
  228. self.status().map_err(Into::into)
  229. }
  230. fn output_ok(&mut self) -> crate::Result<Output> {
  231. let program = self.get_program().to_string_lossy().into_owned();
  232. debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
  233. self.stdout(Stdio::piped());
  234. self.stderr(Stdio::piped());
  235. let mut child = self.spawn()?;
  236. let mut stdout = child.stdout.take().map(BufReader::new).unwrap();
  237. let stdout_lines = Arc::new(Mutex::new(Vec::new()));
  238. let stdout_lines_ = stdout_lines.clone();
  239. std::thread::spawn(move || {
  240. let mut buf = Vec::new();
  241. let mut lines = stdout_lines_.lock().unwrap();
  242. loop {
  243. buf.clear();
  244. match tauri_utils::io::read_line(&mut stdout, &mut buf) {
  245. Ok(s) if s == 0 => break,
  246. _ => (),
  247. }
  248. debug!(action = "stdout"; "{}", String::from_utf8_lossy(&buf));
  249. lines.extend(buf.clone());
  250. lines.push(b'\n');
  251. }
  252. });
  253. let mut stderr = child.stderr.take().map(BufReader::new).unwrap();
  254. let stderr_lines = Arc::new(Mutex::new(Vec::new()));
  255. let stderr_lines_ = stderr_lines.clone();
  256. std::thread::spawn(move || {
  257. let mut buf = Vec::new();
  258. let mut lines = stderr_lines_.lock().unwrap();
  259. loop {
  260. buf.clear();
  261. match tauri_utils::io::read_line(&mut stderr, &mut buf) {
  262. Ok(s) if s == 0 => break,
  263. _ => (),
  264. }
  265. debug!(action = "stderr"; "{}", String::from_utf8_lossy(&buf));
  266. lines.extend(buf.clone());
  267. lines.push(b'\n');
  268. }
  269. });
  270. let status = child.wait()?;
  271. let output = Output {
  272. status,
  273. stdout: std::mem::take(&mut *stdout_lines.lock().unwrap()),
  274. stderr: std::mem::take(&mut *stderr_lines.lock().unwrap()),
  275. };
  276. if output.status.success() {
  277. Ok(output)
  278. } else {
  279. Err(anyhow::anyhow!("failed to run {}", program))
  280. }
  281. }
  282. }