@gjsify/node-gi 0.37.0 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gtk-runtime.d.ts +9 -1
- package/gtk-runtime.js +61 -7
- package/index.d.ts +3 -0
- package/index.js +12 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/node_gi.node +0 -0
- package/prebuilds/darwin-x64/node_gi.node +0 -0
- package/prebuilds/linux-arm64/node_gi.node +0 -0
- package/prebuilds/linux-x64/node_gi.node +0 -0
- package/prebuilds/win32-x64/node_gi.node +0 -0
- package/src/addon.cc +1 -0
- package/src/common.h +1 -0
- package/src/repo.cc +26 -0
- package/system-gi.js +40 -0
package/gtk-runtime.d.ts
CHANGED
|
@@ -14,4 +14,12 @@ export function maybePrependGtkRuntimeDllPath(): void;
|
|
|
14
14
|
/** Windows: wire the env for a full-windowing bundle's runtime data — GSettings schemas, gdk-pixbuf loaders, icon themes, fontconfig (no-op off win32 / for a display-free bundle). */
|
|
15
15
|
export function maybeWireGtkWindowingEnv(): void;
|
|
16
16
|
/** Activate the bundled GTK runtime for the native engine, if one is present. */
|
|
17
|
-
export function activateBundledGtkRuntime(native: {
|
|
17
|
+
export function activateBundledGtkRuntime(native: {
|
|
18
|
+
prependSearchPath: (p: string) => void;
|
|
19
|
+
prependLibraryPath?: (p: string) => void;
|
|
20
|
+
}): GtkRuntimeBundle | null;
|
|
21
|
+
|
|
22
|
+
/** Prepend GI's shared-library search path for the GTK the policy chose. */
|
|
23
|
+
export function activateGiLibraryPath(native: { prependLibraryPath?: (p: string) => void }): string[];
|
|
24
|
+
/** TEST-ONLY: allow the activation to run again. */
|
|
25
|
+
export function resetGiLibraryPathForTests(): void;
|
package/gtk-runtime.js
CHANGED
|
@@ -11,7 +11,7 @@ import { createRequire } from 'node:module';
|
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { dirname, join } from 'node:path';
|
|
13
13
|
import { existsSync } from 'node:fs';
|
|
14
|
-
import { pathCovers, splitSearchPath, systemGiLibraryDirs } from './system-gi.js';
|
|
14
|
+
import { composeDyldFallback, pathCovers, splitSearchPath, systemGiLibraryDirs } from './system-gi.js';
|
|
15
15
|
import { nativeCandidates, prebuildAddonPath } from './native-paths.js';
|
|
16
16
|
|
|
17
17
|
// NB: node:child_process is intentionally NOT imported at module top level. It is
|
|
@@ -309,9 +309,11 @@ export function maybeReexecForGtkRuntime() {
|
|
|
309
309
|
const env = {
|
|
310
310
|
...process.env,
|
|
311
311
|
[REEXEC_SENTINEL]: '1',
|
|
312
|
-
// Our dirs first, then whatever the host asked for
|
|
313
|
-
//
|
|
314
|
-
|
|
312
|
+
// Our dirs first, then whatever the host asked for, then dyld's own
|
|
313
|
+
// default list — setting the variable REPLACES that list, so carrying
|
|
314
|
+
// it is what keeps the re-exec'd child from searching LESS than the
|
|
315
|
+
// process that spawned it.
|
|
316
|
+
DYLD_FALLBACK_LIBRARY_PATH: composeDyldFallback(libDirs),
|
|
315
317
|
};
|
|
316
318
|
// Only a bundle relocates typelibs; a host GI stack's are already on GI's own
|
|
317
319
|
// search path, so the system branch deliberately leaves GI_TYPELIB_PATH alone.
|
|
@@ -457,6 +459,57 @@ function gstScannerLeaf() {
|
|
|
457
459
|
return process.platform === 'win32' ? 'gst-plugin-scanner.exe' : 'gst-plugin-scanner';
|
|
458
460
|
}
|
|
459
461
|
|
|
462
|
+
let libraryPathActivated = false;
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Tell GI where the shared libraries its typelibs name by BARE LEAF actually are
|
|
466
|
+
* — for whichever GTK the policy chose, and WITHOUT an environment variable.
|
|
467
|
+
*
|
|
468
|
+
* THE GAP THIS CLOSES. A typelib records its backer as `libgtk-4.1.dylib`, and on
|
|
469
|
+
* macOS neither a relocated bundle's `lib` dir nor Homebrew's `/usr/local/lib` is
|
|
470
|
+
* on dyld's search path. Node papers over it in {@link maybeReexecForGtkRuntime}
|
|
471
|
+
* by re-execing with `DYLD_FALLBACK_LIBRARY_PATH` set — a Node-shaped re-exec, so
|
|
472
|
+
* it returns early on bun and deno, which got NO loader repair and died at the
|
|
473
|
+
* first widget behind three errors that all point elsewhere (`GtkWidget is not
|
|
474
|
+
* registered…`, `Adw.Application has no property 'application-id'`, `Gtk.GLArea is
|
|
475
|
+
* not a subclassable GObject type`). Measured on the macOS 15.7.9 x86_64 VM.
|
|
476
|
+
*
|
|
477
|
+
* `gi_repository_prepend_library_path()` is GI's own answer, so nothing is
|
|
478
|
+
* captured at process launch: identical on node, bun and deno, no re-exec, and the
|
|
479
|
+
* environment is left alone. The env paths elsewhere in this module stay — they
|
|
480
|
+
* still cover a dylib pulled in by ANOTHER dylib's link closure, which never
|
|
481
|
+
* passes through GI.
|
|
482
|
+
*
|
|
483
|
+
* The dirs come from `gtkSource()`, not a second opinion: the bundle's `libDir`
|
|
484
|
+
* for `bundle`, `systemGiLibraryDirs()` for `system` — the pair the re-exec
|
|
485
|
+
* composes. Mixing them is the two-copies hazard #920 records. Idempotent, and
|
|
486
|
+
* never fatal: an addon predating the binding changes nothing.
|
|
487
|
+
* @param {{ prependLibraryPath?: (p: string) => void }} native the loaded addon
|
|
488
|
+
* @returns {string[]} the directories handed to GI (empty when there was nothing to add)
|
|
489
|
+
*/
|
|
490
|
+
export function activateGiLibraryPath(native) {
|
|
491
|
+
if (libraryPathActivated) return [];
|
|
492
|
+
libraryPathActivated = true;
|
|
493
|
+
if (typeof native?.prependLibraryPath !== 'function') return [];
|
|
494
|
+
|
|
495
|
+
const source = gtkSource();
|
|
496
|
+
const dirs =
|
|
497
|
+
source === 'bundle' ? [resolveGtkRuntimeBundle()?.libDir] : source === 'system' ? systemGiLibraryDirs() : [];
|
|
498
|
+
const applied = [];
|
|
499
|
+
// LAST wins with a prepend, so walking in reverse leaves the array's own
|
|
500
|
+
// order intact in GI's search path.
|
|
501
|
+
for (const dir of dirs.filter(Boolean).reverse()) {
|
|
502
|
+
native.prependLibraryPath(dir);
|
|
503
|
+
applied.unshift(dir);
|
|
504
|
+
}
|
|
505
|
+
return applied;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** TEST-ONLY: allow a spec to run the activation again. */
|
|
509
|
+
export function resetGiLibraryPathForTests() {
|
|
510
|
+
libraryPathActivated = false;
|
|
511
|
+
}
|
|
512
|
+
|
|
460
513
|
let activated = null; // memoize: the activation result (idempotent)
|
|
461
514
|
|
|
462
515
|
/**
|
|
@@ -502,9 +555,10 @@ export function activateBundledGtkRuntime(native) {
|
|
|
502
555
|
} else {
|
|
503
556
|
const existing = process.env.DYLD_FALLBACK_LIBRARY_PATH;
|
|
504
557
|
if (!existing || !existing.split(':').includes(bundle.libDir)) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
558
|
+
// Same composition as the re-exec above: the bundle's libdir first,
|
|
559
|
+
// then the inherited value, then dyld's default list — which this
|
|
560
|
+
// variable REPLACES rather than extends.
|
|
561
|
+
process.env.DYLD_FALLBACK_LIBRARY_PATH = composeDyldFallback([bundle.libDir]);
|
|
508
562
|
}
|
|
509
563
|
}
|
|
510
564
|
|
package/index.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ export function setErrorBuilder(
|
|
|
65
65
|
|
|
66
66
|
/** Prepend a directory to the GIRepository typelib search path. */
|
|
67
67
|
export function prependSearchPath(path: string): void;
|
|
68
|
+
/** Prepend a directory GI searches for the SHARED LIBRARY a typelib names. */
|
|
69
|
+
export function prependLibraryPath(path: string): void;
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
72
|
* Invoke a namespace-level GObject-Introspection function (not an instance
|
|
@@ -519,6 +521,7 @@ declare const native: {
|
|
|
519
521
|
getErrorDomain: typeof getErrorDomain;
|
|
520
522
|
setErrorBuilder: typeof setErrorBuilder;
|
|
521
523
|
prependSearchPath: typeof prependSearchPath;
|
|
524
|
+
prependLibraryPath: typeof prependLibraryPath;
|
|
522
525
|
callFunction: typeof callFunction;
|
|
523
526
|
callMethod: typeof callMethod;
|
|
524
527
|
hasMethod: typeof hasMethod;
|
package/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { hostTarget, nativeCandidates, packageRoot } from './native-paths.js';
|
|
|
11
11
|
import { describeAddonLoadFailure } from './load-diagnostics.js';
|
|
12
12
|
import {
|
|
13
13
|
activateBundledGtkRuntime,
|
|
14
|
+
activateGiLibraryPath,
|
|
14
15
|
maybePrependGtkRuntimeDllPath,
|
|
15
16
|
maybeReexecForGtkRuntime,
|
|
16
17
|
maybeWireGtkWindowingEnv,
|
|
@@ -123,6 +124,16 @@ try {
|
|
|
123
124
|
// Never fatal: a missing/partial bundle just leaves the host GTK in charge.
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
// And where the LIBRARIES those typelibs name actually live. Separate from the
|
|
128
|
+
// call above because it is not about a bundle: it applies to whichever GTK the
|
|
129
|
+
// policy chose, and it is the only loader repair bun and deno get on macOS —
|
|
130
|
+
// the re-exec above is Node-shaped and skips them.
|
|
131
|
+
try {
|
|
132
|
+
activateGiLibraryPath(native);
|
|
133
|
+
} catch {
|
|
134
|
+
// Same contract as above: never fatal.
|
|
135
|
+
}
|
|
136
|
+
|
|
126
137
|
// Cross-runtime microtask checkpoint (Bun/Deno only). Node's napi_make_callback runs
|
|
127
138
|
// the nextTick + microtask checkpoint when its callback scope closes, so promise
|
|
128
139
|
// continuations queued by a loop-dispatched GLib→JS callback drain at the callback
|
|
@@ -182,6 +193,7 @@ export const getEnumValues = native.getEnumValues;
|
|
|
182
193
|
export const getErrorDomain = native.getErrorDomain;
|
|
183
194
|
export const setErrorBuilder = native.setErrorBuilder;
|
|
184
195
|
export const prependSearchPath = native.prependSearchPath;
|
|
196
|
+
export const prependLibraryPath = native.prependLibraryPath;
|
|
185
197
|
export const callFunction = native.callFunction;
|
|
186
198
|
export const callMethod = native.callMethod;
|
|
187
199
|
export const hasMethod = native.hasMethod;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "GObject-Introspection runtime for Node.js — load gi:// namespaces (GLib/GObject/Gio/…) with GJS-compatible semantics. Vendored + rewritten from node-gtk under MIT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/addon.cc
CHANGED
|
@@ -74,6 +74,7 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
74
74
|
exports.Set("getErrorDomain", Napi::Function::New(env, GetErrorDomain));
|
|
75
75
|
exports.Set("setErrorBuilder", Napi::Function::New(env, SetErrorBuilder));
|
|
76
76
|
exports.Set("prependSearchPath", Napi::Function::New(env, PrependSearchPath));
|
|
77
|
+
exports.Set("prependLibraryPath", Napi::Function::New(env, PrependLibraryPath));
|
|
77
78
|
exports.Set("callFunction", Napi::Function::New(env, CallFunction));
|
|
78
79
|
exports.Set("callMethod", Napi::Function::New(env, CallMethod));
|
|
79
80
|
exports.Set("hasMethod", Napi::Function::New(env, HasMethod));
|
package/src/common.h
CHANGED
|
@@ -550,6 +550,7 @@ Napi::Value GetEnumValues(const Napi::CallbackInfo& info);
|
|
|
550
550
|
Napi::Value GetErrorDomain(const Napi::CallbackInfo& info);
|
|
551
551
|
Napi::Value SetErrorBuilder(const Napi::CallbackInfo& info);
|
|
552
552
|
Napi::Value PrependSearchPath(const Napi::CallbackInfo& info);
|
|
553
|
+
Napi::Value PrependLibraryPath(const Napi::CallbackInfo& info);
|
|
553
554
|
|
|
554
555
|
// calls.cc
|
|
555
556
|
Napi::Value CallFunction(const Napi::CallbackInfo& info);
|
package/src/repo.cc
CHANGED
|
@@ -295,4 +295,30 @@ Napi::Value PrependSearchPath(const Napi::CallbackInfo& info) {
|
|
|
295
295
|
return env.Undefined();
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
// prependLibraryPath(path: string) -> void
|
|
299
|
+
// The LIBRARY twin of PrependSearchPath: where GI looks for the shared object a
|
|
300
|
+
// typelib names, as opposed to where it looks for the typelib itself.
|
|
301
|
+
//
|
|
302
|
+
// Native rather than `requireGi('GIRepository').…` because the bundled runtime
|
|
303
|
+
// ships GIRepository 3.0, whose `Repository` this bridge exposes neither as
|
|
304
|
+
// constructible nor with a `get_default()` static — measured, both throw. The C
|
|
305
|
+
// entry point has no such gap and reuses the same `DupDefaultRepository()` handle.
|
|
306
|
+
//
|
|
307
|
+
// It is the env-free repair for a typelib backer named by BARE LEAF: no variable,
|
|
308
|
+
// no re-exec, identical on node, bun and deno. See `gtk-runtime.js`
|
|
309
|
+
// (`activateGiLibraryPath`) for the incident.
|
|
310
|
+
Napi::Value PrependLibraryPath(const Napi::CallbackInfo& info) {
|
|
311
|
+
Napi::Env env = info.Env();
|
|
312
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
313
|
+
Napi::TypeError::New(env, "prependLibraryPath(path: string)")
|
|
314
|
+
.ThrowAsJavaScriptException();
|
|
315
|
+
return env.Undefined();
|
|
316
|
+
}
|
|
317
|
+
std::string path = info[0].As<Napi::String>().Utf8Value();
|
|
318
|
+
GIRepository* repo = DupDefaultRepository();
|
|
319
|
+
gi_repository_prepend_library_path(repo, path.c_str());
|
|
320
|
+
g_object_unref(repo);
|
|
321
|
+
return env.Undefined();
|
|
322
|
+
}
|
|
323
|
+
|
|
298
324
|
} // namespace nodegi
|
package/system-gi.js
CHANGED
|
@@ -227,3 +227,43 @@ export function systemGiLibraryDirs({
|
|
|
227
227
|
export function pathCovers(wanted, current) {
|
|
228
228
|
return wanted.every((dir) => current.includes(dir));
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* dyld's OWN default fallback list, verbatim from `dyld(1)`:
|
|
233
|
+
* `$HOME/lib:/usr/local/lib:/lib:/usr/lib`.
|
|
234
|
+
*
|
|
235
|
+
* Load-bearing because `DYLD_FALLBACK_LIBRARY_PATH` REPLACES this list rather
|
|
236
|
+
* than extending it, so a composition that omits part of it hands the child a
|
|
237
|
+
* SMALLER search path than the same child would have had with the variable
|
|
238
|
+
* unset. MEASURED, and it cost a whole platform: both writers used to append a
|
|
239
|
+
* bare `/usr/lib`, dropping `/usr/local/lib` — where every Homebrew GTK library
|
|
240
|
+
* lives on Intel macOS — so every `gjsify` gjs command on such a Mac died in
|
|
241
|
+
* `dlopen(libsoup-3.0.0.dylib)` before it had downloaded anything (the CLI's own
|
|
242
|
+
* fetcher goes through Soup), and so did the Node-free bootstrap the Getting
|
|
243
|
+
* Started page prints. The tell that it was OURS: plain
|
|
244
|
+
* `gjs -c 'imports.gi.Soup'` loads fine with no DYLD variable set.
|
|
245
|
+
* @param {Record<string, string | undefined>} [env]
|
|
246
|
+
* @returns {string[]}
|
|
247
|
+
*/
|
|
248
|
+
export function dyldDefaultFallbackDirs(env = process.env) {
|
|
249
|
+
const home = env['HOME'];
|
|
250
|
+
return [...(home ? [`${home}/lib`] : []), '/usr/local/lib', '/lib', '/usr/lib'];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Compose a `DYLD_FALLBACK_LIBRARY_PATH` value: `wanted`, then the launching
|
|
255
|
+
* environment's own value, then {@link dyldDefaultFallbackDirs} —
|
|
256
|
+
* unconditionally, because the target is a CHILD (a re-exec, or a spawned gjs)
|
|
257
|
+
* and it must never search less than one launched without gjsify. Deduplicated:
|
|
258
|
+
* `systemGiLibraryDirs()` can legitimately yield a directory the tail also
|
|
259
|
+
* names, and a repeat costs the loader a second stat on every miss. First
|
|
260
|
+
* occurrence wins everywhere, so dropping later repeats preserves behaviour.
|
|
261
|
+
* @param {readonly string[]} wanted
|
|
262
|
+
* @param {Record<string, string | undefined>} [env]
|
|
263
|
+
* @returns {string}
|
|
264
|
+
*/
|
|
265
|
+
export function composeDyldFallback(wanted, env = process.env) {
|
|
266
|
+
return [
|
|
267
|
+
...new Set([...wanted, ...splitSearchPath(env['DYLD_FALLBACK_LIBRARY_PATH']), ...dyldDefaultFallbackDirs(env)]),
|
|
268
|
+
].join(':');
|
|
269
|
+
}
|