/* 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 for Error { fn from(err: io::Error) -> Self { Error::Io(err) } } impl From 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>, profile: Profile, ) -> Result { 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) } }