use std::env;
use std::path::{PathBuf, Path};
use std::process;
use std::collections::HashMap;
use std::io;
use std::io::prelude::*;
use std::fs;
use std::error::Error;
extern crate toml;
pub enum Toolkit {
MSVC,
GNU,
Unknown,
}
#[derive(PartialEq, Eq, Hash, Debug)]
pub enum VersionInfo {
FILEVERSION,
PRODUCTVERSION,
FILEOS,
FILETYPE,
FILESUBTYPE,
FILEFLAGSMASK,
FILEFLAGS,
}
pub struct WindowsResource {
toolkit_path: String,
properties: HashMap<String, String>,
version_info: HashMap<VersionInfo, u64>,
rc_file: Option<String>,
icon: Option<String>,
language: u16,
manifest: Option<String>,
manifest_file: Option<String>,
output_directory: String,
}
impl WindowsResource {
pub fn toolkit() -> Toolkit {
if cfg!(target_env = "gnu") {
Toolkit::GNU
} else if cfg!(target_env = "msvc") {
Toolkit::MSVC
} else {
Toolkit::Unknown
}
}
pub fn new() -> Self {
let mut props: HashMap<String, String> = HashMap::new();
let mut ver: HashMap<VersionInfo, u64> = HashMap::new();
props.insert("FileVersion".to_string(),
env::var("CARGO_PKG_VERSION").unwrap().to_string());
props.insert("ProductVersion".to_string(),
env::var("CARGO_PKG_VERSION").unwrap().to_string());
props.insert("ProductName".to_string(),
env::var("CARGO_PKG_NAME").unwrap().to_string());
props.insert("FileDescription".to_string(),
env::var("CARGO_PKG_DESCRIPTION").unwrap().to_string());
parse_cargo_toml(&mut props).unwrap();
let mut version = 0 as u64;
version |= env::var("CARGO_PKG_VERSION_MAJOR").unwrap().parse().unwrap_or(0) << 48;
version |= env::var("CARGO_PKG_VERSION_MINOR").unwrap().parse().unwrap_or(0) << 32;
version |= env::var("CARGO_PKG_VERSION_PATCH").unwrap().parse().unwrap_or(0) << 16;
ver.insert(VersionInfo::FILEVERSION, version);
ver.insert(VersionInfo::PRODUCTVERSION, version);
ver.insert(VersionInfo::FILEOS, 0x00040004);
ver.insert(VersionInfo::FILETYPE, 1);
ver.insert(VersionInfo::FILESUBTYPE, 0);
ver.insert(VersionInfo::FILEFLAGSMASK, 0x3F);
ver.insert(VersionInfo::FILEFLAGS, 0);
let sdk = match get_sdk() {
Ok(mut v) => v.pop().unwrap(),
Err(_) => String::new(),
};
WindowsResource {
toolkit_path: sdk,
properties: props,
version_info: ver,
rc_file: None,
icon: None,
language: 0,
manifest: None,
manifest_file: None,
output_directory: env::var("OUT_DIR").unwrap_or(".".to_string()),
}
}
pub fn set<'a>(&mut self, name: &'a str, value: &'a str) -> &mut Self {
self.properties.insert(name.to_string(), value.to_string());
self
}
pub fn set_toolkit_path<'a>(&mut self, path: &'a str) -> &mut Self {
self.toolkit_path = path.to_string();
self
}
pub fn set_language(&mut self, language: u16) -> &mut Self {
self.language = language;
self
}
pub fn set_icon<'a>(&mut self, path: &'a str) -> &mut Self {
self.icon = Some(path.to_string());
self
}
pub fn set_version_info(&mut self, field: VersionInfo, value: u64) -> &mut Self {
self.version_info.insert(field, value);
self
}
pub fn set_manifest<'a>(&mut self, manifest: &'a str) -> &mut Self {
self.manifest_file = None;
self.manifest = Some(manifest.to_string());
self
}
pub fn set_manifest_file<'a>(&mut self, file: &'a str) -> &mut Self {
self.manifest_file = Some(file.to_string());
self.manifest = None;
self
}
pub fn write_resource_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
let mut f = try!(fs::File::create(path));
try!(writeln!(f, "#pragma code_page(65001)"));
try!(writeln!(f, "1 VERSIONINFO"));
for (k, v) in self.version_info.iter() {
match *k {
VersionInfo::FILEVERSION |
VersionInfo::PRODUCTVERSION => {
try!(writeln!(f,
"{:?} {}, {}, {}, {}",
k,
(*v >> 48) as u16,
(*v >> 32) as u16,
(*v >> 16) as u16,
*v as u16))
}
_ => try!(writeln!(f, "{:?} {:#x}", k, v)),
};
}
try!(writeln!(f, "{{\nBLOCK \"StringFileInfo\""));
try!(writeln!(f, "{{\nBLOCK \"{:04x}04b0\"\n{{", self.language));
for (k, v) in self.properties.iter() {
if !v.is_empty() {
try!(writeln!(f, "VALUE \"{}\", \"{}\"", k, v));
}
}
try!(writeln!(f, "}}\n}}"));
try!(writeln!(f, "BLOCK \"VarFileInfo\" {{"));
try!(writeln!(f, "VALUE \"Translation\", {:#x}, 0x04b0", self.language));
try!(writeln!(f, "}}\n}}"));
if self.icon.is_some() {
try!(writeln!(f, "1 ICON \"{}\"", self.icon.as_ref().unwrap()));
}
if let Some(e) = self.version_info.get(&VersionInfo::FILETYPE) {
if let Some(manf) = self.manifest.as_ref() {
try!(writeln!(f, "{} 24", e));
try!(writeln!(f, "{{"));
for line in manf.lines() {
try!(writeln!(f, "\"{}\"", line.replace("\"", "\"\"").trim()));
}
try!(writeln!(f, "}}"));
} else if let Some(manf) = self.manifest_file.as_ref() {
try!(writeln!(f, "{} 24 \"{}\"", e, manf));
}
}
Ok(())
}
pub fn set_resource_file<'a>(&mut self, path: &'a str) -> &mut Self {
self.rc_file = Some(path.to_string());
self
}
pub fn set_output_directory<'a>(&mut self, path: &'a str) -> &mut Self {
self.output_directory = path.to_string();
self
}
#[cfg(target_env = "gnu")]
fn compile_with_toolkit<'a>(&self, input: &'a str, output_dir: &'a str) -> io::Result<()> {
let output = PathBuf::from(output_dir).join("resource.o");
let input = PathBuf::from(input);
let status = try!(process::Command::new("windres.exe")
.current_dir(&self.toolkit_path)
.arg(format!("-I{}", env::var("CARGO_MANIFEST_DIR").unwrap()))
.arg(format!("{}", input.display()))
.arg(format!("{}", output.display()))
.status());
if !status.success() {
return Err(io::Error::new(io::ErrorKind::Other, "Could not compile resource file"));
}
let libname = PathBuf::from(output_dir).join("libresource.a");
let status = try!(process::Command::new("ar.exe")
.current_dir(&self.toolkit_path)
.arg("rsc")
.arg(format!("{}", libname.display()))
.arg(format!("{}", output.display()))
.status());
if !status.success() {
return Err(io::Error::new(io::ErrorKind::Other,
"Could not create static library for resource file"));
}
println!("cargo:rustc-link-search=native={}", output_dir);
println!("cargo:rustc-link-lib=static={}", "resource");
Ok(())
}
pub fn compile(&self) -> io::Result<()> {
let output = PathBuf::from(&self.output_directory);
let rc = output.join("resource.rc");
if self.rc_file.is_none() {
try!(self.write_resource_file(&rc));
}
let rc = if let Some(s) = self.rc_file.as_ref() {
s.clone()
} else {
rc.to_str().unwrap().to_string()
};
try!(self.compile_with_toolkit(rc.as_str(), &self.output_directory));
Ok(())
}
#[cfg(target_env = "msvc")]
fn compile_with_toolkit<'a>(&self, input: &'a str, output_dir: &'a str) -> io::Result<()> {
let rc_exe = if cfg!(target_arch = "x86_64") {
PathBuf::from(&self.toolkit_path).join("bin\\x64\\rc.exe")
} else {
PathBuf::from(&self.toolkit_path).join("bin\\x86\\rc.exe")
};
let output = PathBuf::from(output_dir).join("resource.lib");
let input = PathBuf::from(input);
let status = try!(process::Command::new(rc_exe)
.arg(format!("/I{}", env::var("CARGO_MANIFEST_DIR").unwrap()))
.arg("/nologo")
.arg(format!("/fo{}", output.display()))
.arg(format!("{}", input.display()))
.status());
if !status.success() {
return Err(io::Error::new(io::ErrorKind::Other, "Could not compile resource file"));
}
println!("cargo:rustc-link-search=native={}", output_dir);
println!("cargo:rustc-link-lib=static={}", "resource");
Ok(())
}
}
fn get_sdk() -> io::Result<Vec<String>> {
let output = try!(process::Command::new("reg")
.arg("query")
.arg("HKLM\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots")
.output());
let lines = try!(String::from_utf8(output.stdout)
.or_else(|e| Err(io::Error::new(io::ErrorKind::Other, e.description()))));
let mut kits: Vec<String> = Vec::new();
for line in lines.lines() {
if line.trim().starts_with("KitsRoot") {
kits.push(line.chars()
.skip(line.find("REG_SZ").unwrap() + 6)
.skip_while(|c| c.is_whitespace())
.collect());
}
}
Ok(kits)
}
fn parse_cargo_toml(props: &mut HashMap<String, String>) -> io::Result<()> {
let cargo = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("Cargo.toml");
let mut f = try!(fs::File::open(cargo));
let mut cargo_toml = String::new();
try!(f.read_to_string(&mut cargo_toml));
if let Some(ml) = toml::Parser::new(&cargo_toml).parse() {
if let Some(pkg) = ml.get("package") {
if let Some(pkg) = pkg.lookup("metadata.winres") {
if let Some(pkg) = pkg.as_table() {
for (k, v) in pkg {
if let Some(v) = v.as_str() {
props.insert(k.clone(), v.to_string());
} else {
println!("package.metadata.winres.{} is not a string", k);
}
}
} else {
println!("package.metadata.winres is not a table");
}
} else {
println!("package.metadata.winres does not exist");
}
} else {
println!("package section missing");
}
} else {
println!("parsing error")
}
Ok(())
}