pinoc 0.3.6

A CLI tool for setting up pinocchio program project
use super::artifact::{check_built_artifact, read_manifest, record_build, Rebuild};
use crate::config::{self, Arch};
use crate::idl::{generate_idl, Generator};
use anyhow::{Context, Result};
use std::process::Command;

/// Splits repeated and comma/space-separated `--features` values into individual features,
/// the way cargo reads them.
pub fn split_features(features: &[String]) -> Vec<String> {
    features
        .iter()
        .flat_map(|f| f.split(|c: char| c == ',' || c.is_whitespace()))
        .filter(|f| !f.is_empty())
        .map(str::to_string)
        .collect()
}

pub fn features_arg(features: &[String]) -> Option<String> {
    let list = split_features(features);
    (!list.is_empty()).then(|| list.join(","))
}

/// `--arch` for `cargo build-sbf`: the flag, else `[build].arch` in `Pinoc.toml`.
/// With neither, the flag is not passed and the toolchain's default applies.
pub fn resolve_arch(arch: Option<Arch>) -> Result<Option<Arch>> {
    match arch {
        Some(arch) => Ok(Some(arch)),
        None => config::build_arch(),
    }
}

pub fn build_sbf(quiet: bool, features: &[String], arch: Option<Arch>) -> Result<()> {
    println!("Building program");
    let mut cmd = Command::new("cargo");
    cmd.arg("build-sbf");
    if let Some(arch) = arch {
        cmd.arg("--arch").arg(arch.as_str());
    }
    if let Some(features) = features_arg(features) {
        cmd.arg("--features").arg(features);
    }
    if quiet {
        cmd.arg("--").arg("--quiet");
    }

    let status = cmd.spawn()?.wait().context("Failed to build project")?;
    if !status.success() {
        anyhow::bail!("Build failed with exit code: {:?}", status.code());
    }
    println!("Build completed successfully!");
    Ok(())
}

pub fn run_build(
    quiet: bool,
    features: &[String],
    arch: Option<Arch>,
    program_id: Option<&str>,
    idl_generator: Option<Generator>,
) -> Result<()> {
    let arch = resolve_arch(arch)?;
    let manifest = read_manifest();
    let build_features = split_features(features);
    build_sbf(quiet, features, arch)?;
    check_built_artifact(
        manifest.as_ref(),
        Rebuild::Build {
            build_features: &build_features,
        },
    )?;
    record_build(manifest.as_ref(), &build_features, arch);

    if let Err(e) = generate_idl("target/idl", program_id, idl_generator) {
        let full_message = e
            .chain()
            .map(|cause| cause.to_string())
            .collect::<Vec<_>>()
            .join(": ");
        println!("⚠️  Skipped IDL generation: {full_message}");
        if full_message.contains("declare_id") {
            println!(
                "   If your program doesn't use `declare_id!`, pass `--program-id <ADDRESS>` to `pinoc build`."
            );
        } else if full_message.contains("[idl].generator") {
            println!("   Fix the `[idl].generator` value in Pinoc.toml, or override it with `--idl-generator <shank|codama>`.");
        }
    }

    Ok(())
}