Skip to content
This repository was archived by the owner on Dec 29, 2022. It is now read-only.

Use cached build plan mostly instead of Cargo to schedule a multi-target build #462

Merged
merged 7 commits into from
Sep 20, 2017
Merged
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
5 changes: 4 additions & 1 deletion src/actions/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl<'a> NotificationAction<'a> for DidChange {
let ctx = ctx.inited();
let file_path = parse_file_path!(&params.text_document.uri, "on_change")?;

let changes: Vec<Change> = params.content_changes.iter().map(move |i| {
let changes: Vec<Change> = params.content_changes.iter().map(|i| {
if let Some(range) = i.range {
let range = ls_util::range_to_rls(range);
Change::ReplaceText {
Expand All @@ -114,6 +114,9 @@ impl<'a> NotificationAction<'a> for DidChange {
}
}).collect();
ctx.vfs.on_changes(&changes).expect("error committing to VFS");
if !changes.is_empty() {
ctx.build_queue.mark_file_dirty(file_path, params.text_document.version)
}

if !ctx.config.lock().unwrap().build_on_save {
ctx.build_current_project(BuildPriority::Normal, out);
Expand Down
12 changes: 5 additions & 7 deletions src/build/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ impl RlsExecutor {
}
}

/// Returns wheter a given package is a primary one (every member of the
/// workspace is considered as such).
fn is_primary_crate(&self, id: &PackageId) -> bool {
if self.workspace_mode {
self.member_packages.lock().unwrap().contains(id)
Expand All @@ -253,13 +255,9 @@ impl Executor for RlsExecutor {
}

fn force_rebuild(&self, unit: &Unit) -> bool {
// TODO: Currently workspace_mode doesn't use rustc, so it doesn't
// need args. When we start using rustc, we might consider doing
// force_rebuild to retrieve args for given package if they're stale/missing
if self.workspace_mode {
return false;
}

// In workspace_mode we need to force rebuild every package in the
// workspace, even if it's not dirty at a time, to cache compiler
// invocations in the build plan.
// We only do a cargo build if we want to force rebuild the last
// crate (e.g., because some args changed). Therefore we should
// always force rebuild the primary crate.
Expand Down
77 changes: 64 additions & 13 deletions src/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ use std::time::Duration;
mod environment;
mod cargo;
mod rustc;
pub mod plan;
mod plan;

use self::plan::Plan as BuildPlan;
use self::plan::{Plan as BuildPlan, WorkStatus};

/// Manages builds.
///
Expand Down Expand Up @@ -68,13 +68,18 @@ pub struct BuildQueue {
queued: Arc<Mutex<(Build, Build)>>,
}

/// Used when tracking modified files across different builds.
type FileVersion = u64;

// Information needed to run and configure builds.
struct Internals {
// Arguments and environment with which we call rustc.
// This can be further expanded for multi-crate target configuration.
// This lock should only be held transiently.
compilation_cx: Arc<Mutex<CompilationContext>>,
env_lock: Arc<EnvironmentLock>,
/// Set of files that were modified since last build.
dirty_files: Arc<Mutex<HashMap<PathBuf, FileVersion>>>,
vfs: Arc<Vfs>,
// This lock should only be held transiently.
config: Arc<Mutex<Config>>,
Expand Down Expand Up @@ -146,7 +151,8 @@ enum Build {
struct PendingBuild {
build_dir: PathBuf,
priority: BuildPriority,
// Closure to execture once the build is complete.
built_files: HashMap<PathBuf, FileVersion>,
// Closure to execute once the build is complete.
and_then: Box<FnBox(BuildResult) + Send + 'static>,
}

Expand Down Expand Up @@ -228,6 +234,7 @@ impl BuildQueue {

let build = PendingBuild {
build_dir: new_build_dir.to_owned(),
built_files: self.internals.dirty_files.lock().unwrap().clone(),
priority,
and_then: Box::new(and_then),
};
Expand Down Expand Up @@ -324,7 +331,8 @@ impl BuildQueue {
}

// Run the build.
let result = internals.run_build(&build.build_dir, build.priority);
let result = internals.run_build(&build.build_dir, build.priority,
&build.built_files);
// Assert that the build was not squashed.
if let BuildResult::Squashed = result {
unreachable!();
Expand All @@ -340,6 +348,14 @@ impl BuildQueue {
}
}
}

/// Marks a given versioned file as dirty since last build. The dirty flag
/// will be cleared by a successful build that builds this or a more recent
/// version of this file.
pub fn mark_file_dirty(&self, file: PathBuf, version: FileVersion) {
trace!("Marking file as dirty: {:?} ({})", file, version);
self.internals.dirty_files.lock().unwrap().insert(file, version);
}
}

impl Internals {
Expand All @@ -348,6 +364,7 @@ impl Internals {
compilation_cx: Arc::new(Mutex::new(CompilationContext::new())),
vfs,
config,
dirty_files: Arc::new(Mutex::new(HashMap::new())),
// Since environment is global mutable state and we can run multiple server
// instances, be sure to use a global lock to ensure env var consistency
env_lock: EnvironmentLock::get(),
Expand All @@ -356,7 +373,12 @@ impl Internals {
}

// Entry point method for building.
fn run_build(&self, new_build_dir: &Path, priority: BuildPriority) -> BuildResult {
fn run_build(
&self,
new_build_dir: &Path,
priority: BuildPriority,
built_files: &HashMap<PathBuf, FileVersion>,
) -> BuildResult {
trace!("run_build, {:?} {:?}", new_build_dir, priority);

// Check if the build directory changed and update it.
Expand All @@ -375,7 +397,23 @@ impl Internals {
}
}

self.build()
let result = self.build();
// On a successful build, clear dirty files that were successfuly built
// now. It's possible that a build was scheduled with given files, but
// user later changed them. These should still be left as dirty (not built).
match *&result {
BuildResult::Success(_, _) | BuildResult::Failure(_, _) => {
let mut dirty_files = self.dirty_files.lock().unwrap();
dirty_files.retain(|file, dirty_version| {
built_files.get(file)
.map(|built_version| built_version < dirty_version)
.unwrap_or(false)
});
trace!("Files still dirty after the build: {:?}", *dirty_files);
},
_ => {}
};
result
}

// Build the project.
Expand All @@ -401,14 +439,27 @@ impl Internals {
let needs_to_run_cargo = self.compilation_cx.lock().unwrap().args.is_empty();
let workspace_mode = self.config.lock().unwrap().workspace_mode;

if workspace_mode || needs_to_run_cargo {
let result = cargo::cargo(self);

match result {
BuildResult::Err => return BuildResult::Err,
_ if workspace_mode => return result,
_ => {},
if workspace_mode {
// If the build plan has already been cached, use it, unless Cargo
// has to be specifically rerun (e.g. when build scripts changed)
let work = {
let modified: Vec<_> = self.dirty_files.lock().unwrap()
.keys().cloned().collect();
let cx = self.compilation_cx.lock().unwrap();
cx.build_plan.prepare_work(&modified)
};
return match work {
// In workspace_mode, cargo performs the full build and returns
// appropriate diagnostics/analysis data
WorkStatus::NeedsCargo => cargo::cargo(self),
WorkStatus::Execute(job_queue) => job_queue.execute(self),
};
// In single package mode Cargo needs to be run to cache args/envs for
// future rustc calls
} else if needs_to_run_cargo {
if let BuildResult::Err = cargo::cargo(self) {
return BuildResult::Err;
}
}

let compile_cx = self.compilation_cx.lock().unwrap();
Expand Down
Loading