diff --git a/Cargo.lock b/Cargo.lock index 102c7550424..57e639de2f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1108,6 +1108,7 @@ dependencies = [ "rls-rustc 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "rls-span 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rls-vfs 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-workspace-hack 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustfmt-nightly 0.99.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index e7ff825c13a..b7100a0577a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,11 +28,12 @@ racer = { version = "2.1.5", default-features = false } rayon = "1" rls-analysis = "0.16" rls-blacklist = "0.1.2" -rls-data = { version = "0.18", features = ["serialize-serde"] } +rls-data = { version = "0.18", features = ["serialize-serde", "serialize-rustc"] } rls-rustc = "0.5.0" rls-span = { version = "0.4", features = ["serialize-serde"] } rls-vfs = "0.4.6" rustfmt-nightly = "0.99.4" +rustc-serialize = "0.3" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" diff --git a/src/build/external.rs b/src/build/external.rs new file mode 100644 index 00000000000..b30579ddc4a --- /dev/null +++ b/src/build/external.rs @@ -0,0 +1,100 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Performs a build using a provided black-box build command, which ought to +//! return a list of save-analysis JSON files to be reloaded by the RLS. +//! Please note that since the command is ran externally (at a file/OS level) +//! this doesn't work with files that are not saved. + +use std::io::BufRead; +use std::io::Read; +use std::fs::File; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +use super::BuildResult; + +use rls_data::Analysis; +use log::{log, trace}; + +/// Performs a build using an external command and interprets the results. +/// The command should output on stdout a list of save-analysis .json files +/// to be reloaded by the RLS. +/// Note: This is *very* experimental and preliminary - this can viewed as +/// an experimentation until a more complete solution emerges. +pub(super) fn build_with_external_cmd>(cmd_line: S, build_dir: PathBuf) -> BuildResult { + let cmd_line = cmd_line.as_ref(); + let (cmd, args) = { + let mut words = cmd_line.split_whitespace(); + let cmd = match words.next() { + Some(cmd) => cmd, + None => { + return BuildResult::Err("Specified build_command is empty".into(), None); + } + }; + (cmd, words) + }; + let spawned = Command::new(&cmd) + .args(args) + .current_dir(&build_dir) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn(); + + let child = match spawned { + Ok(child) => child, + Err(io) => { + let err_msg = format!("Couldn't execute: {} ({:?})", cmd_line, io.kind()); + trace!("{}", err_msg); + return BuildResult::Err(err_msg, Some(cmd_line.to_owned())); + }, + }; + + let reader = std::io::BufReader::new(child.stdout.unwrap()); + + let files = reader.lines().filter_map(|res| res.ok()) + .map(PathBuf::from) + // Relative paths are relative to build command, not RLS itself (cwd may be different) + .map(|path| if !path.is_absolute() { build_dir.join(path) } else { path }); + + let analyses = match read_analysis_files(files) { + Ok(analyses) => analyses, + Err(cause) => { + let err_msg = format!("Couldn't read analysis data: {}", cause); + return BuildResult::Err(err_msg, Some(cmd_line.to_owned())); + } + }; + + BuildResult::Success(build_dir.clone(), vec![], analyses, false) +} + +/// Reads and deserializes given save-analysis JSON files into corresponding +/// `rls_data::Analysis` for each file. If an error is encountered, a `String` +/// with the error message is returned. +fn read_analysis_files(files: I) -> Result, String> +where + I: Iterator, + I::Item: AsRef +{ + let mut analyses = Vec::new(); + + for path in files { + trace!("external::read_analysis_files: Attempt to read `{}`", path.as_ref().display()); + + let mut file = File::open(path).map_err(|e| e.to_string())?; + let mut contents = String::new(); + file.read_to_string(&mut contents).map_err(|e| e.to_string())?; + + let data = rustc_serialize::json::decode(&contents).map_err(|e| e.to_string())?; + analyses.push(data); + } + + Ok(analyses) +} diff --git a/src/build/mod.rs b/src/build/mod.rs index 73fa4531083..0810901f98a 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -37,6 +37,7 @@ pub mod environment; mod cargo; mod rustc; mod plan; +mod external; use self::plan::{Plan as BuildPlan, WorkStatus}; @@ -530,6 +531,12 @@ impl Internals { // do this so we can load changed code from the VFS, rather than from // disk). + // Check if an override setting was provided and execute that, instead. + if let Some(ref cmd) = self.config.lock().unwrap().build_command { + let build_dir = self.compilation_cx.lock().unwrap().build_dir.clone(); + return external::build_with_external_cmd(cmd, build_dir.unwrap()); + } + // Don't hold this lock when we run Cargo. let needs_to_run_cargo = self.compilation_cx.lock().unwrap().args.is_empty(); diff --git a/src/config.rs b/src/config.rs index e31b45e4a57..4b0cb8446c9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -160,6 +160,13 @@ pub struct Config { /// Use provided rustfmt binary instead of the statically linked one. /// (requires unstable features) pub rustfmt_path: Option, + /// EXPERIMENTAL (needs unstable features) + /// If set, executes a given program responsible for rebuilding save-analysis + /// to be loaded by the RLS. The program given should output a list of + /// resulting .json files on stdout. + /// + /// Implies `build_on_save`: true. + pub build_command: Option, } impl Default for Config { @@ -189,6 +196,7 @@ impl Default for Config { full_docs: Inferrable::Inferred(false), show_hover_context: true, rustfmt_path: None, + build_command: None, }; result.normalise(); result @@ -226,6 +234,9 @@ impl Config { self.build_lib = Inferrable::Inferred(false); self.cfg_test = false; self.rustfmt_path = None; + self.build_command = None; + } else if self.build_command.is_some() { + self.build_on_save = true; } }