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: &Error) => err.fmt(f),
43 Error::Utf8(ref err: &Utf8Error) => err.fmt(f),
44 Error::Compile(ref err: &String) => 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: &Error) => Some(err),
53 Error::Utf8(ref err: &Utf8Error) => 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}
146