@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.
@@ -0,0 +1,160 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Emit Windows/MSVC-ready include dirs or import-lib paths for binding.gyp's
3
+ // `OS=="win"` branch, derived from pkg-config/pkgconf (gvsbuild ships pkgconf.exe
4
+ // + a pkg-config.exe copy in bin/, put on PATH by the CI job).
5
+ //
6
+ // node scripts/win-gi-gyp-flags.mjs --includes -> one bare include dir per line
7
+ // node scripts/win-gi-gyp-flags.mjs --libs -> one import-lib path per line
8
+ //
9
+ // gyp's msvs generator ignores `cflags`/`cflags_cc`/`xcode_settings` (those feed
10
+ // the make/xcode generators only), so the Windows branch must feed BARE include
11
+ // dirs into `include_dirs` and FULL .lib paths into `libraries` — the exact shapes
12
+ // gyp hands straight to cl.exe / link.exe. Emitting resolved paths here avoids the
13
+ // `-I`/`-l`/`-L`→MSVC translation ambiguity of raw pkg-config output going through
14
+ // gyp.
15
+ //
16
+ // --libs uses `--static` so the PRIVATE closure is pulled: girepository-2.0.pc has
17
+ // `Requires.private: gmodule-no-export-2.0, gio-2.0, libffi` and node-gi calls
18
+ // ffi_call / Gio directly. On Linux the dynamic linker resolves those lazily from
19
+ // the shared-object global scope, but MSVC/PE resolves every import at link time,
20
+ // so the private libs must be named explicitly.
21
+ //
22
+ // `--define-prefix` makes pkgconf recompute each .pc's `prefix` from the .pc file
23
+ // location, so the bundle works regardless of where gvsbuild's tree is extracted
24
+ // (its .pc files bake an absolute build-machine prefix otherwise).
25
+ import { execFileSync } from 'node:child_process';
26
+ import { existsSync, readdirSync } from 'node:fs';
27
+ import { join } from 'node:path';
28
+
29
+ const PKGS = ['girepository-2.0', 'cairo'];
30
+ const mode = process.argv[2];
31
+ const pkgConfig = process.env.PKG_CONFIG || 'pkg-config';
32
+
33
+ function pkgConfigOut(extraArgs) {
34
+ // Exit-tolerant: gvsbuild's pkgconf can exit non-zero (e.g. `--static` walks a
35
+ // Requires.private graph and trips over a missing .pc) yet still print the
36
+ // resolvable flags. execFileSync THROWS on a non-zero exit — so capture the
37
+ // stdout it attached to the error instead of letting the whole helper crash
38
+ // (that empty-stdout crash left include_dirs blank → C1083 on ffi.h).
39
+ try {
40
+ return execFileSync(pkgConfig, ['--define-prefix', ...extraArgs, ...PKGS], {
41
+ encoding: 'utf8',
42
+ }).trim();
43
+ } catch (error) {
44
+ return typeof error?.stdout === 'string' ? error.stdout.trim() : '';
45
+ }
46
+ }
47
+
48
+ function tokens(s) {
49
+ // pkg-config separates with spaces; paths from gvsbuild carry no spaces.
50
+ return s.split(/\s+/).filter(Boolean);
51
+ }
52
+
53
+ function stripQuotes(s) {
54
+ return s.replace(/^"(.*)"$/, '$1');
55
+ }
56
+
57
+ // gyp/MSBuild + cl.exe accept forward slashes and treat them uniformly; emit them
58
+ // so an include dir / .lib path never carries a backslash that a downstream
59
+ // generator might mis-handle (the pkg-config dirs already use forward slashes).
60
+ function toGyp(p) {
61
+ return p.replace(/\\/g, '/');
62
+ }
63
+
64
+ // Shallow BFS for a header under `root` (bounded depth so we never walk the whole
65
+ // ~300 MB GTK tree). Returns the CONTAINING directory, or null.
66
+ function findHeaderDir(root, filename, maxDepth = 4) {
67
+ const queue = [[root, 0]];
68
+ while (queue.length) {
69
+ const [dir, depth] = queue.shift();
70
+ let entries;
71
+ try {
72
+ entries = readdirSync(dir, { withFileTypes: true });
73
+ } catch {
74
+ continue;
75
+ }
76
+ for (const e of entries) {
77
+ if (e.isFile() && e.name === filename) return dir;
78
+ }
79
+ if (depth < maxDepth) {
80
+ for (const e of entries) {
81
+ if (e.isDirectory()) queue.push([join(dir, e.name), depth + 1]);
82
+ }
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+
88
+ if (mode === '--includes') {
89
+ // NON-static --cflags-only-I: on gvsbuild `--static` exits non-zero (a private
90
+ // .pc misses) and once threw the whole helper, blanking include_dirs. The plain
91
+ // form resolves glib-2.0/lib-glib-2.0-include/cairo cleanly. It does NOT emit
92
+ // libffi's include dir (libffi is Requires.private), but the addon needs <ffi.h>
93
+ // via girffi.h — the GTK_PREFIX fallback below supplies it.
94
+ const dirs = tokens(pkgConfigOut(['--cflags-only-I']))
95
+ .filter((t) => t.startsWith('-I'))
96
+ .map((t) => stripQuotes(t.slice(2)));
97
+ const prefix = process.env.GTK_PREFIX;
98
+ if (prefix) {
99
+ // Guaranteed base from the known gvsbuild layout (in case pkg-config yields
100
+ // nothing) PLUS the bare include root, where gvsbuild installs libffi's
101
+ // ffi.h + ffitarget.h (MSVC does not auto-search a prefix include root).
102
+ dirs.push(
103
+ join(prefix, 'include'),
104
+ join(prefix, 'include', 'glib-2.0'),
105
+ join(prefix, 'lib', 'glib-2.0', 'include'),
106
+ join(prefix, 'include', 'cairo'),
107
+ );
108
+ // Last resort: locate ffi.h anywhere under the tree and add its dir.
109
+ if (!dirs.some((d) => existsSync(join(d, 'ffi.h')))) {
110
+ const ffiDir =
111
+ findHeaderDir(join(prefix, 'include'), 'ffi.h') ?? findHeaderDir(join(prefix, 'lib'), 'ffi.h');
112
+ if (ffiDir) dirs.push(ffiDir);
113
+ }
114
+ }
115
+ process.stdout.write([...new Set(dirs.filter((d) => existsSync(d)).map(toGyp))].join('\n'));
116
+ } else if (mode === '--libs') {
117
+ // A SHARED addon links each GTK/GLib DLL through its DIRECT import lib and lets
118
+ // that DLL resolve its own private deps at load — so link only the import libs
119
+ // whose symbols the addon's own object files reference, NOT pkg-config's
120
+ // `--static` transitive closure (which over-pulls cairo's/glib's private static
121
+ // deps — libpng/freetype/harfbuzz/pcre2/`-lz`→zlib.lib — whose `-l`→`.lib` names
122
+ // don't all map on gvsbuild, breaking the link). This DIRECT set is known + stable:
123
+ // girepository-2.0 the gi_* engine
124
+ // glib/gobject/gio/gmodule-2.0 g_*/GObject/GValue/GSignal/GClosure/Gio
125
+ // ffi ffi_call / ffi_closure_alloc (girffi + the vfunc/callback trampolines)
126
+ // cairo the native cairo foreign-struct binding (src/cairo.cc)
127
+ // Pango/GdkPixbuf/Graphene are NOT linked — the conformance programs load their
128
+ // typelibs at RUNTIME (girepository dlopens the DLL by soname), not at link time.
129
+ const DIRECT_LIBS = ['girepository-2.0', 'gio-2.0', 'gobject-2.0', 'gmodule-2.0', 'glib-2.0', 'ffi', 'cairo'];
130
+ // Locate the import-lib dir: GTK_PREFIX/lib, else pkg-config's -L.
131
+ const searchDirs = [];
132
+ if (process.env.GTK_PREFIX) searchDirs.push(join(process.env.GTK_PREFIX, 'lib'));
133
+ for (const t of tokens(pkgConfigOut(['--libs-only-L']))) {
134
+ if (t.startsWith('-L')) searchDirs.push(stripQuotes(t.slice(2)));
135
+ }
136
+ const resolved = [];
137
+ for (const name of new Set(DIRECT_LIBS)) {
138
+ let found = null;
139
+ for (const dir of new Set(searchDirs)) {
140
+ for (const cand of [`${name}.lib`, `lib${name}.lib`]) {
141
+ const p = join(dir, cand);
142
+ if (existsSync(p)) {
143
+ found = p;
144
+ break;
145
+ }
146
+ }
147
+ if (found) break;
148
+ }
149
+ if (found) resolved.push(found);
150
+ else if (name !== 'gmodule-2.0') {
151
+ // gmodule is optional (girepository may pull it internally); everything
152
+ // else is required — surface a missing import lib loudly.
153
+ throw new Error(`win-gi-gyp-flags: import lib for '${name}' not found under ${searchDirs.join(', ')}`);
154
+ }
155
+ }
156
+ process.stdout.write([...new Set(resolved.map(toGyp))].join('\n'));
157
+ } else {
158
+ console.error('usage: node scripts/win-gi-gyp-flags.mjs <--includes|--libs>');
159
+ process.exit(2);
160
+ }
package/system.d.ts CHANGED
@@ -30,19 +30,19 @@ export function dumpMemoryInfo(): void;
30
30
 
31
31
  /** The GJS `System` module object (`import System from 'system'`). */
32
32
  export interface SystemModule {
33
- exit(code?: number): void;
34
- gc(): void;
35
- version: number;
36
- readonly programArgs: string[];
37
- readonly programInvocationName: string;
38
- readonly programPath: string | null;
39
- addressOf(): string;
40
- addressOfGObject(): string;
41
- refcount(): number;
42
- breakpoint(): void;
43
- clearDateCaches(): void;
44
- dumpHeap(): void;
45
- dumpMemoryInfo(): void;
33
+ exit(code?: number): void;
34
+ gc(): void;
35
+ version: number;
36
+ readonly programArgs: string[];
37
+ readonly programInvocationName: string;
38
+ readonly programPath: string | null;
39
+ addressOf(): string;
40
+ addressOfGObject(): string;
41
+ refcount(): number;
42
+ breakpoint(): void;
43
+ clearDateCaches(): void;
44
+ dumpHeap(): void;
45
+ dumpMemoryInfo(): void;
46
46
  }
47
47
 
48
48
  declare const System: SystemModule;
package/system.js CHANGED
@@ -35,14 +35,14 @@
35
35
  // module is loaded from `@gjsify/node-gi/globals`, before any bundle body (and
36
36
  // therefore before any register side effect) runs.
37
37
  const runtimeProcess = (() => {
38
- const hostProcess = typeof process !== 'undefined' ? process : undefined;
39
- try {
40
- const builtin = hostProcess?.getBuiltinModule?.('node:process');
41
- if (builtin && typeof builtin.exit === 'function') return builtin;
42
- } catch {
43
- // getBuiltinModule missing or refusing the specifier — use the captured one.
44
- }
45
- return hostProcess;
38
+ const hostProcess = typeof process !== 'undefined' ? process : undefined;
39
+ try {
40
+ const builtin = hostProcess?.getBuiltinModule?.('node:process');
41
+ if (builtin && typeof builtin.exit === 'function') return builtin;
42
+ } catch {
43
+ // getBuiltinModule missing or refusing the specifier — use the captured one.
44
+ }
45
+ return hostProcess;
46
46
  })();
47
47
 
48
48
  const runtimeExit = typeof runtimeProcess?.exit === 'function' ? runtimeProcess.exit.bind(runtimeProcess) : undefined;
@@ -50,17 +50,17 @@ const runtimeExit = typeof runtimeProcess?.exit === 'function' ? runtimeProcess.
50
50
  // `programArgs` mirrors GJS's ARGV: the script arguments, excluding the
51
51
  // interpreter (argv[0]) and the script path (argv[1]).
52
52
  function readProgramArgs() {
53
- return Array.isArray(runtimeProcess?.argv) ? runtimeProcess.argv.slice(2) : [];
53
+ return Array.isArray(runtimeProcess?.argv) ? runtimeProcess.argv.slice(2) : [];
54
54
  }
55
55
 
56
56
  // `programInvocationName` / `programPath` track the running script — Node's
57
57
  // `process.argv[1]`.
58
58
  function readProgramInvocationName() {
59
- return runtimeProcess?.argv?.[1] || '';
59
+ return runtimeProcess?.argv?.[1] || '';
60
60
  }
61
61
 
62
62
  function readProgramPath() {
63
- return runtimeProcess?.argv?.[1] || null;
63
+ return runtimeProcess?.argv?.[1] || null;
64
64
  }
65
65
 
66
66
  /**
@@ -74,12 +74,12 @@ function readProgramPath() {
74
74
  * `conformance/programs/system-exit-lifetime.conf.mjs`).
75
75
  */
76
76
  export function exit(code) {
77
- if (runtimeExit !== undefined) runtimeExit(code ?? 0);
77
+ if (runtimeExit !== undefined) runtimeExit(code ?? 0);
78
78
  }
79
79
 
80
80
  /** Trigger a garbage collection if the host exposed `globalThis.gc` (`--expose-gc`). */
81
81
  export function gc() {
82
- if (typeof globalThis.gc === 'function') globalThis.gc();
82
+ if (typeof globalThis.gc === 'function') globalThis.gc();
83
83
  }
84
84
 
85
85
  /** The SpiderMonkey/mozjs version number — no equivalent on Node, reported as 0. */
@@ -96,17 +96,17 @@ export const programPath = readProgramPath();
96
96
 
97
97
  /** Return the address of a JS object as a string. No Node equivalent — stub. */
98
98
  export function addressOf() {
99
- return '0x0';
99
+ return '0x0';
100
100
  }
101
101
 
102
102
  /** Return the address of a GObject as a string. No Node equivalent — stub. */
103
103
  export function addressOfGObject() {
104
- return '0x0';
104
+ return '0x0';
105
105
  }
106
106
 
107
107
  /** Return the refcount of a GObject. No Node equivalent — stub. */
108
108
  export function refcount() {
109
- return 0;
109
+ return 0;
110
110
  }
111
111
 
112
112
  /** Trigger a debugger breakpoint. No-op on Node. */
@@ -128,25 +128,25 @@ export function dumpMemoryInfo() {}
128
128
  * track the running script.
129
129
  */
130
130
  const System = {
131
- exit,
132
- gc,
133
- version,
134
- addressOf,
135
- addressOfGObject,
136
- refcount,
137
- breakpoint,
138
- clearDateCaches,
139
- dumpHeap,
140
- dumpMemoryInfo,
141
- get programArgs() {
142
- return readProgramArgs();
143
- },
144
- get programInvocationName() {
145
- return readProgramInvocationName();
146
- },
147
- get programPath() {
148
- return readProgramPath();
149
- },
131
+ exit,
132
+ gc,
133
+ version,
134
+ addressOf,
135
+ addressOfGObject,
136
+ refcount,
137
+ breakpoint,
138
+ clearDateCaches,
139
+ dumpHeap,
140
+ dumpMemoryInfo,
141
+ get programArgs() {
142
+ return readProgramArgs();
143
+ },
144
+ get programInvocationName() {
145
+ return readProgramInvocationName();
146
+ },
147
+ get programPath() {
148
+ return readProgramPath();
149
+ },
150
150
  };
151
151
 
152
152
  export default System;