crossbind 2.0.0-beta.60 → 2.0.0-beta.62
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 +7 -5
- package/package.json +1 -1
- package/src/actions/buildDependencies.js +22 -7
- package/src/actions/buildJs.js +1 -1
- package/src/actions/buildLib.js +2 -0
- package/src/actions/buildNode.js +149 -0
- package/src/actions/buildWasm.js +43 -66
- package/src/actions/createInterface.js +101 -27
- package/src/actions/createLib.js +3 -2
- package/src/actions/getAllBridges.js +1 -1
- package/src/actions/getLinkInputs.js +63 -0
- package/src/actions/licenses.js +34 -0
- package/src/actions/run.js +97 -2
- package/src/assets/cmake/CMakeLists.txt +1 -1
- package/src/assets/cmake/dist.cmake +1 -1
- package/src/assets/js-runtime/adapters/worker-comlink.js +6 -2
- package/src/assets/toolchain-digests.json +26 -12
- package/src/bin.js +59 -13
- package/src/index.js +3 -1
- package/src/integration/getCrossbindScript.js +8 -10
- package/src/integration/getDependFilePath.js +9 -12
- package/src/state/index.js +16 -13
- package/src/state/loadConfig.js +3 -0
- package/src/utils/appRustCrates.js +2 -0
- package/src/utils/appSources.js +21 -0
- package/src/utils/cargoImport.js +44 -0
- package/src/utils/cargoTarget.js +2 -0
- package/src/utils/cppDts.js +77 -41
- package/src/utils/embindArgumentGuards.js +45 -0
- package/src/utils/embindRsFingerprint.js +26 -0
- package/src/utils/headerImports.js +45 -0
- package/src/utils/inputStamp.js +23 -0
- package/src/utils/linkLayout.js +8 -1
- package/src/utils/pullDockerImage.js +8 -5
- 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 +4 -3
- package/src/utils/rustBridgeGen.js +895 -250
- package/src/utils/swigInterface.js +19 -4
- package/src/utils/systemKeys.js +8 -0
- package/src/utils/targets.js +56 -0
- package/src/utils/cppFieldBindings.js +0 -23
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import upath from 'upath';
|
|
4
5
|
import state, { saveCache } from '../state/index.js';
|
|
5
|
-
import { getFileHash } from '../utils/hash.js';
|
|
6
|
+
import { getContentHash, getFileHash } from '../utils/hash.js';
|
|
6
7
|
import guardAsyncBindings from '../utils/bridgeAsyncGuard.js';
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
8
|
+
import getDependFilePath from '../integration/getDependFilePath.js';
|
|
9
|
+
import { writeHeaderDts } from '../utils/cppDts.js';
|
|
10
|
+
import { ALL_NAMES, findHeaderImportsIn } from '../utils/headerImports.js';
|
|
9
11
|
import {
|
|
10
12
|
buildInterfaceContent, completingIncludes, findHeaderPrelude, findIgnoredDeclarations, indexTypeDefinitions, interfaceIncludes,
|
|
11
13
|
interfaceToRetryWithoutMacros, parseMacroDump, referencedTypeHeaders, selectSwigMacros,
|
|
@@ -15,6 +17,9 @@ import run, { cxxPreprocessorFor } from './run.js';
|
|
|
15
17
|
|
|
16
18
|
// Part of every interface hash, so interfaces cached by an older generator are rebuilt.
|
|
17
19
|
const INTERFACE_FORMAT = 'swig-macros-1';
|
|
20
|
+
// Part of every bridge hash: each bridge carries the SWIG fork's runtime and all bridges of a module must share it, so
|
|
21
|
+
// bridges from a fork before field bindings and constants are rebuilt.
|
|
22
|
+
const BRIDGE_FORMAT = 'swig-constants-1';
|
|
18
23
|
const predefinedMacros = new Map();
|
|
19
24
|
const typeDefinitions = new Map();
|
|
20
25
|
|
|
@@ -32,13 +37,6 @@ export default function createBridgeFile(headerOrModuleFilePath, target = state.
|
|
|
32
37
|
const interfaceFile = createInterfaceFile(interfaceFilePath, target, sourceDir);
|
|
33
38
|
const bridgeFile = createBridgeFileFromInterfaceFile(interfaceFile, target, sourceDir, getFileHash(interfaceFilePath));
|
|
34
39
|
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
40
|
if (bridgeFile) {
|
|
43
41
|
// Records which header this bridge came from, so directory-driven consumers
|
|
44
42
|
// (getAllBridges) can prune bridges whose header no longer exists.
|
|
@@ -77,14 +75,21 @@ function createInterfaceFile(headerOrModuleFilePath, target, sourceDir) {
|
|
|
77
75
|
const prelude = isModule ? [] : findHeaderPrelude(headerOrModuleFilePath, packages, headerPath);
|
|
78
76
|
const ignored = isModule ? [] : findIgnoredDeclarations(headerOrModuleFilePath, packages, headerPath);
|
|
79
77
|
const completing = includeRoot ? findCompletingIncludes(headerOrModuleFilePath, includeRoot, headerPath) : [];
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
const filePathWithoutExt = headerOrModuleFilePath.match(/^(.*)\..+?$/)?.[1];
|
|
79
|
+
const interfaceFile = !isModule && filePathWithoutExt ? `${filePathWithoutExt}.i` : null;
|
|
80
|
+
const constants = isModule ? [] : importedNames(headerOrModuleFilePath, target);
|
|
81
|
+
// A prelude or ignored-declaration change in the owning package, a completed class moving to another header, an
|
|
82
|
+
// interface the package ships beside the header changing or going away, or another imported name must regenerate
|
|
83
|
+
// the interface as well.
|
|
82
84
|
const fileHash = [
|
|
83
85
|
INTERFACE_FORMAT, getFileHash(headerOrModuleFilePath), ...prelude,
|
|
84
86
|
...(ignored.length ? [`ignored:${ignored.join(',')}`] : []), ...(completing.length ? [`completing:${completing.join(',')}`] : []),
|
|
87
|
+
...(interfaceFile && fs.existsSync(interfaceFile) ? [`shipped:${getFileHash(interfaceFile)}`] : []),
|
|
88
|
+
...(constants.length ? [`constants:${constants === ALL_NAMES ? ALL_NAMES : constants.join(',')}`] : []),
|
|
85
89
|
].join('\n');
|
|
86
90
|
const cachedInterface = state.cache.interfaces[headerOrModuleFilePath];
|
|
87
|
-
if (state.cache.hashes[headerOrModuleFilePath] === fileHash && cachedInterface && fs.existsSync(cachedInterface)
|
|
91
|
+
if (state.cache.hashes[headerOrModuleFilePath] === fileHash && cachedInterface && fs.existsSync(cachedInterface)
|
|
92
|
+
&& !sharedInterface(headerOrModuleFilePath, cachedInterface)) {
|
|
88
93
|
return cachedInterface;
|
|
89
94
|
}
|
|
90
95
|
|
|
@@ -97,11 +102,7 @@ function createInterfaceFile(headerOrModuleFilePath, target, sourceDir) {
|
|
|
97
102
|
return newPath;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
|
|
101
|
-
if (!temp || temp.length < 2) return null;
|
|
102
|
-
|
|
103
|
-
const filePathWithoutExt = temp[1];
|
|
104
|
-
const interfaceFile = `${filePathWithoutExt}.i`;
|
|
105
|
+
if (!filePathWithoutExt) return null;
|
|
105
106
|
|
|
106
107
|
if (fs.existsSync(interfaceFile)) {
|
|
107
108
|
const newPath = `${state.config.paths.build}/interface/${interfaceFile.split('/').at(-1)}`;
|
|
@@ -112,15 +113,16 @@ function createInterfaceFile(headerOrModuleFilePath, target, sourceDir) {
|
|
|
112
113
|
return newPath;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
const fileName = filePathWithoutExt.split('/').at(-1);
|
|
116
|
+
const fileName = interfaceName(headerOrModuleFilePath, filePathWithoutExt.split('/').at(-1));
|
|
116
117
|
|
|
117
118
|
const content = buildInterfaceContent({
|
|
118
119
|
moduleName: fileName.toUpperCase(),
|
|
119
120
|
headerPath,
|
|
120
121
|
prelude,
|
|
121
122
|
completing,
|
|
122
|
-
swigMacros: collectSwigMacros(headerOrModuleFilePath, interfaceIncludes(headerPath, prelude), fileName, target, sourceDir),
|
|
123
|
+
swigMacros: collectSwigMacros(headerOrModuleFilePath, interfaceIncludes(headerPath, prelude), fileName, target, sourceDir, constants),
|
|
123
124
|
ignored,
|
|
125
|
+
constants,
|
|
124
126
|
});
|
|
125
127
|
const outputFilePath = `${state.config.paths.build}/interface/${fileName}.i`;
|
|
126
128
|
fs.writeFileSync(outputFilePath, content);
|
|
@@ -132,6 +134,21 @@ function createInterfaceFile(headerOrModuleFilePath, target, sourceDir) {
|
|
|
132
134
|
return outputFilePath;
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
const interfacePath = (name) => `${state.config.paths.build}/interface/${name}.i`;
|
|
138
|
+
// Each platform build of a package ships its headers under dist/prebuilt/<target>/include: one header for the interface.
|
|
139
|
+
const headerIdentity = (file) => file.match(/\/dist\/prebuilt\/[^/]+\/include\/(.+)$/)?.[1] ?? file;
|
|
140
|
+
const sharedInterface = (headerFile, interfaceFile) => Object.entries(state.cache.interfaces)
|
|
141
|
+
.some(([other, file]) => headerIdentity(other) !== headerIdentity(headerFile) && file === interfaceFile);
|
|
142
|
+
|
|
143
|
+
// An interface is named after its header's file name. A second header with that name (tiffio.h and tiffio.hxx, or one
|
|
144
|
+
// name in two directories) takes its extension, then a hash of its path, so neither replaces the other's bridge.
|
|
145
|
+
function interfaceName(headerFile, base) {
|
|
146
|
+
const candidates = [base, `${base}_${upath.extname(headerFile).slice(1)}`, `${base}_${getContentHash(headerFile).slice(0, 8)}`];
|
|
147
|
+
return candidates.find((name) => !sharedInterface(headerFile, interfacePath(name))) ?? candidates.at(-1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
151
|
+
|
|
135
152
|
// A header is included by its path under a project header directory or a dependency's include directory; one found only
|
|
136
153
|
// through its own directory has no include root to search.
|
|
137
154
|
function includeLocation(headerFile, target) {
|
|
@@ -139,7 +156,7 @@ function includeLocation(headerFile, target) {
|
|
|
139
156
|
if (projectRoot) return { includeRoot: projectRoot, headerPath: headerFile.substr(projectRoot.length + 1) };
|
|
140
157
|
const dependencyRoots = (state.config.dependencyParameters?.getCmakeDependsPathAndName(target).pathsOfCmakeDepends || [])
|
|
141
158
|
.filter((d) => d.startsWith(state.config.paths.base));
|
|
142
|
-
const match = dependencyRoots.map((p) => headerFile.match(new RegExp(`^(${p}/.*?/include)/(.*?)$`, 'i'))).find(Boolean);
|
|
159
|
+
const match = dependencyRoots.map((p) => headerFile.match(new RegExp(`^(${escapeRegExp(p)}/.*?/include)/(.*?)$`, 'i'))).find(Boolean);
|
|
143
160
|
return match ? { includeRoot: match[1], headerPath: match[2] } : { includeRoot: null, headerPath: headerFile.split('/').at(-1) };
|
|
144
161
|
}
|
|
145
162
|
|
|
@@ -171,24 +188,70 @@ function dependencyHeadersOf(headerFile, target) {
|
|
|
171
188
|
.map((header) => upath.join(includeRoot, header));
|
|
172
189
|
}
|
|
173
190
|
|
|
191
|
+
// The proxy module's own exports, never names of the header.
|
|
192
|
+
const PROXY_NAMES = new Set(['initNative', 'AllSymbols']);
|
|
193
|
+
const C_IDENTIFIER = /^[A-Za-z_]\w*$/;
|
|
194
|
+
|
|
195
|
+
// A package header resolves under the target's prebuilt directory, and the bundler may resolve it for another target.
|
|
196
|
+
const targetNeutral = (file) => file?.replace(/\/dist\/prebuilt\/[^/]+\/include\//, '/dist/prebuilt/*/include/');
|
|
197
|
+
|
|
198
|
+
function realPath(file) {
|
|
199
|
+
if (!file) return null;
|
|
200
|
+
try {
|
|
201
|
+
return upath.normalize(fs.realpathSync(file));
|
|
202
|
+
} catch (e) {
|
|
203
|
+
return upath.resolve(file);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// A package that is not a crossbind dependency (the conformance kit) resolves the way the bundler finds it.
|
|
208
|
+
function resolveHeaderImport(specifier, importer, target) {
|
|
209
|
+
if (specifier.startsWith('.')) return upath.resolve(upath.dirname(importer), specifier);
|
|
210
|
+
if (upath.isAbsolute(specifier)) return specifier;
|
|
211
|
+
try {
|
|
212
|
+
return getDependFilePath(specifier, target) ?? createRequire(importer).resolve(specifier);
|
|
213
|
+
} catch (e) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// The names the app's own sources import from this header, or every name for `import * as`.
|
|
219
|
+
function importedNames(headerFile, target) {
|
|
220
|
+
const projectDir = state.config.paths.project;
|
|
221
|
+
if (!projectDir || !fs.existsSync(projectDir)) return [];
|
|
222
|
+
const header = targetNeutral(realPath(headerFile));
|
|
223
|
+
const names = new Set();
|
|
224
|
+
for (const { importer, specifier, names: imported } of findHeaderImportsIn(projectDir, state.config.ext.header)) {
|
|
225
|
+
if (targetNeutral(realPath(resolveHeaderImport(specifier, importer, target))) !== header) continue;
|
|
226
|
+
if (imported === ALL_NAMES) return ALL_NAMES;
|
|
227
|
+
imported.filter((name) => C_IDENTIFIER.test(name) && !PROXY_NAMES.has(name)).forEach((name) => names.add(name));
|
|
228
|
+
}
|
|
229
|
+
return [...names].sort();
|
|
230
|
+
}
|
|
231
|
+
|
|
174
232
|
// 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) {
|
|
233
|
+
function collectSwigMacros(headerFile, includes, name, target, sourceDir, constants) {
|
|
176
234
|
const interfaceDir = `${state.config.paths.build}/interface`;
|
|
177
235
|
try {
|
|
178
236
|
if (!predefinedMacros.has(target.path)) {
|
|
179
237
|
predefinedMacros.set(target.path, dumpMacros(`${interfaceDir}/predefined-${target.path}.macros.h`, [], [], target));
|
|
180
238
|
}
|
|
181
|
-
const macros = dumpMacros(`${interfaceDir}/${name}.macros.h`, includes, swigIncludePath(target, sourceDir), target);
|
|
182
|
-
return selectSwigMacros({ headerText: fs.readFileSync(headerFile, 'utf8'), macros, predefined: predefinedMacros.get(target.path) });
|
|
239
|
+
const macros = dumpMacros(`${interfaceDir}/${name}.macros.h`, includes, swigIncludePath(target, sourceDir), target, [RELEASE_DEFINE]);
|
|
240
|
+
return selectSwigMacros({ headerText: fs.readFileSync(headerFile, 'utf8'), macros, predefined: predefinedMacros.get(target.path), constants });
|
|
183
241
|
} catch (e) {
|
|
184
242
|
console.warn(`crossbind: SWIG reads ${upath.basename(headerFile)} without the macros of its includes (${e.message})`);
|
|
185
243
|
return [];
|
|
186
244
|
}
|
|
187
245
|
}
|
|
188
246
|
|
|
189
|
-
|
|
247
|
+
// Bridges compile in release with NDEBUG defined, so SWIG reads headers the same way: a declaration that exists only
|
|
248
|
+
// without NDEBUG (sqlite3_mutex_held) would otherwise be bound and fail to compile. The predefined dump stays without
|
|
249
|
+
// it, so NDEBUG counts as a macro SWIG needs rather than a compiler predefine.
|
|
250
|
+
const RELEASE_DEFINE = '-DNDEBUG';
|
|
251
|
+
|
|
252
|
+
function dumpMacros(outputFile, includes, includePath, target, defines = []) {
|
|
190
253
|
run(cxxPreprocessorFor(target), [
|
|
191
|
-
'-x', 'c++', '-std=c++17', '-dM', '-E',
|
|
254
|
+
'-x', 'c++', '-std=c++17', '-dM', '-E', ...defines,
|
|
192
255
|
...includePath,
|
|
193
256
|
...includes.flatMap((header) => ['-include', header]),
|
|
194
257
|
'-o', outputFile,
|
|
@@ -221,6 +284,16 @@ function applyAsyncGuard(bridgeFilePath) {
|
|
|
221
284
|
}
|
|
222
285
|
}
|
|
223
286
|
|
|
287
|
+
// A build hides SWIG's output, so the SWIG fork writes each binding it skipped beside the bridge, under the path SWIG saw.
|
|
288
|
+
function reportSkippedBindings(bridgeFilePath) {
|
|
289
|
+
const warningsFile = `${bridgeFilePath}.warnings`;
|
|
290
|
+
if (!fs.existsSync(warningsFile)) return;
|
|
291
|
+
fs.readFileSync(warningsFile, 'utf8').split('\n').filter(Boolean).forEach((line) => {
|
|
292
|
+
const [, file, lineNumber, message] = line.match(/^(.*):(\d+): (.*)$/) ?? [];
|
|
293
|
+
console.warn(`crossbind: ${file ? `${upath.basename(file)}:${lineNumber}: ${message}` : line}`);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
224
297
|
// The interface text only names the header, so its hash alone kept a bridge across header edits: the
|
|
225
298
|
// header's own hash is part of the key.
|
|
226
299
|
function createBridgeFileFromInterfaceFile(interfaceFilePath, target, sourceDir = null, sourceHash = '') {
|
|
@@ -228,7 +301,7 @@ function createBridgeFileFromInterfaceFile(interfaceFilePath, target, sourceDir
|
|
|
228
301
|
return null;
|
|
229
302
|
}
|
|
230
303
|
|
|
231
|
-
const bridgeHash = () => `${getFileHash(interfaceFilePath)}\n${sourceHash}`;
|
|
304
|
+
const bridgeHash = () => `${BRIDGE_FORMAT}\n${getFileHash(interfaceFilePath)}\n${sourceHash}`;
|
|
232
305
|
const fileHash = bridgeHash();
|
|
233
306
|
const cachedBridge = state.cache.bridges[interfaceFilePath];
|
|
234
307
|
if (state.cache.hashes[interfaceFilePath] === fileHash && cachedBridge && fs.existsSync(cachedBridge)) {
|
|
@@ -264,6 +337,7 @@ function createBridgeFileFromInterfaceFile(interfaceFilePath, target, sourceDir
|
|
|
264
337
|
}
|
|
265
338
|
}
|
|
266
339
|
applyAsyncGuard(bridgeFilePath);
|
|
340
|
+
reportSkippedBindings(bridgeFilePath);
|
|
267
341
|
|
|
268
342
|
state.cache.bridges[interfaceFilePath] = bridgeFilePath;
|
|
269
343
|
state.cache.hashes[interfaceFilePath] = bridgeHash();
|
package/src/actions/createLib.js
CHANGED
|
@@ -21,7 +21,7 @@ export default function createLib(target, fileType, options = {}) {
|
|
|
21
21
|
throw new Error('invalid target or options');
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (target.platform === 'ios' && process.platform !== 'darwin') {
|
|
24
|
+
if ((target.platform === 'ios' || target.platform === 'darwin') && process.platform !== 'darwin') {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -35,7 +35,8 @@ export default function createLib(target, fileType, options = {}) {
|
|
|
35
35
|
const nativeGlob = options.nativeGlob ? withDependencyBridges(options.nativeGlob) : undefined;
|
|
36
36
|
// The lib dir is reused while the bridge list grows, so an early smaller nativeGlob build must not satisfy later sets: fingerprint the glob and miss on mismatch.
|
|
37
37
|
const fingerprintFile = `${libdir}/crossbind-nativeglob.fingerprint`;
|
|
38
|
-
|
|
38
|
+
// options.inputs lists what the sources compile against (a binding runtime's headers), so a change there rebuilds too.
|
|
39
|
+
const fingerprint = nativeGlob ? getFilesFingerprint([...nativeGlob, ...(options.inputs ?? [])]) : null;
|
|
39
40
|
const fingerprintChanged = fingerprint !== null
|
|
40
41
|
&& (!fs.existsSync(fingerprintFile) || fs.readFileSync(fingerprintFile, { encoding: 'utf8' }) !== fingerprint);
|
|
41
42
|
|
|
@@ -13,7 +13,7 @@ export default function getAllBridges(log = console.log) {
|
|
|
13
13
|
if (!fs.existsSync(sidecar)) return true;
|
|
14
14
|
const source = fs.readFileSync(sidecar, 'utf8').trim();
|
|
15
15
|
if (!source || fs.existsSync(source)) return true;
|
|
16
|
-
[bridge, `${bridge}.exports.json`, `${bridge}.deps`, sidecar].forEach((f) => fs.rmSync(f, { force: true }));
|
|
16
|
+
[bridge, `${bridge}.exports.json`, `${bridge}.deps`, `${bridge}.warnings`, sidecar].forEach((f) => fs.rmSync(f, { force: true }));
|
|
17
17
|
log(`crossbind: pruned stale bridge '${path.basename(bridge)}' - its header is gone`);
|
|
18
18
|
return false;
|
|
19
19
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import getDependLibs from './getDependLibs.js';
|
|
3
|
+
import state from '../state/index.js';
|
|
4
|
+
import buildAppRustCrates from '../utils/appRustCrates.js';
|
|
5
|
+
|
|
6
|
+
// The archives of a final link (wasm or Node-API addon) and which of them must survive whole.
|
|
7
|
+
// keepFlag spells "keep this symbol" for the target's linker.
|
|
8
|
+
export default function getLinkInputs(target, { keepFlag }) {
|
|
9
|
+
const buildType = target.buildType === 'release' ? 'Release' : 'Debug';
|
|
10
|
+
const { build, output, cache } = state.config.paths;
|
|
11
|
+
const { name } = state.config.general;
|
|
12
|
+
|
|
13
|
+
// buildLib's cache is keyed on paths.output/prebuilt; after a cache clean the
|
|
14
|
+
// build-dir copy can be gone while the output artifact is still valid — link it then.
|
|
15
|
+
const sourceLibCandidates = [
|
|
16
|
+
`${build}/Source-${buildType}/${target.path}/lib${name}.a`,
|
|
17
|
+
`${output}/prebuilt/${target.path}/lib/lib${name}.a`,
|
|
18
|
+
];
|
|
19
|
+
// App-local Rust surfaces (imported .rs files) arrive as ONE super staticlib; it is the
|
|
20
|
+
// single fully-loaded Rust archive of the link (see the libstd rule below).
|
|
21
|
+
const appRustLibs = buildAppRustCrates(target, cache, state.config.cargoDependencies ?? {});
|
|
22
|
+
const libs = [
|
|
23
|
+
...getDependLibs(target),
|
|
24
|
+
...appRustLibs,
|
|
25
|
+
sourceLibCandidates.find((lib) => fs.existsSync(lib)) ?? sourceLibCandidates[0],
|
|
26
|
+
`${build}/Bridge-${buildType}/${target.path}/lib${name}.a`,
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// By default only the Bridge archive is kept whole (see linkLayout.js) so the linker
|
|
30
|
+
// dead-code-eliminates everything unreferenced. Two escape hatches, both
|
|
31
|
+
// `export.wholeArchive: true`: on the APP it restores the legacy layout (every archive
|
|
32
|
+
// wholesale); on a LIBRARY's own config it keeps that library's archives whole in every
|
|
33
|
+
// consumer link (for members that self-register from static initializers).
|
|
34
|
+
const wholeArchiveAll = state.config.export.wholeArchive === true;
|
|
35
|
+
const wholeArchiveNames = new Set();
|
|
36
|
+
// Rust libstd rule: every Rust staticlib bundles its own libstd, so at most ONE Rust archive
|
|
37
|
+
// may be fully loaded (the app super staticlib). Generated cargo bridges are linked lazily
|
|
38
|
+
// with their keep symbol pinned (keepFlag pulls just the registration object); manual-bindings
|
|
39
|
+
// crates have no keep symbol and fall back to whole-archive - safe only while they are the
|
|
40
|
+
// single loaded Rust archive.
|
|
41
|
+
const rustKeepFlags = [];
|
|
42
|
+
if (appRustLibs.length > 0) wholeArchiveNames.add('crossbind_app_super');
|
|
43
|
+
const depends = state.config.dependencyParameters.getCmakeDepends(target);
|
|
44
|
+
depends.forEach((dep) => {
|
|
45
|
+
if (dep.export.wholeArchive === true) {
|
|
46
|
+
(dep.export.libName || []).forEach((libName) => wholeArchiveNames.add(libName));
|
|
47
|
+
}
|
|
48
|
+
if (dep.export.type === 'cargo') {
|
|
49
|
+
const crateLibRs = `${dep.paths.project}/${dep.export.crate ?? 'crate'}/src/lib.rs`.replace('/./', '/');
|
|
50
|
+
const isManual = fs.existsSync(crateLibRs) && fs.readFileSync(crateLibRs, 'utf8').includes('bindings!');
|
|
51
|
+
(dep.export.libName || []).forEach((libName) => {
|
|
52
|
+
if (isManual) wholeArchiveNames.add(libName);
|
|
53
|
+
else rustKeepFlags.push(keepFlag(libName));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const hasRust = rustKeepFlags.length > 0 || appRustLibs.length > 0
|
|
59
|
+
|| depends.some((dep) => dep.export.type === 'cargo');
|
|
60
|
+
return {
|
|
61
|
+
libs, appRustLibs, wholeArchiveAll, wholeArchiveNames, rustKeepFlags, hasRust,
|
|
62
|
+
};
|
|
63
|
+
}
|
package/src/actions/licenses.js
CHANGED
|
@@ -90,6 +90,39 @@ function wasiToolchainRows() {
|
|
|
90
90
|
];
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// A Linux or Windows addon carries its C++ runtime; a Windows one also carries parts of the
|
|
94
|
+
// mingw-w64 runtime and winpthreads, whose licenses ask for their notices in binary distributions.
|
|
95
|
+
// A macOS addon uses the system's C++ runtime.
|
|
96
|
+
function addonToolchainRows(platform) {
|
|
97
|
+
const shared = {
|
|
98
|
+
npmName: null, version: null, nativeVersion: null, sha256: null, licenseSelected: null, licenseText: null, isCopyleft: false,
|
|
99
|
+
};
|
|
100
|
+
const llvmLicense = 'Apache-2.0 WITH LLVM-exception';
|
|
101
|
+
const llvmRuntimes = {
|
|
102
|
+
...shared,
|
|
103
|
+
name: 'llvm-runtimes',
|
|
104
|
+
license: llvmLicense,
|
|
105
|
+
licenseDeclared: llvmLicense,
|
|
106
|
+
sourceUrl: 'https://github.com/llvm/llvm-project',
|
|
107
|
+
};
|
|
108
|
+
const llvmText = 'license text: https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT';
|
|
109
|
+
if (platform === 'linux') {
|
|
110
|
+
return [{ ...llvmRuntimes, licenseNotes: `libc++ and libc++abi of the linux toolchain image, statically linked into the addon; ${llvmText}` }];
|
|
111
|
+
}
|
|
112
|
+
const mingwLicense = 'LicenseRef-MinGW-w64-runtime';
|
|
113
|
+
return [
|
|
114
|
+
{ ...llvmRuntimes, licenseNotes: `libc++, libc++abi, libunwind and compiler-rt of the windows toolchain image (llvm-mingw), statically linked into the addon; ${llvmText}` },
|
|
115
|
+
{
|
|
116
|
+
...shared,
|
|
117
|
+
name: 'mingw-w64-runtime',
|
|
118
|
+
license: mingwLicense,
|
|
119
|
+
licenseDeclared: mingwLicense,
|
|
120
|
+
sourceUrl: 'https://github.com/mingw-w64/mingw-w64',
|
|
121
|
+
licenseNotes: 'the mingw-w64 startup code and winpthreads, statically linked into the addon; a binary distribution includes their notices: https://github.com/mingw-w64/mingw-w64/blob/master/COPYING.MinGW-w64-runtime/COPYING.MinGW-w64-runtime.txt and https://github.com/mingw-w64/mingw-w64/blob/master/mingw-w64-libraries/winpthreads/COPYING (the windows image carries both in /opt/licenses/llvm-mingw)',
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
|
|
93
126
|
async function buildRow(node) {
|
|
94
127
|
const recipe = await loadJs(node.paths.project, 'crossbind.build');
|
|
95
128
|
const manifest = node.package || null;
|
|
@@ -152,5 +185,6 @@ export default async function collectLicenseRows(platform = null) {
|
|
|
152
185
|
if (platform) rows.push(...await bundledRowsOf(node, platform));
|
|
153
186
|
}
|
|
154
187
|
if (platform === 'wasi') rows.push(...wasiToolchainRows());
|
|
188
|
+
if (platform === 'linux' || platform === 'win32') rows.push(...addonToolchainRows(platform));
|
|
155
189
|
return rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
156
190
|
}
|
package/src/actions/run.js
CHANGED
|
@@ -6,6 +6,7 @@ import replaceBasePathForDockerUtil from '../utils/replaceBasePathForDocker.js';
|
|
|
6
6
|
import { DOCKER_RUN_SECURITY_ARGS } from '../utils/dockerSecurity.js';
|
|
7
7
|
import state from '../state/index.js';
|
|
8
8
|
import { wasiCFlags, wasiCxxFlags, resolveWasiSdkPath, WASI_TARGET_TRIPLE } from '../utils/wasiToolchain.js';
|
|
9
|
+
import { HOST_BUILT_PLATFORMS } from '../utils/targets.js';
|
|
9
10
|
|
|
10
11
|
// Native builds can outrun Node's 1 MiB default pipe buffer; without a raised cap a successful build dies with ENOBUFS.
|
|
11
12
|
const EXEC_MAX_BUFFER = 512 * 1024 * 1024;
|
|
@@ -64,6 +65,18 @@ const androidParamsX86_64 = [
|
|
|
64
65
|
|
|
65
66
|
// Xcode 27 refuses deployment targets below 15.0; React Native's own floor is 15.1.
|
|
66
67
|
const IOS_DEPLOYMENT_TARGET = '15.1';
|
|
68
|
+
// Node 22, the oldest supported line, needs macOS 11.
|
|
69
|
+
const DARWIN_DEPLOYMENT_TARGET = '11.0';
|
|
70
|
+
const DARWIN_HOST_ARCH = process.arch === 'x64' ? 'x86_64' : 'arm64';
|
|
71
|
+
const DARWIN_HOST_PACKAGE_PREFIXES = ['/opt/homebrew', '/usr/local', '/opt/local'];
|
|
72
|
+
// Apple's own tools only, which find the SDK through xcrun. A GNU ar earlier on the PATH (Homebrew's
|
|
73
|
+
// binutils) writes archives that Apple's linker rejects.
|
|
74
|
+
const DARWIN_BUILD_PATH = '/usr/bin:/bin:/usr/sbin:/sbin';
|
|
75
|
+
// The linux image's clang wrappers and CMake toolchain files, one per target triple.
|
|
76
|
+
const LINUX_TOOLCHAIN = '/opt/crossbind/linux';
|
|
77
|
+
// The windows image's llvm-mingw and its CMake toolchain files, one per target triple.
|
|
78
|
+
const LLVM_MINGW = '/opt/llvm-mingw';
|
|
79
|
+
const WINDOWS_TOOLCHAIN = '/opt/crossbind/windows';
|
|
67
80
|
const IOS_HOST_FLAGS = `-arch arm64 -isysroot ${iosSdkPath} -fembed-bitcode`;
|
|
68
81
|
const IOS_SIM_HOST_FLAGS = `-arch arm64 -isysroot ${iosSimSdkPath} -fembed-bitcode`;
|
|
69
82
|
const IOS_IPHONE_PARAMS = ['-e', `CFLAGS="${IOS_HOST_FLAGS}"`, '-e', `CXXFLAGS="${IOS_HOST_FLAGS}"`, '-e', `LDFLAGS="${IOS_HOST_FLAGS}"`];
|
|
@@ -118,7 +131,7 @@ export default function run(program, params = [], platformPrefix = null, target
|
|
|
118
131
|
|
|
119
132
|
// wasi is host-run only with a locally configured wasi-sdk; otherwise docker carries it.
|
|
120
133
|
const wasiHostSdk = target?.platform === 'wasi' ? resolveWasiSdkPath(state.config.system) : null;
|
|
121
|
-
if ((target?.platform
|
|
134
|
+
if ((!HOST_BUILT_PLATFORMS.includes(target?.platform) && !(target?.platform === 'wasi' && wasiHostSdk)) || program !== null) {
|
|
122
135
|
// Google ships the linux NDK for x86_64 only, so the android index carries no arm64 leaf;
|
|
123
136
|
// pull its amd64 leaf by ref or an arm64 host finds no matching manifest.
|
|
124
137
|
pullDockerImage(imageRoleFor(target), target?.platform === 'android' ? 'linux/amd64' : undefined);
|
|
@@ -237,6 +250,71 @@ export default function run(program, params = [], platformPrefix = null, target
|
|
|
237
250
|
}
|
|
238
251
|
}
|
|
239
252
|
break;
|
|
253
|
+
case 'linux': {
|
|
254
|
+
[dProgram, ...dParams] = params;
|
|
255
|
+
const triple = target.arch === 'x64' ? 'x86_64-linux-gnu' : 'aarch64-linux-gnu';
|
|
256
|
+
const tool = (name) => `${LINUX_TOOLCHAIN}/bin/${triple}-${name}`;
|
|
257
|
+
// The image's own .pc files describe its libraries, not the sysroot's. Every archive
|
|
258
|
+
// ends up inside a loadable .node module, so -fPIC holds even where a project turns
|
|
259
|
+
// position-independent code off for static builds, as GDAL does.
|
|
260
|
+
platformParams = [
|
|
261
|
+
'-e', `CC=${tool('clang')}`, '-e', `CXX=${tool('clang++')}`,
|
|
262
|
+
'-e', `AR=${tool('ar')}`, '-e', `RANLIB=${tool('ranlib')}`,
|
|
263
|
+
'-e', `NM=${tool('nm')}`, '-e', `STRIP=${tool('strip')}`,
|
|
264
|
+
'-e', 'PKG_CONFIG_LIBDIR=',
|
|
265
|
+
'-e', 'CFLAGS=-fPIC', '-e', 'CXXFLAGS=-fPIC',
|
|
266
|
+
];
|
|
267
|
+
if (dProgram === 'cmake' && dParams[0] !== '--build' && dParams[0] !== '--install') {
|
|
268
|
+
dParams = [...dParams, `-DCMAKE_TOOLCHAIN_FILE=${LINUX_TOOLCHAIN}/${triple}.cmake`];
|
|
269
|
+
}
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case 'win32': {
|
|
273
|
+
[dProgram, ...dParams] = params;
|
|
274
|
+
const triple = target.arch === 'x64' ? 'x86_64-w64-mingw32' : 'aarch64-w64-mingw32';
|
|
275
|
+
const tool = (name) => `${LLVM_MINGW}/bin/${triple}-${name}`;
|
|
276
|
+
platformParams = [
|
|
277
|
+
'-e', `CC=${tool('clang')}`, '-e', `CXX=${tool('clang++')}`,
|
|
278
|
+
'-e', `AR=${tool('ar')}`, '-e', `RANLIB=${tool('ranlib')}`,
|
|
279
|
+
'-e', `NM=${tool('nm')}`, '-e', `STRIP=${tool('strip')}`,
|
|
280
|
+
'-e', `RC=${tool('windres')}`, '-e', `WINDRES=${tool('windres')}`,
|
|
281
|
+
'-e', 'PKG_CONFIG_LIBDIR=',
|
|
282
|
+
];
|
|
283
|
+
if (dProgram === 'cmake' && dParams[0] !== '--build' && dParams[0] !== '--install') {
|
|
284
|
+
dParams = [...dParams, `-DCMAKE_TOOLCHAIN_FILE=${WINDOWS_TOOLCHAIN}/${triple}.cmake`];
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
case 'darwin': {
|
|
289
|
+
[dProgram, ...dParams] = params;
|
|
290
|
+
const appleArch = target.arch === 'x64' ? 'x86_64' : 'arm64';
|
|
291
|
+
// Homebrew and MacPorts packages exist on the build machine only: an archive compiled
|
|
292
|
+
// against one fails to link, or to load, anywhere else. The SDK's libraries stay visible.
|
|
293
|
+
platformParams = ['-e', 'PKG_CONFIG_LIBDIR='];
|
|
294
|
+
if (dProgram === 'cmake') {
|
|
295
|
+
if (dParams[0] !== '--build' && dParams[0] !== '--install') {
|
|
296
|
+
dParams = [
|
|
297
|
+
...dParams,
|
|
298
|
+
`-DCMAKE_OSX_ARCHITECTURES=${appleArch}`,
|
|
299
|
+
`-DCMAKE_OSX_DEPLOYMENT_TARGET=${DARWIN_DEPLOYMENT_TARGET}`,
|
|
300
|
+
// Every archive ends up inside a loadable .node module.
|
|
301
|
+
'-DCMAKE_POSITION_INDEPENDENT_CODE=ON',
|
|
302
|
+
`-DCMAKE_IGNORE_PREFIX_PATH=${DARWIN_HOST_PACKAGE_PREFIXES.join(';')}`,
|
|
303
|
+
// The other architecture's probes cannot run here, so CMake has to know it cross-compiles.
|
|
304
|
+
...(appleArch === DARWIN_HOST_ARCH ? [] : ['-DCMAKE_SYSTEM_NAME=Darwin', `-DCMAKE_SYSTEM_PROCESSOR=${appleArch}`]),
|
|
305
|
+
];
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
const flags = `-arch ${appleArch} -mmacosx-version-min=${DARWIN_DEPLOYMENT_TARGET}`;
|
|
309
|
+
// A --host triple sends configure after prefixed compilers (aarch64-apple-darwin-cc) that
|
|
310
|
+
// Xcode does not ship, and SQLite's autosetup has no fallback to the plain names.
|
|
311
|
+
platformParams.push(
|
|
312
|
+
'-e', 'CC=/usr/bin/clang', '-e', 'CXX=/usr/bin/clang++',
|
|
313
|
+
'-e', `CFLAGS=${flags}`, '-e', `CXXFLAGS=${flags}`, '-e', `LDFLAGS=${flags}`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
240
318
|
default:
|
|
241
319
|
}
|
|
242
320
|
}
|
|
@@ -244,7 +322,7 @@ export default function run(program, params = [], platformPrefix = null, target
|
|
|
244
322
|
const env = {};
|
|
245
323
|
let runner = 'DOCKER';
|
|
246
324
|
if (
|
|
247
|
-
((target?.platform
|
|
325
|
+
((HOST_BUILT_PLATFORMS.includes(target?.platform) || (target?.platform === 'wasi' && wasiHostSdk)) && program === null) ||
|
|
248
326
|
state.config.system.RUNNER === 'LOCAL'
|
|
249
327
|
) {
|
|
250
328
|
runner = 'LOCAL';
|
|
@@ -291,6 +369,12 @@ export default function run(program, params = [], platformPrefix = null, target
|
|
|
291
369
|
let fileExecParams;
|
|
292
370
|
if (runner === 'LOCAL') {
|
|
293
371
|
env.PATH = `/opt/homebrew/bin:${env.PATH}`;
|
|
372
|
+
// A package's *-config script on the PATH (nc-config, sfcgal-config, ...) would compile that
|
|
373
|
+
// package into the archives. cmake is the one host tool a darwin build takes from the PATH.
|
|
374
|
+
if (target?.platform === 'darwin') {
|
|
375
|
+
if (dProgram === 'cmake') dProgram = findExecutable('cmake', env.PATH) ?? dProgram;
|
|
376
|
+
env.PATH = DARWIN_BUILD_PATH;
|
|
377
|
+
}
|
|
294
378
|
|
|
295
379
|
const options = {
|
|
296
380
|
cwd: dockerOptions.workdir || buildPath,
|
|
@@ -351,6 +435,17 @@ export default function run(program, params = [], platformPrefix = null, target
|
|
|
351
435
|
}
|
|
352
436
|
}
|
|
353
437
|
|
|
438
|
+
function findExecutable(name, searchPath) {
|
|
439
|
+
return searchPath.split(':').map((dir) => `${dir}/${name}`).find((file) => {
|
|
440
|
+
try {
|
|
441
|
+
fs.accessSync(file, fs.constants.X_OK);
|
|
442
|
+
return true;
|
|
443
|
+
} catch {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
354
449
|
function replaceBasePathForDocker(data) {
|
|
355
450
|
return replaceBasePathForDockerUtil(data, state.config.paths.base);
|
|
356
451
|
}
|
|
@@ -51,7 +51,7 @@ if(NOT PACKAGE_HOST_INDEX EQUAL -1)
|
|
|
51
51
|
target_link_libraries("${PROJECT_NAME}" INTERFACE "${PROJECT_LIBS_DIR}")
|
|
52
52
|
endif()
|
|
53
53
|
|
|
54
|
-
if(NOT APPLE)
|
|
54
|
+
if(NOT (APPLE AND CMAKE_SYSTEM_NAME STREQUAL "iOS"))
|
|
55
55
|
target_include_directories("${PROJECT_NAME}" INTERFACE "${PROJECT_SOURCE_DIR}/${PROJECT_TARGET_HOST}/include")
|
|
56
56
|
endif()
|
|
57
57
|
endif()
|
|
@@ -210,9 +210,13 @@ Comlink.transferHandlers.set('embindObject', {
|
|
|
210
210
|
&& typeof obj.isDeleted === 'function';
|
|
211
211
|
},
|
|
212
212
|
serialize(obj) {
|
|
213
|
-
|
|
213
|
+
// An object a property returns comes wrapped by its owner's coercion proxy. Registering and exposing
|
|
214
|
+
// the raw object, as the proxy handler does, keeps objects read from it one wrapper deep, which an
|
|
215
|
+
// argument's unwrapping undoes.
|
|
216
|
+
const raw = unwrapCoercionProxy(obj);
|
|
217
|
+
const id = registerEmbindObject(raw);
|
|
214
218
|
const { port1, port2 } = new MessageChannel();
|
|
215
|
-
Comlink.expose(wrapWithVectorCoercion(
|
|
219
|
+
Comlink.expose(wrapWithVectorCoercion(raw), port1);
|
|
216
220
|
return [{ __embindId: id, port: port2 }, [port2]];
|
|
217
221
|
},
|
|
218
222
|
deserialize(data) {
|
|
@@ -1,35 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
2
|
+
"version": "1.0.8",
|
|
3
3
|
"registry": "ghcr.io/crossbind",
|
|
4
4
|
"toolchains": {
|
|
5
5
|
"rust": "1.98.1"
|
|
6
6
|
},
|
|
7
7
|
"images": {
|
|
8
8
|
"rust-sysroot": {
|
|
9
|
-
"index": "sha256:
|
|
9
|
+
"index": "sha256:d816d27f1f3b110aed92f7f81cb8eb99f58ba95cafc96a797c46f30536549a45",
|
|
10
10
|
"platforms": {
|
|
11
|
-
"linux/arm64": "sha256:
|
|
12
|
-
"linux/amd64": "sha256:
|
|
11
|
+
"linux/arm64": "sha256:217f908b3e1c883bed8cd4db65aae48044481e88126c09c3ce9bf754288d8720",
|
|
12
|
+
"linux/amd64": "sha256:15719494b51ea8ee6f3a71556b2c04f37828e632bc46d2c3fb7121a1afb37303"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"base": {
|
|
16
|
-
"index": "sha256:
|
|
16
|
+
"index": "sha256:051aa70184d29e95d5192cc60cc42523b42fab72291efbf13f71006482f063c0",
|
|
17
17
|
"platforms": {
|
|
18
|
-
"linux/
|
|
19
|
-
"linux/
|
|
18
|
+
"linux/arm64": "sha256:cdf189ad4ae220698088fda54c4a5005a207542bba8ab2c01fe58fc008586df8",
|
|
19
|
+
"linux/amd64": "sha256:f501545972e2a253b19f1900946cfe39ce5dea74600d885cae47db3f320b506b"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"web": {
|
|
23
|
-
"index": "sha256:
|
|
23
|
+
"index": "sha256:89e0ea288d42d9f24671e43b3e59dc6b9913e389b68e51d8f0e314e81d111bf3",
|
|
24
24
|
"platforms": {
|
|
25
|
-
"linux/
|
|
26
|
-
"linux/
|
|
25
|
+
"linux/amd64": "sha256:40fe9f7573ee5a696c68043b12486e4562e0b2a87f74ef21a9b11c08454aff75",
|
|
26
|
+
"linux/arm64": "sha256:7172f994c262600dcd1334806eee9b44e873ac42fe286bf8b0983491586d1225"
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"android": {
|
|
30
|
-
"index": "sha256:
|
|
30
|
+
"index": "sha256:ef6dc5ed724e7ea977a1f644793967a5ad7ff62b9a6bb6f2971d4a2a9e8f5a4b",
|
|
31
31
|
"platforms": {
|
|
32
|
-
"linux/amd64": "sha256:
|
|
32
|
+
"linux/amd64": "sha256:d6843be98e4bc56a21e39b2670d081f60d27b45c149c0ee132d2c45bde337ec8"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"linux": {
|
|
36
|
+
"index": "sha256:e8b6e6afd1af9f3a84eee739bd25ce6abb8416c5af87a175b780cd18d871513d",
|
|
37
|
+
"platforms": {
|
|
38
|
+
"linux/amd64": "sha256:39fe90d1cb2fbb411823bc396d463e298020b5a93b9a5244f27644c5c0b87b2a",
|
|
39
|
+
"linux/arm64": "sha256:9cd9550a39010bf7f00bf08d8505a22f85e31155bf63c8a46597852dc72ef4cc"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"windows": {
|
|
43
|
+
"index": "sha256:c90a7c495e8becc6154b20fc6226362813938413bb3af626d0d0982cceaf32c5",
|
|
44
|
+
"platforms": {
|
|
45
|
+
"linux/amd64": "sha256:1fbc5c59e09253cfe86bb54fa76f0c1c6b1c1e942e4059725658340f64708e59",
|
|
46
|
+
"linux/arm64": "sha256:229389bbabe838124cbb572b91b42ae0e3fdcd6020462203fdbbcf5864c4a234"
|
|
33
47
|
}
|
|
34
48
|
}
|
|
35
49
|
}
|