mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-15 05:31:42 +03:00
refactor(cli): prepare for mobile implementation (#4952)
This commit is contained in:
parent
e4d5022077
commit
c53d9ea1b3
@ -4,10 +4,10 @@
|
||||
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs::{rename, File, FileType},
|
||||
io::{BufReader, ErrorKind, Read, Write},
|
||||
fs::{File, FileType},
|
||||
io::{Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, ExitStatus, Stdio},
|
||||
process::ExitStatus,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@ -31,14 +31,12 @@ use tauri_bundler::{
|
||||
};
|
||||
|
||||
use super::{AppSettings, ExitReason, Interface};
|
||||
use crate::{
|
||||
helpers::{
|
||||
app_paths::tauri_dir,
|
||||
config::{reload as reload_config, wix_settings, Config},
|
||||
},
|
||||
CommandExt,
|
||||
use crate::helpers::{
|
||||
app_paths::tauri_dir,
|
||||
config::{reload as reload_config, wix_settings, Config},
|
||||
};
|
||||
|
||||
mod desktop;
|
||||
mod manifest;
|
||||
use manifest::{rewrite_manifest, Manifest};
|
||||
|
||||
@ -108,7 +106,7 @@ impl DevChild {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Target {
|
||||
pub struct Target {
|
||||
name: String,
|
||||
installed: bool,
|
||||
}
|
||||
@ -159,59 +157,17 @@ impl Interface for Rust {
|
||||
}
|
||||
|
||||
fn build(&mut self, mut options: Options) -> crate::Result<()> {
|
||||
let bin_path = self.app_settings.app_binary_path(&options)?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
|
||||
let bin_name = bin_path.file_stem().unwrap();
|
||||
|
||||
options
|
||||
.features
|
||||
.get_or_insert(Vec::new())
|
||||
.push("custom-protocol".into());
|
||||
|
||||
if !std::env::var("STATIC_VCRUNTIME").map_or(false, |v| v == "false") {
|
||||
std::env::set_var("STATIC_VCRUNTIME", "true");
|
||||
}
|
||||
|
||||
if options.target == Some("universal-apple-darwin".into()) {
|
||||
std::fs::create_dir_all(&out_dir)
|
||||
.with_context(|| "failed to create project out directory")?;
|
||||
|
||||
let mut lipo_cmd = Command::new("lipo");
|
||||
lipo_cmd
|
||||
.arg("-create")
|
||||
.arg("-output")
|
||||
.arg(out_dir.join(&bin_name));
|
||||
for triple in ["aarch64-apple-darwin", "x86_64-apple-darwin"] {
|
||||
let mut options = options.clone();
|
||||
options.target.replace(triple.into());
|
||||
|
||||
let triple_out_dir = self
|
||||
.app_settings
|
||||
.out_dir(Some(triple.into()), options.debug)
|
||||
.with_context(|| format!("failed to get {} out dir", triple))?;
|
||||
self
|
||||
.build_production_app(options)
|
||||
.with_context(|| format!("failed to build {} binary", triple))?;
|
||||
|
||||
lipo_cmd.arg(triple_out_dir.join(&bin_name));
|
||||
}
|
||||
|
||||
let lipo_status = lipo_cmd.output_ok()?.status;
|
||||
if !lipo_status.success() {
|
||||
return Err(anyhow::anyhow!(format!(
|
||||
"Result of `lipo` command was unsuccessful: {}. (Is `lipo` installed?)",
|
||||
lipo_status
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
self
|
||||
.build_production_app(options)
|
||||
.with_context(|| "failed to build app")?;
|
||||
}
|
||||
|
||||
rename_app(&bin_path, self.product_name.as_deref())?;
|
||||
|
||||
desktop::build(
|
||||
options,
|
||||
&self.app_settings,
|
||||
self.product_name.clone(),
|
||||
&mut self.available_targets,
|
||||
self.config_features.clone(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -268,50 +224,11 @@ fn lookup<F: FnMut(FileType, PathBuf)>(dir: &Path, mut f: F) {
|
||||
}
|
||||
|
||||
impl Rust {
|
||||
fn fetch_available_targets(&mut self) {
|
||||
if let Ok(output) = Command::new("rustup").args(["target", "list"]).output() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
|
||||
self.available_targets.replace(
|
||||
stdout
|
||||
.split('\n')
|
||||
.map(|t| {
|
||||
let mut s = t.split(' ');
|
||||
let name = s.next().unwrap().to_string();
|
||||
let installed = s.next().map(|v| v == "(installed)").unwrap_or_default();
|
||||
Target { name, installed }
|
||||
})
|
||||
.filter(|t| !t.name.is_empty())
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_target(&self, target: &str) -> crate::Result<()> {
|
||||
if let Some(available_targets) = &self.available_targets {
|
||||
if let Some(target) = available_targets.iter().find(|t| t.name == target) {
|
||||
if !target.installed {
|
||||
anyhow::bail!(
|
||||
"Target {target} is not installed (installed targets: {installed}). Please run `rustup target add {target}`.",
|
||||
target = target.name,
|
||||
installed = available_targets.iter().filter(|t| t.installed).map(|t| t.name.as_str()).collect::<Vec<&str>>().join(", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
if !available_targets.iter().any(|t| t.name == target) {
|
||||
anyhow::bail!("Target {target} does not exist. Please run `rustup target list` to see the available targets.", target = target);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_dev<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
mut options: Options,
|
||||
on_exit: F,
|
||||
) -> crate::Result<DevChild> {
|
||||
let bin_path = self.app_settings.app_binary_path(&options)?;
|
||||
let product_name = self.product_name.clone();
|
||||
|
||||
if !options.args.contains(&"--no-default-features".into()) {
|
||||
let manifest_features = self.app_settings.manifest.features();
|
||||
let enable_features: Vec<String> = manifest_features
|
||||
@ -350,51 +267,15 @@ impl Rust {
|
||||
}
|
||||
options.args = args;
|
||||
|
||||
let manually_killed_app = Arc::new(AtomicBool::default());
|
||||
let manually_killed_app_ = manually_killed_app.clone();
|
||||
let app_child = Arc::new(Mutex::new(None));
|
||||
let app_child_ = app_child.clone();
|
||||
|
||||
let build_child = self.build_dev_app(options, move |status, reason| {
|
||||
if status.success() {
|
||||
let bin_path =
|
||||
rename_app(&bin_path, product_name.as_deref()).expect("failed to rename app");
|
||||
let mut app = Command::new(bin_path);
|
||||
app.stdout(os_pipe::dup_stdout().unwrap());
|
||||
app.stderr(os_pipe::dup_stderr().unwrap());
|
||||
app.args(run_args);
|
||||
let app_child = Arc::new(SharedChild::spawn(&mut app).unwrap());
|
||||
let app_child_t = app_child.clone();
|
||||
std::thread::spawn(move || {
|
||||
let status = app_child_t.wait().expect("failed to wait on app");
|
||||
on_exit(
|
||||
status,
|
||||
if manually_killed_app_.load(Ordering::Relaxed) {
|
||||
ExitReason::TriggeredKill
|
||||
} else {
|
||||
ExitReason::NormalExit
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
app_child_.lock().unwrap().replace(app_child);
|
||||
} else {
|
||||
on_exit(
|
||||
status,
|
||||
if manually_killed_app_.load(Ordering::Relaxed) {
|
||||
ExitReason::TriggeredKill
|
||||
} else {
|
||||
reason
|
||||
},
|
||||
);
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(DevChild {
|
||||
manually_killed_app,
|
||||
build_child,
|
||||
app_child,
|
||||
})
|
||||
desktop::run_dev(
|
||||
options,
|
||||
run_args,
|
||||
&mut self.available_targets,
|
||||
self.config_features.clone(),
|
||||
&self.app_settings,
|
||||
self.product_name.clone(),
|
||||
on_exit,
|
||||
)
|
||||
}
|
||||
|
||||
fn run_dev_watcher<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
|
||||
@ -480,158 +361,6 @@ impl Rust {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_production_app(&mut self, options: Options) -> crate::Result<()> {
|
||||
let mut build_cmd = self.build_command(options)?;
|
||||
let runner = build_cmd.get_program().to_string_lossy().into_owned();
|
||||
match build_cmd.piped() {
|
||||
Ok(status) if status.success() => Ok(()),
|
||||
Ok(_) => Err(anyhow::anyhow!("failed to build app")),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => Err(anyhow::anyhow!(
|
||||
"`{}` command not found.{}",
|
||||
runner,
|
||||
if runner == "cargo" {
|
||||
" Please follow the Tauri setup guide: https://tauri.app/v1/guides/getting-started/prerequisites"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_dev_app<F: FnOnce(ExitStatus, ExitReason) + Send + 'static>(
|
||||
&mut self,
|
||||
options: Options,
|
||||
on_exit: F,
|
||||
) -> crate::Result<Arc<SharedChild>> {
|
||||
let mut build_cmd = self.build_command(options)?;
|
||||
let runner = build_cmd.get_program().to_string_lossy().into_owned();
|
||||
build_cmd
|
||||
.env(
|
||||
"CARGO_TERM_PROGRESS_WIDTH",
|
||||
terminal::stderr_width()
|
||||
.map(|width| {
|
||||
if cfg!(windows) {
|
||||
std::cmp::min(60, width)
|
||||
} else {
|
||||
width
|
||||
}
|
||||
})
|
||||
.unwrap_or(if cfg!(windows) { 60 } else { 80 })
|
||||
.to_string(),
|
||||
)
|
||||
.env("CARGO_TERM_PROGRESS_WHEN", "always");
|
||||
build_cmd.arg("--color");
|
||||
build_cmd.arg("always");
|
||||
|
||||
build_cmd.stdout(os_pipe::dup_stdout()?);
|
||||
build_cmd.stderr(Stdio::piped());
|
||||
|
||||
let build_child = match SharedChild::spawn(&mut build_cmd) {
|
||||
Ok(c) => Ok(c),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => Err(anyhow::anyhow!(
|
||||
"`{}` command not found.{}",
|
||||
runner,
|
||||
if runner == "cargo" {
|
||||
" Please follow the Tauri setup guide: https://tauri.app/v1/guides/getting-started/prerequisites"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)),
|
||||
Err(e) => Err(e.into()),
|
||||
}?;
|
||||
let build_child = Arc::new(build_child);
|
||||
let build_child_stderr = build_child.take_stderr().unwrap();
|
||||
let mut stderr = BufReader::new(build_child_stderr);
|
||||
let stderr_lines = Arc::new(Mutex::new(Vec::new()));
|
||||
let stderr_lines_ = stderr_lines.clone();
|
||||
std::thread::spawn(move || {
|
||||
let mut buf = Vec::new();
|
||||
let mut lines = stderr_lines_.lock().unwrap();
|
||||
let mut io_stderr = std::io::stderr();
|
||||
loop {
|
||||
buf.clear();
|
||||
match tauri_utils::io::read_line(&mut stderr, &mut buf) {
|
||||
Ok(s) if s == 0 => break,
|
||||
_ => (),
|
||||
}
|
||||
let _ = io_stderr.write_all(&buf);
|
||||
if !buf.ends_with(&[b'\r']) {
|
||||
let _ = io_stderr.write_all(b"\n");
|
||||
}
|
||||
lines.push(String::from_utf8_lossy(&buf).into_owned());
|
||||
}
|
||||
});
|
||||
|
||||
let build_child_ = build_child.clone();
|
||||
std::thread::spawn(move || {
|
||||
let status = build_child_.wait().expect("failed to wait on build");
|
||||
|
||||
if status.success() {
|
||||
on_exit(status, ExitReason::NormalExit);
|
||||
} else {
|
||||
let is_cargo_compile_error = stderr_lines
|
||||
.lock()
|
||||
.unwrap()
|
||||
.last()
|
||||
.map(|l| l.contains("could not compile"))
|
||||
.unwrap_or_default();
|
||||
stderr_lines.lock().unwrap().clear();
|
||||
|
||||
on_exit(
|
||||
status,
|
||||
if status.code() == Some(101) && is_cargo_compile_error {
|
||||
ExitReason::CompilationFailed
|
||||
} else {
|
||||
ExitReason::NormalExit
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(build_child)
|
||||
}
|
||||
|
||||
fn build_command(&mut self, options: Options) -> crate::Result<Command> {
|
||||
let runner = options.runner.unwrap_or_else(|| "cargo".into());
|
||||
|
||||
if let Some(target) = &options.target {
|
||||
if self.available_targets.is_none() {
|
||||
self.fetch_available_targets();
|
||||
}
|
||||
self.validate_target(target)?;
|
||||
}
|
||||
|
||||
let mut args = Vec::new();
|
||||
if !options.args.is_empty() {
|
||||
args.extend(options.args);
|
||||
}
|
||||
|
||||
let mut features = self.config_features.clone();
|
||||
if let Some(f) = options.features {
|
||||
features.extend(f);
|
||||
}
|
||||
if !features.is_empty() {
|
||||
args.push("--features".into());
|
||||
args.push(features.join(","));
|
||||
}
|
||||
|
||||
if !options.debug {
|
||||
args.push("--release".into());
|
||||
}
|
||||
|
||||
if let Some(target) = options.target {
|
||||
args.push("--target".into());
|
||||
args.push(target);
|
||||
}
|
||||
|
||||
let mut build_cmd = Command::new(&runner);
|
||||
build_cmd.arg("build");
|
||||
build_cmd.args(args);
|
||||
|
||||
Ok(build_cmd)
|
||||
}
|
||||
}
|
||||
|
||||
/// The `workspace` section of the app configuration (read from Cargo.toml).
|
||||
@ -1153,106 +882,3 @@ fn tauri_config_to_bundle_settings(
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn rename_app(bin_path: &Path, product_name: Option<&str>) -> crate::Result<PathBuf> {
|
||||
if let Some(product_name) = product_name {
|
||||
#[cfg(target_os = "linux")]
|
||||
let product_name = product_name.to_kebab_case();
|
||||
|
||||
let product_path = bin_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(&product_name)
|
||||
.with_extension(bin_path.extension().unwrap_or_default());
|
||||
|
||||
rename(&bin_path, &product_path).with_context(|| {
|
||||
format!(
|
||||
"failed to rename `{}` to `{}`",
|
||||
bin_path.display(),
|
||||
product_path.display(),
|
||||
)
|
||||
})?;
|
||||
Ok(product_path)
|
||||
} else {
|
||||
Ok(bin_path.to_path_buf())
|
||||
}
|
||||
}
|
||||
|
||||
// taken from https://github.com/rust-lang/cargo/blob/78b10d4e611ab0721fc3aeaf0edd5dd8f4fdc372/src/cargo/core/shell.rs#L514
|
||||
#[cfg(unix)]
|
||||
mod terminal {
|
||||
use std::mem;
|
||||
|
||||
pub fn stderr_width() -> Option<usize> {
|
||||
unsafe {
|
||||
let mut winsize: libc::winsize = mem::zeroed();
|
||||
// The .into() here is needed for FreeBSD which defines TIOCGWINSZ
|
||||
// as c_uint but ioctl wants c_ulong.
|
||||
#[allow(clippy::useless_conversion)]
|
||||
if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ.into(), &mut winsize) < 0 {
|
||||
return None;
|
||||
}
|
||||
if winsize.ws_col > 0 {
|
||||
Some(winsize.ws_col as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// taken from https://github.com/rust-lang/cargo/blob/78b10d4e611ab0721fc3aeaf0edd5dd8f4fdc372/src/cargo/core/shell.rs#L543
|
||||
#[cfg(windows)]
|
||||
mod terminal {
|
||||
use std::{cmp, mem, ptr};
|
||||
use winapi::um::fileapi::*;
|
||||
use winapi::um::handleapi::*;
|
||||
use winapi::um::processenv::*;
|
||||
use winapi::um::winbase::*;
|
||||
use winapi::um::wincon::*;
|
||||
use winapi::um::winnt::*;
|
||||
|
||||
pub fn stderr_width() -> Option<usize> {
|
||||
unsafe {
|
||||
let stdout = GetStdHandle(STD_ERROR_HANDLE);
|
||||
let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
|
||||
if GetConsoleScreenBufferInfo(stdout, &mut csbi) != 0 {
|
||||
return Some((csbi.srWindow.Right - csbi.srWindow.Left) as usize);
|
||||
}
|
||||
|
||||
// On mintty/msys/cygwin based terminals, the above fails with
|
||||
// INVALID_HANDLE_VALUE. Use an alternate method which works
|
||||
// in that case as well.
|
||||
let h = CreateFileA(
|
||||
"CONOUT$\0".as_ptr() as *const CHAR,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
ptr::null_mut(),
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
if h == INVALID_HANDLE_VALUE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
|
||||
let rc = GetConsoleScreenBufferInfo(h, &mut csbi);
|
||||
CloseHandle(h);
|
||||
if rc != 0 {
|
||||
let width = (csbi.srWindow.Right - csbi.srWindow.Left) as usize;
|
||||
// Unfortunately cygwin/mintty does not set the size of the
|
||||
// backing console to match the actual window size. This
|
||||
// always reports a size of 80 or 120 (not sure what
|
||||
// determines that). Use a conservative max of 60 which should
|
||||
// work in most circumstances. ConEmu does some magic to
|
||||
// resize the console correctly, but there's no reasonable way
|
||||
// to detect which kind of terminal we are running in, or if
|
||||
// GetConsoleScreenBufferInfo returns accurate information.
|
||||
return Some(cmp::min(60, width));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
436
tooling/cli/src/interface/rust/desktop.rs
Normal file
436
tooling/cli/src/interface/rust/desktop.rs
Normal file
@ -0,0 +1,436 @@
|
||||
use super::{AppSettings, DevChild, ExitReason, Options, RustAppSettings, Target};
|
||||
use crate::CommandExt;
|
||||
|
||||
use anyhow::Context;
|
||||
use heck::ToKebabCase;
|
||||
use shared_child::SharedChild;
|
||||
use std::{
|
||||
fs::rename,
|
||||
io::{BufReader, ErrorKind, Write},
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, ExitStatus, Stdio},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn run_dev<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
|
||||
options: Options,
|
||||
run_args: Vec<String>,
|
||||
available_targets: &mut Option<Vec<Target>>,
|
||||
config_features: Vec<String>,
|
||||
app_settings: &RustAppSettings,
|
||||
product_name: Option<String>,
|
||||
on_exit: F,
|
||||
) -> crate::Result<DevChild> {
|
||||
let bin_path = app_settings.app_binary_path(&options)?;
|
||||
|
||||
let manually_killed_app = Arc::new(AtomicBool::default());
|
||||
let manually_killed_app_ = manually_killed_app.clone();
|
||||
let app_child = Arc::new(Mutex::new(None));
|
||||
let app_child_ = app_child.clone();
|
||||
|
||||
let build_child = build_dev_app(
|
||||
options,
|
||||
available_targets,
|
||||
config_features,
|
||||
move |status, reason| {
|
||||
if status.success() {
|
||||
let bin_path =
|
||||
rename_app(&bin_path, product_name.as_deref()).expect("failed to rename app");
|
||||
let mut app = Command::new(bin_path);
|
||||
app.stdout(os_pipe::dup_stdout().unwrap());
|
||||
app.stderr(os_pipe::dup_stderr().unwrap());
|
||||
app.args(run_args);
|
||||
let app_child = Arc::new(SharedChild::spawn(&mut app).unwrap());
|
||||
let app_child_t = app_child.clone();
|
||||
std::thread::spawn(move || {
|
||||
let status = app_child_t.wait().expect("failed to wait on app");
|
||||
on_exit(
|
||||
status,
|
||||
if manually_killed_app_.load(Ordering::Relaxed) {
|
||||
ExitReason::TriggeredKill
|
||||
} else {
|
||||
ExitReason::NormalExit
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
app_child_.lock().unwrap().replace(app_child);
|
||||
} else {
|
||||
on_exit(
|
||||
status,
|
||||
if manually_killed_app_.load(Ordering::Relaxed) {
|
||||
ExitReason::TriggeredKill
|
||||
} else {
|
||||
reason
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(DevChild {
|
||||
manually_killed_app,
|
||||
build_child,
|
||||
app_child,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build(
|
||||
options: Options,
|
||||
app_settings: &RustAppSettings,
|
||||
product_name: Option<String>,
|
||||
available_targets: &mut Option<Vec<Target>>,
|
||||
config_features: Vec<String>,
|
||||
) -> crate::Result<()> {
|
||||
let bin_path = app_settings.app_binary_path(&options)?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
|
||||
let bin_name = bin_path.file_stem().unwrap();
|
||||
|
||||
if !std::env::var("STATIC_VCRUNTIME").map_or(false, |v| v == "false") {
|
||||
std::env::set_var("STATIC_VCRUNTIME", "true");
|
||||
}
|
||||
|
||||
if options.target == Some("universal-apple-darwin".into()) {
|
||||
std::fs::create_dir_all(&out_dir).with_context(|| "failed to create project out directory")?;
|
||||
|
||||
let mut lipo_cmd = Command::new("lipo");
|
||||
lipo_cmd
|
||||
.arg("-create")
|
||||
.arg("-output")
|
||||
.arg(out_dir.join(&bin_name));
|
||||
for triple in ["aarch64-apple-darwin", "x86_64-apple-darwin"] {
|
||||
let mut options = options.clone();
|
||||
options.target.replace(triple.into());
|
||||
|
||||
let triple_out_dir = app_settings
|
||||
.out_dir(Some(triple.into()), options.debug)
|
||||
.with_context(|| format!("failed to get {} out dir", triple))?;
|
||||
|
||||
build_production_app(options, available_targets, config_features.clone())
|
||||
.with_context(|| format!("failed to build {} binary", triple))?;
|
||||
|
||||
lipo_cmd.arg(triple_out_dir.join(&bin_name));
|
||||
}
|
||||
|
||||
let lipo_status = lipo_cmd.output_ok()?.status;
|
||||
if !lipo_status.success() {
|
||||
return Err(anyhow::anyhow!(format!(
|
||||
"Result of `lipo` command was unsuccessful: {}. (Is `lipo` installed?)",
|
||||
lipo_status
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
build_production_app(options, available_targets, config_features)
|
||||
.with_context(|| "failed to build app")?;
|
||||
}
|
||||
|
||||
rename_app(&bin_path, product_name.as_deref())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_dev_app<F: FnOnce(ExitStatus, ExitReason) + Send + 'static>(
|
||||
options: Options,
|
||||
available_targets: &mut Option<Vec<Target>>,
|
||||
config_features: Vec<String>,
|
||||
on_exit: F,
|
||||
) -> crate::Result<Arc<SharedChild>> {
|
||||
let mut build_cmd = build_command(options, available_targets, config_features)?;
|
||||
let runner = build_cmd.get_program().to_string_lossy().into_owned();
|
||||
build_cmd
|
||||
.env(
|
||||
"CARGO_TERM_PROGRESS_WIDTH",
|
||||
terminal::stderr_width()
|
||||
.map(|width| {
|
||||
if cfg!(windows) {
|
||||
std::cmp::min(60, width)
|
||||
} else {
|
||||
width
|
||||
}
|
||||
})
|
||||
.unwrap_or(if cfg!(windows) { 60 } else { 80 })
|
||||
.to_string(),
|
||||
)
|
||||
.env("CARGO_TERM_PROGRESS_WHEN", "always");
|
||||
build_cmd.arg("--color");
|
||||
build_cmd.arg("always");
|
||||
|
||||
build_cmd.stdout(os_pipe::dup_stdout()?);
|
||||
build_cmd.stderr(Stdio::piped());
|
||||
|
||||
let build_child = match SharedChild::spawn(&mut build_cmd) {
|
||||
Ok(c) => Ok(c),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => Err(anyhow::anyhow!(
|
||||
"`{}` command not found.{}",
|
||||
runner,
|
||||
if runner == "cargo" {
|
||||
" Please follow the Tauri setup guide: https://tauri.app/v1/guides/getting-started/prerequisites"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)),
|
||||
Err(e) => Err(e.into()),
|
||||
}?;
|
||||
let build_child = Arc::new(build_child);
|
||||
let build_child_stderr = build_child.take_stderr().unwrap();
|
||||
let mut stderr = BufReader::new(build_child_stderr);
|
||||
let stderr_lines = Arc::new(Mutex::new(Vec::new()));
|
||||
let stderr_lines_ = stderr_lines.clone();
|
||||
std::thread::spawn(move || {
|
||||
let mut buf = Vec::new();
|
||||
let mut lines = stderr_lines_.lock().unwrap();
|
||||
let mut io_stderr = std::io::stderr();
|
||||
loop {
|
||||
buf.clear();
|
||||
match tauri_utils::io::read_line(&mut stderr, &mut buf) {
|
||||
Ok(s) if s == 0 => break,
|
||||
_ => (),
|
||||
}
|
||||
let _ = io_stderr.write_all(&buf);
|
||||
if !buf.ends_with(&[b'\r']) {
|
||||
let _ = io_stderr.write_all(b"\n");
|
||||
}
|
||||
lines.push(String::from_utf8_lossy(&buf).into_owned());
|
||||
}
|
||||
});
|
||||
|
||||
let build_child_ = build_child.clone();
|
||||
std::thread::spawn(move || {
|
||||
let status = build_child_.wait().expect("failed to wait on build");
|
||||
|
||||
if status.success() {
|
||||
on_exit(status, ExitReason::NormalExit);
|
||||
} else {
|
||||
let is_cargo_compile_error = stderr_lines
|
||||
.lock()
|
||||
.unwrap()
|
||||
.last()
|
||||
.map(|l| l.contains("could not compile"))
|
||||
.unwrap_or_default();
|
||||
stderr_lines.lock().unwrap().clear();
|
||||
|
||||
on_exit(
|
||||
status,
|
||||
if status.code() == Some(101) && is_cargo_compile_error {
|
||||
ExitReason::CompilationFailed
|
||||
} else {
|
||||
ExitReason::NormalExit
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(build_child)
|
||||
}
|
||||
|
||||
fn build_production_app(
|
||||
options: Options,
|
||||
available_targets: &mut Option<Vec<Target>>,
|
||||
config_features: Vec<String>,
|
||||
) -> crate::Result<()> {
|
||||
let mut build_cmd = build_command(options, available_targets, config_features)?;
|
||||
let runner = build_cmd.get_program().to_string_lossy().into_owned();
|
||||
match build_cmd.piped() {
|
||||
Ok(status) if status.success() => Ok(()),
|
||||
Ok(_) => Err(anyhow::anyhow!("failed to build app")),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => Err(anyhow::anyhow!(
|
||||
"`{}` command not found.{}",
|
||||
runner,
|
||||
if runner == "cargo" {
|
||||
" Please follow the Tauri setup guide: https://tauri.app/v1/guides/getting-started/prerequisites"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_command(
|
||||
options: Options,
|
||||
available_targets: &mut Option<Vec<Target>>,
|
||||
config_features: Vec<String>,
|
||||
) -> crate::Result<Command> {
|
||||
let runner = options.runner.unwrap_or_else(|| "cargo".into());
|
||||
|
||||
if let Some(target) = &options.target {
|
||||
if available_targets.is_none() {
|
||||
*available_targets = fetch_available_targets();
|
||||
}
|
||||
validate_target(available_targets, target)?;
|
||||
}
|
||||
|
||||
let mut args = Vec::new();
|
||||
if !options.args.is_empty() {
|
||||
args.extend(options.args);
|
||||
}
|
||||
|
||||
let mut features = config_features;
|
||||
if let Some(f) = options.features {
|
||||
features.extend(f);
|
||||
}
|
||||
if !features.is_empty() {
|
||||
args.push("--features".into());
|
||||
args.push(features.join(","));
|
||||
}
|
||||
|
||||
if !options.debug {
|
||||
args.push("--release".into());
|
||||
}
|
||||
|
||||
if let Some(target) = options.target {
|
||||
args.push("--target".into());
|
||||
args.push(target);
|
||||
}
|
||||
|
||||
let mut build_cmd = Command::new(&runner);
|
||||
build_cmd.arg("build");
|
||||
build_cmd.args(args);
|
||||
|
||||
Ok(build_cmd)
|
||||
}
|
||||
|
||||
fn fetch_available_targets() -> Option<Vec<Target>> {
|
||||
if let Ok(output) = Command::new("rustup").args(["target", "list"]).output() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
|
||||
Some(
|
||||
stdout
|
||||
.split('\n')
|
||||
.map(|t| {
|
||||
let mut s = t.split(' ');
|
||||
let name = s.next().unwrap().to_string();
|
||||
let installed = s.next().map(|v| v == "(installed)").unwrap_or_default();
|
||||
Target { name, installed }
|
||||
})
|
||||
.filter(|t| !t.name.is_empty())
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_target(available_targets: &Option<Vec<Target>>, target: &str) -> crate::Result<()> {
|
||||
if let Some(available_targets) = available_targets {
|
||||
if let Some(target) = available_targets.iter().find(|t| t.name == target) {
|
||||
if !target.installed {
|
||||
anyhow::bail!(
|
||||
"Target {target} is not installed (installed targets: {installed}). Please run `rustup target add {target}`.",
|
||||
target = target.name,
|
||||
installed = available_targets.iter().filter(|t| t.installed).map(|t| t.name.as_str()).collect::<Vec<&str>>().join(", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
if !available_targets.iter().any(|t| t.name == target) {
|
||||
anyhow::bail!("Target {target} does not exist. Please run `rustup target list` to see the available targets.", target = target);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rename_app(bin_path: &Path, product_name: Option<&str>) -> crate::Result<PathBuf> {
|
||||
if let Some(product_name) = product_name {
|
||||
#[cfg(target_os = "linux")]
|
||||
let product_name = product_name.to_kebab_case();
|
||||
|
||||
let product_path = bin_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(&product_name)
|
||||
.with_extension(bin_path.extension().unwrap_or_default());
|
||||
|
||||
rename(&bin_path, &product_path).with_context(|| {
|
||||
format!(
|
||||
"failed to rename `{}` to `{}`",
|
||||
bin_path.display(),
|
||||
product_path.display(),
|
||||
)
|
||||
})?;
|
||||
Ok(product_path)
|
||||
} else {
|
||||
Ok(bin_path.to_path_buf())
|
||||
}
|
||||
}
|
||||
|
||||
// taken from https://github.com/rust-lang/cargo/blob/78b10d4e611ab0721fc3aeaf0edd5dd8f4fdc372/src/cargo/core/shell.rs#L514
|
||||
#[cfg(unix)]
|
||||
mod terminal {
|
||||
use std::mem;
|
||||
|
||||
pub fn stderr_width() -> Option<usize> {
|
||||
unsafe {
|
||||
let mut winsize: libc::winsize = mem::zeroed();
|
||||
// The .into() here is needed for FreeBSD which defines TIOCGWINSZ
|
||||
// as c_uint but ioctl wants c_ulong.
|
||||
#[allow(clippy::useless_conversion)]
|
||||
if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ.into(), &mut winsize) < 0 {
|
||||
return None;
|
||||
}
|
||||
if winsize.ws_col > 0 {
|
||||
Some(winsize.ws_col as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// taken from https://github.com/rust-lang/cargo/blob/78b10d4e611ab0721fc3aeaf0edd5dd8f4fdc372/src/cargo/core/shell.rs#L543
|
||||
#[cfg(windows)]
|
||||
mod terminal {
|
||||
use std::{cmp, mem, ptr};
|
||||
use winapi::um::fileapi::*;
|
||||
use winapi::um::handleapi::*;
|
||||
use winapi::um::processenv::*;
|
||||
use winapi::um::winbase::*;
|
||||
use winapi::um::wincon::*;
|
||||
use winapi::um::winnt::*;
|
||||
|
||||
pub fn stderr_width() -> Option<usize> {
|
||||
unsafe {
|
||||
let stdout = GetStdHandle(STD_ERROR_HANDLE);
|
||||
let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
|
||||
if GetConsoleScreenBufferInfo(stdout, &mut csbi) != 0 {
|
||||
return Some((csbi.srWindow.Right - csbi.srWindow.Left) as usize);
|
||||
}
|
||||
|
||||
// On mintty/msys/cygwin based terminals, the above fails with
|
||||
// INVALID_HANDLE_VALUE. Use an alternate method which works
|
||||
// in that case as well.
|
||||
let h = CreateFileA(
|
||||
"CONOUT$\0".as_ptr() as *const CHAR,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
ptr::null_mut(),
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
if h == INVALID_HANDLE_VALUE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
|
||||
let rc = GetConsoleScreenBufferInfo(h, &mut csbi);
|
||||
CloseHandle(h);
|
||||
if rc != 0 {
|
||||
let width = (csbi.srWindow.Right - csbi.srWindow.Left) as usize;
|
||||
// Unfortunately cygwin/mintty does not set the size of the
|
||||
// backing console to match the actual window size. This
|
||||
// always reports a size of 80 or 120 (not sure what
|
||||
// determines that). Use a conservative max of 60 which should
|
||||
// work in most circumstances. ConEmu does some magic to
|
||||
// resize the console correctly, but there's no reasonable way
|
||||
// to detect which kind of terminal we are running in, or if
|
||||
// GetConsoleScreenBufferInfo returns accurate information.
|
||||
return Some(cmp::min(60, width));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user