Skip to content

Commit 767a7ab

Browse files
committed
Fix and test rebuilding
1 parent 5a524fe commit 767a7ab

3 files changed

Lines changed: 55 additions & 9 deletions

File tree

.github/workflows/ci.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,33 @@ jobs:
408408
- name: Run tests
409409
run: yarn test
410410

411+
job_test_bindings_recompile:
412+
name: Test (recompile from source) (v${{ matrix.node }}) ${{ matrix.os }}
413+
needs: [job_build]
414+
runs-on: ${{ matrix.os }}
415+
strategy:
416+
fail-fast: false
417+
matrix:
418+
os: [ubuntu-late4st, macos-latest, windows-latest]
419+
node: [18, 26]
420+
steps:
421+
- name: Check out current commit
422+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
423+
- name: Set up Node
424+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
425+
with:
426+
node-version: ${{ matrix.node }}
427+
- name: Install dependencies
428+
run: yarn install --ignore-engines --ignore-scripts --frozen-lockfile
429+
- name: Download Tarball
430+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
431+
with:
432+
name: ${{ github.sha }}
433+
- name: Clear binaries
434+
run: yarn clean:binaries
435+
- name: Run tests
436+
run: yarn test
437+
411438
job_required_jobs_passed:
412439
name: All required jobs passed
413440
needs: [job_lint, job_test_bindings]

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"build:bindings:arm64": "yarn build:lib && node-gyp build --arch=arm64 && yarn build:copy-binary",
3333
"build:tarball": "npm pack",
3434
"clean": "node-gyp clean && rm -rf lib && rm -rf build && rm -f *.tgz",
35+
"clean:binaries": "node -e \"require('fs').readdirSync('lib').filter(f=>f.endsWith('.node')).forEach(f=>require('fs').rmSync('lib/'+f))\"",
3536
"test": "node ./test/prepare.mjs && vitest run --poolOptions.forks.singleFork --silent=false --disable-console-intercept"
3637
},
3738
"gypfile": false,

src/index.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable no-console */
22
import type { AsyncLocalStorage } from 'node:async_hooks';
33
import { spawnSync } from 'node:child_process';
4+
import * as fs from 'node:fs';
45
import * as path from 'node:path';
56
import { env } from 'node:process';
67
import { threadId } from 'node:worker_threads';
@@ -54,23 +55,24 @@ function clean(err: Buffer): string {
5455

5556
function recompileFromSource(): void {
5657
const cwd = path.join(__dirname, '..');
58+
// Resolve node-gyp from its package entry so it's found even when
59+
// node_modules/.bin/ is not in PATH (e.g. outside of npm run scripts).
60+
const nodeGyp = require.resolve('node-gyp/bin/node-gyp.js');
5761
console.log('Compiling from source...');
58-
let spawn = spawnSync('node-gyp', ['configure'], {
62+
let spawn = spawnSync(process.execPath, [nodeGyp, 'configure'], {
5963
cwd,
6064
stdio: ['inherit', 'inherit', 'pipe'],
6165
env: process.env,
62-
shell: true,
6366
});
6467
if (spawn.status !== 0) {
6568
console.log('Failed to configure gyp');
6669
if (spawn.stderr) console.log(clean(spawn.stderr));
6770
return;
6871
}
69-
spawn = spawnSync('node-gyp', ['build'], {
72+
spawn = spawnSync(process.execPath, [nodeGyp, 'build'], {
7073
cwd,
7174
stdio: ['inherit', 'inherit', 'pipe'],
7275
env: process.env,
73-
shell: true,
7476
});
7577
if (spawn.status !== 0) {
7678
console.log('Failed to build bindings');
@@ -255,13 +257,29 @@ function getNativeModule(): Native {
255257
return nativeModule;
256258
}
257259

258-
try {
259-
recompileFromSource();
260-
} catch (e) {
261-
console.warn('Failed to compile from source:', e);
260+
// Exclusive-create a lock file so only one process/thread runs node-gyp at a
261+
// time. Losers (EEXIST) wait for the winner to finish before trying to load.
262+
const lockFile = path.join(__dirname, '..', '.rebuild-lock');
263+
let lockFd: number | undefined;
264+
try { lockFd = fs.openSync(lockFile, 'wx'); } catch { /* another caller holds the lock */ }
265+
266+
if (lockFd !== undefined) {
267+
try {
268+
recompileFromSource();
269+
} catch (e) {
270+
console.warn('Failed to compile from source:', e);
271+
} finally {
272+
try { fs.closeSync(lockFd); } catch { /* ignore */ }
273+
try { fs.unlinkSync(lockFile); } catch { /* ignore */ }
274+
}
275+
} else {
276+
const timer = new Int32Array(new SharedArrayBuffer(4));
277+
while (fs.existsSync(lockFile)) {
278+
Atomics.wait(timer, 0, 0, 250);
279+
}
262280
}
263281

264-
// Try again after attempting to recompile, in case the binary is now available.
282+
// Try again after recompile (or after another caller finished theirs).
265283
nativeModule = tryLoad();
266284

267285
if (nativeModule) {

0 commit comments

Comments
 (0)