From d0e516655abf23ff2b568492c8e8300a51fe968f Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 4 Feb 2026 07:50:21 +0900 Subject: [PATCH] fix: rename product executable on macOS (#291948) * fix: rename product executable name on macOS * chore: update test/automation/src/electron.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: update test/automation/src/electron.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: rename in additional places * chore: rename in code-perf.js * chore: create symlink for backwards compatibility --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/vscodeTestRunner.ts | 4 +-- .vscode/launch.json | 6 ++--- .../darwin/product-build-darwin-universal.yml | 14 ++++++++++ .../steps/product-build-darwin-compile.yml | 15 +++++++++++ .../steps/product-build-darwin-test.yml | 8 ++++-- build/gulpfile.vscode.ts | 1 + build/lib/electron.ts | 1 + resources/darwin/bin/code.sh | 2 +- scripts/code-cli.sh | 3 ++- scripts/code-perf.js | 10 +++++-- scripts/code.sh | 3 ++- scripts/node-electron.sh | 3 ++- scripts/test.sh | 3 ++- src/vs/code/node/cli.ts | 2 +- test/automation/src/electron.ts | 27 ++++++++++++++++--- test/mcp/src/application.ts | 2 +- test/sanity/src/context.ts | 6 ++++- test/smoke/src/main.ts | 2 +- 18 files changed, 91 insertions(+), 21 deletions(-) diff --git a/.vscode/extensions/vscode-selfhost-test-provider/src/vscodeTestRunner.ts b/.vscode/extensions/vscode-selfhost-test-provider/src/vscodeTestRunner.ts index 165855ae6eb..ba06d2632ad 100644 --- a/.vscode/extensions/vscode-selfhost-test-provider/src/vscodeTestRunner.ts +++ b/.vscode/extensions/vscode-selfhost-test-provider/src/vscodeTestRunner.ts @@ -312,10 +312,10 @@ export class DarwinTestRunner extends PosixTestRunner { /** @override */ protected override async binaryPath() { - const { nameLong } = await this.readProductJson(); + const { nameLong, nameShort } = await this.readProductJson(); return path.join( this.repoLocation.uri.fsPath, - `.build/electron/${nameLong}.app/Contents/MacOS/Electron` + `.build/electron/${nameLong}.app/Contents/MacOS/${nameShort}` ); } } diff --git a/.vscode/launch.json b/.vscode/launch.json index a7a15cc31a6..9dbed82ee94 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -505,7 +505,7 @@ "request": "launch", "name": "Run Unit Tests", "program": "${workspaceFolder}/test/unit/electron/index.js", - "runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.app/Contents/MacOS/Electron", + "runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.app/Contents/MacOS/Code - OSS", "windows": { "runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.exe" }, @@ -535,7 +535,7 @@ "request": "launch", "name": "Run Unit Tests For Current File", "program": "${workspaceFolder}/test/unit/electron/index.js", - "runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.app/Contents/MacOS/Electron", + "runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.app/Contents/MacOS/Code - OSS", "windows": { "runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.exe" }, @@ -571,7 +571,7 @@ "timeout": 240000, "args": [ "-l", - "${workspaceFolder}/.build/electron/Code - OSS.app/Contents/MacOS/Electron" + "${workspaceFolder}/.build/electron/Code - OSS.app/Contents/MacOS/Code - OSS" ], "outFiles": [ "${cwd}/out/**/*.js" diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index ed94a170791..88722aecc7a 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -98,6 +98,20 @@ jobs: DEBUG=* node build/darwin/create-universal-app.ts $(agent.builddirectory) displayName: Create Universal App + - script: | + set -e + APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" + EXEC_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").nameShort") + # Create a symlink from 'Electron' to the actual executable for backward compatibility + # This ensures apps that relied on the hardcoded path 'Contents/MacOS/Electron' continue to work + # Remove this step once main branch is on 1.112 release. + if [ "$EXEC_NAME" != "Electron" ] && [ ! -L "$APP_PATH/Contents/MacOS/Electron" ]; then + ln -s "$EXEC_NAME" "$APP_PATH/Contents/MacOS/Electron" + fi + displayName: Create Electron symlink for backward compatibility + - script: | set -e APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" 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 7604d54909f..2854c1f9417 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -166,6 +166,21 @@ steps: chmod +x "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" displayName: Make CLI executable + - script: | + set -e + APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" + EXEC_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").nameShort") + # Create a symlink from 'Electron' to the actual executable for backward compatibility + # This ensures apps that relied on the hardcoded path 'Contents/MacOS/Electron' continue to work + # Remove this step once main branch is on 1.112 release. + if [ "$EXEC_NAME" != "Electron" ] && [ ! -L "$APP_PATH/Contents/MacOS/Electron" ]; then + ln -s "$EXEC_NAME" "$APP_PATH/Contents/MacOS/Electron" + fi + condition: eq(variables['BUILT_CLIENT'], 'true') + displayName: Create Electron symlink for backward compatibility + - script: | set -e APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml index 80be5496d97..2028b862f8f 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml @@ -58,7 +58,9 @@ steps: set -e APP_ROOT="$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)" APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + ProductJsonPath=$(find "$APP_ROOT" -name "product.json" -type f | head -n 1) + BINARY_NAME=$(jq -r '.nameShort' "$ProductJsonPath") + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/$BINARY_NAME" \ ./scripts/test-integration.sh --build --tfs "Integration Tests" env: VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) @@ -77,7 +79,9 @@ steps: set -e APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + ProductJsonPath=$(find "$APP_ROOT" -name "product.json" -type f | head -n 1) + BINARY_NAME=$(jq -r '.nameShort' "$ProductJsonPath") + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/$BINARY_NAME" \ ./scripts/test-remote-integration.sh env: VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts index 358bb3acad3..8bc20da0c12 100644 --- a/build/gulpfile.vscode.ts +++ b/build/gulpfile.vscode.ts @@ -364,6 +364,7 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d } else if (platform === 'darwin') { const shortcut = gulp.src('resources/darwin/bin/code.sh') .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(replace('@@NAME@@', product.nameShort)) .pipe(rename('bin/code')); const policyDest = gulp.src('.build/policies/darwin/**', { base: '.build/policies/darwin' }) .pipe(rename(f => f.dirname = `policies/${f.dirname}`)); diff --git a/build/lib/electron.ts b/build/lib/electron.ts index aadc9b5fbe7..64786cb2de7 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -109,6 +109,7 @@ export const config = { productAppName: product.nameLong, companyName: 'Microsoft Corporation', copyright: 'Copyright (C) 2026 Microsoft. All rights reserved', + darwinExecutable: product.nameShort, darwinIcon: 'resources/darwin/code.icns', darwinBundleIdentifier: product.darwinBundleIdentifier, darwinApplicationCategoryType: 'public.app-category.developer-tools', diff --git a/resources/darwin/bin/code.sh b/resources/darwin/bin/code.sh index de5c3bfcab0..9410de8763e 100755 --- a/resources/darwin/bin/code.sh +++ b/resources/darwin/bin/code.sh @@ -29,7 +29,7 @@ if [ -z "$APP_PATH" ]; then exit 1 fi CONTENTS="$APP_PATH/Contents" -ELECTRON="$CONTENTS/MacOS/Electron" +ELECTRON="$CONTENTS/MacOS/@@NAME@@" CLI="$CONTENTS/Resources/app/out/cli.js" export VSCODE_NODE_OPTIONS=$NODE_OPTIONS export VSCODE_NODE_REPL_EXTERNAL_MODULE=$NODE_REPL_EXTERNAL_MODULE diff --git a/scripts/code-cli.sh b/scripts/code-cli.sh index 220c34d1a7e..ef466e50d07 100755 --- a/scripts/code-cli.sh +++ b/scripts/code-cli.sh @@ -12,7 +12,8 @@ function code() { if [[ "$OSTYPE" == "darwin"* ]]; then NAME=`node -p "require('./product.json').nameLong"` - CODE="./.build/electron/$NAME.app/Contents/MacOS/Electron" + EXE_NAME=`node -p "require('./product.json').nameShort"` + CODE="./.build/electron/$NAME.app/Contents/MacOS/$EXE_NAME" else NAME=`node -p "require('./product.json').applicationName"` CODE=".build/electron/$NAME" diff --git a/scripts/code-perf.js b/scripts/code-perf.js index 4bc431479f3..f1fbcb4f0ef 100644 --- a/scripts/code-perf.js +++ b/scripts/code-perf.js @@ -6,6 +6,7 @@ // @ts-check const path = require('path'); +const fs = require('fs'); const perf = require('@vscode/vscode-perf'); const VSCODE_FOLDER = path.join(__dirname, '..'); @@ -62,9 +63,14 @@ function getExePath(buildPath) { } let relativeExePath; switch (process.platform) { - case 'darwin': - relativeExePath = path.join('Contents', 'MacOS', 'Electron'); + case 'darwin': { + const product = require(path.join(buildPath, 'Contents', 'Resources', 'app', 'product.json')); + relativeExePath = path.join('Contents', 'MacOS', product.nameShort); + if (!fs.existsSync(path.join(buildPath, relativeExePath))) { + relativeExePath = path.join('Contents', 'MacOS', 'Electron'); + } break; + } case 'linux': { const product = require(path.join(buildPath, 'resources', 'app', 'product.json')); relativeExePath = product.applicationName; diff --git a/scripts/code.sh b/scripts/code.sh index 1ddbfce7d1a..16fdefde552 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -18,7 +18,8 @@ function code() { if [[ "$OSTYPE" == "darwin"* ]]; then NAME=`node -p "require('./product.json').nameLong"` - CODE="./.build/electron/$NAME.app/Contents/MacOS/Electron" + EXE_NAME=`node -p "require('./product.json').nameShort"` + CODE="./.build/electron/$NAME.app/Contents/MacOS/$EXE_NAME" else NAME=`node -p "require('./product.json').applicationName"` CODE=".build/electron/$NAME" diff --git a/scripts/node-electron.sh b/scripts/node-electron.sh index 102fe073e4f..187bfe314bb 100755 --- a/scripts/node-electron.sh +++ b/scripts/node-electron.sh @@ -11,7 +11,8 @@ pushd $ROOT if [[ "$OSTYPE" == "darwin"* ]]; then NAME=`node -p "require('./product.json').nameLong"` - CODE="$ROOT/.build/electron/$NAME.app/Contents/MacOS/Electron" + EXE_NAME=`node -p "require('./product.json').nameShort"` + CODE="$ROOT/.build/electron/$NAME.app/Contents/MacOS/$EXE_NAME" else NAME=`node -p "require('./product.json').applicationName"` CODE="$ROOT/.build/electron/$NAME" diff --git a/scripts/test.sh b/scripts/test.sh index 9ba8dedee0f..bc4661ecb7f 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -12,7 +12,8 @@ cd $ROOT if [[ "$OSTYPE" == "darwin"* ]]; then NAME=`node -p "require('./product.json').nameLong"` - CODE="./.build/electron/$NAME.app/Contents/MacOS/Electron" + EXE_NAME=`node -p "require('./product.json').nameShort"` + CODE="./.build/electron/$NAME.app/Contents/MacOS/$EXE_NAME" else NAME=`node -p "require('./product.json').applicationName"` CODE=".build/electron/$NAME" diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index b3bdca721dd..8e29f492476 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -73,7 +73,7 @@ export async function main(argv: string[]): Promise { tunnelProcess = spawn('cargo', ['run', '--', subcommand, ...tunnelArgs], { cwd: join(getAppRoot(), 'cli'), stdio, env }); } else { const appPath = process.platform === 'darwin' - // ./Contents/MacOS/Electron => ./Contents/Resources/app/bin/code-tunnel-insiders + // ./Contents/MacOS/Code => ./Contents/Resources/app/bin/code-tunnel-insiders ? join(dirname(dirname(process.execPath)), 'Resources', 'app') : dirname(process.execPath); const tunnelCommand = join(appPath, 'bin', `${product.tunnelApplicationName}${isWindows ? '.exe' : ''}`); diff --git a/test/automation/src/electron.ts b/test/automation/src/electron.ts index 7475a8a8494..f6f1d78551e 100644 --- a/test/automation/src/electron.ts +++ b/test/automation/src/electron.ts @@ -121,13 +121,22 @@ function findFilePath(root: string, path: string): string { throw new Error(`Could not find ${path} in any subdirectory`); } +function parseVersion(version: string) { + const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version); + if (!match) { + throw new Error(`Invalid version string: ${version}`); + } + const [, major, minor, patch] = match; + return { major: parseInt(major), minor: parseInt(minor), patch: parseInt(patch) }; +} + export function getDevElectronPath(): string { const buildPath = join(root, '.build'); const product = require(join(root, 'product.json')); switch (process.platform) { case 'darwin': - return join(buildPath, 'electron', `${product.nameLong}.app`, 'Contents', 'MacOS', 'Electron'); + return join(buildPath, 'electron', `${product.nameLong}.app`, 'Contents', 'MacOS', `${product.nameShort}`); case 'linux': return join(buildPath, 'electron', `${product.applicationName}`); case 'win32': @@ -139,8 +148,20 @@ export function getDevElectronPath(): string { export function getBuildElectronPath(root: string): string { switch (process.platform) { - case 'darwin': - return join(root, 'Contents', 'MacOS', 'Electron'); + case 'darwin': { + const packageJson = require(join(root, 'Contents', 'Resources', 'app', 'package.json')); + const product = require(join(root, 'Contents', 'Resources', 'app', 'product.json')); + const { major, minor } = parseVersion(packageJson.version); + // For macOS builds using the legacy Electron binary name, versions up to and including + // 1.109.x ship the executable as "Electron". From later versions onward, the executable + // is renamed to match product.nameShort. This check preserves compatibility with older + // builds; update the cutoff here only if the binary naming scheme changes again. + if (major === 1 && minor <= 109) { + return join(root, 'Contents', 'MacOS', 'Electron'); + } else { + return join(root, 'Contents', 'MacOS', product.nameShort); + } + } case 'linux': { const product = require(join(root, 'resources', 'app', 'product.json')); return join(root, product.applicationName); diff --git a/test/mcp/src/application.ts b/test/mcp/src/application.ts index 1eff6a914ad..e2749493ad4 100644 --- a/test/mcp/src/application.ts +++ b/test/mcp/src/application.ts @@ -196,7 +196,7 @@ async function ensureStableCode(): Promise { })); if (process.platform === 'darwin') { - // Visual Studio Code.app/Contents/MacOS/Electron + // Visual Studio Code.app/Contents/MacOS/Code stableCodePath = path.dirname(path.dirname(path.dirname(stableCodeExecutable))); } else { // VSCode/Code.exe (Windows) | VSCode/code (Linux) diff --git a/test/sanity/src/context.ts b/test/sanity/src/context.ts index 1ee1b036fb8..95273f9f7fc 100644 --- a/test/sanity/src/context.ts +++ b/test/sanity/src/context.ts @@ -779,18 +779,22 @@ export class TestContext { switch (os.platform()) { case 'darwin': { let appName: string; + let binaryName: string; switch (this.options.quality) { case 'stable': appName = 'Visual Studio Code.app'; + binaryName = 'Code'; break; case 'insider': appName = 'Visual Studio Code - Insiders.app'; + binaryName = 'Code - Insiders'; break; case 'exploration': appName = 'Visual Studio Code - Exploration.app'; + binaryName = 'Code - Exploration'; break; } - filePath = path.join(dir, appName, 'Contents/MacOS/Electron'); + filePath = path.join(dir, appName, 'Contents/MacOS', binaryName); break; } case 'linux': { diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 15279bbd5a1..fc8b4f8800f 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -322,7 +322,7 @@ async function ensureStableCode(): Promise { }); if (process.platform === 'darwin') { - // Visual Studio Code.app/Contents/MacOS/Electron + // Visual Studio Code.app/Contents/MacOS/Code stableCodePath = path.dirname(path.dirname(path.dirname(stableCodeExecutable))); } else { // VSCode/Code.exe (Windows) | VSCode/code (Linux)