Skip to content

Commit 4bc313c

Browse files
authored
Rollup merge of rust-lang#146018 - lambdageek:add-winres-version, r=wesleywiser
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 Invokes `rc.exe` directly, rather than using one of the crates from the ecosystem to avoid adding dependencies. A new internal `rustc_windows_rc` crate has the common build script machinery for locating `rc.exe` and constructing the resource script
2 parents cb19c7d + 095fa86 commit 4bc313c

File tree

11 files changed

+271
-8
lines changed

11 files changed

+271
-8
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.5"
99
bitflags = "2.4.1"
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 = "0.12"
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 = "0.2.73"
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: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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+
/// 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)]
17+
pub 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.
29+
pub 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`
64+
fn 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+
97+
fn 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.
105+
struct ResourceVersion {
106+
major: u16,
107+
minor: u16,
108+
patch: u16,
109+
build: u16,
110+
}
111+
112+
impl 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.
123+
fn 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.
137+
fn 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.
143+
fn 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.
153+
fn 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+
}

0 commit comments

Comments
 (0)