Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3248,6 +3248,7 @@ dependencies = [
"rustc_driver_impl",
"rustc_public",
"rustc_public_bridge",
"rustc_windows_rc",
"tikv-jemalloc-sys",
]

Expand Down Expand Up @@ -3623,6 +3624,7 @@ name = "rustc_driver"
version = "0.0.0"
dependencies = [
"rustc_driver_impl",
"rustc_windows_rc",
]

[[package]]
Expand Down Expand Up @@ -4718,6 +4720,13 @@ dependencies = [
"semver",
]

[[package]]
name = "rustc_windows_rc"
version = "0.0.0"
dependencies = [
"cc",
]

[[package]]
name = "rustdoc"
version = "0.0.0"
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ llvm = ['rustc_driver_impl/llvm']
max_level_info = ['rustc_driver_impl/max_level_info']
rustc_randomized_layouts = ['rustc_driver_impl/rustc_randomized_layouts']
# tidy-alphabetical-end

[build-dependencies]
# tidy-alphabetical-start
rustc_windows_rc = { path = "../rustc_windows_rc" }
# tidy-alphabetical-end
16 changes: 15 additions & 1 deletion compiler/rustc/build.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::env;
use std::{env, path};

use rustc_windows_rc::{VersionInfoFileType, compile_windows_resource_file};

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

// Add a manifest file to rustc.exe.
fn set_windows_exe_options() {
set_windows_resource();
set_windows_manifest();
}

fn set_windows_resource() {
let stem = path::PathBuf::from("rustc_main_resource");
let file_description = "rustc";
let res_file = compile_windows_resource_file(&stem, file_description, VersionInfoFileType::App);
println!("cargo:rustc-link-arg={}", res_file.display());
}

fn set_windows_manifest() {
static WINDOWS_MANIFEST_FILE: &str = "Windows Manifest.xml";

let mut manifest = env::current_dir().unwrap();
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ar_archive_writer = "0.4.2"
bitflags.workspace = true
bstr = "1.11.3"
# `cc` updates often break things, so we pin it here. Cargo enforces "max 1 semver-compat version
# per crate", so if you change this, you need to also change it in `rustc_llvm`.
# per crate", so if you change this, you need to also change it in `rustc_llvm` and `rustc_windows_rc`.
cc = "=1.2.16"
itertools.workspace = true
pathdiff = "0.2.0"
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_driver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ crate-type = ["dylib"]
# tidy-alphabetical-start
rustc_driver_impl = { path = "../rustc_driver_impl" }
# tidy-alphabetical-end

[build-dependencies]
# tidy-alphabetical-start
rustc_windows_rc = { path = "../rustc_windows_rc" }
# tidy-alphabetical-end
21 changes: 21 additions & 0 deletions compiler/rustc_driver/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use std::{env, path};

use rustc_windows_rc::{VersionInfoFileType, compile_windows_resource_file};

fn main() {
let target_os = env::var("CARGO_CFG_TARGET_OS");
let target_env = env::var("CARGO_CFG_TARGET_ENV");
if Ok("windows") == target_os.as_deref() && Ok("msvc") == target_env.as_deref() {
set_windows_dll_options();
} else {
// Avoid rerunning the build script every time.
println!("cargo:rerun-if-changed=build.rs");
}
}

fn set_windows_dll_options() {
let stem = path::PathBuf::from("rustc_driver_resource");
let file_description = "rustc_driver";
let res_file = compile_windows_resource_file(&stem, file_description, VersionInfoFileType::Dll);
println!("cargo:rustc-link-arg={}", res_file.display());
}
2 changes: 1 addition & 1 deletion compiler/rustc_llvm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ libc.workspace = true
[build-dependencies]
# tidy-alphabetical-start
# `cc` updates often break things, so we pin it here. Cargo enforces "max 1 semver-compat version
# per crate", so if you change this, you need to also change it in `rustc_codegen_ssa`.
# per crate", so if you change this, you need to also change it in `rustc_codegen_ssa` and `rustc_windows_rc`.
cc = "=1.2.16"
# tidy-alphabetical-end

Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_windows_rc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "rustc_windows_rc"
version = "0.0.0"
edition = "2024"

[dependencies]
#tidy-alphabetical-start
# `cc` updates often break things, so we pin it here. Cargo enforces "max 1 semver-compat version
# per crate", so if you change this, you need to also change it in `rustc_llvm` and `rustc_codegen_ssa`.
cc = "=1.2.16"
#tidy-alphabetical-end
40 changes: 40 additions & 0 deletions compiler/rustc_windows_rc/rustc.rc.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// A template for the rustc_driver and rustc Windows resource files.
// This file is processed by the build script to produce rustc.rc and rustc_driver.rc
// with the appropriate version information filled in.

// All the strings are in UTF-8
#pragma code_page(65001)

#define RUSTC_FILEDESCRIPTION_STR "@RUSTC_FILEDESCRIPTION_STR@"
#define RUSTC_FILETYPE @RUSTC_FILETYPE@
#define RUSTC_FILEVERSION_QUAD @RUSTC_FILEVERSION_QUAD@
#define RUSTC_FILEVERSION_STR "@RUSTC_FILEVERSION_STR@"

#define RUSTC_PRODUCTNAME_STR "@RUSTC_PRODUCTNAME_STR@"
#define RUSTC_PRODUCTVERSION_QUAD @RUSTC_PRODUCTVERSION_QUAD@
#define RUSTC_PRODUCTVERSION_STR "@RUSTC_PRODUCTVERSION_STR@"


1 VERSIONINFO
FILEVERSION RUSTC_FILEVERSION_QUAD
PRODUCTVERSION RUSTC_PRODUCTVERSION_QUAD
FILEOS 0x00040004
FILETYPE RUSTC_FILETYPE
FILESUBTYPE 0
FILEFLAGSMASK 0x3f
FILEFLAGS 0x0
{
BLOCK "StringFileInfo"
{
BLOCK "000004b0"
{
VALUE "FileDescription", RUSTC_FILEDESCRIPTION_STR
VALUE "FileVersion", RUSTC_FILEVERSION_STR
VALUE "ProductVersion", RUSTC_PRODUCTVERSION_STR
VALUE "ProductName", RUSTC_PRODUCTNAME_STR
}
}
BLOCK "VarFileInfo" {
VALUE "Translation", 0x0, 0x04b0
}
}
147 changes: 147 additions & 0 deletions compiler/rustc_windows_rc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//! 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, ffi, fs, path, process};

use cc::windows_registry;

/// 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 =
find_resource_compiler(&env::var("CARGO_CFG_TARGET_ARCH").unwrap()).expect("found rc.exe");

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);

let rc_path = resources_dir.join(file_stem.with_extension("rc"));
fs::write(&rc_path, resource_script)
.unwrap_or_else(|_| panic!("failed to write resource file {}", rc_path.display()));

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
}

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 })
}
}

/// Find the Windows SDK resource compiler `rc.exe` for the given architecture or target triple.
/// Returns `None` if the tool could not be found.
fn find_resource_compiler(arch_or_target: &str) -> Option<path::PathBuf> {
find_windows_sdk_tool(arch_or_target, "rc.exe")
}

/// Find a Windows SDK tool for the given architecture or target triple.
/// Returns `None` if the tool could not be found.
fn find_windows_sdk_tool(arch_or_target: &str, tool_name: &str) -> Option<path::PathBuf> {
// windows_registry::find_tool can only find MSVC tools, not Windows SDK tools, but
// cc does include the Windows SDK tools in the PATH environment of MSVC tools.

let msvc_linker = windows_registry::find_tool(arch_or_target, "link.exe")?;
let path = &msvc_linker.env().iter().find(|(k, _)| k == "PATH")?.1;
find_tool_in_path(tool_name, path)
}

/// Find a tool in the directories in a given PATH-like string.
fn find_tool_in_path<P: AsRef<ffi::OsStr>>(tool_name: &str, path: P) -> Option<path::PathBuf> {
env::split_paths(path.as_ref()).find_map(|p| {
let tool_path = p.join(tool_name);
if tool_path.try_exists().unwrap_or(false) { Some(tool_path) } else { None }
})
}
10 changes: 5 additions & 5 deletions src/bootstrap/src/core/builder/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1777,7 +1777,7 @@ mod snapshot {
insta::assert_snapshot!(
ctx.config("check")
.path("compiler")
.render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (74 crates)");
.render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (75 crates)");
}

#[test]
Expand All @@ -1803,7 +1803,7 @@ mod snapshot {
ctx.config("check")
.path("compiler")
.stage(1)
.render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (74 crates)");
.render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (75 crates)");
}

#[test]
Expand All @@ -1817,7 +1817,7 @@ mod snapshot {
[build] llvm <host>
[build] rustc 0 <host> -> rustc 1 <host>
[build] rustc 1 <host> -> std 1 <host>
[check] rustc 1 <host> -> rustc 2 <host> (74 crates)
[check] rustc 1 <host> -> rustc 2 <host> (75 crates)
");
}

Expand All @@ -1833,7 +1833,7 @@ mod snapshot {
[build] rustc 0 <host> -> rustc 1 <host>
[build] rustc 1 <host> -> std 1 <host>
[check] rustc 1 <host> -> std 1 <target1>
[check] rustc 1 <host> -> rustc 2 <target1> (74 crates)
[check] rustc 1 <host> -> rustc 2 <target1> (75 crates)
[check] rustc 1 <host> -> rustc 2 <target1>
[check] rustc 1 <host> -> Rustdoc 2 <target1>
[check] rustc 1 <host> -> rustc_codegen_cranelift 2 <target1>
Expand Down Expand Up @@ -1929,7 +1929,7 @@ mod snapshot {
ctx.config("check")
.paths(&["library", "compiler"])
.args(&args)
.render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (74 crates)");
.render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (75 crates)");
}

#[test]
Expand Down
Loading