@lakutata/ffi 0.0.0-stage → 3.1.2
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/LICENSE +23 -0
- package/README.md +163 -2
- package/README.zh-CN.md +140 -0
- package/dist/cjs/components/NativeLibrary.d.ts +229 -0
- package/dist/cjs/components/NativeLibrary.js +346 -0
- package/dist/cjs/decorators/Library.d.ts +61 -0
- package/dist/cjs/decorators/Library.js +44 -0
- package/dist/cjs/decorators/Native.d.ts +69 -0
- package/dist/cjs/decorators/Native.js +90 -0
- package/dist/cjs/exceptions/NativeDeclarationException.d.ts +12 -0
- package/dist/cjs/exceptions/NativeDeclarationException.js +19 -0
- package/dist/cjs/exceptions/NativeLibraryClosedException.d.ts +12 -0
- package/dist/cjs/exceptions/NativeLibraryClosedException.js +19 -0
- package/dist/cjs/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
- package/dist/cjs/exceptions/NativeLibraryNotFoundException.js +18 -0
- package/dist/cjs/exceptions/NativeMemoryException.d.ts +11 -0
- package/dist/cjs/exceptions/NativeMemoryException.js +18 -0
- package/dist/cjs/exports/FFI.d.ts +12 -0
- package/dist/cjs/exports/FFI.js +40 -0
- package/dist/cjs/exports/Generator.d.ts +2 -0
- package/dist/cjs/exports/Generator.js +10 -0
- package/dist/cjs/lib/Memory.d.ts +188 -0
- package/dist/cjs/lib/Memory.js +227 -0
- package/dist/cjs/lib/Runtime.d.ts +98 -0
- package/dist/cjs/lib/Runtime.js +209 -0
- package/dist/cjs/lib/backend/Functions.d.ts +65 -0
- package/dist/cjs/lib/backend/Functions.js +214 -0
- package/dist/cjs/lib/backend/KoffiTypes.d.ts +53 -0
- package/dist/cjs/lib/backend/KoffiTypes.js +426 -0
- package/dist/cjs/lib/declarations/Builtins.d.ts +33 -0
- package/dist/cjs/lib/declarations/Builtins.js +179 -0
- package/dist/cjs/lib/declarations/Layout.d.ts +41 -0
- package/dist/cjs/lib/declarations/Layout.js +180 -0
- package/dist/cjs/lib/declarations/Lexer.d.ts +40 -0
- package/dist/cjs/lib/declarations/Lexer.js +325 -0
- package/dist/cjs/lib/declarations/Parser.d.ts +90 -0
- package/dist/cjs/lib/declarations/Parser.js +1425 -0
- package/dist/cjs/lib/declarations/Platform.d.ts +50 -0
- package/dist/cjs/lib/declarations/Platform.js +27 -0
- package/dist/cjs/lib/declarations/Types.d.ts +173 -0
- package/dist/cjs/lib/declarations/Types.js +179 -0
- package/dist/cjs/lib/generator/CText.d.ts +14 -0
- package/dist/cjs/lib/generator/CText.js +133 -0
- package/dist/cjs/lib/generator/Generator.d.ts +136 -0
- package/dist/cjs/lib/generator/Generator.js +395 -0
- package/dist/cjs/lib/generator/Preprocessor.d.ts +60 -0
- package/dist/cjs/lib/generator/Preprocessor.js +94 -0
- package/dist/cjs/lib/generator/TypeScriptTypes.d.ts +35 -0
- package/dist/cjs/lib/generator/TypeScriptTypes.js +153 -0
- package/dist/cjs/lib/library/NativeCallback.d.ts +44 -0
- package/dist/cjs/lib/library/NativeCallback.js +66 -0
- package/dist/cjs/lib/library/NativeLibraryHandle.d.ts +268 -0
- package/dist/cjs/lib/library/NativeLibraryHandle.js +540 -0
- package/dist/cjs/lib/library/Resolve.d.ts +49 -0
- package/dist/cjs/lib/library/Resolve.js +130 -0
- package/dist/cjs/lib/thread/NativeThreads.d.ts +80 -0
- package/dist/cjs/lib/thread/NativeThreads.js +384 -0
- package/dist/cjs/lib/thread/NativeWorker.d.ts +6 -0
- package/dist/cjs/lib/thread/NativeWorker.js +96 -0
- package/dist/cjs/lib/thread/NativeWorkerCode.d.ts +5 -0
- package/dist/cjs/lib/thread/NativeWorkerCode.js +4 -0
- package/dist/cjs/lib/thread/PackageRequire.d.ts +6 -0
- package/dist/cjs/lib/thread/PackageRequire.js +4 -0
- package/dist/cjs/lib/thread/ThreadProtocol.d.ts +77 -0
- package/dist/cjs/lib/thread/ThreadProtocol.js +2 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/components/NativeLibrary.js +338 -0
- package/dist/esm/decorators/Library.js +40 -0
- package/dist/esm/decorators/Native.js +85 -0
- package/dist/esm/exceptions/NativeDeclarationException.js +15 -0
- package/dist/esm/exceptions/NativeLibraryClosedException.js +15 -0
- package/dist/esm/exceptions/NativeLibraryNotFoundException.js +14 -0
- package/dist/esm/exceptions/NativeMemoryException.js +14 -0
- package/dist/esm/exports/FFI.js +10 -0
- package/dist/esm/exports/Generator.js +2 -0
- package/dist/esm/lib/Memory.js +210 -0
- package/dist/esm/lib/Runtime.js +192 -0
- package/dist/esm/lib/backend/Functions.js +206 -0
- package/dist/esm/lib/backend/KoffiTypes.js +418 -0
- package/dist/esm/lib/declarations/Builtins.js +174 -0
- package/dist/esm/lib/declarations/Layout.js +175 -0
- package/dist/esm/lib/declarations/Lexer.js +321 -0
- package/dist/esm/lib/declarations/Parser.js +1421 -0
- package/dist/esm/lib/declarations/Platform.js +23 -0
- package/dist/esm/lib/declarations/Types.js +170 -0
- package/dist/esm/lib/generator/CText.js +129 -0
- package/dist/esm/lib/generator/Generator.js +388 -0
- package/dist/esm/lib/generator/Preprocessor.js +87 -0
- package/dist/esm/lib/generator/TypeScriptTypes.js +149 -0
- package/dist/esm/lib/library/NativeCallback.js +59 -0
- package/dist/esm/lib/library/NativeLibraryHandle.js +532 -0
- package/dist/esm/lib/library/Resolve.js +122 -0
- package/dist/esm/lib/thread/NativeThreads.js +377 -0
- package/dist/esm/lib/thread/NativeWorker.js +93 -0
- package/dist/esm/lib/thread/NativeWorkerCode.js +1 -0
- package/dist/esm/lib/thread/PackageRequire.js +2 -0
- package/dist/esm/lib/thread/ThreadProtocol.js +1 -0
- package/dist/types/components/NativeLibrary.d.ts +229 -0
- package/dist/types/decorators/Library.d.ts +61 -0
- package/dist/types/decorators/Native.d.ts +69 -0
- package/dist/types/exceptions/NativeDeclarationException.d.ts +12 -0
- package/dist/types/exceptions/NativeLibraryClosedException.d.ts +12 -0
- package/dist/types/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
- package/dist/types/exceptions/NativeMemoryException.d.ts +11 -0
- package/dist/types/exports/FFI.d.ts +12 -0
- package/dist/types/exports/Generator.d.ts +2 -0
- package/dist/types/lib/Memory.d.ts +188 -0
- package/dist/types/lib/Runtime.d.ts +98 -0
- package/dist/types/lib/backend/Functions.d.ts +65 -0
- package/dist/types/lib/backend/KoffiTypes.d.ts +53 -0
- package/dist/types/lib/declarations/Builtins.d.ts +33 -0
- package/dist/types/lib/declarations/Layout.d.ts +41 -0
- package/dist/types/lib/declarations/Lexer.d.ts +40 -0
- package/dist/types/lib/declarations/Parser.d.ts +90 -0
- package/dist/types/lib/declarations/Platform.d.ts +50 -0
- package/dist/types/lib/declarations/Types.d.ts +173 -0
- package/dist/types/lib/generator/CText.d.ts +14 -0
- package/dist/types/lib/generator/Generator.d.ts +136 -0
- package/dist/types/lib/generator/Preprocessor.d.ts +60 -0
- package/dist/types/lib/generator/TypeScriptTypes.d.ts +35 -0
- package/dist/types/lib/library/NativeCallback.d.ts +44 -0
- package/dist/types/lib/library/NativeLibraryHandle.d.ts +268 -0
- package/dist/types/lib/library/Resolve.d.ts +49 -0
- package/dist/types/lib/thread/NativeThreads.d.ts +80 -0
- package/dist/types/lib/thread/NativeWorker.d.ts +6 -0
- package/dist/types/lib/thread/NativeWorkerCode.d.ts +5 -0
- package/dist/types/lib/thread/PackageRequire.d.ts +6 -0
- package/dist/types/lib/thread/ThreadProtocol.d.ts +77 -0
- package/package.json +85 -4
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { type Platform } from '../declarations/Platform.js';
|
|
2
|
+
import { type PreprocessedHeader, type PreprocessOptions } from './Preprocessor.js';
|
|
3
|
+
/**
|
|
4
|
+
* How a library is generated from its header
|
|
5
|
+
*/
|
|
6
|
+
export interface GenerateOptions extends PreprocessOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The library its functions are checked against: a name, as `@Library` takes it, or a file. The name of the header
|
|
9
|
+
* by default (`sqlite3.h`: `sqlite3`): give it when that library cannot be loaded (the report tells why the
|
|
10
|
+
* functions were not checked).
|
|
11
|
+
*/
|
|
12
|
+
readonly library?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The name of the class. The name of the header in PascalCase by default (`sqlite3.h`: `Sqlite3`).
|
|
15
|
+
*/
|
|
16
|
+
readonly className?: string;
|
|
17
|
+
/**
|
|
18
|
+
* More directories whose headers are the ones of the library: the header is, and so are the headers of its
|
|
19
|
+
* directory when it is not a directory of the system.
|
|
20
|
+
*/
|
|
21
|
+
readonly own?: readonly string[];
|
|
22
|
+
/**
|
|
23
|
+
* Whether the functions and the variables are checked against the library, the ones it cannot give left out.
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
readonly check?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A declaration of the header that was left out, and why
|
|
30
|
+
*/
|
|
31
|
+
export interface SkippedDeclaration {
|
|
32
|
+
/**
|
|
33
|
+
* Its name, or the start of its text
|
|
34
|
+
*/
|
|
35
|
+
readonly name: string;
|
|
36
|
+
/**
|
|
37
|
+
* The file it comes from
|
|
38
|
+
*/
|
|
39
|
+
readonly file?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Its line in that file
|
|
42
|
+
*/
|
|
43
|
+
readonly line?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Why it was left out
|
|
46
|
+
*/
|
|
47
|
+
readonly reason: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* What the generation did
|
|
51
|
+
*/
|
|
52
|
+
export interface GenerationReport {
|
|
53
|
+
/**
|
|
54
|
+
* The command that ran the preprocessor
|
|
55
|
+
*/
|
|
56
|
+
readonly command: string;
|
|
57
|
+
/**
|
|
58
|
+
* The platform the library was generated for (`darwin-arm64`)
|
|
59
|
+
*/
|
|
60
|
+
readonly platform: string;
|
|
61
|
+
/**
|
|
62
|
+
* The file of the library the functions were checked against
|
|
63
|
+
*/
|
|
64
|
+
readonly library?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Why they were not checked
|
|
67
|
+
*/
|
|
68
|
+
readonly unchecked?: string;
|
|
69
|
+
/**
|
|
70
|
+
* The functions of the class
|
|
71
|
+
*/
|
|
72
|
+
readonly functions: number;
|
|
73
|
+
/**
|
|
74
|
+
* The variables of the class
|
|
75
|
+
*/
|
|
76
|
+
readonly variables: number;
|
|
77
|
+
/**
|
|
78
|
+
* The interfaces of the structs and unions
|
|
79
|
+
*/
|
|
80
|
+
readonly interfaces: number;
|
|
81
|
+
/**
|
|
82
|
+
* The constants of the declarations: the integer macros, the values of the enums, the static constants
|
|
83
|
+
*/
|
|
84
|
+
readonly constants: number;
|
|
85
|
+
/**
|
|
86
|
+
* The declarations of the library left out: the ones that cannot be read, the functions and variables the library
|
|
87
|
+
* cannot give
|
|
88
|
+
*/
|
|
89
|
+
readonly skipped: readonly SkippedDeclaration[];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A library generated from its header
|
|
93
|
+
*/
|
|
94
|
+
export interface GeneratedLibrary {
|
|
95
|
+
/**
|
|
96
|
+
* The name of its class
|
|
97
|
+
*/
|
|
98
|
+
readonly className: string;
|
|
99
|
+
/**
|
|
100
|
+
* The TypeScript code of its class
|
|
101
|
+
*/
|
|
102
|
+
readonly code: string;
|
|
103
|
+
/**
|
|
104
|
+
* Its C declarations, as the class gives them to `@Library`
|
|
105
|
+
*/
|
|
106
|
+
readonly declarations: string;
|
|
107
|
+
/**
|
|
108
|
+
* What was generated, and what was left out
|
|
109
|
+
*/
|
|
110
|
+
readonly report: GenerationReport;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Generate the class of a library from a header already run through the preprocessor (see generateNativeLibrary).
|
|
114
|
+
* @param preprocessed The output of the preprocessor, the header, the directories of the system.
|
|
115
|
+
* @param options The library, the class, the directories of the library.
|
|
116
|
+
* @param platform The platform the header was preprocessed for.
|
|
117
|
+
* @returns The code of the class and the report.
|
|
118
|
+
*/
|
|
119
|
+
export declare function generateFromPreprocessed(preprocessed: PreprocessedHeader, options?: Omit<GenerateOptions, 'header'>, platform?: Platform): Promise<GeneratedLibrary>;
|
|
120
|
+
/**
|
|
121
|
+
* Generate the class of a native library from its header: the header is run through the preprocessor of the C compiler,
|
|
122
|
+
* its declarations are read, the functions and the variables the library cannot give are left out, and the class gets a
|
|
123
|
+
* property by function (named as the function) with its TypeScript type.
|
|
124
|
+
* @param options The header, the library, the class, the compiler and its options.
|
|
125
|
+
* @returns The code of the class, and what was generated and left out.
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* import {writeFileSync} from 'node:fs'
|
|
129
|
+
* import {generateNativeLibrary} from '@lakutata/ffi/generator'
|
|
130
|
+
*
|
|
131
|
+
* const {code, report} = await generateNativeLibrary({header: 'vendor/include/vendor.h', library: 'vendor/lib/libvendor.so'})
|
|
132
|
+
* writeFileSync('src/native/Vendor.ts', code)
|
|
133
|
+
* for (const skipped of report.skipped) console.log(`${skipped.name}: ${skipped.reason}`)
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export declare function generateNativeLibrary(options: GenerateOptions): Promise<GeneratedLibrary>;
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateFromPreprocessed = generateFromPreprocessed;
|
|
7
|
+
exports.generateNativeLibrary = generateNativeLibrary;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const NativeLibrary_js_1 = require("../../components/NativeLibrary.js");
|
|
10
|
+
const Builtins_js_1 = require("../declarations/Builtins.js");
|
|
11
|
+
const Parser_js_1 = require("../declarations/Parser.js");
|
|
12
|
+
const Platform_js_1 = require("../declarations/Platform.js");
|
|
13
|
+
const NativeLibraryHandle_js_1 = require("../library/NativeLibraryHandle.js");
|
|
14
|
+
const CText_js_1 = require("./CText.js");
|
|
15
|
+
const Preprocessor_js_1 = require("./Preprocessor.js");
|
|
16
|
+
const TypeScriptTypes_js_1 = require("./TypeScriptTypes.js");
|
|
17
|
+
/**
|
|
18
|
+
* The tokens that are not names of types or constants
|
|
19
|
+
*/
|
|
20
|
+
const KEYWORDS = new Set(['struct', 'union', 'enum', 'typedef', 'extern', 'static', 'const', 'volatile', 'signed', 'unsigned', 'inline']);
|
|
21
|
+
/**
|
|
22
|
+
* The names the generated file imports or uses: not names of interfaces
|
|
23
|
+
*/
|
|
24
|
+
const TAKEN = new Set(['Library', 'Native', 'NativeLibrary', 'NativeVariable', 'Pointer', 'Out', 'Buffer', 'ArrayBufferView', 'Array', 'Object', 'Function', 'String', 'Number', 'Date', 'Error', 'Map', 'Set', 'Promise']);
|
|
25
|
+
/**
|
|
26
|
+
* The options of the configuration of the component of a library: not names of properties
|
|
27
|
+
*/
|
|
28
|
+
const CONFIGURATION = ['path', 'search', 'keepLoaded', 'drainTimeout', 'threads'];
|
|
29
|
+
/**
|
|
30
|
+
* The names of the members of a NativeLibrary: a function named as one of them gets another property
|
|
31
|
+
*/
|
|
32
|
+
function memberNames() {
|
|
33
|
+
const names = new Set(CONFIGURATION);
|
|
34
|
+
for (let prototype = NativeLibrary_js_1.NativeLibrary.prototype; prototype && prototype !== Object.prototype; prototype = Object.getPrototypeOf(prototype)) {
|
|
35
|
+
for (const name of Object.getOwnPropertyNames(prototype))
|
|
36
|
+
names.add(name);
|
|
37
|
+
}
|
|
38
|
+
return names;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A path as the paths of the line markers are compared (case-insensitive on Windows)
|
|
42
|
+
* @param file
|
|
43
|
+
*/
|
|
44
|
+
function comparable(file) {
|
|
45
|
+
const resolved = node_path_1.default.resolve(file);
|
|
46
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Whether a file is inside a directory
|
|
50
|
+
* @param file
|
|
51
|
+
* @param directory
|
|
52
|
+
*/
|
|
53
|
+
function inside(file, directory) {
|
|
54
|
+
const relative = node_path_1.default.relative(directory, file);
|
|
55
|
+
return !!relative && !relative.startsWith('..') && !node_path_1.default.isAbsolute(relative);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Which files are the ones of the library: the header, the headers of its directory when it is not a directory of the
|
|
59
|
+
* system, the ones of the directories given
|
|
60
|
+
* @param header
|
|
61
|
+
* @param systemDirectories
|
|
62
|
+
* @param own
|
|
63
|
+
*/
|
|
64
|
+
function ownFiles(header, systemDirectories, own) {
|
|
65
|
+
const main = comparable(header);
|
|
66
|
+
const system = systemDirectories.map(comparable);
|
|
67
|
+
const directory = node_path_1.default.dirname(main);
|
|
68
|
+
const directories = [...system.includes(directory) ? [] : [directory], ...own.map(comparable)];
|
|
69
|
+
const known = new Map();
|
|
70
|
+
return (file) => {
|
|
71
|
+
if (!file || file.startsWith('<'))
|
|
72
|
+
return false;
|
|
73
|
+
let result = known.get(file);
|
|
74
|
+
if (result === undefined) {
|
|
75
|
+
const candidate = comparable(file);
|
|
76
|
+
result = candidate === main || directories.some((dir) => inside(candidate, dir) && !system.some((sys) => sys !== dir && inside(candidate, sys) && inside(sys, dir)));
|
|
77
|
+
known.set(file, result);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The names a declaration uses: of types (\`type x\`, \`struct x\`) and of constants
|
|
84
|
+
* @param tokens
|
|
85
|
+
*/
|
|
86
|
+
function referencesOf(tokens) {
|
|
87
|
+
const references = [];
|
|
88
|
+
tokens.forEach((token, index) => {
|
|
89
|
+
if (token.kind !== 'identifier' || KEYWORDS.has(token.text))
|
|
90
|
+
return;
|
|
91
|
+
const before = tokens[index - 1];
|
|
92
|
+
if (before?.kind === 'identifier' && (before.text === 'struct' || before.text === 'union' || before.text === 'enum'))
|
|
93
|
+
references.push(`${before.text} ${token.text}`);
|
|
94
|
+
else
|
|
95
|
+
references.push(`type ${token.text}`, `constant ${token.text}`);
|
|
96
|
+
});
|
|
97
|
+
return references;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A name in PascalCase (\`sqlite3\`: \`Sqlite3\`, \`vendor_sdk\`: \`VendorSdk\`)
|
|
101
|
+
* @param name
|
|
102
|
+
*/
|
|
103
|
+
function pascalCase(name) {
|
|
104
|
+
const words = name.split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
105
|
+
const joined = words.map((word) => word[0].toUpperCase() + word.slice(1)).join('') || 'Library';
|
|
106
|
+
return /^[0-9]/.test(joined) ? `Library${joined}` : joined;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The name \`@Library\` gets for a library given as a name or a file: \`libsqlite3.dylib\`, \`libsqlite3.so\`, \`sqlite3.dll\`
|
|
110
|
+
* give \`sqlite3\` (completed on each platform); a file with a version is kept as it is
|
|
111
|
+
* @param library
|
|
112
|
+
*/
|
|
113
|
+
function libraryName(library) {
|
|
114
|
+
const file = node_path_1.default.basename(library);
|
|
115
|
+
const plain = file.match(/^(?:lib)?(.+?)\.(?:dylib|so|dll)$/i);
|
|
116
|
+
if (plain && !/\.(?:dylib|so|dll)/i.test(plain[1]))
|
|
117
|
+
return plain[1];
|
|
118
|
+
return file;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* A text as the lines of a comment of TypeScript, wrapped at 120 characters
|
|
122
|
+
* @param text
|
|
123
|
+
*/
|
|
124
|
+
function commentLines(text) {
|
|
125
|
+
const lines = [];
|
|
126
|
+
let line = '//';
|
|
127
|
+
for (const word of text.split(' ')) {
|
|
128
|
+
if (line.length > 2 && line.length + 1 + word.length > 120) {
|
|
129
|
+
lines.push(line);
|
|
130
|
+
line = '//';
|
|
131
|
+
}
|
|
132
|
+
line += line.length > 2 ? ` ${word}` : word;
|
|
133
|
+
}
|
|
134
|
+
lines.push(line);
|
|
135
|
+
return lines;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Text put in a template literal of TypeScript
|
|
139
|
+
* @param text
|
|
140
|
+
*/
|
|
141
|
+
function templateText(text) {
|
|
142
|
+
return text.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The first line of the message of an error
|
|
146
|
+
* @param error
|
|
147
|
+
*/
|
|
148
|
+
function reasonOf(error) {
|
|
149
|
+
return (error instanceof Error ? error.message : String(error)).split('\n')[0].replace(/ \(line \d+, column \d+\)$/, '');
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Generate the class of a library from a header already run through the preprocessor (see generateNativeLibrary).
|
|
153
|
+
* @param preprocessed The output of the preprocessor, the header, the directories of the system.
|
|
154
|
+
* @param options The library, the class, the directories of the library.
|
|
155
|
+
* @param platform The platform the header was preprocessed for.
|
|
156
|
+
* @returns The code of the class and the report.
|
|
157
|
+
*/
|
|
158
|
+
async function generateFromPreprocessed(preprocessed, options = {}, platform = Platform_js_1.CURRENT_PLATFORM) {
|
|
159
|
+
const header = preprocessed.header;
|
|
160
|
+
const headerName = node_path_1.default.basename(header).replace(/\.[^.]+$/, '');
|
|
161
|
+
const source = new Parser_js_1.DeclarationParser(platform).readSource(preprocessed.text);
|
|
162
|
+
const items = source.items;
|
|
163
|
+
const isOwn = ownFiles(header, preprocessed.systemDirectories, options.own ?? []);
|
|
164
|
+
const builtins = (0, Builtins_js_1.builtinTypes)(platform);
|
|
165
|
+
const skipped = [];
|
|
166
|
+
const where = (item) => ({ ...item.file !== undefined ? { file: item.file } : {}, ...item.line !== undefined ? { line: item.line } : {} });
|
|
167
|
+
//The declarations of the library, and the ones of the system they use
|
|
168
|
+
const definers = new Map();
|
|
169
|
+
items.forEach((item, index) => {
|
|
170
|
+
if (item.error)
|
|
171
|
+
return;
|
|
172
|
+
for (const name of item.names)
|
|
173
|
+
definers.set(name, [...definers.get(name) ?? [], index]);
|
|
174
|
+
});
|
|
175
|
+
const selected = new Set();
|
|
176
|
+
const queue = [];
|
|
177
|
+
items.forEach((item, index) => {
|
|
178
|
+
if (!isOwn(item.file))
|
|
179
|
+
return;
|
|
180
|
+
if (item.error) {
|
|
181
|
+
skipped.push({ name: (0, CText_js_1.declarationText)((0, CText_js_1.prunedTokens)(item.tokens.slice(0, 16)), '').join(' ').slice(0, 80), ...where(item), reason: item.error });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!item.names.length)
|
|
185
|
+
return;
|
|
186
|
+
selected.add(index);
|
|
187
|
+
queue.push(index);
|
|
188
|
+
});
|
|
189
|
+
while (queue.length) {
|
|
190
|
+
const index = queue.pop();
|
|
191
|
+
const item = items[index];
|
|
192
|
+
for (const reference of referencesOf(item.tokens)) {
|
|
193
|
+
if (item.names.includes(reference))
|
|
194
|
+
continue;
|
|
195
|
+
if (reference.startsWith('type ') && builtins.has(reference.slice(5)))
|
|
196
|
+
continue;
|
|
197
|
+
//The definition seen at that point: the last one before it
|
|
198
|
+
const candidates = definers.get(reference) ?? [];
|
|
199
|
+
const definer = candidates.filter((candidate) => candidate < index).pop() ?? candidates[0];
|
|
200
|
+
if (definer === undefined || selected.has(definer))
|
|
201
|
+
continue;
|
|
202
|
+
//A typedef of the system redefining a type the package knows is not needed
|
|
203
|
+
if (!isOwn(items[definer].file) && items[definer].names.every((name) => name.startsWith('type ') && builtins.has(name.slice(5))))
|
|
204
|
+
continue;
|
|
205
|
+
selected.add(definer);
|
|
206
|
+
queue.push(definer);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//The macros of the library whose value is an integer, written with their values
|
|
210
|
+
const itemConstants = new Set(items.flatMap((item) => [...item.names]).filter((name) => name.startsWith('constant ')).map((name) => name.slice(9)));
|
|
211
|
+
const macroConstants = [...source.macros]
|
|
212
|
+
.filter(([name, origin]) => isOwn(origin.file) && source.declarations.constants.has(name) && !itemConstants.has(name))
|
|
213
|
+
.map(([name]) => [name, source.declarations.constants.get(name)]);
|
|
214
|
+
//The declarations written again, each one checked: read alone, after the ones before it
|
|
215
|
+
const checker = new Parser_js_1.DeclarationParser(platform);
|
|
216
|
+
const chunks = [];
|
|
217
|
+
for (const index of [...selected].sort((a, b) => a - b)) {
|
|
218
|
+
const item = items[index];
|
|
219
|
+
const body = (0, CText_js_1.declarationText)((0, CText_js_1.prunedTokens)(item.tokens));
|
|
220
|
+
const lines = item.pack !== undefined && item.names.some((name) => name.startsWith('struct ') || name.startsWith('union ') || name.startsWith('type '))
|
|
221
|
+
? [`#pragma pack(push, ${item.pack})`, ...body, '#pragma pack(pop)']
|
|
222
|
+
: body;
|
|
223
|
+
try {
|
|
224
|
+
checker.parse(lines.join('\n'));
|
|
225
|
+
chunks.push({ index: index, lines: lines });
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
selected.delete(index);
|
|
229
|
+
if (isOwn(item.file))
|
|
230
|
+
skipped.push({ name: item.names.join(', '), ...where(item), reason: `its declaration cannot be read again: ${reasonOf(error)}` });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const constantLines = macroConstants.map(([name, value]) => `#define ${name} ${value}`);
|
|
234
|
+
const declarations = [...chunks.flatMap((chunk) => chunk.lines), ...constantLines].join('\n');
|
|
235
|
+
//The functions and the variables of the library, as the declarations written are read (the types the package knows
|
|
236
|
+
//replace the ones of the system of the same name: DWORD, LPCWSTR)
|
|
237
|
+
const emitted = checker.declarations;
|
|
238
|
+
let functions = [];
|
|
239
|
+
let variables = [];
|
|
240
|
+
const prototypes = new Map();
|
|
241
|
+
for (const chunk of chunks) {
|
|
242
|
+
const item = items[chunk.index];
|
|
243
|
+
if (!isOwn(item.file))
|
|
244
|
+
continue;
|
|
245
|
+
for (const name of item.names) {
|
|
246
|
+
if (name.startsWith('function ')) {
|
|
247
|
+
const fn = emitted.functions.get(name.slice(9));
|
|
248
|
+
if (fn)
|
|
249
|
+
functions.push(fn);
|
|
250
|
+
prototypes.set(name.slice(9), (0, CText_js_1.declarationText)((0, CText_js_1.prunedTokens)(item.tokens), '').join(' '));
|
|
251
|
+
}
|
|
252
|
+
else if (name.startsWith('variable ')) {
|
|
253
|
+
const variable = emitted.variables.get(name.slice(9));
|
|
254
|
+
if (variable)
|
|
255
|
+
variables.push(variable);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const ownConstants = macroConstants.length + chunks.filter((chunk) => isOwn(items[chunk.index].file)).flatMap((chunk) => items[chunk.index].names).filter((name) => name.startsWith('constant ')).length;
|
|
260
|
+
//Checked against the library: the functions and the variables it cannot give left out
|
|
261
|
+
let libraryFile;
|
|
262
|
+
let unchecked;
|
|
263
|
+
const library = options.library ?? headerName;
|
|
264
|
+
if (options.check === false) {
|
|
265
|
+
unchecked = 'not asked';
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
let handle;
|
|
269
|
+
try {
|
|
270
|
+
handle = (0, NativeLibraryHandle_js_1.loadNativeLibrary)(library, declarations);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
unchecked = reasonOf(error).replace(/:$/, '').replace(/^The /, 'the ');
|
|
274
|
+
}
|
|
275
|
+
if (handle) {
|
|
276
|
+
libraryFile = handle.file;
|
|
277
|
+
const usable = (declared, bind) => declared.filter((declaration) => {
|
|
278
|
+
try {
|
|
279
|
+
bind(declaration.name);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
skipped.push({ name: declaration.name, reason: reasonOf(error) });
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
functions = usable(functions, (name) => handle.function(name));
|
|
288
|
+
variables = usable(variables, (name) => handle.variable(name));
|
|
289
|
+
await handle.close();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
//The interfaces of the structs and unions of the library
|
|
293
|
+
const records = [];
|
|
294
|
+
const names = new Map();
|
|
295
|
+
const used = new Set([...TAKEN]);
|
|
296
|
+
const className = options.className ?? pascalCase(headerName);
|
|
297
|
+
used.add(className);
|
|
298
|
+
const nameRecord = (record, preferred) => {
|
|
299
|
+
if (names.has(record))
|
|
300
|
+
return;
|
|
301
|
+
let name = preferred;
|
|
302
|
+
while (used.has(name))
|
|
303
|
+
name = `${name}_`;
|
|
304
|
+
used.add(name);
|
|
305
|
+
names.set(record, name);
|
|
306
|
+
records.push({ name: name, record: record });
|
|
307
|
+
};
|
|
308
|
+
for (const chunk of chunks) {
|
|
309
|
+
const item = items[chunk.index];
|
|
310
|
+
if (!isOwn(item.file))
|
|
311
|
+
continue;
|
|
312
|
+
for (const name of item.names) {
|
|
313
|
+
const type = name.startsWith('type ') ? emitted.types.get(name.slice(5)) : name.startsWith('struct ') || name.startsWith('union ') ? emitted.tags.get(name) : undefined;
|
|
314
|
+
if ((type?.kind === 'struct' || type?.kind === 'union') && type.members)
|
|
315
|
+
nameRecord(type, name.startsWith('type ') ? name.slice(5) : name.split(' ')[1]);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const types = new TypeScriptTypes_js_1.TypeScriptTypes(names);
|
|
319
|
+
//The class: a property by function and variable, named as it (another name when the component has a member of that name)
|
|
320
|
+
const reserved = memberNames();
|
|
321
|
+
const property = (name) => {
|
|
322
|
+
if (!reserved.has(name))
|
|
323
|
+
return { name: name, argument: '' };
|
|
324
|
+
let renamed = `${name}_`;
|
|
325
|
+
while (reserved.has(renamed))
|
|
326
|
+
renamed = `${renamed}_`;
|
|
327
|
+
return { name: renamed, argument: `'${name}'` };
|
|
328
|
+
};
|
|
329
|
+
const members = [];
|
|
330
|
+
for (const fn of functions) {
|
|
331
|
+
const prototype = prototypes.get(fn.name);
|
|
332
|
+
const { name, argument } = property(fn.name);
|
|
333
|
+
members.push([...prototype ? [` //${prototype}`] : [], ` @Native(${argument})`, ` public readonly ${name}: ${types.functionType(fn.type)}`].join('\n'));
|
|
334
|
+
}
|
|
335
|
+
for (const variable of variables) {
|
|
336
|
+
const { name, argument } = property(variable.name);
|
|
337
|
+
members.push([` @NativeVariable(${argument})`, ` public readonly ${name}: ${types.type(variable.type, 'received')}`].join('\n'));
|
|
338
|
+
}
|
|
339
|
+
const interfaces = records.map(({ name, record }) => [`export interface ${name} {`, ...types.members(record, ' '), '}'].join('\n'));
|
|
340
|
+
const body = [...interfaces, ...members].join('\n');
|
|
341
|
+
const imports = ['Library', 'Native', 'NativeLibrary', ...variables.length ? ['NativeVariable'] : [], ...body.includes('Out<') ? ['type Out'] : [], ...body.includes('Pointer') ? ['type Pointer'] : []];
|
|
342
|
+
const platformName = `${platform.os}-${platform.arch}`;
|
|
343
|
+
const code = [
|
|
344
|
+
...commentLines(`Generated from ${node_path_1.default.basename(header)} by the generator of @lakutata/ffi (lakutata ffi generate), for ${platformName}${libraryFile ? `; checked against ${libraryFile}` : ''}: ${functions.length} functions, ${variables.length} variables, ${ownConstants} constants.`),
|
|
345
|
+
...commentLines('The functions are synchronous, and their pointer parameters are inputs: where needed, mark the output parameters _Out_ in the declarations and the functions that block {async: true} (see the guide of @lakutata/ffi). Another build of the library may lack some of these functions: the component then refuses to start, naming the function.'),
|
|
346
|
+
`import {${imports.join(', ')}} from '@lakutata/ffi'`,
|
|
347
|
+
'',
|
|
348
|
+
...interfaces.flatMap((text) => [text, '']),
|
|
349
|
+
'@Library({',
|
|
350
|
+
` names: ${JSON.stringify(libraryName(library)).replace(/^"|"$/g, '\'')},`,
|
|
351
|
+
' declarations: `',
|
|
352
|
+
...templateText(declarations).split('\n').map((line) => ` ${line}`),
|
|
353
|
+
' `',
|
|
354
|
+
'})',
|
|
355
|
+
`export class ${className} extends NativeLibrary {`,
|
|
356
|
+
members.join('\n\n'),
|
|
357
|
+
'}',
|
|
358
|
+
''
|
|
359
|
+
].join('\n');
|
|
360
|
+
return {
|
|
361
|
+
className: className,
|
|
362
|
+
code: code,
|
|
363
|
+
declarations: declarations,
|
|
364
|
+
report: {
|
|
365
|
+
command: preprocessed.command,
|
|
366
|
+
platform: platformName,
|
|
367
|
+
...libraryFile ? { library: libraryFile } : {},
|
|
368
|
+
...unchecked ? { unchecked: unchecked } : {},
|
|
369
|
+
functions: functions.length,
|
|
370
|
+
variables: variables.length,
|
|
371
|
+
interfaces: records.length,
|
|
372
|
+
constants: ownConstants,
|
|
373
|
+
skipped: skipped
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Generate the class of a native library from its header: the header is run through the preprocessor of the C compiler,
|
|
379
|
+
* its declarations are read, the functions and the variables the library cannot give are left out, and the class gets a
|
|
380
|
+
* property by function (named as the function) with its TypeScript type.
|
|
381
|
+
* @param options The header, the library, the class, the compiler and its options.
|
|
382
|
+
* @returns The code of the class, and what was generated and left out.
|
|
383
|
+
* @example
|
|
384
|
+
* ```typescript
|
|
385
|
+
* import {writeFileSync} from 'node:fs'
|
|
386
|
+
* import {generateNativeLibrary} from '@lakutata/ffi/generator'
|
|
387
|
+
*
|
|
388
|
+
* const {code, report} = await generateNativeLibrary({header: 'vendor/include/vendor.h', library: 'vendor/lib/libvendor.so'})
|
|
389
|
+
* writeFileSync('src/native/Vendor.ts', code)
|
|
390
|
+
* for (const skipped of report.skipped) console.log(`${skipped.name}: ${skipped.reason}`)
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
async function generateNativeLibrary(options) {
|
|
394
|
+
return await generateFromPreprocessed((0, Preprocessor_js_1.preprocessHeader)(options), options);
|
|
395
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a header is run through the preprocessor
|
|
3
|
+
*/
|
|
4
|
+
export interface PreprocessOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The header
|
|
7
|
+
*/
|
|
8
|
+
readonly header: string;
|
|
9
|
+
/**
|
|
10
|
+
* The compiler whose preprocessor is run: cc, gcc, clang (their -E -dD), or cl, clang-cl (cl /P /d1PP)
|
|
11
|
+
*/
|
|
12
|
+
readonly compiler?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The directories of the headers it includes
|
|
15
|
+
*/
|
|
16
|
+
readonly include?: readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* The macros defined before it: NAME, or NAME=VALUE
|
|
19
|
+
*/
|
|
20
|
+
readonly define?: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A header run through the preprocessor
|
|
24
|
+
*/
|
|
25
|
+
export interface PreprocessedHeader {
|
|
26
|
+
/**
|
|
27
|
+
* The output of the preprocessor: the declarations, the #define kept, the line markers naming the files
|
|
28
|
+
*/
|
|
29
|
+
readonly text: string;
|
|
30
|
+
/**
|
|
31
|
+
* The header, as the line markers name it
|
|
32
|
+
*/
|
|
33
|
+
readonly header: string;
|
|
34
|
+
/**
|
|
35
|
+
* The directories of the headers of the system: their headers are not the ones of the library
|
|
36
|
+
*/
|
|
37
|
+
readonly systemDirectories: readonly string[];
|
|
38
|
+
/**
|
|
39
|
+
* The command run
|
|
40
|
+
*/
|
|
41
|
+
readonly command: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The C compiler whose preprocessor reads the headers by default: the one of the CC variable, else cl on Windows (run
|
|
45
|
+
* in the Developer Command Prompt of Visual Studio), cc elsewhere.
|
|
46
|
+
* @returns The command of the compiler.
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import {defaultCompiler} from '@lakutata/ffi/generator'
|
|
50
|
+
*
|
|
51
|
+
* console.log(defaultCompiler())
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function defaultCompiler(): string;
|
|
55
|
+
/**
|
|
56
|
+
* Run a header through the preprocessor of a compiler, keeping its #define and the line markers naming the files.
|
|
57
|
+
* @param options The header, the compiler, the directories and the macros.
|
|
58
|
+
* @returns The output, the header as the line markers name it, the directories of the system.
|
|
59
|
+
*/
|
|
60
|
+
export declare function preprocessHeader(options: PreprocessOptions): PreprocessedHeader;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.defaultCompiler = defaultCompiler;
|
|
7
|
+
exports.preprocessHeader = preprocessHeader;
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_os_1 = require("node:os");
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const NativeDeclarationException_js_1 = require("../../exceptions/NativeDeclarationException.js");
|
|
13
|
+
/**
|
|
14
|
+
* The C compiler whose preprocessor reads the headers by default: the one of the CC variable, else cl on Windows (run
|
|
15
|
+
* in the Developer Command Prompt of Visual Studio), cc elsewhere.
|
|
16
|
+
* @returns The command of the compiler.
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import {defaultCompiler} from '@lakutata/ffi/generator'
|
|
20
|
+
*
|
|
21
|
+
* console.log(defaultCompiler())
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function defaultCompiler() {
|
|
25
|
+
return process.env.CC?.trim() || (process.platform === 'win32' ? 'cl' : 'cc');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether a compiler takes the options of Microsoft (cl, clang-cl)
|
|
29
|
+
* @param compiler
|
|
30
|
+
*/
|
|
31
|
+
function microsoft(compiler) {
|
|
32
|
+
return /^(?:cl|clang-cl)(?:\.exe)?$/i.test(node_path_1.default.basename(compiler));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run a compiler: its output, its errors
|
|
36
|
+
* @param compiler
|
|
37
|
+
* @param args
|
|
38
|
+
*/
|
|
39
|
+
function run(compiler, args) {
|
|
40
|
+
const result = (0, node_child_process_1.spawnSync)(compiler, args, { encoding: 'latin1', maxBuffer: 1 << 30, windowsHide: true });
|
|
41
|
+
if (result.error?.code === 'ENOENT') {
|
|
42
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(microsoft(compiler)
|
|
43
|
+
? `The compiler ${compiler} is not found: run in the Developer Command Prompt (or PowerShell) of Visual Studio, or give another compiler`
|
|
44
|
+
: `The compiler ${compiler} is not found: install a C compiler (the Command Line Tools of Xcode, gcc, clang), or give another one`);
|
|
45
|
+
}
|
|
46
|
+
if (result.error)
|
|
47
|
+
throw result.error;
|
|
48
|
+
return { stdout: result.stdout, stderr: result.stderr, status: result.status };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The directories of the headers of the system of GCC and Clang, as -v lists them (without the ones given by -I)
|
|
52
|
+
* @param compiler
|
|
53
|
+
*/
|
|
54
|
+
function systemDirectoriesOf(compiler) {
|
|
55
|
+
const { stdout, stderr } = run(compiler, ['-E', '-v', '-x', 'c', node_os_1.devNull]);
|
|
56
|
+
const lines = `${stdout}\n${stderr}`.split(/\r?\n/);
|
|
57
|
+
const start = lines.findIndex((line) => line.startsWith('#include <...> search starts here'));
|
|
58
|
+
const end = lines.findIndex((line, index) => index > start && line.startsWith('End of search list'));
|
|
59
|
+
if (start < 0 || end < 0)
|
|
60
|
+
return [];
|
|
61
|
+
return lines.slice(start + 1, end).map((line) => node_path_1.default.resolve(line.replace(/\(framework directory\)$/, '').trim()));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Run a header through the preprocessor of a compiler, keeping its #define and the line markers naming the files.
|
|
65
|
+
* @param options The header, the compiler, the directories and the macros.
|
|
66
|
+
* @returns The output, the header as the line markers name it, the directories of the system.
|
|
67
|
+
*/
|
|
68
|
+
function preprocessHeader(options) {
|
|
69
|
+
const header = node_path_1.default.resolve(options.header);
|
|
70
|
+
const compiler = options.compiler ?? defaultCompiler();
|
|
71
|
+
const include = (options.include ?? []).map((directory) => node_path_1.default.resolve(directory));
|
|
72
|
+
const define = options.define ?? [];
|
|
73
|
+
if (microsoft(compiler)) {
|
|
74
|
+
//cl /P writes a file; /d1PP keeps the #define in it
|
|
75
|
+
const directory = (0, node_fs_1.mkdtempSync)(node_path_1.default.join((0, node_os_1.tmpdir)(), 'lakutata-ffi-preprocess-'));
|
|
76
|
+
const output = node_path_1.default.join(directory, 'header.i');
|
|
77
|
+
const args = ['/nologo', '/P', '/TC', '/d1PP', `/Fi${output}`, ...include.map((dir) => `/I${dir}`), ...define.map((macro) => `/D${macro}`), header];
|
|
78
|
+
try {
|
|
79
|
+
const { stderr, status } = run(compiler, args);
|
|
80
|
+
if (status !== 0)
|
|
81
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`The preprocessor failed (${compiler} ${args.join(' ')}): ${stderr.trim()}`);
|
|
82
|
+
const systemDirectories = (process.env.INCLUDE ?? '').split(';').filter(Boolean).map((dir) => node_path_1.default.resolve(dir));
|
|
83
|
+
return { text: (0, node_fs_1.readFileSync)(output, 'latin1'), header: header, systemDirectories: systemDirectories, command: [compiler, ...args.filter((arg) => !arg.startsWith('/Fi'))].join(' ') };
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
(0, node_fs_1.rmSync)(directory, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const args = ['-E', '-dD', '-x', 'c', ...include.map((dir) => `-I${dir}`), ...define.map((macro) => `-D${macro}`), header];
|
|
90
|
+
const { stdout, stderr, status } = run(compiler, args);
|
|
91
|
+
if (status !== 0)
|
|
92
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`The preprocessor failed (${compiler} ${args.join(' ')}): ${stderr.trim()}`);
|
|
93
|
+
return { text: stdout, header: header, systemDirectories: systemDirectoriesOf(compiler), command: [compiler, ...args].join(' ') };
|
|
94
|
+
}
|