use std::cell::{Cell, RefCell};
use std::collections::{BTreeMap, BTreeSet, btree_map};
use super::state::State;
use super::deps_log::DepsLog;
use super::build_log::BuildLog;
use super::disk_interface::DiskInterface;
use super::graph::{NodeIndex, EdgeIndex, DependencyScan};
use super::exit_status::ExitStatus;
use super::metrics::Stopwatch;
use super::metrics::get_time_millis;
use super::debug_flags::KEEP_RSP;
use super::timestamp::TimeStamp;
use super::subprocess::SubprocessSet;
use super::utils::{get_load_average, pathbuf_from_bytes};
use super::line_printer::{LinePrinter, LinePrinterLineType};
pub enum EdgeResult {
EdgeFailed,
EdgeSucceeded,
}
pub struct Plan {
wanted_edges: usize,
command_edges: usize,
want: BTreeMap<EdgeIndex, bool>,
ready: BTreeSet<EdgeIndex>,
}
trait IsVacant {
fn is_vacant(&self) -> bool;
}
impl<'a, K, V> IsVacant for btree_map::Entry<'a, K, V> {
fn is_vacant(&self) -> bool {
match self {
&btree_map::Entry::Vacant(_) => true,
_ => false,
}
}
}
impl Plan {
pub fn new() -> Self {
Plan {
wanted_edges: 0usize,
command_edges: 0usize,
want: BTreeMap::new(),
ready: BTreeSet::new(),
}
}
pub fn add_target(&mut self, state: &State, node: NodeIndex) -> Result<bool, String> {
self.add_sub_target(state, node, None)
}
pub fn add_sub_target(
&mut self,
state: &State,
node_idx: NodeIndex,
dependent: Option<NodeIndex>,
) -> Result<bool, String> {
let node = state.node_state.get_node(node_idx);
let edge_idx = node.in_edge();
if edge_idx.is_none() {
if node.is_dirty() {
let mut err = format!("'{}'", String::from_utf8_lossy(node.path()));
if let Some(dependent) = dependent {
err += &format!(
", needed by '{}',",
String::from_utf8_lossy(state.node_state.get_node(dependent).path())
);
}
err += " missing and no known rule to make it";
return Err(err);
}
return Ok(false);
}
let edge_idx = edge_idx.unwrap();
let edge = state.edge_state.get_edge(edge_idx);
if edge.outputs_ready() {
return Ok(false); }
let want = self.want.get(&edge_idx).cloned();
let vacant = want.is_none();
if node.is_dirty() && want.unwrap_or(false) == false {
self.want.insert(edge_idx, true);
self.wanted_edges += 1;
if edge.all_inputs_ready(state) {
self.schedule_work(state, edge_idx);
}
if !edge.is_phony() {
self.command_edges += 1;
}
}
if vacant {
for input_node_idx in edge.inputs.iter() {
self.add_sub_target(state, *input_node_idx, Some(node_idx))?;
}
}
return Ok(true);
}
fn command_edge_count(&self) -> usize {
return self.command_edges;
}
fn reset(&mut self) {
self.command_edges = 0;
self.wanted_edges = 0;
self.ready.clear();
self.want.clear();
}
pub fn more_to_do(&self) -> bool {
self.wanted_edges > 0 && self.command_edges > 0
}
pub fn schedule_work(&mut self, state: &State, edge_idx: EdgeIndex) {
if self.ready.get(&edge_idx).is_some() {
return;
}
let edge = state.edge_state.get_edge(edge_idx);
let mut pool = edge.pool.borrow_mut();
if pool.should_delay_edge() {
pool.delay_edge(state, edge_idx);
pool.retrieve_ready_edges(state, &mut self.ready);
} else {
pool.edge_scheduled(state, edge_idx);
self.ready.insert(edge_idx);
}
}
pub fn find_work(&mut self) -> Option<EdgeIndex> {
match self.ready.iter().next().cloned() {
Some(idx) => {
self.ready.remove(&idx);
Some(idx)
}
None => None,
}
}
pub fn edge_finished(&mut self, state: &mut State, edge_idx: EdgeIndex, result: EdgeResult) {
let directly_wanted = self.want.get(&edge_idx).unwrap().clone();
{
let edge = state.edge_state.get_edge(edge_idx);
if directly_wanted {
edge.pool.borrow_mut().edge_finished(state, edge_idx);
}
edge.pool.borrow_mut().retrieve_ready_edges(
state,
&mut self.ready,
);
}
match result {
EdgeResult::EdgeSucceeded => {
if directly_wanted {
self.wanted_edges -= 1;
}
self.want.remove(&edge_idx);
state.edge_state.get_edge_mut(edge_idx).outputs_ready = true;
for output_node_idx in state
.edge_state
.get_edge_mut(edge_idx)
.outputs
.clone()
.into_iter()
{
self.node_finished(state, output_node_idx);
}
}
_ => {}
};
}
pub fn node_finished(&mut self, state: &mut State, node_idx: NodeIndex) {
for out_edge_idx in state
.node_state
.get_node(node_idx)
.out_edges()
.to_owned()
.into_iter()
{
let want_e = self.want.get(&out_edge_idx).cloned();
if want_e.is_none() {
continue;
}
{
let oe = state.edge_state.get_edge(out_edge_idx);
if !oe.all_inputs_ready(state) {
continue;
}
}
if want_e.unwrap() {
self.schedule_work(state, out_edge_idx);
} else {
self.edge_finished(state, out_edge_idx, EdgeResult::EdgeSucceeded);
}
}
}
pub fn clean_node(
&mut self,
scan: &DependencyScan,
State: &State,
node_idx: NodeIndex,
) -> Result<(), String> {
unimplemented!()
}
}
pub struct CommandRunnerResult {
pub edge: EdgeIndex,
pub status: ExitStatus,
pub output: Vec<u8>,
}
impl CommandRunnerResult {
fn is_success(&self) -> bool {
match self.status {
ExitStatus::ExitSuccess => true,
_ => false,
}
}
}
pub trait CommandRunner {
fn can_run_more(&self) -> bool;
fn start_command(&mut self, state: &State, edge: EdgeIndex) -> bool;
fn wait_for_command(&mut self) -> Option<CommandRunnerResult>;
fn get_active_edges(&self) -> Vec<EdgeIndex>;
fn abort(&mut self);
}
pub enum BuildConfigVerbosity {
NORMAL,
QUIET, VERBOSE,
}
pub struct BuildConfig {
pub verbosity: BuildConfigVerbosity,
pub dry_run: bool,
pub parallelism: usize,
pub failures_allowed: usize,
pub max_load_average: f64,
}
impl BuildConfig {
pub fn new() -> Self {
BuildConfig {
verbosity: BuildConfigVerbosity::NORMAL,
dry_run: false,
parallelism: 1,
failures_allowed: 1,
max_load_average: -0.0f64,
}
}
}
pub struct Builder<'s, 'p, 'a, 'b, 'c>
where
's: 'a,
{
state: &'s mut State,
config: &'p BuildConfig,
plan: Plan,
command_runner: Option<Box<CommandRunner + 'p>>,
disk_interface: &'c DiskInterface,
scan: DependencyScan<'s, 'a, 'b, 'c>,
status: BuildStatus<'p>,
}
impl<'s, 'p, 'a, 'b, 'c> Builder<'s, 'p, 'a, 'b, 'c>
where
's: 'a,
{
pub fn new(
state: &'s mut State,
config: &'p BuildConfig,
build_log: &'a BuildLog<'s>,
deps_log: &'b DepsLog,
disk_interface: &'c DiskInterface,
) -> Self {
Builder {
state,
config,
plan: Plan::new(),
command_runner: None,
disk_interface,
scan: DependencyScan::new(build_log, deps_log, disk_interface),
status: BuildStatus::new(config),
}
}
pub fn add_target(&mut self, node_idx: NodeIndex) -> Result<(), String> {
self.scan.recompute_dirty(self.state, node_idx)?;
if let Some(in_edge) = self.state.node_state.get_node(node_idx).in_edge() {
if self.state.edge_state.get_edge(in_edge).outputs_ready() {
return Ok(()); }
}
self.plan.add_target(self.state, node_idx)?;
Ok(())
}
pub fn is_already_up_to_date(&mut self) -> bool {
!self.plan.more_to_do()
}
pub fn build(&mut self) -> Result<(), String> {
assert!(!self.is_already_up_to_date());
self.status.plan_has_total_edges(
self.plan.command_edge_count(),
);
let mut pending_commands = 0;
let mut failures_allowed = self.config.failures_allowed;
let config = self.config;
if self.command_runner.is_none() {
self.command_runner = Some(if config.dry_run {
Box::new(DryRunCommandRunner::new())
} else {
Box::new(RealCommandRunner::new(config))
});
}
self.status.build_started();
while self.plan.more_to_do() {
if failures_allowed > 0 && self.command_runner.as_ref().unwrap().can_run_more() {
if let Some(edge_idx) = self.plan.find_work() {
if let Err(e) = self.start_edge(edge_idx) {
self.cleanup();
self.status.build_finished();
return Err(e);
};
if self.state.edge_state.get_edge(edge_idx).is_phony() {
self.plan.edge_finished(
self.state,
edge_idx,
EdgeResult::EdgeSucceeded,
);
} else {
pending_commands += 1;
}
continue;
}
}
if pending_commands > 0 {
let result = self.command_runner.as_mut().unwrap().wait_for_command();
if result.is_none() ||
result.as_ref().unwrap().status == ExitStatus::ExitInterrupted
{
self.cleanup();
self.status.build_finished();
return Err("interrupted by user".to_owned());
}
pending_commands -= 1;
let result = self.finish_command(result.unwrap());
if let Err(e) = result {
self.cleanup();
self.status.build_finished();
return Err(e);
}
let result = result.unwrap();
if !result.is_success() {
if failures_allowed > 0 {
failures_allowed -= 1;
}
}
continue;
}
self.status.build_finished();
return match failures_allowed {
0 if config.failures_allowed > 1 => Err("subcommands failed".to_owned()),
0 => Err("subcommand failed".to_owned()),
_ if failures_allowed < self.config.failures_allowed => Err(
"cannot make progress due to previous errors"
.to_owned(),
),
_ => Err("stuck [this is a bug]".to_owned()),
};
}
self.status.build_finished();
return Ok(());
}
fn start_edge(&mut self, edge_idx: EdgeIndex) -> Result<(), String> {
metric_record!("StartEdge");
let edge = self.state.edge_state.get_edge(edge_idx);
if edge.is_phony() {
return Ok(());
}
self.status.build_edge_started(self.state, edge_idx);
for out_idx in edge.outputs.iter() {
let path = pathbuf_from_bytes(
self.state.node_state.get_node(*out_idx).path().to_owned(),
).map_err(|e| {
format!("invalid utf-8 filename: {}", String::from_utf8_lossy(&e))
})?;
if let Some(parent) = path.parent() {
self.disk_interface.make_dirs(parent).map_err(
|e| format!("{}", e),
)?;
}
}
let rspfile = edge.get_unescaped_rspfile(&self.state.node_state);
if !rspfile.as_ref().is_empty() {
let content = edge.get_binding(&self.state.node_state, b"rspfile_content");
let rspfile_path = pathbuf_from_bytes(rspfile.into_owned()).map_err(|e| {
format!("invalid utf-8 filename: {}", String::from_utf8_lossy(&e))
})?;
self.disk_interface
.write_file(&rspfile_path, content.as_ref())
.map_err(|_| String::new())?;
}
if !self.command_runner.as_mut().unwrap().start_command(
self.state,
edge_idx,
)
{
return Err(format!(
"command '{}' failed.",
String::from_utf8_lossy(
&edge.evaluate_command(&self.state.node_state),
)
));
}
Ok(())
}
fn finish_command(
&mut self,
mut result: CommandRunnerResult,
) -> Result<CommandRunnerResult, String> {
use errno;
metric_record!("FinishCommand");
let edge_idx = result.edge;
let mut deps_nodes = Vec::new();
let (deps_type, deps_prefix) = {
let edge = self.state.edge_state.get_edge(edge_idx);
let deps_type = edge.get_binding(&self.state.node_state, b"deps");
let deps_prefix = edge.get_binding(&self.state.node_state, b"msvc_deps_prefix");
(deps_type.into_owned(), deps_prefix.into_owned())
};
if !deps_type.is_empty() {
match self.extract_deps(&mut result, deps_type.as_ref(), deps_prefix.as_ref()) {
Ok(n) => {
deps_nodes = n;
}
Err(e) => {
if result.is_success() {
if !result.output.is_empty() {
result.output.extend_from_slice(b"\n".as_ref());
}
result.output.extend_from_slice(e.as_bytes());
result.status = ExitStatus::ExitFailure;
}
}
}
}
let (start_time, end_time) = self.status.build_edge_finished(
self.state,
edge_idx,
result.is_success(),
&result.output,
);
if !result.is_success() {
self.plan.edge_finished(
self.state,
edge_idx,
EdgeResult::EdgeFailed,
);
return Ok(result);
}
let mut output_mtime = TimeStamp(0);
let restat = self.state.edge_state.get_edge(edge_idx).get_binding_bool(
&self.state
.node_state,
b"restat",
);
if !self.config.dry_run {
let edge = self.state.edge_state.get_edge(edge_idx);
let mut node_cleaned = false;
for o_node_idx in edge.outputs.iter() {
let o_node = self.state.node_state.get_node(*o_node_idx);
let path = pathbuf_from_bytes(o_node.path().to_owned()).map_err(|e| {
format!("Invalid utf-8 pathname {}", String::from_utf8_lossy(&e))
})?;
let new_mtime = self.disk_interface.stat(&path)?;
if new_mtime > output_mtime {
output_mtime = new_mtime;
}
if o_node.mtime() == new_mtime && restat {
self.plan.clean_node(&self.scan, self.state, *o_node_idx)?;
node_cleaned = true;
}
}
if node_cleaned {
let mut restat_mtime = TimeStamp(0);
for i_idx in edge.inputs[edge.non_order_only_deps_range()].iter() {
let path = pathbuf_from_bytes(
self.state.node_state.get_node(*i_idx).path().to_owned(),
).map_err(|e| {
format!("invalid utf-8 filename: {}", String::from_utf8_lossy(&e))
})?;
let input_mtime = self.disk_interface.stat(&path)?;
if input_mtime > restat_mtime {
restat_mtime = input_mtime;
}
}
let depfile = edge.get_unescaped_depfile(&self.state.node_state);
if restat_mtime.0 != 0 && deps_type.is_empty() && !depfile.is_empty() {
let path = pathbuf_from_bytes(depfile.into_owned()).map_err(|e| {
format!("invalid utf-8 filename: {}", String::from_utf8_lossy(&e))
})?;
let depfile_mtime = self.disk_interface.stat(&path)?;
if depfile_mtime > restat_mtime {
restat_mtime = depfile_mtime;
}
}
self.status.plan_has_total_edges(
self.plan.command_edge_count(),
);
output_mtime = restat_mtime;
}
}
self.plan.edge_finished(
self.state,
edge_idx,
EdgeResult::EdgeSucceeded,
);
let edge = self.state.edge_state.get_edge(edge_idx);
let rspfile = edge.get_unescaped_rspfile(&self.state.node_state);
if !rspfile.is_empty() && !KEEP_RSP {
if let Ok(path) = pathbuf_from_bytes(rspfile.into_owned()) {
let _ = self.disk_interface.remove_file(&path);
};
}
if let Some(build_log) = self.scan.build_log() {
build_log
.record_command(self.state, edge_idx, start_time, end_time, output_mtime)
.map_err(|e| {
format!("Error writing to build log: {}", errno::errno())
})?;
}
if !deps_type.is_empty() && !self.config.dry_run {
assert!(edge.outputs.len() == 1);
let out_idx = edge.outputs[0];
let out = self.state.node_state.get_node(out_idx);
let path = pathbuf_from_bytes(out.path().to_owned()).map_err(|e| {
format!("Invalid utf-8 pathname {}", String::from_utf8_lossy(&e))
})?;
let deps_mtime = self.disk_interface.stat(&path)?;
self.scan
.deps_log()
.record_deps(self.state, out_idx, deps_mtime, &deps_nodes)
.map_err(|e| format!("Error writing to deps log: {}", errno::errno()))?;
}
Ok(result)
}
pub fn cleanup(&mut self) {
if self.command_runner.is_none() {
return;
}
let command_runner = self.command_runner.as_mut().unwrap();
let active_edges = command_runner.get_active_edges();
command_runner.abort();
for edge_idx in active_edges.into_iter() {
let edge = self.state.edge_state.get_edge(edge_idx);
let depfile = edge.get_unescaped_depfile(&self.state.node_state)
.into_owned();
for out_idx in edge.outputs.iter() {
let out_node = self.state.node_state.get_node(*out_idx);
match pathbuf_from_bytes(out_node.path().to_owned()) {
Err(e) => {
error!("invalid utf-8 filename: {}", String::from_utf8_lossy(&e));
}
Ok(path) => {
match self.disk_interface.stat(&path) {
Err(e) => {
error!("{}", e);
}
Ok(new_mtime) => {
if !depfile.is_empty() || out_node.mtime() != new_mtime {
let _ = self.disk_interface.remove_file(&path);
}
}
}
}
}
}
if !depfile.is_empty() {
match pathbuf_from_bytes(depfile) {
Err(e) => {
error!("invalid utf-8 filename: {}", String::from_utf8_lossy(&e));
}
Ok(path) => {
let _ = self.disk_interface.remove_file(&path);
}
};
}
}
}
fn extract_deps(
&self,
result: &mut CommandRunnerResult,
deps_type: &[u8],
deps_prefix: &[u8],
) -> Result<Vec<NodeIndex>, String> {
if deps_type == b"msvc" {
return Ok(Vec::new());
unimplemented!{}
} else if deps_type == b"gcc" {
return Ok(Vec::new());
unimplemented!{}
} else {
fatal!("unknown deps type '{}'", String::from_utf8_lossy(deps_type));
unreachable!();
}
}
}
impl<'s, 'p, 'a, 'b, 'c> Drop for Builder<'s, 'p, 'a, 'b, 'c> {
fn drop(&mut self) {
self.cleanup();
}
}
enum BuildStatusEdgeStatus {
EdgeStarted,
EdgeFinished,
}
struct BuildStatus<'a> {
config: &'a BuildConfig,
start_time_millis: u64,
started_edges: usize,
running_edges: BTreeMap<EdgeIndex, u64>,
finished_edges: usize,
total_edges: usize,
progress_status_format: Vec<u8>,
printer: LinePrinter,
overall_rate: RefCell<RateInfo>,
current_rate: RefCell<SlidingRateInfo>,
}
impl<'a> BuildStatus<'a> {
pub fn new(config: &'a BuildConfig) -> Self {
let v = BuildStatus {
config,
start_time_millis: get_time_millis(),
started_edges: 0,
running_edges: BTreeMap::new(),
finished_edges: 0,
total_edges: 0,
progress_status_format: Vec::new(), printer: LinePrinter::new(),
overall_rate: RefCell::new(RateInfo::new()),
current_rate: RefCell::new(SlidingRateInfo::new(config.parallelism)),
};
return v;
unimplemented!{}
}
pub fn plan_has_total_edges(&mut self, total: usize) {
self.total_edges = total;
}
pub fn build_started(&mut self) {
self.overall_rate.borrow_mut().restart();
self.current_rate.borrow_mut().restart();
}
pub fn build_finished(&mut self) {
self.printer.set_console_locked(false);
self.printer.print_on_new_line(b"");
}
pub fn build_edge_started(&mut self, state: &State, edge_idx: EdgeIndex) {
let start_time = get_time_millis() - self.start_time_millis;
self.running_edges.insert(edge_idx, start_time);
self.started_edges += 1;
let edge_use_console = state.edge_state.get_edge(edge_idx).use_console();
if edge_use_console || self.printer.is_smart_terminal() {
self.print_status(state, edge_idx, BuildStatusEdgeStatus::EdgeStarted);
}
if edge_use_console {
self.printer.set_console_locked(true);
}
}
pub fn build_edge_finished(
&mut self,
state: &State,
edge_idx: EdgeIndex,
success: bool,
output: &[u8],
) -> (u64, u64) {
let now = get_time_millis();
self.finished_edges += 1;
let start_time = self.running_edges.remove(&edge_idx).unwrap();
let end_time = now - self.start_time_millis;
if state.edge_state.get_edge(edge_idx).use_console() {
self.printer.set_console_locked(false);
}
match self.config.verbosity {
BuildConfigVerbosity::QUIET => {
return (start_time, end_time);
}
_ => {}
};
return (start_time, end_time);
unimplemented!();
}
pub fn format_progress_status(
progress_status_format: &[u8],
status: BuildStatusEdgeStatus,
) -> Vec<u8> {
return Vec::new();
unimplemented!()
}
fn print_status(&self, state: &State, edge_idx: EdgeIndex, status: BuildStatusEdgeStatus) {
let force_full_command = match self.config.verbosity {
BuildConfigVerbosity::QUIET => {
return;
}
BuildConfigVerbosity::VERBOSE => true,
BuildConfigVerbosity::NORMAL => false,
};
let edge = state.edge_state.get_edge(edge_idx);
let mut desc_or_cmd = edge.get_binding(&state.node_state, b"description");
if desc_or_cmd.is_empty() || force_full_command {
desc_or_cmd = edge.get_binding(&state.node_state, b"command");
}
let mut to_print = Self::format_progress_status(&self.progress_status_format, status);
to_print.extend_from_slice(&desc_or_cmd);
let ty = if force_full_command {
LinePrinterLineType::Full
} else {
LinePrinterLineType::Elide
};
self.printer.print(&to_print, ty);
}
}
struct RateInfo {
rate: f64,
stopwatch: Stopwatch,
}
impl RateInfo {
pub fn new() -> Self {
RateInfo {
rate: -1f64,
stopwatch: Stopwatch::new(),
}
}
pub fn restart(&mut self) {
self.stopwatch.restart()
}
}
struct SlidingRateInfo {
rate: f64,
stopwatch: Stopwatch,
max_len: usize,
times: VecDeque<f64>,
last_update: isize,
}
impl SlidingRateInfo {
pub fn new(n: usize) -> Self {
SlidingRateInfo {
rate: -1.0f64,
stopwatch: Stopwatch::new(),
max_len: n,
times: VecDeque::new(),
last_update: -1,
}
}
pub fn restart(&mut self) {
self.stopwatch.restart();
}
}
use std::collections::VecDeque;
struct DryRunCommandRunner {
finished: VecDeque<EdgeIndex>,
}
impl DryRunCommandRunner {
pub fn new() -> Self {
DryRunCommandRunner { finished: VecDeque::new() }
}
}
impl CommandRunner for DryRunCommandRunner {
fn can_run_more(&self) -> bool {
true
}
fn start_command(&mut self, _: &State, edge: EdgeIndex) -> bool {
self.finished.push_back(edge);
true
}
fn wait_for_command(&mut self) -> Option<CommandRunnerResult> {
match self.finished.pop_front() {
None => None,
Some(e) => Some(CommandRunnerResult {
edge: e,
status: ExitStatus::ExitSuccess,
output: Vec::new(),
}),
}
}
fn get_active_edges(&self) -> Vec<EdgeIndex> {
Vec::new()
}
fn abort(&mut self) {
}
}
struct RealCommandRunner<'a> {
config: &'a BuildConfig,
subprocs: SubprocessSet<EdgeIndex>,
}
impl<'a> RealCommandRunner<'a> {
pub fn new(config: &'a BuildConfig) -> Self {
RealCommandRunner {
config,
subprocs: SubprocessSet::new(),
}
}
}
impl<'a> CommandRunner for RealCommandRunner<'a> {
fn can_run_more(&self) -> bool {
let subproc_number = self.subprocs.running().len() + self.subprocs.finished().len();
if subproc_number >= self.config.parallelism {
return false;
}
if self.subprocs.running().is_empty() {
return true;
}
if self.config.max_load_average <= 0.0f64 {
return true;
}
if get_load_average().unwrap_or(-0.0f64) < self.config.max_load_average {
return true;
}
return false;
}
fn start_command(&mut self, state: &State, edge_idx: EdgeIndex) -> bool {
let edge = state.edge_state.get_edge(edge_idx);
let command = edge.evaluate_command(&state.node_state);
return self.subprocs
.add(&command, edge.use_console(), edge_idx)
.is_some();
}
fn wait_for_command(&mut self) -> Option<CommandRunnerResult> {
let (mut subproc, edge_idx) = loop {
if let Some(next_finished) = self.subprocs.next_finished() {
break next_finished;
}
if self.subprocs.do_work().is_err() {
return None;
}
};
let status = subproc.finish();
let output = subproc.output().to_owned();
Some(CommandRunnerResult {
status,
output,
edge: edge_idx,
})
}
fn get_active_edges(&self) -> Vec<EdgeIndex> {
self.subprocs.iter().map(|x| x.1).collect()
}
fn abort(&mut self) {
self.subprocs.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::test::TestWithStateAndVFS;
use super::super::graph::Node;
struct PlanTestData {
plan: Plan,
}
impl Default for PlanTestData {
fn default() -> Self {
PlanTestData { plan: Plan::new() }
}
}
type PlanTest = TestWithStateAndVFS<PlanTestData>;
impl PlanTest {
pub fn new() -> Self {
Self::new_with_builtin_rule()
}
}
#[test]
fn plantest_basic() {
let mut plantest = PlanTest::new();
plantest.assert_parse(
concat!("build out: cat mid\n", "build mid: cat in\n").as_bytes(),
);
plantest.assert_with_node_mut(b"mid", Node::mark_dirty);
plantest.assert_with_node_mut(b"out", Node::mark_dirty);
let out_node_idx = plantest.assert_node_idx(b"out");
let mut state = plantest.state.borrow_mut();
let state = &mut *state;
let plan = &mut plantest.other.plan;
assert_eq!(Ok(true), plan.add_target(state, out_node_idx));
assert_eq!(true, plan.more_to_do());
let edge_idx = plan.find_work().unwrap();
{
let edge = state.edge_state.get_edge(edge_idx);
let input0 = edge.inputs[0];
assert_eq!(b"in", state.node_state.get_node(input0).path());
let output0 = edge.outputs[0];
assert_eq!(b"mid", state.node_state.get_node(output0).path());
}
assert_eq!(None, plan.find_work());
plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
let edge_idx = plan.find_work().unwrap();
{
let edge = state.edge_state.get_edge(edge_idx);
let input0 = edge.inputs[0];
assert_eq!(b"mid", state.node_state.get_node(input0).path());
let output0 = edge.outputs[0];
assert_eq!(b"out", state.node_state.get_node(output0).path());
}
plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
assert_eq!(false, plan.more_to_do());
assert_eq!(None, plan.find_work());
}
#[test]
fn plantest_double_output_direct() {
let mut plantest = PlanTest::new();
plantest.assert_parse(
concat!("build out: cat mid1 mid2\n", "build mid1 mid2: cat in\n").as_bytes(),
);
plantest.assert_with_node_mut(b"mid1", Node::mark_dirty);
plantest.assert_with_node_mut(b"mid2", Node::mark_dirty);
plantest.assert_with_node_mut(b"out", Node::mark_dirty);
let out_node_idx = plantest.assert_node_idx(b"out");
let mut state = plantest.state.borrow_mut();
let state = &mut *state;
let plan = &mut plantest.other.plan;
assert_eq!(Ok(true), plan.add_target(state, out_node_idx));
assert_eq!(true, plan.more_to_do());
let edge_idx = plan.find_work().unwrap(); plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
let edge_idx = plan.find_work().unwrap(); plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
assert_eq!(None, plan.find_work()); }
#[test]
fn plantest_double_output_indirect() {
let mut plantest = PlanTest::new();
plantest.assert_parse(
concat!(
"build out: cat b1 b2\n",
"build b1: cat a1\n",
"build b2: cat a2\n",
"build a1 a2: cat in\n"
).as_bytes(),
);
plantest.assert_with_node_mut(b"a1", Node::mark_dirty);
plantest.assert_with_node_mut(b"a2", Node::mark_dirty);
plantest.assert_with_node_mut(b"b1", Node::mark_dirty);
plantest.assert_with_node_mut(b"b2", Node::mark_dirty);
plantest.assert_with_node_mut(b"out", Node::mark_dirty);
let out_node_idx = plantest.assert_node_idx(b"out");
let mut state = plantest.state.borrow_mut();
let state = &mut *state;
let plan = &mut plantest.other.plan;
assert_eq!(Ok(true), plan.add_target(state, out_node_idx));
assert_eq!(true, plan.more_to_do());
let edge_idx = plan.find_work().unwrap(); plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
let edge_idx = plan.find_work().unwrap(); plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
let edge_idx = plan.find_work().unwrap(); plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
let edge_idx = plan.find_work().unwrap(); plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
assert_eq!(None, plan.find_work()); }
#[test]
fn plantest_double_dependent() {
let mut plantest = PlanTest::new();
plantest.assert_parse(
concat!(
"build out: cat a1 a2\n",
"build a1: cat mid\n",
"build a2: cat mid\n",
"build mid: cat in\n"
).as_bytes(),
);
plantest.assert_with_node_mut(b"mid", Node::mark_dirty);
plantest.assert_with_node_mut(b"a1", Node::mark_dirty);
plantest.assert_with_node_mut(b"a2", Node::mark_dirty);
plantest.assert_with_node_mut(b"out", Node::mark_dirty);
let out_node_idx = plantest.assert_node_idx(b"out");
let mut state = plantest.state.borrow_mut();
let state = &mut *state;
let plan = &mut plantest.other.plan;
assert_eq!(Ok(true), plan.add_target(state, out_node_idx));
assert_eq!(true, plan.more_to_do());
let edge_idx = plan.find_work().unwrap(); plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
let edge_idx = plan.find_work().unwrap(); plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
let edge_idx = plan.find_work().unwrap(); plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
let edge_idx = plan.find_work().unwrap(); plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
assert_eq!(None, plan.find_work()); }
fn test_pool_with_depth_one_helper(plantest: &mut PlanTest, test_case: &[u8]) {
plantest.assert_parse(test_case);
plantest.assert_with_node_mut(b"out1", Node::mark_dirty);
plantest.assert_with_node_mut(b"out2", Node::mark_dirty);
let out1_node_idx = plantest.assert_node_idx(b"out1");
let out2_node_idx = plantest.assert_node_idx(b"out2");
let mut state = plantest.state.borrow_mut();
let state = &mut *state;
let plan = &mut plantest.other.plan;
assert_eq!(Ok(true), plan.add_target(state, out1_node_idx));
assert_eq!(Ok(true), plan.add_target(state, out2_node_idx));
assert_eq!(true, plan.more_to_do());
let edge_idx = plan.find_work().unwrap();
{
let edge = state.edge_state.get_edge(edge_idx);
let edge_in0_idx = edge.inputs.get(0).cloned().unwrap();
let edge_in0_node = state.node_state.get_node(edge_in0_idx);
assert_eq!(b"in".as_ref(), edge_in0_node.path());
let edge_out0_idx = edge.outputs.get(0).cloned().unwrap();
let edge_out0_node = state.node_state.get_node(edge_out0_idx);
assert_eq!(b"out1".as_ref(), edge_out0_node.path());
}
assert!(plan.find_work().is_none());
plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
let edge_idx = plan.find_work().unwrap();
{
let edge = state.edge_state.get_edge(edge_idx);
let edge_in0_idx = edge.inputs.get(0).cloned().unwrap();
let edge_in0_node = state.node_state.get_node(edge_in0_idx);
assert_eq!(b"in".as_ref(), edge_in0_node.path());
let edge_out0_idx = edge.outputs.get(0).cloned().unwrap();
let edge_out0_node = state.node_state.get_node(edge_out0_idx);
assert_eq!(b"out2".as_ref(), edge_out0_node.path());
}
assert!(plan.find_work().is_none());
plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
assert_eq!(false, plan.more_to_do());
assert_eq!(None, plan.find_work()); }
#[test]
fn plantest_pool_with_depth_one() {
let mut plantest = PlanTest::new();
test_pool_with_depth_one_helper(
&mut plantest,
concat!(
"pool foobar\n",
" depth = 1\n",
"rule poolcat\n",
" command = cat $in > $out\n",
" pool = foobar\n",
"build out1: poolcat in\n",
"build out2: poolcat in\n"
).as_bytes(),
);
}
#[test]
fn plantest_console_pool() {
let mut plantest = PlanTest::new();
test_pool_with_depth_one_helper(
&mut plantest,
concat!(
"rule poolcat\n",
" command = cat $in > $out\n",
" pool = console\n",
"build out1: poolcat in\n",
"build out2: poolcat in\n",
).as_bytes(),
);
}
fn find_work_sorted_helper(
plan: &mut Plan,
state: &State,
count: usize,
) -> VecDeque<EdgeIndex> {
let mut result = (0..count)
.map(|i| {
assert!(plan.more_to_do());
plan.find_work().unwrap()
})
.collect::<Vec<_>>();
assert!(plan.find_work().is_none());
result.sort_by_key(|e| {
state
.node_state
.get_node(state.edge_state.get_edge(*e).outputs[0])
.path()
});
result.into_iter().collect()
}
#[test]
fn plantest_pools_with_depth_two() {
let mut plantest = PlanTest::new();
plantest.assert_parse(
concat!(
"pool foobar\n",
" depth = 2\n",
"pool bazbin\n",
" depth = 2\n",
"rule foocat\n",
" command = cat $in > $out\n",
" pool = foobar\n",
"rule bazcat\n",
" command = cat $in > $out\n",
" pool = bazbin\n",
"build out1: foocat in\n",
"build out2: foocat in\n",
"build out3: foocat in\n",
"build outb1: bazcat in\n",
"build outb2: bazcat in\n",
"build outb3: bazcat in\n",
" pool =\n",
"build allTheThings: cat out1 out2 out3 outb1 outb2 outb3\n"
).as_bytes(),
);
[
b"out1".as_ref(),
b"out2".as_ref(),
b"out3".as_ref(),
b"outb1".as_ref(),
b"outb2".as_ref(),
b"outb3".as_ref(),
b"allTheThings".as_ref(),
].as_ref()
.iter()
.for_each(|path| {
plantest.assert_with_node_mut(path, Node::mark_dirty);
});
let mut state = plantest.state.borrow_mut();
let state = &mut *state;
let plan = &mut plantest.other.plan;
let all_the_things_node = state.node_state.lookup_node(b"allTheThings").unwrap();
assert_eq!(Ok(true), plan.add_target(state, all_the_things_node));
let mut edges = find_work_sorted_helper(plan, state, 5);
{
let edge_idx = edges[0];
let edge = state.edge_state.get_edge(edge_idx);
assert_eq!(
b"in".as_ref(),
state.node_state.get_node(edge.inputs[0]).path()
);
assert_eq!(
b"out1".as_ref(),
state.node_state.get_node(edge.outputs[0]).path()
)
}
{
let edge_idx = edges[1];
let edge = state.edge_state.get_edge(edge_idx);
assert_eq!(
b"in".as_ref(),
state.node_state.get_node(edge.inputs[0]).path()
);
assert_eq!(
b"out2".as_ref(),
state.node_state.get_node(edge.outputs[0]).path()
)
}
{
let edge_idx = edges[2];
let edge = state.edge_state.get_edge(edge_idx);
assert_eq!(
b"in".as_ref(),
state.node_state.get_node(edge.inputs[0]).path()
);
assert_eq!(
b"outb1".as_ref(),
state.node_state.get_node(edge.outputs[0]).path()
)
}
{
let edge_idx = edges[3];
let edge = state.edge_state.get_edge(edge_idx);
assert_eq!(
b"in".as_ref(),
state.node_state.get_node(edge.inputs[0]).path()
);
assert_eq!(
b"outb2".as_ref(),
state.node_state.get_node(edge.outputs[0]).path()
)
}
{
let edge_idx = edges[4];
let edge = state.edge_state.get_edge(edge_idx);
assert_eq!(
b"in".as_ref(),
state.node_state.get_node(edge.inputs[0]).path()
);
assert_eq!(
b"outb3".as_ref(),
state.node_state.get_node(edge.outputs[0]).path()
)
}
plan.edge_finished(state, edges.pop_front().unwrap(), EdgeResult::EdgeSucceeded);
let out3_idx = plan.find_work().unwrap();
{
let edge = state.edge_state.get_edge(out3_idx);
assert_eq!(
b"in".as_ref(),
state.node_state.get_node(edge.inputs[0]).path()
);
assert_eq!(
b"out3".as_ref(),
state.node_state.get_node(edge.outputs[0]).path()
)
}
assert!(plan.find_work().is_none());
plan.edge_finished(state, out3_idx, EdgeResult::EdgeSucceeded);
assert!(plan.find_work().is_none());
edges.into_iter().for_each(|edge_idx| {
plan.edge_finished(state, edge_idx, EdgeResult::EdgeSucceeded);
});
let last_idx = plan.find_work().unwrap();
{
let edge = state.edge_state.get_edge(last_idx);
assert_eq!(
b"allTheThings".as_ref(),
state.node_state.get_node(edge.outputs[0]).path()
)
}
plan.edge_finished(state, last_idx, EdgeResult::EdgeSucceeded);
assert_eq!(false, plan.more_to_do());
assert_eq!(None, plan.find_work()); }
}