/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { spawnSync, execSync } from 'child_process'; import { tmpdir } from 'os'; import fs from 'fs'; import https from 'https'; import path from 'path'; import { createHash } from 'crypto'; import { download } from '../../lib/download.ts'; import { getElectronVersion } from '../../lib/electronVersion.ts'; import type { DebianArchString } from './types.ts'; // Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py. const URL_PREFIX = 'https://msftelectronbuild.z5.web.core.windows.net'; const URL_PATH = 'sysroots/toolchain'; const REPO_ROOT = path.dirname(path.dirname(path.dirname(import.meta.dirname))); const ghApiHeaders: Record = { Accept: 'application/vnd.github.v3+json', 'User-Agent': 'VSCode Build', }; if (process.env.GITHUB_TOKEN) { ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); } const ghDownloadHeaders = { ...ghApiHeaders, Accept: 'application/octet-stream', }; interface IFetchOptions { assetName: string; checksumSha256?: string; dest: string; } function getSha(filename: fs.PathLike): string { const hash = createHash('sha256'); // Read file 1 MB at a time const fd = fs.openSync(filename, 'r'); const buffer = Buffer.alloc(1024 * 1024); let position = 0; let bytesRead = 0; while ((bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) { hash.update(buffer); position += bytesRead; } hash.update(buffer.slice(0, bytesRead)); return hash.digest('hex'); } function getVSCodeSysrootChecksum(expectedName: string) { const checksums = fs.readFileSync(path.join(REPO_ROOT, 'build', 'checksums', 'vscode-sysroot.txt'), 'utf8'); for (const line of checksums.split('\n')) { const [checksum, name] = line.split(/\s+/); if (name === expectedName) { return checksum; } } return undefined; } /* * Do not use the fetch implementation from build/lib/fetch as it relies on vinyl streams * and vinyl-fs breaks the symlinks in the compiler toolchain sysroot. We use the native * tar implementation for that reason. */ async function fetchUrl(options: IFetchOptions): Promise { const version = '20260212-405735'; const releaseUrl = `https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`; const downloadOptions = { attempts: 11, onRetry: (error: Error) => console.log(`Fetching failed: ${error}`) }; const releaseContents = await download(releaseUrl, { ...downloadOptions, headers: ghApiHeaders }); const asset = JSON.parse(Buffer.from(releaseContents).toString()).assets.find((a: { name: string }) => a.name === options.assetName); if (!asset) { throw new Error(`Could not find asset in release of Microsoft/vscode-linux-build-agent @ ${version}`); } console.log(`Found asset ${options.assetName} @ ${asset.url}.`); const assetContents = Buffer.from(await download(asset.url, { ...downloadOptions, headers: ghDownloadHeaders, checksumSha256: options.checksumSha256 })); console.log(`Fetched response body buffer: ${assetContents.byteLength} bytes`); console.log(`Verified SHA256 checksums match for ${asset.url}`); execSync(`tar -xz -C ${options.dest}`, { input: assetContents }); console.log('Fetch complete!'); } type SysrootDictEntry = { Sha256Sum: string; SysrootDir: string; Tarball: string; }; export async function getVSCodeSysroot(arch: DebianArchString, isMusl: boolean = false): Promise { let expectedName: string; let triple: string; const prefix = process.env['VSCODE_SYSROOT_PREFIX'] ?? '-glibc-2.28-gcc-10.5.0'; switch (arch) { case 'amd64': expectedName = `x86_64-linux-gnu${prefix}.tar.gz`; triple = 'x86_64-linux-gnu'; break; case 'arm64': if (isMusl) { expectedName = 'aarch64-linux-musl-gcc-10.3.0.tar.gz'; triple = 'aarch64-linux-musl'; } else { expectedName = `aarch64-linux-gnu${prefix}.tar.gz`; triple = 'aarch64-linux-gnu'; } break; case 'armhf': expectedName = `arm-rpi-linux-gnueabihf${prefix}.tar.gz`; triple = 'arm-rpi-linux-gnueabihf'; break; } console.log(`Fetching ${expectedName} for ${triple}`); const checksumSha256 = getVSCodeSysrootChecksum(expectedName); if (!checksumSha256) { throw new Error(`Could not find checksum for ${expectedName}`); } const sysroot = process.env['VSCODE_SYSROOT_DIR'] ?? path.join(tmpdir(), `vscode-${arch}-sysroot`); const stamp = path.join(sysroot, '.stamp'); let result = `${sysroot}/${triple}/${triple}/sysroot`; if (isMusl) { result = `${sysroot}/output/${triple}`; } if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === expectedName) { return result; } console.log(`Installing ${arch} root image: ${sysroot}`); fs.rmSync(sysroot, { recursive: true, force: true }); fs.mkdirSync(sysroot, { recursive: true }); await fetchUrl({ checksumSha256, assetName: expectedName, dest: sysroot }); fs.writeFileSync(stamp, expectedName); return result; } export async function getChromiumSysroot(arch: DebianArchString): Promise { const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${getElectronVersion().electronVersion}/script/sysroots.json`; const sysrootDictLocation = `${tmpdir()}/sysroots.json`; const result = spawnSync('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]); if (result.status !== 0) { throw new Error('Cannot retrieve sysroots.json. Stderr:\n' + result.stderr); } const sysrootInfo = JSON.parse(fs.readFileSync(sysrootDictLocation, 'utf8')); const sysrootArch = `bullseye_${arch}`; const sysrootDict: SysrootDictEntry = sysrootInfo[sysrootArch]; const tarballFilename = sysrootDict['Tarball']; const tarballSha = sysrootDict['Sha256Sum']; const sysroot = path.join(tmpdir(), sysrootDict['SysrootDir']); const url = [URL_PREFIX, URL_PATH, tarballSha].join('/'); const stamp = path.join(sysroot, '.stamp'); if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === url) { return sysroot; } console.log(`Installing Debian ${arch} root image: ${sysroot}`); fs.rmSync(sysroot, { recursive: true, force: true }); fs.mkdirSync(sysroot); const tarball = path.join(sysroot, tarballFilename); console.log(`Downloading ${url}`); let downloadSuccess = false; for (let i = 0; i < 3 && !downloadSuccess; i++) { fs.writeFileSync(tarball, ''); await new Promise((c) => { https.get(url, (res) => { res.on('data', (chunk) => { fs.appendFileSync(tarball, chunk); }); res.on('end', () => { downloadSuccess = true; c(); }); }).on('error', (err) => { console.error('Encountered an error during the download attempt: ' + err.message); c(); }); }); } if (!downloadSuccess) { fs.rmSync(tarball); throw new Error('Failed to download ' + url); } const sha = getSha(tarball); if (sha !== tarballSha) { throw new Error(`Tarball checksum is wrong. Expected ${tarballSha}, actual ${sha}`); } const proc = spawnSync('tar', ['xf', tarball, '-C', sysroot]); if (proc.status) { throw new Error('Tarball extraction failed with code ' + proc.status); } fs.rmSync(tarball); fs.writeFileSync(stamp, url); return sysroot; }