Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/linux/debian/install-sysroot.ts
3520 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 { spawnSync, execSync } from 'child_process';
7
import { tmpdir } from 'os';
8
import fs from 'fs';
9
import https from 'https';
10
import path from 'path';
11
import { createHash } from 'crypto';
12
import { DebianArchString } from './types';
13
14
// Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py.
15
const URL_PREFIX = 'https://msftelectronbuild.z5.web.core.windows.net';
16
const URL_PATH = 'sysroots/toolchain';
17
const REPO_ROOT = path.dirname(path.dirname(path.dirname(__dirname)));
18
19
const ghApiHeaders: Record<string, string> = {
20
Accept: 'application/vnd.github.v3+json',
21
'User-Agent': 'VSCode Build',
22
};
23
24
if (process.env.GITHUB_TOKEN) {
25
ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64');
26
}
27
28
const ghDownloadHeaders = {
29
...ghApiHeaders,
30
Accept: 'application/octet-stream',
31
};
32
33
interface IFetchOptions {
34
assetName: string;
35
checksumSha256?: string;
36
dest: string;
37
}
38
39
function getElectronVersion(): Record<string, string> {
40
const npmrc = fs.readFileSync(path.join(REPO_ROOT, '.npmrc'), 'utf8');
41
const electronVersion = /^target="(.*)"$/m.exec(npmrc)![1];
42
const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)![1];
43
return { electronVersion, msBuildId };
44
}
45
46
function getSha(filename: fs.PathLike): string {
47
const hash = createHash('sha256');
48
// Read file 1 MB at a time
49
const fd = fs.openSync(filename, 'r');
50
const buffer = Buffer.alloc(1024 * 1024);
51
let position = 0;
52
let bytesRead = 0;
53
while ((bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) {
54
hash.update(buffer);
55
position += bytesRead;
56
}
57
hash.update(buffer.slice(0, bytesRead));
58
return hash.digest('hex');
59
}
60
61
function getVSCodeSysrootChecksum(expectedName: string) {
62
const checksums = fs.readFileSync(path.join(REPO_ROOT, 'build', 'checksums', 'vscode-sysroot.txt'), 'utf8');
63
for (const line of checksums.split('\n')) {
64
const [checksum, name] = line.split(/\s+/);
65
if (name === expectedName) {
66
return checksum;
67
}
68
}
69
return undefined;
70
}
71
72
/*
73
* Do not use the fetch implementation from build/lib/fetch as it relies on vinyl streams
74
* and vinyl-fs breaks the symlinks in the compiler toolchain sysroot. We use the native
75
* tar implementation for that reason.
76
*/
77
async function fetchUrl(options: IFetchOptions, retries = 10, retryDelay = 1000): Promise<undefined> {
78
try {
79
const controller = new AbortController();
80
const timeout = setTimeout(() => controller.abort(), 30 * 1000);
81
const version = '20250407-330404';
82
try {
83
const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, {
84
headers: ghApiHeaders,
85
signal: controller.signal as any /* Typings issue with lib.dom.d.ts */
86
});
87
if (response.ok && (response.status >= 200 && response.status < 300)) {
88
console.log(`Fetch completed: Status ${response.status}.`);
89
const contents = Buffer.from(await response.arrayBuffer());
90
const asset = JSON.parse(contents.toString()).assets.find((a: { name: string }) => a.name === options.assetName);
91
if (!asset) {
92
throw new Error(`Could not find asset in release of Microsoft/vscode-linux-build-agent @ ${version}`);
93
}
94
console.log(`Found asset ${options.assetName} @ ${asset.url}.`);
95
const assetResponse = await fetch(asset.url, {
96
headers: ghDownloadHeaders
97
});
98
if (assetResponse.ok && (assetResponse.status >= 200 && assetResponse.status < 300)) {
99
const assetContents = Buffer.from(await assetResponse.arrayBuffer());
100
console.log(`Fetched response body buffer: ${(assetContents as Buffer).byteLength} bytes`);
101
if (options.checksumSha256) {
102
const actualSHA256Checksum = createHash('sha256').update(assetContents).digest('hex');
103
if (actualSHA256Checksum !== options.checksumSha256) {
104
throw new Error(`Checksum mismatch for ${asset.url} (expected ${options.checksumSha256}, actual ${actualSHA256Checksum}))`);
105
}
106
}
107
console.log(`Verified SHA256 checksums match for ${asset.url}`);
108
const tarCommand = `tar -xz -C ${options.dest}`;
109
execSync(tarCommand, { input: assetContents });
110
console.log(`Fetch complete!`);
111
return;
112
}
113
throw new Error(`Request ${asset.url} failed with status code: ${assetResponse.status}`);
114
}
115
throw new Error(`Request https://api.github.com failed with status code: ${response.status}`);
116
} finally {
117
clearTimeout(timeout);
118
}
119
} catch (e) {
120
if (retries > 0) {
121
console.log(`Fetching failed: ${e}`);
122
await new Promise(resolve => setTimeout(resolve, retryDelay));
123
return fetchUrl(options, retries - 1, retryDelay);
124
}
125
throw e;
126
}
127
}
128
129
type SysrootDictEntry = {
130
Sha256Sum: string;
131
SysrootDir: string;
132
Tarball: string;
133
};
134
135
export async function getVSCodeSysroot(arch: DebianArchString, isMusl: boolean = false): Promise<string> {
136
let expectedName: string;
137
let triple: string;
138
const prefix = process.env['VSCODE_SYSROOT_PREFIX'] ?? '-glibc-2.28-gcc-10.5.0';
139
switch (arch) {
140
case 'amd64':
141
expectedName = `x86_64-linux-gnu${prefix}.tar.gz`;
142
triple = 'x86_64-linux-gnu';
143
break;
144
case 'arm64':
145
if (isMusl) {
146
expectedName = 'aarch64-linux-musl-gcc-10.3.0.tar.gz';
147
triple = 'aarch64-linux-musl';
148
} else {
149
expectedName = `aarch64-linux-gnu${prefix}.tar.gz`;
150
triple = 'aarch64-linux-gnu';
151
}
152
break;
153
case 'armhf':
154
expectedName = `arm-rpi-linux-gnueabihf${prefix}.tar.gz`;
155
triple = 'arm-rpi-linux-gnueabihf';
156
break;
157
}
158
console.log(`Fetching ${expectedName} for ${triple}`);
159
const checksumSha256 = getVSCodeSysrootChecksum(expectedName);
160
if (!checksumSha256) {
161
throw new Error(`Could not find checksum for ${expectedName}`);
162
}
163
const sysroot = process.env['VSCODE_SYSROOT_DIR'] ?? path.join(tmpdir(), `vscode-${arch}-sysroot`);
164
const stamp = path.join(sysroot, '.stamp');
165
let result = `${sysroot}/${triple}/${triple}/sysroot`;
166
if (isMusl) {
167
result = `${sysroot}/output/${triple}`;
168
}
169
if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === expectedName) {
170
return result;
171
}
172
console.log(`Installing ${arch} root image: ${sysroot}`);
173
fs.rmSync(sysroot, { recursive: true, force: true });
174
fs.mkdirSync(sysroot, { recursive: true });
175
await fetchUrl({
176
checksumSha256,
177
assetName: expectedName,
178
dest: sysroot
179
});
180
fs.writeFileSync(stamp, expectedName);
181
return result;
182
}
183
184
export async function getChromiumSysroot(arch: DebianArchString): Promise<string> {
185
const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${getElectronVersion().electronVersion}/script/sysroots.json`;
186
const sysrootDictLocation = `${tmpdir()}/sysroots.json`;
187
const result = spawnSync('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]);
188
if (result.status !== 0) {
189
throw new Error('Cannot retrieve sysroots.json. Stderr:\n' + result.stderr);
190
}
191
const sysrootInfo = require(sysrootDictLocation);
192
const sysrootArch = `bullseye_${arch}`;
193
const sysrootDict: SysrootDictEntry = sysrootInfo[sysrootArch];
194
const tarballFilename = sysrootDict['Tarball'];
195
const tarballSha = sysrootDict['Sha256Sum'];
196
const sysroot = path.join(tmpdir(), sysrootDict['SysrootDir']);
197
const url = [URL_PREFIX, URL_PATH, tarballSha].join('/');
198
const stamp = path.join(sysroot, '.stamp');
199
if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === url) {
200
return sysroot;
201
}
202
203
console.log(`Installing Debian ${arch} root image: ${sysroot}`);
204
fs.rmSync(sysroot, { recursive: true, force: true });
205
fs.mkdirSync(sysroot);
206
const tarball = path.join(sysroot, tarballFilename);
207
console.log(`Downloading ${url}`);
208
let downloadSuccess = false;
209
for (let i = 0; i < 3 && !downloadSuccess; i++) {
210
fs.writeFileSync(tarball, '');
211
await new Promise<void>((c) => {
212
https.get(url, (res) => {
213
res.on('data', (chunk) => {
214
fs.appendFileSync(tarball, chunk);
215
});
216
res.on('end', () => {
217
downloadSuccess = true;
218
c();
219
});
220
}).on('error', (err) => {
221
console.error('Encountered an error during the download attempt: ' + err.message);
222
c();
223
});
224
});
225
}
226
if (!downloadSuccess) {
227
fs.rmSync(tarball);
228
throw new Error('Failed to download ' + url);
229
}
230
const sha = getSha(tarball);
231
if (sha !== tarballSha) {
232
throw new Error(`Tarball sha1sum is wrong. Expected ${tarballSha}, actual ${sha}`);
233
}
234
235
const proc = spawnSync('tar', ['xf', tarball, '-C', sysroot]);
236
if (proc.status) {
237
throw new Error('Tarball extraction failed with code ' + proc.status);
238
}
239
fs.rmSync(tarball);
240
fs.writeFileSync(stamp, url);
241
return sysroot;
242
}
243
244