prism-llvm 0.1.0

A Rust abstraction layer for LLVM with a compilation pipeline.
#[cfg(unix)]
use std::path::Path;
use std::{env, path::PathBuf, process::Command};

use crate::error::PrismError;

pub struct Linker {
    executable: PathBuf,
}

impl Linker {
    pub fn new(executable: impl Into<PathBuf>) -> Self {
        Self {
            executable: executable.into(),
        }
    }

    pub fn discover() -> Result<Self, PrismError> {
        for name in ["cc", "clang", "gcc"] {
            if let Some(path) = Self::find_executable(name) {
                return Ok(Self::new(path));
            }
        }

        Err(PrismError::Compilation(
            "No linker found. Specify one with --linker.".into(),
        ))
    }

    pub fn link(&self, object: &Path, output: &Path) -> Result<(), PrismError> {
        let status = Command::new(&self.executable)
            .arg(object)
            .arg("-o")
            .arg(output)
            .status()
            .map_err(PrismError::Io)?;

        if !status.success() {
            return Err(PrismError::Compilation(format!(
                "Linker '{}' failed with status {status}.",
                self.executable.display()
            )));
        }

        Ok(())
    }

    fn find_executable(name: &str) -> Option<PathBuf> {
        let paths = env::var_os("PATH")?;
        let paths = env::split_paths(&paths);

        let target_name = if cfg!(windows) && !name.ends_with(".exe") {
            format!("{name}.exe")
        } else {
            name.to_owned()
        };

        paths
            .map(|path| path.join(&target_name))
            .find(|path| path.is_file() && Self::is_executable(path))
    }

    #[cfg(unix)]
    fn is_executable(path: &Path) -> bool {
        use std::os::unix::fs::PermissionsExt;

        path.metadata()
            .is_ok_and(|metadata| metadata.permissions().mode() & 0o111 != 0)
    }

    #[cfg(not(unix))]
    fn is_executable(_path: &Path) -> bool {
        true
    }
}