@gtkx/codegen 1.1.0 → 1.2.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/dist/analysis/descriptor-render.d.ts.map +1 -1
- package/dist/analysis/descriptor-render.js +14 -10
- package/dist/analysis/descriptor-render.js.map +1 -1
- package/dist/analysis/descriptor.d.ts +11 -5
- package/dist/analysis/descriptor.d.ts.map +1 -1
- package/dist/analysis/descriptor.js +25 -9
- package/dist/analysis/descriptor.js.map +1 -1
- package/dist/analysis/interface-conflicts.d.ts +3 -1
- package/dist/analysis/interface-conflicts.d.ts.map +1 -1
- package/dist/analysis/interface-conflicts.js +3 -8
- package/dist/analysis/interface-conflicts.js.map +1 -1
- package/dist/analysis/ts-type.d.ts +3 -2
- package/dist/analysis/ts-type.d.ts.map +1 -1
- package/dist/analysis/ts-type.js +16 -9
- package/dist/analysis/ts-type.js.map +1 -1
- package/dist/analysis/type-shape.d.ts +3 -2
- package/dist/analysis/type-shape.d.ts.map +1 -1
- package/dist/analysis/type-shape.js +2 -1
- package/dist/analysis/type-shape.js.map +1 -1
- package/dist/compile.d.ts +4 -5
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +56 -4
- package/dist/compile.js.map +1 -1
- package/dist/docs/api-reference.d.ts +2 -0
- package/dist/docs/api-reference.d.ts.map +1 -1
- package/dist/docs/api-reference.js +1 -1
- package/dist/docs/api-reference.js.map +1 -1
- package/dist/docs/pipeline.d.ts +1 -0
- package/dist/docs/pipeline.d.ts.map +1 -1
- package/dist/docs/pipeline.js +10 -3
- package/dist/docs/pipeline.js.map +1 -1
- package/dist/docs/symbol-page.d.ts.map +1 -1
- package/dist/docs/symbol-page.js +1 -3
- package/dist/docs/symbol-page.js.map +1 -1
- package/dist/fingerprint.d.ts +11 -5
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +22 -19
- package/dist/fingerprint.js.map +1 -1
- package/dist/gi.d.ts +1 -0
- package/dist/gi.d.ts.map +1 -1
- package/dist/gi.js +7 -2
- package/dist/gi.js.map +1 -1
- package/dist/gir/gir-path.d.ts +1 -1
- package/dist/gir/gir-path.js +1 -1
- package/dist/gir/gir-path.js.map +1 -1
- package/dist/gir/library.d.ts +5 -1
- package/dist/gir/library.d.ts.map +1 -1
- package/dist/gir/library.js +8 -1
- package/dist/gir/library.js.map +1 -1
- package/dist/gir/parse.d.ts.map +1 -1
- package/dist/gir/parse.js +4 -1
- package/dist/gir/parse.js.map +1 -1
- package/dist/gir/primitives.js +1 -1
- package/dist/gir/primitives.js.map +1 -1
- package/dist/khronos/args.d.ts.map +1 -1
- package/dist/khronos/args.js +8 -6
- package/dist/khronos/args.js.map +1 -1
- package/dist/khronos/render.d.ts.map +1 -1
- package/dist/khronos/render.js +8 -4
- package/dist/khronos/render.js.map +1 -1
- package/dist/runner.d.ts +2 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +5 -3
- package/dist/runner.js.map +1 -1
- package/dist/staging.d.ts.map +1 -1
- package/dist/staging.js +1 -1
- package/dist/staging.js.map +1 -1
- package/dist/store/compile-store.d.ts +0 -1
- package/dist/store/compile-store.d.ts.map +1 -1
- package/dist/store/compile-store.js +7 -34
- package/dist/store/compile-store.js.map +1 -1
- package/dist/store/gi/callables.d.ts +1 -3
- package/dist/store/gi/callables.d.ts.map +1 -1
- package/dist/store/gi/callables.js +19 -4
- package/dist/store/gi/callables.js.map +1 -1
- package/dist/store/gi/class.d.ts.map +1 -1
- package/dist/store/gi/class.js +48 -9
- package/dist/store/gi/class.js.map +1 -1
- package/dist/store/gi/constant.js +1 -1
- package/dist/store/gi/constant.js.map +1 -1
- package/dist/store/gi/enum.d.ts.map +1 -1
- package/dist/store/gi/enum.js +2 -5
- package/dist/store/gi/enum.js.map +1 -1
- package/dist/store/gi/interface.js +2 -2
- package/dist/store/gi/interface.js.map +1 -1
- package/dist/store/gi/method.d.ts.map +1 -1
- package/dist/store/gi/method.js +4 -7
- package/dist/store/gi/method.js.map +1 -1
- package/dist/store/gi/property-accessor.d.ts +0 -3
- package/dist/store/gi/property-accessor.d.ts.map +1 -1
- package/dist/store/gi/property-accessor.js +15 -108
- package/dist/store/gi/property-accessor.js.map +1 -1
- package/dist/store/gi/record-constructor.js +3 -3
- package/dist/store/gi/record-constructor.js.map +1 -1
- package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
- package/dist/store/gi/record-field-accessor.js +15 -3
- package/dist/store/gi/record-field-accessor.js.map +1 -1
- package/dist/store/gi/return-wrap.js +1 -1
- package/dist/store/gi/return-wrap.js.map +1 -1
- package/dist/store/gi/signal.d.ts.map +1 -1
- package/dist/store/gi/signal.js +25 -4
- package/dist/store/gi/signal.js.map +1 -1
- package/dist/store/jsx-store.d.ts.map +1 -1
- package/dist/store/jsx-store.js +0 -1
- package/dist/store/jsx-store.js.map +1 -1
- package/dist/store/store-fs.d.ts +0 -1
- package/dist/store/store-fs.d.ts.map +1 -1
- package/dist/store/store-fs.js +2 -7
- package/dist/store/store-fs.js.map +1 -1
- package/dist/xml.d.ts +1 -0
- package/dist/xml.d.ts.map +1 -1
- package/dist/xml.js +28 -1
- package/dist/xml.js.map +1 -1
- package/overrides/gobject/object.ts.ejs +2 -0
- package/package.json +3 -3
- package/src/analysis/descriptor-render.ts +19 -10
- package/src/analysis/descriptor.ts +43 -9
- package/src/analysis/interface-conflicts.ts +6 -8
- package/src/analysis/ts-type.ts +30 -11
- package/src/analysis/type-shape.ts +5 -2
- package/src/compile.ts +87 -6
- package/src/docs/api-reference.ts +3 -1
- package/src/docs/pipeline.ts +13 -7
- package/src/docs/symbol-page.ts +0 -4
- package/src/fingerprint.ts +31 -38
- package/src/gi.ts +9 -2
- package/src/gir/gir-path.ts +1 -1
- package/src/gir/library.ts +9 -1
- package/src/gir/parse.ts +6 -1
- package/src/gir/primitives.ts +1 -1
- package/src/khronos/args.ts +9 -6
- package/src/khronos/render.ts +8 -6
- package/src/runner.ts +7 -3
- package/src/staging.ts +1 -3
- package/src/store/compile-store.ts +7 -40
- package/src/store/gi/callables.ts +26 -7
- package/src/store/gi/class.ts +63 -7
- package/src/store/gi/constant.ts +1 -1
- package/src/store/gi/enum.ts +2 -6
- package/src/store/gi/interface.ts +2 -2
- package/src/store/gi/method.ts +4 -9
- package/src/store/gi/property-accessor.ts +22 -193
- package/src/store/gi/record-constructor.ts +3 -3
- package/src/store/gi/record-field-accessor.ts +18 -3
- package/src/store/gi/return-wrap.ts +1 -1
- package/src/store/gi/signal.ts +30 -4
- package/src/store/jsx-store.ts +0 -1
- package/src/store/store-fs.ts +3 -9
- package/src/xml.ts +43 -1
package/src/compile.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { errorMessage, normalizeError } from "@gtkx/utils";
|
|
1
|
+
import { errorCode, errorMessage, normalizeError } from "@gtkx/utils";
|
|
2
2
|
import { existsSync, mkdirSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -14,10 +14,19 @@ type SourceModule = ProjectFile & {
|
|
|
14
14
|
source: string;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
type
|
|
17
|
+
type ProjectContext = {
|
|
18
18
|
projectDir: string;
|
|
19
19
|
files: ProjectFile[];
|
|
20
|
+
label: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type CompileProjectParams = ProjectContext & {
|
|
20
24
|
compilerOptions: Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type EmitModulesParams = {
|
|
28
|
+
projectDir: string;
|
|
29
|
+
files: SourceModule[];
|
|
21
30
|
label: string;
|
|
22
31
|
};
|
|
23
32
|
|
|
@@ -60,9 +69,33 @@ const CHECK_OPTIONS = {
|
|
|
60
69
|
noEmit: true,
|
|
61
70
|
};
|
|
62
71
|
|
|
72
|
+
const DECLARATION_EMIT = {
|
|
73
|
+
declaration: true,
|
|
74
|
+
isolatedDeclarations: true,
|
|
75
|
+
removeComments: false,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const MODULE_EMIT = {
|
|
79
|
+
removeComments: false,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const DECLARATION_OPTIONS = ts.convertCompilerOptionsFromJson(
|
|
83
|
+
{ ...BASE_COMPILER_OPTIONS, ...DECLARATION_EMIT },
|
|
84
|
+
".",
|
|
85
|
+
).options;
|
|
86
|
+
|
|
87
|
+
const MODULE_OPTIONS = ts.convertCompilerOptionsFromJson(
|
|
88
|
+
{ ...BASE_COMPILER_OPTIONS, ...MODULE_EMIT },
|
|
89
|
+
".",
|
|
90
|
+
).options;
|
|
91
|
+
|
|
92
|
+
const TS_EXTENSION_PATTERN = /\.tsx?$/;
|
|
93
|
+
const DECLARATION_EXTENSION = ".d.ts";
|
|
94
|
+
const JAVASCRIPT_EXTENSION = ".js";
|
|
63
95
|
const CHECK_DIR = ".gtkx-check";
|
|
64
96
|
const FAILED_CHECK_DIR = `${CHECK_DIR}.failed`;
|
|
65
97
|
const LEGACY_CHECK_PREFIX = `${CHECK_DIR}-`;
|
|
98
|
+
const ESM_SCOPE_MANIFEST = `${JSON.stringify({ type: "module" }, null, 4)}\n`;
|
|
66
99
|
|
|
67
100
|
const FORMAT_HOST: ts.FormatDiagnosticsHost = {
|
|
68
101
|
getCanonicalFileName: (fileName) => fileName,
|
|
@@ -90,6 +123,16 @@ const linkToolingModules = (projectDir: string): (() => void) => {
|
|
|
90
123
|
};
|
|
91
124
|
};
|
|
92
125
|
|
|
126
|
+
const ensureModuleScope = (projectDir: string): void => {
|
|
127
|
+
try {
|
|
128
|
+
writeFileSync(join(projectDir, "package.json"), ESM_SCOPE_MANIFEST, { flag: "wx" });
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (errorCode(error) !== "EEXIST") {
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
93
136
|
const isProjectFile = (rel: string): boolean =>
|
|
94
137
|
!rel.startsWith("..") && !isAbsolute(rel) && !rel.split(/[/\\]/).includes("node_modules");
|
|
95
138
|
|
|
@@ -160,17 +203,17 @@ const originLines = (diagnosed: DiagnosedFile[], files: ProjectFile[]): string[]
|
|
|
160
203
|
([origin, generated]) => `Generated from ${origin}: ${[...generated].join(", ")}`,
|
|
161
204
|
);
|
|
162
205
|
|
|
163
|
-
const diagnosticError = (params:
|
|
206
|
+
const diagnosticError = (params: ProjectContext, diagnostics: ts.Diagnostic[]): Error => {
|
|
164
207
|
const diagnosed = projectDiagnosticFiles(diagnostics, params.projectDir);
|
|
165
208
|
const { label } = params;
|
|
166
209
|
|
|
167
210
|
if (diagnosed.length === 0) {
|
|
168
|
-
return new Error(`
|
|
211
|
+
return new Error(`Compiling ${label} failed:\n${ts.formatDiagnostics(diagnostics, FORMAT_HOST).trim()}`);
|
|
169
212
|
}
|
|
170
213
|
|
|
171
214
|
const lines = [...diagnosed.map((entry) => entry.text), ...originLines(diagnosed, params.files)];
|
|
172
215
|
|
|
173
|
-
return new Error(`
|
|
216
|
+
return new Error(`Compiling ${label} found ${String(diagnosed.length)} error(s):\n${lines.join("\n")}`);
|
|
174
217
|
};
|
|
175
218
|
|
|
176
219
|
const keepFailedProject = (input: FailedProjectInput): Error => {
|
|
@@ -214,7 +257,45 @@ const runProgram = (params: CompileProjectParams): ts.Diagnostic[] => {
|
|
|
214
257
|
return [...program.emit().diagnostics];
|
|
215
258
|
};
|
|
216
259
|
|
|
260
|
+
const replaceExtension = (fileName: string, extension: string): string =>
|
|
261
|
+
`${fileName.replace(TS_EXTENSION_PATTERN, "")}${extension}`;
|
|
262
|
+
|
|
263
|
+
const transpileOptions = (compilerOptions: ts.CompilerOptions, fileName: string): ts.TranspileOptions => ({
|
|
264
|
+
compilerOptions,
|
|
265
|
+
fileName,
|
|
266
|
+
reportDiagnostics: true,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const emitModule = (module: SourceModule, projectDir: string): ts.Diagnostic[] => {
|
|
270
|
+
const fileName = join(projectDir, module.fileName);
|
|
271
|
+
const declaration = ts.transpileDeclaration(module.source, transpileOptions(DECLARATION_OPTIONS, fileName));
|
|
272
|
+
const javascript = ts.transpileModule(module.source, transpileOptions(MODULE_OPTIONS, fileName));
|
|
273
|
+
const diagnostics = [...declaration.diagnostics ?? [], ...javascript.diagnostics ?? []];
|
|
274
|
+
|
|
275
|
+
if (diagnostics.length > 0) {
|
|
276
|
+
return diagnostics;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
writeFileSync(replaceExtension(fileName, DECLARATION_EXTENSION), declaration.outputText);
|
|
280
|
+
writeFileSync(replaceExtension(fileName, JAVASCRIPT_EXTENSION), javascript.outputText);
|
|
281
|
+
|
|
282
|
+
return [];
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const emitModules = (params: EmitModulesParams): void => {
|
|
286
|
+
const diagnostics: ts.Diagnostic[] = [];
|
|
287
|
+
|
|
288
|
+
for (const module of params.files) {
|
|
289
|
+
diagnostics.push(...emitModule(module, params.projectDir));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (diagnostics.length > 0) {
|
|
293
|
+
throw diagnosticError(params, diagnostics);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
217
297
|
const compileProject = (params: CompileProjectParams): void => {
|
|
298
|
+
ensureModuleScope(params.projectDir);
|
|
218
299
|
const unlinkToolingModules = linkToolingModules(params.projectDir);
|
|
219
300
|
|
|
220
301
|
try {
|
|
@@ -261,4 +342,4 @@ const checkModules = (params: { modules: SourceModule[]; resolveFrom: string; la
|
|
|
261
342
|
rmSync(keepAt, { recursive: true, force: true });
|
|
262
343
|
};
|
|
263
344
|
|
|
264
|
-
export {
|
|
345
|
+
export { checkModules, emitModules, keepFailedProject, type SourceModule };
|
|
@@ -24,6 +24,8 @@ type ApiReferenceOptions = {
|
|
|
24
24
|
props?: ElementProps;
|
|
25
25
|
/** GObject properties the project omits from generated props; without it pages show props that do not exist. */
|
|
26
26
|
omittedProps?: OmittedProps;
|
|
27
|
+
/** Describes GIR byte sequences as `Uint8Array` rather than `number[]`; defaults to false. */
|
|
28
|
+
isByteArrayTyped?: boolean;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
/** Narrows an `ApiReference.symbols` enumeration. */
|
|
@@ -371,7 +373,7 @@ class ApiReference {
|
|
|
371
373
|
this.libraries = options.libraries;
|
|
372
374
|
this.props = options.props ?? {};
|
|
373
375
|
this.omittedProps = options.omittedProps ?? {};
|
|
374
|
-
this.library = Library.load(options.libraries, options.girPath);
|
|
376
|
+
this.library = Library.load(options.libraries, options.girPath, options.isByteArrayTyped === true);
|
|
375
377
|
this.elementContext = { library: this.library, linkFor: (): string | undefined => undefined };
|
|
376
378
|
this.buildIndex();
|
|
377
379
|
}
|
package/src/docs/pipeline.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
computeDocsFingerprint,
|
|
6
6
|
type DocsFingerprintInput,
|
|
7
7
|
FINGERPRINT_FILENAME,
|
|
8
|
+
type GiInputs,
|
|
8
9
|
isDocsOutputFresh,
|
|
9
10
|
} from "../fingerprint.js";
|
|
10
11
|
import { Library } from "../gir/library.js";
|
|
@@ -37,6 +38,7 @@ type DocsOptions = {
|
|
|
37
38
|
props?: ElementProps;
|
|
38
39
|
omittedProps?: OmittedProps;
|
|
39
40
|
isForced?: boolean;
|
|
41
|
+
isByteArrayTyped?: boolean;
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
type DocsManifest = {
|
|
@@ -253,6 +255,13 @@ const outDirRefusal = (outDir: string, reason: string): Error =>
|
|
|
253
255
|
"empty directory or at one gtkx generated, or remove it yourself first.",
|
|
254
256
|
);
|
|
255
257
|
|
|
258
|
+
const giInputs = (options: DocsOptions, girFiles: string[]): GiInputs => ({
|
|
259
|
+
girFiles,
|
|
260
|
+
libraries: options.libraries,
|
|
261
|
+
girPath: options.girPath,
|
|
262
|
+
isByteArrayTyped: options.isByteArrayTyped === true,
|
|
263
|
+
});
|
|
264
|
+
|
|
256
265
|
const assertOwnedOutDir = (options: DocsOptions, manifest: DocsManifest | undefined): void => {
|
|
257
266
|
const stats = statSync(options.outDir, { throwIfNoEntry: false });
|
|
258
267
|
|
|
@@ -301,7 +310,7 @@ const cachedDocsResult = (
|
|
|
301
310
|
return undefined;
|
|
302
311
|
}
|
|
303
312
|
|
|
304
|
-
if (!isDocsOutputFresh(options.outDir, options
|
|
313
|
+
if (!isDocsOutputFresh(options.outDir, giInputs(options, []), input)) {
|
|
305
314
|
return undefined;
|
|
306
315
|
}
|
|
307
316
|
|
|
@@ -335,18 +344,15 @@ const writeDocs = (options: DocsOptions): DocsResult => {
|
|
|
335
344
|
}
|
|
336
345
|
|
|
337
346
|
assertOwnedOutDir(options, previous);
|
|
338
|
-
const library = Library.load(options.libraries, options.girPath);
|
|
347
|
+
const library = Library.load(options.libraries, options.girPath, options.isByteArrayTyped === true);
|
|
339
348
|
const { pages, namespaces } = generatePages(options, input.basePath, library);
|
|
340
349
|
clearOutDir(options, previous);
|
|
341
350
|
mkdirSync(options.outDir, { recursive: true });
|
|
342
351
|
const manifest: DocsManifest = { generator: MANIFEST_GENERATOR, namespaces };
|
|
343
352
|
writeFileSync(manifestPath, JSON.stringify(manifest));
|
|
344
353
|
writePages(options.outDir, pages);
|
|
345
|
-
|
|
346
|
-
writeFileSync(
|
|
347
|
-
join(options.outDir, FINGERPRINT_FILENAME),
|
|
348
|
-
JSON.stringify(computeDocsFingerprint(library.girFiles, options.libraries, options.girPath, input)),
|
|
349
|
-
);
|
|
354
|
+
const fingerprint = computeDocsFingerprint(giInputs(options, library.girFiles), input);
|
|
355
|
+
writeFileSync(join(options.outDir, FINGERPRINT_FILENAME), JSON.stringify(fingerprint));
|
|
350
356
|
|
|
351
357
|
return { isRegenerated: true, namespaces };
|
|
352
358
|
};
|
package/src/docs/symbol-page.ts
CHANGED
|
@@ -16,7 +16,6 @@ import { PRIMITIVE_TS_TYPE, primitiveCategory } from "../gir/primitives.js";
|
|
|
16
16
|
import { callableSpec } from "../store/gi/callable-doc.js";
|
|
17
17
|
import {
|
|
18
18
|
dedupeCallables,
|
|
19
|
-
indexMethodsByName,
|
|
20
19
|
instanceScope,
|
|
21
20
|
matchStaticFinishFunction,
|
|
22
21
|
renderInstanceMethodSignature,
|
|
@@ -146,7 +145,6 @@ type MemberOwner = {
|
|
|
146
145
|
type PropertyAccessorSetup = {
|
|
147
146
|
context: ModuleContext;
|
|
148
147
|
claimedNames: Set<string>;
|
|
149
|
-
methodByName: Map<string, GirFunction>;
|
|
150
148
|
};
|
|
151
149
|
|
|
152
150
|
type ResolvedRecordField = NonNullable<ReturnType<typeof resolveRecordFieldEntry>>;
|
|
@@ -391,7 +389,6 @@ const propertyAccessorSetup = (
|
|
|
391
389
|
return {
|
|
392
390
|
context: docsSignatureContext(owner.namespace, library),
|
|
393
391
|
claimedNames,
|
|
394
|
-
methodByName: indexMethodsByName(dedupeCallables(owner.klass.methods)),
|
|
395
392
|
};
|
|
396
393
|
};
|
|
397
394
|
|
|
@@ -418,7 +415,6 @@ const ownerPropertyEntries = (owner: MemberOwner, setup: PropertyAccessorSetup,
|
|
|
418
415
|
context: setup.context,
|
|
419
416
|
property,
|
|
420
417
|
claimedNames: setup.claimedNames,
|
|
421
|
-
methodByName: setup.methodByName,
|
|
422
418
|
});
|
|
423
419
|
|
|
424
420
|
if (accessor === undefined || seen.has(accessor.jsName)) {
|
package/src/fingerprint.ts
CHANGED
|
@@ -7,6 +7,13 @@ import packageManifest from "../package.json" with { type: "json" };
|
|
|
7
7
|
import { arrayGuard, hasFields, isNumber, isString, optionalGuard } from "./guards.js";
|
|
8
8
|
import { readJsonFile } from "./json.js";
|
|
9
9
|
|
|
10
|
+
type GiInputs = {
|
|
11
|
+
girFiles: string[];
|
|
12
|
+
libraries: string[];
|
|
13
|
+
girPath: string[];
|
|
14
|
+
isByteArrayTyped: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
10
17
|
type GiFingerprint = {
|
|
11
18
|
value: string;
|
|
12
19
|
girFiles: string[];
|
|
@@ -56,15 +63,17 @@ const overrideFiles = (): string[] => {
|
|
|
56
63
|
.map((entry: Dirent) => join(entry.parentPath, entry.name));
|
|
57
64
|
};
|
|
58
65
|
|
|
59
|
-
const hashGi = (
|
|
66
|
+
const hashGi = (inputs: GiInputs): string => {
|
|
60
67
|
const hash = createHash("sha256");
|
|
61
68
|
hash.update(CODEGEN_VERSION);
|
|
62
69
|
hash.update("\n");
|
|
63
|
-
hash.update(sortAlpha(libraries));
|
|
70
|
+
hash.update(sortAlpha(inputs.libraries));
|
|
71
|
+
hash.update("\n");
|
|
72
|
+
hash.update(sortAlpha(inputs.girPath));
|
|
64
73
|
hash.update("\n");
|
|
65
|
-
hash.update(
|
|
74
|
+
hash.update(String(inputs.isByteArrayTyped));
|
|
66
75
|
hash.update("\n");
|
|
67
|
-
const hashedFiles = sortStrings([...girFiles, ...overrideFiles()]);
|
|
76
|
+
const hashedFiles = sortStrings([...inputs.girFiles, ...overrideFiles()]);
|
|
68
77
|
|
|
69
78
|
for (const file of hashedFiles) {
|
|
70
79
|
hash.update("\n");
|
|
@@ -76,15 +85,11 @@ const hashGi = (girFiles: string[], libraries: string[], girPath: string[]): str
|
|
|
76
85
|
return hash.digest("hex");
|
|
77
86
|
};
|
|
78
87
|
|
|
79
|
-
const computeGiFingerprint = (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
value: hashGi(girFiles, libraries, girPath),
|
|
85
|
-
girFiles,
|
|
86
|
-
libraries,
|
|
87
|
-
girPath,
|
|
88
|
+
const computeGiFingerprint = (inputs: GiInputs): GiFingerprint => ({
|
|
89
|
+
value: hashGi(inputs),
|
|
90
|
+
girFiles: inputs.girFiles,
|
|
91
|
+
libraries: inputs.libraries,
|
|
92
|
+
girPath: inputs.girPath,
|
|
88
93
|
});
|
|
89
94
|
|
|
90
95
|
const readSentinel = (storeDir: string): unknown => readJsonFile(join(storeDir, FINGERPRINT_FILENAME));
|
|
@@ -103,30 +108,27 @@ const isDocsFingerprint = (value: unknown): value is DocsFingerprint =>
|
|
|
103
108
|
const isJsxFingerprint = (value: unknown): value is JsxFingerprint =>
|
|
104
109
|
hasFields<JsxFingerprint>(value, { value: isString, intrinsicElementCount: isNumber });
|
|
105
110
|
|
|
106
|
-
const recordedGiValue = (sentinel: GiFingerprint,
|
|
107
|
-
if (!hasMatchingRecordedInputs(sentinel,
|
|
111
|
+
const recordedGiValue = (sentinel: GiFingerprint, inputs: GiInputs): string | undefined => {
|
|
112
|
+
if (!hasMatchingRecordedInputs(sentinel, inputs)) {
|
|
108
113
|
return undefined;
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
try {
|
|
112
|
-
return hashGi(sentinel.girFiles, sentinel.libraries
|
|
117
|
+
return hashGi({ ...inputs, girFiles: sentinel.girFiles, libraries: sentinel.libraries });
|
|
113
118
|
} catch {
|
|
114
119
|
return undefined;
|
|
115
120
|
}
|
|
116
121
|
};
|
|
117
122
|
|
|
118
|
-
const isGiStoreFresh = (
|
|
119
|
-
giStoreDir: string,
|
|
120
|
-
libraries: string[],
|
|
121
|
-
girPath: string[],
|
|
122
|
-
): boolean => {
|
|
123
|
+
const isGiStoreFresh = (giStoreDir: string, inputs: GiInputs): boolean => {
|
|
123
124
|
const sentinel = readSentinel(giStoreDir);
|
|
124
125
|
|
|
125
|
-
return isGiFingerprint(sentinel) && recordedGiValue(sentinel,
|
|
126
|
+
return isGiFingerprint(sentinel) && recordedGiValue(sentinel, inputs) === sentinel.value;
|
|
126
127
|
};
|
|
127
128
|
|
|
128
|
-
const hasMatchingRecordedInputs = (sentinel: GiFingerprint,
|
|
129
|
-
sortAlpha(sentinel.libraries) === sortAlpha(libraries) &&
|
|
129
|
+
const hasMatchingRecordedInputs = (sentinel: GiFingerprint, inputs: GiInputs): boolean =>
|
|
130
|
+
sortAlpha(sentinel.libraries) === sortAlpha(inputs.libraries) &&
|
|
131
|
+
sortAlpha(sentinel.girPath ?? []) === sortAlpha(inputs.girPath);
|
|
130
132
|
|
|
131
133
|
const hashDocs = (giValue: string, input: DocsFingerprintInput): string =>
|
|
132
134
|
createHash("sha256")
|
|
@@ -140,30 +142,20 @@ const hashDocs = (giValue: string, input: DocsFingerprintInput): string =>
|
|
|
140
142
|
)
|
|
141
143
|
.digest("hex");
|
|
142
144
|
|
|
143
|
-
const computeDocsFingerprint = (
|
|
144
|
-
|
|
145
|
-
libraries: string[],
|
|
146
|
-
girPath: string[],
|
|
147
|
-
input: DocsFingerprintInput,
|
|
148
|
-
): DocsFingerprint => {
|
|
149
|
-
const gi = computeGiFingerprint(girFiles, libraries, girPath);
|
|
145
|
+
const computeDocsFingerprint = (inputs: GiInputs, input: DocsFingerprintInput): DocsFingerprint => {
|
|
146
|
+
const gi = computeGiFingerprint(inputs);
|
|
150
147
|
|
|
151
148
|
return { value: hashDocs(gi.value, input), gi };
|
|
152
149
|
};
|
|
153
150
|
|
|
154
|
-
const isDocsOutputFresh = (
|
|
155
|
-
outDir: string,
|
|
156
|
-
libraries: string[],
|
|
157
|
-
girPath: string[],
|
|
158
|
-
input: DocsFingerprintInput,
|
|
159
|
-
): boolean => {
|
|
151
|
+
const isDocsOutputFresh = (outDir: string, inputs: GiInputs, input: DocsFingerprintInput): boolean => {
|
|
160
152
|
const sentinel = readSentinel(outDir);
|
|
161
153
|
|
|
162
154
|
if (!isDocsFingerprint(sentinel)) {
|
|
163
155
|
return false;
|
|
164
156
|
}
|
|
165
157
|
|
|
166
|
-
const giValue = recordedGiValue(sentinel.gi,
|
|
158
|
+
const giValue = recordedGiValue(sentinel.gi, inputs);
|
|
167
159
|
|
|
168
160
|
return giValue !== undefined && hashDocs(giValue, input) === sentinel.value;
|
|
169
161
|
};
|
|
@@ -217,6 +209,7 @@ export {
|
|
|
217
209
|
computeJsxFingerprint,
|
|
218
210
|
jsxStoreFreshness,
|
|
219
211
|
type DocsFingerprintInput,
|
|
212
|
+
type GiInputs,
|
|
220
213
|
type GiFingerprint,
|
|
221
214
|
type JsxFingerprintInput,
|
|
222
215
|
};
|
package/src/gi.ts
CHANGED
|
@@ -9,10 +9,11 @@ type GiCodegenOptions = {
|
|
|
9
9
|
gi: StoreOptions;
|
|
10
10
|
libraries: string[];
|
|
11
11
|
girPath: string[];
|
|
12
|
+
isByteArrayTyped: boolean;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
const runGiCodegen = (library: Library, options: GiCodegenOptions): number => {
|
|
15
|
-
const { gi, libraries, girPath } = options;
|
|
16
|
+
const { gi, libraries, girPath, isByteArrayTyped } = options;
|
|
16
17
|
|
|
17
18
|
const namespaces: GiNamespaceInput[] = Array.from(library.namespaces.values(), (namespace) => ({
|
|
18
19
|
directory: namespaceDirectory(namespace),
|
|
@@ -20,7 +21,13 @@ const runGiCodegen = (library: Library, options: GiCodegenOptions): number => {
|
|
|
20
21
|
girFile: namespace.girFile,
|
|
21
22
|
}));
|
|
22
23
|
|
|
23
|
-
const fingerprint = computeGiFingerprint(
|
|
24
|
+
const fingerprint = computeGiFingerprint({
|
|
25
|
+
girFiles: library.girFiles,
|
|
26
|
+
libraries: [...libraries],
|
|
27
|
+
girPath: [...girPath],
|
|
28
|
+
isByteArrayTyped,
|
|
29
|
+
});
|
|
30
|
+
|
|
24
31
|
writeGiStore(gi, namespaces, fingerprint);
|
|
25
32
|
|
|
26
33
|
return library.namespaces.size;
|
package/src/gir/gir-path.ts
CHANGED
|
@@ -7,7 +7,7 @@ const SYSTEM_GIR_PATH = "/usr/share/gir-1.0";
|
|
|
7
7
|
/**
|
|
8
8
|
* The directories to search for `.gir` files, in precedence order: the config's own `girPath`, then
|
|
9
9
|
* `GTKX_GIR_PATH`, then `/usr/share/gir-1.0`, then the girdir pkg-config reports for
|
|
10
|
-
* gobject-introspection-1.0. Duplicates are dropped, and the
|
|
10
|
+
* gobject-introspection-1.0. Duplicates are dropped, and the system directories are included only when they exist.
|
|
11
11
|
*
|
|
12
12
|
* @throws If pkg-config is installed but fails while being queried.
|
|
13
13
|
*/
|
package/src/gir/library.ts
CHANGED
|
@@ -84,10 +84,12 @@ class Library {
|
|
|
84
84
|
*
|
|
85
85
|
* @param libraries GIR identifiers to load, such as `Gtk-4.0`.
|
|
86
86
|
* @param girPath Directories searched, in order, for each `.gir` file.
|
|
87
|
+
* @param isByteArrayTyped Whether byte sequences render as `Uint8Array` rather than `number[]`.
|
|
87
88
|
* @returns A library holding every namespace that was parsed.
|
|
88
89
|
*/
|
|
89
|
-
static load(libraries: string[], girPath: string[]): Library {
|
|
90
|
+
static load(libraries: string[], girPath: string[], isByteArrayTyped = false): Library {
|
|
90
91
|
const library = new Library();
|
|
92
|
+
library.isByteArrayTypedValue = isByteArrayTyped;
|
|
91
93
|
this.drive(library, libraries, girPath);
|
|
92
94
|
|
|
93
95
|
return library;
|
|
@@ -99,6 +101,7 @@ class Library {
|
|
|
99
101
|
private nsIdByName: Map<string, number> = new Map();
|
|
100
102
|
private typeTables: TypeTable[] = [];
|
|
101
103
|
private girFilesValue: string[] = [];
|
|
104
|
+
private isByteArrayTypedValue = false;
|
|
102
105
|
|
|
103
106
|
/** Creates a library with no namespaces loaded; {@link Library.load} builds a populated one. */
|
|
104
107
|
constructor() {
|
|
@@ -328,6 +331,11 @@ class Library {
|
|
|
328
331
|
return this.namespacesByName;
|
|
329
332
|
}
|
|
330
333
|
|
|
334
|
+
/** Whether byte sequences render as `Uint8Array` rather than `number[]`. */
|
|
335
|
+
public get isByteArrayTyped(): boolean {
|
|
336
|
+
return this.isByteArrayTypedValue;
|
|
337
|
+
}
|
|
338
|
+
|
|
331
339
|
/** Paths of the `.gir` files that were read, in the order they were discovered. */
|
|
332
340
|
public get girFiles(): string[] {
|
|
333
341
|
return this.girFilesValue;
|
package/src/gir/parse.ts
CHANGED
|
@@ -5,6 +5,11 @@ type RawNode = Record<string, unknown>;
|
|
|
5
5
|
|
|
6
6
|
const GIR_LABEL = "GIR file";
|
|
7
7
|
|
|
8
|
+
const GIR_MALFORMED_REMEDY =
|
|
9
|
+
"No XML parser can read this file, so report it to whoever ships it. To get past it now, name the " +
|
|
10
|
+
"libraries you need explicitly in `libraries` and leave this one out, or put a corrected copy of the " +
|
|
11
|
+
"file in a directory listed in `girPath`, which is searched ahead of the system location.";
|
|
12
|
+
|
|
8
13
|
const MULTI_TAGS: Set<string> = new Set([
|
|
9
14
|
"include",
|
|
10
15
|
"class",
|
|
@@ -44,7 +49,7 @@ function renameReservedTag(tag: string): string {
|
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
const parseGirFile = (path: string): RawNode =>
|
|
47
|
-
parseXmlFile({ parser: PARSER, label: GIR_LABEL, path }) as RawNode;
|
|
52
|
+
parseXmlFile({ parser: PARSER, label: GIR_LABEL, path, malformedRemedy: GIR_MALFORMED_REMEDY }) as RawNode;
|
|
48
53
|
|
|
49
54
|
const attr = (node: RawNode | undefined, name: string): string | undefined => {
|
|
50
55
|
if (node === undefined) {
|
package/src/gir/primitives.ts
CHANGED
|
@@ -110,7 +110,7 @@ const PRIMITIVE_TS_TYPE: Record<PrimitiveCategory, string> = {
|
|
|
110
110
|
float64: "number",
|
|
111
111
|
string: "string",
|
|
112
112
|
unichar: "string",
|
|
113
|
-
pointer: "
|
|
113
|
+
pointer: "bigint",
|
|
114
114
|
};
|
|
115
115
|
|
|
116
116
|
const primitiveCategory = (name: string): PrimitiveCategory | undefined => PRIMITIVE_BY_NAME.get(name);
|
package/src/khronos/args.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { toCamelIdentifier } from "@gtkx/utils";
|
|
|
2
2
|
import type { GlCommand, GlParam } from "./model.js";
|
|
3
3
|
import type { CommandPlan, GlScalar, ParamPlan, ReturnPlan } from "./plan.js";
|
|
4
4
|
import {
|
|
5
|
+
type ArrayLayout,
|
|
5
6
|
tArray,
|
|
6
7
|
tBoolean,
|
|
7
8
|
tBuffer,
|
|
@@ -50,6 +51,7 @@ type BuildArgOptions = {
|
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
const OUT_PLAN_KINDS: Set<string> = new Set(["ref-out", "ref-array-out", "ref-fixed-out", "string-out"]);
|
|
54
|
+
const NUMERIC_LAYOUT: ArrayLayout = { isBytes: false };
|
|
53
55
|
|
|
54
56
|
const scalarAliasOrGroup = (scalar: GlScalar, group: string | undefined): string =>
|
|
55
57
|
group !== undefined && scalar.isGroupBearing === true ? group : scalar.tsAlias;
|
|
@@ -122,12 +124,13 @@ const buildInArg = (options: BuildArgOptions, name: string, track: (alias: strin
|
|
|
122
124
|
return inArg(name, "string", tString("borrowed"));
|
|
123
125
|
}
|
|
124
126
|
case "string-array-in": {
|
|
125
|
-
return inArg(name, "string[]", tArray(tString("borrowed")));
|
|
127
|
+
return inArg(name, "string[]", tArray(tString("borrowed"), undefined, NUMERIC_LAYOUT));
|
|
126
128
|
}
|
|
127
129
|
case "array-in": {
|
|
128
130
|
track(scalarAliasOrGroup(plan.scalar, param.group));
|
|
131
|
+
const descriptor = tArray(plan.scalar.descriptor, undefined, NUMERIC_LAYOUT);
|
|
129
132
|
|
|
130
|
-
return inArg(name, arrayInTsType(plan.scalar, param.group),
|
|
133
|
+
return inArg(name, arrayInTsType(plan.scalar, param.group), descriptor);
|
|
131
134
|
}
|
|
132
135
|
case "buffer": {
|
|
133
136
|
return inArg(name, `ArrayBufferView | ${track("GLintptr")} | null`, tBuffer);
|
|
@@ -136,7 +139,7 @@ const buildInArg = (options: BuildArgOptions, name: string, track: (alias: strin
|
|
|
136
139
|
return inArg(name, track("GLintptr"), tUint64);
|
|
137
140
|
}
|
|
138
141
|
case "byte-offset-array": {
|
|
139
|
-
return inArg(name, `${track("GLintptr")}[]`, tArray(tUint64));
|
|
142
|
+
return inArg(name, `${track("GLintptr")}[]`, tArray(tUint64, undefined, NUMERIC_LAYOUT));
|
|
140
143
|
}
|
|
141
144
|
case "ref-array-out":
|
|
142
145
|
case "ref-fixed-out":
|
|
@@ -165,14 +168,14 @@ const outArgFields = (options: OutArgFieldsOptions): OutArgFields => {
|
|
|
165
168
|
return {
|
|
166
169
|
seed: `const ${cellName} = { value: new Array<number>(${lenIdentifier}).fill(0) };`,
|
|
167
170
|
tsType: `${track(scalarAliasOrGroup(plan.scalar, param.group))}[]`,
|
|
168
|
-
descriptor: tRef(tSizedArray(plan.scalar.descriptor, sizeIndex)),
|
|
171
|
+
descriptor: tRef(tSizedArray(plan.scalar.descriptor, sizeIndex, undefined, NUMERIC_LAYOUT)),
|
|
169
172
|
};
|
|
170
173
|
}
|
|
171
174
|
case "ref-fixed-out": {
|
|
172
175
|
return {
|
|
173
176
|
seed: `const ${cellName} = { value: new Array<number>(${String(plan.length)}).fill(0) };`,
|
|
174
177
|
tsType: `${track(plan.scalar.tsAlias)}[]`,
|
|
175
|
-
descriptor: tRef(tFixedArray(plan.scalar.descriptor, plan.length)),
|
|
178
|
+
descriptor: tRef(tFixedArray(plan.scalar.descriptor, plan.length, undefined, NUMERIC_LAYOUT)),
|
|
176
179
|
};
|
|
177
180
|
}
|
|
178
181
|
case "string-out": {
|
|
@@ -204,7 +207,7 @@ const buildOutArg = (options: BuildArgOptions, track: (alias: string) => string)
|
|
|
204
207
|
throw new Error(`Parameter index ${String(index)} out of range on ${command.name}`);
|
|
205
208
|
}
|
|
206
209
|
|
|
207
|
-
const cellName = `
|
|
210
|
+
const cellName = `__out${String(outIndex)}`;
|
|
208
211
|
const cell: OutArgCell = { isOut: true, cellName, paramIndex: index };
|
|
209
212
|
|
|
210
213
|
return { ...cell, ...outArgFields({ command, plan, param, cellName, track }) };
|
package/src/khronos/render.ts
CHANGED
|
@@ -114,7 +114,7 @@ const returnStatements = (call: string, returned: EmittedReturn, outs: OutArg[])
|
|
|
114
114
|
return [`${call};`, tail];
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
return [`const
|
|
117
|
+
return [`const __result = ${call};`, `return [${returned.expr("__result")}, ${outValues.join(", ")}];`];
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
const renderCommand = (
|
|
@@ -135,7 +135,7 @@ const renderCommand = (
|
|
|
135
135
|
const seeds = outs.map((out) => out.seed);
|
|
136
136
|
const bindExpression = glBind(command.name, descriptors, returned.descriptor);
|
|
137
137
|
const isInline = plan.params.some((paramPlan) => paramPlan.kind === "string-out");
|
|
138
|
-
const bindingName = isInline ? "
|
|
138
|
+
const bindingName = isInline ? "__binding" : toCamelIdentifier(command.name);
|
|
139
139
|
|
|
140
140
|
const body = [
|
|
141
141
|
...seeds,
|
|
@@ -252,12 +252,14 @@ const deriveGenSingular = (
|
|
|
252
252
|
const descriptors = [...prefix.map((arg) => arg.descriptor), countScalar.descriptor, tRef(outScalar.descriptor)];
|
|
253
253
|
usedTypes.add(outScalar.tsAlias);
|
|
254
254
|
const signature = prefix.map((arg) => `${arg.name}: ${arg.tsType}`).join(", ");
|
|
255
|
-
const callArgs = [...prefix.map((arg) => arg.name), "1", "
|
|
255
|
+
const callArgs = [...prefix.map((arg) => arg.name), "1", "__out"].join(", ");
|
|
256
256
|
const jsDoc = genSingularJsDoc({ plan, provenance, docs }, prefix, shape);
|
|
257
257
|
|
|
258
|
-
const body = [
|
|
259
|
-
"
|
|
260
|
-
)
|
|
258
|
+
const body = [
|
|
259
|
+
" const __out = { value: 0 };",
|
|
260
|
+
` ${bindingName}(${callArgs});`,
|
|
261
|
+
" return __out.value;",
|
|
262
|
+
].join("\n");
|
|
261
263
|
|
|
262
264
|
const binding = glBind(plan.command.name, renderDescriptorList(descriptors), tVoid);
|
|
263
265
|
|
package/src/runner.ts
CHANGED
|
@@ -39,6 +39,8 @@ type CodegenRunnerOptions = {
|
|
|
39
39
|
userOmittedProps?: OmittedProps;
|
|
40
40
|
/** Regenerates both stores even when their fingerprints are fresh. */
|
|
41
41
|
isForced?: boolean;
|
|
42
|
+
/** Emits GIR byte sequences as `Uint8Array` rather than `number[]`; defaults to false. */
|
|
43
|
+
isByteArrayTyped?: boolean;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
/** What a `runCodegen` run produced. */
|
|
@@ -157,12 +159,14 @@ const emitStoresWithConfig = async (config: {
|
|
|
157
159
|
girPath: string[];
|
|
158
160
|
}): Promise<StoreResult> => {
|
|
159
161
|
const { options, gi, jsx, libraries, girPath } = config;
|
|
162
|
+
const isByteArrayTyped = options.isByteArrayTyped === true;
|
|
160
163
|
let library: Library | undefined;
|
|
161
|
-
const loadLibrary = (): Library => (library ??= Library.load(libraries, girPath));
|
|
162
|
-
const
|
|
164
|
+
const loadLibrary = (): Library => (library ??= Library.load(libraries, girPath, isByteArrayTyped));
|
|
165
|
+
const giInputs = { girFiles: [] as string[], libraries, girPath, isByteArrayTyped };
|
|
166
|
+
const isGiRegenerated = options.isForced === true || !isGiStoreFresh(gi.storeDir, giInputs);
|
|
163
167
|
|
|
164
168
|
const namespaces = isGiRegenerated
|
|
165
|
-
? runGiCodegen(loadLibrary(), { gi, libraries, girPath })
|
|
169
|
+
? runGiCodegen(loadLibrary(), { gi, libraries, girPath, isByteArrayTyped })
|
|
166
170
|
: 0;
|
|
167
171
|
|
|
168
172
|
if (jsx === undefined) {
|
package/src/staging.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
+
import { errorCode } from "@gtkx/utils";
|
|
1
2
|
import { mkdirSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
2
3
|
import { basename, dirname, join } from "node:path";
|
|
3
4
|
|
|
4
5
|
const STAGING_SUFFIX = ".tmp-";
|
|
5
6
|
const OWNER_PATTERN = /^(?<pid>\d+)-/;
|
|
6
7
|
|
|
7
|
-
const errorCode = (error: unknown): string | undefined =>
|
|
8
|
-
error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : undefined;
|
|
9
|
-
|
|
10
8
|
const isOwnerRunning = (pid: number): boolean => {
|
|
11
9
|
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
12
10
|
return false;
|