diff options
Diffstat (limited to 'rust-analyzer-chromiumos-wrapper/src/main.rs')
-rw-r--r-- | rust-analyzer-chromiumos-wrapper/src/main.rs | 145 |
1 files changed, 106 insertions, 39 deletions
diff --git a/rust-analyzer-chromiumos-wrapper/src/main.rs b/rust-analyzer-chromiumos-wrapper/src/main.rs index f59af454..43ca5a3d 100644 --- a/rust-analyzer-chromiumos-wrapper/src/main.rs +++ b/rust-analyzer-chromiumos-wrapper/src/main.rs @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +use std::collections::HashMap; use std::env; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Write}; @@ -12,13 +13,17 @@ use std::str::from_utf8; use std::thread; use anyhow::{anyhow, bail, Context, Result}; - +use lazy_static::lazy_static; use log::trace; +use regex::Regex; + use simplelog::{Config, LevelFilter, WriteLogger}; use serde_json::{from_slice, to_writer, Value}; +const CHROOT_SERVER_PATH: &str = "/usr/sbin/rust-analyzer"; + fn main() -> Result<()> { let args = env::args().skip(1); @@ -28,7 +33,7 @@ fn main() -> Result<()> { None => { // It doesn't appear that we're in a chroot. Run the // regular rust-analyzer. - return Err(process::Command::new("rust-analyzer").args(args).exec())?; + bail!(process::Command::new("rust-analyzer").args(args).exec()); } }; @@ -39,21 +44,23 @@ fn main() -> Result<()> { // * We don't support the arguments, so we bail. // * We still need to do our path translation in the LSP protocol. fn run(args: &[String]) -> Result<()> { - return Err(process::Command::new("cros_sdk") + bail!(process::Command::new("cros_sdk") .args(["--", "rust-analyzer"]) .args(args) - .exec())?; + .exec()); } - if args.iter().any(|x| match x.as_str() { - "--version" | "--help" | "-h" | "--print-config-schema" => true, - _ => false, + if args.iter().any(|x| { + matches!( + x.as_str(), + "--version" | "--help" | "-h" | "--print-config-schema" + ) }) { // With any of these options rust-analyzer will just print something and exit. return run(&args); } - if !args[0].starts_with("-") { + if !args[0].starts_with('-') { // It's a subcommand, and seemingly none of these need the path translation // rust-analyzer-chromiumos-wrapper provides. return run(&args); @@ -68,26 +75,46 @@ fn main() -> Result<()> { init_log()?; + // Get the rust sysroot, this is needed to translate filepaths to sysroot + // related files, e.g. crate sources. + let outside_rust_sysroot = { + let output = process::Command::new("rustc") + .arg("--print") + .arg("sysroot") + .output()?; + if !output.status.success() { + bail!("Unable to find rustc installation outside of sysroot"); + } + std::str::from_utf8(&output.stdout)?.to_owned() + }; + let outside_rust_sysroot = outside_rust_sysroot.trim(); + + // The /home path inside the chroot is visible outside through "<chromiumos-root>/out/home". + let outside_home: &'static str = + Box::leak(format!("{}/out/home", chromiumos_root.display()).into_boxed_str()); + let outside_prefix: &'static str = { - let path = chromiumos_root + let mut path = chromiumos_root .to_str() - .ok_or_else(|| anyhow!("Path is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("Path is not valid UTF-8"))? + .to_owned(); - let mut tmp = format!("file://{}", path); - if Some(&b'/') != tmp.as_bytes().last() { - tmp.push('/'); + if Some(&b'/') == path.as_bytes().last() { + let _ = path.pop(); } // No need to ever free this memory, so let's get a static reference. - Box::leak(tmp.into_boxed_str()) + Box::leak(path.into_boxed_str()) }; trace!("Found chromiumos root {}", outside_prefix); - let inside_prefix: &'static str = "file:///mnt/host/source/"; + let outside_sysroot_prefix: &'static str = + Box::leak(format!("{outside_rust_sysroot}/lib/rustlib").into_boxed_str()); + let inside_prefix: &'static str = "/mnt/host/source"; let cmd = "cros_sdk"; - let all_args = ["--", "rust-analyzer"] + let all_args = ["--", CHROOT_SERVER_PATH] .into_iter() .chain(args.iter().map(|x| x.as_str())); let mut child = KillOnDrop(run_command(cmd, all_args)?); @@ -95,22 +122,28 @@ fn main() -> Result<()> { let mut child_stdin = BufWriter::new(child.0.stdin.take().unwrap()); let mut child_stdout = BufReader::new(child.0.stdout.take().unwrap()); + let replacement_map = { + let mut m = HashMap::new(); + m.insert(outside_prefix, inside_prefix); + m.insert(outside_sysroot_prefix, "/usr/lib/rustlib"); + m.insert(outside_home, "/home"); + m + }; + let join_handle = { + let rm = replacement_map.clone(); thread::spawn(move || { let mut stdin = io::stdin().lock(); - stream_with_replacement(&mut stdin, &mut child_stdin, outside_prefix, inside_prefix) + stream_with_replacement(&mut stdin, &mut child_stdin, &rm) .context("Streaming from stdin into rust-analyzer") }) }; + // For the mapping between inside to outside, we just reverse the map. + let replacement_map_rev = replacement_map.iter().map(|(k, v)| (*v, *k)).collect(); let mut stdout = BufWriter::new(io::stdout().lock()); - stream_with_replacement( - &mut child_stdout, - &mut stdout, - inside_prefix, - outside_prefix, - ) - .context("Streaming from rust-analyzer into stdout")?; + stream_with_replacement(&mut child_stdout, &mut stdout, &replacement_map_rev) + .context("Streaming from rust-analyzer into stdout")?; join_handle.join().unwrap()?; @@ -174,21 +207,37 @@ fn read_header<R: BufRead>(r: &mut R, header: &mut Header) -> Result<()> { } } -/// Extend `dest` with `contents`, replacing any occurrence of `pattern` in a json string in -/// `contents` with `replacement`. -fn replace(contents: &[u8], pattern: &str, replacement: &str, dest: &mut Vec<u8>) -> Result<()> { - fn map_value(val: Value, pattern: &str, replacement: &str) -> Value { +/// Extend `dest` with `contents`, replacing any occurrence of patterns in a json string in +/// `contents` with a replacement. +fn replace( + contents: &[u8], + replacement_map: &HashMap<&str, &str>, + dest: &mut Vec<u8>, +) -> Result<()> { + fn map_value(val: Value, replacement_map: &HashMap<&str, &str>) -> Value { match val { Value::String(s) => // `s.replace` is very likely doing more work than necessary. Probably we only need // to look for the pattern at the beginning of the string. { - Value::String(s.replace(pattern, replacement)) + lazy_static! { + static ref SERVER_PATH_REGEX: Regex = + Regex::new(r".*/rust-analyzer-chromiumos-wrapper$").unwrap(); + } + // Always replace the server path everywhere. + let mut s = SERVER_PATH_REGEX + .replace_all(&s, CHROOT_SERVER_PATH) + .to_string(); + // Then replace all mappings we get. + for (pattern, replacement) in replacement_map { + s = s.replace(pattern, replacement); + } + Value::String(s.to_string()) } Value::Array(mut v) => { for val_ref in v.iter_mut() { let value = std::mem::replace(val_ref, Value::Null); - *val_ref = map_value(value, pattern, replacement); + *val_ref = map_value(value, replacement_map); } Value::Array(v) } @@ -196,7 +245,7 @@ fn replace(contents: &[u8], pattern: &str, replacement: &str, dest: &mut Vec<u8> // Surely keys can't be paths. for val_ref in map.values_mut() { let value = std::mem::replace(val_ref, Value::Null); - *val_ref = map_value(value, pattern, replacement); + *val_ref = map_value(value, replacement_map); } Value::Object(map) } @@ -211,26 +260,25 @@ fn replace(contents: &[u8], pattern: &str, replacement: &str, dest: &mut Vec<u8> ), Ok(s) => format!("JSON parsing content of length {}:\n{}", contents.len(), s), })?; - let mapped_val = map_value(init_val, pattern, replacement); + let mapped_val = map_value(init_val, replacement_map); to_writer(dest, &mapped_val)?; Ok(()) } -/// Read LSP messages from `r`, replacing each occurrence of `pattern` in a json string in the -/// payload with `replacement`, adjusting the `Content-Length` in the header to match, and writing +/// Read LSP messages from `r`, replacing each occurrence of patterns in a json string in the +/// payload with replacements, adjusting the `Content-Length` in the header to match, and writing /// the result to `w`. fn stream_with_replacement<R: BufRead, W: Write>( r: &mut R, w: &mut W, - pattern: &str, - replacement: &str, + replacement_map: &HashMap<&str, &str>, ) -> Result<()> { let mut head = Header::default(); let mut buf = Vec::with_capacity(1024); let mut buf2 = Vec::with_capacity(1024); loop { read_header(r, &mut head)?; - if head.length.is_none() && head.other_fields.len() == 0 { + if head.length.is_none() && head.other_fields.is_empty() { // No content in the header means we're apparently done. return Ok(()); } @@ -251,7 +299,7 @@ fn stream_with_replacement<R: BufRead, W: Write>( trace!("Received payload\n{}", from_utf8(&buf)?); buf2.clear(); - replace(&buf, pattern, replacement, &mut buf2)?; + replace(&buf, replacement_map, &mut buf2)?; trace!("After replacements payload\n{}", from_utf8(&buf2)?); @@ -307,7 +355,12 @@ mod test { json_expected: &str, ) -> Result<()> { let mut w = Vec::<u8>::with_capacity(read.len()); - stream_with_replacement(&mut read.as_bytes(), &mut w, pattern, replacement)?; + let replacement_map = { + let mut m = HashMap::new(); + m.insert(pattern, replacement); + m + }; + stream_with_replacement(&mut read.as_bytes(), &mut w, &replacement_map)?; // serde_json may not format the json output the same as we do, so we can't just compare // as strings or slices. @@ -361,4 +414,18 @@ mod test { \"key3\": \"morereplacementtext\"}, \"key4\": 1}", ) } + + #[test] + fn test_stream_with_replacement_3() -> Result<()> { + test_stream_with_replacement( + // read + "Content-Length: 55\r\n\r\n{\"path\": \"/my_folder/rust-analyzer-chromiumos-wrapper\"}", + // pattern + "", + // replacement + "", + // json_expected + "{\"path\": \"/usr/sbin/rust-analyzer\"}", + ) + } } |