mirror of https://github.com/rust-lang/rls
261 lines
9.1 KiB
Rust
261 lines
9.1 KiB
Rust
//! Specified a notion of a build graph, which ultimately can be queried as to
|
|
//! what work is required (either a list of `rustc` invocations or a rebuild
|
|
//! request) for a given set of dirty files.
|
|
//! Currently, there are 2 types of build plans:
|
|
//! * Cargo - used when we run Cargo in-process and intercept it
|
|
//! * External - dependency graph between invocations
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::ffi::OsStr;
|
|
use std::hash::Hash;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::mpsc::Sender;
|
|
use std::sync::Arc;
|
|
|
|
use cargo_util::ProcessBuilder;
|
|
use log::trace;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::actions::progress::ProgressUpdate;
|
|
use crate::build::cargo_plan::CargoPlan;
|
|
use crate::build::external::ExternalPlan;
|
|
use crate::build::{BuildResult, Internals, PackageArg};
|
|
|
|
pub(crate) trait BuildKey {
|
|
type Key: Eq + Hash;
|
|
fn key(&self) -> Self::Key;
|
|
}
|
|
|
|
pub(crate) trait BuildGraph {
|
|
type Unit: BuildKey;
|
|
|
|
fn units(&self) -> Vec<&Self::Unit>;
|
|
fn get(&self, key: <Self::Unit as BuildKey>::Key) -> Option<&Self::Unit>;
|
|
fn get_mut(&mut self, key: <Self::Unit as BuildKey>::Key) -> Option<&mut Self::Unit>;
|
|
fn deps(&self, key: <Self::Unit as BuildKey>::Key) -> Vec<&Self::Unit>;
|
|
|
|
fn add<T: Into<Self::Unit>>(&mut self, unit: T, deps: Vec<T>);
|
|
|
|
fn dirties<T: AsRef<Path>>(&self, files: &[T]) -> Vec<&Self::Unit>;
|
|
/// For a given set of select dirty units, returns a set of all the
|
|
/// dependencies that has to be rebuilt transitively.
|
|
fn dirties_transitive<T: AsRef<Path>>(&self, files: &[T]) -> Vec<&Self::Unit>;
|
|
/// Returns a topological ordering of units with regards to reverse
|
|
/// dependencies.
|
|
/// The output is a stack of units that can be linearly rebuilt, starting
|
|
/// from the last element.
|
|
fn topological_sort(&self, units: Vec<&Self::Unit>) -> Vec<&Self::Unit>;
|
|
fn prepare_work<T: AsRef<Path> + std::fmt::Debug>(&self, files: &[T]) -> WorkStatus;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum WorkStatus {
|
|
NeedsCargo(PackageArg),
|
|
Execute(JobQueue),
|
|
}
|
|
|
|
#[allow(clippy::large_enum_variant)]
|
|
#[derive(Debug)]
|
|
pub(crate) enum BuildPlan {
|
|
External(ExternalPlan),
|
|
Cargo(CargoPlan),
|
|
}
|
|
|
|
impl BuildPlan {
|
|
pub fn new() -> BuildPlan {
|
|
BuildPlan::Cargo(Default::default())
|
|
}
|
|
|
|
pub fn as_cargo_mut(&mut self) -> Option<&mut CargoPlan> {
|
|
match self {
|
|
BuildPlan::Cargo(plan) => Some(plan),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct JobQueue(Vec<ProcessBuilder>);
|
|
|
|
/// Returns an immediately next argument to the one specified in a given
|
|
/// ProcessBuilder (or `None` if the searched or the next argument could not be found).
|
|
///
|
|
/// This is useful for returning values for arguments of `--key <value>` format.
|
|
/// For example, if `[.., "--crate-name", "rls", ...]` arguments are specified,
|
|
/// then proc_arg(prc, "--crate-name") returns Some(&OsStr::new("rls"));
|
|
fn proc_argument_value<T: AsRef<OsStr>>(prc: &ProcessBuilder, key: T) -> Option<&std::ffi::OsStr> {
|
|
let args = prc.get_args().collect::<Vec<_>>();
|
|
let (idx, _) = args.iter().enumerate().find(|(_, arg)| arg.as_os_str() == key.as_ref())?;
|
|
|
|
Some(args.get(idx + 1)?.as_os_str())
|
|
}
|
|
|
|
impl JobQueue {
|
|
pub(crate) fn with_commands(jobs: Vec<ProcessBuilder>) -> JobQueue {
|
|
JobQueue(jobs)
|
|
}
|
|
|
|
pub(crate) fn dequeue(&mut self) -> Option<ProcessBuilder> {
|
|
self.0.pop()
|
|
}
|
|
|
|
/// Performs a rustc build using cached compiler invocations.
|
|
pub(super) fn execute(
|
|
mut self,
|
|
internals: &Internals,
|
|
progress_sender: Sender<ProgressUpdate>,
|
|
) -> BuildResult {
|
|
// TODO: In case of an empty job queue we shouldn't be here, since the
|
|
// returned results will replace currently held diagnostics/analyses.
|
|
// Either allow to return a BuildResult::Squashed here or just delegate
|
|
// to Cargo (which we do currently) in `prepare_work`
|
|
assert!(!self.0.is_empty());
|
|
|
|
let mut compiler_messages = vec![];
|
|
let mut analyses = vec![];
|
|
let mut input_files = HashMap::<_, HashSet<_>>::new();
|
|
let (build_dir, mut cwd) = {
|
|
let comp_cx = internals.compilation_cx.lock().unwrap();
|
|
(comp_cx.build_dir.clone().expect("no build directory"), comp_cx.cwd.clone())
|
|
};
|
|
|
|
// Go through cached compiler invocations sequentially, collecting each
|
|
// invocation's compiler messages for diagnostics and analysis data
|
|
while let Some(job) = self.dequeue() {
|
|
trace!("Executing: {:#?}", job);
|
|
let mut args: Vec<_> = job
|
|
.get_args()
|
|
.cloned()
|
|
.map(|x| x.into_string().expect("cannot stringify job args"))
|
|
.collect();
|
|
|
|
let program =
|
|
job.get_program().clone().into_string().expect("cannot stringify job program");
|
|
args.insert(0, program.clone());
|
|
|
|
// Needed to parse rustc diagnostics
|
|
if args.iter().find(|x| x.as_str() == "--error-format=json").is_none() {
|
|
args.push("--error-format=json".to_owned());
|
|
}
|
|
|
|
if args.iter().find(|x| x.as_str() == "--sysroot").is_none() {
|
|
let sysroot = super::rustc::current_sysroot()
|
|
.expect("need to specify SYSROOT env var or use rustup or multirust");
|
|
|
|
let config = internals.config.lock().unwrap();
|
|
if config.sysroot.is_none() {
|
|
args.push("--sysroot".to_owned());
|
|
args.push(sysroot);
|
|
}
|
|
}
|
|
|
|
// Send a window/progress notification.
|
|
{
|
|
let crate_name = proc_argument_value(&job, "--crate-name").and_then(OsStr::to_str);
|
|
let update = match crate_name {
|
|
Some(name) => {
|
|
let cfg_test = job.get_args().any(|arg| *arg == "--test");
|
|
ProgressUpdate::Message(if cfg_test {
|
|
format!("{} cfg(test)", name)
|
|
} else {
|
|
name.to_owned()
|
|
})
|
|
}
|
|
None => {
|
|
// divide by zero is avoided by earlier assert!
|
|
let percentage = compiler_messages.len() as f64 / self.0.len() as f64;
|
|
ProgressUpdate::Percentage(percentage)
|
|
}
|
|
};
|
|
|
|
progress_sender.send(update).expect("Failed to send progress update");
|
|
}
|
|
|
|
match super::rustc::rustc(
|
|
&internals.vfs,
|
|
&args,
|
|
job.get_envs(),
|
|
job.get_cwd().or_else(|| cwd.as_deref()),
|
|
&build_dir,
|
|
Arc::clone(&internals.config),
|
|
&internals.env_lock.as_facade(),
|
|
) {
|
|
BuildResult::Success(c, mut messages, mut analysis, files, success) => {
|
|
compiler_messages.append(&mut messages);
|
|
analyses.append(&mut analysis);
|
|
for (file, inputs) in files {
|
|
input_files.entry(file).or_default().extend(inputs);
|
|
}
|
|
|
|
cwd = Some(c);
|
|
|
|
// This compilation failed, but the build as a whole does not
|
|
// need to error out.
|
|
if !success {
|
|
return BuildResult::Success(
|
|
cwd.unwrap(),
|
|
compiler_messages,
|
|
analyses,
|
|
input_files,
|
|
false,
|
|
);
|
|
}
|
|
}
|
|
BuildResult::Err(cause, _) => {
|
|
let cmd = format!("{} {}", program, args.join(" "));
|
|
return BuildResult::Err(cause, Some(cmd));
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
BuildResult::Success(
|
|
cwd.unwrap_or_else(|| PathBuf::from(".")),
|
|
compiler_messages,
|
|
analyses,
|
|
input_files,
|
|
true,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Build system-agnostic, basic compilation unit
|
|
#[derive(PartialEq, Eq, Hash, Debug, Clone, Deserialize, Serialize)]
|
|
pub struct Crate {
|
|
pub name: String,
|
|
pub src_path: Option<PathBuf>,
|
|
pub edition: Edition,
|
|
/// From rustc; mainly used to group other properties used to disambiguate a
|
|
/// given compilation unit.
|
|
pub disambiguator: (u64, u64),
|
|
}
|
|
|
|
// Temporary, until Edition from rustfmt is available
|
|
#[derive(PartialEq, Eq, Hash, Debug, PartialOrd, Ord, Copy, Clone, Deserialize, Serialize)]
|
|
pub enum Edition {
|
|
Edition2015,
|
|
Edition2018,
|
|
Edition2021,
|
|
Edition2024,
|
|
}
|
|
|
|
impl Default for Edition {
|
|
fn default() -> Edition {
|
|
Edition::Edition2015
|
|
}
|
|
}
|
|
|
|
impl std::convert::TryFrom<&str> for Edition {
|
|
type Error = &'static str;
|
|
|
|
fn try_from(val: &str) -> Result<Self, Self::Error> {
|
|
Ok(match val {
|
|
"2015" => Edition::Edition2015,
|
|
"2018" => Edition::Edition2018,
|
|
"2021" => Edition::Edition2021,
|
|
"2024" => Edition::Edition2024,
|
|
_ => return Err("unknown"),
|
|
})
|
|
}
|
|
}
|