llvm_config/
lib.rs

1//! A thin wrapper around the `llvm-config` tool so you can call it from Rust.
2//!
3//! This is mainly intended as a tool for build scripts which need to use LLVM
4//! but don't want to manually parse the output and handle errors every time.
5
6#![forbid(unsafe_code)]
7#![deny(missing_docs, missing_debug_implementations)]
8
9use std::{
10    ffi::OsStr,
11    fmt::{self, Display, Formatter},
12    io,
13    path::PathBuf,
14    process::{Command, Output, Stdio},
15    string::FromUtf8Error,
16};
17
18/// Print LLVM version.
19pub fn version() -> Result<String, Error> {
20    map_stdout(&["--verson"], ToString::to_string)
21}
22
23/// Print the installation prefix.
24pub fn prefix() -> Result<PathBuf, Error> {
25    map_stdout(&["--prefix"], |s| PathBuf::from(s))
26}
27
28/// Print the source root LLVM was built from.
29pub fn src_root() -> Result<PathBuf, Error> {
30    map_stdout(&["--src-root"], |s| PathBuf::from(s))
31}
32/// Print the object root used to build LLVM.
33pub fn obj_root() -> Result<PathBuf, Error> {
34    map_stdout(&["--obj-root"], |s| PathBuf::from(s))
35}
36
37/// Directory containing LLVM executables.
38pub fn bin_dir() -> Result<PathBuf, Error> {
39    map_stdout(&["--bin-dir"], |s| PathBuf::from(s))
40}
41
42/// Directory containing LLVM headers.
43pub fn include_dir() -> Result<PathBuf, Error> {
44    map_stdout(&["--include-dir"], |s| PathBuf::from(s))
45}
46
47/// Directory containing LLVM libraries.
48pub fn lib_dir() -> Result<PathBuf, Error> {
49    map_stdout(&["--lib-dir"], |s| PathBuf::from(s))
50}
51
52/// Directory containing LLVM cmake modules.
53pub fn cmake_dir() -> Result<PathBuf, Error> {
54    map_stdout(&["--cmake-dir"], |s| PathBuf::from(s))
55}
56
57/// C preprocessor flags for files that include LLVM headers.
58pub fn cpp_flags() -> Result<impl Iterator<Item = String>, Error> {
59    stdout_words(&["--cppflags"])
60}
61
62/// C compiler flags for files that include LLVM headers.
63pub fn c_flags() -> Result<impl Iterator<Item = String>, Error> {
64    stdout_words(&["--cflags"])
65}
66
67/// C++ compiler flags for files that include LLVM headers.
68pub fn cxx_flags() -> Result<impl Iterator<Item = String>, Error> {
69    stdout_words(&["--cxxflags"])
70}
71
72/// Print Linker flags.
73pub fn ldflags() -> Result<impl Iterator<Item = String>, Error> {
74    stdout_words(&["--ldflags"])
75}
76
77/// System Libraries needed to link against LLVM components.
78pub fn system_libs() -> Result<impl Iterator<Item = String>, Error> {
79    stdout_words(&["--system-libs"])
80}
81
82/// Libraries needed to link against LLVM components.
83pub fn libs() -> Result<impl Iterator<Item = String>, Error> {
84    stdout_words(&["--libs"])
85}
86
87/// Bare library names for in-tree builds.
88pub fn libnames() -> Result<String, Error> {
89    map_stdout(&["--libnames"], |s| String::from(s))
90}
91
92/// Fully qualified library filenames for makefile depends.
93pub fn libfiles() -> Result<impl Iterator<Item = String>, Error> {
94    stdout_words(&["--libfiles"])
95}
96
97/// List of all possible components.
98pub fn components() -> Result<impl Iterator<Item = String>, Error> {
99    stdout_words(&["--components"])
100}
101
102#[derive(Debug)]
103struct SpaceSeparatedStrings {
104    src: String,
105    next_character_index: usize,
106}
107
108impl SpaceSeparatedStrings {
109    fn new<S: Into<String>>(src: S) -> Self {
110        SpaceSeparatedStrings {
111            src: src.into(),
112            next_character_index: 0,
113        }
114    }
115}
116
117impl Iterator for SpaceSeparatedStrings {
118    type Item = String;
119
120    fn next(&mut self) -> Option<Self::Item> {
121        let rest = &self.src[self.next_character_index..];
122        let trimmed = rest.trim_start();
123        // Note: We need to keep track of how much whitespace was skipped...
124        self.next_character_index += rest.len() - trimmed.len();
125        let rest = trimmed;
126
127        if rest.is_empty() {
128            return None;
129        }
130
131        let word = match rest.find(char::is_whitespace) {
132            Some(end_ix) => &rest[..end_ix],
133            None => rest,
134        };
135
136        self.next_character_index += word.len();
137        Some(word.to_string())
138    }
139}
140
141fn run<I, O>(args: I) -> Result<Output, Error>
142where
143    I: IntoIterator<Item = O>,
144    O: AsRef<OsStr>,
145{
146    let mut command = Command::new("llvm-config");
147    command.stdin(Stdio::null());
148
149    for arg in args {
150        command.arg(arg);
151    }
152
153    let output = command.output().map_err(Error::UnableToInvoke)?;
154
155    if output.status.success() {
156        Ok(output)
157    } else {
158        Err(Error::BadExitCode(output))
159    }
160}
161
162/// Invoke `llvm-config` then transform STDOUT.
163fn map_stdout<I, O, F, T>(args: I, map: F) -> Result<T, Error>
164where
165    I: IntoIterator<Item = O>,
166    O: AsRef<OsStr>,
167    F: FnOnce(&str) -> T,
168{
169    let output = run(args)?;
170    let stdout = String::from_utf8(output.stdout)?;
171    Ok(map(stdout.trim()))
172}
173
174/// Invoke `llvm-config` then split STDOUT by spaces.
175fn stdout_words<I, O>(args: I) -> Result<impl Iterator<Item = String>, Error>
176where
177    I: IntoIterator<Item = O>,
178    O: AsRef<OsStr>,
179{
180    let output = run(args)?;
181    let stdout = String::from_utf8(output.stdout)?;
182    Ok(SpaceSeparatedStrings::new(stdout))
183}
184
185/// An error that may occur while trying to use `llvm-config`.
186#[derive(Debug)]
187pub enum Error {
188    /// The output wasn't valid UTF-8.
189    Utf8(FromUtf8Error),
190    /// Unable to invoke `llvm-config`.
191    UnableToInvoke(io::Error),
192    /// The command ran to completion, but finished with an unsuccessful status
193    /// code (as reported by [`std::process::ExitStatus`]).
194    BadExitCode(Output),
195}
196
197impl From<FromUtf8Error> for Error {
198    fn from(other: FromUtf8Error) -> Error { Error::Utf8(other) }
199}
200
201impl Display for Error {
202    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
203        match self {
204            Error::Utf8(_) => write!(f, "The output wasn't valid UTF-8"),
205            Error::UnableToInvoke(_) => write!(f, "Unable to invoke llvm-config. Is it installed and on your $PATH?"),
206            Error::BadExitCode(output) => {
207                write!(f, "llvm-config ran unsuccessfully")?;
208
209                if let Some(code) = output.status.code() {
210                    write!(f, " with exit code {}", code)?;
211                }
212
213                Ok(())
214            }
215        }
216    }
217}
218
219impl std::error::Error for Error {
220    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
221        match self {
222            Error::Utf8(inner) => Some(inner),
223            Error::UnableToInvoke(inner) => Some(inner),
224            Error::BadExitCode(_) => None,
225        }
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn strings_are_split_correctly() {
235        let src = "aarch64 aarch64asmparser aarch64codegen aarch64desc
236        aarch64disassembler aarch64info aarch64utils aggressiveinstcombine
237        all all-targets amdgpu amdgpuasmparser amdgpucodegen";
238        let expected = vec![
239            "aarch64",
240            "aarch64asmparser",
241            "aarch64codegen",
242            "aarch64desc",
243            "aarch64disassembler",
244            "aarch64info",
245            "aarch64utils",
246            "aggressiveinstcombine",
247            "all",
248            "all-targets",
249            "amdgpu",
250            "amdgpuasmparser",
251            "amdgpucodegen",
252        ];
253
254        let got: Vec<_> = SpaceSeparatedStrings::new(src).collect();
255
256        assert_eq!(got, expected);
257    }
258}