#![allow(private_bounds)]
extern crate cc;
#[cfg(any(not(target_os = "windows"), all(target_os = "windows", not(target_env = "msvc"))))]
extern crate memchr;
#[cfg(all(target_os = "windows", target_env = "msvc"))]
extern crate vswhom;
#[cfg(all(target_os = "windows", target_env = "msvc"))]
extern crate winreg;
extern crate rustc_version;
extern crate toml;
#[cfg(not(target_os = "windows"))]
mod non_windows;
#[cfg(all(target_os = "windows", target_env = "msvc"))]
mod windows_msvc;
#[cfg(all(target_os = "windows", not(target_env = "msvc")))]
mod windows_not_msvc;
#[cfg(not(target_os = "windows"))]
use self::non_windows::*;
#[cfg(all(target_os = "windows", target_env = "msvc"))]
use self::windows_msvc::*;
#[cfg(all(target_os = "windows", not(target_env = "msvc")))]
use self::windows_not_msvc::*;
use std::{env, fs};
use std::ffi::{OsString, OsStr};
use std::borrow::Cow;
use std::process::Command;
use toml::Table as TomlTable;
use std::fmt::{self, Display};
use std::path::{Path, PathBuf};
pub const NONE: &[&OsStr] = &[];
#[derive(PartialEq, Eq, Debug)] struct ParameterBundle<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>> {
macros: Mi,
include_dirs: Ii,
}
impl<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>> From<Mi> for ParameterBundle<Ms, Mi, &'static &'static OsStr, &'static [&'static OsStr]> {
fn from(macros: Mi) -> Self {
ParamsMacros(macros).into()
}
}
pub struct ParamsMacros<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>>(pub Mi);
impl<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>> From<ParamsMacros<Ms, Mi>> for ParameterBundle<Ms, Mi, &'static &'static OsStr, &'static [&'static OsStr]> {
fn from(macros: ParamsMacros<Ms, Mi>) -> Self {
ParamsMacrosAndIncludeDirs(macros.0, NONE).into()
}
}
pub struct ParamsIncludeDirs<Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(pub Ii);
impl<Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>> From<ParamsIncludeDirs<Is, Ii>>
for ParameterBundle<&'static &'static OsStr, &'static [&'static OsStr], Is, Ii> {
fn from(include_dirs: ParamsIncludeDirs<Is, Ii>) -> Self {
ParamsMacrosAndIncludeDirs(NONE, include_dirs.0).into()
}
}
pub struct ParamsMacrosAndIncludeDirs<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(pub Mi, pub Ii);
impl<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>> From<ParamsMacrosAndIncludeDirs<Ms, Mi, Is, Ii>>
for ParameterBundle<Ms, Mi, Is, Ii> {
fn from(maid: ParamsMacrosAndIncludeDirs<Ms, Mi, Is, Ii>) -> Self {
Self {
macros: maid.0,
include_dirs: maid.1,
}
}
}
#[cfg(test)]
#[allow(dead_code)]
fn compat_3_0_5() {
use std::collections::BTreeSet;
let _ = compile("", std::iter::empty::<&str>());
let _ = compile("", None::<&str>);
let marcos = &[format!("VERSION_PATCH={}", env!("CARGO_PKG_VERSION_PATCH"))];
let _ = compile("", marcos);
let marcos = vec![format!("VERSION_PATCH={}", env!("CARGO_PKG_VERSION_PATCH"))];
let _ = compile("", marcos);
let _ = compile("", [""]);
let _ = compile("", &[""]);
let _ = compile("", vec![""]);
let _ = compile("", vec![Path::new("gaming=baming")].into_iter().collect::<BTreeSet<_>>());
let _ = compile("", vec![Path::new("gaming=baming").to_owned()].into_iter().collect::<BTreeSet<_>>());
let _ = compile("", [PathBuf::from("gaming=baming")].iter());
let _ = compile("", [PathBuf::from("gaming=baming")].iter().collect::<BTreeSet<_>>());
let _ = compile("", ParamsIncludeDirs(&[Path::new("include_dir")]));
let _ = compile("", ParamsIncludeDirs([PathBuf::from("include_dir")]));
let _ = compile("", ParamsIncludeDirs(vec![Path::new("include_dir1"), Path::new("include_dir2")]));
let _ = compile("", ParamsMacrosAndIncludeDirs(NONE, NONE));
let _ = compile("", ParamsMacrosAndIncludeDirs([""], [""]));
}
#[test]
fn argument_bundle_into() {
assert_eq!(ParameterBundle::from(NONE),
ParameterBundle {
macros: NONE,
include_dirs: NONE,
});
assert_eq!(ParameterBundle::from([""]),
ParameterBundle {
macros: [""],
include_dirs: NONE,
});
assert_eq!(ParameterBundle::from(ParamsMacros(NONE)),
ParameterBundle {
macros: NONE,
include_dirs: NONE,
});
assert_eq!(ParameterBundle::from(ParamsMacros([""])),
ParameterBundle {
macros: [""],
include_dirs: NONE,
});
assert_eq!(ParameterBundle::from(ParamsIncludeDirs(NONE)),
ParameterBundle {
macros: NONE,
include_dirs: NONE,
});
assert_eq!(ParameterBundle::from(ParamsIncludeDirs([""])),
ParameterBundle {
macros: NONE,
include_dirs: [""],
});
assert_eq!(ParameterBundle::from(ParamsMacrosAndIncludeDirs(NONE, NONE)),
ParameterBundle {
macros: NONE,
include_dirs: NONE,
});
assert_eq!(ParameterBundle::from(ParamsMacrosAndIncludeDirs([""], NONE)),
ParameterBundle {
macros: [""],
include_dirs: NONE,
});
assert_eq!(ParameterBundle::from(ParamsMacrosAndIncludeDirs(NONE, [""])),
ParameterBundle {
macros: NONE,
include_dirs: [""],
});
assert_eq!(ParameterBundle::from(ParamsMacrosAndIncludeDirs([""], [""])),
ParameterBundle {
macros: [""],
include_dirs: [""],
});
}
#[must_use]
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompilationResult {
NotWindows,
Ok,
NotAttempted(Cow<'static, str>),
Failed(Cow<'static, str>),
}
impl CompilationResult {
pub fn manifest_optional(self) -> Result<(), CompilationResult> {
match self {
CompilationResult::NotWindows |
CompilationResult::Ok |
CompilationResult::NotAttempted(..) => Ok(()),
err @ CompilationResult::Failed(..) => Err(err),
}
}
pub fn manifest_required(self) -> Result<(), CompilationResult> {
match self {
CompilationResult::NotWindows |
CompilationResult::Ok => Ok(()),
err @ CompilationResult::NotAttempted(..) |
err @ CompilationResult::Failed(..) => Err(err),
}
}
}
impl Display for CompilationResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.write_str("embed-resource: ")?;
match self {
CompilationResult::NotWindows => f.write_str("not building for windows"),
CompilationResult::Ok => f.write_str("OK"),
CompilationResult::NotAttempted(why) => {
f.write_str("compilation not attempted: ")?;
if !why.contains(' ') {
f.write_str("missing compiler: ")?;
}
f.write_str(why)
}
CompilationResult::Failed(err) => f.write_str(err),
}
}
}
impl std::error::Error for CompilationResult {}
macro_rules! try_compile_impl {
($expr:expr) => {
match $expr {
Result::Ok(val) => val,
Result::Err(err) => return err,
}
};
}
pub fn compile<T: AsRef<Path>,
Ms: AsRef<OsStr>,
Mi: IntoIterator<Item = Ms>,
Is: AsRef<OsStr>,
Ii: IntoIterator<Item = Is>,
P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
resource_file: T, parameters: P)
-> CompilationResult {
let (prefix, out_dir, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
let hasbins = fs::read_to_string("Cargo.toml")
.unwrap_or_else(|err| {
eprintln!("Couldn't read Cargo.toml: {}; assuming src/main.rs or S_ISDIR(src/bin/)", err);
String::new()
})
.parse::<TomlTable>()
.unwrap_or_else(|err| {
eprintln!("Couldn't parse Cargo.toml: {}; assuming src/main.rs or S_ISDIR(src/bin/)", err);
TomlTable::new()
})
.contains_key("bin") || (Path::new("src/main.rs").exists() || Path::new("src/bin").is_dir());
eprintln!("Final verdict: crate has binaries: {}", hasbins);
if hasbins && rustc_version::version().expect("couldn't get rustc version") >= rustc_version::Version::new(1, 50, 0) {
println!("cargo:rustc-link-arg-bins={}", out_file);
} else {
println!("cargo:rustc-link-search=native={}", out_dir);
println!("cargo:rustc-link-lib=dylib={}", prefix);
}
CompilationResult::Ok
}
pub fn compile_for<T: AsRef<Path>,
J: Display,
I: IntoIterator<Item = J>,
Ms: AsRef<OsStr>,
Mi: IntoIterator<Item = Ms>,
Is: AsRef<OsStr>,
Ii: IntoIterator<Item = Is>,
P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
resource_file: T, for_bins: I, parameters: P)
-> CompilationResult {
let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
for bin in for_bins {
println!("cargo:rustc-link-arg-bin={}={}", bin, out_file);
}
CompilationResult::Ok
}
pub fn compile_for_tests<T: AsRef<Path>,
Ms: AsRef<OsStr>,
Mi: IntoIterator<Item = Ms>,
Is: AsRef<OsStr>,
Ii: IntoIterator<Item = Is>,
P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
resource_file: T, parameters: P)
-> CompilationResult {
let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
println!("cargo:rustc-link-arg-tests={}", out_file);
CompilationResult::Ok
}
pub fn compile_for_benchmarks<T: AsRef<Path>,
Ms: AsRef<OsStr>,
Mi: IntoIterator<Item = Ms>,
Is: AsRef<OsStr>,
Ii: IntoIterator<Item = Is>,
P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
resource_file: T, parameters: P)
-> CompilationResult {
let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
println!("cargo:rustc-link-arg-benches={}", out_file);
CompilationResult::Ok
}
pub fn compile_for_examples<T: AsRef<Path>,
Ms: AsRef<OsStr>,
Mi: IntoIterator<Item = Ms>,
Is: AsRef<OsStr>,
Ii: IntoIterator<Item = Is>,
P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
resource_file: T, parameters: P)
-> CompilationResult {
let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
println!("cargo:rustc-link-arg-examples={}", out_file);
CompilationResult::Ok
}
pub fn compile_for_cdylib<T: AsRef<Path>,
Ms: AsRef<OsStr>,
Mi: IntoIterator<Item = Ms>,
Is: AsRef<OsStr>,
Ii: IntoIterator<Item = Is>,
P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
resource_file: T, parameters: P)
-> CompilationResult {
let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
println!("cargo:rustc-link-arg-cdylib={}", out_file);
CompilationResult::Ok
}
pub fn compile_for_everything<T: AsRef<Path>,
Ms: AsRef<OsStr>,
Mi: IntoIterator<Item = Ms>,
Is: AsRef<OsStr>,
Ii: IntoIterator<Item = Is>,
P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
resource_file: T, parameters: P)
-> CompilationResult {
let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
println!("cargo:rustc-link-arg={}", out_file);
CompilationResult::Ok
}
fn compile_impl<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>, P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
resource_file: &Path, parameters: P)
-> Result<(&str, String, String), CompilationResult> {
let mut comp = ResourceCompiler::new();
if let Some(missing) = comp.is_supported() {
if missing.is_empty() {
Err(CompilationResult::NotWindows)
} else {
Err(CompilationResult::NotAttempted(missing))
}
} else {
let prefix = &resource_file.file_stem().expect("resource_file has no stem").to_str().expect("resource_file's stem not UTF-8");
let out_dir = env::var("OUT_DIR").expect("No OUT_DIR env var");
let out_file = comp.compile_resource(&out_dir, &prefix, resource_file.to_str().expect("resource_file not UTF-8"), parameters.into())
.map_err(CompilationResult::Failed)?;
Ok((prefix, out_dir, out_file))
}
}
fn apply_parameters<'t, Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(to: &'t mut Command, macro_pref: &str,
include_dir_pref: &str,
parameters: ParameterBundle<Ms,
Mi,
Is,
Ii>)
-> &'t mut Command {
for m in parameters.macros {
to.arg(macro_pref).arg(m);
}
for id in parameters.include_dirs {
to.arg(include_dir_pref).arg(id);
}
to
}
pub fn find_windows_sdk_tool<T: AsRef<str>>(tool: T) -> Option<PathBuf> {
find_windows_sdk_tool_impl(tool.as_ref())
}
#[allow(unused)]
fn env_target_and_rc() -> Result<(String, Option<OsString>), Cow<'static, str>> {
let target = env::var("TARGET").map_err(|_| Cow::from("no $TARGET"))?;
let rc = env::var_os(&format!("RC_{}", target)).or_else(|| env::var_os(&format!("RC_{}", target.replace('-', "_")))).or_else(|| env::var_os("RC"));
Ok((target, rc))
}
#[cfg(any(not(target_os = "windows"), all(target_os = "windows", not(target_env = "msvc"))))]
mod windres {
use self::super::{ParameterBundle, apply_parameters};
use std::process::{Command, Stdio};
use std::ffi::{OsString, OsStr};
use std::borrow::Cow;
use std::path::Path;
use memchr::memmem;
use std::fs;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompilerType {
LlvmRc { has_no_preprocess: bool, },
WindRes,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Compiler {
pub tp: CompilerType,
pub executable: Cow<'static, OsStr>,
}
fn is_runnable(s: &str) -> bool {
Command::new(s).stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()).spawn().map(|mut c| c.kill()).is_ok()
}
fn if_runnable(executable: Cow<'static, str>, maketp: fn(&str) -> CompilerType) -> Result<Compiler, Cow<'static, str>> {
if is_runnable(&executable) {
Ok(Compiler {
tp: maketp(&executable),
executable: match executable {
Cow::Owned(e) => Cow::Owned(OsString::from(e)),
Cow::Borrowed(e) => Cow::Borrowed(OsStr::new(e)),
},
})
} else {
Err(executable)
}
}
impl Compiler {
pub fn llvm_rc(executable: Cow<'static, str>) -> Result<Compiler, Cow<'static, str>> {
if_runnable(executable, |executable| {
CompilerType::LlvmRc {
has_no_preprocess: Command::new(executable)
.arg("/?")
.output()
.ok()
.map(|out| memmem::find(&out.stdout, b"no-preprocess").is_some())
.unwrap_or(false),
}
})
}
pub fn windres(executable: Cow<'static, str>) -> Result<Compiler, Cow<'static, str>> {
if_runnable(executable, |_| CompilerType::WindRes)
}
pub fn compile<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>, Wp: FnOnce(&mut Command) -> &mut Command>(
&self, out_dir: &str, prefix: &str, out_file: String, resource: &str, parameters: ParameterBundle<Ms, Mi, Is, Ii>, fo: &str, c: &str,
no_preprocess: &str, windres_params: Wp)
-> Result<String, Cow<'static, str>> {
match self.tp {
CompilerType::LlvmRc { has_no_preprocess } => {
let preprocessed_path = format!("{}/{}-preprocessed.rc", out_dir, prefix);
fs::write(&preprocessed_path,
cc_xc(apply_parameters_cc(cc::Build::new().define("RC_INVOKED", None), parameters))
.file(resource)
.cargo_metadata(false)
.include(out_dir)
.expand()).map_err(|e| e.to_string())?;
try_command(Command::new(&*self.executable)
.args(&[fo, &out_file])
.args(&[c, "65001"]) .args(if has_no_preprocess {
Some(no_preprocess)
} else {
None
})
.args(&["--", &preprocessed_path])
.stdin(Stdio::piped())
.current_dir(or_curdir(Path::new(resource).parent().expect("Resource parent nonexistent?"))),
Path::new(&self.executable),
"compile",
&preprocessed_path,
&out_file)?;
}
CompilerType::WindRes => {
try_command(apply_parameters(windres_params(Command::new(&*self.executable)
.args(&["--input", resource, "--output", &out_file, "--include-dir", out_dir, "--output-format=coff"])),
"-D",
"-I",
parameters),
Path::new(&self.executable),
"compile",
resource,
&out_file)?;
}
}
Ok(out_file)
}
}
fn apply_parameters_cc<'t, Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(to: &'t mut cc::Build,
parameters: ParameterBundle<Ms,
Mi,
Is,
Ii>)
-> &'t mut cc::Build {
for m in parameters.macros {
let mut m = m.as_ref().to_str().expect("macros must be UTF-8 in this configuration").splitn(2, '=');
to.define(m.next().unwrap(), m.next());
}
for id in parameters.include_dirs {
to.include(id.as_ref());
}
to
}
fn cc_xc(to: &mut cc::Build) -> &mut cc::Build {
if to.get_compiler().is_like_msvc() {
to.flag("-Xclang");
}
to.flag("-xc");
to
}
fn try_command(cmd: &mut Command, exec: &Path, action: &str, whom: &str, whre: &str) -> Result<(), Cow<'static, str>> {
match cmd.status() {
Ok(stat) if stat.success() => Ok(()),
Ok(stat) => Err(format!("{} failed to {} \"{}\" into \"{}\" with {}", exec.display(), action, whom, whre, stat).into()),
Err(e) => Err(format!("Couldn't execute {} to {} \"{}\" into \"{}\": {}", exec.display(), action, whom, whre, e).into()),
}
}
fn or_curdir(directory: &Path) -> &Path {
if directory == Path::new("") {
Path::new(".")
} else {
directory
}
}
}