crossbind 2.0.0-beta.60 → 2.0.0-beta.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/package.json +6 -1
- package/src/actions/buildDependencies.js +37 -9
- package/src/actions/buildExternal.js +9 -1
- package/src/actions/buildJs.js +1 -1
- package/src/actions/buildLib.js +27 -8
- package/src/actions/buildNativeCommand.js +84 -0
- package/src/actions/buildNode.js +190 -0
- package/src/actions/buildTypes.js +8 -2
- package/src/actions/buildWasiCommand.js +0 -4
- package/src/actions/buildWasm.js +92 -95
- package/src/actions/cloudCommands.js +133 -0
- package/src/actions/createImportedBridges.js +64 -0
- package/src/actions/createInterface.js +318 -65
- package/src/actions/createLib.js +60 -43
- package/src/actions/getAllBridges.js +1 -1
- package/src/actions/getData.js +4 -1
- package/src/actions/getLinkInputs.js +70 -0
- package/src/actions/isSourceNewer.js +2 -0
- package/src/actions/licenses.js +175 -17
- package/src/actions/prepareConanDependencies.js +51 -0
- package/src/actions/publishBinary.js +32 -0
- package/src/actions/run.js +124 -32
- package/src/actions/runnerCommands.js +421 -0
- package/src/actions/writeRuntimeEntry.js +69 -0
- package/src/assets/cmake/CMakeLists.txt +1 -1
- package/src/assets/cmake/command/CMakeLists.txt +21 -0
- package/src/assets/cmake/command/command.cpp +1 -0
- package/src/assets/cmake/dist.cmake +2 -1
- package/src/assets/configure/install.sh +22 -0
- package/src/assets/js-runtime/adapters/path-url.js +6 -3
- package/src/assets/js-runtime/adapters/unboundMessage.js +2 -0
- package/src/assets/js-runtime/adapters/worker-comlink.js +87 -56
- package/src/assets/js-runtime/core.js +11 -0
- package/src/assets/node-import-hooks.mjs +61 -0
- package/src/assets/toolchain-digests.json +27 -13
- package/src/assets/wasi-runtime/stubs.c +29 -0
- package/src/bin.js +290 -37
- package/src/index.js +6 -1
- package/src/integration/bindHeaderImports.js +18 -0
- package/src/integration/getCrossbindScript.js +53 -19
- package/src/integration/getDependFilePath.js +18 -12
- package/src/integration/resolveNativeImport.js +14 -0
- package/src/node/dev.mjs +19 -0
- package/src/runner/blobs.js +135 -0
- package/src/runner/files.js +193 -0
- package/src/runner/server.js +320 -0
- package/src/state/attachConanDependencies.js +56 -0
- package/src/state/index.js +31 -20
- package/src/state/loadConfig.js +36 -24
- package/src/state/refreshBuiltDependencies.js +20 -0
- package/src/state/refreshConanDependencies.js +17 -0
- package/src/utils/androidToolchain.js +4 -0
- package/src/utils/appRustCrates.js +17 -1
- package/src/utils/appSources.js +112 -0
- package/src/utils/archiveFlags.js +12 -0
- package/src/utils/boundHeaders.js +79 -0
- package/src/utils/bridgeExtras.js +486 -0
- package/src/utils/bridgeRuntimeFixes.js +48 -0
- package/src/utils/bundledLicenses.js +26 -0
- package/src/utils/cargoImport.js +44 -0
- package/src/utils/cargoTarget.js +23 -3
- package/src/utils/cloudAccount.js +55 -0
- package/src/utils/conanDependencies.js +66 -0
- package/src/utils/conanImport.js +58 -0
- package/src/utils/conanInstall.js +154 -0
- package/src/utils/conanProfile.js +203 -0
- package/src/utils/conanStage.js +292 -0
- package/src/utils/copyToDistSource.js +13 -0
- package/src/utils/cppDts.js +134 -55
- package/src/utils/darwinToolchain.js +11 -0
- package/src/utils/dirLock.js +137 -28
- package/src/utils/downloadAndExtractFile.js +34 -7
- package/src/utils/embindArgumentGuards.js +84 -0
- package/src/utils/embindCallArguments.js +19 -0
- package/src/utils/embindJspiQueue.js +25 -0
- package/src/utils/embindRsFingerprint.js +26 -0
- package/src/utils/execContainer.js +49 -0
- package/src/utils/headerImports.js +56 -0
- package/src/utils/hostPlatform.js +21 -0
- package/src/utils/inputStamp.js +23 -0
- package/src/utils/iosToolchain.js +6 -0
- package/src/utils/isSourceCmakePackage.js +10 -0
- package/src/utils/licenseReport.js +11 -2
- package/src/utils/linkLayout.js +8 -1
- package/src/utils/linuxToolchain.js +24 -0
- package/src/utils/makeTreeWritable.js +12 -0
- package/src/utils/nativeImports.js +23 -0
- package/src/utils/nativeSourceStamp.js +36 -0
- package/src/utils/nodeDevBuild.js +92 -0
- package/src/utils/packageLicense.js +101 -0
- package/src/utils/pthreadMainScript.js +14 -0
- package/src/utils/pullDockerImage.js +10 -5
- package/src/utils/relocatePrebuilt.js +119 -0
- package/src/utils/remoteClient.js +127 -0
- package/src/utils/remoteOutputs.js +39 -0
- package/src/utils/remoteRunner.js +97 -0
- package/src/utils/remoteUpload.js +134 -0
- package/src/utils/replaceFile.js +8 -0
- package/src/utils/resolveCompanionPackage.js +51 -0
- package/src/utils/resolveEmbindNapi.js +22 -0
- package/src/utils/resolveEmbindRust.js +17 -52
- package/src/utils/runCargo.js +51 -53
- package/src/utils/runConan.js +257 -0
- package/src/utils/runtimeEntries.js +56 -0
- package/src/utils/rustBridgeGen.js +903 -253
- package/src/utils/rustMt.js +4 -0
- package/src/utils/scopedEmbind.js +22 -0
- package/src/utils/selectRunner.js +81 -0
- package/src/utils/sourceFingerprint.js +4 -3
- package/src/utils/swigInterface.js +86 -6
- package/src/utils/systemKeys.js +43 -3
- package/src/utils/targets.js +104 -3
- package/src/utils/toolchainNotices.js +5 -0
- package/src/utils/wasiToolchain.js +14 -1
- package/src/utils/windowsToolchain.js +18 -0
- package/vitest.config.js +11 -0
- package/src/utils/cppFieldBindings.js +0 -23
|
@@ -2,23 +2,59 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import upath from 'upath';
|
|
4
4
|
import state, { saveCache } from '../state/index.js';
|
|
5
|
-
import
|
|
5
|
+
import refreshConanDependencies from '../state/refreshConanDependencies.js';
|
|
6
|
+
import refreshBuiltDependencies from '../state/refreshBuiltDependencies.js';
|
|
7
|
+
import { conanInputsOf } from '../utils/conanDependencies.js';
|
|
8
|
+
import { getContentHash, getFileHash } from '../utils/hash.js';
|
|
6
9
|
import guardAsyncBindings from '../utils/bridgeAsyncGuard.js';
|
|
7
|
-
import
|
|
8
|
-
import { injectFieldBindings } from '../utils/cppFieldBindings.js';
|
|
10
|
+
import fixBridgeRuntime from '../utils/bridgeRuntimeFixes.js';
|
|
9
11
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
interfaceExtras, bridgeExtras, hasPointerRuntime, withPointerRuntimeAnchor, withoutBridgeExtras, POINTER_RUNTIME_ANCHOR,
|
|
13
|
+
} from '../utils/bridgeExtras.js';
|
|
14
|
+
import resolveNativeImport from '../integration/resolveNativeImport.js';
|
|
15
|
+
import { writeHeaderDts, writeConanImportDts } from '../utils/cppDts.js';
|
|
16
|
+
import { ALL_NAMES, findHeaderImportsIn } from '../utils/headerImports.js';
|
|
17
|
+
import {
|
|
18
|
+
buildInterfaceContent, completingIncludes, findHeaderPrelude, findIgnoredDeclarations, findSwigInlineIncludes, findSwigPreamble,
|
|
19
|
+
indexTypeDefinitions, inlineIncludes, interfaceIncludes, interfaceToRetryWithoutMacros, interfaceWithAllFunctions, parseMacroDump, referencedTypeHeaders,
|
|
20
|
+
selectSwigMacros,
|
|
12
21
|
} from '../utils/swigInterface.js';
|
|
13
22
|
import writeIfChanged from '../utils/writeIfChanged.js';
|
|
23
|
+
import { withDirLockSync } from '../utils/dirLock.js';
|
|
24
|
+
import { imageRoleFor } from '../utils/pullDockerImage.js';
|
|
25
|
+
import { conanImportOfHeader } from '../utils/conanImport.js';
|
|
14
26
|
import run, { cxxPreprocessorFor } from './run.js';
|
|
27
|
+
import isSourceCmakePackage from '../utils/isSourceCmakePackage.js';
|
|
15
28
|
|
|
16
29
|
// Part of every interface hash, so interfaces cached by an older generator are rebuilt.
|
|
17
|
-
const INTERFACE_FORMAT = 'swig-macros-
|
|
30
|
+
const INTERFACE_FORMAT = 'swig-macros-3';
|
|
31
|
+
// Part of every bridge hash: each bridge carries the SWIG fork's runtime and all bridges of a module must share it, so
|
|
32
|
+
// bridges from a fork before field bindings and constants are rebuilt.
|
|
33
|
+
const BRIDGE_FORMAT = 'swig-extras-1';
|
|
18
34
|
const predefinedMacros = new Map();
|
|
19
35
|
const typeDefinitions = new Map();
|
|
20
36
|
|
|
21
|
-
|
|
37
|
+
// Each process keeps the cache it loaded, so an interface or bridge another one has rebuilt since must not pass for
|
|
38
|
+
// this one's: beside each file is the hash of what it was made from, blank while the file is written. A file without one
|
|
39
|
+
// comes from an older crossbind or a cache CI restores, and its cache record holds.
|
|
40
|
+
const keyOf = (file) => `${file}.key`;
|
|
41
|
+
const isMadeFrom = (file, hash) => !fs.existsSync(keyOf(file)) || fs.readFileSync(keyOf(file), 'utf8') === getContentHash(hash);
|
|
42
|
+
|
|
43
|
+
// Metro binds headers in worker processes, so two of them can generate one bridge at once. The headers a header's types
|
|
44
|
+
// come from are bound in its turn.
|
|
45
|
+
export default function createBridgeFile(headerOrModuleFilePath, target = state.targets.find((t) => t.platform === 'wasm'), options = {}) {
|
|
46
|
+
if (options.withDependencies === false) return bindHeader(headerOrModuleFilePath, target, options);
|
|
47
|
+
return withDirLockSync(`${state.config.paths.build}/bridge.lock`, () => bindHeader(headerOrModuleFilePath, target, options), {
|
|
48
|
+
pollMs: 50,
|
|
49
|
+
heldBy: 'another process is binding a header',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function bindHeader(headerOrModuleFilePath, target, { withDependencies = true, wholeHeaders = [] } = {}) {
|
|
54
|
+
// A header can include a Conan package's headers that a build staged after this process loaded, or belong to a
|
|
55
|
+
// dependency built since.
|
|
56
|
+
refreshConanDependencies(state.config);
|
|
57
|
+
refreshBuiltDependencies(state.config);
|
|
22
58
|
const interfaceFilePath = upath.resolve(headerOrModuleFilePath);
|
|
23
59
|
if (!fs.existsSync(`${state.config.paths.build}/interface`)) {
|
|
24
60
|
fs.mkdirSync(`${state.config.paths.build}/interface`, { recursive: true });
|
|
@@ -29,33 +65,42 @@ export default function createBridgeFile(headerOrModuleFilePath, target = state.
|
|
|
29
65
|
// The header may live outside paths.native (e.g. a package-subpath import like
|
|
30
66
|
// '@scope/pkg/native/x.h'), so its own directory must reach swig's include list.
|
|
31
67
|
const sourceDir = upath.dirname(interfaceFilePath);
|
|
32
|
-
const
|
|
33
|
-
const
|
|
68
|
+
const allNames = wholeHeaders.some((file) => upath.resolve(file) === interfaceFilePath);
|
|
69
|
+
const names = bindingNames(interfaceFilePath, target, allNames);
|
|
70
|
+
if (withDependencies) warnUnimported(interfaceFilePath, names.functions);
|
|
71
|
+
const interfaceFile = createInterfaceFile(interfaceFilePath, target, sourceDir, names);
|
|
72
|
+
const { headerPath } = includeLocation(interfaceFilePath, target);
|
|
73
|
+
const inlined = findSwigInlineIncludes(interfaceFilePath, [state.config, ...state.config.allDependencies], headerPath);
|
|
74
|
+
const swigView = inlined.length ? writeSwigView(interfaceFilePath, headerPath, inlined) : null;
|
|
75
|
+
const sourceHash = [getFileHash(interfaceFilePath), ...conanInputsHash()].join('\n');
|
|
76
|
+
// The names imported by name decide what the bridge adds to SWIG's output, so another one makes another bridge.
|
|
77
|
+
const bridgeSourceHash = names.named.length ? `${sourceHash}\nnamed:${names.named.join(',')}` : sourceHash;
|
|
78
|
+
const extras = { named: names.named, headerText: () => fs.readFileSync(swigView ? upath.join(swigView.dir, headerPath) : interfaceFilePath, 'utf8') };
|
|
79
|
+
const bridgeFile = createBridgeFileFromInterfaceFile(interfaceFile, target, sourceDir, bridgeSourceHash, swigView, extras);
|
|
34
80
|
const moduleRegex = new RegExp(`.(${state.config.ext.module.join('|')})$`);
|
|
35
|
-
if (bridgeFile && !moduleRegex.test(interfaceFilePath)) {
|
|
36
|
-
// SWIG's -embind backend emits no member variables; inject .property lines for the
|
|
37
|
-
// header's public value fields. Idempotent, so the cached-bridge path is safe too.
|
|
38
|
-
const bridgeContent = fs.readFileSync(bridgeFile, 'utf8');
|
|
39
|
-
const injected = injectFieldBindings(bridgeContent, parseCppSurface(fs.readFileSync(interfaceFilePath, 'utf8'), () => {}));
|
|
40
|
-
if (injected !== bridgeContent) fs.writeFileSync(bridgeFile, injected);
|
|
41
|
-
}
|
|
42
81
|
if (bridgeFile) {
|
|
43
82
|
// Records which header this bridge came from, so directory-driven consumers
|
|
44
83
|
// (getAllBridges) can prune bridges whose header no longer exists.
|
|
45
84
|
writeIfChanged(`${bridgeFile}.source`, `${interfaceFilePath}\n`);
|
|
46
|
-
|
|
85
|
+
const dtsOptions = {
|
|
47
86
|
headerFile: interfaceFilePath,
|
|
48
87
|
exportsFile: `${bridgeFile}.exports.json`,
|
|
49
88
|
projectPath: state.config.paths.project,
|
|
50
89
|
cacheDir: state.config.paths.cache,
|
|
51
90
|
dtsMode: state.config.dts,
|
|
52
|
-
}
|
|
91
|
+
};
|
|
92
|
+
writeHeaderDts(dtsOptions);
|
|
93
|
+
if (conanImportOfHeader(interfaceFilePath, state.config.paths.cache)) {
|
|
94
|
+
dtsOptions.declarationsFile = createConanDeclarations(interfaceFile, target, sourceDir, sourceHash, swigView);
|
|
95
|
+
}
|
|
96
|
+
writeConanImportDts(dtsOptions);
|
|
53
97
|
}
|
|
54
98
|
if (bridgeFile && withDependencies && !moduleRegex.test(interfaceFilePath)) {
|
|
55
99
|
// A header's own bindings need the types its declarations use registered, which only their headers' bridges do.
|
|
100
|
+
// Such a bridge replaces the header's own, so a header bound whole keeps every constant here too.
|
|
56
101
|
const dependencyBridges = dependencyHeadersOf(interfaceFilePath, target).map((header) => {
|
|
57
102
|
try {
|
|
58
|
-
return createBridgeFile(header, target, { withDependencies: false });
|
|
103
|
+
return createBridgeFile(header, target, { withDependencies: false, wholeHeaders });
|
|
59
104
|
} catch (e) {
|
|
60
105
|
console.warn(`crossbind: ${upath.basename(interfaceFilePath)} uses types from ${upath.basename(header)}, whose bindings failed (${e.message})`);
|
|
61
106
|
return null;
|
|
@@ -66,7 +111,36 @@ export default function createBridgeFile(headerOrModuleFilePath, target = state.
|
|
|
66
111
|
return bridgeFile;
|
|
67
112
|
}
|
|
68
113
|
|
|
69
|
-
|
|
114
|
+
// The Conan packages a header's includes may reach, for the interface and bridge caches: they are
|
|
115
|
+
// staged after a header may already be bound, and restaged in place for another version.
|
|
116
|
+
function conanInputsHash() {
|
|
117
|
+
const conan = conanInputsOf(state.config);
|
|
118
|
+
return conan ? [`conan:${getContentHash(JSON.stringify(conan))}`] : [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// A header binds the constants the app imports from it, and so the free functions of a dependency's header: the project's
|
|
122
|
+
// own headers and .i modules bind every function.
|
|
123
|
+
function bindingNames(headerOrModuleFilePath, target, allNames) {
|
|
124
|
+
if (new RegExp(`.(${state.config.ext.module.join('|')})$`).test(headerOrModuleFilePath)) return { constants: [], functions: ALL_NAMES, named: [] };
|
|
125
|
+
const { names, takesModule, named } = headerUse(headerOrModuleFilePath, target);
|
|
126
|
+
if (allNames) return { constants: ALL_NAMES, functions: ALL_NAMES, named };
|
|
127
|
+
// A package's own .i says what the header binds.
|
|
128
|
+
const shipsInterface = fs.existsSync(headerOrModuleFilePath.replace(/\.[^./]+$/, '.i'));
|
|
129
|
+
const bindsEveryFunction = takesModule || shipsInterface || !includeLocation(headerOrModuleFilePath, target).isDependency;
|
|
130
|
+
return { constants: names, functions: bindsEveryFunction ? ALL_NAMES : names, named };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const warnedUnimported = new Set();
|
|
134
|
+
|
|
135
|
+
// A bundler transforms the header because something imports it, but no source the scan reads names it: a package in
|
|
136
|
+
// node_modules, say.
|
|
137
|
+
function warnUnimported(header, functions) {
|
|
138
|
+
if (functions === ALL_NAMES || functions.length || warnedUnimported.has(header)) return;
|
|
139
|
+
warnedUnimported.add(header);
|
|
140
|
+
console.warn(`crossbind: ${upath.basename(header)} binds none of its functions, since no source of the app imports a name from it. Import the functions you call by name, or the whole header with import * as.`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function createInterfaceFile(headerOrModuleFilePath, target, sourceDir, { constants, functions, named = [] }) {
|
|
70
144
|
if (!headerOrModuleFilePath) {
|
|
71
145
|
return null;
|
|
72
146
|
}
|
|
@@ -76,62 +150,87 @@ function createInterfaceFile(headerOrModuleFilePath, target, sourceDir) {
|
|
|
76
150
|
const packages = [state.config, ...state.config.allDependencies];
|
|
77
151
|
const prelude = isModule ? [] : findHeaderPrelude(headerOrModuleFilePath, packages, headerPath);
|
|
78
152
|
const ignored = isModule ? [] : findIgnoredDeclarations(headerOrModuleFilePath, packages, headerPath);
|
|
153
|
+
const preamble = isModule ? [] : findSwigPreamble(headerOrModuleFilePath, packages, headerPath);
|
|
79
154
|
const completing = includeRoot ? findCompletingIncludes(headerOrModuleFilePath, includeRoot, headerPath) : [];
|
|
80
|
-
|
|
81
|
-
|
|
155
|
+
const filePathWithoutExt = headerOrModuleFilePath.match(/^(.*)\..+?$/)?.[1];
|
|
156
|
+
const interfaceFile = !isModule && filePathWithoutExt ? `${filePathWithoutExt}.i` : null;
|
|
157
|
+
// A prelude or ignored-declaration change in the owning package, a completed class moving to another header, an
|
|
158
|
+
// interface the package ships beside the header changing or going away, another imported name, or Conan packages
|
|
159
|
+
// its includes may reach being staged or moving to another version must regenerate the interface as well.
|
|
82
160
|
const fileHash = [
|
|
83
161
|
INTERFACE_FORMAT, getFileHash(headerOrModuleFilePath), ...prelude,
|
|
84
162
|
...(ignored.length ? [`ignored:${ignored.join(',')}`] : []), ...(completing.length ? [`completing:${completing.join(',')}`] : []),
|
|
163
|
+
...(preamble.length ? [`preamble:${preamble.join('\n')}`] : []),
|
|
164
|
+
...(interfaceFile && fs.existsSync(interfaceFile) ? [`shipped:${getFileHash(interfaceFile)}`] : []),
|
|
165
|
+
...(constants.length ? [`constants:${constants === ALL_NAMES ? ALL_NAMES : constants.join(',')}`] : []),
|
|
166
|
+
...(functions === ALL_NAMES ? [] : [`functions:${functions.join(',')}`]),
|
|
167
|
+
...(named.length ? [`named:${named.join(',')}`] : []),
|
|
168
|
+
...conanInputsHash(),
|
|
85
169
|
].join('\n');
|
|
86
170
|
const cachedInterface = state.cache.interfaces[headerOrModuleFilePath];
|
|
87
|
-
if (state.cache.hashes[headerOrModuleFilePath] === fileHash && cachedInterface && fs.existsSync(cachedInterface)
|
|
171
|
+
if (state.cache.hashes[headerOrModuleFilePath] === fileHash && cachedInterface && fs.existsSync(cachedInterface)
|
|
172
|
+
&& isMadeFrom(cachedInterface, fileHash) && !sharedInterface(headerOrModuleFilePath, cachedInterface)) {
|
|
88
173
|
return cachedInterface;
|
|
89
174
|
}
|
|
90
175
|
|
|
91
176
|
if (isModule) {
|
|
92
177
|
const newPath = `${state.config.paths.build}/interface/${headerOrModuleFilePath.split('/').pop()}`;
|
|
93
|
-
fs.copyFileSync(headerOrModuleFilePath, newPath);
|
|
94
|
-
state.cache.interfaces[headerOrModuleFilePath] = newPath;
|
|
95
|
-
state.cache.hashes[headerOrModuleFilePath] = fileHash;
|
|
96
|
-
saveCache();
|
|
97
|
-
return newPath;
|
|
178
|
+
return recordInterface(headerOrModuleFilePath, newPath, fileHash, () => fs.copyFileSync(headerOrModuleFilePath, newPath));
|
|
98
179
|
}
|
|
99
180
|
|
|
100
|
-
|
|
101
|
-
if (!temp || temp.length < 2) return null;
|
|
102
|
-
|
|
103
|
-
const filePathWithoutExt = temp[1];
|
|
104
|
-
const interfaceFile = `${filePathWithoutExt}.i`;
|
|
181
|
+
if (!filePathWithoutExt) return null;
|
|
105
182
|
|
|
106
183
|
if (fs.existsSync(interfaceFile)) {
|
|
107
184
|
const newPath = `${state.config.paths.build}/interface/${interfaceFile.split('/').at(-1)}`;
|
|
108
|
-
fs.copyFileSync(interfaceFile, newPath);
|
|
109
|
-
state.cache.interfaces[headerOrModuleFilePath] = newPath;
|
|
110
|
-
state.cache.hashes[headerOrModuleFilePath] = fileHash;
|
|
111
|
-
saveCache();
|
|
112
|
-
return newPath;
|
|
185
|
+
return recordInterface(headerOrModuleFilePath, newPath, fileHash, () => fs.copyFileSync(interfaceFile, newPath));
|
|
113
186
|
}
|
|
114
187
|
|
|
115
|
-
const fileName = filePathWithoutExt.split('/').at(-1);
|
|
188
|
+
const fileName = interfaceName(headerOrModuleFilePath, filePathWithoutExt.split('/').at(-1));
|
|
116
189
|
|
|
190
|
+
const macros = collectSwigMacros(headerOrModuleFilePath, interfaceIncludes(headerPath, prelude), fileName, target, sourceDir, constants);
|
|
191
|
+
const extras = interfaceExtras(named, macros.table);
|
|
117
192
|
const content = buildInterfaceContent({
|
|
118
193
|
moduleName: fileName.toUpperCase(),
|
|
119
194
|
headerPath,
|
|
120
195
|
prelude,
|
|
121
196
|
completing,
|
|
122
|
-
swigMacros:
|
|
197
|
+
swigMacros: macros.lines,
|
|
123
198
|
ignored,
|
|
199
|
+
constants,
|
|
200
|
+
preamble,
|
|
201
|
+
functions: functions === ALL_NAMES ? ALL_NAMES : [...functions, ...extras.functions],
|
|
202
|
+
extras: extras.lines,
|
|
124
203
|
});
|
|
125
204
|
const outputFilePath = `${state.config.paths.build}/interface/${fileName}.i`;
|
|
126
|
-
fs.writeFileSync(outputFilePath, content);
|
|
205
|
+
return recordInterface(headerOrModuleFilePath, outputFilePath, fileHash, () => fs.writeFileSync(outputFilePath, content));
|
|
206
|
+
}
|
|
127
207
|
|
|
128
|
-
|
|
129
|
-
|
|
208
|
+
// The key is blank while the file is written, so a process that dies in between leaves no file passing for another hash.
|
|
209
|
+
function recordInterface(headerFile, interfaceFile, fileHash, write) {
|
|
210
|
+
fs.writeFileSync(keyOf(interfaceFile), '');
|
|
211
|
+
write();
|
|
212
|
+
fs.writeFileSync(keyOf(interfaceFile), getContentHash(fileHash));
|
|
213
|
+
state.cache.interfaces[headerFile] = interfaceFile;
|
|
214
|
+
state.cache.hashes[headerFile] = fileHash;
|
|
130
215
|
saveCache();
|
|
216
|
+
return interfaceFile;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const interfacePath = (name) => `${state.config.paths.build}/interface/${name}.i`;
|
|
220
|
+
// Each platform build of a package ships its headers under dist/prebuilt/<target>/include: one header for the interface.
|
|
221
|
+
const headerIdentity = (file) => file.match(/\/dist\/prebuilt\/[^/]+\/include\/(.+)$/)?.[1] ?? file;
|
|
222
|
+
const sharedInterface = (headerFile, interfaceFile) => Object.entries(state.cache.interfaces)
|
|
223
|
+
.some(([other, file]) => headerIdentity(other) !== headerIdentity(headerFile) && file === interfaceFile);
|
|
131
224
|
|
|
132
|
-
|
|
225
|
+
// An interface is named after its header's file name. A second header with that name (tiffio.h and tiffio.hxx, or one
|
|
226
|
+
// name in two directories) takes its extension, then a hash of its path, so neither replaces the other's bridge.
|
|
227
|
+
function interfaceName(headerFile, base) {
|
|
228
|
+
const candidates = [base, `${base}_${upath.extname(headerFile).slice(1)}`, `${base}_${getContentHash(headerFile).slice(0, 8)}`];
|
|
229
|
+
return candidates.find((name) => !sharedInterface(headerFile, interfacePath(name))) ?? candidates.at(-1);
|
|
133
230
|
}
|
|
134
231
|
|
|
232
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
233
|
+
|
|
135
234
|
// A header is included by its path under a project header directory or a dependency's include directory; one found only
|
|
136
235
|
// through its own directory has no include root to search.
|
|
137
236
|
function includeLocation(headerFile, target) {
|
|
@@ -139,11 +238,12 @@ function includeLocation(headerFile, target) {
|
|
|
139
238
|
if (projectRoot) return { includeRoot: projectRoot, headerPath: headerFile.substr(projectRoot.length + 1) };
|
|
140
239
|
const dependencyRoots = (state.config.dependencyParameters?.getCmakeDependsPathAndName(target).pathsOfCmakeDepends || [])
|
|
141
240
|
.filter((d) => d.startsWith(state.config.paths.base));
|
|
142
|
-
const match = dependencyRoots.map((p) => headerFile.match(new RegExp(`^(${p}/.*?/include)/(.*?)$`, 'i'))).find(Boolean);
|
|
143
|
-
return match ? { includeRoot: match[1], headerPath: match[2] } : { includeRoot: null, headerPath: headerFile.split('/').at(-1) };
|
|
241
|
+
const match = dependencyRoots.map((p) => headerFile.match(new RegExp(`^(${escapeRegExp(p)}/.*?/include)/(.*?)$`, 'i'))).find(Boolean);
|
|
242
|
+
return match ? { includeRoot: match[1], headerPath: match[2], isDependency: true } : { includeRoot: null, headerPath: headerFile.split('/').at(-1) };
|
|
144
243
|
}
|
|
145
244
|
|
|
146
|
-
// The headers under an include root are indexed once per build.
|
|
245
|
+
// The headers under an include root are indexed once per build. A copy of a header is that header, and the copy nearest
|
|
246
|
+
// the root stands for it (libpng installs its headers in include/ and include/libpng16/).
|
|
147
247
|
function definitionsUnder(includeRoot) {
|
|
148
248
|
if (!typeDefinitions.has(includeRoot)) {
|
|
149
249
|
const extensions = new RegExp(`\\.(${state.config.ext.header.join('|')})$`, 'i');
|
|
@@ -152,48 +252,120 @@ function definitionsUnder(includeRoot) {
|
|
|
152
252
|
.map((entry) => upath.relative(includeRoot, upath.join(entry.parentPath, entry.name)))
|
|
153
253
|
.sort()
|
|
154
254
|
.map((path) => ({ path, text: fs.readFileSync(upath.join(includeRoot, path), 'utf8') }));
|
|
155
|
-
|
|
255
|
+
const depth = (path) => path.split('/').length;
|
|
256
|
+
const nearest = new Map();
|
|
257
|
+
files.forEach(({ path, text }) => {
|
|
258
|
+
if (!nearest.has(text) || depth(path) < depth(nearest.get(text))) nearest.set(text, path);
|
|
259
|
+
});
|
|
260
|
+
typeDefinitions.set(includeRoot, {
|
|
261
|
+
definitions: indexTypeDefinitions(files.filter(({ path, text }) => nearest.get(text) === path)),
|
|
262
|
+
copyOf: new Map(files.map(({ path, text }) => [path, nearest.get(text)])),
|
|
263
|
+
});
|
|
156
264
|
}
|
|
157
265
|
return typeDefinitions.get(includeRoot);
|
|
158
266
|
}
|
|
159
267
|
|
|
268
|
+
function lookupContext(includeRoot, headerPath, headerText) {
|
|
269
|
+
const { definitions, copyOf } = definitionsUnder(includeRoot);
|
|
270
|
+
return { headerText, headerPath: copyOf.get(headerPath) ?? headerPath, definitions };
|
|
271
|
+
}
|
|
272
|
+
|
|
160
273
|
function findCompletingIncludes(headerFile, includeRoot, headerPath) {
|
|
161
274
|
const headerText = fs.readFileSync(headerFile, 'utf8');
|
|
162
275
|
if (!headerText.includes('unique_ptr')) return [];
|
|
163
|
-
return completingIncludes(
|
|
276
|
+
return completingIncludes(lookupContext(includeRoot, headerPath, headerText));
|
|
164
277
|
}
|
|
165
278
|
|
|
166
279
|
function dependencyHeadersOf(headerFile, target) {
|
|
167
280
|
const { includeRoot, headerPath } = includeLocation(headerFile, target);
|
|
168
281
|
if (!includeRoot) return [];
|
|
169
282
|
const headerText = fs.readFileSync(headerFile, 'utf8');
|
|
170
|
-
return referencedTypeHeaders(
|
|
283
|
+
return referencedTypeHeaders(lookupContext(includeRoot, headerPath, headerText))
|
|
171
284
|
.map((header) => upath.join(includeRoot, header));
|
|
172
285
|
}
|
|
173
286
|
|
|
287
|
+
// The proxy module's own exports, never names of the header.
|
|
288
|
+
const PROXY_NAMES = new Set(['initNative', 'AllSymbols']);
|
|
289
|
+
const C_IDENTIFIER = /^[A-Za-z_]\w*$/;
|
|
290
|
+
|
|
291
|
+
// A package header resolves under the target's prebuilt directory, and the bundler may resolve it for another target.
|
|
292
|
+
const targetNeutral = (file) => file?.replace(/\/dist\/prebuilt\/[^/]+\/include\//, '/dist/prebuilt/*/include/');
|
|
293
|
+
|
|
294
|
+
function realPath(file) {
|
|
295
|
+
if (!file) return null;
|
|
296
|
+
try {
|
|
297
|
+
return upath.normalize(fs.realpathSync(file));
|
|
298
|
+
} catch (e) {
|
|
299
|
+
return upath.resolve(file);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// The names the app's own sources import from this header, or every name for `import * as` and the other whole-module
|
|
304
|
+
// imports. The app also takes the module, and reaches its functions by name, when it imports AllSymbols or when its only
|
|
305
|
+
// imports of the header take initNative alone.
|
|
306
|
+
// `named` keeps the names imported by name even when another import takes the whole module: macros, variadic functions
|
|
307
|
+
// and mutable globals bind only for those.
|
|
308
|
+
function headerUse(headerFile, target) {
|
|
309
|
+
const projectDir = state.config.paths.project;
|
|
310
|
+
if (!projectDir || !fs.existsSync(projectDir)) return { names: [], takesModule: false, named: [] };
|
|
311
|
+
const header = targetNeutral(realPath(headerFile));
|
|
312
|
+
const names = new Set();
|
|
313
|
+
let isImported = false;
|
|
314
|
+
let takesAllSymbols = false;
|
|
315
|
+
let takesWhole = false;
|
|
316
|
+
for (const { importer, specifier, names: imported } of findHeaderImportsIn(projectDir, state.config.ext.header)) {
|
|
317
|
+
if (targetNeutral(realPath(resolveNativeImport(specifier, importer, target))) !== header) continue;
|
|
318
|
+
if (imported === ALL_NAMES) {
|
|
319
|
+
takesWhole = true;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
isImported = true;
|
|
323
|
+
const listed = imported.filter((name) => C_IDENTIFIER.test(name));
|
|
324
|
+
takesAllSymbols ||= listed.includes('AllSymbols');
|
|
325
|
+
listed.filter((name) => !PROXY_NAMES.has(name)).forEach((name) => names.add(name));
|
|
326
|
+
}
|
|
327
|
+
const named = [...names].sort();
|
|
328
|
+
if (takesWhole) return { names: ALL_NAMES, takesModule: true, named };
|
|
329
|
+
return { names: named, takesModule: takesAllSymbols || (isImported && names.size === 0), named };
|
|
330
|
+
}
|
|
331
|
+
|
|
174
332
|
// A header that does not preprocess on its own, or a host without the image's compiler, keeps the plain interface.
|
|
175
|
-
function collectSwigMacros(headerFile, includes, name, target, sourceDir) {
|
|
333
|
+
function collectSwigMacros(headerFile, includes, name, target, sourceDir, constants) {
|
|
176
334
|
const interfaceDir = `${state.config.paths.build}/interface`;
|
|
177
335
|
try {
|
|
178
336
|
if (!predefinedMacros.has(target.path)) {
|
|
179
337
|
predefinedMacros.set(target.path, dumpMacros(`${interfaceDir}/predefined-${target.path}.macros.h`, [], [], target));
|
|
180
338
|
}
|
|
181
|
-
const macros = dumpMacros(`${interfaceDir}/${name}.macros.h`, includes, swigIncludePath(target, sourceDir), target);
|
|
182
|
-
return
|
|
339
|
+
const macros = dumpMacros(`${interfaceDir}/${name}.macros.h`, includes, swigIncludePath(target, sourceDir), target, [RELEASE_DEFINE]);
|
|
340
|
+
return {
|
|
341
|
+
lines: selectSwigMacros({ headerText: fs.readFileSync(headerFile, 'utf8'), macros, predefined: predefinedMacros.get(target.path), constants }),
|
|
342
|
+
table: macros,
|
|
343
|
+
};
|
|
183
344
|
} catch (e) {
|
|
184
345
|
console.warn(`crossbind: SWIG reads ${upath.basename(headerFile)} without the macros of its includes (${e.message})`);
|
|
185
|
-
return [];
|
|
346
|
+
return { lines: [], table: new Map() };
|
|
186
347
|
}
|
|
187
348
|
}
|
|
188
349
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
350
|
+
// Bridges compile in release with NDEBUG defined, so SWIG reads headers the same way: a declaration that exists only
|
|
351
|
+
// without NDEBUG (sqlite3_mutex_held) would otherwise be bound and fail to compile. The predefined dump stays without
|
|
352
|
+
// it, so NDEBUG counts as a macro SWIG needs rather than a compiler predefine.
|
|
353
|
+
const RELEASE_DEFINE = '-DNDEBUG';
|
|
354
|
+
|
|
355
|
+
// One bridge serves every desktop target, so SWIG and the macro table it reads come from the web image's em++,
|
|
356
|
+
// which defines no operating system; an android bridge is read with the NDK it compiles with. The headers
|
|
357
|
+
// still come from the target's own packages.
|
|
358
|
+
const toolTargetFor = (target) => (imageRoleFor(target) === 'android' ? target : { ...target, platform: 'wasm' });
|
|
359
|
+
|
|
360
|
+
function dumpMacros(outputFile, includes, includePath, target, defines = []) {
|
|
361
|
+
const tool = toolTargetFor(target);
|
|
362
|
+
run(cxxPreprocessorFor(tool), [
|
|
363
|
+
'-x', 'c++', '-std=c++17', '-dM', '-E', ...defines,
|
|
192
364
|
...includePath,
|
|
193
365
|
...includes.flatMap((header) => ['-include', header]),
|
|
194
366
|
'-o', outputFile,
|
|
195
367
|
'/dev/null',
|
|
196
|
-
], null,
|
|
368
|
+
], null, tool);
|
|
197
369
|
return parseMacroDump(fs.readFileSync(outputFile, 'utf8'));
|
|
198
370
|
}
|
|
199
371
|
|
|
@@ -204,6 +376,9 @@ function swigIncludePath(target, sourceDir) {
|
|
|
204
376
|
...state.config.allDependencies.map((d) => `${d.paths.output}/prebuilt/${target.path}/swig`),
|
|
205
377
|
...state.config.paths.header,
|
|
206
378
|
...allHeaders,
|
|
379
|
+
// A package that ships its sources has no prebuilt include directory: the app reaches its headers through
|
|
380
|
+
// the package's own CMakeLists.
|
|
381
|
+
...state.config.allDependencies.filter(isSourceCmakePackage).flatMap((d) => d.paths.header),
|
|
207
382
|
...(sourceDir ? [sourceDir] : []),
|
|
208
383
|
].filter((path) => !!path.toString()).map((path) => `-I${path}`);
|
|
209
384
|
return [...new Set(includePath)];
|
|
@@ -215,37 +390,94 @@ function swigIncludePath(target, sourceDir) {
|
|
|
215
390
|
// next time they are picked up.
|
|
216
391
|
function applyAsyncGuard(bridgeFilePath) {
|
|
217
392
|
const bridgeText = fs.readFileSync(bridgeFilePath, { encoding: 'utf8' });
|
|
218
|
-
const guarded = guardAsyncBindings(bridgeText);
|
|
393
|
+
const guarded = guardAsyncBindings(fixBridgeRuntime(bridgeText));
|
|
219
394
|
if (guarded !== bridgeText) {
|
|
220
395
|
fs.writeFileSync(bridgeFilePath, guarded);
|
|
221
396
|
}
|
|
222
397
|
}
|
|
223
398
|
|
|
399
|
+
// A build hides SWIG's output, so the SWIG fork writes each binding it skipped beside the bridge, under the path SWIG saw.
|
|
400
|
+
function reportSkippedBindings(bridgeFilePath) {
|
|
401
|
+
const warningsFile = `${bridgeFilePath}.warnings`;
|
|
402
|
+
if (!fs.existsSync(warningsFile)) return;
|
|
403
|
+
fs.readFileSync(warningsFile, 'utf8').split('\n').filter(Boolean).forEach((line) => {
|
|
404
|
+
const [, file, lineNumber, message] = line.match(/^(.*):(\d+): (.*)$/) ?? [];
|
|
405
|
+
console.warn(`crossbind: ${file ? `${upath.basename(file)}:${lineNumber}: ${message}` : line}`);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// SWIG skips function-like macros, variadic functions and mutable globals: the ones the app imports bind here, after its
|
|
410
|
+
// output. Their C++ uses SWIG's pointer runtime, which SWIG writes only for a pointer binding, so a bridge without one
|
|
411
|
+
// is generated again with a binding that has one.
|
|
412
|
+
function addBridgeExtras(bridgeFilePath, interfaceFilePath, named, headerText, swig) {
|
|
413
|
+
const warningsFile = `${bridgeFilePath}.warnings`;
|
|
414
|
+
const readWarnings = () => (fs.existsSync(warningsFile) ? fs.readFileSync(warningsFile, 'utf8').split('\n').filter(Boolean) : []);
|
|
415
|
+
const interfaceText = fs.readFileSync(interfaceFilePath, 'utf8');
|
|
416
|
+
const module = interfaceText.match(/^%module (\w+)$/m)?.[1];
|
|
417
|
+
let extras = bridgeExtras({ interfaceText, named, bridgeText: fs.readFileSync(bridgeFilePath, 'utf8'), warnings: readWarnings(), headerText, module });
|
|
418
|
+
if (!extras.code && !extras.notes.length) return;
|
|
419
|
+
if (extras.code && !hasPointerRuntime(fs.readFileSync(bridgeFilePath, 'utf8'))) {
|
|
420
|
+
const anchored = withPointerRuntimeAnchor(interfaceText);
|
|
421
|
+
if (anchored !== interfaceText) {
|
|
422
|
+
fs.writeFileSync(interfaceFilePath, anchored);
|
|
423
|
+
swig();
|
|
424
|
+
applyAsyncGuard(bridgeFilePath);
|
|
425
|
+
}
|
|
426
|
+
if (!hasPointerRuntime(fs.readFileSync(bridgeFilePath, 'utf8'))) {
|
|
427
|
+
extras = {
|
|
428
|
+
code: '', exports: [], answered: new Set(),
|
|
429
|
+
notes: [...extras.notes, `${extras.exports.join(', ')}: these need the pointer runtime, which SWIG left out of this bridge; skipped.`],
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (extras.code) fs.appendFileSync(bridgeFilePath, `\n${extras.code}`);
|
|
434
|
+
const exportsFile = `${bridgeFilePath}.exports.json`;
|
|
435
|
+
const exported = JSON.parse(fs.readFileSync(exportsFile, 'utf8')).filter((name) => name !== POINTER_RUNTIME_ANCHOR);
|
|
436
|
+
fs.writeFileSync(exportsFile, `${JSON.stringify([...new Set([...exported, ...extras.exports])])}\n`);
|
|
437
|
+
fs.writeFileSync(warningsFile, [...readWarnings().filter((line) => !extras.answered.has(line)), ...extras.notes].map((line) => `${line}\n`).join(''));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// SWIG looks in its working directory and the interface's before its include path, and neither holds the header, so
|
|
441
|
+
// it reads this copy, put first on that path: the includes its package lists are inlined there, while the compiler
|
|
442
|
+
// still includes the real header.
|
|
443
|
+
function writeSwigView(headerFile, headerPath, names) {
|
|
444
|
+
const dir = `${state.config.paths.build}/swigview`;
|
|
445
|
+
const viewFile = upath.join(dir, headerPath);
|
|
446
|
+
fs.mkdirSync(upath.dirname(viewFile), { recursive: true });
|
|
447
|
+
const readInclude = (name) => fs.readFileSync(upath.join(upath.dirname(headerFile), name), 'utf8');
|
|
448
|
+
writeIfChanged(viewFile, inlineIncludes(fs.readFileSync(headerFile, 'utf8'), names, readInclude));
|
|
449
|
+
return { dir, hash: getFileHash(viewFile) };
|
|
450
|
+
}
|
|
451
|
+
|
|
224
452
|
// The interface text only names the header, so its hash alone kept a bridge across header edits: the
|
|
225
|
-
// header's own hash
|
|
226
|
-
function createBridgeFileFromInterfaceFile(interfaceFilePath, target, sourceDir = null, sourceHash = '') {
|
|
453
|
+
// header's own hash, and the inlined copy's when SWIG reads one, are part of the key.
|
|
454
|
+
function createBridgeFileFromInterfaceFile(interfaceFilePath, target, sourceDir = null, sourceHash = '', swigView = null, extras = { named: [], headerText: () => '' }) {
|
|
227
455
|
if (!interfaceFilePath) {
|
|
228
456
|
return null;
|
|
229
457
|
}
|
|
230
458
|
|
|
231
|
-
const bridgeHash = () =>
|
|
459
|
+
const bridgeHash = () => [
|
|
460
|
+
BRIDGE_FORMAT, getFileHash(interfaceFilePath), sourceHash, ...(swigView ? [`view:${swigView.hash}`] : []),
|
|
461
|
+
].join('\n');
|
|
232
462
|
const fileHash = bridgeHash();
|
|
233
463
|
const cachedBridge = state.cache.bridges[interfaceFilePath];
|
|
234
|
-
if (state.cache.hashes[interfaceFilePath] === fileHash && cachedBridge && fs.existsSync(cachedBridge)) {
|
|
464
|
+
if (state.cache.hashes[interfaceFilePath] === fileHash && cachedBridge && fs.existsSync(cachedBridge) && isMadeFrom(cachedBridge, fileHash)) {
|
|
235
465
|
applyAsyncGuard(cachedBridge);
|
|
236
466
|
return cachedBridge;
|
|
237
467
|
}
|
|
238
468
|
|
|
239
469
|
const bridgeFilePath = `${state.config.paths.build}/bridge/${interfaceFilePath.split('/').at(-1)}.cpp`;
|
|
470
|
+
fs.writeFileSync(keyOf(bridgeFilePath), '');
|
|
240
471
|
// #error lines guard branches SWIG evaluates without the compiler's predefined macros, so they only warn.
|
|
241
472
|
const swig = () => run('swig', [
|
|
242
473
|
'-c++',
|
|
243
474
|
'-embind',
|
|
244
475
|
'-cpperraswarn',
|
|
245
476
|
'-o', bridgeFilePath,
|
|
477
|
+
...(swigView ? [`-I${swigView.dir}`] : []),
|
|
246
478
|
...swigIncludePath(target, sourceDir),
|
|
247
479
|
interfaceFilePath,
|
|
248
|
-
], null, target);
|
|
480
|
+
], null, toolTargetFor(target));
|
|
249
481
|
try {
|
|
250
482
|
swig();
|
|
251
483
|
} catch (e) {
|
|
@@ -264,10 +496,31 @@ function createBridgeFileFromInterfaceFile(interfaceFilePath, target, sourceDir
|
|
|
264
496
|
}
|
|
265
497
|
}
|
|
266
498
|
applyAsyncGuard(bridgeFilePath);
|
|
499
|
+
if (extras.named.length) addBridgeExtras(bridgeFilePath, interfaceFilePath, extras.named, extras.headerText(), swig);
|
|
500
|
+
reportSkippedBindings(bridgeFilePath);
|
|
267
501
|
|
|
502
|
+
const madeFrom = bridgeHash();
|
|
503
|
+
fs.writeFileSync(keyOf(bridgeFilePath), getContentHash(madeFrom));
|
|
268
504
|
state.cache.bridges[interfaceFilePath] = bridgeFilePath;
|
|
269
|
-
state.cache.hashes[interfaceFilePath] =
|
|
505
|
+
state.cache.hashes[interfaceFilePath] = madeFrom;
|
|
270
506
|
saveCache();
|
|
271
507
|
|
|
272
508
|
return state.cache.bridges[interfaceFilePath];
|
|
273
509
|
}
|
|
510
|
+
|
|
511
|
+
// Generate a full export catalog for the editor, without compiling or linking these extra functions.
|
|
512
|
+
function createConanDeclarations(interfaceFile, target, sourceDir, sourceHash, swigView) {
|
|
513
|
+
const dir = `${state.config.paths.build}/declarations`;
|
|
514
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
515
|
+
const file = `${dir}/${upath.basename(interfaceFile)}`;
|
|
516
|
+
writeIfChanged(file, interfaceWithAllFunctions(withoutBridgeExtras(fs.readFileSync(interfaceFile, 'utf8'))));
|
|
517
|
+
const bridge = `${file}.cpp`;
|
|
518
|
+
const hash = getContentHash([BRIDGE_FORMAT, getFileHash(file), sourceHash, swigView?.hash ?? ''].join('\n'));
|
|
519
|
+
if (!fs.existsSync(`${bridge}.exports.json`) || !fs.existsSync(keyOf(bridge)) || fs.readFileSync(keyOf(bridge), 'utf8') !== hash) {
|
|
520
|
+
fs.writeFileSync(keyOf(bridge), '');
|
|
521
|
+
run('swig', ['-c++', '-embind', '-cpperraswarn', '-o', bridge,
|
|
522
|
+
...(swigView ? [`-I${swigView.dir}`] : []), ...swigIncludePath(target, sourceDir), file], null, toolTargetFor(target));
|
|
523
|
+
fs.writeFileSync(keyOf(bridge), hash);
|
|
524
|
+
}
|
|
525
|
+
return `${bridge}.exports.json`;
|
|
526
|
+
}
|