1#![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
18pub fn version() -> Result<String, Error> {
20 map_stdout(&["--verson"], ToString::to_string)
21}
22
23pub fn prefix() -> Result<PathBuf, Error> {
25 map_stdout(&["--prefix"], |s| PathBuf::from(s))
26}
27
28pub fn src_root() -> Result<PathBuf, Error> {
30 map_stdout(&["--src-root"], |s| PathBuf::from(s))
31}
32pub fn obj_root() -> Result<PathBuf, Error> {
34 map_stdout(&["--obj-root"], |s| PathBuf::from(s))
35}
36
37pub fn bin_dir() -> Result<PathBuf, Error> {
39 map_stdout(&["--bin-dir"], |s| PathBuf::from(s))
40}
41
42pub fn include_dir() -> Result<PathBuf, Error> {
44 map_stdout(&["--include-dir"], |s| PathBuf::from(s))
45}
46
47pub fn lib_dir() -> Result<PathBuf, Error> {
49 map_stdout(&["--lib-dir"], |s| PathBuf::from(s))
50}
51
52pub fn cmake_dir() -> Result<PathBuf, Error> {
54 map_stdout(&["--cmake-dir"], |s| PathBuf::from(s))
55}
56
57pub fn cpp_flags() -> Result<impl Iterator<Item = String>, Error> {
59 stdout_words(&["--cppflags"])
60}
61
62pub fn c_flags() -> Result<impl Iterator<Item = String>, Error> {
64 stdout_words(&["--cflags"])
65}
66
67pub fn cxx_flags() -> Result<impl Iterator<Item = String>, Error> {
69 stdout_words(&["--cxxflags"])
70}
71
72pub fn ldflags() -> Result<impl Iterator<Item = String>, Error> {
74 stdout_words(&["--ldflags"])
75}
76
77pub fn system_libs() -> Result<impl Iterator<Item = String>, Error> {
79 stdout_words(&["--system-libs"])
80}
81
82pub fn libs() -> Result<impl Iterator<Item = String>, Error> {
84 stdout_words(&["--libs"])
85}
86
87pub fn libnames() -> Result<String, Error> {
89 map_stdout(&["--libnames"], |s| String::from(s))
90}
91
92pub fn libfiles() -> Result<impl Iterator<Item = String>, Error> {
94 stdout_words(&["--libfiles"])
95}
96
97pub 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 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
162fn 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
174fn 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#[derive(Debug)]
187pub enum Error {
188 Utf8(FromUtf8Error),
190 UnableToInvoke(io::Error),
192 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}