crossbind 2.0.0-beta.62 → 2.0.0-beta.64
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/AGENTS.md +2 -1
- package/README.md +2 -2
- package/package.json +7 -2
- package/src/actions/buildBinTools.js +0 -0
- package/src/actions/buildDependencies.js +15 -2
- package/src/actions/buildExternal.js +9 -1
- package/src/actions/buildJs.js +2 -9
- package/src/actions/buildLib.js +25 -8
- package/src/actions/buildNativeCommand.js +84 -0
- package/src/actions/buildNode.js +49 -10
- package/src/actions/buildTypes.js +8 -2
- package/src/actions/buildWasiCommand.js +10 -9
- package/src/actions/buildWasm.js +72 -31
- package/src/actions/cloudCommands.js +133 -0
- package/src/actions/createImportedBridges.js +71 -0
- package/src/actions/createInterface.js +247 -68
- package/src/actions/createLib.js +70 -42
- package/src/actions/getData.js +6 -2
- package/src/actions/getEnv.js +7 -0
- package/src/actions/getLinkInputs.js +12 -5
- package/src/actions/isSourceNewer.js +2 -0
- package/src/actions/licenses.js +143 -19
- package/src/actions/prepareConanDependencies.js +51 -0
- package/src/actions/publishBinary.js +32 -0
- package/src/actions/run.js +63 -66
- package/src/actions/runnerCommands.js +433 -0
- package/src/actions/writeRuntimeEntry.js +80 -0
- 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/cpp-runtime/node.cpp +18 -1
- 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 +138 -60
- package/src/assets/js-runtime/core.js +12 -0
- package/src/assets/node-import-hooks.mjs +61 -0
- package/src/assets/toolchain-digests.json +19 -19
- package/src/assets/wasi-runtime/http/README.md +17 -0
- package/src/assets/wasi-runtime/http/bindings/crossbind_http.c +2537 -0
- package/src/assets/wasi-runtime/http/bindings/crossbind_http.h +826 -0
- package/src/assets/wasi-runtime/http/bindings/crossbind_http_component_type.o +0 -0
- package/src/assets/wasi-runtime/http/handler.cpp +298 -0
- package/src/assets/wasi-runtime/http/include/crossbind/http.h +31 -0
- package/src/assets/wasi-runtime/http/wit/deps/LICENSE.md +8 -0
- package/src/assets/wasi-runtime/http/wit/deps/cli.wit +28 -0
- package/src/assets/wasi-runtime/http/wit/deps/clocks.wit +43 -0
- package/src/assets/wasi-runtime/http/wit/deps/http.wit +509 -0
- package/src/assets/wasi-runtime/http/wit/deps/random.wit +18 -0
- package/src/assets/wasi-runtime/http/wit/world.wit +6 -0
- package/src/assets/wasi-runtime/stubs.c +29 -0
- package/src/bin.js +218 -161
- package/src/cli.js +14 -0
- package/src/index.js +3 -0
- package/src/integration/bindHeaderImports.js +18 -0
- package/src/integration/getCrossbindScript.js +50 -13
- package/src/integration/getDependFilePath.js +9 -0
- package/src/integration/resolveNativeImport.js +14 -0
- package/src/node/dev.mjs +19 -0
- package/src/program.js +169 -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 +20 -11
- package/src/state/loadConfig.js +37 -35
- 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 +19 -1
- package/src/utils/appSources.js +97 -6
- package/src/utils/archiveFlags.js +12 -0
- package/src/utils/boundHeaders.js +79 -0
- package/src/utils/bridgeExtras.js +554 -0
- package/src/utils/bridgeRuntimeFixes.js +48 -0
- package/src/utils/bundledLicenses.js +26 -0
- package/src/utils/cargoLock.js +19 -0
- package/src/utils/cargoTarget.js +21 -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 +60 -15
- 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 +41 -2
- package/src/utils/embindCallArguments.js +19 -0
- package/src/utils/embindJspiQueue.js +25 -0
- package/src/utils/embindObjectLifetime.js +35 -0
- package/src/utils/execContainer.js +49 -0
- package/src/utils/headerImports.js +16 -5
- package/src/utils/hostPlatform.js +21 -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/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 +103 -0
- package/src/utils/pthreadMainScript.js +14 -0
- package/src/utils/pullDockerImage.js +3 -1
- 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/resetCMakeCacheForSource.js +26 -0
- package/src/utils/runCargo.js +48 -51
- package/src/utils/runConan.js +257 -0
- package/src/utils/runtimeEntries.js +56 -0
- package/src/utils/rustBridgeGen.js +22 -8
- package/src/utils/rustMt.js +9 -0
- package/src/utils/rustSysroot.js +7 -5
- 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 +70 -4
- package/src/utils/systemConfig.js +13 -0
- package/src/utils/systemKeys.js +35 -3
- package/src/utils/targets.js +60 -4
- package/src/utils/toolchainNotices.js +5 -0
- package/src/utils/wasiHttp.js +50 -0
- package/src/utils/wasiToolchain.js +14 -1
- package/src/utils/windowsToolchain.js +18 -0
- package/vitest.config.js +11 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// The one module a package publishes per runtime binary, dist/<runtimeEnv>/<format>.mjs. It exports every
|
|
2
|
+
// bound name as an ESM live binding that initNative() fills, the way a bundler's module for a header does,
|
|
3
|
+
// so `import { initNative, X } from '<package>/node/napi'` works with or without init options.
|
|
4
|
+
|
|
5
|
+
const PROXY_NAMES = new Set(['initNative', 'AllSymbols']);
|
|
6
|
+
|
|
7
|
+
// A name is a plain string or { local, wire }: the second exports `local` while reading the differently
|
|
8
|
+
// registered `wire` off the module. Two headers may bind one name; the module exports it once.
|
|
9
|
+
export function uniqueBindings(names) {
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
return names
|
|
12
|
+
.map((name) => (typeof name === 'string' ? { local: name, wire: name } : name))
|
|
13
|
+
.filter(({ local }) => {
|
|
14
|
+
if (PROXY_NAMES.has(local) || seen.has(local)) return false;
|
|
15
|
+
seen.add(local);
|
|
16
|
+
return true;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Each runtime's loader, imported as `boot`, and the config it boots with; the binaries sit in dist, one
|
|
21
|
+
// directory above the entry.
|
|
22
|
+
export const ENTRY_RUNTIMES = {
|
|
23
|
+
// The Node-API loader and the Node wasm build are both CommonJS.
|
|
24
|
+
node: (loader) => ({
|
|
25
|
+
imports: ["import { createRequire } from 'node:module';", `const boot = createRequire(import.meta.url)('../${loader}');`],
|
|
26
|
+
config: 'config',
|
|
27
|
+
}),
|
|
28
|
+
// Edge runtimes cannot compile WebAssembly from bytes, so the binary is a module import their bundler resolves.
|
|
29
|
+
edge: (loader, wasm) => ({
|
|
30
|
+
imports: [`import boot from '../${loader}';`, `import wasm from '../${wasm}';`],
|
|
31
|
+
config: '{ getWasmFunction: () => wasm, ...config }',
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// A bound name may be a JavaScript reserved word, which an export list accepts but a declaration does not.
|
|
36
|
+
export function runtimeEntryModule(names, { imports, config }) {
|
|
37
|
+
const bindings = uniqueBindings(names);
|
|
38
|
+
return [
|
|
39
|
+
'// Generated by crossbind - do not edit.',
|
|
40
|
+
...imports,
|
|
41
|
+
'',
|
|
42
|
+
'export let AllSymbols = {};',
|
|
43
|
+
...bindings.map(({ local }) => `let $${local} = null;`),
|
|
44
|
+
...(bindings.length ? [`export { ${bindings.map(({ local }) => `$${local} as ${local}`).join(', ')} };`] : []),
|
|
45
|
+
'',
|
|
46
|
+
'export function initNative(config = {}) {',
|
|
47
|
+
` return boot(${config}).then((m) => {`,
|
|
48
|
+
' AllSymbols = m;',
|
|
49
|
+
...bindings.map(({ local, wire }) => ` $${local} = m.${wire};`),
|
|
50
|
+
' return m;',
|
|
51
|
+
' });',
|
|
52
|
+
'}',
|
|
53
|
+
'initNative.terminate = () => boot.terminate();',
|
|
54
|
+
'',
|
|
55
|
+
].join('\n');
|
|
56
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { followCargoLock } from './cargoLock.js';
|
|
4
5
|
import resolveEmbindRustRoot, { embindRustVersion } from './resolveEmbindRust.js';
|
|
5
6
|
import runCargo, { toHostPath } from './runCargo.js';
|
|
6
7
|
import writeIfChanged from './writeIfChanged.js';
|
|
@@ -179,6 +180,10 @@ function matchFnSignature(line) {
|
|
|
179
180
|
return [line, head[1], params, ret];
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
// What a lazy link pins to pull a bridge's registrations: the consumer's -u names `crossbind_keep_<lib>`.
|
|
184
|
+
export const keepSymbolName = (libName) => `crossbind_keep_${libName}`;
|
|
185
|
+
const keepSymbol = (libName) => `\n#[no_mangle]\npub extern "C" fn ${keepSymbolName(libName)}() {}\n`;
|
|
186
|
+
|
|
182
187
|
export default function generateRustBridge({ crateDir, vectors = [], dtsFile = null, keepName = null, dtsMode = 'sync', log = console.log }) {
|
|
183
188
|
const libRsPath = `${crateDir}/src/lib.rs`;
|
|
184
189
|
if (!fs.existsSync(libRsPath)) throw new Error(`crossbind: rust bridge: ${libRsPath} not found`);
|
|
@@ -192,7 +197,7 @@ export default function generateRustBridge({ crateDir, vectors = [], dtsFile = n
|
|
|
192
197
|
// own libstd, and fully loading two of them duplicates thousands of std symbols). Instead the
|
|
193
198
|
// consumer pins this keep symbol (-u/--undefined); codegen-units=1 puts it in the same object
|
|
194
199
|
// as the init-array constructor, so pulling it pulls the registrations - and only them.
|
|
195
|
-
if (keepName) bridge +=
|
|
200
|
+
if (keepName) bridge += keepSymbol(keepName);
|
|
196
201
|
if (dtsFile) writeIfChanged(dtsFile, emitDts(model, vectors, dtsMode));
|
|
197
202
|
|
|
198
203
|
const bridgeDir = `${crateDir}/.crossbind/bridge-crate`;
|
|
@@ -234,7 +239,8 @@ export default function generateRustBridge({ crateDir, vectors = [], dtsFile = n
|
|
|
234
239
|
// crate: the user file is embedded via `#[path] mod user;` (no copy) and the bridge lives in the
|
|
235
240
|
// same crate, so registrations reference `user::Type` directly. The bundler transformer calls
|
|
236
241
|
// this on import (like createBridgeFile for C++); the native builds compile every crate under
|
|
237
|
-
// <project>/.crossbind/rust-bridges/ and link
|
|
242
|
+
// <project>/.crossbind/rust-bridges/ into one staticlib and link it whole-archive, or on Windows
|
|
243
|
+
// through the keep symbol each bridge exports.
|
|
238
244
|
export function createRustBridgeCrate({ rsFile, cacheDir, projectPath, vectors = [], cargoDependencies = {}, dtsMode = 'sync', log = console.log }) {
|
|
239
245
|
const stem = path.basename(rsFile, '.rs').replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase();
|
|
240
246
|
const dir = `${cacheDir}/rust-bridges/${stem}`;
|
|
@@ -280,7 +286,7 @@ export function createRustBridgeCrate({ rsFile, cacheDir, projectPath, vectors =
|
|
|
280
286
|
].join('\n');
|
|
281
287
|
|
|
282
288
|
writeIfChanged(`${dir}/Cargo.toml`, manifest);
|
|
283
|
-
writeIfChanged(`${dir}/src/lib.rs`, bridge);
|
|
289
|
+
writeIfChanged(`${dir}/src/lib.rs`, bridge + keepSymbol(`${stem}_crossbind_app`));
|
|
284
290
|
// Relative `./x.rs` imports are typed by path resolution (ambient declarations only work
|
|
285
291
|
// for non-relative names like `cargo:x`), so the declaration mirrors the project-relative
|
|
286
292
|
// path under the language-neutral <cache>/types/ overlay (same home as .h declarations);
|
|
@@ -298,7 +304,9 @@ export function createRustBridgeCrate({ rsFile, cacheDir, projectPath, vectors =
|
|
|
298
304
|
// through the root and through a module registers once and is the same JS class for both.
|
|
299
305
|
const crateSourceCache = new Map();
|
|
300
306
|
const crateModelCache = new Map();
|
|
301
|
-
export function createCrateImportBridge({
|
|
307
|
+
export function createCrateImportBridge({
|
|
308
|
+
crateName, modulePath = [], spec, cacheDir, projectDir, dtsMode = 'sync', log = console.log,
|
|
309
|
+
}) {
|
|
302
310
|
const importName = [crateName, ...modulePath].join('/');
|
|
303
311
|
const crateIdent = crateName.replaceAll('-', '_');
|
|
304
312
|
const stem = `crate_${crateName.replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase()}`;
|
|
@@ -338,7 +346,9 @@ export function createCrateImportBridge({ crateName, modulePath = [], spec, cach
|
|
|
338
346
|
// cargo metadata needs a resolvable lib target before the real bridge exists.
|
|
339
347
|
if (!fs.existsSync(`${dir}/src/lib.rs`)) writeIfChanged(`${dir}/src/lib.rs`, '// crossbind placeholder\n');
|
|
340
348
|
|
|
341
|
-
const { srcDir, features } = crateSource({
|
|
349
|
+
const { srcDir, features } = crateSource({
|
|
350
|
+
dir, depLine, crateName, importName, projectDir,
|
|
351
|
+
});
|
|
342
352
|
const ownKey = modulePath.join('::');
|
|
343
353
|
const imports = [];
|
|
344
354
|
for (const importPath of crateImports(cacheDir, crateName, modulePath)) {
|
|
@@ -367,7 +377,7 @@ export function createCrateImportBridge({ crateName, modulePath = [], spec, cach
|
|
|
367
377
|
writeIfChanged(`${dir}/Cargo.toml`, manifestWith(shared.model.usesJson ? jsonDeps : []));
|
|
368
378
|
writeIfChanged(`${dir}/src/lib.rs`, emitBridge(shared.model, {
|
|
369
379
|
userCrate: crateIdent, vectors: [], log, rustPaths: shared.rustPaths, wireNames: shared.wireNames,
|
|
370
|
-
}));
|
|
380
|
+
}) + keepSymbol(`${stem}_crossbind_app`));
|
|
371
381
|
for (const { modulePath: importPath } of imports) {
|
|
372
382
|
writeImportDts({ cacheDir, crateName, modulePath: importPath, shared, dtsMode });
|
|
373
383
|
}
|
|
@@ -380,10 +390,14 @@ export function createCrateImportBridge({ crateName, modulePath = [], spec, cach
|
|
|
380
390
|
}
|
|
381
391
|
|
|
382
392
|
// cargo metadata locates the crate's source and the feature set it resolves to (fetching the crate
|
|
383
|
-
// on first use); both hold for every import of the crate.
|
|
384
|
-
|
|
393
|
+
// on first use); both hold for every import of the crate. It resolves with the project's pins, so
|
|
394
|
+
// the bridge is made from the version the app links.
|
|
395
|
+
function crateSource({
|
|
396
|
+
dir, depLine, crateName, importName, projectDir,
|
|
397
|
+
}) {
|
|
385
398
|
const cacheKey = `${dir}|${depLine}`;
|
|
386
399
|
if (crateSourceCache.has(cacheKey)) return crateSourceCache.get(cacheKey);
|
|
400
|
+
followCargoLock(projectDir, dir);
|
|
387
401
|
let meta;
|
|
388
402
|
try {
|
|
389
403
|
const probe = runCargo(['metadata', '--format-version', '1', '--manifest-path', `${dir}/Cargo.toml`], {
|
package/src/utils/rustMt.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
// One argv per flag: runCargo joins these with cargo's 0x1F separator, so a flag must not
|
|
5
6
|
// contain a space of its own.
|
|
6
7
|
const MT_RUSTFLAGS = ['-Ctarget-feature=+atomics,+bulk-memory,+mutable-globals'];
|
|
8
|
+
// A musl std links Rust's own static libunwind under crt-static, its default; the addon's C++ already
|
|
9
|
+
// unwinds through libgcc_s, and one module must not carry two unwinders.
|
|
10
|
+
const MUSL_RUSTFLAGS = ['-Ctarget-feature=-crt-static'];
|
|
7
11
|
|
|
8
12
|
// mt (shared-memory) wasm needs every Rust object built with the atomics/bulk-memory features,
|
|
9
13
|
// std included - and rustup ships only a featureless std. The nightly-only -Zbuild-std rebuild
|
|
@@ -36,6 +40,10 @@ export function cargoTargetDirFor(baseDir, target) {
|
|
|
36
40
|
// points at whatever version that image pinned, so the CLI carries no version of its own.
|
|
37
41
|
const CONTAINER_SYSROOT = '/opt/crossbind/rust/current';
|
|
38
42
|
|
|
43
|
+
// True only inside the toolchain image. A CLI running there with RUNNER=LOCAL (the playground
|
|
44
|
+
// compiler) has these trees, but not rustup's stock wasm target.
|
|
45
|
+
export const hasContainerSysroot = () => fs.existsSync(CONTAINER_SYSROOT);
|
|
46
|
+
|
|
39
47
|
export function cargoBuildInvocation({
|
|
40
48
|
target, triple, targetDir, manifestPath, sysroot = false,
|
|
41
49
|
}) {
|
|
@@ -66,6 +74,7 @@ export function cargoBuildInvocation({
|
|
|
66
74
|
rustflags: [
|
|
67
75
|
...(wasmSysroot ? ['--sysroot', wasmSysroot] : []),
|
|
68
76
|
...(isMt ? MT_RUSTFLAGS : []),
|
|
77
|
+
...(target.platform === 'linuxmusl' ? MUSL_RUSTFLAGS : []),
|
|
69
78
|
],
|
|
70
79
|
panic: target.platform === 'wasm' ? 'abort' : undefined,
|
|
71
80
|
// Only the nightly build-std rebuild needs unstable flags; where a sysroot answers, stable
|
package/src/utils/rustSysroot.js
CHANGED
|
@@ -5,7 +5,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
5
5
|
import withDirLock from './dirLock.js';
|
|
6
6
|
import fetchOciLayer from './ociArtifact.js';
|
|
7
7
|
import { cargoRunner } from './runCargo.js';
|
|
8
|
-
import { isMtWasm } from './rustMt.js';
|
|
8
|
+
import { hasContainerSysroot, isMtWasm } from './rustMt.js';
|
|
9
9
|
import PIN from './rustSysrootPin.js';
|
|
10
10
|
|
|
11
11
|
// The second consumption channel for the Rust sysroots. A containerized build gets them as an image
|
|
@@ -149,7 +149,7 @@ let resolved = null;
|
|
|
149
149
|
// Called from buildDependencies, the one async step every build path awaits before the sync work
|
|
150
150
|
// starts. Only downloads when a target will actually consume it.
|
|
151
151
|
export async function prepareRustSysroot(targets, log = console.log) {
|
|
152
|
-
if (resolved || !PIN) return resolved;
|
|
152
|
+
if (resolved || !PIN || hasContainerSysroot()) return resolved;
|
|
153
153
|
if (!targets.some((target) => isMtWasm(target) && cargoRunner(target) === 'LOCAL')) return null;
|
|
154
154
|
try {
|
|
155
155
|
resolved = await ensureRustSysroot({ image: PIN.image, index: PIN.index });
|
|
@@ -161,11 +161,13 @@ export async function prepareRustSysroot(targets, log = console.log) {
|
|
|
161
161
|
return resolved;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
// What cargoBuildInvocation takes for `sysroot`: true where the image carries the trees
|
|
165
|
-
// absolute path where a downloaded artifact answers,
|
|
166
|
-
// st needs nothing - rustup's stock std is already
|
|
164
|
+
// What cargoBuildInvocation takes for `sysroot`: true where the image carries the trees (also a
|
|
165
|
+
// LOCAL build running inside the image), an absolute path where a downloaded artifact answers,
|
|
166
|
+
// false where mt must rebuild std on nightly. st needs nothing - rustup's stock std is already
|
|
167
|
+
// correct for it.
|
|
167
168
|
export function sysrootFor(target) {
|
|
168
169
|
if (cargoRunner(target) !== 'LOCAL') return true;
|
|
170
|
+
if (target.platform === 'wasm' && hasContainerSysroot()) return true;
|
|
169
171
|
if (!isMtWasm(target)) return false;
|
|
170
172
|
return resolved ?? false;
|
|
171
173
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
// Every package's loader bundles its own embind runtime, which keeps its state, and the functions its addon calls by
|
|
4
|
+
// name, on globalThis and Module. Shadowing both gives each bundle a scope of its own with the real global as its
|
|
5
|
+
// prototype, which the loader hands to its addon: addons of several packages then run in one process.
|
|
6
|
+
export default function scopedEmbind(embindFile) {
|
|
7
|
+
const realPath = (file) => {
|
|
8
|
+
try {
|
|
9
|
+
return fs.realpathSync(file);
|
|
10
|
+
} catch (e) {
|
|
11
|
+
return file;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const embind = realPath(embindFile);
|
|
15
|
+
return {
|
|
16
|
+
name: 'crossbind-scoped-embind',
|
|
17
|
+
transform(code, id) {
|
|
18
|
+
if (realPath(id) !== embind) return null;
|
|
19
|
+
return `const globalThis = Object.create(global);\nconst Module = (globalThis.Module = {});\n${code}\nexport { globalThis as crossbindScope };\n`;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { cloudCredentials, cloudUrl } from './cloudAccount.js';
|
|
2
|
+
import logger from './logger.js';
|
|
3
|
+
import systemKeys, { assertRunner } from './systemKeys.js';
|
|
4
|
+
|
|
5
|
+
const RUNNER_VARIABLE = 'CROSSBIND_RUNNER';
|
|
6
|
+
const SYSTEM_CONFIG = '~/.crossbind.json';
|
|
7
|
+
const SHARED = { key: 'REMOTE_URL', url: 'CROSSBIND_REMOTE_URL', token: 'CROSSBIND_TOKEN' };
|
|
8
|
+
|
|
9
|
+
export const remoteVariables = (role) => {
|
|
10
|
+
const suffix = `_${role.toUpperCase()}`;
|
|
11
|
+
return { key: `${SHARED.key}${suffix}`, url: `${SHARED.url}${suffix}`, token: `${SHARED.token}${suffix}` };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Unchecked, so commands that run no step still work with an invalid RUNNER, `crossbind config set` among them.
|
|
15
|
+
function runnerSetting(system, env) {
|
|
16
|
+
if (env[RUNNER_VARIABLE]) return { runner: env[RUNNER_VARIABLE], name: RUNNER_VARIABLE };
|
|
17
|
+
return { runner: system?.RUNNER ?? systemKeys.RUNNER.default, name: `RUNNER in ${SYSTEM_CONFIG}` };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function chosenRunner(system, env = process.env) {
|
|
21
|
+
const { runner, name } = runnerSetting(system, env);
|
|
22
|
+
return assertRunner(runner, name);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const isLocalRunner = (system, env = process.env) => runnerSetting(system, env).runner === 'LOCAL';
|
|
26
|
+
|
|
27
|
+
// A variable wins over the same key in the system config, and an image's own address over the shared one.
|
|
28
|
+
// A token goes only to the address it is paired with, so a shared token never reaches the runner of one image.
|
|
29
|
+
function remoteAddress(role, system, env) {
|
|
30
|
+
for (const names of [remoteVariables(role), SHARED]) {
|
|
31
|
+
const url = env[names.url] || system?.[names.key];
|
|
32
|
+
if (url) {
|
|
33
|
+
const from = env[names.url] ? `$${names.url}` : `${names.key} in ${SYSTEM_CONFIG}`;
|
|
34
|
+
if (!['http:', 'https:'].includes(URL.parse(url)?.protocol)) {
|
|
35
|
+
throw new Error(`crossbind: ${from} is ${JSON.stringify(url)}, not an http or https address.`);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
url, from, token: env[names.token], tokenVariable: names.token,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const announced = new Set();
|
|
46
|
+
|
|
47
|
+
// Each image says once which runner its steps go to and which settings sent them there.
|
|
48
|
+
function announce(role, remote, env) {
|
|
49
|
+
const key = `${role} ${remote.url}`;
|
|
50
|
+
if (announced.has(key)) return;
|
|
51
|
+
announced.add(key);
|
|
52
|
+
const chosenBy = env[RUNNER_VARIABLE] ? `$${RUNNER_VARIABLE}` : SYSTEM_CONFIG;
|
|
53
|
+
const shown = remote.url.replace(/\/\/[^/]*@/, '//');
|
|
54
|
+
logger.info(`crossbind: ${role} steps run on the runner at ${shown} (RUNNER=REMOTE from ${chosenBy}, address from ${remote.from}).`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// A runner address named anywhere, for any image, keeps every image off the cloud: the sources go only where the
|
|
58
|
+
// user sends them.
|
|
59
|
+
const namesOwnRunner = (system, env) => Object.keys(systemKeys).some((key) => key.startsWith(SHARED.key) && system?.[key])
|
|
60
|
+
|| Object.keys(env).some((name) => name.startsWith(SHARED.url) && env[name]);
|
|
61
|
+
|
|
62
|
+
// The sign-in of `crossbind login` carries its own token; a token set for runners never reaches the cloud.
|
|
63
|
+
function cloudAddress(role, system, env) {
|
|
64
|
+
const url = cloudUrl(system, env);
|
|
65
|
+
const signedIn = cloudCredentials(url);
|
|
66
|
+
if (!signedIn) return null;
|
|
67
|
+
return {
|
|
68
|
+
url: `${url}/runner/${role}`, from: `crossbind login as ${signedIn.login}`, token: signedIn.token, tokenVariable: null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Under REMOTE, an image runs on its own address, else the shared one; a machine that names no runner address at all
|
|
73
|
+
// runs on crossbind cloud once signed in. Without any it gets no runner: its steps stop instead of running in the
|
|
74
|
+
// local Docker, where the build would only seem to run on a runner.
|
|
75
|
+
export function runnerFor(role, system, env = process.env) {
|
|
76
|
+
const runner = chosenRunner(system, env);
|
|
77
|
+
if (runner !== 'REMOTE') return { runner };
|
|
78
|
+
const remote = remoteAddress(role, system, env) ?? (namesOwnRunner(system, env) ? null : cloudAddress(role, system, env));
|
|
79
|
+
if (remote) announce(role, remote, env);
|
|
80
|
+
return { runner, remote };
|
|
81
|
+
}
|
|
@@ -6,12 +6,13 @@ import { getContentHash } from './hash.js';
|
|
|
6
6
|
// version, with provenance and licence metadata already claiming the new one.
|
|
7
7
|
export const SOURCE_FINGERPRINT_FILE = 'crossbind-source.fingerprint';
|
|
8
8
|
|
|
9
|
-
export function getSourceFingerprint(config) {
|
|
9
|
+
export function getSourceFingerprint(config, target) {
|
|
10
10
|
const nativeVersion = config?.package?.nativeVersion || null;
|
|
11
11
|
const sha256 = config?.build?.sha256 || null;
|
|
12
|
-
//
|
|
12
|
+
// Local-source packages pin neither; buildLib checks their native source inventory instead.
|
|
13
13
|
if (!nativeVersion && !sha256) return null;
|
|
14
|
-
|
|
14
|
+
const sourceReplacements = target ? config?.build?.sourceReplaceList?.(target, config.allDependencyPaths?.[target.path] ?? {}) : undefined;
|
|
15
|
+
return getContentHash(JSON.stringify({ nativeVersion, sha256, ...(sourceReplacements?.length ? { sourceReplacements } : {}) }));
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function isSourceFingerprintStale(libdir, fingerprint) {
|
|
@@ -16,6 +16,29 @@ export function findIgnoredDeclarations(headerFile, configs, headerPath) {
|
|
|
16
16
|
return findHeaderOption('ignoredDeclarations', headerFile, configs, headerPath);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// SWIG preprocessor lines a package puts right before one of its public headers, in export.swigPreamble: GDAL hides
|
|
20
|
+
// its C SRS API behind `#ifndef SWIG` for its own bindings, and picks VSIStatBufL's typedef by whether VSI_STAT64_T is
|
|
21
|
+
// defined, which a macro defined as itself answers while each platform's compiler still resolves the name.
|
|
22
|
+
export function findSwigPreamble(headerFile, configs, headerPath) {
|
|
23
|
+
return findHeaderOption('swigPreamble', headerFile, configs, headerPath);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Includes SWIG reads inlined into one of a package's headers, in export.swigInlineIncludes. SWIG reads no #include, and
|
|
27
|
+
// libgeotiff fills its key and code enums from .inc files included inside the enum bodies, where the file markers
|
|
28
|
+
// -includeall wraps around an included file do not parse.
|
|
29
|
+
export function findSwigInlineIncludes(headerFile, configs, headerPath) {
|
|
30
|
+
return findHeaderOption('swigInlineIncludes', headerFile, configs, headerPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const INCLUDE_LINE = /^[ \t]*#[ \t]*include[ \t]*"([^"]+)"[^\n]*$/gm;
|
|
34
|
+
|
|
35
|
+
export function inlineIncludes(headerText, names, readInclude) {
|
|
36
|
+
const included = new Set([...headerText.matchAll(INCLUDE_LINE)].map(([, name]) => name));
|
|
37
|
+
const missing = names.filter((name) => !included.has(name));
|
|
38
|
+
if (missing.length) throw new Error(`crossbind: swigInlineIncludes lists ${missing.join(', ')}, which the header does not include.`);
|
|
39
|
+
return headerText.replace(INCLUDE_LINE, (line, name) => (names.includes(name) ? readInclude(name) : line));
|
|
40
|
+
}
|
|
41
|
+
|
|
19
42
|
function findHeaderOption(option, headerFile, configs, headerPath) {
|
|
20
43
|
const file = upath.normalize(headerFile);
|
|
21
44
|
let owner = null;
|
|
@@ -40,26 +63,36 @@ export function interfaceIncludes(headerPath, prelude = []) {
|
|
|
40
63
|
|
|
41
64
|
// The prelude and the completing includes reach only the compiled wrapper and swigMacros only SWIG's parse: SWIG still
|
|
42
65
|
// wraps just the imported header. The ignored declarations follow the macros, so the retry without macros keeps them.
|
|
43
|
-
export function buildInterfaceContent({
|
|
66
|
+
export function buildInterfaceContent({
|
|
67
|
+
moduleName, headerPath, prelude = [], completing = [], swigMacros = [], ignored = [], constants = [], preamble = [], functions = ALL_NAMES,
|
|
68
|
+
extras = [],
|
|
69
|
+
}) {
|
|
44
70
|
const includes = [...new Set([...interfaceIncludes(headerPath, prelude), ...completing])]
|
|
45
71
|
.map((header) => `#include "${header}"`)
|
|
46
72
|
.join('\n');
|
|
47
73
|
const macros = swigMacros.length ? `${swigMacros.join('\n')}\n\n` : '';
|
|
48
74
|
const ignores = ignored.length ? `${ignored.map((name) => `%ignore ${name};`).join('\n')}\n\n` : '';
|
|
75
|
+
// The marker keeps a preamble that starts with #define apart from the macro block the retry drops.
|
|
76
|
+
const preambleBlock = preamble.length ? `// package preamble\n${preamble.join('\n')}\n\n` : '';
|
|
77
|
+
const extraBlock = extras.length ? `${extras.join('\n')}\n\n` : '';
|
|
78
|
+
// Named constants and functions are asked for ahead of the macros, which may define an imported name. Every constant
|
|
79
|
+
// is asked for after them: SWIG binds an interface's own #define lines, and the prelude only carries other headers'
|
|
80
|
+
// values for the parse.
|
|
81
|
+
const everyConstant = constants === ALL_NAMES;
|
|
49
82
|
const module = moduleName.replace(/\W/g, '_');
|
|
50
83
|
return `#ifndef _${module}_I
|
|
51
84
|
#define _${module}_I
|
|
52
85
|
|
|
53
86
|
%module ${module}
|
|
54
87
|
|
|
55
|
-
%{
|
|
88
|
+
${extraBlock}%{
|
|
56
89
|
${includes}
|
|
57
90
|
%}
|
|
58
91
|
|
|
59
|
-
${constantRequests(constants)}%feature("shared_ptr");
|
|
92
|
+
${everyConstant ? '' : constantRequests(constants)}${functionRequests(functions)}%feature("shared_ptr");
|
|
60
93
|
%feature("polymorphic_shared_ptr");
|
|
61
94
|
|
|
62
|
-
${macros}${ignores}%include "${headerPath}"
|
|
95
|
+
${macros}${ignores}${preambleBlock}${everyConstant ? constantRequests(constants) : ''}%include "${headerPath}"
|
|
63
96
|
|
|
64
97
|
#endif
|
|
65
98
|
`;
|
|
@@ -71,6 +104,19 @@ function constantRequests(constants) {
|
|
|
71
104
|
return constants.length ? `${constants.map((name) => `%feature("embind:constant") ${name};`).join('\n')}\n\n` : '';
|
|
72
105
|
}
|
|
73
106
|
|
|
107
|
+
// SWIG ignores every global function, then wraps the listed names again: class members, enums and constants stay. The
|
|
108
|
+
// package's own ignores come after, since SWIG applies the later of two renames of a name.
|
|
109
|
+
function functionRequests(functions) {
|
|
110
|
+
if (functions === ALL_NAMES) return '';
|
|
111
|
+
const names = [...new Set(functions)].sort();
|
|
112
|
+
return `%rename($ignore, %$isfunction, %$isglobal) "";\n${names.map((name) => `%rename("%s") ${name};\n`).join('')}\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Editor declarations need the whole header even when the compiled bridge selects functions by import.
|
|
116
|
+
export function interfaceWithAllFunctions(content) {
|
|
117
|
+
return content.replace(/%rename\(\$ignore, %\$isfunction, %\$isglobal\) "";\n(?:%rename\("%s"\) [A-Za-z_]\w*;\n)*\n/g, '');
|
|
118
|
+
}
|
|
119
|
+
|
|
74
120
|
export function withoutSwigMacros(content) {
|
|
75
121
|
return content.replace(/(%feature\("polymorphic_shared_ptr"\);\n\n)(?:#define [^\n]*\n)+\n/, '$1');
|
|
76
122
|
}
|
|
@@ -333,6 +379,26 @@ function walkScopes(text, visit) {
|
|
|
333
379
|
});
|
|
334
380
|
}
|
|
335
381
|
|
|
382
|
+
// Whether the token at `index` sits inside the parentheses of the declaration it belongs to: a prototype's parameter.
|
|
383
|
+
function isInsideParentheses(tokens, index) {
|
|
384
|
+
let depth = 0;
|
|
385
|
+
for (let at = index - 1; at >= 0 && ![';', '{', '}'].includes(tokens[at]); at -= 1) {
|
|
386
|
+
if (tokens[at] === '(') depth += 1;
|
|
387
|
+
else if (tokens[at] === ')') depth -= 1;
|
|
388
|
+
}
|
|
389
|
+
return depth > 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Whether a variable `name` is declared outside every class, namespace and block, as `extern int name;` is.
|
|
393
|
+
export function declaresAtFileScope(text, name) {
|
|
394
|
+
let found = false;
|
|
395
|
+
walkScopes(text, (token, index, tokens, scopes) => {
|
|
396
|
+
if (token === name && scopes.every((scope) => scope.kind === 'extern') && [';', '=', '[', ','].includes(tokens[index + 1])
|
|
397
|
+
&& !isInsideParentheses(tokens, index)) found = true;
|
|
398
|
+
});
|
|
399
|
+
return found;
|
|
400
|
+
}
|
|
401
|
+
|
|
336
402
|
// Classes, structs and enums by qualified name, including the names C typedefs give them, each mapped to the first header
|
|
337
403
|
// defining it. Preprocessor branches are all read.
|
|
338
404
|
export function indexTypeDefinitions(files) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import loadJson from './loadJson.js';
|
|
3
|
+
import systemKeys from './systemKeys.js';
|
|
4
|
+
|
|
5
|
+
export const systemConfigPath = () => `${os.homedir()}/.crossbind.json`;
|
|
6
|
+
|
|
7
|
+
export default function loadSystemConfig() {
|
|
8
|
+
const system = loadJson(systemConfigPath()) || {};
|
|
9
|
+
const defaults = Object.entries(systemKeys)
|
|
10
|
+
.filter(([key]) => !(key in system))
|
|
11
|
+
.map(([key, value]) => [key, value.default]);
|
|
12
|
+
return { ...system, ...Object.fromEntries(defaults) };
|
|
13
|
+
}
|
package/src/utils/systemKeys.js
CHANGED
|
@@ -4,12 +4,36 @@ const systemKeys = {
|
|
|
4
4
|
default: '',
|
|
5
5
|
},
|
|
6
6
|
RUNNER: {
|
|
7
|
-
description: '
|
|
8
|
-
options: ['DOCKER_RUN', 'DOCKER_EXEC', 'LOCAL'],
|
|
7
|
+
description: 'Where toolchain steps run: DOCKER_RUN, DOCKER_EXEC, LOCAL, or REMOTE on the runners REMOTE_URL and REMOTE_URL_<IMAGE> name; a machine that names none builds on crossbind cloud once signed in with crossbind login. The CROSSBIND_RUNNER environment variable overrides it.',
|
|
8
|
+
options: ['DOCKER_RUN', 'DOCKER_EXEC', 'LOCAL', 'REMOTE'],
|
|
9
9
|
default: 'DOCKER_RUN',
|
|
10
10
|
},
|
|
11
|
+
REMOTE_URL: {
|
|
12
|
+
description: 'Under RUNNER=REMOTE, the runner of every image without an address of its own. Its token comes from the CROSSBIND_TOKEN environment variable. The CROSSBIND_REMOTE_URL environment variable overrides it.',
|
|
13
|
+
default: '',
|
|
14
|
+
},
|
|
15
|
+
REMOTE_URL_WEB: {
|
|
16
|
+
description: 'Under RUNNER=REMOTE, the runner of the web image: wasm builds, wasi builds and the bridge steps of every platform but android. Token: CROSSBIND_TOKEN_WEB. The CROSSBIND_REMOTE_URL_WEB environment variable overrides it.',
|
|
17
|
+
default: '',
|
|
18
|
+
},
|
|
19
|
+
REMOTE_URL_ANDROID: {
|
|
20
|
+
description: 'Under RUNNER=REMOTE, the runner of the android image. Token: CROSSBIND_TOKEN_ANDROID. The CROSSBIND_REMOTE_URL_ANDROID environment variable overrides it.',
|
|
21
|
+
default: '',
|
|
22
|
+
},
|
|
23
|
+
REMOTE_URL_LINUX: {
|
|
24
|
+
description: 'Under RUNNER=REMOTE, the runner of the linux image (linux and linuxmusl builds). Token: CROSSBIND_TOKEN_LINUX. The CROSSBIND_REMOTE_URL_LINUX environment variable overrides it.',
|
|
25
|
+
default: '',
|
|
26
|
+
},
|
|
27
|
+
REMOTE_URL_WINDOWS: {
|
|
28
|
+
description: 'Under RUNNER=REMOTE, the runner of the windows image (win32 builds). Token: CROSSBIND_TOKEN_WINDOWS. The CROSSBIND_REMOTE_URL_WINDOWS environment variable overrides it.',
|
|
29
|
+
default: '',
|
|
30
|
+
},
|
|
31
|
+
CLOUD_URL: {
|
|
32
|
+
description: 'The crossbind cloud that crossbind login, logout and usage talk to. The CROSSBIND_CLOUD_URL environment variable overrides it.',
|
|
33
|
+
default: 'https://api.crossbind.dev',
|
|
34
|
+
},
|
|
11
35
|
WASI_SDK_PATH: {
|
|
12
|
-
description: 'Path to an extracted wasi-sdk (>= 34, wasm32-wasip3 sysroot)
|
|
36
|
+
description: 'Path to an extracted wasi-sdk (>= 34, wasm32-wasip3 sysroot) that platform:\'wasi\' builds use under RUNNER=LOCAL; the other runners use the sdk in the image. The CROSSBIND_WASI_SDK_PATH environment variable overrides it.',
|
|
13
37
|
default: '',
|
|
14
38
|
},
|
|
15
39
|
DOCKER_REGISTRY_MIRROR: {
|
|
@@ -35,3 +59,11 @@ const systemKeys = {
|
|
|
35
59
|
};
|
|
36
60
|
|
|
37
61
|
export default systemKeys;
|
|
62
|
+
|
|
63
|
+
// Read as LOCAL, a runner crossbind does not know would run recipes and build scripts on the host.
|
|
64
|
+
export function assertRunner(runner, name = 'RUNNER') {
|
|
65
|
+
if (!systemKeys.RUNNER.options.includes(runner)) {
|
|
66
|
+
throw new Error(`crossbind: the runner ${runner} is invalid; ${name} is one of ${systemKeys.RUNNER.options.join(', ')}.`);
|
|
67
|
+
}
|
|
68
|
+
return runner;
|
|
69
|
+
}
|
package/src/utils/targets.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Single source for the build-target matrix and its naming; state/ copies (and then
|
|
2
2
|
// decorates) these entries, and consumer runtimes read them without touching CLI state.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const BASE_TARGETS = [
|
|
5
5
|
{
|
|
6
6
|
platform: 'wasm', arch: 'wasm32', runtime: 'st', buildType: 'release', runtimeEnv: 'browser',
|
|
7
7
|
},
|
|
@@ -63,10 +63,16 @@ export const TARGETS = [
|
|
|
63
63
|
platform: 'wasm', arch: 'wasm64', runtime: 'mt', buildType: 'debug', runtimeEnv: 'node',
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
|
-
platform: 'wasi', arch: 'wasm32', runtime: 'st', buildType: 'release',
|
|
66
|
+
platform: 'wasi', arch: 'wasm32', runtime: 'st', buildType: 'release', runtimeEnv: 'wasi',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
platform: 'wasi', arch: 'wasm32', runtime: 'st', buildType: 'debug', runtimeEnv: 'wasi',
|
|
67
70
|
},
|
|
68
71
|
{
|
|
69
|
-
platform: 'wasi', arch: 'wasm32', runtime: 'st', buildType: '
|
|
72
|
+
platform: 'wasi', arch: 'wasm32', runtime: 'st', buildType: 'release', runtimeEnv: 'http',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
platform: 'wasi', arch: 'wasm32', runtime: 'st', buildType: 'debug', runtimeEnv: 'http',
|
|
70
76
|
},
|
|
71
77
|
{
|
|
72
78
|
platform: 'android', arch: 'arm64-v8a', runtime: 'mt', buildType: 'release',
|
|
@@ -116,6 +122,18 @@ export const TARGETS = [
|
|
|
116
122
|
{
|
|
117
123
|
platform: 'linux', arch: 'x64', runtime: 'mt', buildType: 'debug', runtimeEnv: 'node',
|
|
118
124
|
},
|
|
125
|
+
{
|
|
126
|
+
platform: 'linuxmusl', arch: 'arm64', runtime: 'mt', buildType: 'release', runtimeEnv: 'node',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
platform: 'linuxmusl', arch: 'arm64', runtime: 'mt', buildType: 'debug', runtimeEnv: 'node',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
platform: 'linuxmusl', arch: 'x64', runtime: 'mt', buildType: 'release', runtimeEnv: 'node',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
platform: 'linuxmusl', arch: 'x64', runtime: 'mt', buildType: 'debug', runtimeEnv: 'node',
|
|
136
|
+
},
|
|
119
137
|
{
|
|
120
138
|
platform: 'win32', arch: 'arm64', runtime: 'mt', buildType: 'release', runtimeEnv: 'node',
|
|
121
139
|
},
|
|
@@ -130,17 +148,43 @@ export const TARGETS = [
|
|
|
130
148
|
},
|
|
131
149
|
];
|
|
132
150
|
|
|
151
|
+
// A native executable links the same archives as the Node-API addon of its platform and arch.
|
|
152
|
+
export const TARGETS = [
|
|
153
|
+
...BASE_TARGETS,
|
|
154
|
+
...BASE_TARGETS
|
|
155
|
+
.filter((target) => target.runtimeEnv === 'node' && target.platform !== 'wasm')
|
|
156
|
+
.map((target) => ({ ...target, runtimeEnv: 'native' })),
|
|
157
|
+
];
|
|
158
|
+
|
|
133
159
|
// Built only when named with -p: a plain `crossbind build` keeps producing what it did before
|
|
134
160
|
// native Node.js addons existed.
|
|
135
|
-
export const OPT_IN_PLATFORMS = ['darwin', 'linux', 'win32'];
|
|
161
|
+
export const OPT_IN_PLATFORMS = ['darwin', 'linux', 'linuxmusl', 'win32'];
|
|
136
162
|
|
|
137
163
|
// Built on the host itself: Apple's linker and SDKs exist in no toolchain image.
|
|
138
164
|
export const HOST_BUILT_PLATFORMS = ['ios', 'darwin'];
|
|
139
165
|
|
|
166
|
+
// The runtime environment names the binary a build makes; android and ios have none, since
|
|
167
|
+
// React Native links them.
|
|
168
|
+
export const runtimeEnvsOf = (platform) => [
|
|
169
|
+
...new Set(TARGETS.filter((target) => target.platform === platform && target.runtimeEnv).map((target) => target.runtimeEnv)),
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
export const RUNTIME_ENVS = [...new Set(TARGETS.map((target) => target.runtimeEnv).filter(Boolean))];
|
|
173
|
+
|
|
174
|
+
// A build makes only the binaries it is asked for: -e first, then the config's target.runtimeEnv.
|
|
175
|
+
// Bundler plugins ask through getTargetParams instead.
|
|
176
|
+
export function selectRuntimeEnvs(cliRuntimeEnvs, configRuntimeEnv) {
|
|
177
|
+
if (cliRuntimeEnvs?.length) return cliRuntimeEnvs;
|
|
178
|
+
return configRuntimeEnv ? [configRuntimeEnv] : [];
|
|
179
|
+
}
|
|
180
|
+
|
|
140
181
|
export function targetPathOf(target) {
|
|
141
182
|
return `${target.platform}-${target.arch}-${target.runtime}-${target.buildType}`;
|
|
142
183
|
}
|
|
143
184
|
|
|
185
|
+
// The release build of a target, whose prebuilts a debug build of it falls back to.
|
|
186
|
+
export const releaseTargetOf = (target) => ({ ...target, buildType: 'release', path: target.releasePath });
|
|
187
|
+
|
|
144
188
|
// Every platform/arch addon of one build type sits next to a single loader, which fills in
|
|
145
189
|
// {platform} and {arch} from the running process. The loader is CommonJS whatever the package
|
|
146
190
|
// "type" says, since it needs __dirname and process.dlopen.
|
|
@@ -154,6 +198,18 @@ export function nodeAddonNamesOf(target, projectName) {
|
|
|
154
198
|
};
|
|
155
199
|
}
|
|
156
200
|
|
|
201
|
+
// A wasi build is one component, with no JS glue: the command keeps the plain name, an http component adds its own.
|
|
202
|
+
export const wasiNamesOf = (target, projectName) => ({
|
|
203
|
+
wasmName: `${projectName}-${targetPathOf(target)}${target.runtimeEnv === 'http' ? '.http' : ''}.wasm`,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// One executable per platform, arch and build type, named like the addons it replaces.
|
|
207
|
+
export function nativeCommandNamesOf(target, projectName) {
|
|
208
|
+
const suffix = target.buildType === 'debug' ? '.debug' : '';
|
|
209
|
+
const extension = target.platform === 'win32' ? '.exe' : '';
|
|
210
|
+
return { commandName: `${projectName}.${target.platform}-${target.arch}${suffix}${extension}` };
|
|
211
|
+
}
|
|
212
|
+
|
|
157
213
|
export function filterTargetSpecs(targetSpecs, target) {
|
|
158
214
|
return targetSpecs?.filter(t => (
|
|
159
215
|
(!t.platform || t.platform === target.platform)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Where a build keeps the notices of the runtime a toolchain image linked into its binaries,
|
|
2
|
+
// relative to the build directory; crossbind licenses reads them from there.
|
|
3
|
+
export default function toolchainNoticesDir(platform) {
|
|
4
|
+
return `toolchain-licenses/${platform}`;
|
|
5
|
+
}
|