Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/darwin/sign.ts
5267 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import fs from 'fs';
7
import path from 'path';
8
import { sign, type SignOptions } from '@electron/osx-sign';
9
import { spawn } from '@malept/cross-spawn-promise';
10
11
const root = path.dirname(path.dirname(import.meta.dirname));
12
const baseDir = path.dirname(import.meta.dirname);
13
const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8'));
14
const helperAppBaseName = product.nameShort;
15
const gpuHelperAppName = helperAppBaseName + ' Helper (GPU).app';
16
const rendererHelperAppName = helperAppBaseName + ' Helper (Renderer).app';
17
const pluginHelperAppName = helperAppBaseName + ' Helper (Plugin).app';
18
19
function getElectronVersion(): string {
20
const npmrc = fs.readFileSync(path.join(root, '.npmrc'), 'utf8');
21
const target = /^target="(.*)"$/m.exec(npmrc)![1];
22
return target;
23
}
24
25
function getEntitlementsForFile(filePath: string): string {
26
if (filePath.includes(gpuHelperAppName)) {
27
return path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-gpu-entitlements.plist');
28
} else if (filePath.includes(rendererHelperAppName)) {
29
return path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist');
30
} else if (filePath.includes(pluginHelperAppName)) {
31
return path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-plugin-entitlements.plist');
32
}
33
return path.join(baseDir, 'azure-pipelines', 'darwin', 'app-entitlements.plist');
34
}
35
36
async function retrySignOnKeychainError<T>(fn: () => Promise<T>, maxRetries: number = 3): Promise<T> {
37
let lastError: Error | undefined;
38
39
for (let attempt = 1; attempt <= maxRetries; attempt++) {
40
try {
41
return await fn();
42
} catch (error) {
43
lastError = error as Error;
44
45
// Check if this is the specific keychain error we want to retry
46
const errorMessage = error instanceof Error ? error.message : String(error);
47
const isKeychainError = errorMessage.includes('The specified item could not be found in the keychain.');
48
49
if (!isKeychainError || attempt === maxRetries) {
50
throw error;
51
}
52
53
console.log(`Signing attempt ${attempt} failed with keychain error, retrying...`);
54
console.log(`Error: ${errorMessage}`);
55
56
const delay = 1000 * Math.pow(2, attempt - 1);
57
console.log(`Waiting ${Math.round(delay)}ms before retry ${attempt}/${maxRetries}...`);
58
await new Promise(resolve => setTimeout(resolve, delay));
59
}
60
}
61
62
throw lastError;
63
}
64
65
async function main(buildDir?: string): Promise<void> {
66
const tempDir = process.env['AGENT_TEMPDIRECTORY'];
67
const arch = process.env['VSCODE_ARCH'];
68
const identity = process.env['CODESIGN_IDENTITY'];
69
70
if (!buildDir) {
71
throw new Error('$AGENT_BUILDDIRECTORY not set');
72
}
73
74
if (!tempDir) {
75
throw new Error('$AGENT_TEMPDIRECTORY not set');
76
}
77
78
const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`);
79
const appName = product.nameLong + '.app';
80
const infoPlistPath = path.resolve(appRoot, appName, 'Contents', 'Info.plist');
81
82
const appOpts: SignOptions = {
83
app: path.join(appRoot, appName),
84
platform: 'darwin',
85
optionsForFile: (filePath) => ({
86
entitlements: getEntitlementsForFile(filePath),
87
hardenedRuntime: true,
88
}),
89
preAutoEntitlements: false,
90
preEmbedProvisioningProfile: false,
91
keychain: path.join(tempDir, 'buildagent.keychain'),
92
version: getElectronVersion(),
93
identity,
94
};
95
96
// Only overwrite plist entries for x64 and arm64 builds,
97
// universal will get its copy from the x64 build.
98
if (arch !== 'universal') {
99
await spawn('plutil', [
100
'-insert',
101
'NSAppleEventsUsageDescription',
102
'-string',
103
'An application in Visual Studio Code wants to use AppleScript.',
104
`${infoPlistPath}`
105
]);
106
await spawn('plutil', [
107
'-replace',
108
'NSMicrophoneUsageDescription',
109
'-string',
110
'An application in Visual Studio Code wants to use the Microphone.',
111
`${infoPlistPath}`
112
]);
113
await spawn('plutil', [
114
'-replace',
115
'NSCameraUsageDescription',
116
'-string',
117
'An application in Visual Studio Code wants to use the Camera.',
118
`${infoPlistPath}`
119
]);
120
await spawn('plutil', [
121
'-replace',
122
'NSAudioCaptureUsageDescription',
123
'-string',
124
'An application in Visual Studio Code wants to use Audio Capture.',
125
`${infoPlistPath}`
126
]);
127
}
128
129
await retrySignOnKeychainError(() => sign(appOpts));
130
}
131
132
if (import.meta.main) {
133
main(process.argv[2]).catch(async err => {
134
console.error(err);
135
const tempDir = process.env['AGENT_TEMPDIRECTORY'];
136
if (tempDir) {
137
const keychain = path.join(tempDir, 'buildagent.keychain');
138
const identities = await spawn('security', ['find-identity', '-p', 'codesigning', '-v', keychain]);
139
console.error(`Available identities:\n${identities}`);
140
const dump = await spawn('security', ['dump-keychain', keychain]);
141
console.error(`Keychain dump:\n${dump}`);
142
}
143
process.exit(1);
144
});
145
}
146
147