feat: add dmg for macOS (#289179)

* feat: add dmg for macOS arm64

* chore: create dmg for all archs

* chore: move zx dependency to build

* fix: invalid condition in universal build

* chore: update background

* fix: publishing universal dmg
This commit is contained in:
Robo
2026-01-23 11:21:39 +09:00
committed by GitHub
parent 11893a22bb
commit 0ced11929b
13 changed files with 3340 additions and 63 deletions

View File

@@ -782,10 +782,16 @@ function getPlatform(product: string, os: string, arch: string, type: string): s
case 'darwin':
switch (product) {
case 'client':
if (arch === 'x64') {
return 'darwin';
switch (type) {
case 'dmg':
return `darwin-${arch}-dmg`;
case 'archive':
default:
if (arch === 'x64') {
return 'darwin';
}
return `darwin-${arch}`;
}
return `darwin-${arch}`;
case 'server':
if (arch === 'x64') {
return 'server-darwin';

View File

@@ -12,17 +12,25 @@ async function main() {
const pipelineWorkspace = e('PIPELINE_WORKSPACE');
const folder = `${pipelineWorkspace}/vscode_client_darwin_${arch}_archive`;
const dmgFolder = `${pipelineWorkspace}/vscode_client_darwin_${arch}_dmg`;
const glob = `VSCode-darwin-${arch}.zip`;
const dmgGlob = `VSCode-darwin-${arch}.dmg`;
// Codesign
printBanner('Codesign');
const codeSignTask = spawnCodesignProcess(esrpCliDLLPath, 'sign-darwin', folder, glob);
await streamProcessOutputAndCheckResult('Codesign', codeSignTask);
const archiveCodeSignTask = spawnCodesignProcess(esrpCliDLLPath, 'sign-darwin', folder, glob);
const dmgCodeSignTask = spawnCodesignProcess(esrpCliDLLPath, 'sign-darwin', dmgFolder, dmgGlob);
printBanner('Codesign Archive');
await streamProcessOutputAndCheckResult('Codesign Archive', archiveCodeSignTask);
printBanner('Codesign DMG');
await streamProcessOutputAndCheckResult('Codesign DMG', dmgCodeSignTask);
// Notarize
printBanner('Notarize');
const notarizeTask = spawnCodesignProcess(esrpCliDLLPath, 'notarize-darwin', folder, glob);
await streamProcessOutputAndCheckResult('Notarize', notarizeTask);
const archiveNotarizeTask = spawnCodesignProcess(esrpCliDLLPath, 'notarize-darwin', folder, glob);
const dmgNotarizeTask = spawnCodesignProcess(esrpCliDLLPath, 'notarize-darwin', dmgFolder, dmgGlob);
printBanner('Notarize Archive');
await streamProcessOutputAndCheckResult('Notarize Archive', archiveNotarizeTask);
printBanner('Notarize DMG');
await streamProcessOutputAndCheckResult('Notarize DMG', dmgNotarizeTask);
}
main().then(() => {

View File

@@ -14,6 +14,13 @@ jobs:
sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)
sbomPackageName: "VS Code macOS $(VSCODE_ARCH)"
sbomPackageVersion: $(Build.SourceVersion)
- output: pipelineArtifact
targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_dmg/VSCode-darwin-$(VSCODE_ARCH).dmg
artifactName: vscode_client_darwin_$(VSCODE_ARCH)_dmg
displayName: Publish client DMG
sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)
sbomPackageName: "VS Code macOS $(VSCODE_ARCH)"
sbomPackageVersion: $(Build.SourceVersion)
steps:
- template: ../common/checkout.yml@self
@@ -112,8 +119,16 @@ jobs:
- script: |
set -e
mkdir -p $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive
pushd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip * && popd
DMG_OUT="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_dmg"
mkdir -p $DMG_OUT
node build/darwin/create-dmg.ts $(agent.builddirectory) $DMG_OUT
echo "##vso[task.setvariable variable=DMG_PATH]$DMG_OUT/VSCode-darwin-$(VSCODE_ARCH).dmg"
displayName: Create DMG installer
- script: |
set -e
mkdir -p $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive
pushd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip * && popd
displayName: Archive build
- task: UseDotNet@2
@@ -132,17 +147,21 @@ jobs:
Pattern: noop
displayName: 'Install ESRP Tooling'
- script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
displayName: ✍️ Codesign
- pwsh: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
$EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)/_tasks | Select-Object -last 1).FullName
$Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName
echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version/net6.0/esrpcli.dll"
displayName: Find ESRP CLI
- script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip
- script: node build/azure-pipelines/darwin/codesign.ts
env:
EsrpCliDllPath: $(EsrpCliDllPath)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
displayName: ✍️ Notarize
displayName: ✍️ Codesign & Notarize
- script: unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)
- script: unzip $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)
displayName: Extract signed app
- script: |
@@ -157,5 +176,8 @@ jobs:
- script: |
set -e
mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive
mv $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip
mv $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip
mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_dmg
mv $(DMG_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_dmg/VSCode-darwin-$(VSCODE_ARCH).dmg
displayName: Move artifact to out directory

View File

@@ -69,6 +69,13 @@ jobs:
sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web
sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Web"
sbomPackageVersion: $(Build.SourceVersion)
- output: pipelineArtifact
targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_dmg/VSCode-darwin-$(VSCODE_ARCH).dmg
artifactName: vscode_client_darwin_$(VSCODE_ARCH)_dmg
displayName: Publish client DMG
sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)
sbomPackageName: "VS Code macOS $(VSCODE_ARCH)"
sbomPackageVersion: $(Build.SourceVersion)
steps:
- template: ./steps/product-build-darwin-compile.yml@self
parameters:

View File

@@ -224,6 +224,15 @@ steps:
DEBUG=electron-osx-sign* node build/darwin/sign.ts $(agent.builddirectory)
displayName: Set Hardened Entitlements
- script: |
set -e
DMG_OUT="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_dmg"
mkdir -p $DMG_OUT
node build/darwin/create-dmg.ts $(agent.builddirectory) $DMG_OUT
echo "##vso[task.setvariable variable=DMG_PATH]$DMG_OUT/VSCode-darwin-$(VSCODE_ARCH).dmg"
condition: eq(variables['BUILT_CLIENT'], 'true')
displayName: Create DMG installer
- script: |
set -e
ARCHIVE_PATH="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip"
@@ -298,6 +307,9 @@ steps:
mv $(CLIENT_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip
fi
mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_dmg
mv $(DMG_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_dmg/VSCode-darwin-$(VSCODE_ARCH).dmg
mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_server_darwin_$(VSCODE_ARCH)_archive
mv $(SERVER_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_server_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH).zip

150
build/darwin/create-dmg.ts Normal file
View File

@@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import fs from 'fs';
import path from 'path';
import { spawn } from '@malept/cross-spawn-promise';
const root = path.dirname(path.dirname(import.meta.dirname));
const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8'));
interface DmgBuildSettings {
title: string;
icon?: string | null;
background?: string;
'background-color'?: string;
'icon-size'?: number;
'text-size'?: number;
format?: string;
window?: {
position?: { x: number; y: number };
size?: { width: number; height: number };
};
contents: Array<{
path: string;
x: number;
y: number;
type: 'file' | 'link';
name?: string;
}>;
}
function getDmgBuilderPath(): string {
return path.join(import.meta.dirname, '..', 'node_modules', 'dmg-builder');
}
function getDmgBuilderVendorPath(): string {
return path.join(getDmgBuilderPath(), 'vendor');
}
async function runDmgBuild(settingsFile: string, volumeName: string, artifactPath: string): Promise<void> {
const vendorDir = getDmgBuilderVendorPath();
const scriptPath = path.join(vendorDir, 'run_dmgbuild.py');
await spawn('python3', [scriptPath, '-s', settingsFile, volumeName, artifactPath], {
cwd: vendorDir,
stdio: 'inherit'
});
}
async function main(buildDir?: string, outDir?: string): Promise<void> {
const arch = process.env['VSCODE_ARCH'];
const quality = process.env['VSCODE_QUALITY'];
if (!buildDir) {
throw new Error('Build directory argument is required');
}
if (!arch) {
throw new Error('$VSCODE_ARCH not set');
}
if (!outDir) {
throw new Error('Output directory argument is required');
}
const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`);
const appName = product.nameLong + '.app';
const appPath = path.join(appRoot, appName);
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`);
let title = 'Code OSS';
switch (quality) {
case 'stable':
title = 'VS Code';
break;
case 'insider':
title = 'VS Code Insiders';
break;
case 'exploration':
title = 'VS Code Exploration';
break;
}
if (!fs.existsSync(appPath)) {
throw new Error(`App path does not exist: ${appPath}`);
}
console.log(`Creating DMG for ${product.nameLong}...`);
console.log(` App path: ${appPath}`);
console.log(` Output directory: ${outDir}`);
console.log(` DMG name: ${dmgName}`);
if (fs.existsSync(artifactPath)) {
fs.unlinkSync(artifactPath);
}
const settings: DmgBuildSettings = {
title,
icon: appIconPath,
background: backgroundPath,
format: 'ULMO',
'text-size': 12,
window: {
position: { x: 100, y: 400 },
size: { width: 480, height: 320 }
},
contents: [
{
path: appPath,
x: 120,
y: 160,
type: 'file'
},
{
path: '/Applications',
x: 360,
y: 160,
type: 'link'
}
]
};
const settingsFile = path.join(outDir, '.dmg-settings.json');
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
try {
await runDmgBuild(settingsFile, dmgName, artifactPath);
} finally {
if (fs.existsSync(settingsFile)) {
fs.unlinkSync(settingsFile);
}
}
if (!fs.existsSync(artifactPath)) {
throw new Error(`DMG was not created at expected path: ${artifactPath}`);
}
const stats = fs.statSync(artifactPath);
console.log(`Successfully created DMG: ${artifactPath} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`);
}
if (import.meta.main) {
main(process.argv[2], process.argv[3]).catch(err => {
console.error('Failed to create DMG:', err);
process.exit(1);
});
}

Binary file not shown.

Binary file not shown.

View File

@@ -44,6 +44,7 @@ export const unicodeFilter = Object.freeze<string[]>([
'!**/*.test.ts',
'!**/*.{d.ts,json,md}',
'!**/*.mp3',
'!**/*.tiff',
'!build/win32/**',
'!extensions/markdown-language-features/notebook-out/*.js',
@@ -138,6 +139,7 @@ export const indentationFilter = Object.freeze<string[]>([
'!**/Dockerfile.*',
'!**/*.Dockerfile',
'!**/*.dockerfile',
'!**/*.tiff',
// except for built files
'!extensions/mermaid-chat-features/chat-webview-out/*.js',
@@ -173,6 +175,7 @@ export const copyrightFilter = Object.freeze<string[]>([
'!**/*.code-workspace',
'!**/*.js.map',
'!**/*.wasm',
'!**/*.tiff',
'!build/**/*.init',
'!build/linux/libcxx-fetcher.*',
'!build/npm/gyp/custom-headers/*.patch',

3134
build/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,6 +47,7 @@
"ansi-colors": "^3.2.3",
"byline": "^5.0.0",
"debug": "^4.3.2",
"dmg-builder": "^26.5.0",
"esbuild": "0.27.2",
"extract-zip": "^2.0.1",
"gulp-merge-json": "^2.1.1",
@@ -60,7 +61,8 @@
"tree-sitter": "^0.22.4",
"vscode-universal-bundler": "^0.1.3",
"workerpool": "^6.4.0",
"yauzl": "^2.10.0"
"yauzl": "^2.10.0",
"zx": "^8.8.5"
},
"type": "module",
"scripts": {

16
package-lock.json generated
View File

@@ -159,8 +159,7 @@
"webpack-cli": "^5.1.4",
"webpack-stream": "^7.0.0",
"xml2js": "^0.5.0",
"yaserver": "^0.4.0",
"zx": "^8.8.5"
"yaserver": "^0.4.0"
},
"optionalDependencies": {
"windows-foreground-love": "0.6.1"
@@ -18258,19 +18257,6 @@
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zx": {
"version": "8.8.5",
"resolved": "https://registry.npmjs.org/zx/-/zx-8.8.5.tgz",
"integrity": "sha512-SNgDF5L0gfN7FwVOdEFguY3orU5AkfFZm9B5YSHog/UDHv+lvmd82ZAsOenOkQixigwH2+yyH198AwNdKhj+RA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"zx": "build/cli.js"
},
"engines": {
"node": ">= 12.17.0"
}
}
}
}

View File

@@ -222,8 +222,7 @@
"webpack-cli": "^5.1.4",
"webpack-stream": "^7.0.0",
"xml2js": "^0.5.0",
"yaserver": "^0.4.0",
"zx": "^8.8.5"
"yaserver": "^0.4.0"
},
"overrides": {
"node-gyp-build": "4.8.1",