Skip to content

Commit 772905e

Browse files
authored
Add sysroot to compile Linux prebuilds against glibc 2.28 (#853)
* Add sysroot to compile Linux prebuilds against glibc 2.28 Fixes #851 * Add CI check for glibc version * Fix linker flags to include sysroot library paths * Revert package-lock change
1 parent 7c34e58 commit 772905e

File tree

7 files changed

+287
-4
lines changed

7 files changed

+287
-4
lines changed

.github/workflows/ci.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,28 @@ jobs:
2222
with:
2323
node-version: '22.x'
2424

25+
- name: Install sysroot
26+
if: runner.os == 'Linux'
27+
run: |
28+
sudo apt-get update -qq
29+
sudo apt-get install -y gcc-10 g++-10
30+
SYSROOT_PATH=$(node scripts/linux/install-sysroot.js x64 | grep "SYSROOT_PATH=" | cut -d= -f2)
31+
echo "SYSROOT_PATH=$SYSROOT_PATH" >> $GITHUB_ENV
32+
echo "Sysroot path set to: $SYSROOT_PATH"
33+
echo "CC=gcc-10" >> $GITHUB_ENV
34+
echo "CXX=g++-10" >> $GITHUB_ENV
35+
2536
- name: Install dependencies and build
2637
run: npm ci
2738

39+
- name: Verify GLIBC requirements
40+
if: runner.os == 'Linux'
41+
run: |
42+
EXPECTED_GLIBC_VERSION="2.28" \
43+
EXPECTED_GLIBCXX_VERSION="3.4.25" \
44+
SEARCH_PATH="build" \
45+
./scripts/linux/verify-glibc-requirements.sh
46+
2847
- name: Test
2948
run: npm test
3049

binding.gyp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
'-lutil'
8181
],
8282
'cflags': ['-Wall', '-O2', '-D_FORTIFY_SOURCE=2'],
83+
'ldflags': [],
8384
'conditions': [
8485
# http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html
8586
# One some systems (at least including Cygwin, Interix,
@@ -88,6 +89,61 @@
8889
'libraries!': [
8990
'-lutil'
9091
]
92+
}],
93+
['OS=="linux"', {
94+
'variables': {
95+
'sysroot%': '<!(node -p "process.env.SYSROOT_PATH || \'\'")',
96+
'target_arch%': '<!(node -p "process.env.npm_config_arch || process.arch")',
97+
},
98+
'conditions': [
99+
['sysroot!=""', {
100+
'variables': {
101+
'gcc_include%': '<!(${CXX:-g++} -print-file-name=include)',
102+
},
103+
'conditions': [
104+
['target_arch=="x64"', {
105+
'cflags': [
106+
'--sysroot=<(sysroot)',
107+
'-nostdinc',
108+
'-isystem<(gcc_include)',
109+
'-isystem<(sysroot)/usr/include',
110+
'-isystem<(sysroot)/usr/include/x86_64-linux-gnu'
111+
],
112+
'cflags_cc': [
113+
'-nostdinc++',
114+
'-isystem<(sysroot)/../include/c++/10.5.0',
115+
'-isystem<(sysroot)/../include/c++/10.5.0/x86_64-linux-gnu',
116+
'-isystem<(sysroot)/../include/c++/10.5.0/backward'
117+
],
118+
'ldflags': [
119+
'--sysroot=<(sysroot)',
120+
'-L<(sysroot)/lib',
121+
'-L<(sysroot)/usr/lib'
122+
],
123+
}],
124+
['target_arch=="arm64"', {
125+
'cflags': [
126+
'--sysroot=<(sysroot)',
127+
'-nostdinc',
128+
'-isystem<(gcc_include)',
129+
'-isystem<(sysroot)/usr/include',
130+
'-isystem<(sysroot)/usr/include/aarch64-linux-gnu'
131+
],
132+
'cflags_cc': [
133+
'-nostdinc++',
134+
'-isystem<(sysroot)/../include/c++/10.5.0',
135+
'-isystem<(sysroot)/../include/c++/10.5.0/aarch64-linux-gnu',
136+
'-isystem<(sysroot)/../include/c++/10.5.0/backward'
137+
],
138+
'ldflags': [
139+
'--sysroot=<(sysroot)',
140+
'-L<(sysroot)/lib',
141+
'-L<(sysroot)/usr/lib'
142+
],
143+
}]
144+
]
145+
}]
146+
]
91147
}]
92148
]
93149
}

pipelines/build.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,23 @@ steps:
1313
addToPath: true
1414
displayName: 'Use latest Python 3.x'
1515

16+
- bash: |
17+
if [ "$(uname)" = "Linux" ]; then
18+
sudo apt-get update -qq
19+
sudo apt-get install -y gcc-10 g++-10
20+
SYSROOT_PATH=$(node scripts/linux/install-sysroot.js ${{ parameters.arch }} | grep "SYSROOT_PATH=" | cut -d= -f2)
21+
echo "##vso[task.setvariable variable=SYSROOT_PATH]$SYSROOT_PATH"
22+
echo "##vso[task.setvariable variable=CC]gcc-10"
23+
echo "##vso[task.setvariable variable=CXX]g++-10"
24+
echo "Sysroot path set to: $SYSROOT_PATH"
25+
fi
26+
displayName: 'Install sysroot (Linux only)'
27+
1628
- script: npm ci
1729
displayName: 'Install dependencies'
1830
env:
1931
ARCH: ${{ parameters.arch }}
2032
npm_config_arch: ${{ parameters.arch }}
33+
SYSROOT_PATH: $(SYSROOT_PATH)
34+
CC: $(CC)
35+
CXX: $(CXX)

pipelines/prebuilds.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,6 @@ extends:
138138
cp -r $(Build.ArtifactStagingDirectory)/win32-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/
139139
cp -r $(Build.ArtifactStagingDirectory)/darwin-x64 $(Build.ArtifactStagingDirectory)/prebuilds/
140140
cp -r $(Build.ArtifactStagingDirectory)/darwin-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/
141-
142-
# Exclude Linux prebuilds for now
143-
# cp -r $(Build.ArtifactStagingDirectory)/linux-x64 $(Build.ArtifactStagingDirectory)/prebuilds/
144-
# cp -r $(Build.ArtifactStagingDirectory)/linux-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/
141+
cp -r $(Build.ArtifactStagingDirectory)/linux-x64 $(Build.ArtifactStagingDirectory)/prebuilds/
142+
cp -r $(Build.ArtifactStagingDirectory)/linux-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/
145143
displayName: 'Create prebuilds archive'

scripts/linux/checksums.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
3122af49c493c5c767c2b0772a41119cbdc9803125a705683445b4066dc88b82 x86_64-linux-gnu-glibc-2.28-gcc-10.5.0.tar.gz
2+
3baac81a39b69e0929e4700f4f78f022adefc515010054ec393565657c4fff32 aarch64-linux-gnu-glibc-2.28-gcc-10.5.0.tar.gz

scripts/linux/install-sysroot.js

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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+
const { execSync } = require('child_process');
7+
const { tmpdir } = require('os');
8+
const fs = require('fs');
9+
const path = require('path');
10+
const { createHash } = require('crypto');
11+
12+
const REPO_ROOT = path.join(__dirname, '..', '..');
13+
14+
const ghApiHeaders = {
15+
Accept: 'application/vnd.github.v3+json',
16+
'User-Agent': 'node-pty Build',
17+
};
18+
19+
if (process.env.GITHUB_TOKEN) {
20+
ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64');
21+
}
22+
23+
const ghDownloadHeaders = {
24+
...ghApiHeaders,
25+
Accept: 'application/octet-stream',
26+
};
27+
28+
function getSysrootChecksum(expectedName) {
29+
const checksumPath = path.join(REPO_ROOT, 'scripts', 'linux', 'checksums.txt');
30+
const checksums = fs.readFileSync(checksumPath, 'utf8');
31+
for (const line of checksums.split('\n')) {
32+
const [checksum, name] = line.split(/\s+/);
33+
if (name === expectedName) {
34+
return checksum;
35+
}
36+
}
37+
return undefined;
38+
}
39+
40+
async function fetchUrl(options, retries = 10, retryDelay = 1000) {
41+
try {
42+
const controller = new AbortController();
43+
const timeout = setTimeout(() => controller.abort(), 30 * 1000);
44+
const version = '20250407-330404';
45+
try {
46+
const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, {
47+
headers: ghApiHeaders,
48+
signal: controller.signal
49+
});
50+
if (response.ok && (response.status >= 200 && response.status < 300)) {
51+
console.log(`Fetch completed: Status ${response.status}.`);
52+
const contents = Buffer.from(await response.arrayBuffer());
53+
const asset = JSON.parse(contents.toString()).assets.find((a) => a.name === options.assetName);
54+
if (!asset) {
55+
throw new Error(`Could not find asset in release of Microsoft/vscode-linux-build-agent @ ${version}`);
56+
}
57+
console.log(`Found asset ${options.assetName} @ ${asset.url}.`);
58+
const assetResponse = await fetch(asset.url, {
59+
headers: ghDownloadHeaders
60+
});
61+
if (assetResponse.ok && (assetResponse.status >= 200 && assetResponse.status < 300)) {
62+
const assetContents = Buffer.from(await assetResponse.arrayBuffer());
63+
console.log(`Fetched response body buffer: ${assetContents.byteLength} bytes`);
64+
if (options.checksumSha256) {
65+
const actualSHA256Checksum = createHash('sha256').update(assetContents).digest('hex');
66+
if (actualSHA256Checksum !== options.checksumSha256) {
67+
throw new Error(`Checksum mismatch for ${asset.url} (expected ${options.checksumSha256}, actual ${actualSHA256Checksum})`);
68+
}
69+
}
70+
console.log(`Verified SHA256 checksums match for ${asset.url}`);
71+
const tarCommand = `tar -xz -C ${options.dest}`;
72+
execSync(tarCommand, { input: assetContents });
73+
console.log(`Fetch complete!`);
74+
return;
75+
}
76+
throw new Error(`Request ${asset.url} failed with status code: ${assetResponse.status}`);
77+
}
78+
throw new Error(`Request https://api.github.com failed with status code: ${response.status}`);
79+
} finally {
80+
clearTimeout(timeout);
81+
}
82+
} catch (e) {
83+
if (retries > 0) {
84+
console.log(`Fetching failed: ${e}`);
85+
await new Promise(resolve => setTimeout(resolve, retryDelay));
86+
return fetchUrl(options, retries - 1, retryDelay);
87+
}
88+
throw e;
89+
}
90+
}
91+
92+
async function getSysroot(arch) {
93+
let expectedName;
94+
let triple;
95+
const prefix = '-glibc-2.28-gcc-10.5.0';
96+
97+
switch (arch) {
98+
case 'x64':
99+
expectedName = `x86_64-linux-gnu${prefix}.tar.gz`;
100+
triple = 'x86_64-linux-gnu';
101+
break;
102+
case 'arm64':
103+
expectedName = `aarch64-linux-gnu${prefix}.tar.gz`;
104+
triple = 'aarch64-linux-gnu';
105+
break;
106+
default:
107+
throw new Error(`Unsupported architecture: ${arch}`);
108+
}
109+
110+
console.log(`Fetching ${expectedName} for ${triple}`);
111+
const checksumSha256 = getSysrootChecksum(expectedName);
112+
if (!checksumSha256) {
113+
throw new Error(`Could not find checksum for ${expectedName}`);
114+
}
115+
116+
const sysroot = path.join(tmpdir(), `vscode-${arch}-sysroot`);
117+
const stamp = path.join(sysroot, '.stamp');
118+
const result = `${sysroot}/${triple}/${triple}/sysroot`;
119+
120+
if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === expectedName) {
121+
console.log(`Sysroot already installed: ${result}`);
122+
return result;
123+
}
124+
125+
console.log(`Installing ${arch} root image: ${sysroot}`);
126+
fs.rmSync(sysroot, { recursive: true, force: true });
127+
fs.mkdirSync(sysroot, { recursive: true });
128+
129+
await fetchUrl({
130+
checksumSha256,
131+
assetName: expectedName,
132+
dest: sysroot
133+
});
134+
135+
fs.writeFileSync(stamp, expectedName);
136+
console.log(`Sysroot installed: ${result}`);
137+
return result;
138+
}
139+
140+
async function main() {
141+
const arch = process.argv[2] || process.env.ARCH || 'x64';
142+
console.log(`Installing sysroot for architecture: ${arch}`);
143+
144+
try {
145+
const sysrootPath = await getSysroot(arch);
146+
console.log(`SYSROOT_PATH=${sysrootPath}`);
147+
} catch (error) {
148+
console.error('Error installing sysroot:', error);
149+
process.exit(1);
150+
}
151+
}
152+
153+
if (require.main === module) {
154+
main();
155+
}
156+
157+
module.exports = { getSysroot };
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
# Get all files with .node extension from given folder
6+
files=$(find $SEARCH_PATH -name "*.node")
7+
8+
echo "Verifying requirements for files: $files"
9+
10+
for file in $files; do
11+
glibc_version="$EXPECTED_GLIBC_VERSION"
12+
glibcxx_version="$EXPECTED_GLIBCXX_VERSION"
13+
while IFS= read -r line; do
14+
if [[ $line == *"GLIBC_"* ]]; then
15+
version=$(echo "$line" | awk '{if ($5 ~ /^[0-9a-fA-F]+$/) print $6; else print $5}' | tr -d '()')
16+
version=${version#*_}
17+
if [[ $(printf "%s\n%s" "$version" "$glibc_version" | sort -V | tail -n1) == "$version" ]]; then
18+
glibc_version=$version
19+
fi
20+
elif [[ $line == *"GLIBCXX_"* ]]; then
21+
version=$(echo "$line" | awk '{if ($5 ~ /^[0-9a-fA-F]+$/) print $6; else print $5}' | tr -d '()')
22+
version=${version#*_}
23+
if [[ $(printf "%s\n%s" "$version" "$glibcxx_version" | sort -V | tail -n1) == "$version" ]]; then
24+
glibcxx_version=$version
25+
fi
26+
fi
27+
done < <("$SYSROOT_PATH/../bin/objdump" -T "$file")
28+
29+
if [[ "$glibc_version" != "$EXPECTED_GLIBC_VERSION" ]]; then
30+
echo "Error: File $file has dependency on GLIBC > $EXPECTED_GLIBC_VERSION, found $glibc_version"
31+
exit 1
32+
fi
33+
if [[ "$glibcxx_version" != "$EXPECTED_GLIBCXX_VERSION" ]]; then
34+
echo "Error: File $file has dependency on GLIBCXX > $EXPECTED_GLIBCXX_VERSION, found $glibcxx_version"
35+
fi
36+
done

0 commit comments

Comments
 (0)