fg-mako 0.1.5

Fast SAM/BAM sorter (installs the `mako` binary).
#![deny(unsafe_code)]

use anyhow::Result;
use clap::Parser;
use env_logger::Env;
use fgumi_lib::commands::{command::Command, sort::Sort};

#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;

mod built_info {
    include!(concat!(env!("OUT_DIR"), "/built.rs"));
}

/// Spilled sorted runs mako allows on disk before consolidating the oldest into
/// one.
///
/// The engine defaults to 64 to match samtools. mako exists to sort whole-genome
/// BAMs, which spill well past that, and a consolidation pass there is close to
/// pure overhead: it rewrites already-sorted runs without making the final merge
/// meaningfully cheaper, because the k-way merge is not sensitive to fan-in at
/// these counts.
///
/// A 1.29B-read WGS BAM spills 93 runs at the default memory limit. Sorting it at
/// 64 spends 117s consolidating and merges 62 runs in 344s; at 256 it consolidates
/// nothing and merges all 93 in 336s — 14% less wall clock overall. 256 also keeps
/// the merge's open-file count far below any reasonable descriptor limit, and
/// leaves headroom above the observed 93 for larger inputs or a lower
/// `--max-memory`.
const DEFAULT_MAX_TEMP_FILES: usize = 256;

/// Fast SAM/BAM sorter.
///
/// `mako` is a focused, single-purpose sort utility for SAM/BAM files,
/// powered by the sort engine from
/// [fgumi](https://github.com/fulcrumgenomics/fgumi).
///
/// Supports coordinate, queryname (lexicographic and natural), and
/// template-coordinate sort orders, as well as a `--verify` mode that
/// checks sortedness without rewriting.
#[derive(Parser)]
#[command(
    name = "mako",
    version = mako_long_version(),
    about = "Fast SAM/BAM sorter.",
    long_about = None,
)]
struct Cli {
    /// Show info-level progress logs from the sort engine (config
    /// snapshot, per-phase markers, final summary). Without this flag
    /// only warnings and errors are shown. `RUST_LOG` overrides both
    /// (e.g. `RUST_LOG=debug` for the noisiest output).
    #[arg(short = 'v', long = "verbose")]
    verbose: bool,

    #[command(flatten)]
    sort: Sort,
}

fn mako_long_version() -> &'static str {
    // Includes the mako version, the git rev mako was built from, and the
    // resolved fgumi version. Built once at compile time via the `built`
    // crate.
    static LONG_VERSION: std::sync::OnceLock<String> = std::sync::OnceLock::new();
    LONG_VERSION.get_or_init(|| {
        let rev = built_info::GIT_COMMIT_HASH_SHORT.unwrap_or("unknown");
        let fgumi_ver = built_info::DEPENDENCIES
            .iter()
            .find_map(|(name, version)| (*name == "fgumi").then_some(*version))
            .unwrap_or("unknown");
        format!("{} (rev {rev})\npowered by fgumi {fgumi_ver}", built_info::PKG_VERSION)
    })
}

fn main() -> Result<()> {
    // Capture the original invocation for the @PG header record before clap
    // consumes it.
    let command_line = std::env::args().collect::<Vec<_>>().join(" ");

    let mut cli = Cli::parse();

    // An explicit --max-temp-files always wins; absent one, mako's default
    // replaces the engine's.
    cli.sort.max_temp_files = cli.sort.max_temp_files.or(Some(DEFAULT_MAX_TEMP_FILES));

    // Default to warn-only so the sort engine's per-phase info logs don't
    // dominate the terminal. `-v`/`--verbose` opts in to info; `RUST_LOG`
    // (read by `from_env`) always wins for finer control.
    let default_level = if cli.verbose { "info" } else { "warn" };
    env_logger::Builder::from_env(Env::default().default_filter_or(default_level)).init();

    cli.sort.execute(&command_line)
}