aboutsummaryrefslogtreecommitdiff
path: root/rust-analyzer-chromiumos-wrapper/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust-analyzer-chromiumos-wrapper/src/main.rs')
-rw-r--r--rust-analyzer-chromiumos-wrapper/src/main.rs145
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\"}",
+ )
+ }
}