1pub use anyhow::Result;
6
7mod build;
8mod completions;
9mod dev;
10mod helpers;
11mod icon;
12mod info;
13mod init;
14mod interface;
15mod plugin;
16mod signer;
17
18use clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand};
19use env_logger::fmt::Color;
20use env_logger::Builder;
21use log::{debug, log_enabled, Level};
22use serde::Deserialize;
23use std::io::{BufReader, Write};
24use std::process::{exit, Command, ExitStatus, Output, Stdio};
25use std::{
26 ffi::OsString,
27 io::BufRead,
28 sync::{Arc, Mutex},
29};
30
31#[derive(Deserialize)]
32pub struct VersionMetadata {
33 tauri: String,
34 #[serde(rename = "tauri-build")]
35 tauri_build: String,
36}
37
38#[derive(Deserialize)]
39pub struct PackageJson {
40 name: Option<String>,
41 version: Option<String>,
42 product_name: Option<String>,
43}
44
45#[derive(Parser)]
46#[clap(
47 author,
48 version,
49 about,
50 bin_name("cargo-tauri"),
51 subcommand_required(true),
52 arg_required_else_help(true),
53 propagate_version(true),
54 no_binary_name(true)
55)]
56pub(crate) struct Cli {
57 #[clap(short, long, global = true, action = ArgAction::Count)]
59 verbose: u8,
60 #[clap(subcommand)]
61 command: Commands,
62}
63
64#[derive(Subcommand)]
65enum Commands {
66 Build(build::Options),
67 Dev(dev::Options),
68 Icon(icon::Options),
69 Info(info::Options),
70 Init(init::Options),
71 Plugin(plugin::Cli),
72 Signer(signer::Cli),
73 Completions(completions::Options),
74}
75
76fn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error {
77 let mut app = I::command();
78 err.format(&mut app)
79}
80
81pub fn run<I, A>(args: I, bin_name: Option<String>)
94where
95 I: IntoIterator<Item = A>,
96 A: Into<OsString> + Clone,
97{
98 if let Err(e) = try_run(args, bin_name) {
99 log::error!("{:#}", e);
100 exit(1);
101 }
102}
103
104pub fn try_run<I, A>(args: I, bin_name: Option<String>) -> Result<()>
108where
109 I: IntoIterator<Item = A>,
110 A: Into<OsString> + Clone,
111{
112 let cli = match bin_name {
113 Some(bin_name) => Cli::command().bin_name(bin_name),
114 None => Cli::command(),
115 };
116 let cli_ = cli.clone();
117 let matches = cli.get_matches_from(args);
118
119 let res = Cli::from_arg_matches(&matches).map_err(format_error::<Cli>);
120 let cli = match res {
121 Ok(s) => s,
122 Err(e) => e.exit(),
123 };
124
125 let mut builder = Builder::from_default_env();
126 let init_res = builder
127 .format_indent(Some(12))
128 .filter(None, verbosity_level(cli.verbose).to_level_filter())
129 .format(|f, record| {
130 let mut is_command_output = false;
131 if let Some(action) = record.key_values().get("action".into()) {
132 let action = action.to_cow_str().unwrap();
133 is_command_output = action == "stdout" || action == "stderr";
134 if !is_command_output {
135 let mut action_style = f.style();
136 action_style.set_color(Color::Green).set_bold(true);
137
138 write!(f, "{:>12} ", action_style.value(action))?;
139 }
140 } else {
141 let mut level_style = f.default_level_style(record.level());
142 level_style.set_bold(true);
143
144 write!(
145 f,
146 "{:>12} ",
147 level_style.value(prettyprint_level(record.level()))
148 )?;
149 }
150
151 if !is_command_output && log_enabled!(Level::Debug) {
152 let mut target_style = f.style();
153 target_style.set_color(Color::Black);
154
155 write!(f, "[{}] ", target_style.value(record.target()))?;
156 }
157
158 writeln!(f, "{}", record.args())
159 })
160 .try_init();
161
162 if let Err(err) = init_res {
163 eprintln!("Failed to attach logger: {err}");
164 }
165
166 match cli.command {
167 Commands::Build(options) => build::command(options, cli.verbose)?,
168 Commands::Dev(options) => dev::command(options)?,
169 Commands::Icon(options) => icon::command(options)?,
170 Commands::Info(options) => info::command(options)?,
171 Commands::Init(options) => init::command(options)?,
172 Commands::Plugin(cli) => plugin::command(cli)?,
173 Commands::Signer(cli) => signer::command(cli)?,
174 Commands::Completions(options) => completions::command(options, cli_)?,
175 }
176
177 Ok(())
178}
179
180fn verbosity_level(num: u8) -> Level {
182 match num {
183 0 => Level::Info,
184 1 => Level::Debug,
185 2.. => Level::Trace,
186 }
187}
188
189fn prettyprint_level(lvl: Level) -> &'static str {
191 match lvl {
192 Level::Error => "Error",
193 Level::Warn => "Warn",
194 Level::Info => "Info",
195 Level::Debug => "Debug",
196 Level::Trace => "Trace",
197 }
198}
199
200pub trait CommandExt {
201 fn piped(&mut self) -> std::io::Result<ExitStatus>;
204 fn output_ok(&mut self) -> crate::Result<Output>;
205}
206
207impl CommandExt for Command {
208 fn piped(&mut self) -> std::io::Result<ExitStatus> {
209 self.stdout(os_pipe::dup_stdout()?);
210 self.stderr(os_pipe::dup_stderr()?);
211 let program = self.get_program().to_string_lossy().into_owned();
212 debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
213
214 self.status()
215 }
216
217 fn output_ok(&mut self) -> crate::Result<Output> {
218 let program = self.get_program().to_string_lossy().into_owned();
219 debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
220
221 self.stdout(Stdio::piped());
222 self.stderr(Stdio::piped());
223
224 let mut child = self.spawn()?;
225
226 let mut stdout = child.stdout.take().map(BufReader::new).unwrap();
227 let stdout_lines = Arc::new(Mutex::new(Vec::new()));
228 let stdout_lines_ = stdout_lines.clone();
229 std::thread::spawn(move || {
230 let mut line = String::new();
231 let mut lines = stdout_lines_.lock().unwrap();
232 loop {
233 line.clear();
234 match stdout.read_line(&mut line) {
235 Ok(0) => break,
236 Ok(_) => {
237 debug!(action = "stdout"; "{}", line.trim_end());
238 lines.extend(line.as_bytes().to_vec());
239 }
240 Err(_) => (),
241 }
242 }
243 });
244
245 let mut stderr = child.stderr.take().map(BufReader::new).unwrap();
246 let stderr_lines = Arc::new(Mutex::new(Vec::new()));
247 let stderr_lines_ = stderr_lines.clone();
248 std::thread::spawn(move || {
249 let mut line = String::new();
250 let mut lines = stderr_lines_.lock().unwrap();
251 loop {
252 line.clear();
253 match stderr.read_line(&mut line) {
254 Ok(0) => break,
255 Ok(_) => {
256 debug!(action = "stderr"; "{}", line.trim_end());
257 lines.extend(line.as_bytes().to_vec());
258 }
259 Err(_) => (),
260 }
261 }
262 });
263
264 let status = child.wait()?;
265
266 let output = Output {
267 status,
268 stdout: std::mem::take(&mut *stdout_lines.lock().unwrap()),
269 stderr: std::mem::take(&mut *stderr_lines.lock().unwrap()),
270 };
271
272 if output.status.success() {
273 Ok(output)
274 } else {
275 Err(anyhow::anyhow!("failed to run {}", program))
276 }
277 }
278}