use self::super::{ParameterBundle, env_target_and_rc, apply_parameters};
use std::process::{Command, Stdio};
use std::ffi::{OsString, OsStr};
use std::path::{PathBuf, Path};
use std::borrow::Cow;
use std::{fs, mem};
use memchr::memmem;
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ResourceCompiler {
compiler: Result<Compiler, Cow<'static, str>>,
}
impl ResourceCompiler {
pub fn new() -> ResourceCompiler {
ResourceCompiler { compiler: Compiler::probe() }
}
#[inline]
pub fn is_supported(&mut self) -> Option<Cow<'static, str>> {
match mem::replace(&mut self.compiler, Err("".into())) {
Ok(c) => {
self.compiler = Ok(c);
None
}
Err(e) => Some(e),
}
}
pub fn compile_resource<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(
&self, out_dir: &str, prefix: &str, resource: &str, parameters: ParameterBundle<Ms, Mi, Is, Ii>)
-> Result<String, Cow<'static, str>> {
self.compiler.as_ref().expect("Not supported but we got to compile_resource()?").compile(out_dir, prefix, resource, parameters)
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
enum CompilerType {
LlvmRc { has_no_preprocess: bool, },
WindRes,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct Compiler {
tp: CompilerType,
executable: Cow<'static, OsStr>,
}
impl Compiler {
pub fn probe() -> Result<Compiler, Cow<'static, str>> {
let (target, rc) = env_target_and_rc()?;
if let Some(rc) = rc {
return guess_compiler_variant(rc);
}
if target.ends_with("-windows-gnu") || target.ends_with("-windows-gnullvm") {
let executable = format!("{}-w64-mingw32-windres", &target[0..target.find('-').unwrap_or_default()]);
if is_runnable(&executable) {
return Ok(Compiler {
tp: CompilerType::WindRes,
executable: OsString::from(executable).into(),
});
} else {
return Err(executable.into());
}
} else if target.ends_with("-windows-msvc") {
if is_runnable("llvm-rc") {
return Ok(Compiler {
tp: CompilerType::LlvmRc {
has_no_preprocess: Command::new("llvm-rc")
.arg("/?")
.output()
.ok()
.map(|out| memmem::find(&out.stdout, b"no-preprocess").is_some())
.unwrap_or(false),
},
executable: OsStr::new("llvm-rc").into(),
});
} else {
return Err("llvm-rc".into());
}
}
Err("".into())
}
pub fn compile<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(&self, out_dir: &str, prefix: &str,
resource: &str,
parameters: ParameterBundle<Ms, Mi, Is, Ii>)
-> Result<String, Cow<'static, str>> {
let out_file = format!("{}/{}.lib", out_dir, prefix);
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 {
&["/no-preprocess"][..]
} else {
&[][..]
})
.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(Command::new(&self.executable)
.args(&["--input", resource, "--output-format=coff", "--output", &out_file, "--include-dir", out_dir]),
"-D",
"--include-dir",
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
}
}
fn guess_compiler_variant(s: OsString) -> Result<Compiler, Cow<'static, str>> {
match Command::new(&s).args(&["-V", "/?"]).output() {
Ok(out) => {
if out.stdout.starts_with(b"GNU windres") {
Ok(Compiler {
executable: s.into(),
tp: CompilerType::WindRes,
})
} else if out.stdout.starts_with(b"OVERVIEW: Resource Converter") || out.stdout.starts_with(b"OVERVIEW: LLVM Resource Converter") {
Ok(Compiler {
executable: s.into(),
tp: CompilerType::LlvmRc { has_no_preprocess: memmem::find(&out.stdout, b"no-preprocess").is_some() },
})
} else {
Err(format!("Unknown RC compiler variant: {}", Path::new(&s).display()).into()) }
}
Err(err) => Err(format!("Couldn't execute {}: {}", Path::new(&s).display(), err).into()),
}
}
fn is_runnable(s: &str) -> bool {
Command::new(s).spawn().map(|mut c| c.kill()).is_ok()
}
pub fn find_windows_sdk_tool_impl(_: &str) -> Option<PathBuf> {
None
}