Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
}
}
145 changes: 145 additions & 0 deletions compiler/rustc_windows_rc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//! 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");

const VFT_APP: u32 = 0x00000001; // VFT_APP
const VFT_DLL: u32 = 0x00000002; // VFT_DLL

#[derive(Debug, Clone, Copy)]
#[repr(u32)]
pub enum VersionInfoFileType {
App = VFT_APP,
Dll = VFT_DLL,
}

/// 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("rc.exe not found");

let mut resource_script = RESOURCE_TEMPLATE.to_string();

// Set the string product version to the same thing as `rustc --version`
let product_version = env::var("CFG_VERSION").unwrap_or("unknown".to_string());
let file_version = &product_version;

// This is just "major.minor.patch" and a "-dev", "-nightly" or similar suffix
let rel_version = env::var("CFG_RELEASE").unwrap();

let product_name = product_name(env::var("CFG_RELEASE_CHANNEL").unwrap());

// remove the suffix, if present and parse into [`ResourceVersion`]
let version = parse_version(rel_version.split("-").next().unwrap_or("0.0.0"))
.expect("could not parse 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@", file_version)
.replace("@RUSTC_PRODUCTNAME_STR@", &product_name)
.replace("@RUSTC_PRODUCTVERSION_QUAD@", &version.to_quad_string())
.replace("@RUSTC_PRODUCTVERSION_STR@", &product_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("failed to execute rc.exe");
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