From 3e7b7e8bd748fe8e9217ba675b9cf631bb3e657b Mon Sep 17 00:00:00 2001 From: zerone0x Date: Sun, 8 Mar 2026 14:29:31 +0800 Subject: [PATCH] fix: handle null libc version on Android/Termux to prevent crash On Android/Termux with bionic libc, detect-libc returns null for the libc version. The npm installer's binary.js crashes with "Cannot read properties of null (reading 'split')" because it calls .split(".") on the null return value without a null check. This adds a null check for libcVersion in binary.js and falls back to the static musl binary when the libc version cannot be detected. The patched binary.js is applied to the npm package during the release build via a new step in the release workflow. Fixes #271 Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 16 ++++ npm/binary.js | 137 ++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 npm/binary.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b2d110a..f5250053 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -212,6 +212,22 @@ jobs: echo "EOF" >> "$GITHUB_OUTPUT" cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: Patch npm binary.js for libc detection + shell: bash + run: | + # Fix null libcVersion crash on Android/Termux (bionic libc) + # See: https://github.com/googleworkspace/cli/issues/271 + NPM_TARBALL=$(find target/distrib -name '*-npm-package.tar.gz' | head -1) + if [ -n "$NPM_TARBALL" ]; then + WORK_DIR=$(mktemp -d) + tar xzf "$NPM_TARBALL" -C "$WORK_DIR" + cp npm/binary.js "$WORK_DIR/package/binary.js" + tar czf "$NPM_TARBALL" -C "$WORK_DIR" package + rm -rf "$WORK_DIR" + echo "Patched npm binary.js in $NPM_TARBALL" + else + echo "No npm tarball found, skipping patch" + fi - name: "Upload artifacts" uses: actions/upload-artifact@v6 with: diff --git a/npm/binary.js b/npm/binary.js new file mode 100644 index 00000000..e908a5ec --- /dev/null +++ b/npm/binary.js @@ -0,0 +1,137 @@ +const { Package } = require("./binary-install"); +const os = require("os"); +const cTable = require("console.table"); +const libc = require("detect-libc"); +const { configureProxy } = require("axios-proxy-builder"); + +const error = (msg) => { + console.error(msg); + process.exit(1); +}; + +const { + name, + artifactDownloadUrls, + supportedPlatforms, + glibcMinimum, +} = require("./package.json"); + +// FIXME: implement NPM installer handling of fallback download URLs +const artifactDownloadUrl = artifactDownloadUrls[0]; +const builderGlibcMajorVersion = glibcMinimum.major; +const builderGlibcMinorVersion = glibcMinimum.series; + +const getPlatform = () => { + const rawOsType = os.type(); + const rawArchitecture = os.arch(); + + // We want to use rust-style target triples as the canonical key + // for a platform, so translate the "os" library's concepts into rust ones + let osType = ""; + switch (rawOsType) { + case "Windows_NT": + osType = "pc-windows-msvc"; + break; + case "Darwin": + osType = "apple-darwin"; + break; + case "Linux": + osType = "unknown-linux-gnu"; + break; + } + + let arch = ""; + switch (rawArchitecture) { + case "x64": + arch = "x86_64"; + break; + case "arm64": + arch = "aarch64"; + break; + } + + if (rawOsType === "Linux") { + if (libc.familySync() == "musl") { + osType = "unknown-linux-musl-dynamic"; + } else if (libc.isNonGlibcLinuxSync()) { + console.warn( + "Your libc is neither glibc nor musl; trying static musl binary instead", + ); + osType = "unknown-linux-musl-static"; + } else { + let libcVersion = libc.versionSync(); + if (libcVersion == null) { + // detect-libc could not determine the libc version (e.g. Android/Termux + // with bionic libc). Fall back to static musl binary. + console.warn( + "Could not detect libc version; trying static musl binary instead", + ); + osType = "unknown-linux-musl-static"; + } else { + let splitLibcVersion = libcVersion.split("."); + let libcMajorVersion = splitLibcVersion[0]; + let libcMinorVersion = splitLibcVersion[1]; + if ( + libcMajorVersion != builderGlibcMajorVersion || + libcMinorVersion < builderGlibcMinorVersion + ) { + // We can't run the glibc binaries, but we can run the static musl ones + // if they exist + console.warn( + "Your glibc isn't compatible; trying static musl binary instead", + ); + osType = "unknown-linux-musl-static"; + } + } + } + } + + // Assume the above succeeded and build a target triple to look things up with. + // If any of it failed, this lookup will fail and we'll handle it like normal. + let targetTriple = `${arch}-${osType}`; + let platform = supportedPlatforms[targetTriple]; + + if (!platform) { + error( + `Platform with type "${rawOsType}" and architecture "${rawArchitecture}" is not supported by ${name}.\nYour system must be one of the following:\n\n${Object.keys( + supportedPlatforms, + ).join(",")}`, + ); + } + + return platform; +}; + +const getPackage = () => { + const platform = getPlatform(); + const url = `${artifactDownloadUrl}/${platform.artifactName}`; + let filename = platform.artifactName; + let ext = platform.zipExt; + let binary = new Package(platform, name, url, filename, ext, platform.bins); + + return binary; +}; + +const install = (suppressLogs) => { + if (!artifactDownloadUrl || artifactDownloadUrl.length === 0) { + console.warn("in demo mode, not installing binaries"); + return; + } + const package = getPackage(); + const proxy = configureProxy(package.url); + + return package.install(proxy, suppressLogs); +}; + +const run = (binaryName) => { + const package = getPackage(); + const proxy = configureProxy(package.url); + + package.run(binaryName, proxy); +}; + +module.exports = { + install, + run, + getPackage, +};