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

Commit dd0b935

Browse files
committed
Use cached build plan under workspace_mode if available to perform build
Because of how cached build plan works, under `workspace_mode` every member package must be forcefully rebuilt to store the arguments Cargo would generate for the target. This is required when only a certain member package will be rebuilt initially, but changes made to another one will pull a rebuild of a package whose rustc call wasn't cached. Furthermore, sometimes user can quickly input changes and save, before any build can be made. When this happens and there are no modified files, build plan will return an empty job queue, since no files are dirty at that time. When that happens, it delegates to Cargo, just when build scripts are modified. In the future, detecting dirty files and guessing which files relate to which rebuilt package is a concrete area for improvement.
1 parent 53ba829 commit dd0b935

File tree

3 files changed

+77
-17
lines changed

3 files changed

+77
-17
lines changed

src/build/cargo.rs

+5-7
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ impl RlsExecutor {
227227
}
228228
}
229229

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

255257
fn force_rebuild(&self, unit: &Unit) -> bool {
256-
// TODO: Currently workspace_mode doesn't use rustc, so it doesn't
257-
// need args. When we start using rustc, we might consider doing
258-
// force_rebuild to retrieve args for given package if they're stale/missing
259-
if self.workspace_mode {
260-
return false;
261-
}
262-
258+
// In workspace_mode we need to force rebuild every package in the
259+
// workspace, even if it's not dirty at a time, to cache compiler
260+
// invocations in the build plan.
263261
// We only do a cargo build if we want to force rebuild the last
264262
// crate (e.g., because some args changed). Therefore we should
265263
// always force rebuild the primary crate.

src/build/mod.rs

+21-8
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ use std::time::Duration;
3030
mod environment;
3131
mod cargo;
3232
mod rustc;
33-
pub mod plan;
33+
mod plan;
3434

35-
use self::plan::Plan as BuildPlan;
35+
use self::plan::{Plan as BuildPlan, WorkStatus};
3636

3737
/// Manages builds.
3838
///
@@ -401,14 +401,27 @@ impl Internals {
401401
let needs_to_run_cargo = self.compilation_cx.lock().unwrap().args.is_empty();
402402
let workspace_mode = self.config.lock().unwrap().workspace_mode;
403403

404-
if workspace_mode || needs_to_run_cargo {
405-
let result = cargo::cargo(self);
404+
if workspace_mode {
405+
// If the build plan has already been cached, use it, unless Cargo
406+
// has to be specifically rerun (e.g. when build scripts changed)
407+
let work = {
408+
let cx = self.compilation_cx.lock().unwrap();
409+
let modified: Vec<_> = self.vfs.get_changes().keys().cloned().collect();
406410

407-
match result {
408-
BuildResult::Err => return BuildResult::Err,
409-
_ if workspace_mode => return result,
410-
_ => {},
411+
cx.build_plan.prepare_work(&modified)
411412
};
413+
return match work {
414+
// In workspace_mode, cargo performs the full build and returns
415+
// appropriate diagnostics/analysis data
416+
WorkStatus::NeedsCargo => cargo::cargo(self),
417+
WorkStatus::Execute(job_queue) => job_queue.execute(self),
418+
};
419+
// In single package mode Cargo needs to be run to cache args/envs for
420+
// future rustc calls
421+
} else if needs_to_run_cargo {
422+
if let BuildResult::Err = cargo::cargo(self) {
423+
return BuildResult::Err;
424+
}
412425
}
413426

414427
let compile_cx = self.compilation_cx.lock().unwrap();

src/build/plan.rs

+51-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ use cargo::core::{PackageId, Profile, Target, TargetKind};
3232
use cargo::ops::{Kind, Unit, Context};
3333
use cargo::util::{CargoResult, ProcessBuilder};
3434

35+
use super::{BuildResult, Internals};
36+
3537
/// Main key type by which `Unit`s will be distinguished in the build plan.
3638
pub type UnitKey = (PackageId, TargetKind);
3739
/// Holds the information how exactly the build will be performed for a given
@@ -61,6 +63,8 @@ impl Plan {
6163
*self = Plan::new();
6264
}
6365

66+
/// Returns whether a build plan has cached compiler invocations and dep
67+
/// graph so it's at all able to return a job queue via `prepare_work`.
6468
pub fn is_ready(&self) -> bool {
6569
self.compiler_jobs.is_empty() == false
6670
}
@@ -229,6 +233,8 @@ impl Plan {
229233
}
230234

231235
pub fn prepare_work<T: AsRef<Path> + fmt::Debug>(&self, modified: &[T]) -> WorkStatus {
236+
if self.is_ready() == false { return WorkStatus::NeedsCargo; }
237+
232238
let dirties = self.fetch_dirty_units(modified);
233239
trace!("fetch_dirty_units: for files {:?}, these units are dirty: {:?}", modified, dirties);
234240

@@ -240,11 +246,15 @@ impl Plan {
240246

241247
let queue = self.topological_sort(&graph);
242248
trace!("Topologically sorted dirty graph: {:?}", queue);
243-
let jobs = queue.iter()
249+
let jobs: Vec<_> = queue.iter()
244250
.map(|x| self.compiler_jobs.get(x).unwrap().clone())
245251
.collect();
246252

247-
WorkStatus::Execute(JobQueue(jobs))
253+
if jobs.is_empty() {
254+
WorkStatus::NeedsCargo
255+
} else {
256+
WorkStatus::Execute(JobQueue(jobs))
257+
}
248258
}
249259
}
250260
}
@@ -260,6 +270,45 @@ impl JobQueue {
260270
pub fn dequeue(&mut self) -> Option<ProcessBuilder> {
261271
self.0.pop()
262272
}
273+
274+
/// Performs a rustc build using cached compiler invocations.
275+
pub(super) fn execute(mut self, internals: &Internals) -> BuildResult {
276+
// TODO: In case of an empty job queue we shouldn't be here, since the
277+
// returned results will replace currently held diagnostics/analyses.
278+
// Either allow to return a BuildResult::Squashed here or just delegate
279+
// to Cargo (which we do currently) in `prepare_work`
280+
assert!(self.0.is_empty() == false);
281+
282+
let build_dir = internals.compilation_cx.lock().unwrap().build_dir.clone().unwrap();
283+
284+
let mut compiler_messages = vec![];
285+
let mut analyses = vec![];
286+
// Go through cached compiler invocations sequentially, collecting each
287+
// invocation's compiler messages for diagnostics and analysis data
288+
while let Some(job) = self.dequeue() {
289+
trace!("Executing: {:?}", job);
290+
let mut args: Vec<_> = job.get_args().iter().cloned()
291+
.map(|x| x.into_string().unwrap()).collect();
292+
// TODO: is it crucial to pass *actual* executed program (e.g. rustc
293+
// shim) or does the compiler API just care about the order and not
294+
// which exact binary was run (1st argument)?
295+
args.insert(0, "rustc".to_owned());
296+
297+
match super::rustc::rustc(&internals.vfs, &args, job.get_envs(),
298+
&build_dir, internals.config.clone(),
299+
internals.env_lock.as_facade()) {
300+
BuildResult::Success(mut messages, mut analysis) |
301+
BuildResult::Failure(mut messages, mut analysis) => {
302+
compiler_messages.append(&mut messages);
303+
analyses.append(&mut analysis);
304+
},
305+
BuildResult::Err => { return BuildResult:: Err },
306+
_ => {}
307+
}
308+
}
309+
// TODO: Should we combine with ::Failure? What's the difference between those two?
310+
return BuildResult::Success(compiler_messages, analyses);
311+
}
263312
}
264313

265314
fn key_from_unit(unit: &Unit) -> UnitKey {

0 commit comments

Comments
 (0)