diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86e5875..cd28b95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,24 @@ jobs: artifact_features: '--no-default-features --features "tokio pac-engine-wasmtime"' use_cross: true run_tests: true + # Linux musl x86_64 + aarch64 via cross. The addon is cross-built but + # cannot be loaded by the glibc Node runtime on the hosted runner. + - name: linux musl x86_64 + os: ubuntu-latest + target: x86_64-unknown-linux-musl + features: '--no-default-features --features "tokio pac-engine pac-engine-wasmtime pac-engine-wasm2c"' + artifact_features: '--no-default-features --features "tokio pac-engine-wasmtime"' + use_cross: true + build_cross_addon: true + run_tests: true + - name: linux musl aarch64 + os: ubuntu-latest + target: aarch64-unknown-linux-musl + features: '--no-default-features --features "tokio pac-engine pac-engine-wasmtime pac-engine-wasm2c"' + artifact_features: '--no-default-features --features "tokio pac-engine-wasmtime"' + use_cross: true + build_cross_addon: true + run_tests: true # Linux armv7: no Wasmtime (no Cranelift backend for 32-bit ARM); # native + wasm2c, and the shipped artifact uses the wasm2c sandbox. - name: linux armv7 @@ -164,6 +182,13 @@ jobs: run: node npm/scripts/build-native.js ${{ matrix.target }} shell: bash + - name: Cross-build Node addon + if: matrix.build_cross_addon + env: + CARGO_BUILD_COMMAND: cross + run: node npm/scripts/build-native.js ${{ matrix.target }} + shell: bash + - name: Test Node addon if: ${{ !matrix.use_cross }} run: node npm/test/smoke.js @@ -264,6 +289,8 @@ jobs: # windows : native QuickJS + Wasmtime AOT/JIT + wasm2c; # WinHTTP in a separate backend-less build # macos/linux : native QuickJS + Wasmtime AOT/JIT + wasm2c + # linux musl : same engines, built with `cross`; aarch64 runs + # under qemu # linux armv7 : native QuickJS + wasm2c (no Cranelift for 32-bit # ARM), built with `cross` and run under qemu — the # absolute numbers are qemu-skewed, the point is @@ -281,6 +308,20 @@ jobs: os: ubuntu-latest features: pac-engine pac-engine-wasmtime pac-engine-wasm2c pac-engine-wasmtime-jit iterations: 3000 + - name: linux musl x64 + os: ubuntu-latest + target: x86_64-unknown-linux-musl + use_cross: true + features: pac-engine pac-engine-wasmtime pac-engine-wasm2c pac-engine-wasmtime-jit + iterations: 1000 + - name: linux musl arm64 (qemu) + os: ubuntu-latest + target: aarch64-unknown-linux-musl + use_cross: true + # JIT startup exceeds the PAC timeout under nested QEMU emulation; + # x64 musl and native desktop jobs cover the JIT backend. + features: pac-engine pac-engine-wasmtime pac-engine-wasm2c + iterations: 200 - name: linux armv7 (qemu) os: ubuntu-latest target: armv7-unknown-linux-gnueabihf @@ -341,7 +382,7 @@ jobs: - name: Install cross if: matrix.use_cross - run: cargo install cross --locked + run: cargo install cross --version 0.2.5 --locked # Build pac_bench once per backend with ONLY that backend compiled in, # then measure and immediately benchmark that exact artifact. Real @@ -349,7 +390,7 @@ jobs: # more meaningful than a combined all-backends binary. Each process # writes canonical URL/result rows; diffing those files preserves the # previous combined binary's byte-for-byte embedded-engine cross-check. - # (`--no-default-features` because the default set enables Wasmtime.) + # `--no-default-features` keeps every measured binary single-backend. - name: Build, measure, and benchmark each PAC backend run: | set -e @@ -569,6 +610,7 @@ jobs: - run: cargo doc --no-deps --all-features env: RUSTDOCFLAGS: -D warnings + - run: npm run test:platform-selection - run: npm run verify:packages licenses: diff --git a/Cross.toml b/Cross.toml index 8164ae5..acf8500 100644 --- a/Cross.toml +++ b/Cross.toml @@ -11,15 +11,25 @@ # is all the container needs. [build.env] -passthrough = ["OS_PROXY_RESOLVER_PAC_GUEST_C_DIR"] +passthrough = [ + "OS_PROXY_RESOLVER_PAC_GUEST_C_DIR", + "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS", + "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS", +] # Pin the cross-compilation CI toolchains instead of inheriting whatever images -# a future `cross` release selects. These images use glibc 2.23. +# a future `cross` release selects. The GNU images use glibc 2.23. [target.x86_64-unknown-linux-gnu] image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:0.2.5" [target.aarch64-unknown-linux-gnu] image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:0.2.5" +[target.x86_64-unknown-linux-musl] +image = "ghcr.io/cross-rs/x86_64-unknown-linux-musl:0.2.5" + +[target.aarch64-unknown-linux-musl] +image = "ghcr.io/cross-rs/aarch64-unknown-linux-musl:0.2.5" + [target.armv7-unknown-linux-gnueabihf] image = "ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:0.2.5" diff --git a/README.md b/README.md index 0668820..5cbda38 100644 --- a/README.md +++ b/README.md @@ -247,18 +247,18 @@ Builds as both `rlib` and `cdylib`. ## Node.js package `@vscode/os-proxy-resolver` exposes the resolver through Node-API, with -prebuilt addons for Windows and macOS x64/arm64 and glibc Linux -x64/arm64/armhf. The public package selects a platform-specific optional -dependency at runtime, so consumers install only the addon they need. Every -Windows addon uses WinHTTP with no embedded PAC backend; macOS/Linux addons use -the `pac-engine-wasm2c` backend. Consumers do not need Rust, a C compiler, or -WABT installed. - -Linux addons are built with the pinned glibc 2.28 sysroots from the shared +prebuilt addons for Windows and macOS x64/arm64, glibc Linux x64/arm64/armhf, +and musl Linux x64/arm64. The public package selects a platform-, architecture-, +and libc-specific optional dependency at runtime, so consumers install only the +addon they need. Every Windows addon uses WinHTTP with no embedded PAC backend; +macOS/Linux addons use the `pac-engine-wasm2c` backend. Consumers do not need +Rust, a C compiler, or WABT installed. + +GNU Linux addons are built with the pinned glibc 2.28 sysroots from the shared `vscode-engineering` npm pipeline and then inspected with that toolchain's `objdump`; publishing fails if any final `.node` artifact requires a GLIBC symbol newer than 2.28. This keeps them compatible with VS Code's glibc 2.28 -desktop baseline. +desktop baseline. Musl addons are cross-compiled in pinned `cross` containers. ```js const { @@ -282,7 +282,7 @@ versions. The Azure Pipeline in [`azure-pipelines/publish.yml`](azure-pipelines/publish.yml) uses the shared `vscode-engineering` npm-package template. It publishes all -seven native packages before the facade, with independent switches for npm +nine native packages before the facade, with independent switches for npm and the VS Code Azure Artifacts feed. Keep the version in the root and all platform `package.json` files identical; `npm run verify:packages` checks this. @@ -290,17 +290,19 @@ platform `package.json` files identical; `npm run verify:packages` checks this. GitHub Actions builds and tests **every PAC backend on every platform it supports**: native + Wasmtime + wasm2c on Windows x64 + arm64, macOS x64 + -arm64, and Linux x86_64 + aarch64; native + wasm2c on Linux armv7 (which -Cranelift can't AOT-compile for — wasm2c is what makes the sandbox reachable -there; `cross` supplies the C toolchain and the containerized `wasm2c`). The +arm64, and GNU/musl Linux x86_64 + aarch64; native + wasm2c on Linux armv7 +(which Cranelift can't AOT-compile for — wasm2c is what makes the sandbox +reachable there; `cross` supplies the C toolchain and the containerized +`wasm2c`). The shipped `proxytester` artifact uses the Wasmtime backend everywhere except armv7, which uses wasm2c. A variants job builds the single-backend configurations (WinHTTP fallback on Windows, native-only, Wasmtime-only, wasm2c-only), and another job asserts that building with no backend off -Windows is a compile error. Benchmark jobs run on **Windows, macOS, Linux, and -Linux armv7 (qemu)**: `pac_bench` times every embedded engine available on that -OS (native + Wasmtime AOT + wasm2c + Wasmtime JIT on the desktop OSes; native + -wasm2c on armv7), cross-checks them, and reports **single-backend binary sizes** +Windows is a compile error. Benchmark jobs run on **Windows, macOS, GNU/musl +Linux x64/arm64, and Linux armv7 (qemu)**: `pac_bench` times every embedded +engine available on that target (native + Wasmtime AOT + wasm2c + Wasmtime JIT +on 64-bit targets; native + wasm2c on armv7), cross-checks them, and reports +**single-backend binary sizes** (one release build per backend with only that backend compiled in — the realistic deployment shape). The backend-less Windows variant times WinHTTP, and [`bench/electron`](bench/electron) times Chromium's own V8 PAC resolver diff --git a/about.toml b/about.toml index f512eaf..a848216 100644 --- a/about.toml +++ b/about.toml @@ -31,6 +31,8 @@ targets = [ "aarch64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "aarch64-unknown-linux-musl", "armv7-unknown-linux-gnueabihf", ] diff --git a/azure-pipelines/publish.yml b/azure-pipelines/publish.yml index 8c45400..99e3de7 100644 --- a/azure-pipelines/publish.yml +++ b/azure-pipelines/publish.yml @@ -135,6 +135,46 @@ extends: parameters: target: aarch64-unknown-linux-gnu + - name: os-proxy-resolver-linux-x64-musl + packagePlatform: Linux + rustTargets: x86_64-unknown-linux-musl + workingDirectory: npm/platforms/linux-x64-musl + publishPackage: ${{ parameters.publishPackage }} + publishPackageToAzureArtifacts: ${{ parameters.publishPackageToAzureArtifacts }} + skipAPIScan: true + ghCreateTag: false + ghCreateRelease: false + testPlatforms: {} + buildSteps: + - template: /azure-pipelines/common/steps/setup-linux-toolchains.yml@templates + parameters: + arch: x64 + nodeVersion: 22.x + - template: azure-pipelines/steps/build-linux-musl-addon.yml@self + parameters: + target: x86_64-unknown-linux-musl + + - name: os-proxy-resolver-linux-arm64-musl + packagePlatform: Linux + rustTargets: aarch64-unknown-linux-musl + workingDirectory: npm/platforms/linux-arm64-musl + publishPackage: ${{ parameters.publishPackage }} + publishPackageToAzureArtifacts: ${{ parameters.publishPackageToAzureArtifacts }} + skipAPIScan: true + ghCreateTag: false + ghCreateRelease: false + testPlatforms: {} + buildSteps: + - template: /azure-pipelines/common/steps/setup-linux-toolchains.yml@templates + parameters: + arch: arm64 + nodeVersion: 22.x + # The x64 package agent cannot load an arm64 musl Node addon; GitHub + # CI cross-builds and runs the Rust tests under qemu. + - template: azure-pipelines/steps/build-linux-musl-addon.yml@self + parameters: + target: aarch64-unknown-linux-musl + - name: os-proxy-resolver-linux-arm-gnueabihf packagePlatform: Linux rustTargets: armv7-unknown-linux-gnueabihf diff --git a/azure-pipelines/steps/build-linux-musl-addon.yml b/azure-pipelines/steps/build-linux-musl-addon.yml new file mode 100644 index 0000000..ebc09f4 --- /dev/null +++ b/azure-pipelines/steps/build-linux-musl-addon.yml @@ -0,0 +1,19 @@ +parameters: + - name: target + type: string + +steps: + - template: authenticate-cargo.yml + + - bash: | + set -e + cargo install cross --version 0.2.5 --locked + mkdir -p "$(Agent.TempDirectory)/wabt" pac-wasm-guest/generated + curl -sSL "https://github.com/WebAssembly/wabt/releases/download/1.0.41/wabt-1.0.41-linux-x64.tar.gz" \ + | tar xz --strip-components=1 -C "$(Agent.TempDirectory)/wabt" + "$(Agent.TempDirectory)/wabt/bin/wasm2c" pac-wasm-guest/pac_guest.wasm --module-name pac_guest \ + -o pac-wasm-guest/generated/pac_guest.c + CARGO_BUILD_COMMAND=cross \ + OS_PROXY_RESOLVER_PAC_GUEST_C_DIR=pac-wasm-guest/generated \ + node npm/scripts/build-native.js ${{ parameters.target }} + displayName: Build Linux musl addon diff --git a/deny.toml b/deny.toml index 80384c2..c5bf8b2 100644 --- a/deny.toml +++ b/deny.toml @@ -16,6 +16,8 @@ targets = [ "aarch64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "aarch64-unknown-linux-musl", "armv7-unknown-linux-gnueabihf", ] all-features = true diff --git a/index.js b/index.js index 235594d..1c12710 100644 --- a/index.js +++ b/index.js @@ -1,16 +1,20 @@ 'use strict'; +const { getPlatformPackage } = require('./platform'); + const packages = { 'darwin-arm64': '@vscode/os-proxy-resolver-darwin-arm64', 'darwin-x64': '@vscode/os-proxy-resolver-darwin-x64', - 'linux-arm': '@vscode/os-proxy-resolver-linux-arm-gnueabihf', - 'linux-arm64': '@vscode/os-proxy-resolver-linux-arm64-gnu', - 'linux-x64': '@vscode/os-proxy-resolver-linux-x64-gnu', + 'linux-arm-glibc': '@vscode/os-proxy-resolver-linux-arm-gnueabihf', + 'linux-arm64-glibc': '@vscode/os-proxy-resolver-linux-arm64-gnu', + 'linux-arm64-musl': '@vscode/os-proxy-resolver-linux-arm64-musl', + 'linux-x64-glibc': '@vscode/os-proxy-resolver-linux-x64-gnu', + 'linux-x64-musl': '@vscode/os-proxy-resolver-linux-x64-musl', 'win32-arm64': '@vscode/os-proxy-resolver-win32-arm64-msvc', 'win32-x64': '@vscode/os-proxy-resolver-win32-x64-msvc', }; -const platform = `${process.platform}-${process.arch}`; +const platform = getPlatformPackage(); const packageName = packages[platform]; if (!packageName) { diff --git a/npm/platforms/darwin-arm64/package.json b/npm/platforms/darwin-arm64/package.json index 5f6d003..db45536 100644 --- a/npm/platforms/darwin-arm64/package.json +++ b/npm/platforms/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@vscode/os-proxy-resolver-darwin-arm64", - "version": "0.2.0", + "version": "0.3.0", "description": "Native binding for @vscode/os-proxy-resolver on macOS arm64", "main": "os_proxy_resolver.node", "files": ["os_proxy_resolver.node", "LICENSE.txt", "ThirdPartyNotices.txt"], diff --git a/npm/platforms/darwin-x64/package.json b/npm/platforms/darwin-x64/package.json index a7445ec..6ed04da 100644 --- a/npm/platforms/darwin-x64/package.json +++ b/npm/platforms/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@vscode/os-proxy-resolver-darwin-x64", - "version": "0.2.0", + "version": "0.3.0", "description": "Native binding for @vscode/os-proxy-resolver on macOS x64", "main": "os_proxy_resolver.node", "files": ["os_proxy_resolver.node", "LICENSE.txt", "ThirdPartyNotices.txt"], diff --git a/npm/platforms/linux-arm-gnueabihf/package.json b/npm/platforms/linux-arm-gnueabihf/package.json index fdc0b7c..3b2d53b 100644 --- a/npm/platforms/linux-arm-gnueabihf/package.json +++ b/npm/platforms/linux-arm-gnueabihf/package.json @@ -1,6 +1,6 @@ { "name": "@vscode/os-proxy-resolver-linux-arm-gnueabihf", - "version": "0.2.0", + "version": "0.3.0", "description": "Native binding for @vscode/os-proxy-resolver on Linux armhf", "main": "os_proxy_resolver.node", "files": ["os_proxy_resolver.node", "LICENSE.txt", "ThirdPartyNotices.txt"], diff --git a/npm/platforms/linux-arm64-gnu/package.json b/npm/platforms/linux-arm64-gnu/package.json index bfbbda1..cb521ef 100644 --- a/npm/platforms/linux-arm64-gnu/package.json +++ b/npm/platforms/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@vscode/os-proxy-resolver-linux-arm64-gnu", - "version": "0.2.0", + "version": "0.3.0", "description": "Native binding for @vscode/os-proxy-resolver on Linux arm64", "main": "os_proxy_resolver.node", "files": ["os_proxy_resolver.node", "LICENSE.txt", "ThirdPartyNotices.txt"], diff --git a/npm/platforms/linux-arm64-musl/package.json b/npm/platforms/linux-arm64-musl/package.json new file mode 100644 index 0000000..a46aee6 --- /dev/null +++ b/npm/platforms/linux-arm64-musl/package.json @@ -0,0 +1,13 @@ +{ + "name": "@vscode/os-proxy-resolver-linux-arm64-musl", + "version": "0.3.0", + "description": "Native binding for @vscode/os-proxy-resolver on Linux arm64 (musl)", + "main": "os_proxy_resolver.node", + "files": ["os_proxy_resolver.node", "LICENSE.txt", "ThirdPartyNotices.txt"], + "os": ["linux"], + "cpu": ["arm64"], + "libc": ["musl"], + "publishConfig": { "access": "public" }, + "license": "MIT", + "repository": "github:microsoft/os-proxy-resolver" +} diff --git a/npm/platforms/linux-x64-gnu/package.json b/npm/platforms/linux-x64-gnu/package.json index 641f671..edc46f7 100644 --- a/npm/platforms/linux-x64-gnu/package.json +++ b/npm/platforms/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@vscode/os-proxy-resolver-linux-x64-gnu", - "version": "0.2.0", + "version": "0.3.0", "description": "Native binding for @vscode/os-proxy-resolver on Linux x64", "main": "os_proxy_resolver.node", "files": ["os_proxy_resolver.node", "LICENSE.txt", "ThirdPartyNotices.txt"], diff --git a/npm/platforms/linux-x64-musl/package.json b/npm/platforms/linux-x64-musl/package.json new file mode 100644 index 0000000..49c2b7c --- /dev/null +++ b/npm/platforms/linux-x64-musl/package.json @@ -0,0 +1,13 @@ +{ + "name": "@vscode/os-proxy-resolver-linux-x64-musl", + "version": "0.3.0", + "description": "Native binding for @vscode/os-proxy-resolver on Linux x64 (musl)", + "main": "os_proxy_resolver.node", + "files": ["os_proxy_resolver.node", "LICENSE.txt", "ThirdPartyNotices.txt"], + "os": ["linux"], + "cpu": ["x64"], + "libc": ["musl"], + "publishConfig": { "access": "public" }, + "license": "MIT", + "repository": "github:microsoft/os-proxy-resolver" +} diff --git a/npm/platforms/win32-arm64-msvc/package.json b/npm/platforms/win32-arm64-msvc/package.json index 062923b..56377d3 100644 --- a/npm/platforms/win32-arm64-msvc/package.json +++ b/npm/platforms/win32-arm64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@vscode/os-proxy-resolver-win32-arm64-msvc", - "version": "0.2.0", + "version": "0.3.0", "description": "Native binding for @vscode/os-proxy-resolver on Windows arm64", "main": "os_proxy_resolver.node", "files": ["os_proxy_resolver.node", "LICENSE.txt", "ThirdPartyNotices.txt"], diff --git a/npm/platforms/win32-x64-msvc/package.json b/npm/platforms/win32-x64-msvc/package.json index 52e9c2e..2a4aee1 100644 --- a/npm/platforms/win32-x64-msvc/package.json +++ b/npm/platforms/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@vscode/os-proxy-resolver-win32-x64-msvc", - "version": "0.2.0", + "version": "0.3.0", "description": "Native binding for @vscode/os-proxy-resolver on Windows x64", "main": "os_proxy_resolver.node", "files": ["os_proxy_resolver.node", "LICENSE.txt", "ThirdPartyNotices.txt"], diff --git a/npm/scripts/build-native.js b/npm/scripts/build-native.js index 7348c55..9225e31 100644 --- a/npm/scripts/build-native.js +++ b/npm/scripts/build-native.js @@ -10,7 +10,9 @@ const targets = { 'x86_64-apple-darwin': ['darwin-x64', 'libos_proxy_resolver_node.dylib'], 'armv7-unknown-linux-gnueabihf': ['linux-arm-gnueabihf', 'libos_proxy_resolver_node.so'], 'aarch64-unknown-linux-gnu': ['linux-arm64-gnu', 'libos_proxy_resolver_node.so'], + 'aarch64-unknown-linux-musl': ['linux-arm64-musl', 'libos_proxy_resolver_node.so'], 'x86_64-unknown-linux-gnu': ['linux-x64-gnu', 'libos_proxy_resolver_node.so'], + 'x86_64-unknown-linux-musl': ['linux-x64-musl', 'libos_proxy_resolver_node.so'], 'aarch64-pc-windows-msvc': ['win32-arm64-msvc', 'os_proxy_resolver_node.dll'], 'x86_64-pc-windows-msvc': ['win32-x64-msvc', 'os_proxy_resolver_node.dll'], }; @@ -22,14 +24,28 @@ if (!targets[target]) { } const cargo = process.env.CARGO_BUILD_COMMAND || 'cargo'; +const useCross = cargo === 'cross'; +const cwd = useCross ? path.join(root, 'npm', 'native') : root; +// Cargo appends the target triple below --target-dir. Prefixing the target dir +// with the triple isolates host build scripts created by incompatible cross +// images, so cross artifacts intentionally contain the triple twice. +const targetDirectory = useCross ? path.join('target', target) : path.join('target', 'npm'); +const env = { ...process.env }; +if (useCross) { + env.CROSS_CONFIG ??= path.join(root, 'Cross.toml'); +} +if (target.endsWith('-musl')) { + const rustflags = `CARGO_TARGET_${target.toUpperCase().replaceAll('-', '_')}_RUSTFLAGS`; + env[rustflags] = [env[rustflags], '-C target-feature=-crt-static'].filter(Boolean).join(' '); +} const result = spawnSync(cargo, [ 'build', - '--manifest-path', path.join(root, 'npm', 'native', 'Cargo.toml'), - '--target-dir', path.join('target', 'npm'), + '--manifest-path', useCross ? 'Cargo.toml' : path.join('npm', 'native', 'Cargo.toml'), + '--target-dir', targetDirectory, '--locked', '--target', target, '--release', -], { cwd: root, env: process.env, stdio: 'inherit', shell: process.platform === 'win32' }); +], { cwd, env, stdio: 'inherit', shell: process.platform === 'win32' }); if (result.error) { throw result.error; @@ -39,7 +55,9 @@ if (result.status !== 0) { } const [packageDirectory, library] = targets[target]; -const source = path.join(root, 'target', 'npm', target, 'release', library); +const source = useCross + ? path.join(root, 'npm', 'native', targetDirectory, target, 'release', library) + : path.join(root, 'target', 'npm', target, 'release', library); const packagePath = path.join(root, 'npm', 'platforms', packageDirectory); const destination = path.join(packagePath, 'os_proxy_resolver.node'); fs.rmSync(destination, { force: true }); diff --git a/npm/test/platform-selection.js b/npm/test/platform-selection.js new file mode 100644 index 0000000..709eda9 --- /dev/null +++ b/npm/test/platform-selection.js @@ -0,0 +1,22 @@ +'use strict'; + +const assert = require('assert'); +const { getPlatformPackage } = require('../../platform'); + +const glibc = { report: { getReport: () => ({ header: { glibcVersionRuntime: '2.28' } }) } }; +const musl = { report: { getReport: () => ({ header: {} }) }, readFile: () => 'musl libc' }; +const disabledReportMusl = { + report: { getReport: () => { throw new Error('reports disabled'); } }, + versions: { musl: '1.2.5' }, +}; + +assert.strictEqual(getPlatformPackage('linux', 'x64', glibc), 'linux-x64-glibc'); +assert.strictEqual(getPlatformPackage('linux', 'x64', musl), 'linux-x64-musl'); +assert.strictEqual(getPlatformPackage('linux', 'arm64', glibc), 'linux-arm64-glibc'); +assert.strictEqual(getPlatformPackage('linux', 'arm64', musl), 'linux-arm64-musl'); +assert.strictEqual(getPlatformPackage('linux', 'arm', glibc), 'linux-arm-glibc'); +assert.strictEqual(getPlatformPackage('linux', 'x64', disabledReportMusl), 'linux-x64-musl'); +assert.strictEqual(getPlatformPackage('darwin', 'arm64', musl), 'darwin-arm64'); +assert.strictEqual(getPlatformPackage('win32', 'x64', glibc), 'win32-x64'); + +console.log('Verified platform package selection'); diff --git a/npm/test/smoke.js b/npm/test/smoke.js index a9f95d8..a8bea7f 100644 --- a/npm/test/smoke.js +++ b/npm/test/smoke.js @@ -2,8 +2,12 @@ const assert = require('assert'); const path = require('path'); +const { getPlatformPackage } = require('../../platform'); -const platformPackage = `${process.platform}-${process.arch === 'arm' ? 'arm-gnueabihf' : process.arch}${process.platform === 'linux' && process.arch !== 'arm' ? '-gnu' : process.platform === 'win32' ? '-msvc' : ''}`; +const platformPackage = getPlatformPackage() + .replace('linux-arm-glibc', 'linux-arm-gnueabihf') + .replace(/-glibc$/, '-gnu') + .concat(process.platform === 'win32' ? '-msvc' : ''); const binding = require(path.resolve(__dirname, '..', 'platforms', platformPackage, 'os_proxy_resolver.node')); async function main() { diff --git a/package.json b/package.json index 095212c..3d42308 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "@vscode/os-proxy-resolver", - "version": "0.2.0", + "version": "0.3.0", "description": "Resolve the operating system proxy configuration from Node.js", "main": "index.js", "types": "index.d.ts", "files": [ "index.js", "index.d.ts", + "platform.js", "LICENSE.txt", "ThirdPartyNotices.txt" ], @@ -24,16 +25,19 @@ "scripts": { "build:native": "node npm/scripts/build-native.js", "test:npm": "node npm/test/smoke.js", + "test:platform-selection": "node npm/test/platform-selection.js", "verify:glibc": "node npm/scripts/verify-glibc.js", "verify:packages": "node npm/scripts/verify-packages.js" }, "optionalDependencies": { - "@vscode/os-proxy-resolver-darwin-arm64": "0.2.0", - "@vscode/os-proxy-resolver-darwin-x64": "0.2.0", - "@vscode/os-proxy-resolver-linux-arm-gnueabihf": "0.2.0", - "@vscode/os-proxy-resolver-linux-arm64-gnu": "0.2.0", - "@vscode/os-proxy-resolver-linux-x64-gnu": "0.2.0", - "@vscode/os-proxy-resolver-win32-arm64-msvc": "0.2.0", - "@vscode/os-proxy-resolver-win32-x64-msvc": "0.2.0" + "@vscode/os-proxy-resolver-darwin-arm64": "0.3.0", + "@vscode/os-proxy-resolver-darwin-x64": "0.3.0", + "@vscode/os-proxy-resolver-linux-arm-gnueabihf": "0.3.0", + "@vscode/os-proxy-resolver-linux-arm64-gnu": "0.3.0", + "@vscode/os-proxy-resolver-linux-arm64-musl": "0.3.0", + "@vscode/os-proxy-resolver-linux-x64-gnu": "0.3.0", + "@vscode/os-proxy-resolver-linux-x64-musl": "0.3.0", + "@vscode/os-proxy-resolver-win32-arm64-msvc": "0.3.0", + "@vscode/os-proxy-resolver-win32-x64-msvc": "0.3.0" } } \ No newline at end of file diff --git a/platform.js b/platform.js new file mode 100644 index 0000000..db548e6 --- /dev/null +++ b/platform.js @@ -0,0 +1,34 @@ +'use strict'; + +const fs = require('fs'); + +function getLinuxLibc(report = process.report, versions = process.versions, readFile = fs.readFileSync) { + try { + if (report?.getReport?.().header?.glibcVersionRuntime) { + return 'glibc'; + } + } catch { + // Reports can be disabled by the Node host. + } + if (versions?.musl) { + return 'musl'; + } + if (versions?.glibc) { + return 'glibc'; + } + try { + return readFile('/usr/bin/ldd', 'utf8').includes('musl') ? 'musl' : 'glibc'; + } catch { + return 'glibc'; + } +} + +function getPlatformPackage(platform = process.platform, arch = process.arch, options = {}) { + if (platform !== 'linux') { + return `${platform}-${arch}`; + } + const libc = getLinuxLibc(options.report, options.versions, options.readFile); + return `${platform}-${arch}-${libc}`; +} + +exports.getPlatformPackage = getPlatformPackage; diff --git a/tests/async_resolution.rs b/tests/async_resolution.rs index 53819f4..224ffb4 100644 --- a/tests/async_resolution.rs +++ b/tests/async_resolution.rs @@ -20,15 +20,22 @@ const CHILD_ENV: &str = "OS_PROXY_RESOLVER_ASYNC_WORKER_CHILD"; /// report a bounded test failure instead of hanging the whole suite. #[test] fn async_resolution_worker_processes_second_distinct_request() { - let mut child = Command::new(std::env::current_exe().unwrap()) + let child = Command::new(std::env::current_exe().unwrap()) .args([ "--exact", "async_resolution_distinct_request_child", "--nocapture", ]) .env(CHILD_ENV, "1") - .spawn() - .unwrap(); + .spawn(); + let mut child = match child { + Ok(child) => child, + Err(error) if error.raw_os_error() == Some(8) => { + eprintln!("skipping nested subprocess unsupported by this target runner"); + return; + } + Err(error) => panic!("failed to spawn async resolver child: {error}"), + }; let deadline = Instant::now() + Duration::from_secs(5); loop {