cbindgen/bindgen/cargo/
cargo_expand.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::bindgen::config::Profile;
6use std::env;
7use std::error;
8use std::fmt;
9use std::io;
10use std::path::{Path, PathBuf};
11use std::process::Command;
12use std::str::{from_utf8, Utf8Error};
13
14extern crate tempfile;
15use self::tempfile::Builder;
16
17#[derive(Debug)]
18/// Possible errors that can occur during `rustc -Zunpretty=expanded`.
19pub enum Error {
20    /// Error during creation of temporary directory
21    Io(io::Error),
22    /// Output of `cargo metadata` was not valid utf8
23    Utf8(Utf8Error),
24    /// Error during execution of `cargo rustc -Zunpretty=expanded`
25    Compile(String),
26}
27
28impl From<io::Error> for Error {
29    fn from(err: io::Error) -> Self {
30        Error::Io(err)
31    }
32}
33impl From<Utf8Error> for Error {
34    fn from(err: Utf8Error) -> Self {
35        Error::Utf8(err)
36    }
37}
38
39impl fmt::Display for Error {
40    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41        match self {
42            Error::Io(ref err) => err.fmt(f),
43            Error::Utf8(ref err) => err.fmt(f),
44            Error::Compile(ref err) => write!(f, "{err}"),
45        }
46    }
47}
48
49impl error::Error for Error {
50    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
51        match self {
52            Error::Io(ref err) => Some(err),
53            Error::Utf8(ref err) => Some(err),
54            Error::Compile(..) => None,
55        }
56    }
57}
58
59/// Use rustc to expand and pretty print the crate into a single file,
60/// removing any macros in the process.
61#[allow(clippy::too_many_arguments)]
62pub fn expand(
63    manifest_path: &Path,
64    crate_name: &str,
65    version: Option<&str>,
66    use_tempdir: bool,
67    expand_all_features: bool,
68    expand_default_features: bool,
69    expand_features: &Option<Vec<String>>,
70    profile: Profile,
71) -> Result<String, Error> {
72    let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo"));
73    let mut cmd = Command::new(cargo);
74
75    let mut _temp_dir = None; // drop guard
76    if use_tempdir {
77        _temp_dir = Some(Builder::new().prefix("cbindgen-expand").tempdir()?);
78        cmd.env("CARGO_TARGET_DIR", _temp_dir.unwrap().path());
79    } else if let Ok(ref path) = env::var("CARGO_EXPAND_TARGET_DIR") {
80        cmd.env("CARGO_TARGET_DIR", path);
81    } else if let Ok(ref path) = env::var("OUT_DIR") {
82        // When cbindgen was started programatically from a build.rs file, Cargo is running and
83        // locking the default target directory. In this case we need to use another directory,
84        // else we would end up in a deadlock. If Cargo is running `OUT_DIR` will be set, so we
85        // can use a directory relative to that.
86        cmd.env("CARGO_TARGET_DIR", PathBuf::from(path).join("expanded"));
87    }
88
89    // Set this variable so that we don't call it recursively if we expand a crate that is using
90    // cbindgen
91    cmd.env("_CBINDGEN_IS_RUNNING", "1");
92
93    cmd.arg("rustc");
94    cmd.arg("--lib");
95    // When build with the release profile we can't choose the `check` profile.
96    if profile != Profile::Release {
97        cmd.arg("--profile=check");
98    }
99    cmd.arg("--manifest-path");
100    cmd.arg(manifest_path);
101    if let Some(features) = expand_features {
102        cmd.arg("--features");
103        let mut features_str = String::new();
104        for (index, feature) in features.iter().enumerate() {
105            if index != 0 {
106                features_str.push(' ');
107            }
108            features_str.push_str(feature);
109        }
110        cmd.arg(features_str);
111    }
112    if expand_all_features {
113        cmd.arg("--all-features");
114    }
115    if !expand_default_features {
116        cmd.arg("--no-default-features");
117    }
118    match profile {
119        Profile::Debug => {}
120        Profile::Release => {
121            cmd.arg("--release");
122        }
123    }
124    cmd.arg("-p");
125    let mut package = crate_name.to_owned();
126    if let Some(version) = version {
127        package.push(':');
128        package.push_str(version);
129    }
130    cmd.arg(&package);
131    cmd.arg("--verbose");
132    cmd.arg("--");
133    cmd.arg("-Zunpretty=expanded");
134    info!("Command: {cmd:?}");
135    let output = cmd.output()?;
136
137    let src = from_utf8(&output.stdout)?.to_owned();
138    let error = from_utf8(&output.stderr)?.to_owned();
139
140    if src.is_empty() {
141        Err(Error::Compile(error))
142    } else {
143        Ok(src)
144    }
145}