diff --git a/Cargo.lock b/Cargo.lock index 8b9c243879caf..03bda287c4930 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3248,6 +3248,7 @@ dependencies = [ "rustc_driver_impl", "rustc_public", "rustc_public_bridge", + "rustc_windows_rc", "tikv-jemalloc-sys", ] @@ -3623,6 +3624,7 @@ name = "rustc_driver" version = "0.0.0" dependencies = [ "rustc_driver_impl", + "rustc_windows_rc", ] [[package]] @@ -4718,6 +4720,13 @@ dependencies = [ "semver", ] +[[package]] +name = "rustc_windows_rc" +version = "0.0.0" +dependencies = [ + "cc", +] + [[package]] name = "rustdoc" version = "0.0.0" diff --git a/compiler/rustc/Cargo.toml b/compiler/rustc/Cargo.toml index 3ca752354466f..e3214d1ab9cec 100644 --- a/compiler/rustc/Cargo.toml +++ b/compiler/rustc/Cargo.toml @@ -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 diff --git a/compiler/rustc/build.rs b/compiler/rustc/build.rs index 8b7d28d2b8aa6..9b5def53e3cbf 100644 --- a/compiler/rustc/build.rs +++ b/compiler/rustc/build.rs @@ -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"); @@ -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(); diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml index 57e1fee2c0a67..afcfe82d68d26 100644 --- a/compiler/rustc_codegen_ssa/Cargo.toml +++ b/compiler/rustc_codegen_ssa/Cargo.toml @@ -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" diff --git a/compiler/rustc_driver/Cargo.toml b/compiler/rustc_driver/Cargo.toml index e3ee83512952a..5190971982763 100644 --- a/compiler/rustc_driver/Cargo.toml +++ b/compiler/rustc_driver/Cargo.toml @@ -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 diff --git a/compiler/rustc_driver/build.rs b/compiler/rustc_driver/build.rs new file mode 100644 index 0000000000000..ba44fe7a86ed9 --- /dev/null +++ b/compiler/rustc_driver/build.rs @@ -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()); +} diff --git a/compiler/rustc_llvm/Cargo.toml b/compiler/rustc_llvm/Cargo.toml index e74de453be216..6321104ee3ef1 100644 --- a/compiler/rustc_llvm/Cargo.toml +++ b/compiler/rustc_llvm/Cargo.toml @@ -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 diff --git a/compiler/rustc_windows_rc/Cargo.toml b/compiler/rustc_windows_rc/Cargo.toml new file mode 100644 index 0000000000000..080acd35c38cb --- /dev/null +++ b/compiler/rustc_windows_rc/Cargo.toml @@ -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 diff --git a/compiler/rustc_windows_rc/rustc.rc.in b/compiler/rustc_windows_rc/rustc.rc.in new file mode 100644 index 0000000000000..10a00b9bd4e32 --- /dev/null +++ b/compiler/rustc_windows_rc/rustc.rc.in @@ -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 + } +} diff --git a/compiler/rustc_windows_rc/src/lib.rs b/compiler/rustc_windows_rc/src/lib.rs new file mode 100644 index 0000000000000..caa5e5ef27656 --- /dev/null +++ b/compiler/rustc_windows_rc/src/lib.rs @@ -0,0 +1,158 @@ +//! 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 rc_path = resources_dir.join(file_stem.with_extension("rc")); + + write_resource_script_file(&rc_path, file_description, filetype); + + 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 +} + +/// Writes a Windows resource script file for the rust compiler with the product and file version information +/// into `rc_path` +fn write_resource_script_file( + rc_path: &path::Path, + file_description: &str, + filetype: VersionInfoFileType, +) { + 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); + + fs::write(&rc_path, resource_script) + .unwrap_or_else(|_| panic!("failed to write resource file {}", rc_path.display())); +} + +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 { + let mut parts = version.split('.'); + let major = parts.next()?.parse::().ok()?; + let minor = parts.next()?.parse::().ok()?; + let patch = parts.next()?.parse::().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 { + 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 { + // 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>(tool_name: &str, path: P) -> Option { + 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 } + }) +} diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index b079117c5a7d1..9d6382cb4e389 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -1777,7 +1777,7 @@ mod snapshot { insta::assert_snapshot!( ctx.config("check") .path("compiler") - .render_steps(), @"[check] rustc 0 -> rustc 1 (74 crates)"); + .render_steps(), @"[check] rustc 0 -> rustc 1 (75 crates)"); } #[test] @@ -1803,7 +1803,7 @@ mod snapshot { ctx.config("check") .path("compiler") .stage(1) - .render_steps(), @"[check] rustc 0 -> rustc 1 (74 crates)"); + .render_steps(), @"[check] rustc 0 -> rustc 1 (75 crates)"); } #[test] @@ -1817,7 +1817,7 @@ mod snapshot { [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [check] rustc 1 -> rustc 2 (74 crates) + [check] rustc 1 -> rustc 2 (75 crates) "); } @@ -1833,7 +1833,7 @@ mod snapshot { [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 [check] rustc 1 -> std 1 - [check] rustc 1 -> rustc 2 (74 crates) + [check] rustc 1 -> rustc 2 (75 crates) [check] rustc 1 -> rustc 2 [check] rustc 1 -> Rustdoc 2 [check] rustc 1 -> rustc_codegen_cranelift 2 @@ -1929,7 +1929,7 @@ mod snapshot { ctx.config("check") .paths(&["library", "compiler"]) .args(&args) - .render_steps(), @"[check] rustc 0 -> rustc 1 (74 crates)"); + .render_steps(), @"[check] rustc 0 -> rustc 1 (75 crates)"); } #[test]