Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 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 @@ -127,6 +127,7 @@ object = { version = "0.36.7", default-features = false, features = [
"macho",
"pe",
] }
postcard = {version = "1.1.3", features = ["alloc"]}
process-wrap = { version = "8.2.1", features = ["std"] }
pulldown-cmark-to-cmark = "10.0.4"
pulldown-cmark = { version = "0.9.6", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions crates/proc-macro-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ stdx.workspace = true
span = { path = "../span", version = "0.0.0", default-features = false}

intern.workspace = true
postcard.workspace = true

[lints]
workspace = true
38 changes: 37 additions & 1 deletion crates/proc-macro-api/src/legacy_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub mod json;
pub mod msg;
pub mod postcard_wire;

use std::{
io::{BufRead, Write},
Expand Down Expand Up @@ -150,7 +151,11 @@ fn send_task(srv: &ProcMacroServerProcess, req: Request) -> Result<Response, Ser
return Err(server_error.clone());
}

srv.send_task(send_request, req)
if srv.use_postcard() {
srv.send_task_bin(send_request_postcard, req)
} else {
srv.send_task(send_request, req)
}
}

/// Sends a request to the server and reads the response.
Expand All @@ -170,3 +175,34 @@ fn send_request(
})?;
Ok(res)
}

fn send_request_postcard(
mut writer: &mut dyn Write,
mut reader: &mut dyn BufRead,
req: Request,
buf: &mut Vec<u8>,
) -> Result<Option<Response>, ServerError> {
let bytes = postcard_wire::encode_cobs(&req)
.map_err(|_| ServerError { message: "failed to write request".into(), io: None })?;

postcard_wire::write_postcard(&mut writer, &bytes).map_err(|err| ServerError {
message: "failed to write request".into(),
io: Some(Arc::new(err)),
})?;

let frame = postcard_wire::read_postcard(&mut reader, buf).map_err(|err| ServerError {
message: "failed to read response".into(),
io: Some(Arc::new(err)),
})?;

match frame {
None => Ok(None),
Some(bytes) => {
let resp: Response = postcard_wire::decode_cobs(bytes).map_err(|e| ServerError {
message: format!("failed to decode message: {e}"),
io: None,
})?;
Ok(Some(resp))
}
}
}
25 changes: 25 additions & 0 deletions crates/proc-macro-api/src/legacy_protocol/postcard_wire.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use std::io::{self, BufRead, Write};

pub fn read_postcard<'a>(
input: &mut impl BufRead,
buf: &'a mut Vec<u8>,
) -> io::Result<Option<&'a mut [u8]>> {
buf.clear();
let n = input.read_until(0, buf)?;
if n == 0 {
return Ok(None);
}
Ok(Some(&mut buf[..]))
}
pub fn write_postcard(out: &mut impl Write, msg: &[u8]) -> io::Result<()> {
out.write_all(msg)?;
out.flush()
}

pub fn encode_cobs<T: serde::Serialize>(value: &T) -> Result<Vec<u8>, postcard::Error> {
postcard::to_allocvec_cobs(value)
}

pub fn decode_cobs<T: serde::de::DeserializeOwned>(bytes: &mut [u8]) -> Result<T, postcard::Error> {
postcard::from_bytes_cobs(bytes)
}
3 changes: 2 additions & 1 deletion crates/proc-macro-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ pub mod version {
/// Whether literals encode their kind as an additional u32 field and idents their rawness as a u32 field.
pub const EXTENDED_LEAF_DATA: u32 = 5;
pub const HASHED_AST_ID: u32 = 6;
pub const POSTCARD_WIRE: u32 = 7;

/// Current API version of the proc-macro protocol.
pub const CURRENT_API_VERSION: u32 = HASHED_AST_ID;
pub const CURRENT_API_VERSION: u32 = POSTCARD_WIRE;
}

/// Represents different kinds of procedural macros that can be expanded by the external server.
Expand Down
65 changes: 64 additions & 1 deletion crates/proc-macro-api/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub(crate) struct ProcMacroServerProcess {
#[derive(Debug)]
enum Protocol {
LegacyJson { mode: SpanMode },
Postcard { mode: SpanMode },
}

/// Maintains the state of the proc-macro server process.
Expand Down Expand Up @@ -82,7 +83,11 @@ impl ProcMacroServerProcess {
if srv.version >= version::RUST_ANALYZER_SPAN_SUPPORT
&& let Ok(mode) = srv.enable_rust_analyzer_spans()
{
srv.protocol = Protocol::LegacyJson { mode };
if srv.version >= version::POSTCARD_WIRE {
srv.protocol = Protocol::Postcard { mode };
} else {
srv.protocol = Protocol::LegacyJson { mode };
}
}
tracing::info!("Proc-macro server protocol: {:?}", srv.protocol);
Ok(srv)
Expand All @@ -99,6 +104,10 @@ impl ProcMacroServerProcess {
self.exited.get().map(|it| &it.0)
}

pub(crate) fn use_postcard(&self) -> bool {
matches!(self.protocol, Protocol::Postcard { .. })
}

/// Retrieves the API version of the proc-macro server.
pub(crate) fn version(&self) -> u32 {
self.version
Expand All @@ -108,20 +117,23 @@ impl ProcMacroServerProcess {
pub(crate) fn rust_analyzer_spans(&self) -> bool {
match self.protocol {
Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer,
Protocol::Postcard { mode } => mode == SpanMode::RustAnalyzer,
}
}

/// Checks the API version of the running proc-macro server.
fn version_check(&self) -> Result<u32, ServerError> {
match self.protocol {
Protocol::LegacyJson { .. } => legacy_protocol::version_check(self),
Protocol::Postcard { .. } => legacy_protocol::version_check(self),
}
}

/// Enable support for rust-analyzer span mode if the server supports it.
fn enable_rust_analyzer_spans(&self) -> Result<SpanMode, ServerError> {
match self.protocol {
Protocol::LegacyJson { .. } => legacy_protocol::enable_rust_analyzer_spans(self),
Protocol::Postcard { .. } => legacy_protocol::enable_rust_analyzer_spans(self),
}
}

Expand All @@ -132,6 +144,7 @@ impl ProcMacroServerProcess {
) -> Result<Result<Vec<(String, ProcMacroKind)>, String>, ServerError> {
match self.protocol {
Protocol::LegacyJson { .. } => legacy_protocol::find_proc_macros(self, dylib_path),
Protocol::Postcard { .. } => legacy_protocol::find_proc_macros(self, dylib_path),
}
}

Expand Down Expand Up @@ -188,6 +201,56 @@ impl ProcMacroServerProcess {
}
})
}

pub(crate) fn send_task_bin<Request, Response>(
&self,
serialize_req: impl FnOnce(
&mut dyn Write,
&mut dyn BufRead,
Request,
&mut Vec<u8>,
) -> Result<Option<Response>, ServerError>,
req: Request,
) -> Result<Response, ServerError> {
let state = &mut *self.state.lock().unwrap();
let mut buf = Vec::<u8>::new();
serialize_req(&mut state.stdin, &mut state.stdout, req, &mut buf)
.and_then(|res| {
res.ok_or_else(|| ServerError {
message: "proc-macro server did not respond with data".to_owned(),
io: Some(Arc::new(io::Error::new(
io::ErrorKind::BrokenPipe,
"proc-macro server did not respond with data",
))),
})
})
.map_err(|e| {
// keep the exact BrokenPipe/exit handling from `send_task`
if e.io.as_ref().map(|it| it.kind()) == Some(io::ErrorKind::BrokenPipe) {
match state.process.child.try_wait() {
Ok(None) | Err(_) => e,
Ok(Some(status)) => {
let mut msg = String::new();
if !status.success()
&& let Some(stderr) = state.process.child.stderr.as_mut()
{
_ = stderr.read_to_string(&mut msg);
}
let server_error = ServerError {
message: format!(
"proc-macro server exited with {status}{}{msg}",
if msg.is_empty() { "" } else { ": " }
),
io: None,
};
self.exited.get_or_init(|| AssertUnwindSafe(server_error)).0.clone()
}
}
} else {
e
}
})
}
}

/// Manages the execution of the proc-macro server process.
Expand Down
4 changes: 1 addition & 3 deletions crates/proc-macro-srv-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ publish = false
proc-macro-srv.workspace = true
proc-macro-api.workspace = true
tt.workspace = true
postcard.workspace = true
clap = {version = "4.5.42", default-features = false, features = ["std"]}
postcard = { version = "1.1.3", optional = true }

[features]
default = ["postcard"]
sysroot-abi = ["proc-macro-srv/sysroot-abi"]
in-rust-tree = ["proc-macro-srv/in-rust-tree", "sysroot-abi"]
postcard = ["dep:postcard"]


[[bin]]
Expand Down
7 changes: 2 additions & 5 deletions crates/proc-macro-srv-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn main() -> std::io::Result<()> {
clap::Arg::new("format")
.long("format")
.action(clap::ArgAction::Set)
.default_value("json")
.default_value("postcard")
.value_parser(clap::builder::EnumValueParser::<ProtocolFormat>::new()),
clap::Arg::new("version")
.long("version")
Expand All @@ -51,26 +51,23 @@ fn main() -> std::io::Result<()> {
#[derive(Copy, Clone)]
enum ProtocolFormat {
Json,
#[cfg(feature = "postcard")]
Postcard,
}

impl ValueEnum for ProtocolFormat {
fn value_variants<'a>() -> &'a [Self] {
&[ProtocolFormat::Json]
&[ProtocolFormat::Json, ProtocolFormat::Postcard]
}

fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
match self {
ProtocolFormat::Json => Some(clap::builder::PossibleValue::new("json")),
#[cfg(feature = "postcard")]
ProtocolFormat::Postcard => Some(clap::builder::PossibleValue::new("postcard")),
}
}
fn from_str(input: &str, _ignore_case: bool) -> Result<Self, String> {
match input {
"json" => Ok(ProtocolFormat::Json),
#[cfg(feature = "postcard")]
"postcard" => Ok(ProtocolFormat::Postcard),
_ => Err(format!("unknown protocol format: {input}")),
}
Expand Down
Loading
Loading