Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,28 @@ jobs:
with:
node-version: '22.x'

- name: Install sysroot
if: runner.os == 'Linux'
run: |
sudo apt-get update -qq
sudo apt-get install -y gcc-10 g++-10
SYSROOT_PATH=$(node scripts/linux/install-sysroot.js x64 | grep "SYSROOT_PATH=" | cut -d= -f2)
echo "SYSROOT_PATH=$SYSROOT_PATH" >> $GITHUB_ENV
echo "Sysroot path set to: $SYSROOT_PATH"
echo "CC=gcc-10" >> $GITHUB_ENV
echo "CXX=g++-10" >> $GITHUB_ENV

- name: Install dependencies and build
run: npm ci

- name: Verify GLIBC requirements
if: runner.os == 'Linux'
run: |
EXPECTED_GLIBC_VERSION="2.28" \
EXPECTED_GLIBCXX_VERSION="3.4.25" \
SEARCH_PATH="build" \
./scripts/linux/verify-glibc-requirements.sh

- name: Test
run: npm test

Expand Down
56 changes: 56 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
'-lutil'
],
'cflags': ['-Wall', '-O2', '-D_FORTIFY_SOURCE=2'],
'ldflags': [],
'conditions': [
# http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html
# One some systems (at least including Cygwin, Interix,
Expand All @@ -88,6 +89,61 @@
'libraries!': [
'-lutil'
]
}],
['OS=="linux"', {
'variables': {
'sysroot%': '<!(node -p "process.env.SYSROOT_PATH || \'\'")',
'target_arch%': '<!(node -p "process.env.npm_config_arch || process.arch")',
},
'conditions': [
['sysroot!=""', {
'variables': {
'gcc_include%': '<!(${CXX:-g++} -print-file-name=include)',
},
'conditions': [
['target_arch=="x64"', {
'cflags': [
'--sysroot=<(sysroot)',
'-nostdinc',
'-isystem<(gcc_include)',
'-isystem<(sysroot)/usr/include',
'-isystem<(sysroot)/usr/include/x86_64-linux-gnu'
],
'cflags_cc': [
'-nostdinc++',
'-isystem<(sysroot)/../include/c++/10.5.0',
'-isystem<(sysroot)/../include/c++/10.5.0/x86_64-linux-gnu',
'-isystem<(sysroot)/../include/c++/10.5.0/backward'
],
'ldflags': [
'--sysroot=<(sysroot)',
'-L<(sysroot)/lib',
'-L<(sysroot)/usr/lib'
],
}],
['target_arch=="arm64"', {
'cflags': [
'--sysroot=<(sysroot)',
'-nostdinc',
'-isystem<(gcc_include)',
'-isystem<(sysroot)/usr/include',
'-isystem<(sysroot)/usr/include/aarch64-linux-gnu'
],
'cflags_cc': [
'-nostdinc++',
'-isystem<(sysroot)/../include/c++/10.5.0',
'-isystem<(sysroot)/../include/c++/10.5.0/aarch64-linux-gnu',
'-isystem<(sysroot)/../include/c++/10.5.0/backward'
],
'ldflags': [
'--sysroot=<(sysroot)',
'-L<(sysroot)/lib',
'-L<(sysroot)/usr/lib'
],
}]
]
}]
]
}]
]
}
Expand Down
15 changes: 15 additions & 0 deletions pipelines/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,23 @@ steps:
addToPath: true
displayName: 'Use latest Python 3.x'

- bash: |
if [ "$(uname)" = "Linux" ]; then
sudo apt-get update -qq
sudo apt-get install -y gcc-10 g++-10
SYSROOT_PATH=$(node scripts/linux/install-sysroot.js ${{ parameters.arch }} | grep "SYSROOT_PATH=" | cut -d= -f2)
echo "##vso[task.setvariable variable=SYSROOT_PATH]$SYSROOT_PATH"
echo "##vso[task.setvariable variable=CC]gcc-10"
echo "##vso[task.setvariable variable=CXX]g++-10"
echo "Sysroot path set to: $SYSROOT_PATH"
fi
displayName: 'Install sysroot (Linux only)'

- script: npm ci
displayName: 'Install dependencies'
env:
ARCH: ${{ parameters.arch }}
npm_config_arch: ${{ parameters.arch }}
SYSROOT_PATH: $(SYSROOT_PATH)
CC: $(CC)
CXX: $(CXX)
6 changes: 2 additions & 4 deletions pipelines/prebuilds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,6 @@ extends:
cp -r $(Build.ArtifactStagingDirectory)/win32-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/
cp -r $(Build.ArtifactStagingDirectory)/darwin-x64 $(Build.ArtifactStagingDirectory)/prebuilds/
cp -r $(Build.ArtifactStagingDirectory)/darwin-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/

# Exclude Linux prebuilds for now
# cp -r $(Build.ArtifactStagingDirectory)/linux-x64 $(Build.ArtifactStagingDirectory)/prebuilds/
# cp -r $(Build.ArtifactStagingDirectory)/linux-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/
cp -r $(Build.ArtifactStagingDirectory)/linux-x64 $(Build.ArtifactStagingDirectory)/prebuilds/
cp -r $(Build.ArtifactStagingDirectory)/linux-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/
displayName: 'Create prebuilds archive'
2 changes: 2 additions & 0 deletions scripts/linux/checksums.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
3122af49c493c5c767c2b0772a41119cbdc9803125a705683445b4066dc88b82 x86_64-linux-gnu-glibc-2.28-gcc-10.5.0.tar.gz
3baac81a39b69e0929e4700f4f78f022adefc515010054ec393565657c4fff32 aarch64-linux-gnu-glibc-2.28-gcc-10.5.0.tar.gz
157 changes: 157 additions & 0 deletions scripts/linux/install-sysroot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

const { execSync } = require('child_process');
const { tmpdir } = require('os');
const fs = require('fs');
const path = require('path');
const { createHash } = require('crypto');

const REPO_ROOT = path.join(__dirname, '..', '..');

const ghApiHeaders = {
Accept: 'application/vnd.github.v3+json',
'User-Agent': 'node-pty Build',
};

if (process.env.GITHUB_TOKEN) {
ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64');
}

const ghDownloadHeaders = {
...ghApiHeaders,
Accept: 'application/octet-stream',
};

function getSysrootChecksum(expectedName) {
const checksumPath = path.join(REPO_ROOT, 'scripts', 'linux', 'checksums.txt');
const checksums = fs.readFileSync(checksumPath, 'utf8');
for (const line of checksums.split('\n')) {
const [checksum, name] = line.split(/\s+/);
if (name === expectedName) {
return checksum;
}
}
return undefined;
}

async function fetchUrl(options, retries = 10, retryDelay = 1000) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30 * 1000);
const version = '20250407-330404';
try {
const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, {
headers: ghApiHeaders,
signal: controller.signal
});
if (response.ok && (response.status >= 200 && response.status < 300)) {
console.log(`Fetch completed: Status ${response.status}.`);
const contents = Buffer.from(await response.arrayBuffer());
const asset = JSON.parse(contents.toString()).assets.find((a) => 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 assetResponse = await fetch(asset.url, {
headers: ghDownloadHeaders
});
if (assetResponse.ok && (assetResponse.status >= 200 && assetResponse.status < 300)) {
const assetContents = Buffer.from(await assetResponse.arrayBuffer());
console.log(`Fetched response body buffer: ${assetContents.byteLength} bytes`);
if (options.checksumSha256) {
const actualSHA256Checksum = createHash('sha256').update(assetContents).digest('hex');
if (actualSHA256Checksum !== options.checksumSha256) {
throw new Error(`Checksum mismatch for ${asset.url} (expected ${options.checksumSha256}, actual ${actualSHA256Checksum})`);
}
}
console.log(`Verified SHA256 checksums match for ${asset.url}`);
const tarCommand = `tar -xz -C ${options.dest}`;
execSync(tarCommand, { input: assetContents });
console.log(`Fetch complete!`);
return;
}
throw new Error(`Request ${asset.url} failed with status code: ${assetResponse.status}`);
}
throw new Error(`Request https://api.github.com failed with status code: ${response.status}`);
} finally {
clearTimeout(timeout);
}
} catch (e) {
if (retries > 0) {
console.log(`Fetching failed: ${e}`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
return fetchUrl(options, retries - 1, retryDelay);
}
throw e;
}
}

async function getSysroot(arch) {
let expectedName;
let triple;
const prefix = '-glibc-2.28-gcc-10.5.0';

switch (arch) {
case 'x64':
expectedName = `x86_64-linux-gnu${prefix}.tar.gz`;
triple = 'x86_64-linux-gnu';
break;
case 'arm64':
expectedName = `aarch64-linux-gnu${prefix}.tar.gz`;
triple = 'aarch64-linux-gnu';
break;
default:
throw new Error(`Unsupported architecture: ${arch}`);
}

console.log(`Fetching ${expectedName} for ${triple}`);
const checksumSha256 = getSysrootChecksum(expectedName);
if (!checksumSha256) {
throw new Error(`Could not find checksum for ${expectedName}`);
}

const sysroot = path.join(tmpdir(), `vscode-${arch}-sysroot`);
const stamp = path.join(sysroot, '.stamp');
const result = `${sysroot}/${triple}/${triple}/sysroot`;

if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === expectedName) {
console.log(`Sysroot already installed: ${result}`);
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);
console.log(`Sysroot installed: ${result}`);
return result;
}

async function main() {
const arch = process.argv[2] || process.env.ARCH || 'x64';
console.log(`Installing sysroot for architecture: ${arch}`);

try {
const sysrootPath = await getSysroot(arch);
console.log(`SYSROOT_PATH=${sysrootPath}`);
} catch (error) {
console.error('Error installing sysroot:', error);
process.exit(1);
}
}

if (require.main === module) {
main();
}

module.exports = { getSysroot };
36 changes: 36 additions & 0 deletions scripts/linux/verify-glibc-requirements.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash

set -e

# Get all files with .node extension from given folder
files=$(find $SEARCH_PATH -name "*.node")

echo "Verifying requirements for files: $files"

for file in $files; do
glibc_version="$EXPECTED_GLIBC_VERSION"
glibcxx_version="$EXPECTED_GLIBCXX_VERSION"
while IFS= read -r line; do
if [[ $line == *"GLIBC_"* ]]; then
version=$(echo "$line" | awk '{if ($5 ~ /^[0-9a-fA-F]+$/) print $6; else print $5}' | tr -d '()')
version=${version#*_}
if [[ $(printf "%s\n%s" "$version" "$glibc_version" | sort -V | tail -n1) == "$version" ]]; then
glibc_version=$version
fi
elif [[ $line == *"GLIBCXX_"* ]]; then
version=$(echo "$line" | awk '{if ($5 ~ /^[0-9a-fA-F]+$/) print $6; else print $5}' | tr -d '()')
version=${version#*_}
if [[ $(printf "%s\n%s" "$version" "$glibcxx_version" | sort -V | tail -n1) == "$version" ]]; then
glibcxx_version=$version
fi
fi
done < <("$SYSROOT_PATH/../bin/objdump" -T "$file")

if [[ "$glibc_version" != "$EXPECTED_GLIBC_VERSION" ]]; then
echo "Error: File $file has dependency on GLIBC > $EXPECTED_GLIBC_VERSION, found $glibc_version"
exit 1
fi
if [[ "$glibcxx_version" != "$EXPECTED_GLIBCXX_VERSION" ]]; then
echo "Error: File $file has dependency on GLIBCXX > $EXPECTED_GLIBCXX_VERSION, found $glibcxx_version"
fi
done