nixpacks 1.1.1

Generate an OCI compliant image based off app source
use super::Provider;
use crate::nixpacks::{
    app::App,
    environment::Environment,
    nix::pkg::Pkg,
    plan::{
        phase::{Phase, StartPhase},
        BuildPlan,
    },
};
use anyhow::Result;
use serde::Deserialize;
use std::collections::BTreeMap;
use std::env::consts::ARCH;

const STACK_CACHE_DIR: &str = "/root/.stack";
const STACK_WORK_CACHE_DIR: &str = ".stack-work";

pub struct HaskellStackProvider {}

impl Provider for HaskellStackProvider {
    fn name(&self) -> &str {
        "haskell"
    }

    fn detect(&self, app: &App, _env: &Environment) -> Result<bool> {
        Ok(app.includes_file("package.yaml") && app.has_match("**/*.hs"))
    }

    fn get_build_plan(&self, app: &App, _env: &Environment) -> Result<Option<BuildPlan>> {
        let mut setup = Phase::setup(Some(vec![Pkg::new("stack")]));
        setup.add_apt_pkgs(vec![
            "libgmp-dev".to_string(),
            "gcc".to_string(),
            "binutils".to_string(),
            "make".to_string(),
            "zlib1g-dev".to_string(),
        ]);
        if ARCH == "aarch64" {
            setup.add_apt_pkgs(vec![
                "libnuma1".to_string(),
                "libnuma-dev".to_string(),
                "libtinfo-dev".to_string(),
                "libtinfo5".to_string(),
                "libc6-dev".to_string(),
                "libtinfo6".to_string(),
                "llvm-11".to_string(),
                "clang".to_string(),
                "ninja-build".to_string(),
                "zlib1g-dev".to_string(),
            ]);
        }

        let mut install = Phase::install(Some("stack setup".to_string()));
        install.add_cache_directory(STACK_CACHE_DIR.to_string());

        let mut build = Phase::build(Some("stack install".to_string()));
        build.add_cache_directory(STACK_CACHE_DIR.to_string());
        build.add_cache_directory(STACK_WORK_CACHE_DIR.to_string());

        let package: HaskellStackPackageYaml = app.read_yaml("package.yaml")?;
        let exe_names: Vec<String> = package.executables.keys().cloned().collect();

        let name = exe_names
            .get(0)
            .ok_or_else(|| anyhow::anyhow!("Failed to get executable name"))?;

        let start = StartPhase::new(format!("/root/.local/bin/{name}"));

        let plan = BuildPlan::new(&vec![setup, install, build], Some(start));

        Ok(Some(plan))
    }
}

#[derive(Deserialize)]
#[allow(clippy::zero_sized_map_values)]
struct HaskellStackPackageYaml {
    pub executables: BTreeMap<String, HaskellStackExecutableDefinition>,
}

#[derive(Deserialize)]
struct HaskellStackExecutableDefinition {}