Fix Windows icon resource for bin zed.exe (#54738)
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions

## 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 <john-tur@outlook.com>
This commit is contained in:
Zyl0812 2026-05-02 06:33:54 +08:00 committed by GitHub
parent 1581a08c49
commit 9155bf4e17
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 173 additions and 42 deletions

12
Cargo.lock generated
View file

@ -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",

View file

@ -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",

View file

@ -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"]

View file

@ -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");
}
}

View file

@ -52,3 +52,6 @@ plist = "1.3"
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true
[build-dependencies]
windows_resources = { path = "../windows_resources" }

View file

@ -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");
}
}

View file

@ -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"

View file

@ -0,0 +1 @@
../../LICENSE-GPL

View file

@ -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<String> {
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<dyn std::error::Error>> {
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::<u16>().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(())
}

View file

@ -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 = [

View file

@ -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");
}
}