rustc_windows_rc/
lib.rs

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, ffi, fs, path, process};
6
7use cc::windows_registry;
8
9/// The template for the Windows resource file.
10const RESOURCE_TEMPLATE: &str = include_str!("../rustc.rc.in");
11
12/// A subset of the possible values for the `FILETYPE` field in a Windows resource file
13///
14/// See the `dwFileType` member of [VS_FIXEDFILEINFO](https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo#members)
15#[derive(Debug, Clone, Copy)]
16#[repr(u32)]
17pub enum VersionInfoFileType {
18    /// `VFT_APP` - The file is an application.
19    App = 0x00000001,
20    /// `VFT_DLL` - The file is a dynamic link library.
21    Dll = 0x00000002,
22}
23
24/// Create and compile a Windows resource file with the product and file version information for the rust compiler.
25///
26/// Returns the path to the compiled resource file
27///
28/// Does not emit any cargo directives, the caller is responsible for that.
29pub fn compile_windows_resource_file(
30    file_stem: &path::Path,
31    file_description: &str,
32    filetype: VersionInfoFileType,
33) -> path::PathBuf {
34    let mut resources_dir = path::PathBuf::from(env::var_os("OUT_DIR").unwrap());
35    resources_dir.push("resources");
36    fs::create_dir_all(&resources_dir).unwrap();
37
38    let resource_compiler =
39        find_resource_compiler(&env::var("CARGO_CFG_TARGET_ARCH").unwrap()).expect("found rc.exe");
40
41    let rc_path = resources_dir.join(file_stem.with_extension("rc"));
42
43    write_resource_script_file(&rc_path, file_description, filetype);
44
45    let res_path = resources_dir.join(file_stem.with_extension("res"));
46
47    let status = process::Command::new(resource_compiler)
48        .arg("/fo")
49        .arg(&res_path)
50        .arg(&rc_path)
51        .status()
52        .expect("can execute resource compiler");
53    assert!(status.success(), "rc.exe failed with status {}", status);
54    assert!(
55        res_path.try_exists().unwrap_or(false),
56        "resource file {} was not created",
57        res_path.display()
58    );
59    res_path
60}
61
62/// Writes a Windows resource script file for the rust compiler with the product and file version information
63/// into `rc_path`
64fn write_resource_script_file(
65    rc_path: &path::Path,
66    file_description: &str,
67    filetype: VersionInfoFileType,
68) {
69    let mut resource_script = RESOURCE_TEMPLATE.to_string();
70
71    // Set the string product and file version to the same thing as `rustc --version`
72    let descriptive_version = env::var("CFG_VERSION").unwrap_or("unknown".to_string());
73
74    // Set the product name to "Rust Compiler" or "Rust Compiler (nightly)" etc
75    let product_name = product_name(env::var("CFG_RELEASE_CHANNEL").unwrap());
76
77    // For the numeric version we need `major,minor,patch,build`.
78    // Extract them from `CFG_RELEASE` which is "major.minor.patch" and a "-dev", "-nightly" or similar suffix
79    let cfg_release = env::var("CFG_RELEASE").unwrap();
80    // remove the suffix, if present and parse into [`ResourceVersion`]
81    let version = parse_version(cfg_release.split("-").next().unwrap_or("0.0.0"))
82        .expect("valid CFG_RELEASE version");
83
84    resource_script = resource_script
85        .replace("@RUSTC_FILEDESCRIPTION_STR@", file_description)
86        .replace("@RUSTC_FILETYPE@", &format!("{}", filetype as u32))
87        .replace("@RUSTC_FILEVERSION_QUAD@", &version.to_quad_string())
88        .replace("@RUSTC_FILEVERSION_STR@", &descriptive_version)
89        .replace("@RUSTC_PRODUCTNAME_STR@", &product_name)
90        .replace("@RUSTC_PRODUCTVERSION_QUAD@", &version.to_quad_string())
91        .replace("@RUSTC_PRODUCTVERSION_STR@", &descriptive_version);
92
93    fs::write(&rc_path, resource_script)
94        .unwrap_or_else(|_| panic!("failed to write resource file {}", rc_path.display()));
95}
96
97fn product_name(channel: String) -> String {
98    format!(
99        "Rust Compiler{}",
100        if channel == "stable" { "".to_string() } else { format!(" ({})", channel) }
101    )
102}
103
104/// Windows resources store versions as four 16-bit integers.
105struct ResourceVersion {
106    major: u16,
107    minor: u16,
108    patch: u16,
109    build: u16,
110}
111
112impl ResourceVersion {
113    /// Format the version as a comma-separated string of four integers
114    /// as expected by Windows resource scripts for the `FILEVERSION` and `PRODUCTVERSION` fields.
115    fn to_quad_string(&self) -> String {
116        format!("{},{},{},{}", self.major, self.minor, self.patch, self.build)
117    }
118}
119
120/// Parse a string in the format "major.minor.patch" into a [`ResourceVersion`].
121/// The build is set to 0.
122/// Returns `None` if the version string is not in the expected format.
123fn parse_version(version: &str) -> Option<ResourceVersion> {
124    let mut parts = version.split('.');
125    let major = parts.next()?.parse::<u16>().ok()?;
126    let minor = parts.next()?.parse::<u16>().ok()?;
127    let patch = parts.next()?.parse::<u16>().ok()?;
128    if parts.next().is_some() {
129        None
130    } else {
131        Some(ResourceVersion { major, minor, patch, build: 0 })
132    }
133}
134
135/// Find the Windows SDK resource compiler `rc.exe` for the given architecture or target triple.
136/// Returns `None` if the tool could not be found.
137fn find_resource_compiler(arch_or_target: &str) -> Option<path::PathBuf> {
138    find_windows_sdk_tool(arch_or_target, "rc.exe")
139}
140
141/// Find a Windows SDK tool for the given architecture or target triple.
142/// Returns `None` if the tool could not be found.
143fn find_windows_sdk_tool(arch_or_target: &str, tool_name: &str) -> Option<path::PathBuf> {
144    // windows_registry::find_tool can only find MSVC tools, not Windows SDK tools, but
145    // cc does include the Windows SDK tools in the PATH environment of MSVC tools.
146
147    let msvc_linker = windows_registry::find_tool(arch_or_target, "link.exe")?;
148    let path = &msvc_linker.env().iter().find(|(k, _)| k == "PATH")?.1;
149    find_tool_in_path(tool_name, path)
150}
151
152/// Find a tool in the directories in a given PATH-like string.
153fn find_tool_in_path<P: AsRef<ffi::OsStr>>(tool_name: &str, path: P) -> Option<path::PathBuf> {
154    env::split_paths(path.as_ref()).find_map(|p| {
155        let tool_path = p.join(tool_name);
156        if tool_path.try_exists().unwrap_or(false) { Some(tool_path) } else { None }
157    })
158}