From 0074df9396a2de55b541de7f14978cbbd4976213 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 28 Jan 2026 20:03:31 +0900 Subject: [PATCH] fix: update disk badge icons for dmg (#290879) * fix: update disk badge icons for dmg * chore: try with code icons as badge * fix: patch badge icon outside of dmgbuild Refs https://github.com/dmgbuild/dmgbuild/issues/278 which doesn't work well on macOS 26. We avoid forking the project in this manner. --- .../darwin/product-build-darwin-universal.yml | 3 + .../steps/product-build-darwin-compile.yml | 3 + build/darwin/create-dmg.ts | 5 +- build/darwin/patch-dmg.py | 71 +++++++++++++++++++ build/filters.ts | 2 + package.json | 2 +- 6 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 build/darwin/patch-dmg.py diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index 215df6f65ea..ed94a170791 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -119,9 +119,12 @@ jobs: - script: | set -e + # Needed for https://github.com/dmgbuild/dmgbuild/blob/main/src/dmgbuild/badge.py + python3 -m pip install pyobjc-framework-Quartz DMG_OUT="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_dmg" mkdir -p $DMG_OUT node build/darwin/create-dmg.ts $(agent.builddirectory) $DMG_OUT + python3 build/darwin/patch-dmg.py $DMG_OUT/VSCode-darwin-$(VSCODE_ARCH).dmg resources/darwin/disk.icns echo "##vso[task.setvariable variable=DMG_PATH]$DMG_OUT/VSCode-darwin-$(VSCODE_ARCH).dmg" displayName: Create DMG installer diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml index 5bd2665e8f7..e20ef8deb01 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -226,9 +226,12 @@ steps: - script: | set -e + # Needed for https://github.com/dmgbuild/dmgbuild/blob/main/src/dmgbuild/badge.py + python3 -m pip install pyobjc-framework-Quartz DMG_OUT="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_dmg" mkdir -p $DMG_OUT node build/darwin/create-dmg.ts $(agent.builddirectory) $DMG_OUT + python3 build/darwin/patch-dmg.py $DMG_OUT/VSCode-darwin-$(VSCODE_ARCH).dmg resources/darwin/disk.icns echo "##vso[task.setvariable variable=DMG_PATH]$DMG_OUT/VSCode-darwin-$(VSCODE_ARCH).dmg" condition: eq(variables['BUILT_CLIENT'], 'true') displayName: Create DMG installer diff --git a/build/darwin/create-dmg.ts b/build/darwin/create-dmg.ts index 63a9d4d403b..6bea7e76d5f 100644 --- a/build/darwin/create-dmg.ts +++ b/build/darwin/create-dmg.ts @@ -13,6 +13,7 @@ const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf interface DmgBuildSettings { title: string; icon?: string | null; + 'badge-icon'?: string | null; background?: string; 'background-color'?: string; 'icon-size'?: number; @@ -70,7 +71,7 @@ async function main(buildDir?: string, outDir?: string): Promise { const dmgName = `VSCode-darwin-${arch}`; const artifactPath = path.join(outDir, `${dmgName}.dmg`); const backgroundPath = path.join(import.meta.dirname, `dmg-background-${quality}.tiff`); - const appIconPath = path.join(appPath, 'Contents', 'Resources', `${product.nameShort}.icns`); + const diskIconPath = path.join(root, 'resources', 'darwin', 'code.icns'); let title = 'Code OSS'; switch (quality) { case 'stable': @@ -99,7 +100,7 @@ async function main(buildDir?: string, outDir?: string): Promise { const settings: DmgBuildSettings = { title, - icon: appIconPath, + 'badge-icon': diskIconPath, background: backgroundPath, format: 'ULMO', 'text-size': 12, diff --git a/build/darwin/patch-dmg.py b/build/darwin/patch-dmg.py new file mode 100644 index 00000000000..ac6ed0436e8 --- /dev/null +++ b/build/darwin/patch-dmg.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +import subprocess +import shutil +import tempfile +import plistlib +import os + +def patch_dmg_icon(dmg_path, new_icon_path): + """Replace the volume icon in an existing DMG.""" + + # 1. Convert to read-write format + temp_rw = tempfile.NamedTemporaryFile(suffix=".dmg", delete=False) + temp_rw.close() + + subprocess.run([ + "hdiutil", "convert", dmg_path, + "-format", "UDRW", # Read-write + "-o", temp_rw.name, + "-ov" # Overwrite + ], check=True) + + # 2. Attach the writable DMG + result = subprocess.run( + ["hdiutil", "attach", "-nobrowse", "-plist", temp_rw.name], + capture_output=True, check=True + ) + plist = plistlib.loads(result.stdout) + + mount_point = None + device = None + for entity in plist["system-entities"]: + if "mount-point" in entity: + mount_point = entity["mount-point"] + device = entity["dev-entry"] + break + + try: + # 3. Copy custom icon + icon_target = os.path.join(mount_point, ".VolumeIcon.icns") + shutil.copyfile(new_icon_path, icon_target) + + # 4. Set the custom icon attribute on the volume + subprocess.run(["/usr/bin/SetFile", "-a", "C", mount_point], check=True) + + # Sync before detach + subprocess.run(["sync", "--file-system", mount_point], check=True) + + finally: + # 5. Detach + subprocess.run(["hdiutil", "detach", device], check=True) + + # 6. Convert back to compressed format (ULMO = lzma) + subprocess.run([ + "hdiutil", "convert", temp_rw.name, + "-format", "ULMO", + "-o", dmg_path, + "-ov" + ], check=True) + + # Cleanup temp file + os.unlink(temp_rw.name) + print(f"Successfully patched {dmg_path} with new icon") + + +if __name__ == "__main__": + import sys + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + + patch_dmg_icon(sys.argv[1], sys.argv[2]) diff --git a/build/filters.ts b/build/filters.ts index dfe0db5e147..e5f577e00e1 100644 --- a/build/filters.ts +++ b/build/filters.ts @@ -88,6 +88,7 @@ export const indentationFilter = Object.freeze([ '!test/unit/assert.js', '!resources/linux/snap/electron-launch', '!build/ext.js', + '!build/darwin/patch-dmg.py', '!build/npm/gyp/patches/gyp_spectre_mitigation_support.patch', '!product.overrides.json', @@ -177,6 +178,7 @@ export const copyrightFilter = Object.freeze([ '!**/*.wasm', '!**/*.tiff', '!build/**/*.init', + '!build/darwin/patch-dmg.py', '!build/linux/libcxx-fetcher.*', '!build/npm/gyp/custom-headers/*.patch', '!resources/linux/snap/snapcraft.yaml', diff --git a/package.json b/package.json index 977853c6f02..bba9b07dbbe 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.109.0", - "distro": "ce54912c8b9b4ad03645ec984f7fb9e0a6247c32", + "distro": "1468752e41ea530021336a556d31c13ff82e02b9", "author": { "name": "Microsoft Corporation" },