From e7b513c47f4692d78115b688d36c62b562e9733c Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Tue, 30 Dec 2025 10:29:14 -0500 Subject: [PATCH 1/4] Add sysroot to compile Linux prebuilds against glibc 2.28 Fixes #851 --- .github/workflows/ci.yml | 11 +++ binding.gyp | 48 ++++++++++ pipelines/build.yml | 15 +++ pipelines/prebuilds.yml | 6 +- scripts/linux/checksums.txt | 2 + scripts/linux/install-sysroot.js | 157 +++++++++++++++++++++++++++++++ 6 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 scripts/linux/checksums.txt create mode 100755 scripts/linux/install-sysroot.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8350464..32a18256 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,17 @@ 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 diff --git a/binding.gyp b/binding.gyp index 5f63978b..9f57e8e8 100644 --- a/binding.gyp +++ b/binding.gyp @@ -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, @@ -88,6 +89,53 @@ 'libraries!': [ '-lutil' ] + }], + ['OS=="linux"', { + 'variables': { + 'sysroot%': ' 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 }; From 965cf18ff852ae2bb0fd70582a2fe495ded40ff7 Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Wed, 31 Dec 2025 22:34:54 +0000 Subject: [PATCH 2/4] Add CI check for glibc version --- .github/workflows/ci.yml | 8 +++++ package-lock.json | 24 +++++++++++++-- scripts/linux/verify-glibc-requirements.sh | 36 ++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100755 scripts/linux/verify-glibc-requirements.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32a18256..3839a3ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,14 @@ jobs: - 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 diff --git a/package-lock.json b/package-lock.json index 0255eb07..0da9d83e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "node-pty", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "node-pty", - "version": "1.0.0", + "version": "1.1.0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -298,6 +298,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.34.0.tgz", "integrity": "sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==", "dev": true, + "peer": true, "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", "@typescript-eslint/experimental-utils": "2.34.0", @@ -362,6 +363,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -935,6 +937,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "ajv": "^6.10.0", @@ -1374,6 +1377,21 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -3150,6 +3168,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3228,6 +3247,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/scripts/linux/verify-glibc-requirements.sh b/scripts/linux/verify-glibc-requirements.sh new file mode 100755 index 00000000..a911dad1 --- /dev/null +++ b/scripts/linux/verify-glibc-requirements.sh @@ -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 From 26ba629f35ba52c83b5911302f2ee3c9f6bdc287 Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Wed, 31 Dec 2025 22:45:38 +0000 Subject: [PATCH 3/4] Fix linker flags to include sysroot library paths --- binding.gyp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/binding.gyp b/binding.gyp index 9f57e8e8..8c8c535a 100644 --- a/binding.gyp +++ b/binding.gyp @@ -115,7 +115,11 @@ '-isystem<(sysroot)/../include/c++/10.5.0/x86_64-linux-gnu', '-isystem<(sysroot)/../include/c++/10.5.0/backward' ], - 'ldflags': ['--sysroot=<(sysroot)'], + 'ldflags': [ + '--sysroot=<(sysroot)', + '-L<(sysroot)/lib', + '-L<(sysroot)/usr/lib' + ], }], ['target_arch=="arm64"', { 'cflags': [ @@ -131,7 +135,11 @@ '-isystem<(sysroot)/../include/c++/10.5.0/aarch64-linux-gnu', '-isystem<(sysroot)/../include/c++/10.5.0/backward' ], - 'ldflags': ['--sysroot=<(sysroot)'], + 'ldflags': [ + '--sysroot=<(sysroot)', + '-L<(sysroot)/lib', + '-L<(sysroot)/usr/lib' + ], }] ] }] From e1ee344ef98a596c00485f29e5f7f1990d2ae217 Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Wed, 31 Dec 2025 22:52:33 +0000 Subject: [PATCH 4/4] Revert package-lock change --- package-lock.json | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0da9d83e..0255eb07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "node-pty", - "version": "1.1.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "node-pty", - "version": "1.1.0", + "version": "1.0.0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -298,7 +298,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.34.0.tgz", "integrity": "sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==", "dev": true, - "peer": true, "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", "@typescript-eslint/experimental-utils": "2.34.0", @@ -363,7 +362,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -937,7 +935,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "ajv": "^6.10.0", @@ -1377,21 +1374,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -3168,7 +3150,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3247,7 +3228,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver"