1//! A build script dependency to create a Windows resource file for the compiler
2//!
3//! Uses values from the `CFG_VERSION` and `CFG_RELEASE` environment variables
4//! to set the product and file version information in the Windows resource file.
5use std::{env, fs, path, process};
67/// The template for the Windows resource file.
8const RESOURCE_TEMPLATE: &str = "// A template for the rustc_driver and rustc Windows resource files.\n// This file is processed by the build script to produce rustc.rc and rustc_driver.rc\n// with the appropriate version information filled in.\n\n// All the strings are in UTF-8\n#pragma code_page(65001)\n\n#define RUSTC_FILEDESCRIPTION_STR \"@RUSTC_FILEDESCRIPTION_STR@\"\n#define RUSTC_FILETYPE @RUSTC_FILETYPE@\n#define RUSTC_FILEVERSION_QUAD @RUSTC_FILEVERSION_QUAD@\n#define RUSTC_FILEVERSION_STR \"@RUSTC_FILEVERSION_STR@\"\n\n#define RUSTC_PRODUCTNAME_STR \"@RUSTC_PRODUCTNAME_STR@\"\n#define RUSTC_PRODUCTVERSION_QUAD @RUSTC_PRODUCTVERSION_QUAD@\n#define RUSTC_PRODUCTVERSION_STR \"@RUSTC_PRODUCTVERSION_STR@\"\n\n\n1 VERSIONINFO\nFILEVERSION RUSTC_FILEVERSION_QUAD\nPRODUCTVERSION RUSTC_PRODUCTVERSION_QUAD\nFILEOS 0x00040004\nFILETYPE RUSTC_FILETYPE\nFILESUBTYPE 0\nFILEFLAGSMASK 0x3f\nFILEFLAGS 0x0\n{\n BLOCK \"StringFileInfo\"\n {\n BLOCK \"000004b0\"\n {\n VALUE \"FileDescription\", RUSTC_FILEDESCRIPTION_STR\n VALUE \"FileVersion\", RUSTC_FILEVERSION_STR\n VALUE \"ProductVersion\", RUSTC_PRODUCTVERSION_STR\n VALUE \"ProductName\", RUSTC_PRODUCTNAME_STR\n }\n }\n BLOCK \"VarFileInfo\" {\n VALUE \"Translation\", 0x0, 0x04b0\n }\n}\n"include_str!("../rustc.rc.in");
910/// A subset of the possible values for the `FILETYPE` field in a Windows resource file
11///
12/// See the `dwFileType` member of [VS_FIXEDFILEINFO](https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo#members)
13#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VersionInfoFileType {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
VersionInfoFileType::App => "App",
VersionInfoFileType::Dll => "Dll",
})
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VersionInfoFileType {
#[inline]
fn clone(&self) -> VersionInfoFileType { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for VersionInfoFileType { }Copy)]
14#[repr(u32)]
15pub enum VersionInfoFileType {
16/// `VFT_APP` - The file is an application.
17App = 0x00000001,
18/// `VFT_DLL` - The file is a dynamic link library.
19Dll = 0x00000002,
20}
2122/// Create and compile a Windows resource file with the product and file version information for the rust compiler.
23///
24/// Returns the path to the compiled resource file
25///
26/// Does not emit any cargo directives, the caller is responsible for that.
27pub fn compile_windows_resource_file(
28 file_stem: &path::Path,
29 file_description: &str,
30 filetype: VersionInfoFileType,
31) -> path::PathBuf {
32let mut resources_dir = path::PathBuf::from(env::var_os("OUT_DIR").unwrap());
33resources_dir.push("resources");
34 fs::create_dir_all(&resources_dir).unwrap();
3536let resource_compiler = if let Ok(path) = env::var("RUSTC_WINDOWS_RC") {
37path.into()
38 } else {
39 find_msvc_tools::find_tool(&env::var("CARGO_CFG_TARGET_ARCH").unwrap(), "rc.exe")
40 .expect("found rc.exe")
41 .path()
42 .to_owned()
43 };
4445let rc_path = resources_dir.join(file_stem.with_extension("rc"));
4647write_resource_script_file(&rc_path, file_description, filetype);
4849let res_path = resources_dir.join(file_stem.with_extension("res"));
5051let status = process::Command::new(resource_compiler)
52 .arg("/fo")
53 .arg(&res_path)
54 .arg(&rc_path)
55 .status()
56 .expect("can execute resource compiler");
57if !status.success() {
{
::core::panicking::panic_fmt(format_args!("rc.exe failed with status {0}",
status));
}
};assert!(status.success(), "rc.exe failed with status {}", status);
58if !res_path.try_exists().unwrap_or(false) {
{
::core::panicking::panic_fmt(format_args!("resource file {0} was not created",
res_path.display()));
}
};assert!(
59 res_path.try_exists().unwrap_or(false),
60"resource file {} was not created",
61 res_path.display()
62 );
63res_path64}
6566/// Writes a Windows resource script file for the rust compiler with the product and file version information
67/// into `rc_path`
68fn write_resource_script_file(
69 rc_path: &path::Path,
70 file_description: &str,
71 filetype: VersionInfoFileType,
72) {
73let mut resource_script = RESOURCE_TEMPLATE.to_string();
7475// Set the string product and file version to the same thing as `rustc --version`
76let descriptive_version = env::var("CFG_VERSION").unwrap_or("unknown".to_string());
7778// Set the product name to "Rust Compiler" or "Rust Compiler (nightly)" etc
79let product_name = product_name(env::var("CFG_RELEASE_CHANNEL").unwrap());
8081// For the numeric version we need `major,minor,patch,build`.
82 // Extract them from `CFG_RELEASE` which is "major.minor.patch" and a "-dev", "-nightly" or similar suffix
83let cfg_release = env::var("CFG_RELEASE").unwrap();
84// remove the suffix, if present and parse into [`ResourceVersion`]
85let version = parse_version(cfg_release.split("-").next().unwrap_or("0.0.0"))
86 .expect("valid CFG_RELEASE version");
8788resource_script = resource_script89 .replace("@RUSTC_FILEDESCRIPTION_STR@", file_description)
90 .replace("@RUSTC_FILETYPE@", &::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}", filetype as u32))
})format!("{}", filetype as u32))
91 .replace("@RUSTC_FILEVERSION_QUAD@", &version.to_quad_string())
92 .replace("@RUSTC_FILEVERSION_STR@", &descriptive_version)
93 .replace("@RUSTC_PRODUCTNAME_STR@", &product_name)
94 .replace("@RUSTC_PRODUCTVERSION_QUAD@", &version.to_quad_string())
95 .replace("@RUSTC_PRODUCTVERSION_STR@", &descriptive_version);
9697 fs::write(&rc_path, resource_script)
98 .unwrap_or_else(|_| {
::core::panicking::panic_fmt(format_args!("failed to write resource file {0}",
rc_path.display()));
}panic!("failed to write resource file {}", rc_path.display()));
99}
100101fn product_name(channel: String) -> String {
102::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Rust Compiler{0}",
if channel == "stable" {
"".to_string()
} else {
::alloc::__export::must_use({
::alloc::fmt::format(format_args!(" ({0})", channel))
})
}))
})format!(
103"Rust Compiler{}",
104if channel == "stable" { "".to_string() } else { format!(" ({})", channel) }
105 )106}
107108/// Windows resources store versions as four 16-bit integers.
109struct ResourceVersion {
110 major: u16,
111 minor: u16,
112 patch: u16,
113 build: u16,
114}
115116impl ResourceVersion {
117/// Format the version as a comma-separated string of four integers
118 /// as expected by Windows resource scripts for the `FILEVERSION` and `PRODUCTVERSION` fields.
119fn to_quad_string(&self) -> String {
120::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0},{1},{2},{3}", self.major,
self.minor, self.patch, self.build))
})format!("{},{},{},{}", self.major, self.minor, self.patch, self.build)121 }
122}
123124/// Parse a string in the format "major.minor.patch" into a [`ResourceVersion`].
125/// The build is set to 0.
126/// Returns `None` if the version string is not in the expected format.
127fn parse_version(version: &str) -> Option<ResourceVersion> {
128let mut parts = version.split('.');
129let major = parts.next()?.parse::<u16>().ok()?;
130let minor = parts.next()?.parse::<u16>().ok()?;
131let patch = parts.next()?.parse::<u16>().ok()?;
132if parts.next().is_some() {
133None134 } else {
135Some(ResourceVersion { major, minor, patch, build: 0 })
136 }
137}