From a85ee03b131c5948bfd80b2d73b55753fedd719b 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 4c95e43ead..57bbd15efc 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 a8c68c01ed..2fac513d4e 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 aa5bf6ac40..81c31a63c3 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 2910632c7f..b91bbcb4bf 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 cfd807c035..e8667c6087 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 a3c4bc6437..8bda0576a0 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 0000000000..4344040660 --- /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 0000000000..89e542f750 --- /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 0000000000..fee3e7368b --- /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 0374f6ec60..d8ac8be336 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 80bf1d8642..b27eba36a8 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"); } }