From 9155bf4e1795131339ccf9e5e8e491d35ab9cfdd Mon Sep 17 00:00:00 2001 From: Zyl0812 <128681759+Zyl0812@users.noreply.github.com> Date: Sat, 2 May 2026 06:33:54 +0800 Subject: [PATCH] Fix Windows icon resource for bin zed.exe (#54738) ## Summary Fixes the missing Windows icon and version resource metadata for the executable installed as `bin\zed.exe`. On Windows, the bundling process builds `cli.exe` and installs it as `bin\zed.exe`. The root `Zed.exe` already embeds the Zed Windows icon and version metadata through `crates/zed/build.rs`, but the CLI executable did not embed equivalent Windows resources. As a result, Windows integrations that discover or display Zed through `bin\zed.exe` may show a missing/default application icon. This change adds Windows resource embedding to the `cli` crate and uses the same release-channel icon selection as the main Zed executable. Fixes #51154 ## Testing - Built the Windows CLI executable: ```powershell cargo build --release --package cli --target x86_64-pc-windows-msvc --locked --offline ``` - Verified `target\x86_64-pc-windows-msvc\release\cli.exe` contains: - `FileDescription = Zed` - `ProductName = Zed` - Verified the executable displays the Zed icon in Windows Explorer. - Confirmed the Windows bundling script installs `cli.exe` as `bin\zed.exe`. - Started a full Windows bundle build and confirmed it passed license generation and progressed into executable builds. The local full bundle build could not be completed because the machine is missing the VS Spectre-mitigated C++ libraries. ## Release Notes - N/A ## Notes This change is limited to Windows executable resource metadata for the CLI binary. It does not change Zed runtime behavior. --------- Co-authored-by: John Tur --- Cargo.lock | 12 +- Cargo.toml | 1 + crates/auto_update_helper/Cargo.toml | 4 +- crates/auto_update_helper/build.rs | 12 +- crates/cli/Cargo.toml | 3 + crates/cli/build.rs | 8 ++ crates/windows_resources/Cargo.toml | 16 +++ crates/windows_resources/LICENSE-GPL | 1 + .../resources}/manifest.xml | 0 .../src/windows_resources.rs | 125 ++++++++++++++++++ crates/zed/Cargo.toml | 4 +- crates/zed/build.rs | 29 +--- 12 files changed, 173 insertions(+), 42 deletions(-) create mode 100644 crates/windows_resources/Cargo.toml create mode 120000 crates/windows_resources/LICENSE-GPL rename crates/{auto_update_helper => windows_resources/resources}/manifest.xml (100%) create mode 100644 crates/windows_resources/src/windows_resources.rs diff --git a/Cargo.lock b/Cargo.lock index 4c95e43eadb..57bbd15efc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1268,7 +1268,7 @@ dependencies = [ "simplelog", "tempfile", "windows 0.61.3", - "winresource", + "windows_resources", ] [[package]] @@ -2946,6 +2946,7 @@ dependencies = [ "util", "walkdir", "windows 0.61.3", + "windows_resources", ] [[package]] @@ -21417,6 +21418,13 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_resources" +version = "0.1.0" +dependencies = [ + "embed-resource", +] + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -22512,7 +22520,7 @@ dependencies = [ "web_search_providers", "which_key", "windows 0.61.3", - "winresource", + "windows_resources", "workspace", "zed-reqwest", "zed_actions", diff --git a/Cargo.toml b/Cargo.toml index a8c68c01ed6..2fac513d4ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -225,6 +225,7 @@ members = [ "crates/x_ai", "crates/zed", "crates/zed_actions", + "crates/windows_resources", "crates/zed_credentials_provider", "crates/zed_env_vars", "crates/zeta_prompt", diff --git a/crates/auto_update_helper/Cargo.toml b/crates/auto_update_helper/Cargo.toml index aa5bf6ac40b..81c31a63c36 100644 --- a/crates/auto_update_helper/Cargo.toml +++ b/crates/auto_update_helper/Cargo.toml @@ -25,8 +25,8 @@ windows.workspace = true [target.'cfg(target_os = "windows")'.dev-dependencies] tempfile.workspace = true -[target.'cfg(target_os = "windows")'.build-dependencies] -winresource = "0.1" +[build-dependencies] +windows_resources = { path = "../windows_resources" } [package.metadata.docs.rs] targets = ["x86_64-pc-windows-msvc"] diff --git a/crates/auto_update_helper/build.rs b/crates/auto_update_helper/build.rs index 2910632c7ff..b91bbcb4bf5 100644 --- a/crates/auto_update_helper/build.rs +++ b/crates/auto_update_helper/build.rs @@ -1,15 +1,9 @@ fn main() { #[cfg(target_os = "windows")] { - println!("cargo:rerun-if-changed=manifest.xml"); + println!("cargo:rerun-if-env-changed=RELEASE_CHANNEL"); + println!("cargo:rerun-if-env-changed=GITHUB_RUN_NUMBER"); - let mut res = winresource::WindowsResource::new(); - res.set_manifest_file("manifest.xml"); - res.set_icon("app-icon.ico"); - - if let Err(e) = res.compile() { - eprintln!("{}", e); - std::process::exit(1); - } + windows_resources::compile(true).expect("failed to compile Windows resources"); } } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index cfd807c0356..e8667c60875 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -52,3 +52,6 @@ plist = "1.3" [target.'cfg(target_os = "windows")'.dependencies] windows.workspace = true + +[build-dependencies] +windows_resources = { path = "../windows_resources" } diff --git a/crates/cli/build.rs b/crates/cli/build.rs index a3c4bc64373..8bda0576a07 100644 --- a/crates/cli/build.rs +++ b/crates/cli/build.rs @@ -26,4 +26,12 @@ fn main() { if let Some(build_identifier) = option_env!("GITHUB_RUN_NUMBER") { println!("cargo:rustc-env=ZED_BUILD_ID={build_identifier}"); } + + #[cfg(windows)] + { + println!("cargo:rerun-if-env-changed=RELEASE_CHANNEL"); + println!("cargo:rerun-if-env-changed=GITHUB_RUN_NUMBER"); + + windows_resources::compile(false).expect("failed to compile Windows resources"); + } } diff --git a/crates/windows_resources/Cargo.toml b/crates/windows_resources/Cargo.toml new file mode 100644 index 00000000000..4344040660c --- /dev/null +++ b/crates/windows_resources/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "windows_resources" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/windows_resources.rs" +doctest = false + +[target.'cfg(target_os = "windows")'.dependencies] +embed-resource = "3.0" diff --git a/crates/windows_resources/LICENSE-GPL b/crates/windows_resources/LICENSE-GPL new file mode 120000 index 00000000000..89e542f750c --- /dev/null +++ b/crates/windows_resources/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/auto_update_helper/manifest.xml b/crates/windows_resources/resources/manifest.xml similarity index 100% rename from crates/auto_update_helper/manifest.xml rename to crates/windows_resources/resources/manifest.xml diff --git a/crates/windows_resources/src/windows_resources.rs b/crates/windows_resources/src/windows_resources.rs new file mode 100644 index 00000000000..fee3e7368bd --- /dev/null +++ b/crates/windows_resources/src/windows_resources.rs @@ -0,0 +1,125 @@ +#![allow( + clippy::disallowed_methods, + reason = "build helper used only from build scripts" +)] +#![cfg(target_os = "windows")] + +use std::process::Command; + +fn git_sha() -> Option { + if let Ok(sha) = std::env::var("ZED_COMMIT_SHA") { + return Some(sha); + } + + Command::new("git") + .args(["rev-parse", "HEAD"]) + .output() + .ok() + .filter(|output| output.status.success()) + .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string()) +} + +fn product_version() -> String { + let commit_sha = git_sha(); + let pkg_version = std::env::var("CARGO_PKG_VERSION").unwrap_or_default(); + let channel = std::env::var("RELEASE_CHANNEL").unwrap_or_else(|_| "dev".into()); + let build_id = std::env::var("GITHUB_RUN_NUMBER").ok(); + + let mut metadata = channel; + if let Some(build_id) = &build_id { + metadata.push('.'); + metadata.push_str(build_id); + } + if let Some(sha) = &commit_sha { + metadata.push('.'); + metadata.push_str(sha); + } + + format!("{pkg_version}+{metadata}") +} + +const ICON_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../zed/resources/windows"); +const MANIFEST_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/resources/manifest.xml"); + +pub fn compile(manifest: bool) -> Result<(), Box> { + let channel = option_env!("RELEASE_CHANNEL").unwrap_or("dev"); + let (icon_filename, product_name) = match channel { + "stable" => ("app-icon.ico", "Zed"), + "preview" => ("app-icon-preview.ico", "Zed Preview"), + "nightly" => ("app-icon-nightly.ico", "Zed Nightly"), + _ => ("app-icon-dev.ico", "Zed Dev"), + }; + let icon = std::path::PathBuf::from(ICON_DIR).join(icon_filename); + let icon_escaped = icon.to_string_lossy().replace('\\', "\\\\"); + + let manifest_line = if manifest { + let escaped = MANIFEST_PATH.replace('\\', "\\\\"); + format!("1 24 \"{escaped}\"") + } else { + String::new() + }; + + let pkg_version = std::env::var("CARGO_PKG_VERSION").unwrap_or_default(); + let product_version = product_version(); + let mut version_parts = pkg_version + .split('.') + .map(|part| part.parse::().unwrap_or(0)) + .chain(std::iter::repeat(0)); + let file_version = format!( + "{},{},{},{}", + version_parts.next().unwrap_or(0), + version_parts.next().unwrap_or(0), + version_parts.next().unwrap_or(0), + version_parts.next().unwrap_or(0), + ); + + let rc_content = format!( + r#"1 ICON "{icon_escaped}" +{manifest_line} + +1 VERSIONINFO +FILEVERSION {file_version} +PRODUCTVERSION {file_version} +FILEFLAGSMASK 0x3fL +FILEFLAGS 0x0L +FILEOS 0x40004L +FILETYPE 0x1L +FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "{product_name}\0" + VALUE "FileVersion", "{pkg_version}\0" + VALUE "ProductName", "{product_name}\0" + VALUE "ProductVersion", "{product_version}\0" + VALUE "CompanyName", "Zed Industries, Inc.\0" + VALUE "LegalCopyright", "Copyright 2022 - 2025 Zed Industries, Inc.\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END +END +"# + ); + + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?); + let rc_path = out_dir.join("zed_resources.rc"); + std::fs::write(&rc_path, rc_content)?; + + if let Ok(toolkit_path) = std::env::var("ZED_RC_TOOLKIT_PATH") { + let rc_exe = std::path::Path::new(&toolkit_path).join("rc.exe"); + unsafe { + std::env::set_var("RC", rc_exe); + } + } + + embed_resource::compile(&rc_path, embed_resource::NONE) + .manifest_optional() + .unwrap(); + + Ok(()) +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 0374f6ec605..d8ac8be3369 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -233,8 +233,8 @@ gpui = { workspace = true, features = [ "windows-manifest", ] } -[target.'cfg(target_os = "windows")'.build-dependencies] -winresource = "0.1" +[build-dependencies] +windows_resources = { path = "../windows_resources" } [target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies] gpui = { workspace = true, features = [ diff --git a/crates/zed/build.rs b/crates/zed/build.rs index 80bf1d8642e..b27eba36a8a 100644 --- a/crates/zed/build.rs +++ b/crates/zed/build.rs @@ -202,37 +202,12 @@ fn main() { } } - let release_channel = option_env!("RELEASE_CHANNEL").unwrap_or("dev"); - let icon = match release_channel { - "stable" => "resources/windows/app-icon.ico", - "preview" => "resources/windows/app-icon-preview.ico", - "nightly" => "resources/windows/app-icon-nightly.ico", - "dev" => "resources/windows/app-icon-dev.ico", - _ => "resources/windows/app-icon-dev.ico", - }; - let icon = std::path::Path::new(icon); - println!("cargo:rerun-if-env-changed=RELEASE_CHANNEL"); - println!("cargo:rerun-if-changed={}", icon.display()); + println!("cargo:rerun-if-env-changed=GITHUB_RUN_NUMBER"); #[cfg(windows)] { - let mut res = winresource::WindowsResource::new(); - - // Depending on the security applied to the computer, winresource might fail - // fetching the RC path. Therefore, we add a way to explicitly specify the - // toolkit path, allowing winresource to use a valid RC path. - if let Some(explicit_rc_toolkit_path) = std::env::var("ZED_RC_TOOLKIT_PATH").ok() { - res.set_toolkit_path(explicit_rc_toolkit_path.as_str()); - } - res.set_icon(icon.to_str().unwrap()); - res.set("FileDescription", "Zed"); - res.set("ProductName", "Zed"); - - if let Err(e) = res.compile() { - eprintln!("{}", e); - std::process::exit(1); - } + windows_resources::compile(false).expect("failed to compile Windows resources"); } }