turtle-build 0.4.6

Ninja-compatible build system for high-level programming languages
use super::context::Context;
use crate::{
    error::ApplicationError,
    hash_type::HashType,
    ir::{Build, Rule},
};
use std::{
    collections::hash_map::DefaultHasher,
    hash::{Hash, Hasher},
};

const BUFFER_CAPACITY: usize = 2 << 10;

pub async fn calculate_timestamp_hash(
    context: &Context,
    build: &Build,
    file_inputs: &[&str],
    phony_inputs: &[&str],
) -> Result<u64, ApplicationError> {
    if let Some(hash) = calculate_phony_hash(build, file_inputs, phony_inputs) {
        return Ok(hash);
    }

    let mut hasher = DefaultHasher::new();

    hash_command(build, &mut hasher);

    for input in file_inputs {
        context
            .application()
            .file_system()
            .metadata(input.as_ref())
            .await?
            .modified_time()
            .hash(&mut hasher);
    }

    for &input in phony_inputs {
        get_build_hash(context, HashType::Timestamp, input)?.hash(&mut hasher);
    }

    Ok(hasher.finish())
}

pub async fn calculate_content_hash(
    context: &Context,
    build: &Build,
    file_inputs: &[&str],
    phony_inputs: &[&str],
) -> Result<u64, ApplicationError> {
    if let Some(hash) = calculate_phony_hash(build, file_inputs, phony_inputs) {
        return Ok(hash);
    }

    let mut hasher = DefaultHasher::new();

    hash_command(build, &mut hasher);

    let mut buffer = Vec::with_capacity(BUFFER_CAPACITY);

    for input in file_inputs {
        context
            .application()
            .file_system()
            .read_file(input.as_ref(), &mut buffer)
            .await?;
        buffer.hash(&mut hasher);
        buffer.clear();
    }

    for &input in phony_inputs {
        get_build_hash(context, HashType::Content, input)?.hash(&mut hasher);
    }

    Ok(hasher.finish())
}

fn get_build_hash(
    context: &Context,
    r#type: HashType,
    input: &str,
) -> Result<u64, ApplicationError> {
    context
        .application()
        .database()
        .get_hash(
            r#type,
            context
                .configuration()
                .outputs()
                .get(input)
                .ok_or_else(|| ApplicationError::InputNotFound(input.into()))?
                .id(),
        )?
        .ok_or_else(|| ApplicationError::InputNotBuilt(input.into()))
}

fn calculate_phony_hash(build: &Build, file_inputs: &[&str], phony_inputs: &[&str]) -> Option<u64> {
    if build.rule().is_none() && file_inputs.is_empty() && phony_inputs.is_empty() {
        Some(rand::random())
    } else {
        None
    }
}

fn hash_command(build: &Build, hasher: &mut impl Hasher) {
    build.rule().map(Rule::command).hash(hasher);
}