muntjac 0.1.0

Translate uv.lock into Buck2 build rules
Documentation
//! `muntjac buckify` — read uv.lock + muntjac.toml, emit BUCK + muntjac.bzl + config/BUCK + wiring.bzl.

use std::fs;
use std::path::Path;
use std::str::FromStr;

use anyhow::{Context, Result};

use crate::buck::{
    BuckEmitter, BuildEmitContext, StringTemplateEmitter, build_emit_input, write_outputs,
};
use crate::cli::Globals;
use crate::config::Config;
use crate::lock;

pub fn run(globals: &Globals) -> Result<()> {
    let cwd = globals.workdir().context("resolving working directory")?;
    let cfg_path = cwd.join("muntjac.toml");
    let cfg_bytes =
        fs::read_to_string(&cfg_path).with_context(|| format!("reading {}", cfg_path.display()))?;
    let config =
        Config::from_str(&cfg_bytes).with_context(|| format!("parsing {}", cfg_path.display()))?;

    let emitter = StringTemplateEmitter;
    let cfg_dir = cfg_path.parent().unwrap_or(Path::new("."));

    for tree in &config.trees {
        if let Some(filter) = &globals.tree {
            if &tree.name != filter {
                continue;
            }
        }

        // Resolve uv.lock relative to the tree's manifest directory.
        let manifest_dir = cfg_dir.join(tree.manifest_path.parent().unwrap_or(Path::new("")));
        let lockfile_path = manifest_dir.join("uv.lock");
        let lock_bytes = fs::read_to_string(&lockfile_path)
            .with_context(|| format!("reading {}", lockfile_path.display()))?;
        let lockfile = lock::parser::parse(&lock_bytes)
            .with_context(|| format!("parsing {}", lockfile_path.display()))?;

        let third_party_dir = cwd.join(&tree.third_party_dir);
        let manifest_path = third_party_dir.join("prebake/.manifest.toml");
        let manifest = if manifest_path.is_file() {
            Some(crate::sdist::Manifest::load(&manifest_path)?)
        } else {
            None
        };

        let canonical_third_party_dir =
            std::fs::canonicalize(&third_party_dir).unwrap_or_else(|_| third_party_dir.clone());

        let fixups = crate::fixup::EffectiveFixups::load(
            &config.fixups.registry,
            &third_party_dir,
            config.fixups.allow_local_overrides,
            globals.no_network,
        )
        .with_context(|| format!("loading fixups for tree '{}'", tree.name))?;

        let input = build_emit_input(
            &config,
            tree,
            &lockfile,
            &BuildEmitContext {
                manifest: manifest.as_ref(),
                fixups: Some(&fixups),
                abs_third_party_dir: Some(&canonical_third_party_dir),
            },
        )?;
        let output = emitter.emit(&input);

        write_outputs(&output, &third_party_dir)?;
    }
    Ok(())
}