llvm_tools/
lib.rs

1//! Provides access to the llvm tools installed through the `llvm-tools-preview` rustup component.
2
3#![deny(missing_docs)]
4
5use std::{borrow::Cow, io, ops::Deref, path::PathBuf, process::Command, string::FromUtf8Error};
6
7/// Allows to retrieve paths of llvm tools.
8#[derive(Debug)]
9pub struct LlvmTools {
10    bin_dir: PathBuf,
11}
12
13impl LlvmTools {
14    /// Find the directory where the llvm-tools live.
15    ///
16    /// This only works if the user has the `llvm-tools-preview` rustup component installed.
17    pub fn new() -> Result<Self, Error> {
18        let sysroot = {
19            let mut rustc_cmd = Command::new("rustc");
20            rustc_cmd.arg("--print").arg("sysroot");
21            let rustc_cmd_string = format!("{:?}", rustc_cmd);
22            let out = rustc_cmd
23                .output()
24                .map_err(|e| Error::CommandExecution(rustc_cmd_string, e))?;
25            if !out.status.success() {
26                return Err(Error::RetrieveSysroot(String::from_utf8(out.stderr)));
27            }
28
29            let sysroot_string =
30                String::from_utf8(out.stdout).map_err(Error::SysrootInvalidUtf8)?;
31            PathBuf::from(sysroot_string.trim())
32        };
33
34        let example_tool_name = exe("llvm-objdump");
35        let rustlib = sysroot.join("lib").join("rustlib");
36        for entry in rustlib.read_dir().map_err(Error::ReadDirFailed)? {
37            let bin_dir = entry.map_err(Error::ReadEntryFailed)?.path().join("bin");
38            let tool_path = bin_dir.join(example_tool_name.deref());
39            if tool_path.exists() {
40                return Ok(Self { bin_dir });
41            }
42        }
43
44        Err(Error::NotFound)
45    }
46
47    /// Returns the path to the specified tool, or None if the tool is not found.
48    ///
49    /// Use the [`exe`] function to append `*.exe` to the executable name on Windows.
50    pub fn tool(&self, tool_name: &str) -> Option<PathBuf> {
51        let tool_path = self.bin_dir.join(&tool_name);
52
53        if tool_path.exists() {
54            Some(tool_path)
55        } else {
56            None
57        }
58    }
59}
60
61/// Errors that can occur during the construction of [`LlvmTools`].
62#[derive(Debug)]
63pub enum Error {
64    /// Failed to run a command
65    CommandExecution(String, io::Error),
66    /// Failed to find out the sysroot by running `rustc --print sysroot`
67    RetrieveSysroot(Result<String, FromUtf8Error>),
68    /// The sysroot path is not valid utf8
69    SysrootInvalidUtf8(FromUtf8Error),
70    /// Calling `read_dir` on sysroot directory failed
71    ReadDirFailed(io::Error),
72    /// Failed to read entry of sysroot directory
73    ReadEntryFailed(io::Error),
74    /// Could not find llvm-tools component
75    ///
76    /// Maybe the rustup component `llvm-tools-preview` is missing? Install it through:
77    /// `rustup component add llvm-tools-preview`
78    NotFound,
79}
80
81/// Appends `*.exe` on Windows, returns the original name on other platforms.
82pub fn exe(executable_name: &str) -> Cow<str> {
83    if cfg!(target_os = "windows") {
84        Cow::Owned(format!("{}.exe", executable_name))
85    } else {
86        Cow::Borrowed(executable_name)
87    }
88}