Файли
openidea/crates/ide-host/src/bin/server.rs
T
l0ng-ai 1f9be73174 feat(remote): Reload the environment and apply devcontainer settings on the remote server (#183)
The remote server read the login shell's PATH once and cached it, had no project-level environment, and changed environment variables only took effect after reinstalling or a manual restart.

- Every openidea-server start re-reads the remote login shell's full environment (`$SHELL -ilc env -0`), with the cached PATH only as a fallback when the shell doesn't answer.
- A project's `.devcontainer/devcontainer.json` (or `.devcontainer.json`) applies on remote hosts: `remoteEnv` (with `${localEnv|containerEnv|env:X[:default]}`, workspace variables and `null` to unset) reaches every child process through one environment overlay (terminals, run configurations, language servers, debuggers, build tools, git), and `postStartCommand` (string, array or parallel object) runs once per server start in the project root as a cancellable background task; a failure raises a notification with the exit code and the last output lines without blocking the project.
- Reload Environment (`remote.reloadEnvironment`, File menu and Find Action) re-resolves the login shell and `remoteEnv` without restarting the server, starting from the process's original environment so prepended PATH entries don't pile up; it lists the changed variables, notes that running processes keep their environment, and offers to restart language servers.
- The JSONC reader moves to `ide_core::jsonc` and is shared with ide-run.

Claude-Session: https://claude.ai/code/session_01Dq6jQQn8ZhRfkkyj3PRutA
2026-10-29 09:28:18 +08:00

74 рядки
3.2 KiB
Rust

//! `openidea-server --stdio`: the IDE backend for remote development, serving
//! one desktop client over stdin / stdout (started by the client over SSH).
use ide_host::rpc::{self, Dispatcher};
use ide_host::{Host, HostConfig};
use ide_language::LanguageRegistry;
use std::path::PathBuf;
use std::sync::{Arc, OnceLock};
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
if args.iter().any(|a| a == "--version") {
println!("openidea-server {} (protocol {})", env!("CARGO_PKG_VERSION"), rpc::PROTOCOL_VERSION);
return;
}
if !args.iter().any(|a| a == "--stdio") {
eprintln!("usage: openidea-server --stdio | --version");
std::process::exit(2);
}
// stdout carries the protocol: logs go to stderr.
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_env_filter(tracing_subscriber::EnvFilter::try_from_env("OPENIDEA_LOG").unwrap_or_else(|_| "info".into()))
.init();
let home = dirs::home_dir().unwrap_or_default();
let base = std::env::var_os("OPENIDEA_SERVER_HOME").map(PathBuf::from).unwrap_or_else(|| home.join(".openidea"));
// An SSH exec channel gets a minimal environment: take the login shell's, read
// afresh on every start, plus the IDE-managed tools. Before any other thread starts.
ide_host::shell_env::inherit_login_shell_env("openidea-server");
let data_dir = base.join("data");
ide_tools::extend_process_path(&data_dir);
ide_dap::adapter::set_tools_root(ide_tools::tools_dir(&data_dir));
ide_sdk::java::add_jdk_root(&ide_tools::jdk::jdks_dir(&data_dir));
let runtime = tokio::runtime::Builder::new_multi_thread().enable_all().build().expect("tokio runtime");
let code = runtime.block_on(run(base));
std::process::exit(code);
}
async fn run(base: PathBuf) -> i32 {
let (config_dir, data_dir) = (base.join("config"), base.join("data"));
for dir in [&config_dir, &data_dir] {
if let Err(e) = std::fs::create_dir_all(dir) {
eprintln!("cannot create {}: {e}", dir.display());
return 1;
}
}
let languages = match LanguageRegistry::load(Some(&config_dir.join("languages"))) {
Ok(l) => Arc::new(l),
Err(e) => {
eprintln!("language definitions: {e}");
return 1;
}
};
let peer = Arc::new(OnceLock::new());
let sink_peer = peer.clone();
let host = Arc::new(Host::new(HostConfig {
config_dir,
data_dir,
languages,
syntax: Arc::default(),
sink_for: Box::new(move |window| rpc::event_sink(sink_peer.clone(), Some(window.to_owned()))),
owner: |w| w,
runtime: tokio::runtime::Handle::current(),
project_environment: true,
}));
tracing::info!("openidea-server {} ready", env!("CARGO_PKG_VERSION"));
rpc::serve(host.clone(), Dispatcher::standard(), peer, tokio::io::stdin(), tokio::io::stdout()).await;
// `process::exit` skips destructors: nothing started for the client may outlive it.
if tokio::time::timeout(std::time::Duration::from_secs(10), host.shutdown()).await.is_err() {
tracing::warn!("language servers did not shut down in time");
}
0
}