windows: Add support for SSH (#29145)
Closes #19892 This PR builds on top of #20587 and improves upon it. Release Notes: - N/A --------- Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
parent
8bd739d869
commit
0ca0914cca
26 changed files with 1435 additions and 354 deletions
|
@ -49,7 +49,10 @@ use std::{
|
|||
time::{Duration, Instant},
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
use util::ResultExt;
|
||||
use util::{
|
||||
ResultExt,
|
||||
paths::{PathStyle, RemotePathBuf},
|
||||
};
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
|
||||
|
@ -59,7 +62,10 @@ pub struct SshProjectId(pub u64);
|
|||
#[derive(Clone)]
|
||||
pub struct SshSocket {
|
||||
connection_options: SshConnectionOptions,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
socket_path: PathBuf,
|
||||
#[cfg(target_os = "windows")]
|
||||
envs: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)]
|
||||
|
@ -85,6 +91,11 @@ pub struct SshConnectionOptions {
|
|||
pub upload_binary_over_ssh: bool,
|
||||
}
|
||||
|
||||
pub struct SshArgs {
|
||||
pub arguments: Vec<String>,
|
||||
pub envs: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! shell_script {
|
||||
($fmt:expr, $($name:ident = $arg:expr),+ $(,)?) => {{
|
||||
|
@ -338,6 +349,28 @@ pub trait SshClientDelegate: Send + Sync {
|
|||
}
|
||||
|
||||
impl SshSocket {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn new(options: SshConnectionOptions, socket_path: PathBuf) -> Result<Self> {
|
||||
Ok(Self {
|
||||
connection_options: options,
|
||||
socket_path,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn new(options: SshConnectionOptions, temp_dir: &TempDir, secret: String) -> Result<Self> {
|
||||
let askpass_script = temp_dir.path().join("askpass.bat");
|
||||
std::fs::write(&askpass_script, "@ECHO OFF\necho %ZED_SSH_ASKPASS%")?;
|
||||
let mut envs = HashMap::default();
|
||||
envs.insert("SSH_ASKPASS_REQUIRE".into(), "force".into());
|
||||
envs.insert("SSH_ASKPASS".into(), askpass_script.display().to_string());
|
||||
envs.insert("ZED_SSH_ASKPASS".into(), secret);
|
||||
Ok(Self {
|
||||
connection_options: options,
|
||||
envs,
|
||||
})
|
||||
}
|
||||
|
||||
// :WARNING: ssh unquotes arguments when executing on the remote :WARNING:
|
||||
// e.g. $ ssh host sh -c 'ls -l' is equivalent to $ ssh host sh -c ls -l
|
||||
// and passes -l as an argument to sh, not to ls.
|
||||
|
@ -375,6 +408,7 @@ impl SshSocket {
|
|||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn ssh_options<'a>(&self, command: &'a mut process::Command) -> &'a mut process::Command {
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
|
@ -384,14 +418,68 @@ impl SshSocket {
|
|||
.arg(format!("ControlPath={}", self.socket_path.display()))
|
||||
}
|
||||
|
||||
fn ssh_args(&self) -> Vec<String> {
|
||||
vec![
|
||||
"-o".to_string(),
|
||||
"ControlMaster=no".to_string(),
|
||||
"-o".to_string(),
|
||||
format!("ControlPath={}", self.socket_path.display()),
|
||||
self.connection_options.ssh_url(),
|
||||
]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn ssh_options<'a>(&self, command: &'a mut process::Command) -> &'a mut process::Command {
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.envs(self.envs.clone())
|
||||
}
|
||||
|
||||
// On Windows, we need to use `SSH_ASKPASS` to provide the password to ssh.
|
||||
// On Linux, we use the `ControlPath` option to create a socket file that ssh can use to
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn ssh_args(&self) -> SshArgs {
|
||||
SshArgs {
|
||||
arguments: vec![
|
||||
"-o".to_string(),
|
||||
"ControlMaster=no".to_string(),
|
||||
"-o".to_string(),
|
||||
format!("ControlPath={}", self.socket_path.display()),
|
||||
self.connection_options.ssh_url(),
|
||||
],
|
||||
envs: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn ssh_args(&self) -> SshArgs {
|
||||
SshArgs {
|
||||
arguments: vec![self.connection_options.ssh_url()],
|
||||
envs: Some(self.envs.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn platform(&self) -> Result<SshPlatform> {
|
||||
let uname = self.run_command("sh", &["-c", "uname -sm"]).await?;
|
||||
let Some((os, arch)) = uname.split_once(" ") else {
|
||||
anyhow::bail!("unknown uname: {uname:?}")
|
||||
};
|
||||
|
||||
let os = match os.trim() {
|
||||
"Darwin" => "macos",
|
||||
"Linux" => "linux",
|
||||
_ => anyhow::bail!(
|
||||
"Prebuilt remote servers are not yet available for {os:?}. See https://zed.dev/docs/remote-development"
|
||||
),
|
||||
};
|
||||
// exclude armv5,6,7 as they are 32-bit.
|
||||
let arch = if arch.starts_with("armv8")
|
||||
|| arch.starts_with("armv9")
|
||||
|| arch.starts_with("arm64")
|
||||
|| arch.starts_with("aarch64")
|
||||
{
|
||||
"aarch64"
|
||||
} else if arch.starts_with("x86") {
|
||||
"x86_64"
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Prebuilt remote servers are not yet available for {arch:?}. See https://zed.dev/docs/remote-development"
|
||||
)
|
||||
};
|
||||
|
||||
Ok(SshPlatform { os, arch })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -560,6 +648,7 @@ pub struct SshRemoteClient {
|
|||
client: Arc<ChannelClient>,
|
||||
unique_identifier: String,
|
||||
connection_options: SshConnectionOptions,
|
||||
path_style: PathStyle,
|
||||
state: Arc<Mutex<Option<State>>>,
|
||||
}
|
||||
|
||||
|
@ -620,22 +709,25 @@ impl SshRemoteClient {
|
|||
|
||||
let client =
|
||||
cx.update(|cx| ChannelClient::new(incoming_rx, outgoing_tx, cx, "client"))?;
|
||||
let this = cx.new(|_| Self {
|
||||
client: client.clone(),
|
||||
unique_identifier: unique_identifier.clone(),
|
||||
connection_options: connection_options.clone(),
|
||||
state: Arc::new(Mutex::new(Some(State::Connecting))),
|
||||
})?;
|
||||
|
||||
let ssh_connection = cx
|
||||
.update(|cx| {
|
||||
cx.update_default_global(|pool: &mut ConnectionPool, cx| {
|
||||
pool.connect(connection_options, &delegate, cx)
|
||||
pool.connect(connection_options.clone(), &delegate, cx)
|
||||
})
|
||||
})?
|
||||
.await
|
||||
.map_err(|e| e.cloned())?;
|
||||
|
||||
let path_style = ssh_connection.path_style();
|
||||
let this = cx.new(|_| Self {
|
||||
client: client.clone(),
|
||||
unique_identifier: unique_identifier.clone(),
|
||||
connection_options,
|
||||
path_style,
|
||||
state: Arc::new(Mutex::new(Some(State::Connecting))),
|
||||
})?;
|
||||
|
||||
let io_task = ssh_connection.start_proxy(
|
||||
unique_identifier,
|
||||
false,
|
||||
|
@ -1065,18 +1157,18 @@ impl SshRemoteClient {
|
|||
self.client.subscribe_to_entity(remote_id, entity);
|
||||
}
|
||||
|
||||
pub fn ssh_args(&self) -> Option<Vec<String>> {
|
||||
pub fn ssh_info(&self) -> Option<(SshArgs, PathStyle)> {
|
||||
self.state
|
||||
.lock()
|
||||
.as_ref()
|
||||
.and_then(|state| state.ssh_connection())
|
||||
.map(|ssh_connection| ssh_connection.ssh_args())
|
||||
.map(|ssh_connection| (ssh_connection.ssh_args(), ssh_connection.path_style()))
|
||||
}
|
||||
|
||||
pub fn upload_directory(
|
||||
&self,
|
||||
src_path: PathBuf,
|
||||
dest_path: PathBuf,
|
||||
dest_path: RemotePathBuf,
|
||||
cx: &App,
|
||||
) -> Task<Result<()>> {
|
||||
let state = self.state.lock();
|
||||
|
@ -1110,6 +1202,10 @@ impl SshRemoteClient {
|
|||
self.connection_state() == ConnectionState::Disconnected
|
||||
}
|
||||
|
||||
pub fn path_style(&self) -> PathStyle {
|
||||
self.path_style
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn simulate_disconnect(&self, client_cx: &mut App) -> Task<()> {
|
||||
let opts = self.connection_options();
|
||||
|
@ -1288,12 +1384,19 @@ trait RemoteConnection: Send + Sync {
|
|||
delegate: Arc<dyn SshClientDelegate>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Task<Result<i32>>;
|
||||
fn upload_directory(&self, src_path: PathBuf, dest_path: PathBuf, cx: &App)
|
||||
-> Task<Result<()>>;
|
||||
fn upload_directory(
|
||||
&self,
|
||||
src_path: PathBuf,
|
||||
dest_path: RemotePathBuf,
|
||||
cx: &App,
|
||||
) -> Task<Result<()>>;
|
||||
async fn kill(&self) -> Result<()>;
|
||||
fn has_been_killed(&self) -> bool;
|
||||
fn ssh_args(&self) -> Vec<String>;
|
||||
/// On Windows, we need to use `SSH_ASKPASS` to provide the password to ssh.
|
||||
/// On Linux, we use the `ControlPath` option to create a socket file that ssh can use to
|
||||
fn ssh_args(&self) -> SshArgs;
|
||||
fn connection_options(&self) -> SshConnectionOptions;
|
||||
fn path_style(&self) -> PathStyle;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn simulate_disconnect(&self, _: &AsyncApp) {}
|
||||
|
@ -1302,7 +1405,9 @@ trait RemoteConnection: Send + Sync {
|
|||
struct SshRemoteConnection {
|
||||
socket: SshSocket,
|
||||
master_process: Mutex<Option<Child>>,
|
||||
remote_binary_path: Option<PathBuf>,
|
||||
remote_binary_path: Option<RemotePathBuf>,
|
||||
ssh_platform: SshPlatform,
|
||||
ssh_path_style: PathStyle,
|
||||
_temp_dir: TempDir,
|
||||
}
|
||||
|
||||
|
@ -1321,7 +1426,7 @@ impl RemoteConnection for SshRemoteConnection {
|
|||
self.master_process.lock().is_none()
|
||||
}
|
||||
|
||||
fn ssh_args(&self) -> Vec<String> {
|
||||
fn ssh_args(&self) -> SshArgs {
|
||||
self.socket.ssh_args()
|
||||
}
|
||||
|
||||
|
@ -1332,7 +1437,7 @@ impl RemoteConnection for SshRemoteConnection {
|
|||
fn upload_directory(
|
||||
&self,
|
||||
src_path: PathBuf,
|
||||
dest_path: PathBuf,
|
||||
dest_path: RemotePathBuf,
|
||||
cx: &App,
|
||||
) -> Task<Result<()>> {
|
||||
let mut command = util::command::new_smol_command("scp");
|
||||
|
@ -1352,7 +1457,7 @@ impl RemoteConnection for SshRemoteConnection {
|
|||
.arg(format!(
|
||||
"{}:{}",
|
||||
self.socket.connection_options.scp_url(),
|
||||
dest_path.display()
|
||||
dest_path.to_string()
|
||||
))
|
||||
.output();
|
||||
|
||||
|
@ -1363,7 +1468,7 @@ impl RemoteConnection for SshRemoteConnection {
|
|||
output.status.success(),
|
||||
"failed to upload directory {} -> {}: {}",
|
||||
src_path.display(),
|
||||
dest_path.display(),
|
||||
dest_path.to_string(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
|
@ -1389,7 +1494,7 @@ impl RemoteConnection for SshRemoteConnection {
|
|||
|
||||
let mut start_proxy_command = shell_script!(
|
||||
"exec {binary_path} proxy --identifier {identifier}",
|
||||
binary_path = &remote_binary_path.to_string_lossy(),
|
||||
binary_path = &remote_binary_path.to_string(),
|
||||
identifier = &unique_identifier,
|
||||
);
|
||||
|
||||
|
@ -1432,19 +1537,13 @@ impl RemoteConnection for SshRemoteConnection {
|
|||
&cx,
|
||||
)
|
||||
}
|
||||
|
||||
fn path_style(&self) -> PathStyle {
|
||||
self.ssh_path_style
|
||||
}
|
||||
}
|
||||
|
||||
impl SshRemoteConnection {
|
||||
#[cfg(not(unix))]
|
||||
async fn new(
|
||||
_connection_options: SshConnectionOptions,
|
||||
_delegate: Arc<dyn SshClientDelegate>,
|
||||
_cx: &mut AsyncApp,
|
||||
) -> Result<Self> {
|
||||
anyhow::bail!("ssh is not supported on this platform");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn new(
|
||||
connection_options: SshConnectionOptions,
|
||||
delegate: Arc<dyn SshClientDelegate>,
|
||||
|
@ -1470,27 +1569,38 @@ impl SshRemoteConnection {
|
|||
// Start the master SSH process, which does not do anything except for establish
|
||||
// the connection and keep it open, allowing other ssh commands to reuse it
|
||||
// via a control socket.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let socket_path = temp_dir.path().join("ssh.sock");
|
||||
|
||||
let mut master_process = process::Command::new("ssh")
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.env("SSH_ASKPASS_REQUIRE", "force")
|
||||
.env("SSH_ASKPASS", &askpass.script_path())
|
||||
.args(connection_options.additional_args())
|
||||
.args([
|
||||
let mut master_process = {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let args = [
|
||||
"-N",
|
||||
"-o",
|
||||
"ControlPersist=no",
|
||||
"-o",
|
||||
"ControlMaster=yes",
|
||||
"-o",
|
||||
])
|
||||
.arg(format!("ControlPath={}", socket_path.display()))
|
||||
.arg(&url)
|
||||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
];
|
||||
// On Windows, `ControlMaster` and `ControlPath` are not supported:
|
||||
// https://github.com/PowerShell/Win32-OpenSSH/issues/405
|
||||
// https://github.com/PowerShell/Win32-OpenSSH/wiki/Project-Scope
|
||||
#[cfg(target_os = "windows")]
|
||||
let args = ["-N"];
|
||||
let mut master_process = process::Command::new("ssh");
|
||||
master_process
|
||||
.kill_on_drop(true)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.env("SSH_ASKPASS_REQUIRE", "force")
|
||||
.env("SSH_ASKPASS", askpass.script_path())
|
||||
.args(connection_options.additional_args())
|
||||
.args(args);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
master_process.arg(format!("ControlPath={}", socket_path.display()));
|
||||
master_process.arg(&url).spawn()?
|
||||
};
|
||||
// Wait for this ssh process to close its stdout, indicating that authentication
|
||||
// has completed.
|
||||
let mut stdout = master_process.stdout.take().unwrap();
|
||||
|
@ -1529,11 +1639,16 @@ impl SshRemoteConnection {
|
|||
anyhow::bail!(error_message);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let socket = SshSocket::new(connection_options, socket_path)?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let socket = SshSocket::new(connection_options, &temp_dir, askpass.get_password())?;
|
||||
drop(askpass);
|
||||
|
||||
let socket = SshSocket {
|
||||
connection_options,
|
||||
socket_path,
|
||||
let ssh_platform = socket.platform().await?;
|
||||
let ssh_path_style = match ssh_platform.os {
|
||||
"windows" => PathStyle::Windows,
|
||||
_ => PathStyle::Posix,
|
||||
};
|
||||
|
||||
let mut this = Self {
|
||||
|
@ -1541,6 +1656,8 @@ impl SshRemoteConnection {
|
|||
master_process: Mutex::new(Some(master_process)),
|
||||
_temp_dir: temp_dir,
|
||||
remote_binary_path: None,
|
||||
ssh_path_style,
|
||||
ssh_platform,
|
||||
};
|
||||
|
||||
let (release_channel, version, commit) = cx.update(|cx| {
|
||||
|
@ -1558,37 +1675,6 @@ impl SshRemoteConnection {
|
|||
Ok(this)
|
||||
}
|
||||
|
||||
async fn platform(&self) -> Result<SshPlatform> {
|
||||
let uname = self.socket.run_command("sh", &["-c", "uname -sm"]).await?;
|
||||
let Some((os, arch)) = uname.split_once(" ") else {
|
||||
anyhow::bail!("unknown uname: {uname:?}")
|
||||
};
|
||||
|
||||
let os = match os.trim() {
|
||||
"Darwin" => "macos",
|
||||
"Linux" => "linux",
|
||||
_ => anyhow::bail!(
|
||||
"Prebuilt remote servers are not yet available for {os:?}. See https://zed.dev/docs/remote-development"
|
||||
),
|
||||
};
|
||||
// exclude armv5,6,7 as they are 32-bit.
|
||||
let arch = if arch.starts_with("armv8")
|
||||
|| arch.starts_with("armv9")
|
||||
|| arch.starts_with("arm64")
|
||||
|| arch.starts_with("aarch64")
|
||||
{
|
||||
"aarch64"
|
||||
} else if arch.starts_with("x86") {
|
||||
"x86_64"
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Prebuilt remote servers are not yet available for {arch:?}. See https://zed.dev/docs/remote-development"
|
||||
)
|
||||
};
|
||||
|
||||
Ok(SshPlatform { os, arch })
|
||||
}
|
||||
|
||||
fn multiplex(
|
||||
mut ssh_proxy_process: Child,
|
||||
incoming_tx: UnboundedSender<Envelope>,
|
||||
|
@ -1699,11 +1785,10 @@ impl SshRemoteConnection {
|
|||
version: SemanticVersion,
|
||||
commit: Option<AppCommitSha>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<PathBuf> {
|
||||
) -> Result<RemotePathBuf> {
|
||||
let version_str = match release_channel {
|
||||
ReleaseChannel::Nightly => {
|
||||
let commit = commit.map(|s| s.full()).unwrap_or_default();
|
||||
|
||||
format!("{}-{}", version, commit)
|
||||
}
|
||||
ReleaseChannel::Dev => "build".to_string(),
|
||||
|
@ -1714,19 +1799,23 @@ impl SshRemoteConnection {
|
|||
release_channel.dev_name(),
|
||||
version_str
|
||||
);
|
||||
let dst_path = paths::remote_server_dir_relative().join(binary_name);
|
||||
let dst_path = RemotePathBuf::new(
|
||||
paths::remote_server_dir_relative().join(binary_name),
|
||||
self.ssh_path_style,
|
||||
);
|
||||
|
||||
let build_remote_server = std::env::var("ZED_BUILD_REMOTE_SERVER").ok();
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(build_remote_server) = build_remote_server {
|
||||
let src_path = self
|
||||
.build_local(build_remote_server, self.platform().await?, delegate, cx)
|
||||
.await?;
|
||||
let tmp_path = paths::remote_server_dir_relative().join(format!(
|
||||
"download-{}-{}",
|
||||
std::process::id(),
|
||||
src_path.file_name().unwrap().to_string_lossy()
|
||||
));
|
||||
let src_path = self.build_local(build_remote_server, delegate, cx).await?;
|
||||
let tmp_path = RemotePathBuf::new(
|
||||
paths::remote_server_dir_relative().join(format!(
|
||||
"download-{}-{}",
|
||||
std::process::id(),
|
||||
src_path.file_name().unwrap().to_string_lossy()
|
||||
)),
|
||||
self.ssh_path_style,
|
||||
);
|
||||
self.upload_local_server_binary(&src_path, &tmp_path, delegate, cx)
|
||||
.await?;
|
||||
self.extract_server_binary(&dst_path, &tmp_path, delegate, cx)
|
||||
|
@ -1736,7 +1825,7 @@ impl SshRemoteConnection {
|
|||
|
||||
if self
|
||||
.socket
|
||||
.run_command(&dst_path.to_string_lossy(), &["version"])
|
||||
.run_command(&dst_path.to_string(), &["version"])
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
|
@ -1754,16 +1843,17 @@ impl SshRemoteConnection {
|
|||
_ => Ok(Some(AppVersion::global(cx))),
|
||||
})??;
|
||||
|
||||
let platform = self.platform().await?;
|
||||
|
||||
let tmp_path_gz = PathBuf::from(format!(
|
||||
"{}-download-{}.gz",
|
||||
dst_path.to_string_lossy(),
|
||||
std::process::id()
|
||||
));
|
||||
let tmp_path_gz = RemotePathBuf::new(
|
||||
PathBuf::from(format!(
|
||||
"{}-download-{}.gz",
|
||||
dst_path.to_string(),
|
||||
std::process::id()
|
||||
)),
|
||||
self.ssh_path_style,
|
||||
);
|
||||
if !self.socket.connection_options.upload_binary_over_ssh {
|
||||
if let Some((url, body)) = delegate
|
||||
.get_download_params(platform, release_channel, wanted_version, cx)
|
||||
.get_download_params(self.ssh_platform, release_channel, wanted_version, cx)
|
||||
.await?
|
||||
{
|
||||
match self
|
||||
|
@ -1786,7 +1876,7 @@ impl SshRemoteConnection {
|
|||
}
|
||||
|
||||
let src_path = delegate
|
||||
.download_server_binary_locally(platform, release_channel, wanted_version, cx)
|
||||
.download_server_binary_locally(self.ssh_platform, release_channel, wanted_version, cx)
|
||||
.await?;
|
||||
self.upload_local_server_binary(&src_path, &tmp_path_gz, delegate, cx)
|
||||
.await?;
|
||||
|
@ -1799,7 +1889,7 @@ impl SshRemoteConnection {
|
|||
&self,
|
||||
url: &str,
|
||||
body: &str,
|
||||
tmp_path_gz: &Path,
|
||||
tmp_path_gz: &RemotePathBuf,
|
||||
delegate: &Arc<dyn SshClientDelegate>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
|
@ -1809,10 +1899,7 @@ impl SshRemoteConnection {
|
|||
"sh",
|
||||
&[
|
||||
"-c",
|
||||
&shell_script!(
|
||||
"mkdir -p {parent}",
|
||||
parent = parent.to_string_lossy().as_ref()
|
||||
),
|
||||
&shell_script!("mkdir -p {parent}", parent = parent.to_string().as_ref()),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
@ -1835,7 +1922,7 @@ impl SshRemoteConnection {
|
|||
&body,
|
||||
&url,
|
||||
"-o",
|
||||
&tmp_path_gz.to_string_lossy(),
|
||||
&tmp_path_gz.to_string(),
|
||||
],
|
||||
)
|
||||
.await
|
||||
|
@ -1857,7 +1944,7 @@ impl SshRemoteConnection {
|
|||
&body,
|
||||
&url,
|
||||
"-O",
|
||||
&tmp_path_gz.to_string_lossy(),
|
||||
&tmp_path_gz.to_string(),
|
||||
],
|
||||
)
|
||||
.await
|
||||
|
@ -1880,7 +1967,7 @@ impl SshRemoteConnection {
|
|||
async fn upload_local_server_binary(
|
||||
&self,
|
||||
src_path: &Path,
|
||||
tmp_path_gz: &Path,
|
||||
tmp_path_gz: &RemotePathBuf,
|
||||
delegate: &Arc<dyn SshClientDelegate>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
|
@ -1890,10 +1977,7 @@ impl SshRemoteConnection {
|
|||
"sh",
|
||||
&[
|
||||
"-c",
|
||||
&shell_script!(
|
||||
"mkdir -p {parent}",
|
||||
parent = parent.to_string_lossy().as_ref()
|
||||
),
|
||||
&shell_script!("mkdir -p {parent}", parent = parent.to_string().as_ref()),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
@ -1918,33 +2002,33 @@ impl SshRemoteConnection {
|
|||
|
||||
async fn extract_server_binary(
|
||||
&self,
|
||||
dst_path: &Path,
|
||||
tmp_path: &Path,
|
||||
dst_path: &RemotePathBuf,
|
||||
tmp_path: &RemotePathBuf,
|
||||
delegate: &Arc<dyn SshClientDelegate>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
delegate.set_status(Some("Extracting remote development server"), cx);
|
||||
let server_mode = 0o755;
|
||||
|
||||
let orig_tmp_path = tmp_path.to_string_lossy();
|
||||
let orig_tmp_path = tmp_path.to_string();
|
||||
let script = if let Some(tmp_path) = orig_tmp_path.strip_suffix(".gz") {
|
||||
shell_script!(
|
||||
"gunzip -f {orig_tmp_path} && chmod {server_mode} {tmp_path} && mv {tmp_path} {dst_path}",
|
||||
server_mode = &format!("{:o}", server_mode),
|
||||
dst_path = &dst_path.to_string_lossy()
|
||||
dst_path = &dst_path.to_string(),
|
||||
)
|
||||
} else {
|
||||
shell_script!(
|
||||
"chmod {server_mode} {orig_tmp_path} && mv {orig_tmp_path} {dst_path}",
|
||||
server_mode = &format!("{:o}", server_mode),
|
||||
dst_path = &dst_path.to_string_lossy()
|
||||
dst_path = &dst_path.to_string()
|
||||
)
|
||||
};
|
||||
self.socket.run_command("sh", &["-c", &script]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upload_file(&self, src_path: &Path, dest_path: &Path) -> Result<()> {
|
||||
async fn upload_file(&self, src_path: &Path, dest_path: &RemotePathBuf) -> Result<()> {
|
||||
log::debug!("uploading file {:?} to {:?}", src_path, dest_path);
|
||||
let mut command = util::command::new_smol_command("scp");
|
||||
let output = self
|
||||
|
@ -1961,7 +2045,7 @@ impl SshRemoteConnection {
|
|||
.arg(format!(
|
||||
"{}:{}",
|
||||
self.socket.connection_options.scp_url(),
|
||||
dest_path.display()
|
||||
dest_path.to_string()
|
||||
))
|
||||
.output()
|
||||
.await?;
|
||||
|
@ -1970,7 +2054,7 @@ impl SshRemoteConnection {
|
|||
output.status.success(),
|
||||
"failed to upload file {} -> {}: {}",
|
||||
src_path.display(),
|
||||
dest_path.display(),
|
||||
dest_path.to_string(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
Ok(())
|
||||
|
@ -1980,7 +2064,6 @@ impl SshRemoteConnection {
|
|||
async fn build_local(
|
||||
&self,
|
||||
build_remote_server: String,
|
||||
platform: SshPlatform,
|
||||
delegate: &Arc<dyn SshClientDelegate>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<PathBuf> {
|
||||
|
@ -1999,7 +2082,9 @@ impl SshRemoteConnection {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
if platform.arch == std::env::consts::ARCH && platform.os == std::env::consts::OS {
|
||||
if self.ssh_platform.arch == std::env::consts::ARCH
|
||||
&& self.ssh_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").args([
|
||||
|
@ -2025,12 +2110,15 @@ impl SshRemoteConnection {
|
|||
let path = std::env::current_dir()?.join("target/remote_server/debug/remote_server.gz");
|
||||
return Ok(path);
|
||||
}
|
||||
let Some(triple) = platform.triple() else {
|
||||
anyhow::bail!("can't cross compile for: {:?}", platform);
|
||||
let Some(triple) = self.ssh_platform.triple() else {
|
||||
anyhow::bail!("can't cross compile for: {:?}", self.ssh_platform);
|
||||
};
|
||||
smol::fs::create_dir_all("target/remote_server").await?;
|
||||
|
||||
if build_remote_server.contains("cross") {
|
||||
#[cfg(target_os = "windows")]
|
||||
use util::paths::SanitizedPath;
|
||||
|
||||
delegate.set_status(Some("Installing cross.rs for cross-compilation"), cx);
|
||||
log::info!("installing cross");
|
||||
run_cmd(Command::new("cargo").args([
|
||||
|
@ -2049,6 +2137,13 @@ impl SshRemoteConnection {
|
|||
cx,
|
||||
);
|
||||
log::info!("building remote server binary from source for {}", &triple);
|
||||
|
||||
// On Windows, the binding needs to be set to the canonical path
|
||||
#[cfg(target_os = "windows")]
|
||||
let src =
|
||||
SanitizedPath::from(smol::fs::canonicalize("./target").await?).to_glob_string();
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let src = "./target";
|
||||
run_cmd(
|
||||
Command::new("cross")
|
||||
.args([
|
||||
|
@ -2064,7 +2159,7 @@ impl SshRemoteConnection {
|
|||
])
|
||||
.env(
|
||||
"CROSS_CONTAINER_OPTS",
|
||||
"--mount type=bind,src=./target,dst=/app/target",
|
||||
format!("--mount type=bind,src={src},dst=/app/target"),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
@ -2074,9 +2169,18 @@ impl SshRemoteConnection {
|
|||
.await;
|
||||
|
||||
if which.is_err() {
|
||||
anyhow::bail!(
|
||||
"zig not found on $PATH, install zig (see https://ziglang.org/learn/getting-started or use zigup) or pass ZED_BUILD_REMOTE_SERVER=cross to use cross"
|
||||
)
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
anyhow::bail!(
|
||||
"zig not found on $PATH, install zig (see https://ziglang.org/learn/getting-started or use zigup) or pass ZED_BUILD_REMOTE_SERVER=cross to use cross"
|
||||
)
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
anyhow::bail!(
|
||||
"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) or pass ZED_BUILD_REMOTE_SERVER=cross to use cross"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
delegate.set_status(Some("Adding rustup target for cross-compilation"), cx);
|
||||
|
@ -2112,12 +2216,31 @@ impl SshRemoteConnection {
|
|||
if !build_remote_server.contains("nocompress") {
|
||||
delegate.set_status(Some("Compressing binary"), cx);
|
||||
|
||||
run_cmd(Command::new("gzip").args([
|
||||
"-9",
|
||||
"-f",
|
||||
&format!("target/remote_server/{}/debug/remote_server", triple),
|
||||
]))
|
||||
.await?;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
run_cmd(Command::new("gzip").args([
|
||||
"-9",
|
||||
"-f",
|
||||
&format!("target/remote_server/{}/debug/remote_server", triple),
|
||||
]))
|
||||
.await?;
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// On Windows, we use 7z to compress the binary
|
||||
let seven_zip = which::which("7z.exe").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,
|
||||
&format!("target/remote_server/{}/debug/remote_server", triple),
|
||||
]))
|
||||
.await?;
|
||||
}
|
||||
|
||||
path = std::env::current_dir()?.join(format!(
|
||||
"target/remote_server/{triple}/debug/remote_server.gz"
|
||||
|
@ -2450,9 +2573,11 @@ mod fake {
|
|||
use gpui::{App, AppContext as _, AsyncApp, SemanticVersion, Task, TestAppContext};
|
||||
use release_channel::ReleaseChannel;
|
||||
use rpc::proto::Envelope;
|
||||
use util::paths::{PathStyle, RemotePathBuf};
|
||||
|
||||
use super::{
|
||||
ChannelClient, RemoteConnection, SshClientDelegate, SshConnectionOptions, SshPlatform,
|
||||
ChannelClient, RemoteConnection, SshArgs, SshClientDelegate, SshConnectionOptions,
|
||||
SshPlatform,
|
||||
};
|
||||
|
||||
pub(super) struct FakeRemoteConnection {
|
||||
|
@ -2488,13 +2613,17 @@ mod fake {
|
|||
false
|
||||
}
|
||||
|
||||
fn ssh_args(&self) -> Vec<String> {
|
||||
Vec::new()
|
||||
fn ssh_args(&self) -> SshArgs {
|
||||
SshArgs {
|
||||
arguments: Vec::new(),
|
||||
envs: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_directory(
|
||||
&self,
|
||||
_src_path: PathBuf,
|
||||
_dest_path: PathBuf,
|
||||
_dest_path: RemotePathBuf,
|
||||
_cx: &App,
|
||||
) -> Task<Result<()>> {
|
||||
unreachable!()
|
||||
|
@ -2513,7 +2642,6 @@ mod fake {
|
|||
|
||||
fn start_proxy(
|
||||
&self,
|
||||
|
||||
_unique_identifier: String,
|
||||
_reconnect: bool,
|
||||
mut client_incoming_tx: mpsc::UnboundedSender<Envelope>,
|
||||
|
@ -2551,6 +2679,10 @@ mod fake {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn path_style(&self) -> PathStyle {
|
||||
PathStyle::current()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct Delegate;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue