windows_resources.rs

  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}