-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
dev.rs
359 lines (329 loc) · 11 KB
/
dev.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{
helpers::{
app_paths::{app_dir, tauri_dir},
command_env,
config::{get as get_config, reload as reload_config, AppUrl, BeforeDevCommand, WindowUrl},
},
interface::{AppInterface, ExitReason, Interface},
CommandExt, Result,
};
use clap::{ArgAction, Parser};
use anyhow::{bail, Context};
use log::{error, info, warn};
use once_cell::sync::OnceCell;
use shared_child::SharedChild;
use std::{
env::set_current_dir,
process::{exit, Command, ExitStatus, Stdio},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
};
static BEFORE_DEV: OnceCell<Mutex<Arc<SharedChild>>> = OnceCell::new();
static KILL_BEFORE_DEV_FLAG: OnceCell<AtomicBool> = OnceCell::new();
#[cfg(unix)]
const KILL_CHILDREN_SCRIPT: &[u8] = include_bytes!("../scripts/kill-children.sh");
pub const TAURI_DEV_WATCHER_GITIGNORE: &[u8] = include_bytes!("../tauri-dev-watcher.gitignore");
#[derive(Debug, Clone, Parser)]
#[clap(about = "Tauri dev", trailing_var_arg(true))]
pub struct Options {
/// Binary to use to run the application
#[clap(short, long)]
pub runner: Option<String>,
/// Target triple to build against
#[clap(short, long)]
pub target: Option<String>,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
/// Exit on panic
#[clap(short, long)]
exit_on_panic: bool,
/// JSON string or path to JSON file to merge with tauri.conf.json
#[clap(short, long)]
pub config: Option<String>,
/// Run the code in release mode
#[clap(long = "release")]
pub release_mode: bool,
/// Command line arguments passed to the runner.
/// Use `--` to explicitly mark the start of the arguments. Arguments after a second `--` are passed to the application
/// e.g. `tauri dev -- [runnerArgs] -- [appArgs]`.
pub args: Vec<String>,
/// Disable the file watcher
#[clap(long)]
pub no_watch: bool,
/// Disable the dev server for static files.
#[clap(long)]
pub no_dev_server: bool,
/// Specify port for the dev server for static files. Defaults to 1430
/// Can also be set using `TAURI_DEV_SERVER_PORT` env var.
#[clap(long)]
pub port: Option<u16>,
}
pub fn command(options: Options) -> Result<()> {
let r = command_internal(options);
if r.is_err() {
kill_before_dev_process();
}
r
}
fn command_internal(mut options: Options) -> Result<()> {
let tauri_path = tauri_dir();
options.config = if let Some(config) = &options.config {
Some(if config.starts_with('{') {
config.to_string()
} else {
std::fs::read_to_string(config).with_context(|| "failed to read custom configuration")?
})
} else {
None
};
set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
let config = get_config(options.config.as_deref())?;
let mut interface = AppInterface::new(
config.lock().unwrap().as_ref().unwrap(),
options.target.clone(),
)?;
if let Some(before_dev) = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.before_dev_command
.clone()
{
let (script, script_cwd, wait) = match before_dev {
BeforeDevCommand::Script(s) if s.is_empty() => (None, None, false),
BeforeDevCommand::Script(s) => (Some(s), None, false),
BeforeDevCommand::ScriptWithOptions { script, cwd, wait } => {
(Some(script), cwd.map(Into::into), wait)
}
};
let cwd = script_cwd.unwrap_or_else(|| app_dir().clone());
if let Some(before_dev) = script {
info!(action = "Running"; "BeforeDevCommand (`{}`)", before_dev);
let mut env = command_env(true);
env.extend(interface.env());
#[cfg(windows)]
let mut command = {
let mut command = Command::new("cmd");
command
.arg("/S")
.arg("/C")
.arg(&before_dev)
.current_dir(cwd)
.envs(env);
command
};
#[cfg(not(windows))]
let mut command = {
let mut command = Command::new("sh");
command
.arg("-c")
.arg(&before_dev)
.current_dir(cwd)
.envs(env);
command
};
if wait {
let status = command.piped().with_context(|| {
format!(
"failed to run `{}` with `{}`",
before_dev,
if cfg!(windows) { "cmd /S /C" } else { "sh -c" }
)
})?;
if !status.success() {
bail!(
"beforeDevCommand `{}` failed with exit code {}",
before_dev,
status.code().unwrap_or_default()
);
}
} else {
command.stdin(Stdio::piped());
command.stdout(os_pipe::dup_stdout()?);
command.stderr(os_pipe::dup_stderr()?);
let child = SharedChild::spawn(&mut command)
.unwrap_or_else(|_| panic!("failed to run `{before_dev}`"));
let child = Arc::new(child);
let child_ = child.clone();
std::thread::spawn(move || {
let status = child_
.wait()
.expect("failed to wait on \"beforeDevCommand\"");
if !(status.success() || KILL_BEFORE_DEV_FLAG.get().unwrap().load(Ordering::Relaxed)) {
error!("The \"beforeDevCommand\" terminated with a non-zero status code.");
exit(status.code().unwrap_or(1));
}
});
BEFORE_DEV.set(Mutex::new(child)).unwrap();
KILL_BEFORE_DEV_FLAG.set(AtomicBool::default()).unwrap();
let _ = ctrlc::set_handler(move || {
kill_before_dev_process();
exit(130);
});
}
}
}
if options.runner.is_none() {
options
.runner
.clone_from(&config.lock().unwrap().as_ref().unwrap().build.runner);
}
let mut cargo_features = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.features
.clone()
.unwrap_or_default();
if let Some(features) = &options.features {
cargo_features.extend(features.clone());
}
let mut dev_path = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.dev_path
.clone();
if !options.no_dev_server {
if let AppUrl::Url(WindowUrl::App(path)) = &dev_path {
use crate::helpers::web_dev_server::start_dev_server;
if path.exists() {
let path = path.canonicalize()?;
let server_url = start_dev_server(path, options.port)?;
let server_url = format!("http://{server_url}");
dev_path = AppUrl::Url(WindowUrl::External(server_url.parse().unwrap()));
// TODO: in v2, use an env var to pass the url to the app context
// or better separate the config passed from the cli internally and
// config passed by the user in `--config` into to separate env vars
// and the context merges, the user first, then the internal cli config
if let Some(c) = options.config {
let mut c: tauri_utils::config::Config = serde_json::from_str(&c)?;
c.build.dev_path = dev_path.clone();
options.config = Some(serde_json::to_string(&c).unwrap());
} else {
options.config = Some(format!(r#"{{ "build": {{ "devPath": "{server_url}" }} }}"#))
}
}
}
reload_config(options.config.as_deref())?;
}
if std::env::var_os("TAURI_SKIP_DEVSERVER_CHECK") != Some("true".into()) {
if let AppUrl::Url(WindowUrl::External(dev_server_url)) = dev_path {
let host = dev_server_url
.host()
.unwrap_or_else(|| panic!("No host name in the URL"));
let port = dev_server_url
.port_or_known_default()
.unwrap_or_else(|| panic!("No port number in the URL"));
let addrs;
let addr;
let addrs = match host {
url::Host::Domain(domain) => {
use std::net::ToSocketAddrs;
addrs = (domain, port).to_socket_addrs()?;
addrs.as_slice()
}
url::Host::Ipv4(ip) => {
addr = (ip, port).into();
std::slice::from_ref(&addr)
}
url::Host::Ipv6(ip) => {
addr = (ip, port).into();
std::slice::from_ref(&addr)
}
};
let mut i = 0;
let sleep_interval = std::time::Duration::from_secs(2);
let timeout_duration = std::time::Duration::from_secs(1);
let max_attempts = 90;
'waiting: loop {
for addr in addrs.iter() {
if std::net::TcpStream::connect_timeout(addr, timeout_duration).is_ok() {
break 'waiting;
}
}
if i % 3 == 1 {
warn!(
"Waiting for your frontend dev server to start on {}...",
dev_server_url
);
}
i += 1;
if i == max_attempts {
error!(
"Could not connect to `{}` after {}s. Please make sure that is the URL to your dev server.",
dev_server_url, i * sleep_interval.as_secs()
);
exit(1);
}
std::thread::sleep(sleep_interval);
}
}
}
let exit_on_panic = options.exit_on_panic;
let no_watch = options.no_watch;
interface.dev(options.into(), move |status, reason| {
on_dev_exit(status, reason, exit_on_panic, no_watch)
})
}
fn on_dev_exit(status: ExitStatus, reason: ExitReason, exit_on_panic: bool, no_watch: bool) {
if no_watch
|| (!matches!(reason, ExitReason::TriggeredKill)
&& (exit_on_panic || matches!(reason, ExitReason::NormalExit)))
{
kill_before_dev_process();
exit(status.code().unwrap_or(0));
}
}
fn kill_before_dev_process() {
if let Some(child) = BEFORE_DEV.get() {
let child = child.lock().unwrap();
KILL_BEFORE_DEV_FLAG
.get()
.unwrap()
.store(true, Ordering::Relaxed);
#[cfg(windows)]
{
let powershell_path = std::env::var("SYSTEMROOT").map_or_else(
|_| "powershell.exe".to_string(),
|p| format!("{p}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"),
);
let _ = Command::new(powershell_path)
.arg("-NoProfile")
.arg("-Command")
.arg(format!("function Kill-Tree {{ Param([int]$ppid); Get-CimInstance Win32_Process | Where-Object {{ $_.ParentProcessId -eq $ppid }} | ForEach-Object {{ Kill-Tree $_.ProcessId }}; Stop-Process -Id $ppid -ErrorAction SilentlyContinue }}; Kill-Tree {}", child.id()))
.status();
}
#[cfg(unix)]
{
use std::io::Write;
let mut kill_children_script_path = std::env::temp_dir();
kill_children_script_path.push("kill-children.sh");
if !kill_children_script_path.exists() {
if let Ok(mut file) = std::fs::File::create(&kill_children_script_path) {
use std::os::unix::fs::PermissionsExt;
let _ = file.write_all(KILL_CHILDREN_SCRIPT);
let mut permissions = file.metadata().unwrap().permissions();
permissions.set_mode(0o770);
let _ = file.set_permissions(permissions);
}
}
let _ = Command::new(&kill_children_script_path)
.arg(child.id().to_string())
.output();
}
let _ = child.kill();
}
}