blob: 565a0d1b27aa9ec83b3b0c5cb18017348906d12b [file] [log] [blame]
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::bindgen::config::Profile;
use std::env;
use std::error;
use std::fmt;
use std::io;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::{from_utf8, Utf8Error};
extern crate tempfile;
use self::tempfile::Builder;
#[derive(Debug)]
/// Possible errors that can occur during `rustc -Zunpretty=expanded`.
pub enum Error {
/// Error during creation of temporary directory
Io(io::Error),
/// Output of `cargo metadata` was not valid utf8
Utf8(Utf8Error),
/// Error during execution of `cargo rustc -Zunpretty=expanded`
Compile(String),
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::Io(err)
}
}
impl From<Utf8Error> for Error {
fn from(err: Utf8Error) -> Self {
Error::Utf8(err)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Io(ref err) => err.fmt(f),
Error::Utf8(ref err) => err.fmt(f),
Error::Compile(ref err) => write!(f, "{}", err),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::Io(ref err) => Some(err),
Error::Utf8(ref err) => Some(err),
Error::Compile(..) => None,
}
}
}
/// Use rustc to expand and pretty print the crate into a single file,
/// removing any macros in the process.
#[allow(clippy::too_many_arguments)]
pub fn expand(
manifest_path: &Path,
crate_name: &str,
version: Option<&str>,
use_tempdir: bool,
expand_all_features: bool,
expand_default_features: bool,
expand_features: &Option<Vec<String>>,
profile: Profile,
) -> Result<String, Error> {
let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo"));
let mut cmd = Command::new(cargo);
let mut _temp_dir = None; // drop guard
if use_tempdir {
_temp_dir = Some(Builder::new().prefix("cbindgen-expand").tempdir()?);
cmd.env("CARGO_TARGET_DIR", _temp_dir.unwrap().path());
} else if let Ok(ref path) = env::var("CARGO_EXPAND_TARGET_DIR") {
cmd.env("CARGO_TARGET_DIR", path);
} else if let Ok(ref path) = env::var("OUT_DIR") {
// When cbindgen was started programatically from a build.rs file, Cargo is running and
// locking the default target directory. In this case we need to use another directory,
// else we would end up in a deadlock. If Cargo is running `OUT_DIR` will be set, so we
// can use a directory relative to that.
cmd.env("CARGO_TARGET_DIR", PathBuf::from(path).join("expanded"));
}
// Set this variable so that we don't call it recursively if we expand a crate that is using
// cbindgen
cmd.env("_CBINDGEN_IS_RUNNING", "1");
cmd.arg("rustc");
cmd.arg("--lib");
// When build with the release profile we can't choose the `check` profile.
if profile != Profile::Release {
cmd.arg("--profile=check");
}
cmd.arg("--manifest-path");
cmd.arg(manifest_path);
if let Some(features) = expand_features {
cmd.arg("--features");
let mut features_str = String::new();
for (index, feature) in features.iter().enumerate() {
if index != 0 {
features_str.push(' ');
}
features_str.push_str(feature);
}
cmd.arg(features_str);
}
if expand_all_features {
cmd.arg("--all-features");
}
if !expand_default_features {
cmd.arg("--no-default-features");
}
match profile {
Profile::Debug => {}
Profile::Release => {
cmd.arg("--release");
}
}
cmd.arg("-p");
let mut package = crate_name.to_owned();
if let Some(version) = version {
package.push(':');
package.push_str(version);
}
cmd.arg(&package);
cmd.arg("--verbose");
cmd.arg("--");
cmd.arg("-Zunpretty=expanded");
info!("Command: {:?}", cmd);
let output = cmd.output()?;
let src = from_utf8(&output.stdout)?.to_owned();
let error = from_utf8(&output.stderr)?.to_owned();
if src.is_empty() {
Err(Error::Compile(error))
} else {
Ok(src)
}
}