lib.rs 7.6 KB

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