Skip to content

Commit

Permalink
Add server implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
simonrw committed Dec 6, 2023
1 parent b5391a1 commit 50488e5
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ resolver = "2"
members = [
"transport",
"debugger",
"server",
]

[profile.release]
Expand Down
14 changes: 14 additions & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "server"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow.workspace = true
tracing.workspace = true
transport = { path = "../transport" }

[dev-dependencies]
tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] }
114 changes: 114 additions & 0 deletions server/src/debugpy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::{
io::{BufRead, BufReader},
process::{Child, Stdio},
sync::mpsc,
thread,
};

use anyhow::Context;

use crate::Server;

pub struct DebugpyServer {
child: Child,
}

impl Server for DebugpyServer {
fn on_port(port: impl Into<u16>) -> anyhow::Result<Self> {
let port = port.into();

tracing::debug!(port = ?port, "starting server process");
let cwd = std::env::current_dir().unwrap();
let mut child = std::process::Command::new("python")
.args([
"-m",
"debugpy.adapter",
"--host",
"127.0.0.1",
"--port",
&format!("{port}"),
"--log-stderr",
])
.stderr(Stdio::piped())
.current_dir(cwd.join("..").canonicalize().unwrap())
.spawn()
.context("spawning background process")?;

// wait until server is ready
tracing::debug!("waiting until server is ready");
let stderr = child.stderr.take().unwrap();
let reader = BufReader::new(stderr);

let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let mut should_signal = true;
for line in reader.lines() {
let line = line.unwrap();
if should_signal && line.contains("Listening for incoming Client connections") {
should_signal = false;
let _ = tx.send(());
}
}
});
let _ = rx.recv();

tracing::debug!("server ready");
Ok(Self { child })
}
}

impl Drop for DebugpyServer {
fn drop(&mut self) {
tracing::debug!("terminating server");
match self.child.kill() {
Ok(_) => {
tracing::debug!("server terminated");
let _ = self.child.wait();
}
Err(e) => tracing::warn!(error = %e, "could not terminate server process"),
}
}
}

#[cfg(test)]
mod tests {
use std::{io::IsTerminal, net::TcpStream};

use anyhow::Context;
use tracing_subscriber::EnvFilter;
use transport::bindings::get_random_tcp_port;

use crate::{for_implementation_on_port, Implementation};

fn init_test_logger() {
let in_ci = std::env::var("CI")
.map(|val| val == "true")
.unwrap_or(false);

if std::io::stderr().is_terminal() || in_ci {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();
} else {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.json()
.init();
}
}

#[test]
fn test_create() -> anyhow::Result<()> {
init_test_logger();

let port = get_random_tcp_port().context("reserving custom port")?;
let _server =
for_implementation_on_port(Implementation::Debugpy, port).context("creating server")?;

// server should be running
tracing::info!("making connection");
let _conn =
TcpStream::connect(&format!("127.0.0.1:{port}")).context("connecting to server")?;
Ok(())
}
}
37 changes: 37 additions & 0 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use anyhow::Context;
use transport::DEFAULT_DAP_PORT;

pub mod debugpy;

pub enum Implementation {
Debugpy,
}

pub trait Server {
fn on_port(port: impl Into<u16>) -> anyhow::Result<Self>
where
Self: Sized;

fn new() -> anyhow::Result<Self>
where
Self: Sized,
{
Self::on_port(DEFAULT_DAP_PORT)
}
}

pub fn for_implementation(implementation: Implementation) -> anyhow::Result<Box<dyn Server>> {
for_implementation_on_port(implementation, DEFAULT_DAP_PORT)
}

pub fn for_implementation_on_port(
implementation: Implementation,
port: impl Into<u16>,
) -> anyhow::Result<Box<dyn Server>> {
match implementation {
Implementation::Debugpy => {
let server = crate::debugpy::DebugpyServer::on_port(port).context("creating server")?;
Ok(Box::new(server))
}
}
}
3 changes: 3 additions & 0 deletions transport/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ pub mod types;

pub use client::Client;
pub use client::Received;

/// The default port the DAP protocol listens on
pub const DEFAULT_DAP_PORT: u16 = 5678;

0 comments on commit 50488e5

Please sign in to comment.