1#![allow(
2 clippy::disallowed_methods,
3 reason = "build helper used only from build scripts"
4)]
5#![cfg(target_os = "windows")]
6
7use std::process::Command;
8
9fn git_sha() -> Option<String> {
10 if let Ok(sha) = std::env::var("ZED_COMMIT_SHA") {
11 return Some(sha);
12 }
13
14 Command::new("git")
15 .args(["rev-parse", "HEAD"])
16 .output()
17 .ok()
18 .filter(|output| output.status.success())
19 .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string())
20}
21
22fn product_version() -> String {
23 let commit_sha = git_sha();
24 let pkg_version = std::env::var("CARGO_PKG_VERSION").unwrap_or_default();
25 let channel = std::env::var("RELEASE_CHANNEL").unwrap_or_else(|_| "dev".into());
26 let build_id = std::env::var("GITHUB_RUN_NUMBER").ok();
27
28 let mut metadata = channel;
29 if let Some(build_id) = &build_id {
30 metadata.push('.');
31 metadata.push_str(build_id);
32 }
33 if let Some(sha) = &commit_sha {
34 metadata.push('.');
35 metadata.push_str(sha);
36 }
37
38 format!("{pkg_version}+{metadata}")
39}
40
41const ICON_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../zed/resources/windows");
42const MANIFEST_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/resources/manifest.xml");
43
44pub fn compile(manifest: bool) -> Result<(), Box<dyn std::error::Error>> {
45 let channel = option_env!("RELEASE_CHANNEL").unwrap_or("dev");
46 let (icon_filename, product_name) = match channel {
47 "stable" => ("app-icon.ico", "Zed"),
48 "preview" => ("app-icon-preview.ico", "Zed Preview"),
49 "nightly" => ("app-icon-nightly.ico", "Zed Nightly"),
50 _ => ("app-icon-dev.ico", "Zed Dev"),
51 };
52 let icon = std::path::PathBuf::from(ICON_DIR).join(icon_filename);
53 let icon_escaped = icon.to_string_lossy().replace('\\', "\\\\");
54
55 let manifest_line = if manifest {
56 let escaped = MANIFEST_PATH.replace('\\', "\\\\");
57 format!("1 24 \"{escaped}\"")
58 } else {
59 String::new()
60 };
61
62 let pkg_version = std::env::var("CARGO_PKG_VERSION").unwrap_or_default();
63 let product_version = product_version();
64 let mut version_parts = pkg_version
65 .split('.')
66 .map(|part| part.parse::<u16>().unwrap_or(0))
67 .chain(std::iter::repeat(0));
68 let file_version = format!(
69 "{},{},{},{}",
70 version_parts.next().unwrap_or(0),
71 version_parts.next().unwrap_or(0),
72 version_parts.next().unwrap_or(0),
73 version_parts.next().unwrap_or(0),
74 );
75
76 let rc_content = format!(
77 r#"1 ICON "{icon_escaped}"
78{manifest_line}
79
801 VERSIONINFO
81FILEVERSION {file_version}
82PRODUCTVERSION {file_version}
83FILEFLAGSMASK 0x3fL
84FILEFLAGS 0x0L
85FILEOS 0x40004L
86FILETYPE 0x1L
87FILESUBTYPE 0x0L
88BEGIN
89 BLOCK "StringFileInfo"
90 BEGIN
91 BLOCK "040904b0"
92 BEGIN
93 VALUE "FileDescription", "{product_name}\0"
94 VALUE "FileVersion", "{pkg_version}\0"
95 VALUE "ProductName", "{product_name}\0"
96 VALUE "ProductVersion", "{product_version}\0"
97 VALUE "CompanyName", "Zed Industries, Inc.\0"
98 VALUE "LegalCopyright", "Copyright 2022 - 2025 Zed Industries, Inc.\0"
99 END
100 END
101 BLOCK "VarFileInfo"
102 BEGIN
103 VALUE "Translation", 0x0409, 1200
104 END
105END
106"#
107 );
108
109 let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?);
110 let rc_path = out_dir.join("zed_resources.rc");
111 std::fs::write(&rc_path, rc_content)?;
112
113 if let Ok(toolkit_path) = std::env::var("ZED_RC_TOOLKIT_PATH") {
114 let rc_exe = std::path::Path::new(&toolkit_path).join("rc.exe");
115 unsafe {
116 std::env::set_var("RC", rc_exe);
117 }
118 }
119
120 embed_resource::compile(&rc_path, embed_resource::NONE)
121 .manifest_optional()
122 .unwrap();
123
124 Ok(())
125}