| //! A build script dependency to create a Windows resource file for the compiler | |
| //! | |
| //! Uses values from the `CFG_VERSION` and `CFG_RELEASE` environment variables | |
| //! to set the product and file version information in the Windows resource file. | |
| use std::{env, fs, path, process}; | |
| /// The template for the Windows resource file. | |
| const RESOURCE_TEMPLATE: &str = include_str!("../rustc.rc.in"); | |
| /// A subset of the possible values for the `FILETYPE` field in a Windows resource file | |
| /// | |
| /// See the `dwFileType` member of [VS_FIXEDFILEINFO](https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo#members) | |
| #[derive(Debug, Clone, Copy)] | |
| #[repr(u32)] | |
| pub enum VersionInfoFileType { | |
| /// `VFT_APP` - The file is an application. | |
| App = 0x00000001, | |
| /// `VFT_DLL` - The file is a dynamic link library. | |
| Dll = 0x00000002, | |
| } | |
| /// Create and compile a Windows resource file with the product and file version information for the rust compiler. | |
| /// | |
| /// Returns the path to the compiled resource file | |
| /// | |
| /// Does not emit any cargo directives, the caller is responsible for that. | |
| pub fn compile_windows_resource_file( | |
| file_stem: &path::Path, | |
| file_description: &str, | |
| filetype: VersionInfoFileType, | |
| ) -> path::PathBuf { | |
| let mut resources_dir = path::PathBuf::from(env::var_os("OUT_DIR").unwrap()); | |
| resources_dir.push("resources"); | |
| fs::create_dir_all(&resources_dir).unwrap(); | |
| let resource_compiler = if let Ok(path) = env::var("RUSTC_WINDOWS_RC") { | |
| path.into() | |
| } else { | |
| find_msvc_tools::find_tool(&env::var("CARGO_CFG_TARGET_ARCH").unwrap(), "rc.exe") | |
| .expect("found rc.exe") | |
| .path() | |
| .to_owned() | |
| }; | |
| let rc_path = resources_dir.join(file_stem.with_extension("rc")); | |
| write_resource_script_file(&rc_path, file_description, filetype); | |
| let res_path = resources_dir.join(file_stem.with_extension("res")); | |
| let status = process::Command::new(resource_compiler) | |
| .arg("/fo") | |
| .arg(&res_path) | |
| .arg(&rc_path) | |
| .status() | |
| .expect("can execute resource compiler"); | |
| assert!(status.success(), "rc.exe failed with status {}", status); | |
| assert!( | |
| res_path.try_exists().unwrap_or(false), | |
| "resource file {} was not created", | |
| res_path.display() | |
| ); | |
| res_path | |
| } | |
| /// Writes a Windows resource script file for the rust compiler with the product and file version information | |
| /// into `rc_path` | |
| fn write_resource_script_file( | |
| rc_path: &path::Path, | |
| file_description: &str, | |
| filetype: VersionInfoFileType, | |
| ) { | |
| let mut resource_script = RESOURCE_TEMPLATE.to_string(); | |
| // Set the string product and file version to the same thing as `rustc --version` | |
| let descriptive_version = env::var("CFG_VERSION").unwrap_or("unknown".to_string()); | |
| // Set the product name to "Rust Compiler" or "Rust Compiler (nightly)" etc | |
| let product_name = product_name(env::var("CFG_RELEASE_CHANNEL").unwrap()); | |
| // For the numeric version we need `major,minor,patch,build`. | |
| // Extract them from `CFG_RELEASE` which is "major.minor.patch" and a "-dev", "-nightly" or similar suffix | |
| let cfg_release = env::var("CFG_RELEASE").unwrap(); | |
| // remove the suffix, if present and parse into [`ResourceVersion`] | |
| let version = parse_version(cfg_release.split("-").next().unwrap_or("0.0.0")) | |
| .expect("valid CFG_RELEASE version"); | |
| resource_script = resource_script | |
| .replace("@RUSTC_FILEDESCRIPTION_STR@", file_description) | |
| .replace("@RUSTC_FILETYPE@", &format!("{}", filetype as u32)) | |
| .replace("@RUSTC_FILEVERSION_QUAD@", &version.to_quad_string()) | |
| .replace("@RUSTC_FILEVERSION_STR@", &descriptive_version) | |
| .replace("@RUSTC_PRODUCTNAME_STR@", &product_name) | |
| .replace("@RUSTC_PRODUCTVERSION_QUAD@", &version.to_quad_string()) | |
| .replace("@RUSTC_PRODUCTVERSION_STR@", &descriptive_version); | |
| fs::write(&rc_path, resource_script) | |
| .unwrap_or_else(|_| panic!("failed to write resource file {}", rc_path.display())); | |
| } | |
| fn product_name(channel: String) -> String { | |
| format!( | |
| "Rust Compiler{}", | |
| if channel == "stable" { "".to_string() } else { format!(" ({})", channel) } | |
| ) | |
| } | |
| /// Windows resources store versions as four 16-bit integers. | |
| struct ResourceVersion { | |
| major: u16, | |
| minor: u16, | |
| patch: u16, | |
| build: u16, | |
| } | |
| impl ResourceVersion { | |
| /// Format the version as a comma-separated string of four integers | |
| /// as expected by Windows resource scripts for the `FILEVERSION` and `PRODUCTVERSION` fields. | |
| fn to_quad_string(&self) -> String { | |
| format!("{},{},{},{}", self.major, self.minor, self.patch, self.build) | |
| } | |
| } | |
| /// Parse a string in the format "major.minor.patch" into a [`ResourceVersion`]. | |
| /// The build is set to 0. | |
| /// Returns `None` if the version string is not in the expected format. | |
| fn parse_version(version: &str) -> Option<ResourceVersion> { | |
| let mut parts = version.split('.'); | |
| let major = parts.next()?.parse::<u16>().ok()?; | |
| let minor = parts.next()?.parse::<u16>().ok()?; | |
| let patch = parts.next()?.parse::<u16>().ok()?; | |
| if parts.next().is_some() { | |
| None | |
| } else { | |
| Some(ResourceVersion { major, minor, patch, build: 0 }) | |
| } | |
| } |