use std::rc::Rc;
use std::cell::{Cell, RefCell};
use std::borrow::Cow;
use std::ops::Range;
use super::state::{Pool, State, NodeState};
use super::build_log::BuildLog;
use super::deps_log::DepsLog;
use super::disk_interface::DiskInterface;
use super::state::{PHONY_RULE, CONSOLE_POOL};
use super::eval_env::{Env, Rule, BindingEnv};
use super::timestamp::TimeStamp;
use super::utils::WINDOWS_PATH;
use super::utils::{decanonicalize_path, pathbuf_from_bytes};
use super::utils::{ExtendFromEscapedSlice, RangeContains};
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)]
pub struct NodeIndex(pub(crate) usize);
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)]
pub struct EdgeIndex(pub(crate) usize);
pub struct Node {
path: Vec<u8>,
slash_bits: u64,
in_edge: Option<EdgeIndex>,
out_edges: Vec<EdgeIndex>,
id: isize,
mtime: TimeStamp,
dirty: bool,
}
impl Node {
pub fn new(path: &[u8], slash_bits: u64) -> Self {
Node {
path: path.to_owned(),
slash_bits,
in_edge: None,
out_edges: Vec::new(),
id: -1,
mtime: TimeStamp(-1),
dirty: false,
}
}
pub fn id(&self) -> isize {
self.id
}
pub fn set_id(&mut self, id: isize) {
self.id = id;
}
pub fn path(&self) -> &[u8] {
&self.path
}
pub fn slash_bits(&self) -> u64 {
self.slash_bits
}
pub fn mtime(&self) -> TimeStamp {
self.mtime
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn set_dirty(&mut self, dirty: bool) {
self.dirty = dirty;
}
pub fn mark_dirty(&mut self) {
self.dirty = true;
}
pub fn reset_state(&mut self) {
self.mtime = TimeStamp(-1);
self.dirty = false;
}
pub fn mark_missing(&mut self) {
self.mtime = TimeStamp(0);
}
pub fn exists(&self) -> bool {
self.mtime.0 != 0
}
pub fn status_known(&self) -> bool {
self.mtime.0 != -1
}
pub fn in_edge(&self) -> Option<EdgeIndex> {
self.in_edge
}
pub fn set_in_edge(&mut self, edge: Option<EdgeIndex>) {
self.in_edge = edge;
}
pub fn out_edges(&self) -> &[EdgeIndex] {
&self.out_edges
}
pub fn add_out_edge(&mut self, edge: EdgeIndex) {
self.out_edges.push(edge);
}
pub fn path_decanonicalized(&self) -> Vec<u8> {
decanonicalize_path(&self.path, self.slash_bits)
}
pub fn stat(&mut self, disk_interface: &DiskInterface) -> Result<(), String> {
self.mtime = TimeStamp(-1);
let pathbuf = pathbuf_from_bytes(self.path.clone()).map_err(|e| {
format!("invalid utf-8 pathname: {}", String::from_utf8_lossy(&e))
})?;
self.mtime = disk_interface.stat(&pathbuf)?;
Ok(())
}
pub fn stat_if_necessary(&mut self, disk_interface: &DiskInterface) -> Result<(), String> {
if self.status_known() {
return Ok(());
}
self.stat(disk_interface)
}
}
#[derive(Clone, Copy)]
pub enum EdgeVisitMark {
VisitNone,
VisitInStack,
VisitDone,
}
pub struct Edge {
rule: Rc<Rule>,
pub pool: Rc<RefCell<Pool>>,
pub inputs: Vec<NodeIndex>,
pub outputs: Vec<NodeIndex>,
pub env: Rc<RefCell<BindingEnv>>,
pub mark: EdgeVisitMark,
pub outputs_ready: bool,
pub deps_missing: bool,
pub implicit_deps: usize,
pub order_only_deps: usize,
pub implicit_outs: usize,
}
impl Edge {
pub fn new(rule: Rc<Rule>, pool: Rc<RefCell<Pool>>, env: Rc<RefCell<BindingEnv>>) -> Self {
Edge {
rule,
pool,
inputs: Vec::new(),
outputs: Vec::new(),
env,
mark: EdgeVisitMark::VisitNone,
outputs_ready: false,
deps_missing: false,
implicit_deps: 0,
order_only_deps: 0,
implicit_outs: 0,
}
}
pub fn rule(&self) -> &Rc<Rule> {
&self.rule
}
pub fn pool(&self) -> &Rc<RefCell<Pool>> {
&self.pool
}
pub fn weight(&self) -> usize {
1
}
pub fn outputs_ready(&self) -> bool {
self.outputs_ready
}
pub fn explicit_deps_range(&self) -> Range<usize> {
0..(self.inputs.len() - self.implicit_deps - self.order_only_deps)
}
pub fn implicit_deps_range(&self) -> Range<usize> {
(self.inputs.len() - self.implicit_deps - self.order_only_deps)..
(self.inputs.len() - self.order_only_deps)
}
pub fn non_order_only_deps_range(&self) -> Range<usize> {
0..(self.inputs.len() - self.order_only_deps)
}
pub fn order_only_deps_range(&self) -> Range<usize> {
(self.inputs.len() - self.order_only_deps)..(self.inputs.len())
}
pub fn explicit_outs_range(&self) -> Range<usize> {
0..(self.outputs.len() - self.implicit_outs)
}
pub fn implicit_outs_range(&self) -> Range<usize> {
(self.outputs.len() - self.implicit_outs)..(self.outputs.len())
}
pub fn get_binding(&self, node_state: &NodeState, key: &[u8]) -> Cow<[u8]> {
let env = EdgeEnv::new(self, node_state, EdgeEnvEscapeKind::ShellEscape);
env.lookup_variable(key).into_owned().into()
}
pub fn get_binding_bool(&self, node_state: &NodeState, key: &[u8]) -> bool {
!self.get_binding(node_state, key).is_empty()
}
pub fn get_unescaped_depfile(&self, node_state: &NodeState) -> Cow<[u8]> {
let env = EdgeEnv::new(self, node_state, EdgeEnvEscapeKind::DoNotEscape);
env.lookup_variable(b"depfile").into_owned().into()
}
pub fn get_unescaped_rspfile(&self, node_state: &NodeState) -> Cow<[u8]> {
let env = EdgeEnv::new(self, node_state, EdgeEnvEscapeKind::DoNotEscape);
env.lookup_variable(b"rspfile").into_owned().into()
}
pub fn is_phony(&self) -> bool {
self.rule.as_ref() as *const Rule == PHONY_RULE.with(|x| x.as_ref() as *const Rule)
}
pub fn use_console(&self) -> bool {
&*self.pool().borrow() as *const Pool == CONSOLE_POOL.with(|x| &*x.borrow() as *const Pool)
}
pub fn evaluate_command(&self, node_state: &NodeState) -> Vec<u8> {
self.evaluate_command_with_rsp_file(node_state, false)
}
pub fn evaluate_command_with_rsp_file(
&self,
node_state: &NodeState,
incl_rsp_file: bool,
) -> Vec<u8> {
let mut command = self.get_binding(node_state, b"command").into_owned();
if incl_rsp_file {
let rspfile_content = self.get_binding(node_state, b"rspfile_content");
if !rspfile_content.is_empty() {
command.extend_from_slice(b";rspfile=");
command.extend_from_slice(rspfile_content.as_ref());
}
}
command
}
pub fn maybe_phonycycle_diagnostic(&self) -> bool {
self.is_phony() && self.outputs.len() == 1 && self.implicit_outs == 0 &&
self.implicit_deps == 0
}
pub fn all_inputs_ready(&self, state: &State) -> bool {
for input_idx in self.inputs.iter() {
let input = state.node_state.get_node(*input_idx);
if let Some(in_edge) = input.in_edge() {
if !state.edge_state.get_edge(in_edge).outputs_ready() {
return false;
}
}
}
return true;
}
pub fn dump(&self) {
unimplemented!();
}
}
struct ImplicitDepLoader<'b, 'c> {
disk_interface: &'c DiskInterface,
deps_log: &'b DepsLog,
}
impl<'b, 'c> ImplicitDepLoader<'b, 'c> {
pub fn new(deps_log: &'b DepsLog, disk_interface: &'c DiskInterface) -> Self {
ImplicitDepLoader {
deps_log,
disk_interface,
}
}
pub fn deps_log(&self) -> &'b DepsLog {
self.deps_log
}
pub fn load_deps(&self, state: &State, edge_idx: EdgeIndex) -> Result<bool, String> {
let edge = state.edge_state.get_edge(edge_idx);
let deps_type = edge.get_binding(&state.node_state, b"deps");
if !deps_type.is_empty() {
return self.load_deps_from_log(state, edge_idx);
}
let depfile = edge.get_unescaped_depfile(&state.node_state);
if !depfile.is_empty() {
return self.load_dep_file(state, edge_idx, &depfile);
}
return Ok(true);
}
pub fn load_dep_file(
&self,
state: &State,
edge_idx: EdgeIndex,
path: &[u8],
) -> Result<bool, String> {
unimplemented!{}
}
pub fn load_deps_from_log(&self, state: &State, edge_idx: EdgeIndex) -> Result<bool, String> {
return Ok(false);
unimplemented!{}
}
}
pub struct DependencyScan<'s, 'a, 'b, 'c>
where
's: 'a,
{
build_log: Option<&'a BuildLog<'s>>,
deps_log: &'b DepsLog,
disk_interface: &'c DiskInterface,
dep_loader: ImplicitDepLoader<'b, 'c>,
}
impl<'s, 'a, 'b, 'c> DependencyScan<'s, 'a, 'b, 'c>
where
's: 'a,
{
pub fn new(
build_log: &'a BuildLog<'s>,
deps_log: &'b DepsLog,
disk_interface: &'c DiskInterface,
) -> Self {
DependencyScan {
build_log: Some(build_log),
deps_log,
disk_interface,
dep_loader: ImplicitDepLoader::new(deps_log, disk_interface),
}
}
pub fn build_log(&self) -> Option<&'a BuildLog<'s>> {
self.build_log.clone()
}
pub fn deps_log(&self) -> &'b DepsLog {
self.dep_loader.deps_log()
}
pub fn recompute_dirty(&self, state: &mut State, node_idx: NodeIndex) -> Result<(), String> {
let mut stack = Vec::new();
return self.recompute_dirty_inner(state, node_idx, &mut stack);
}
fn recompute_dirty_inner(
&self,
state: &mut State,
node_idx: NodeIndex,
stack: &mut Vec<NodeIndex>,
) -> Result<(), String> {
match state.node_state.get_node(node_idx).in_edge.as_ref() {
None => {
let node = state.node_state.get_node_mut(node_idx);
if node.status_known() {
return Ok(());
};
node.stat_if_necessary(self.disk_interface)?;
if !node.exists() {
explain!(
"{} has no in-edge and is missing",
String::from_utf8_lossy(node.path())
);
}
let dirty = !node.exists();
node.set_dirty(dirty);
Ok(())
}
Some(&edge_idx) => {
match state.edge_state.get_edge(edge_idx).mark {
EdgeVisitMark::VisitDone => {
return Ok(());
}
_ => {}
};
self.verify_dag(state, node_idx, stack)?;
let mut dirty = false;
let mut outputs_ready = true;
let mut deps_missing = false;
state.edge_state.get_edge_mut(edge_idx).mark = EdgeVisitMark::VisitInStack;
stack.push(node_idx);
for o_idx in state.edge_state.get_edge(edge_idx).outputs.iter().cloned() {
state.node_state.get_node_mut(o_idx).stat_if_necessary(
self.disk_interface,
)?;
}
if !self.dep_loader.load_deps(state, edge_idx)? {
dirty = true;
deps_missing = true;
}
let mut most_recent_input = None;
{
let (order_only_range, inputs) = {
let edge = state.edge_state.get_edge(edge_idx);
(edge.order_only_deps_range(), edge.inputs.clone())
};
for (i, i_idx) in inputs.into_iter().enumerate() {
self.recompute_dirty_inner(state, i_idx, stack)?;
if let Some(in_edge) = state.node_state.get_node(i_idx).in_edge() {
if !state.edge_state.get_edge(in_edge).outputs_ready {
outputs_ready = false;
}
}
if !order_only_range.contains_stable(i) {
let i_node = state.node_state.get_node(i_idx);
if i_node.is_dirty() {
explain!("{} is dirty", String::from_utf8_lossy(i_node.path()));
dirty = true;
} else {
if most_recent_input
.as_ref()
.map(|&prev_idx| {
let prev_node = state.node_state.get_node(prev_idx);
i_node.mtime() > prev_node.mtime()
})
.unwrap_or(true)
{
most_recent_input = Some(i_idx);
}
}
}
}
}
if !dirty {
dirty = self.recompute_outputs_dirty(
state,
edge_idx,
most_recent_input,
)?;
}
if dirty {
let edge = state.edge_state.get_edge(edge_idx);
for o_idx in edge.outputs.iter().cloned() {
state.node_state.get_node_mut(o_idx).mark_dirty();
}
if !(edge.is_phony() && edge.inputs.is_empty()) {
outputs_ready = false;
}
}
let edge = state.edge_state.get_edge_mut(edge_idx);
edge.deps_missing = deps_missing;
edge.outputs_ready = outputs_ready;
edge.mark = EdgeVisitMark::VisitDone;
debug_assert!(stack.last() == Some(&node_idx));
stack.pop();
Ok(())
}
}
}
fn verify_dag(
&self,
state: &State,
node_idx: NodeIndex,
stack: &mut Vec<NodeIndex>,
) -> Result<(), String> {
let edge_idx = state.node_state.get_node(node_idx).in_edge().unwrap();
match state.edge_state.get_edge(edge_idx).mark {
EdgeVisitMark::VisitInStack => {}
_ => {
return Ok(());
}
};
let mut start = 0;
while start < stack.len() {
let item = stack[start];
if state.node_state.get_node(item).in_edge() == Some(edge_idx) {
break;
}
start += 1;
}
assert!(start < stack.len());
stack[start] = node_idx;
let mut err = "dependency cycle: ".to_owned();
for iter_idx in stack[start..].iter() {
err += String::from_utf8_lossy(state.node_state.get_node(*iter_idx).path()).as_ref();
err += " -> ";
}
err += String::from_utf8_lossy(state.node_state.get_node(node_idx).path()).as_ref();
if start + 1 == stack.len() &&
state
.edge_state
.get_edge(edge_idx)
.maybe_phonycycle_diagnostic()
{
err += " [-w phonycycle=err]";
}
return Err(err);
}
fn recompute_outputs_dirty(
&self,
state: &State,
edge_idx: EdgeIndex,
most_recent_input: Option<NodeIndex>,
) -> Result<bool, String> {
let edge = state.edge_state.get_edge(edge_idx);
let command = edge.evaluate_command_with_rsp_file(&state.node_state, true);
for output in edge.outputs.iter() {
if self.recompute_output_dirty(state, edge_idx, most_recent_input, &command, *output) {
return Ok(true);
}
}
return Ok(false);
}
fn recompute_output_dirty(
&self,
state: &State,
edge_idx: EdgeIndex,
most_recent_input: Option<NodeIndex>,
command: &[u8],
output_node_idx: NodeIndex,
) -> bool {
let edge = state.edge_state.get_edge(edge_idx);
let output = state.node_state.get_node(output_node_idx);
let mut entry = None;
if edge.is_phony() {
if edge.inputs.is_empty() && !output.exists() {
explain!(
"output {} of phony edge with no inputs doesn't exist",
String::from_utf8_lossy(output.path())
);
return true;
}
return false;
}
if !output.exists() {
explain!(
"output {} doesn't exist",
String::from_utf8_lossy(output.path())
);
return true;
}
if let Some(ref most_recent_input_idx) = most_recent_input {
let most_recent_input = state.node_state.get_node(*most_recent_input_idx);
if output.mtime() < most_recent_input.mtime() {
let mut output_mtime = output.mtime();
let mut used_restat = false;
if edge.get_binding_bool(&state.node_state, b"restat") {
if let Some(build_log) = self.build_log() {
if entry.is_none() {
entry = build_log.lookup_by_output(output.path());
}
if let Some(found_entry) = entry {
output_mtime = found_entry.mtime;
used_restat = true;
}
}
}
if output_mtime < most_recent_input.mtime() {
explain!(
"{}output {} older than most recent input {} ({} vs {})",
if used_restat { "restat of " } else { "" },
String::from_utf8_lossy(output.path()),
String::from_utf8_lossy(most_recent_input.path()),
output_mtime,
most_recent_input.mtime
);
return true;
}
}
}
if let Some(build_log) = self.build_log() {
let generator = edge.get_binding_bool(&state.node_state, b"generator");
if entry.is_none() {
entry = build_log.lookup_by_output(output.path());
}
if let Some(found_entry) = entry {
unimplemented!()
}
if entry.is_none() && !generator {
explain!(
"command line not found in log for {}",
String::from_utf8_lossy(output.path())
);
return true;
}
}
return false;
}
}
#[derive(Clone, Copy)]
enum EdgeEnvEscapeKind {
ShellEscape,
DoNotEscape,
}
struct EdgeEnv<'a, 'b> {
lookups: RefCell<Vec<Vec<u8>>>,
edge: &'a Edge,
node_state: &'b NodeState,
escape_in_out: EdgeEnvEscapeKind,
recursive: Cell<bool>,
}
impl<'a, 'b> EdgeEnv<'a, 'b> {
pub fn new(edge: &'a Edge, node_state: &'b NodeState, escape: EdgeEnvEscapeKind) -> Self {
EdgeEnv {
lookups: RefCell::new(Vec::new()),
edge,
node_state,
escape_in_out: escape,
recursive: Cell::new(false),
}
}
pub fn make_path_list(
node_state: &NodeState,
nodes: &[NodeIndex],
sep: u8,
escape_in_out: EdgeEnvEscapeKind,
) -> Vec<u8> {
let mut result = Vec::new();
for node_idx in nodes {
if !result.is_empty() {
result.push(sep);
};
let node = node_state.get_node(*node_idx);
let path = node.path_decanonicalized();
match escape_in_out {
EdgeEnvEscapeKind::ShellEscape => {
if WINDOWS_PATH {
result.extend_from_win32_escaped_slice(&path);
} else {
result.extend_from_shell_escaped_slice(&path);
}
}
EdgeEnvEscapeKind::DoNotEscape => {
result.extend_from_slice(&path);
}
}
}
result
}
}
impl<'a, 'b> Env for EdgeEnv<'a, 'b> {
fn lookup_variable(&self, var: &[u8]) -> Cow<[u8]> {
if var == b"in".as_ref() || var == b"in_newline".as_ref() {
let sep = if var == b"in".as_ref() { b' ' } else { b'\n' };
let explicit_deps_range = self.edge.explicit_deps_range();
return Cow::Owned(EdgeEnv::make_path_list(
self.node_state,
&self.edge.inputs[explicit_deps_range],
sep,
self.escape_in_out,
));
} else if var == b"out".as_ref() {
let explicit_outs_range = self.edge.explicit_outs_range();
return Cow::Owned(EdgeEnv::make_path_list(
self.node_state,
&self.edge.outputs[explicit_outs_range],
b' ',
self.escape_in_out,
));
}
if self.recursive.get() {
let lookups = self.lookups.borrow();
let mut lookups_iter = lookups.iter().skip_while(|v| var != v as &[u8]).peekable();
if lookups_iter.peek().is_some() {
let mut cycle = Vec::new();
for it in lookups_iter {
cycle.extend_from_slice(&it);
cycle.extend_from_slice(b" -> ");
}
cycle.extend_from_slice(var);
fatal!(
"cycle in rule variables: {}",
String::from_utf8_lossy(&cycle)
)
}
}
let eval = self.edge.rule.get_binding(var);
if self.recursive.get() && eval.is_some() {
self.lookups.borrow_mut().push(var.to_owned());
}
self.recursive.set(true);
return Cow::Owned(
self.edge
.env
.borrow()
.lookup_with_fallback(var, eval, self)
.into_owned(),
);
}
}