@gjsify/node-gi 0.26.0 → 0.27.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/README.md +57 -12
- package/cairo.d.ts +121 -121
- package/cairo.js +425 -426
- package/gettext.d.ts +24 -24
- package/gettext.js +42 -42
- package/gi.d.ts +170 -122
- package/gi.js +1449 -1460
- package/globals.d.ts +28 -28
- package/globals.js +103 -97
- package/gtk-runtime.js +9 -5
- package/index.d.ts +133 -152
- package/index.js +82 -100
- package/native-paths.js +104 -0
- package/overrides/_signals.js +108 -111
- package/overrides/byte-array.js +135 -135
- package/overrides/gio-dbus.js +681 -658
- package/overrides/mainloop.js +27 -27
- package/overrides/package.js +128 -128
- package/package.json +6 -2
- package/prebuilds/darwin-arm64/node_gi.node +0 -0
- package/prebuilds/darwin-x64/node_gi.node +0 -0
- package/prebuilds/win32-x64/node_gi.node +0 -0
- package/scripts/install.mjs +238 -0
- package/scripts/win-gi-gyp-flags.mjs +160 -0
- package/system.d.ts +13 -13
- package/system.js +35 -35
package/overrides/mainloop.js
CHANGED
|
@@ -18,43 +18,43 @@ const DEFAULT_IDLE_PRIORITY = 200; // GLib.PRIORITY_DEFAULT_IDLE
|
|
|
18
18
|
* @returns {{ run(name?: string): void, quit(name?: string): void, idle_add: Function, timeout_add: Function, timeout_add_seconds: Function, source_remove: Function }}
|
|
19
19
|
*/
|
|
20
20
|
export function createMainloop(GLib) {
|
|
21
|
-
|
|
21
|
+
const _mainLoops = Object.create(null);
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
function run(name = '') {
|
|
24
|
+
if (!_mainLoops[name]) _mainLoops[name] = GLib.MainLoop.new(null, false);
|
|
25
|
+
_mainLoops[name].run();
|
|
26
|
+
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
function quit(name = '') {
|
|
29
|
+
if (!_mainLoops[name]) throw new Error('No main loop with this id');
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const loop = _mainLoops[name];
|
|
32
|
+
_mainLoops[name] = null;
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
if (!loop.is_running()) throw new Error('Main loop was stopped already');
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
loop.quit();
|
|
37
|
+
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
// GJS signature is `idle_add(handler, priority)` — handler first — whereas the
|
|
40
|
+
// introspected GLib.idle_add is `(priority, handler)`; bridge the argument order.
|
|
41
|
+
function idle_add(handler, priority = DEFAULT_IDLE_PRIORITY) {
|
|
42
|
+
return GLib.idle_add(priority, handler);
|
|
43
|
+
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
function timeout_add(timeout, handler, priority = GLib.PRIORITY_DEFAULT) {
|
|
46
|
+
return GLib.timeout_add(priority, timeout, handler);
|
|
47
|
+
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
function timeout_add_seconds(timeout, handler, priority = GLib.PRIORITY_DEFAULT) {
|
|
50
|
+
return GLib.timeout_add_seconds(priority, timeout, handler);
|
|
51
|
+
}
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
function source_remove(id) {
|
|
54
|
+
return GLib.source_remove(id);
|
|
55
|
+
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
return { run, quit, idle_add, timeout_add, timeout_add_seconds, source_remove };
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export default createMainloop;
|
package/overrides/package.js
CHANGED
|
@@ -19,111 +19,111 @@
|
|
|
19
19
|
* @param {{ requireGi: Function, gettext: any, imports: Record<string, any> }} deps
|
|
20
20
|
*/
|
|
21
21
|
export function createPackage({ requireGi, gettext, imports }) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
const pkg = {
|
|
23
|
+
// Public fields GJS source may read. Populated by init().
|
|
24
|
+
name: undefined,
|
|
25
|
+
version: undefined,
|
|
26
|
+
prefix: undefined,
|
|
27
|
+
libdir: undefined,
|
|
28
|
+
datadir: undefined,
|
|
29
|
+
pkgdatadir: undefined,
|
|
30
|
+
pkglibdir: undefined,
|
|
31
|
+
moduledir: undefined,
|
|
32
|
+
localedir: undefined,
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
/**
|
|
35
|
+
* @param {{ name?: string, version?: string, prefix?: string, libdir?: string, datadir?: string }} params
|
|
36
|
+
*/
|
|
37
|
+
init(params = {}) {
|
|
38
|
+
// The accessor GJS apps use right after: `pkg.initGettext()`.
|
|
39
|
+
globalThis.pkg = pkg;
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
pkg.name = params.name;
|
|
42
|
+
pkg.version = params.version;
|
|
43
|
+
pkg.prefix = params.prefix;
|
|
44
|
+
pkg.libdir = params.libdir;
|
|
45
|
+
pkg.datadir = params.datadir ?? (params.prefix ? `${params.prefix}/share` : undefined);
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
// Installed-layout dirs — NOT load-bearing on node-gi (apps carry their own
|
|
48
|
+
// constants + register their own resources), computed for source-compat only.
|
|
49
|
+
if (pkg.name) {
|
|
50
|
+
if (pkg.datadir) {
|
|
51
|
+
pkg.pkgdatadir = `${pkg.datadir}/${pkg.name}`;
|
|
52
|
+
pkg.moduledir = pkg.pkgdatadir;
|
|
53
|
+
pkg.localedir = `${pkg.datadir}/locale`;
|
|
54
|
+
}
|
|
55
|
+
if (pkg.libdir) pkg.pkglibdir = `${pkg.libdir}/${pkg.name}`;
|
|
56
|
+
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
// Cheap + safe: keeps ARGV[0]/app-id semantics (window-manager grouping etc.).
|
|
59
|
+
try {
|
|
60
|
+
const GLib = requireGi('GLib', '2.0');
|
|
61
|
+
if (pkg.name && typeof GLib.set_prgname === 'function') GLib.set_prgname(pkg.name);
|
|
62
|
+
} catch {
|
|
63
|
+
/* GLib not loadable in this context — skip */
|
|
64
|
+
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
// GJS unshifts moduledir onto imports.searchPath (script-import machinery).
|
|
67
|
+
// node-gi apps use ESM + gi://, so the search path is inert here — but keep
|
|
68
|
+
// the array present + the unshift safe so source that reads it doesn't throw.
|
|
69
|
+
if (!Array.isArray(imports.searchPath)) imports.searchPath = [];
|
|
70
|
+
if (pkg.moduledir) imports.searchPath.unshift(pkg.moduledir);
|
|
71
|
+
},
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
initGettext() {
|
|
74
|
+
if (pkg.name) {
|
|
75
|
+
try {
|
|
76
|
+
gettext.bindtextdomain?.(pkg.name, pkg.localedir ?? null);
|
|
77
|
+
gettext.textdomain?.(pkg.name);
|
|
78
|
+
} catch {
|
|
79
|
+
/* passthrough gettext — no catalog, ignore */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// The globals GJS apps + compiled Blueprint use pervasively.
|
|
83
|
+
globalThis._ = (msgid) => gettext.gettext(msgid);
|
|
84
|
+
globalThis.C_ = (context, msgid) => gettext.pgettext(context, msgid);
|
|
85
|
+
globalThis.N_ = (msgid) => msgid;
|
|
86
|
+
},
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
88
|
+
initFormat() {
|
|
89
|
+
// GJS sets `String.prototype.format = imports.format.format` (backed by the
|
|
90
|
+
// native `_format` vprintf). node-gi has no `imports.format`, so supply an
|
|
91
|
+
// equivalent JS vprintf here (see makeVprintf).
|
|
92
|
+
const vprintf = makeVprintf();
|
|
93
|
+
Object.defineProperty(String.prototype, 'format', {
|
|
94
|
+
configurable: true,
|
|
95
|
+
writable: true,
|
|
96
|
+
enumerable: false,
|
|
97
|
+
value: function format(...args) {
|
|
98
|
+
return vprintf(this, args);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
99
101
|
},
|
|
100
|
-
});
|
|
101
|
-
},
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
103
|
+
// Script-import machinery is not ported — node-gi apps use ESM + gi://. These
|
|
104
|
+
// keep the surface present for source that references them (Learn6502 does not).
|
|
105
|
+
require() {
|
|
106
|
+
/* no-op: apps import via ESM / gi:// on node-gi */
|
|
107
|
+
},
|
|
108
|
+
requireSymbol() {
|
|
109
|
+
return true;
|
|
110
|
+
},
|
|
111
|
+
checkSymbol() {
|
|
112
|
+
return true;
|
|
113
|
+
},
|
|
114
|
+
start(params = {}) {
|
|
115
|
+
pkg.init(params);
|
|
116
|
+
if (typeof params.main === 'function') params.main(globalThis.ARGV ? ['', ...globalThis.ARGV] : []);
|
|
117
|
+
},
|
|
118
|
+
run() {
|
|
119
|
+
/* the app drives its own main() on node-gi */
|
|
120
|
+
},
|
|
121
|
+
initSubmodule() {
|
|
122
|
+
/* n/a on node-gi */
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
return pkg;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// A small vprintf covering the GJS `String.prototype.format` spec
|
|
@@ -131,37 +131,37 @@ export function createPackage({ requireGi, gettext, imports }) {
|
|
|
131
131
|
// width, `0`-padding for numerics, and `%%` → literal `%`. Positional `%N$s` is
|
|
132
132
|
// supported (GJS honours it). Learn6502 only uses `%s`; the rest is fidelity.
|
|
133
133
|
function makeVprintf() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
134
|
+
return function vprintf(fmt, args) {
|
|
135
|
+
let auto = 0;
|
|
136
|
+
return String(fmt).replace(
|
|
137
|
+
/%(?:(\d+)\$)?(0)?(\d+)?(?:\.(\d+))?([%sdxf])/g,
|
|
138
|
+
(match, pos, zero, width, prec, spec) => {
|
|
139
|
+
if (spec === '%') return '%';
|
|
140
|
+
const arg = pos !== undefined ? args[Number(pos) - 1] : args[auto++];
|
|
141
|
+
let str;
|
|
142
|
+
switch (spec) {
|
|
143
|
+
case 's':
|
|
144
|
+
str = String(arg);
|
|
145
|
+
break;
|
|
146
|
+
case 'd':
|
|
147
|
+
str = String(Math.trunc(Number(arg)));
|
|
148
|
+
break;
|
|
149
|
+
case 'x':
|
|
150
|
+
str = Math.trunc(Number(arg)).toString(16);
|
|
151
|
+
break;
|
|
152
|
+
case 'f':
|
|
153
|
+
str = prec !== undefined ? Number(arg).toFixed(Number(prec)) : String(Number(arg));
|
|
154
|
+
break;
|
|
155
|
+
default:
|
|
156
|
+
return match;
|
|
157
|
+
}
|
|
158
|
+
if (width !== undefined) {
|
|
159
|
+
const w = Number(width);
|
|
160
|
+
const pad = zero && spec !== 's' ? '0' : ' ';
|
|
161
|
+
while (str.length < w) str = pad + str;
|
|
162
|
+
}
|
|
163
|
+
return str;
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
167
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.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",
|
|
@@ -52,9 +52,12 @@
|
|
|
52
52
|
"gettext.d.ts",
|
|
53
53
|
"cairo.js",
|
|
54
54
|
"cairo.d.ts",
|
|
55
|
+
"native-paths.js",
|
|
55
56
|
"overrides",
|
|
56
57
|
"src",
|
|
57
58
|
"binding.gyp",
|
|
59
|
+
"scripts/install.mjs",
|
|
60
|
+
"scripts/win-gi-gyp-flags.mjs",
|
|
58
61
|
"prebuilds"
|
|
59
62
|
],
|
|
60
63
|
"gjsify": {
|
|
@@ -62,6 +65,7 @@
|
|
|
62
65
|
"linux-x64",
|
|
63
66
|
"linux-arm64",
|
|
64
67
|
"darwin-arm64",
|
|
68
|
+
"darwin-x64",
|
|
65
69
|
"win32-x64"
|
|
66
70
|
],
|
|
67
71
|
"runtimes": {
|
|
@@ -73,7 +77,7 @@
|
|
|
73
77
|
"tier": 2
|
|
74
78
|
},
|
|
75
79
|
"scripts": {
|
|
76
|
-
"install": "node
|
|
80
|
+
"install": "node scripts/install.mjs",
|
|
77
81
|
"build": "node-gyp rebuild",
|
|
78
82
|
"build:native": "node-gyp rebuild",
|
|
79
83
|
"build:prebuild": "node-gyp rebuild && node scripts/stage-prebuild.mjs",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// @gjsify/node-gi — the `install` lifecycle script. Use the SHIPPED PREBUILD when
|
|
4
|
+
// this package carries one for the host, and run the node-gyp source build only
|
|
5
|
+
// when it does not. The source build is the FALLBACK, not the default.
|
|
6
|
+
//
|
|
7
|
+
// THE FAILURE THIS REPLACES. Until this script existed, `package.json` declared
|
|
8
|
+
// `"install": "node-gyp rebuild"`, which npm/yarn/pnpm run on EVERY install — a
|
|
9
|
+
// shipped prebuild could not prevent it. Measured on a `windows-latest` runner
|
|
10
|
+
// against `@gjsify/node-gi@0.26.1`, whose tarball DOES contain
|
|
11
|
+
// `prebuilds/win32-x64/node_gi.node`:
|
|
12
|
+
//
|
|
13
|
+
// win-gi-gyp-flags: import lib for 'girepository-2.0' not found
|
|
14
|
+
// gyp ERR! configure error
|
|
15
|
+
// → npm rolls the install back; the package is not installed at all
|
|
16
|
+
//
|
|
17
|
+
// So on any machine without the GTK/GObject-Introspection development toolchain
|
|
18
|
+
// — i.e. every end user — `npm install @gjsify/node-gi` FAILED, prebuild or not,
|
|
19
|
+
// and it failed by uninstalling the package rather than by degrading. The
|
|
20
|
+
// prebuild was reachable only through `gjsify install`, which deliberately runs
|
|
21
|
+
// no lifecycle scripts; that asymmetry is the whole reason
|
|
22
|
+
// `gjsify showcase --runtime node` worked on Windows while a plain npm
|
|
23
|
+
// dependency on the same version could not be installed.
|
|
24
|
+
//
|
|
25
|
+
// WHY EXISTENCE, NOT LOADABILITY, IS THE GATE. The obvious-looking alternative
|
|
26
|
+
// (`node-gyp-build`) gates on its `node-gyp-build-test` bin, which `require()`s
|
|
27
|
+
// the addon and falls back to a source build if the load throws. On exactly the
|
|
28
|
+
// platforms whose whole point is the prebuild that is a FALSE NEGATIVE: the
|
|
29
|
+
// win32/darwin addon's DLL/dylib closure is only findable after
|
|
30
|
+
// `gtk-runtime.js` has wired the batteries-included GTK bundle into `PATH` /
|
|
31
|
+
// `DYLD_FALLBACK_LIBRARY_PATH`, which `index.js` does at import time and a bare
|
|
32
|
+
// install-time `require()` of the `.node` never does. A load-based gate would
|
|
33
|
+
// therefore call a perfectly good prebuild broken and rebuild — reproducing the
|
|
34
|
+
// exact failure above. Existence of the file the loader actually probes is the
|
|
35
|
+
// honest question at install time; whether it LOADS is what the release
|
|
36
|
+
// workflow's per-target load test answers (release.yml runs
|
|
37
|
+
// `NODE_GI_NATIVE=prebuild node --test test/smoke.test.mjs` on every leg), where
|
|
38
|
+
// there is a real GTK to load against.
|
|
39
|
+
//
|
|
40
|
+
// WHY NOT DEPEND ON `node-gyp-build` (verified, not assumed):
|
|
41
|
+
// • its skip gate is that false-negative load test (above) — the disqualifier;
|
|
42
|
+
// • its probe order is `build/` FIRST, then the prebuild, the OPPOSITE of
|
|
43
|
+
// node-gi's deliberate consumer default (see native-paths.js
|
|
44
|
+
// `nativeCandidates`), so the two would disagree about which binary matters;
|
|
45
|
+
// • it cannot produce ONE actionable diagnosis — on failure it propagates a
|
|
46
|
+
// bare node-gyp exit code, which is the status quo this change removes;
|
|
47
|
+
// • it would add a runtime `dependencies` entry to a Tier-2 package whose dep
|
|
48
|
+
// surface is otherwise just `node-addon-api`, to make a decision this
|
|
49
|
+
// package can make from its own already-shared probe.
|
|
50
|
+
// (It would, contrary to first assumption, MATCH the `node_gi.node` filename:
|
|
51
|
+
// node-gyp-build 4.8.4's `matchTags` treats a missing runtime tag as
|
|
52
|
+
// permissive — checked against the installed copy. The filename was not the
|
|
53
|
+
// blocker; the gate and the message are.)
|
|
54
|
+
//
|
|
55
|
+
// THE SOURCE BUILD MUST KEEP WORKING, and does. `prebuilds/` is gitignored
|
|
56
|
+
// (packages/node-gi/node-gi/.gitignore), so a fresh checkout has none and this
|
|
57
|
+
// script builds exactly as before — which is what the ~12 `npm install
|
|
58
|
+
// --foreground-scripts` steps in node-gi.yml and the three
|
|
59
|
+
// `node-gi-prebuild-*` legs in release.yml rely on to produce
|
|
60
|
+
// `build/Release/node_gi.node` for `scripts/stage-prebuild.mjs`. Depending on a
|
|
61
|
+
// directory being ABSENT is too implicit to be the only guarantee, so the build
|
|
62
|
+
// is also forceable: `NODE_GI_BUILD_FROM_SOURCE=1` (or npm's own
|
|
63
|
+
// `--build-from-source`, which sets `npm_config_build_from_source=true`) skips
|
|
64
|
+
// the prebuild check entirely. `npm run build:native` / `build:prebuild` /
|
|
65
|
+
// `rebuild` still invoke `node-gyp` directly and are untouched by this script.
|
|
66
|
+
//
|
|
67
|
+
// FAIL-SAFE DIRECTION: every uncertain branch builds. Only a positively
|
|
68
|
+
// confirmed prebuild file for this exact host skips the build, so a bug here
|
|
69
|
+
// degrades to the previous behaviour rather than to a package with no addon.
|
|
70
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
71
|
+
import { spawnSync } from 'node:child_process';
|
|
72
|
+
import { createRequire } from 'node:module';
|
|
73
|
+
import { join } from 'node:path';
|
|
74
|
+
import { hostTarget, packageRoot, prebuildAddonPath } from '../native-paths.js';
|
|
75
|
+
|
|
76
|
+
const target = hostTarget();
|
|
77
|
+
|
|
78
|
+
/** `1`/`true` on an env var, tolerant of the shapes people actually type. */
|
|
79
|
+
const isEnabled = (value) => value === '1' || value === 'true';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Did the user explicitly ask for a source build? `NODE_GI_BUILD_FROM_SOURCE` is
|
|
83
|
+
* ours; `npm_config_build_from_source` is what npm exports for
|
|
84
|
+
* `--build-from-source` (the flag every node-gyp-build package honours), so a CI
|
|
85
|
+
* job or a maintainer can force the build without knowing node-gi's own name for
|
|
86
|
+
* it.
|
|
87
|
+
*/
|
|
88
|
+
const buildFromSourceRequested = () =>
|
|
89
|
+
isEnabled(process.env.NODE_GI_BUILD_FROM_SOURCE) || process.env.npm_config_build_from_source === 'true';
|
|
90
|
+
|
|
91
|
+
/** The `<os>-<arch>` targets this package PROMISES a prebuild for, from its own manifest. */
|
|
92
|
+
function declaredPlatforms() {
|
|
93
|
+
try {
|
|
94
|
+
const manifest = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
95
|
+
const platforms = manifest?.gjsify?.platforms;
|
|
96
|
+
return Array.isArray(platforms) ? platforms : [];
|
|
97
|
+
} catch {
|
|
98
|
+
// An unreadable own manifest is not worth failing an install over — the
|
|
99
|
+
// diagnosis below just loses one line of context.
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Run `node-gyp rebuild` in the package root, inheriting stdio so the real
|
|
106
|
+
* compiler output stays visible (it is the actual diagnosis; the framed message
|
|
107
|
+
* below only tells the reader what to DO about it).
|
|
108
|
+
*
|
|
109
|
+
* node-gyp is resolved the way node-gyp-build's own bin does: prefer node-gyp's
|
|
110
|
+
* JS entry if it happens to be resolvable, else the `node-gyp` shim that npm,
|
|
111
|
+
* yarn and pnpm all put on `PATH` for lifecycle scripts (`node-gyp.cmd` on
|
|
112
|
+
* Windows, hence `shell` there).
|
|
113
|
+
* @returns {{ status: number, error?: Error }}
|
|
114
|
+
*/
|
|
115
|
+
function runNodeGyp() {
|
|
116
|
+
const win32 = process.platform === 'win32';
|
|
117
|
+
let command = win32 ? 'node-gyp.cmd' : 'node-gyp';
|
|
118
|
+
let args = ['rebuild'];
|
|
119
|
+
let shell = win32;
|
|
120
|
+
try {
|
|
121
|
+
// Only succeeds when node-gyp is genuinely resolvable from here; npm's
|
|
122
|
+
// bundled copy lives in npm's own tree and is not, which is why the PATH
|
|
123
|
+
// shim above stays the normal path. `createRequire().resolve` rather than
|
|
124
|
+
// `import.meta.resolve` — the latter is only synchronous from Node 20.6,
|
|
125
|
+
// and `engines.node` is `>=20`.
|
|
126
|
+
const entry = createRequire(import.meta.url).resolve('node-gyp/bin/node-gyp.js');
|
|
127
|
+
command = process.execPath;
|
|
128
|
+
args = [entry, 'rebuild'];
|
|
129
|
+
shell = false;
|
|
130
|
+
} catch {
|
|
131
|
+
// Not resolvable — use the PATH shim.
|
|
132
|
+
}
|
|
133
|
+
const res = spawnSync(command, args, { cwd: packageRoot, stdio: 'inherit', shell, windowsHide: true });
|
|
134
|
+
return { status: res.status === null ? 1 : res.status, error: res.error };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Are we running inside a source CHECKOUT rather than an installed tarball?
|
|
139
|
+
* Keyed on two paths `files` deliberately does not publish (`test/` and the
|
|
140
|
+
* staging tool), because the distinction changes the advice: in a checkout
|
|
141
|
+
* `prebuilds/` is gitignored, so its absence is expected and the source build IS
|
|
142
|
+
* the path; in an installed copy a missing prebuild for a PROMISED target is a
|
|
143
|
+
* packaging regression the consumer should report instead of debugging.
|
|
144
|
+
*/
|
|
145
|
+
function isRepoCheckout() {
|
|
146
|
+
return existsSync(join(packageRoot, 'test')) || existsSync(join(packageRoot, 'scripts', 'stage-prebuild.mjs'));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* ONE actionable message for "no usable prebuild AND the source build did not
|
|
151
|
+
* succeed" — the case that used to surface as nothing but a node-gyp stack. It
|
|
152
|
+
* distinguishes the three very different reasons the prebuild was missing,
|
|
153
|
+
* because the reader's next action differs each time: a checkout is SUPPOSED to
|
|
154
|
+
* build; an undeclared platform means "build it yourself or use a supported
|
|
155
|
+
* target"; and a DECLARED platform with no artifact in an installed copy is a
|
|
156
|
+
* packaging regression that should be reported, not worked around (precisely how
|
|
157
|
+
* 0.26.0 shipped `prebuilds/linux-*` only while promising four targets — and
|
|
158
|
+
* nothing told the consumer).
|
|
159
|
+
*/
|
|
160
|
+
function reportFailure(status, spawnError) {
|
|
161
|
+
const platforms = declaredPlatforms();
|
|
162
|
+
const promised = platforms.includes(target);
|
|
163
|
+
const checkout = isRepoCheckout();
|
|
164
|
+
const bar = '─'.repeat(78);
|
|
165
|
+
const lines = [
|
|
166
|
+
'',
|
|
167
|
+
bar,
|
|
168
|
+
`@gjsify/node-gi: could not obtain the native addon for ${target}.`,
|
|
169
|
+
'',
|
|
170
|
+
checkout
|
|
171
|
+
? ' • This is a source checkout, where `prebuilds/` is gitignored — no staged prebuild\n' +
|
|
172
|
+
' for this host was expected, and the source build IS the path here.'
|
|
173
|
+
: promised
|
|
174
|
+
? ` • This package PROMISES a prebuild for ${target} (package.json#gjsify.platforms) but\n` +
|
|
175
|
+
` the installed copy carries no prebuilds/${target}/node_gi.node. That is a packaging\n` +
|
|
176
|
+
' bug in this release, not a problem with your machine — please report it at\n' +
|
|
177
|
+
' https://github.com/gjsify/gjsify/issues.'
|
|
178
|
+
: ` • No prebuild ships for ${target}.` +
|
|
179
|
+
(platforms.length > 0 ? ` Prebuilds ship for: ${platforms.join(', ')}.` : ''),
|
|
180
|
+
spawnError
|
|
181
|
+
? ` • The node-gyp source build could not even be STARTED: ${spawnError.message}\n` +
|
|
182
|
+
' (node-gyp is normally provided on PATH by npm/yarn/pnpm for install scripts.)'
|
|
183
|
+
: ` • The node-gyp source build above exited ${status}. Its output is the actual diagnosis.`,
|
|
184
|
+
'',
|
|
185
|
+
'Building from source needs a C++ toolchain plus GLib >= 2.80 development headers',
|
|
186
|
+
'exposing girepository-2.0, and cairo:',
|
|
187
|
+
' Fedora dnf install gcc-c++ make python3 pkgconf-pkg-config glib2-devel \\',
|
|
188
|
+
' gobject-introspection-devel cairo-devel',
|
|
189
|
+
' Debian/Ubuntu apt install g++ make python3 pkg-config libglib2.0-dev \\',
|
|
190
|
+
' libgirepository-2.0-dev libcairo2-dev',
|
|
191
|
+
' macOS brew install gobject-introspection cairo pkg-config',
|
|
192
|
+
' Windows gvsbuild GTK4 (MSVC ABI), then PKG_CONFIG_PATH=<prefix>\\lib\\pkgconfig',
|
|
193
|
+
'',
|
|
194
|
+
'Escape hatches:',
|
|
195
|
+
' NODE_GI_SKIP_NATIVE_BUILD=1 install the JS without an addon (every gi:// import',
|
|
196
|
+
' then throws until you point NODE_GI_NATIVE at one)',
|
|
197
|
+
' NODE_GI_NATIVE=/abs/node_gi.node load an addon you built or copied yourself',
|
|
198
|
+
' NODE_GI_BUILD_FROM_SOURCE=1 build from source even when a prebuild exists',
|
|
199
|
+
bar,
|
|
200
|
+
'',
|
|
201
|
+
];
|
|
202
|
+
console.error(lines.join('\n'));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function main() {
|
|
206
|
+
// Deliberately first: an explicit "do not build" wins over everything, so a
|
|
207
|
+
// consumer on an unsupported platform can complete an install and supply an
|
|
208
|
+
// addon via NODE_GI_NATIVE afterwards.
|
|
209
|
+
if (isEnabled(process.env.NODE_GI_SKIP_NATIVE_BUILD)) {
|
|
210
|
+
console.log(
|
|
211
|
+
`@gjsify/node-gi: NODE_GI_SKIP_NATIVE_BUILD is set — skipping the native addon for ${target}. ` +
|
|
212
|
+
'gi:// imports will throw until NODE_GI_NATIVE points at a node_gi.node.',
|
|
213
|
+
);
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const prebuild = prebuildAddonPath();
|
|
218
|
+
if (!buildFromSourceRequested() && existsSync(prebuild)) {
|
|
219
|
+
console.log(
|
|
220
|
+
`@gjsify/node-gi: using the shipped prebuild for ${target} ` +
|
|
221
|
+
`(prebuilds/${target}/node_gi.node) — no node-gyp, no C++ toolchain needed. ` +
|
|
222
|
+
'Set NODE_GI_BUILD_FROM_SOURCE=1 to build from source anyway.',
|
|
223
|
+
);
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const { status, error } = runNodeGyp();
|
|
228
|
+
if (status === 0 && !error) return 0;
|
|
229
|
+
reportFailure(status, error);
|
|
230
|
+
// Fail the install rather than half-install. This package IS its native
|
|
231
|
+
// addon: a zero exit here would trade a clear install-time failure for a
|
|
232
|
+
// deferred `import` failure in the consumer's own code, and npm's rollback is
|
|
233
|
+
// the honest outcome. NODE_GI_SKIP_NATIVE_BUILD is the opt-in for anyone who
|
|
234
|
+
// wants the other trade.
|
|
235
|
+
return status === 0 ? 1 : status;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
process.exit(main());
|