cps-deps 0.3.2

Common Package Specification (CPS) Rust Library
Documentation
use anyhow::{anyhow, Result};
use std::collections::HashMap;
use std::path::PathBuf;
use std::process::Command;
use std::sync::OnceLock;

use crate::pkg_config::PkgConfigFile;

fn get_multiarch_lib_path_iter() -> &'static [PathBuf] {
    static MULTIARCH_PATH: OnceLock<Vec<PathBuf>> = OnceLock::new();
    MULTIARCH_PATH.get_or_init(|| {
        Command::new("gcc")
            .arg("-dumpmachine")
            .output()
            .map(|o| String::from_utf8(o.stdout).unwrap_or_default())
            .map_or(vec![], |arch| {
                vec![PathBuf::from(format!("/usr/lib/{}", arch.trim()))]
            })
    })
}

pub fn find_library(library: &str, extension: &str, search_paths: &[PathBuf]) -> Result<String> {
    let filepaths: Vec<_> = search_paths
        .iter()
        .chain(get_multiarch_lib_path_iter())
        .map(|base| base.join(format!("lib{}.{}", library, extension)))
        .collect();

    let error = anyhow!(
        "Could not find required library `{}` at paths: `{:?}`",
        library,
        &filepaths
    );
    Ok(filepaths
        .into_iter()
        .find(|path| path.exists())
        .ok_or(error)?
        .into_os_string()
        .into_string()
        .unwrap())
}

#[derive(Debug)]
pub enum LibraryLocation {
    Archive(String),
    Dylib(String),
    Both { archive: String, dylib: String },
}

impl LibraryLocation {
    pub fn find(library: &str, search_paths: &[PathBuf]) -> Result<Self> {
        let dylib = find_library(library, "so", search_paths);
        let archive = find_library(library, "a", search_paths);

        match (dylib, archive) {
            (Ok(dylib), Err(_)) => Ok(Self::Dylib(dylib)),
            (Err(_), Ok(archive)) => Ok(Self::Archive(archive)),
            (Ok(dylib), Ok(archive)) => Ok(Self::Both { archive, dylib }),
            (Err(dylib_error), Err(archive_error)) => {
                Err(anyhow!("{}\n{}", dylib_error, archive_error))
            }
        }
    }
}

pub fn find_locations(pkg_config: &PkgConfigFile) -> Result<HashMap<String, LibraryLocation>> {
    let search_paths = pkg_config
        .link_locations
        .iter()
        .map(PathBuf::from)
        .collect::<Vec<_>>();

    Ok(pkg_config
        .link_libraries
        .iter()
        .map(|name| -> Result<(String, LibraryLocation)> {
            let location = LibraryLocation::find(name, &search_paths)?;
            Ok((name.clone(), location))
        })
        .collect::<Result<Vec<_>>>()?
        .into_iter()
        .collect())
}