Files
oak-gpui/crates/remote/src/transport.rs
T
Jakub Konka fdf5bf7e6a remote: Support building x86_64-linux-musl proxy in nix-darwin (#41291)
This change adds two things to our remote server build
process:
1. It now checks if all required tooling is installed before using it or installing it on demand. This includes checks for `rustup` and `cargo-zigbuild` in your `PATH`.
2. Next, if `ZED_BUILD_REMOTE_SERVER` contains `musl` and `ZED_ZSTD_MUSL_LIB`
is set, we will pass its value (the path) to `cargo-zigbuild` as `-C
link-arg=-L{path}`.

Release Notes:

- N/A
2025-10-28 06:58:09 +01:00

315 lines
11 KiB
Rust

use crate::{
json_log::LogRecord,
protocol::{MESSAGE_LEN_SIZE, message_len_from_buffer, read_message_with_len, write_message},
};
use anyhow::{Context as _, Result};
use futures::{
AsyncReadExt as _, FutureExt as _, StreamExt as _,
channel::mpsc::{Sender, UnboundedReceiver, UnboundedSender},
};
use gpui::{AppContext as _, AsyncApp, Task};
use rpc::proto::Envelope;
use smol::process::Child;
pub mod ssh;
pub mod wsl;
fn handle_rpc_messages_over_child_process_stdio(
mut ssh_proxy_process: Child,
incoming_tx: UnboundedSender<Envelope>,
mut outgoing_rx: UnboundedReceiver<Envelope>,
mut connection_activity_tx: Sender<()>,
cx: &AsyncApp,
) -> Task<Result<i32>> {
let mut child_stderr = ssh_proxy_process.stderr.take().unwrap();
let mut child_stdout = ssh_proxy_process.stdout.take().unwrap();
let mut child_stdin = ssh_proxy_process.stdin.take().unwrap();
let mut stdin_buffer = Vec::new();
let mut stdout_buffer = Vec::new();
let mut stderr_buffer = Vec::new();
let mut stderr_offset = 0;
let stdin_task = cx.background_spawn(async move {
while let Some(outgoing) = outgoing_rx.next().await {
write_message(&mut child_stdin, &mut stdin_buffer, outgoing).await?;
}
anyhow::Ok(())
});
let stdout_task = cx.background_spawn({
let mut connection_activity_tx = connection_activity_tx.clone();
async move {
loop {
stdout_buffer.resize(MESSAGE_LEN_SIZE, 0);
let len = child_stdout.read(&mut stdout_buffer).await?;
if len == 0 {
return anyhow::Ok(());
}
if len < MESSAGE_LEN_SIZE {
child_stdout.read_exact(&mut stdout_buffer[len..]).await?;
}
let message_len = message_len_from_buffer(&stdout_buffer);
let envelope =
read_message_with_len(&mut child_stdout, &mut stdout_buffer, message_len)
.await?;
connection_activity_tx.try_send(()).ok();
incoming_tx.unbounded_send(envelope).ok();
}
}
});
let stderr_task: Task<anyhow::Result<()>> = cx.background_spawn(async move {
loop {
stderr_buffer.resize(stderr_offset + 1024, 0);
let len = child_stderr
.read(&mut stderr_buffer[stderr_offset..])
.await?;
if len == 0 {
return anyhow::Ok(());
}
stderr_offset += len;
let mut start_ix = 0;
while let Some(ix) = stderr_buffer[start_ix..stderr_offset]
.iter()
.position(|b| b == &b'\n')
{
let line_ix = start_ix + ix;
let content = &stderr_buffer[start_ix..line_ix];
start_ix = line_ix + 1;
if let Ok(record) = serde_json::from_slice::<LogRecord>(content) {
record.log(log::logger())
} else {
eprintln!("(remote) {}", String::from_utf8_lossy(content));
}
}
stderr_buffer.drain(0..start_ix);
stderr_offset -= start_ix;
connection_activity_tx.try_send(()).ok();
}
});
cx.background_spawn(async move {
let result = futures::select! {
result = stdin_task.fuse() => {
result.context("stdin")
}
result = stdout_task.fuse() => {
result.context("stdout")
}
result = stderr_task.fuse() => {
result.context("stderr")
}
};
let status = ssh_proxy_process.status().await?.code().unwrap_or(1);
match result {
Ok(_) => Ok(status),
Err(error) => Err(error),
}
})
}
#[cfg(debug_assertions)]
async fn build_remote_server_from_source(
platform: &crate::RemotePlatform,
delegate: &dyn crate::RemoteClientDelegate,
cx: &mut AsyncApp,
) -> Result<Option<std::path::PathBuf>> {
use smol::process::{Command, Stdio};
use std::env::VarError;
use std::path::Path;
// By default, we make building remote server from source opt-out and we do not force artifact compression
// for quicker builds.
let build_remote_server =
std::env::var("ZED_BUILD_REMOTE_SERVER").unwrap_or("nocompress".into());
if build_remote_server == "false"
|| build_remote_server == "no"
|| build_remote_server == "off"
|| build_remote_server == "0"
{
return Ok(None);
}
async fn run_cmd(command: &mut Command) -> Result<()> {
let output = command
.kill_on_drop(true)
.stderr(Stdio::inherit())
.output()
.await?;
anyhow::ensure!(
output.status.success(),
"Failed to run command: {command:?}"
);
Ok(())
}
let use_musl = !build_remote_server.contains("nomusl");
let triple = format!(
"{}-{}",
platform.arch,
match platform.os {
"linux" =>
if use_musl {
"unknown-linux-musl"
} else {
"unknown-linux-gnu"
},
"macos" => "apple-darwin",
_ => anyhow::bail!("can't cross compile for: {:?}", platform),
}
);
let mut rust_flags = match std::env::var("RUSTFLAGS") {
Ok(val) => val,
Err(VarError::NotPresent) => String::new(),
Err(e) => {
log::error!("Failed to get env var `RUSTFLAGS` value: {e}");
String::new()
}
};
if platform.os == "linux" && use_musl {
rust_flags.push_str(" -C target-feature=+crt-static");
if let Ok(path) = std::env::var("ZED_ZSTD_MUSL_LIB") {
rust_flags.push_str(&format!(" -C link-arg=-L{path}"));
}
}
if build_remote_server.contains("mold") {
rust_flags.push_str(" -C link-arg=-fuse-ld=mold");
}
if platform.arch == std::env::consts::ARCH && platform.os == std::env::consts::OS {
delegate.set_status(Some("Building remote server binary from source"), cx);
log::info!("building remote server binary from source");
run_cmd(
Command::new("cargo")
.current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/../.."))
.args([
"build",
"--package",
"remote_server",
"--features",
"debug-embed",
"--target-dir",
"target/remote_server",
"--target",
&triple,
])
.env("RUSTFLAGS", &rust_flags),
)
.await?;
} else {
if which("zig", cx).await?.is_none() {
anyhow::bail!(if cfg!(not(windows)) {
"zig not found on $PATH, install zig (see https://ziglang.org/learn/getting-started or use zigup)"
} else {
"zig not found on $PATH, install zig (use `winget install -e --id zig.zig` or see https://ziglang.org/learn/getting-started or use zigup)"
});
}
let rustup = which("rustup", cx)
.await?
.context("rustup not found on $PATH, install rustup (see https://rustup.rs/)")?;
delegate.set_status(Some("Adding rustup target for cross-compilation"), cx);
log::info!("adding rustup target");
run_cmd(Command::new(rustup).args(["target", "add"]).arg(&triple)).await?;
if which("cargo-zigbuild", cx).await?.is_none() {
delegate.set_status(Some("Installing cargo-zigbuild for cross-compilation"), cx);
log::info!("installing cargo-zigbuild");
run_cmd(Command::new("cargo").args(["install", "--locked", "cargo-zigbuild"])).await?;
}
delegate.set_status(
Some(&format!(
"Building remote binary from source for {triple} with Zig"
)),
cx,
);
log::info!("building remote binary from source for {triple} with Zig");
run_cmd(
Command::new("cargo")
.args([
"zigbuild",
"--package",
"remote_server",
"--features",
"debug-embed",
"--target-dir",
"target/remote_server",
"--target",
&triple,
])
.env("RUSTFLAGS", &rust_flags),
)
.await?;
};
let bin_path = Path::new("target")
.join("remote_server")
.join(&triple)
.join("debug")
.join("remote_server");
let path = if !build_remote_server.contains("nocompress") {
delegate.set_status(Some("Compressing binary"), cx);
#[cfg(not(target_os = "windows"))]
{
run_cmd(Command::new("gzip").args(["-f", &bin_path.to_string_lossy()])).await?;
}
#[cfg(target_os = "windows")]
{
// On Windows, we use 7z to compress the binary
let seven_zip = which("7z.exe",cx)
.await?
.context("7z.exe not found on $PATH, install it (e.g. with `winget install -e --id 7zip.7zip`) or, if you don't want this behaviour, set $env:ZED_BUILD_REMOTE_SERVER=\"nocompress\"")?;
let gz_path = format!("target/remote_server/{}/debug/remote_server.gz", triple);
if smol::fs::metadata(&gz_path).await.is_ok() {
smol::fs::remove_file(&gz_path).await?;
}
run_cmd(Command::new(seven_zip).args([
"a",
"-tgzip",
&gz_path,
&bin_path.to_string_lossy(),
]))
.await?;
}
let mut archive_path = bin_path;
archive_path.set_extension("gz");
std::env::current_dir()?.join(archive_path)
} else {
bin_path
};
Ok(Some(path))
}
#[cfg(debug_assertions)]
async fn which(
binary_name: impl AsRef<str>,
cx: &mut AsyncApp,
) -> Result<Option<std::path::PathBuf>> {
let binary_name = binary_name.as_ref().to_string();
let binary_name_cloned = binary_name.clone();
let res = cx
.background_spawn(async move { which::which(binary_name_cloned) })
.await;
match res {
Ok(path) => Ok(Some(path)),
Err(which::Error::CannotFindBinaryPath) => Ok(None),
Err(err) => Err(anyhow::anyhow!(
"Failed to run 'which' to find the binary '{binary_name}': {err}"
)),
}
}