Skip to content

Commit f0ddbf2

Browse files
committed
compiler: Add Windows resources to rustc-main and rustc_driver
Adds Windows resources with the rust version information to rustc-main.exe and rustc_driver.dll
1 parent fe55364 commit f0ddbf2

File tree

10 files changed

+245
-3
lines changed

10 files changed

+245
-3
lines changed

Cargo.lock

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3248,6 +3248,7 @@ dependencies = [
32483248
"rustc_driver_impl",
32493249
"rustc_public",
32503250
"rustc_public_bridge",
3251+
"rustc_windows_rc",
32513252
"tikv-jemalloc-sys",
32523253
]
32533254

@@ -3623,6 +3624,7 @@ name = "rustc_driver"
36233624
version = "0.0.0"
36243625
dependencies = [
36253626
"rustc_driver_impl",
3627+
"rustc_windows_rc",
36263628
]
36273629

36283630
[[package]]
@@ -4718,6 +4720,13 @@ dependencies = [
47184720
"semver",
47194721
]
47204722

4723+
[[package]]
4724+
name = "rustc_windows_rc"
4725+
version = "0.0.0"
4726+
dependencies = [
4727+
"cc",
4728+
]
4729+
47214730
[[package]]
47224731
name = "rustdoc"
47234732
version = "0.0.0"

compiler/rustc/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ llvm = ['rustc_driver_impl/llvm']
3333
max_level_info = ['rustc_driver_impl/max_level_info']
3434
rustc_randomized_layouts = ['rustc_driver_impl/rustc_randomized_layouts']
3535
# tidy-alphabetical-end
36+
37+
[build-dependencies]
38+
# tidy-alphabetical-start
39+
rustc_windows_rc = { path = "../rustc_windows_rc" }
40+
# tidy-alphabetical-end

compiler/rustc/build.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use std::env;
1+
use std::{env, path};
2+
3+
use rustc_windows_rc::{VersionInfoFileType, compile_windows_resource_file};
24

35
fn main() {
46
let target_os = env::var("CARGO_CFG_TARGET_OS");
@@ -13,6 +15,18 @@ fn main() {
1315

1416
// Add a manifest file to rustc.exe.
1517
fn set_windows_exe_options() {
18+
set_windows_resource();
19+
set_windows_manifest();
20+
}
21+
22+
fn set_windows_resource() {
23+
let stem = path::PathBuf::from("rustc_main_resource");
24+
let file_description = "rustc";
25+
let res_file = compile_windows_resource_file(&stem, file_description, VersionInfoFileType::App);
26+
println!("cargo:rustc-link-arg={}", res_file.display());
27+
}
28+
29+
fn set_windows_manifest() {
1630
static WINDOWS_MANIFEST_FILE: &str = "Windows Manifest.xml";
1731

1832
let mut manifest = env::current_dir().unwrap();

compiler/rustc_codegen_ssa/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ ar_archive_writer = "0.4.2"
99
bitflags.workspace = true
1010
bstr = "1.11.3"
1111
# `cc` updates often break things, so we pin it here. Cargo enforces "max 1 semver-compat version
12-
# per crate", so if you change this, you need to also change it in `rustc_llvm`.
12+
# per crate", so if you change this, you need to also change it in `rustc_llvm` and `rustc_windows_rc`.
1313
cc = "=1.2.16"
1414
itertools.workspace = true
1515
pathdiff = "0.2.0"

compiler/rustc_driver/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ crate-type = ["dylib"]
1010
# tidy-alphabetical-start
1111
rustc_driver_impl = { path = "../rustc_driver_impl" }
1212
# tidy-alphabetical-end
13+
14+
[build-dependencies]
15+
# tidy-alphabetical-start
16+
rustc_windows_rc = { path = "../rustc_windows_rc" }
17+
# tidy-alphabetical-end

compiler/rustc_driver/build.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use std::{env, path};
2+
3+
use rustc_windows_rc::{VersionInfoFileType, compile_windows_resource_file};
4+
5+
fn main() {
6+
let target_os = env::var("CARGO_CFG_TARGET_OS");
7+
let target_env = env::var("CARGO_CFG_TARGET_ENV");
8+
if Ok("windows") == target_os.as_deref() && Ok("msvc") == target_env.as_deref() {
9+
set_windows_dll_options();
10+
} else {
11+
// Avoid rerunning the build script every time.
12+
println!("cargo:rerun-if-changed=build.rs");
13+
}
14+
}
15+
16+
fn set_windows_dll_options() {
17+
let stem = path::PathBuf::from("rustc_driver_resource");
18+
let file_description = "rustc_driver";
19+
let res_file = compile_windows_resource_file(&stem, file_description, VersionInfoFileType::Dll);
20+
println!("cargo:rustc-link-arg={}", res_file.display());
21+
}

compiler/rustc_llvm/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ libc.workspace = true
1111
[build-dependencies]
1212
# tidy-alphabetical-start
1313
# `cc` updates often break things, so we pin it here. Cargo enforces "max 1 semver-compat version
14-
# per crate", so if you change this, you need to also change it in `rustc_codegen_ssa`.
14+
# per crate", so if you change this, you need to also change it in `rustc_codegen_ssa` and `rustc_windows_rc`.
1515
cc = "=1.2.16"
1616
# tidy-alphabetical-end
1717

compiler/rustc_windows_rc/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "rustc_windows_rc"
3+
version = "0.0.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
#tidy-alphabetical-start
8+
# `cc` updates often break things, so we pin it here. Cargo enforces "max 1 semver-compat version
9+
# per crate", so if you change this, you need to also change it in `rustc_llvm` and `rustc_codegen_ssa`.
10+
cc = "=1.2.16"
11+
#tidy-alphabetical-end

compiler/rustc_windows_rc/rustc.rc.in

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// A template for the rustc_driver and rustc Windows resource files.
2+
// This file is processed by the build script to produce rustc.rc and rustc_driver.rc
3+
// with the appropriate version information filled in.
4+
5+
// All the strings are in UTF-8
6+
#pragma code_page(65001)
7+
8+
#define RUSTC_FILEDESCRIPTION_STR "@RUSTC_FILEDESCRIPTION_STR@"
9+
#define RUSTC_FILETYPE @RUSTC_FILETYPE@
10+
#define RUSTC_FILEVERSION_QUAD @RUSTC_FILEVERSION_QUAD@
11+
#define RUSTC_FILEVERSION_STR "@RUSTC_FILEVERSION_STR@"
12+
13+
#define RUSTC_PRODUCTNAME_STR "@RUSTC_PRODUCTNAME_STR@"
14+
#define RUSTC_PRODUCTVERSION_QUAD @RUSTC_PRODUCTVERSION_QUAD@
15+
#define RUSTC_PRODUCTVERSION_STR "@RUSTC_PRODUCTVERSION_STR@"
16+
17+
18+
1 VERSIONINFO
19+
FILEVERSION RUSTC_FILEVERSION_QUAD
20+
PRODUCTVERSION RUSTC_PRODUCTVERSION_QUAD
21+
FILEOS 0x00040004
22+
FILETYPE RUSTC_FILETYPE
23+
FILESUBTYPE 0
24+
FILEFLAGSMASK 0x3f
25+
FILEFLAGS 0x0
26+
{
27+
BLOCK "StringFileInfo"
28+
{
29+
BLOCK "000004b0"
30+
{
31+
VALUE "FileDescription", RUSTC_FILEDESCRIPTION_STR
32+
VALUE "FileVersion", RUSTC_FILEVERSION_STR
33+
VALUE "ProductVersion", RUSTC_PRODUCTVERSION_STR
34+
VALUE "ProductName", RUSTC_PRODUCTNAME_STR
35+
}
36+
}
37+
BLOCK "VarFileInfo" {
38+
VALUE "Translation", 0x0, 0x04b0
39+
}
40+
}

compiler/rustc_windows_rc/src/lib.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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.
5+
use std::{env, ffi, fs, path, process};
6+
7+
use cc::windows_registry;
8+
9+
/// The template for the Windows resource file.
10+
const RESOURCE_TEMPLATE: &str = include_str!("../rustc.rc.in");
11+
12+
const VFT_APP: u32 = 0x00000001; // VFT_APP
13+
const VFT_DLL: u32 = 0x00000002; // VFT_DLL
14+
15+
#[derive(Debug, Clone, Copy)]
16+
#[repr(u32)]
17+
pub enum VersionInfoFileType {
18+
App = VFT_APP,
19+
Dll = VFT_DLL,
20+
}
21+
22+
/// 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.
27+
pub fn compile_windows_resource_file(
28+
file_stem: &path::Path,
29+
file_description: &str,
30+
filetype: VersionInfoFileType,
31+
) -> path::PathBuf {
32+
let mut resources_dir = path::PathBuf::from(env::var_os("OUT_DIR").unwrap());
33+
resources_dir.push("resources");
34+
fs::create_dir_all(&resources_dir).unwrap();
35+
36+
let resource_compiler = find_resource_compiler(&env::var("CARGO_CFG_TARGET_ARCH").unwrap())
37+
.expect("rc.exe not found");
38+
39+
let mut resource_script = RESOURCE_TEMPLATE.to_string();
40+
41+
// Set the string product version to the same thing as `rustc --version`
42+
let product_version = env::var("CFG_VERSION").unwrap_or("unknown".to_string());
43+
let file_version = &product_version;
44+
45+
// This is just "major.minor.patch" and a "-dev", "-nightly" or similar suffix
46+
let rel_version = env::var("CFG_RELEASE").unwrap();
47+
let product_name = format!("Rust {}", &rel_version);
48+
49+
// remove the suffix, if present and parse into [`ResourceVersion`]
50+
let version = parse_version(rel_version.split("-").next().unwrap_or("0.0.0"))
51+
.expect("could not parse CFG_RELEASE version");
52+
53+
resource_script = resource_script
54+
.replace("@RUSTC_FILEDESCRIPTION_STR@", file_description)
55+
.replace("@RUSTC_FILETYPE@", &format!("{}", filetype as u32))
56+
.replace("@RUSTC_FILEVERSION_QUAD@", &version.to_quad_string())
57+
.replace("@RUSTC_FILEVERSION_STR@", file_version)
58+
.replace("@RUSTC_PRODUCTNAME_STR@", &product_name)
59+
.replace("@RUSTC_PRODUCTVERSION_QUAD@", &version.to_quad_string())
60+
.replace("@RUSTC_PRODUCTVERSION_STR@", &product_version);
61+
62+
let rc_path = resources_dir.join(file_stem.with_extension("rc"));
63+
fs::write(&rc_path, resource_script)
64+
.unwrap_or_else(|_| panic!("failed to write resource file {}", rc_path.display()));
65+
66+
let res_path = resources_dir.join(file_stem.with_extension("res"));
67+
68+
let status = process::Command::new(resource_compiler)
69+
.arg("/fo")
70+
.arg(&res_path)
71+
.arg(&rc_path)
72+
.status()
73+
.expect("failed to execute rc.exe");
74+
assert!(status.success(), "rc.exe failed with status {}", status);
75+
assert!(
76+
res_path.try_exists().unwrap_or(false),
77+
"resource file {} was not created",
78+
res_path.display()
79+
);
80+
res_path
81+
}
82+
83+
/// Windows resources store versions as four 16-bit integers.
84+
struct ResourceVersion {
85+
major: u16,
86+
minor: u16,
87+
patch: u16,
88+
build: u16,
89+
}
90+
91+
impl ResourceVersion {
92+
/// Format the version as a comma-separated string of four integers
93+
/// as expected by Windows resource scripts for the `FILEVERSION` and `PRODUCTVERSION` fields.
94+
fn to_quad_string(&self) -> String {
95+
format!("{},{},{},{}", self.major, self.minor, self.patch, self.build)
96+
}
97+
}
98+
99+
/// Parse a version string in the format "major.minor.patch" into a form suitable for
100+
/// [`winres::VersionInfo::PRODUCTVERSION`].
101+
/// Returns `None` if the version string is not in the expected format.
102+
fn parse_version(version: &str) -> Option<ResourceVersion> {
103+
let mut parts = version.split('.');
104+
let major = parts.next()?.parse::<u16>().ok()?;
105+
let minor = parts.next()?.parse::<u16>().ok()?;
106+
let patch = parts.next()?.parse::<u16>().ok()?;
107+
if parts.next().is_some() {
108+
None
109+
} else {
110+
Some(ResourceVersion { major, minor, patch, build: 0 })
111+
}
112+
}
113+
114+
/// Find the Windows SDK resource compiler `rc.exe` for the given architecture or target triple.
115+
/// Returns `None` if the tool could not be found.
116+
fn find_resource_compiler(arch_or_target: &str) -> Option<path::PathBuf> {
117+
find_windows_sdk_tool(arch_or_target, "rc.exe")
118+
}
119+
120+
/// Find a Windows SDK tool for the given architecture or target triple.
121+
/// Returns `None` if the tool could not be found.
122+
fn find_windows_sdk_tool(arch_or_target: &str, tool_name: &str) -> Option<path::PathBuf> {
123+
// windows_registry::find_tool can only find MSVC tools, not Windows SDK tools, but
124+
// cc does include the Windows SDK tools in the PATH environment of MSVC tools.
125+
126+
let msvc_linker = windows_registry::find_tool(arch_or_target, "link.exe")?;
127+
let path = &msvc_linker.env().iter().find(|(k, _)| k == "PATH")?.1;
128+
find_tool_in_path(tool_name, path)
129+
}
130+
131+
/// Find a tool in the directories in a given PATH-like string.
132+
fn find_tool_in_path<P: AsRef<ffi::OsStr>>(tool_name: &str, path: P) -> Option<path::PathBuf> {
133+
env::split_paths(path.as_ref()).find_map(|p| {
134+
let tool_path = p.join(tool_name);
135+
if tool_path.try_exists().unwrap_or(false) { Some(tool_path) } else { None }
136+
})
137+
}

0 commit comments

Comments
 (0)