#![forbid(unsafe_code)]
#![deny(missing_docs, missing_debug_implementations)]
use std::{
ffi::OsStr,
fmt::{self, Display, Formatter},
io,
path::PathBuf,
process::{Command, Output, Stdio},
string::FromUtf8Error,
};
pub fn version() -> Result<String, Error> {
map_stdout(&["--verson"], ToString::to_string)
}
pub fn prefix() -> Result<PathBuf, Error> {
map_stdout(&["--prefix"], |s| PathBuf::from(s))
}
pub fn src_root() -> Result<PathBuf, Error> {
map_stdout(&["--src-root"], |s| PathBuf::from(s))
}
pub fn obj_root() -> Result<PathBuf, Error> {
map_stdout(&["--obj-root"], |s| PathBuf::from(s))
}
pub fn bin_dir() -> Result<PathBuf, Error> {
map_stdout(&["--bin-dir"], |s| PathBuf::from(s))
}
pub fn include_dir() -> Result<PathBuf, Error> {
map_stdout(&["--include-dir"], |s| PathBuf::from(s))
}
pub fn lib_dir() -> Result<PathBuf, Error> {
map_stdout(&["--lib-dir"], |s| PathBuf::from(s))
}
pub fn cmake_dir() -> Result<PathBuf, Error> {
map_stdout(&["--cmake-dir"], |s| PathBuf::from(s))
}
pub fn cpp_flags() -> Result<impl Iterator<Item = String>, Error> {
stdout_words(&["--cppflags"])
}
pub fn c_flags() -> Result<impl Iterator<Item = String>, Error> {
stdout_words(&["--cflags"])
}
pub fn cxx_flags() -> Result<impl Iterator<Item = String>, Error> {
stdout_words(&["--cxxflags"])
}
pub fn ldflags() -> Result<impl Iterator<Item = String>, Error> {
stdout_words(&["--ldflags"])
}
pub fn system_libs() -> Result<impl Iterator<Item = String>, Error> {
stdout_words(&["--system-libs"])
}
pub fn libs() -> Result<impl Iterator<Item = String>, Error> {
stdout_words(&["--libs"])
}
pub fn libnames() -> Result<String, Error> {
map_stdout(&["--libnames"], |s| String::from(s))
}
pub fn libfiles() -> Result<impl Iterator<Item = String>, Error> {
stdout_words(&["--libfiles"])
}
pub fn components() -> Result<impl Iterator<Item = String>, Error> {
stdout_words(&["--components"])
}
#[derive(Debug)]
struct SpaceSeparatedStrings {
src: String,
next_character_index: usize,
}
impl SpaceSeparatedStrings {
fn new<S: Into<String>>(src: S) -> Self {
SpaceSeparatedStrings {
src: src.into(),
next_character_index: 0,
}
}
}
impl Iterator for SpaceSeparatedStrings {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
let rest = &self.src[self.next_character_index..];
let trimmed = rest.trim_start();
self.next_character_index += rest.len() - trimmed.len();
let rest = trimmed;
if rest.is_empty() {
return None;
}
let word = match rest.find(char::is_whitespace) {
Some(end_ix) => &rest[..end_ix],
None => rest,
};
self.next_character_index += word.len();
Some(word.to_string())
}
}
fn run<I, O>(args: I) -> Result<Output, Error>
where
I: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let mut command = Command::new("llvm-config");
command.stdin(Stdio::null());
for arg in args {
command.arg(arg);
}
let output = command.output().map_err(Error::UnableToInvoke)?;
if output.status.success() {
Ok(output)
} else {
Err(Error::BadExitCode(output))
}
}
fn map_stdout<I, O, F, T>(args: I, map: F) -> Result<T, Error>
where
I: IntoIterator<Item = O>,
O: AsRef<OsStr>,
F: FnOnce(&str) -> T,
{
let output = run(args)?;
let stdout = String::from_utf8(output.stdout)?;
Ok(map(stdout.trim()))
}
fn stdout_words<I, O>(args: I) -> Result<impl Iterator<Item = String>, Error>
where
I: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let output = run(args)?;
let stdout = String::from_utf8(output.stdout)?;
Ok(SpaceSeparatedStrings::new(stdout))
}
#[derive(Debug)]
pub enum Error {
Utf8(FromUtf8Error),
UnableToInvoke(io::Error),
BadExitCode(Output),
}
impl From<FromUtf8Error> for Error {
fn from(other: FromUtf8Error) -> Error { Error::Utf8(other) }
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Error::Utf8(_) => write!(f, "The output wasn't valid UTF-8"),
Error::UnableToInvoke(_) => write!(f, "Unable to invoke llvm-config. Is it installed and on your $PATH?"),
Error::BadExitCode(output) => {
write!(f, "llvm-config ran unsuccessfully")?;
if let Some(code) = output.status.code() {
write!(f, " with exit code {}", code)?;
}
Ok(())
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Utf8(inner) => Some(inner),
Error::UnableToInvoke(inner) => Some(inner),
Error::BadExitCode(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strings_are_split_correctly() {
let src = "aarch64 aarch64asmparser aarch64codegen aarch64desc
aarch64disassembler aarch64info aarch64utils aggressiveinstcombine
all all-targets amdgpu amdgpuasmparser amdgpucodegen";
let expected = vec![
"aarch64",
"aarch64asmparser",
"aarch64codegen",
"aarch64desc",
"aarch64disassembler",
"aarch64info",
"aarch64utils",
"aggressiveinstcombine",
"all",
"all-targets",
"amdgpu",
"amdgpuasmparser",
"amdgpucodegen",
];
let got: Vec<_> = SpaceSeparatedStrings::new(src).collect();
assert_eq!(got, expected);
}
}