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
package/src/bin.js
CHANGED
|
@@ -2,75 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
|
-
import {
|
|
5
|
+
import { Option } from 'commander';
|
|
6
6
|
|
|
7
7
|
import { state } from './index.js';
|
|
8
|
+
import { addSystemCommands, createProgram, handleFatal } from './program.js';
|
|
8
9
|
import createBridgeFile from './actions/createInterface.js';
|
|
9
10
|
import createLib from './actions/createLib.js';
|
|
10
11
|
import buildWasm from './actions/buildWasm.js';
|
|
11
12
|
import buildWasiCommand from './actions/buildWasiCommand.js';
|
|
12
|
-
import buildNode, { publishNodeData } from './actions/buildNode.js';
|
|
13
|
+
import buildNode, { addonsInPackages, bundleNodeLoader, publishNodeData } from './actions/buildNode.js';
|
|
14
|
+
import buildNativeCommand from './actions/buildNativeCommand.js';
|
|
13
15
|
import buildExternal from './actions/buildExternal.js';
|
|
14
16
|
import buildPackageTypes from './actions/buildTypes.js';
|
|
15
17
|
import buildLib from './actions/buildLib.js';
|
|
16
18
|
import buildDependencies from './actions/buildDependencies.js';
|
|
19
|
+
import writeRuntimeEntry, { removeImportHooks, writeImportHooks, writeImportManifest } from './actions/writeRuntimeEntry.js';
|
|
20
|
+
import { bindsAppImports, createImportedBridges, findAppNativeImports } from './actions/createImportedBridges.js';
|
|
21
|
+
import { nativeExtensions } from './utils/nativeImports.js';
|
|
22
|
+
import getDependFilePath from './integration/getDependFilePath.js';
|
|
23
|
+
import { boundHeaderSpecifiers, bridgeTargets, nodeBridgeTarget, resolveBoundHeaders } from './utils/boundHeaders.js';
|
|
24
|
+
import { ENTRY_RUNTIMES } from './utils/runtimeEntries.js';
|
|
25
|
+
import { publishNativeCommand, publishNodeAddon, publishWasiCommand } from './actions/publishBinary.js';
|
|
17
26
|
import runCrossbindApp from './actions/run.js';
|
|
18
27
|
import { getBuildTargets, getFilteredBuildTargets } from './actions/target.js';
|
|
19
|
-
import { OPT_IN_PLATFORMS } from './utils/targets.js';
|
|
28
|
+
import { OPT_IN_PLATFORMS, selectRuntimeEnvs } from './utils/targets.js';
|
|
29
|
+
import { resolveHostPlatform } from './utils/hostPlatform.js';
|
|
20
30
|
import resolveEmbindNapiRoot, { resolveEmbindJsiRoot } from './utils/resolveEmbindNapi.js';
|
|
21
31
|
|
|
22
|
-
import writeJson from './utils/writeJson.js';
|
|
23
|
-
import flattenConfigForTable from './utils/flattenConfigForTable.js';
|
|
24
|
-
import systemKeys from './utils/systemKeys.js';
|
|
25
32
|
import logger from './utils/logger.js';
|
|
26
33
|
import { getDockerImage, getDockerContainerName } from './utils/pullDockerImage.js';
|
|
34
|
+
import {
|
|
35
|
+
RUNNER_PLATFORMS, RUNNER_ROLES, connectCommands, deleteRunner, deploySteps, initRunner, newRunnerToken, runnerHostPort, startRunner,
|
|
36
|
+
} from './actions/runnerCommands.js';
|
|
27
37
|
import { cargoHome } from './utils/runCargo.js';
|
|
38
|
+
import { conanRoot } from './utils/runConan.js';
|
|
28
39
|
import { cleanDepsCache } from './utils/dependencyRebuild.js';
|
|
29
40
|
import collectLicenseRows from './actions/licenses.js';
|
|
30
41
|
import { formatNoticesMarkdown, formatCycloneDxSbom, validateSpdx } from './utils/licenseReport.js';
|
|
42
|
+
import { SBOM_FILE, nodePackageMeta, writeNodePackageLicense } from './utils/packageLicense.js';
|
|
31
43
|
import findFiles from './utils/findFiles.js';
|
|
32
44
|
|
|
33
|
-
const packageJSON = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url)));
|
|
34
|
-
|
|
35
45
|
const platforms = [...new Set(state.targets.map(target => target.platform).filter(t => t))];
|
|
36
46
|
const archs = [...new Set(state.targets.map(target => target.arch).filter(t => t))];
|
|
37
47
|
const runtimes = [...new Set(state.targets.map(target => target.runtime).filter(t => t))];
|
|
38
48
|
const buildTypes = [...new Set(state.targets.map(target => target.buildType).filter(t => t))];
|
|
39
49
|
const runtimeEnvs = [...new Set(state.targets.map(target => target.runtimeEnv).filter(t => t))];
|
|
40
50
|
|
|
41
|
-
// Build failures bubble up as exceptions with a `cause` chain (see actions/run.js);
|
|
42
|
-
// show the operator a single line — DEBUG=1 for the full stack — and exit non-zero.
|
|
43
|
-
const handleFatal = (e) => {
|
|
44
|
-
if (process.env.DEBUG) {
|
|
45
|
-
console.error(e);
|
|
46
|
-
} else {
|
|
47
|
-
let message = e?.message || String(e);
|
|
48
|
-
if (e?.cause?.message) message += `\n caused by: ${e.cause.message.split('\n')[0]}`;
|
|
49
|
-
console.error(message);
|
|
50
|
-
}
|
|
51
|
-
process.exit(1);
|
|
52
|
-
};
|
|
53
51
|
process.on('uncaughtException', handleFatal);
|
|
54
52
|
process.on('unhandledRejection', handleFatal);
|
|
55
53
|
|
|
56
|
-
const program =
|
|
57
|
-
|
|
58
|
-
program
|
|
59
|
-
.name('crossbind')
|
|
60
|
-
.description('Compile C++ files to WebAssembly and native platforms.')
|
|
61
|
-
.version(packageJSON.version)
|
|
62
|
-
.showHelpAfterError();
|
|
54
|
+
const program = createProgram();
|
|
63
55
|
|
|
64
56
|
program.command('build')
|
|
65
57
|
.description('compile the project that was set up using crossbind')
|
|
66
|
-
.addOption(new Option('-p, --platform <platform>', 'target platform').argParser(createListParser(platforms)))
|
|
58
|
+
.addOption(new Option('-p, --platform <platform>', 'target platform (host: the one of this machine)').argParser(createListParser(platforms, resolveHostPlatform)))
|
|
67
59
|
.addOption(new Option('-a, --arch <arch>', 'target architecture').argParser(createListParser(archs)))
|
|
68
60
|
.addOption(new Option('-r, --runtime <runtime>', 'target runtime').argParser(createListParser(runtimes)))
|
|
69
61
|
.addOption(new Option('-b, --build-type <buildType>', 'target build type').argParser(createListParser(buildTypes)))
|
|
70
|
-
.addOption(new Option('-e, --runtime-env <runtimeEnv>', '
|
|
62
|
+
.addOption(new Option('-e, --runtime-env <runtimeEnv>', 'binaries to make, by runtime environment (without it, only the archives are built)').argParser(createListParser(runtimeEnvs)))
|
|
71
63
|
.option('--rebuild-deps [list]', 'rebuild dependencies from source instead of using prebuilt (all, or a comma-separated list of names)')
|
|
64
|
+
.option('--bind-imports', "also bind what the app's JavaScript imports into browser binaries (node binaries always do), for a page that resolves those imports itself")
|
|
72
65
|
.action((options) => {
|
|
73
|
-
const { rebuildDeps, ...rest } = options;
|
|
66
|
+
const { rebuildDeps, bindImports, ...rest } = options;
|
|
74
67
|
const targetParams = { ...rest };
|
|
75
68
|
|
|
76
69
|
if (!targetParams.arch) {
|
|
@@ -84,12 +77,14 @@ program.command('build')
|
|
|
84
77
|
targetParams.arch = targetParams.arch || archs;
|
|
85
78
|
targetParams.runtime = targetParams.runtime || runtimes;
|
|
86
79
|
targetParams.buildType = targetParams.buildType || buildTypes;
|
|
87
|
-
|
|
80
|
+
const binaryRuntimeEnvs = selectRuntimeEnvs(targetParams.runtimeEnv, state.config.target.runtimeEnv);
|
|
81
|
+
// The archives do not depend on the runtime environment; only the binaries do.
|
|
82
|
+
targetParams.runtimeEnv = runtimeEnvs;
|
|
88
83
|
|
|
89
84
|
if (state.config.build.withBuildConfig) {
|
|
90
85
|
buildExternal(targetParams);
|
|
91
86
|
} else {
|
|
92
|
-
build(targetParams, rebuildDeps);
|
|
87
|
+
build(targetParams, rebuildDeps, binaryRuntimeEnvs, { bindImports });
|
|
93
88
|
}
|
|
94
89
|
});
|
|
95
90
|
|
|
@@ -110,8 +105,14 @@ program.command('licenses')
|
|
|
110
105
|
.option('--sbom [file]', 'write a CycloneDX SBOM json file (default: sbom.cdx.json)')
|
|
111
106
|
.option('--check', 'exit non-zero when a license field is missing or not valid SPDX')
|
|
112
107
|
.option('--platform <platform>', "also list what this platform's artifact statically links beyond the package graph (vendored copies, toolchain runtime)")
|
|
108
|
+
.addOption(new Option('-e, --runtime-env <runtimeEnv>', 'also list the crossbind runtime an addon of --platform compiles in, or without --platform the one its loader bundles').choices(['node']))
|
|
109
|
+
.option('--json [file]', 'write the rows, license texts included, as JSON (default: licenses.json)')
|
|
110
|
+
.option('--package', "write this Node-API package's LICENSE, sbom.cdx.json and package.json license field (with -e node)")
|
|
113
111
|
.action(async (options) => {
|
|
114
|
-
|
|
112
|
+
if (options.package && options.runtimeEnv !== 'node') {
|
|
113
|
+
throw new Error('crossbind licenses --package writes the license files of a Node-API package; pass -e node.');
|
|
114
|
+
}
|
|
115
|
+
const rows = await collectLicenseRows(options.platform || null, { runtimeEnv: options.runtimeEnv || null });
|
|
115
116
|
rows.forEach((row) => {
|
|
116
117
|
console.log(`${row.isCopyleft ? '! ' : ' '}${(row.name || '').padEnd(14)} ${String(row.license || '(missing)').padEnd(48)} native ${String(row.nativeVersion || '-').padEnd(10)} ${row.sourceUrl || ''}`);
|
|
117
118
|
});
|
|
@@ -136,6 +137,15 @@ program.command('licenses')
|
|
|
136
137
|
fs.writeFileSync(file, formatCycloneDxSbom(rows, target));
|
|
137
138
|
logger.info(`crossbind: wrote ${file}`);
|
|
138
139
|
}
|
|
140
|
+
if (options.json) {
|
|
141
|
+
const file = typeof options.json === 'string' ? options.json : `${state.config.paths.project}/licenses.json`;
|
|
142
|
+
fs.writeFileSync(file, `${JSON.stringify(rows, null, 2)}\n`);
|
|
143
|
+
logger.info(`crossbind: wrote ${file}`);
|
|
144
|
+
}
|
|
145
|
+
if (options.package) {
|
|
146
|
+
const expression = writeNodePackageLicense(state.config.paths.project, { ...nodePackageMeta(state.config), rows });
|
|
147
|
+
logger.info(`crossbind: wrote LICENSE and ${SBOM_FILE} (${expression})`);
|
|
148
|
+
}
|
|
139
149
|
if (options.check) {
|
|
140
150
|
const invalid = rows
|
|
141
151
|
.map((row) => ({ row, result: validateSpdx(row.license) }))
|
|
@@ -197,13 +207,17 @@ imageArgument(commandDocker.command('create').description('create docker contain
|
|
|
197
207
|
.action((image) => {
|
|
198
208
|
// Google ships the linux NDK for x86_64 only, so an android container is always amd64.
|
|
199
209
|
const platform = image === 'android' ? 'linux/amd64' : undefined;
|
|
210
|
+
// Docker on Linux makes a missing mount source owned by root, which the user's builds cannot write.
|
|
211
|
+
[cargoHome(), conanRoot()].forEach((dir) => fs.mkdirSync(dir, { recursive: true }));
|
|
200
212
|
dockerExec([
|
|
201
213
|
'run', '-dit',
|
|
202
214
|
...(platform ? ['--platform', platform] : []),
|
|
203
215
|
'--name', dockerContainerName(image),
|
|
204
216
|
'-v', `${state.config.paths.base}:/tmp/crossbind/live`,
|
|
205
|
-
// Cargo's registry cache, shared with host builds and with every other project
|
|
217
|
+
// Cargo's registry cache, shared with host builds and with every other project, and the
|
|
218
|
+
// Conan store and work directories every other container build uses.
|
|
206
219
|
'-v', `${cargoHome()}:/var/cache/crossbind/cargo`,
|
|
220
|
+
'-v', `${conanRoot()}:/var/cache/crossbind/conan`,
|
|
207
221
|
getDockerImage(image, platform),
|
|
208
222
|
'bash',
|
|
209
223
|
]);
|
|
@@ -218,33 +232,56 @@ imageArgument(commandDocker.command('stop').description('stop docker container')
|
|
|
218
232
|
imageArgument(commandDocker.command('delete').description('delete docker container'))
|
|
219
233
|
.action((image) => dockerExec(['rm', dockerContainerName(image)]));
|
|
220
234
|
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
.
|
|
237
|
-
.
|
|
238
|
-
.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
235
|
+
const runnerAction = (fn) => (...args) => {
|
|
236
|
+
try {
|
|
237
|
+
fn(...args);
|
|
238
|
+
} catch (e) {
|
|
239
|
+
console.error(e.message);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
const roleOption = () => new Option('--role <role>', 'toolchain image the runner serves').choices(RUNNER_ROLES).default('web');
|
|
244
|
+
|
|
245
|
+
const commandRunner = program.command('runner')
|
|
246
|
+
.description('run the remote build runner that RUNNER=REMOTE sends builds to');
|
|
247
|
+
|
|
248
|
+
commandRunner.command('start')
|
|
249
|
+
.description('start a runner in local docker, from the toolchain image this CLI pins')
|
|
250
|
+
.addOption(roleOption())
|
|
251
|
+
.option('--port <port>', `host port (default: ${RUNNER_ROLES.map((role) => `${runnerHostPort(role)} for ${role}`).join(', ')})`)
|
|
252
|
+
.option('--host <address>', 'host address to listen on', '127.0.0.1')
|
|
253
|
+
.option('--token <token>', 'token builds must send (default: $CROSSBIND_RUNNER_TOKEN, else a new one)')
|
|
254
|
+
.action(runnerAction((options) => {
|
|
255
|
+
const runner = startRunner({
|
|
256
|
+
role: options.role, port: options.port && Number(options.port), host: options.host, token: options.token,
|
|
257
|
+
});
|
|
258
|
+
console.log(`crossbind: ${runner.name} is running. Build against it with:`);
|
|
259
|
+
connectCommands(options.role, runner.url, runner.displayToken).forEach((line) => console.log(` ${line}`));
|
|
260
|
+
}));
|
|
261
|
+
|
|
262
|
+
commandRunner.command('delete')
|
|
263
|
+
.description('stop and delete the local runner')
|
|
264
|
+
.addOption(roleOption())
|
|
265
|
+
.action(runnerAction((options) => deleteRunner({ role: options.role })));
|
|
266
|
+
|
|
267
|
+
commandRunner.command('init')
|
|
268
|
+
.description('write a folder that deploys a runner to a hosting platform')
|
|
269
|
+
.argument('<platform>', `hosting platform (${RUNNER_PLATFORMS.join(', ')})`)
|
|
270
|
+
.addOption(roleOption())
|
|
271
|
+
.option('--dir <dir>', 'folder to write (default: crossbind-runner-<platform>-<role>)')
|
|
272
|
+
.option('--vcpu <count>', 'cloudflare: vCPUs of the container (1-4), with the least memory Cloudflare allows them (default: standard-4)')
|
|
273
|
+
.action(runnerAction((platform, options) => {
|
|
274
|
+
const { dir, url } = initRunner({
|
|
275
|
+
platform,
|
|
276
|
+
role: options.role,
|
|
277
|
+
dir: options.dir ?? `crossbind-runner-${platform}-${options.role}`,
|
|
278
|
+
vcpu: options.vcpu === undefined ? undefined : Number(options.vcpu),
|
|
279
|
+
});
|
|
280
|
+
console.log(`crossbind: wrote ${dir}. Deploy it:`);
|
|
281
|
+
deploySteps(platform, dir, newRunnerToken(), options.role, url).forEach((step) => console.log(` ${step}`));
|
|
282
|
+
}));
|
|
244
283
|
|
|
245
|
-
|
|
246
|
-
.description('list all available system configuration keys for crossbind')
|
|
247
|
-
.action(() => listSystemKeys());
|
|
284
|
+
addSystemCommands(program);
|
|
248
285
|
|
|
249
286
|
// `pnpm run <script> -- --flag` forwards the `--` literally and commander would then
|
|
250
287
|
// treat the flags as positionals. Strip the first separator for commands where every
|
|
@@ -257,80 +294,48 @@ if (['build', 'clean-deps', 'licenses'].includes(argv[2])) {
|
|
|
257
294
|
|
|
258
295
|
program.parse(argv);
|
|
259
296
|
|
|
260
|
-
function
|
|
261
|
-
|
|
262
|
-
console.table(systemKeys);
|
|
297
|
+
function run(programName, params) {
|
|
298
|
+
runCrossbindApp(programName, params, null, null, { console: true });
|
|
263
299
|
}
|
|
264
300
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const output = {};
|
|
272
|
-
if (key in state.config.system) {
|
|
273
|
-
output[key] = { value: state.config.system[key] || 'undefined', default: systemKeys[key].default };
|
|
274
|
-
}
|
|
275
|
-
Object.keys(systemKeys).filter((k) => k.includes(key)).forEach((k) => {
|
|
276
|
-
if (k in state.config.system) {
|
|
277
|
-
output[k] = { value: state.config.system[k] || 'undefined', default: systemKeys[k].default };
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
console.table(output);
|
|
301
|
+
// A package whose platform packages link its addons, building nothing else, has no archive of its own that
|
|
302
|
+
// anything links.
|
|
303
|
+
function onlyLoadsPackagedAddons(binaryParams) {
|
|
304
|
+
if (binaryParams.runtimeEnv.length === 0) return false;
|
|
305
|
+
const targets = getBuildTargets(binaryParams);
|
|
306
|
+
return targets.every((target) => target.runtimeEnv === 'node' && target.platform !== 'wasm') && addonsInPackages();
|
|
282
307
|
}
|
|
283
308
|
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
throw new Error(`Value ${value} is not available. Please choose from the following available values: ${systemKeys[key].options.join(', ')}`);
|
|
309
|
+
async function build(targetParams, rebuildOption, binaryRuntimeEnvs, wasmOptions = {}) {
|
|
310
|
+
await buildDependencies({ targetParams, rebuildOption });
|
|
311
|
+
const binaryParams = { ...targetParams, runtimeEnv: binaryRuntimeEnvs };
|
|
312
|
+
if (!onlyLoadsPackagedAddons(binaryParams)) {
|
|
313
|
+
buildLib(targetParams);
|
|
290
314
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
throw new Error(`Configuration ${key} is not available. Please choose from the following available configurations: ${Object.keys(systemKeys).join(', ')}`);
|
|
315
|
+
if (binaryRuntimeEnvs.length === 0) {
|
|
316
|
+
logger.info(`crossbind: no runtime environment was selected, so only the archives were built - pass -e (${runtimeEnvs.join(', ')}) for a binary.`);
|
|
317
|
+
} else {
|
|
318
|
+
await createWasmJs(binaryParams, wasmOptions);
|
|
319
|
+
await createNodeAddons(binaryParams);
|
|
320
|
+
await createNativeCommands(binaryParams);
|
|
321
|
+
await createWasiCommands(binaryParams);
|
|
299
322
|
}
|
|
300
|
-
|
|
301
|
-
writeJson(state.config.paths.systemConfig, state.config.system);
|
|
323
|
+
buildPackageTypes();
|
|
302
324
|
}
|
|
303
325
|
|
|
304
|
-
function
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (type === 'all' || type === 'project') {
|
|
316
|
-
console.log(`Project Configuration (paths relative to ${projectConfig.paths.base})`);
|
|
317
|
-
console.table(flattenConfigForTable(projectConfig, { base: projectConfig.paths.base }));
|
|
326
|
+
async function createNativeCommands(targetParams) {
|
|
327
|
+
const targets = getBuildTargets(targetParams).filter((target) => target.runtimeEnv === 'native');
|
|
328
|
+
for (const target of targets) {
|
|
329
|
+
const distCommand = `${state.config.paths.output}/${target.commandName}`;
|
|
330
|
+
const built = await buildNativeCommand(target, { force: !fs.existsSync(distCommand) });
|
|
331
|
+
publishNodeData(target, { refresh: built });
|
|
332
|
+
if (!built) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
publishNativeCommand(target);
|
|
318
336
|
}
|
|
319
337
|
}
|
|
320
338
|
|
|
321
|
-
function run(programName, params) {
|
|
322
|
-
runCrossbindApp(programName, params, null, null, { console: true });
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
async function build(targetParams, rebuildOption) {
|
|
326
|
-
await buildDependencies({ targetParams, rebuildOption });
|
|
327
|
-
buildLib(targetParams);
|
|
328
|
-
await createWasmJs(targetParams);
|
|
329
|
-
await createNodeAddons(targetParams);
|
|
330
|
-
await createWasiCommands(targetParams);
|
|
331
|
-
buildPackageTypes();
|
|
332
|
-
}
|
|
333
|
-
|
|
334
339
|
async function createWasiCommands(targetParams) {
|
|
335
340
|
const targets = getFilteredBuildTargets(targetParams, { platform: 'wasi' });
|
|
336
341
|
if (targets.length === 0) {
|
|
@@ -342,35 +347,38 @@ async function createWasiCommands(targetParams) {
|
|
|
342
347
|
if (!built) {
|
|
343
348
|
continue;
|
|
344
349
|
}
|
|
345
|
-
|
|
346
|
-
// Copied (not renamed) so a later fingerprint cache-hit still has the
|
|
347
|
-
// build-dir tree to serve from.
|
|
348
|
-
if (fs.existsSync(`${state.config.paths.build}/data`)) {
|
|
349
|
-
fs.rmSync(`${state.config.paths.output}/data`, { recursive: true, force: true });
|
|
350
|
-
fs.cpSync(`${state.config.paths.build}/data`, `${state.config.paths.output}/data`, { recursive: true });
|
|
351
|
-
}
|
|
350
|
+
publishWasiCommand(target);
|
|
352
351
|
}
|
|
353
352
|
}
|
|
354
353
|
|
|
355
|
-
function createBridges() {
|
|
354
|
+
function createBridges(target) {
|
|
356
355
|
let headers = [];
|
|
357
356
|
state.config.paths.header.forEach((headerPath) => {
|
|
358
357
|
headers.push(findFiles('**/*.h', { cwd: headerPath }));
|
|
359
358
|
});
|
|
360
359
|
headers = headers.filter((path) => !!path.toString()).flat();
|
|
361
360
|
|
|
362
|
-
|
|
363
|
-
headers.forEach((header) => {
|
|
364
|
-
const bridgePath = createBridgeFile(header);
|
|
365
|
-
bridges.push(bridgePath);
|
|
366
|
-
});
|
|
367
|
-
return bridges;
|
|
361
|
+
return headers.map((header) => ({ file: header, bridge: createBridgeFile(header, target) }));
|
|
368
362
|
}
|
|
369
363
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
364
|
+
// The dependency headers the config binds whole, each read where one of the targets' packages ships it.
|
|
365
|
+
function createBoundBridges(targets) {
|
|
366
|
+
const headers = resolveBoundHeaders(boundHeaderSpecifiers(state.config), bridgeTargets(targets, state.targets), getDependFilePath);
|
|
367
|
+
const wholeHeaders = headers.map((header) => header.file);
|
|
368
|
+
return headers.map((header) => ({ ...header, bridge: createBridgeFile(header.file, header.target, { wholeHeaders }) }));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// A package whose platform packages link its addons ships what loads them: the loader and the data.
|
|
372
|
+
async function publishLoaders(targets) {
|
|
373
|
+
fs.mkdirSync(state.config.paths.output, { recursive: true });
|
|
374
|
+
targets.forEach((target) => publishNodeData(target, { refresh: true }));
|
|
375
|
+
for (const target of new Map(targets.map((t) => [t.jsName, t])).values()) {
|
|
376
|
+
await bundleNodeLoader(target);
|
|
377
|
+
fs.copyFileSync(`${state.config.paths.build}/${target.jsName}`, `${state.config.paths.output}/${target.jsName}`);
|
|
373
378
|
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function createNodeAddons(targetParams) {
|
|
374
382
|
const targets = getBuildTargets(targetParams)
|
|
375
383
|
.filter((target) => target.runtimeEnv === 'node' && target.platform !== 'wasm');
|
|
376
384
|
if (targets.length === 0) {
|
|
@@ -381,46 +389,71 @@ async function createNodeAddons(targetParams) {
|
|
|
381
389
|
`${resolveEmbindJsiRoot()}/cpp/src`,
|
|
382
390
|
`${resolveEmbindNapiRoot()}/third_party/node-api-jsi/jsi`,
|
|
383
391
|
];
|
|
392
|
+
const boundHeaders = createBoundBridges(targets);
|
|
393
|
+
const ownHeaders = [...createBridges(nodeBridgeTarget(targets)), ...boundHeaders];
|
|
394
|
+
const imports = findAppNativeImports(bridgeTargets(targets, state.targets));
|
|
395
|
+
const headers = [...ownHeaders, ...createImportedBridges(ownHeaders, imports)];
|
|
384
396
|
const opt = {
|
|
385
397
|
buildSource: false,
|
|
386
398
|
nativeGlob: [
|
|
387
399
|
`${state.config.paths.cli}/assets/cpp-runtime/commonBridges.cpp`,
|
|
388
|
-
...
|
|
400
|
+
...headers.flatMap((header) => header.bridge ?? []),
|
|
389
401
|
],
|
|
390
402
|
headerDirs,
|
|
391
403
|
inputs: headerDirs.flatMap((dir) => findFiles('**/*.h', { cwd: dir })),
|
|
392
404
|
};
|
|
393
405
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
406
|
+
if (addonsInPackages()) {
|
|
407
|
+
await publishLoaders(targets);
|
|
408
|
+
} else {
|
|
409
|
+
for (const target of targets) {
|
|
410
|
+
const distComplete = fs.existsSync(`${state.config.paths.output}/${target.addonName}`)
|
|
411
|
+
&& fs.existsSync(`${state.config.paths.output}/${target.jsName}`);
|
|
412
|
+
createLib(target, 'Bridge', opt);
|
|
413
|
+
const built = await buildNode(target, { force: !distComplete });
|
|
414
|
+
publishNodeData(target, { refresh: built });
|
|
415
|
+
if (!built) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
publishNodeAddon(target);
|
|
402
419
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
420
|
+
}
|
|
421
|
+
const loaderTarget = targets.find((target) => target.buildType === 'release') ?? targets[0];
|
|
422
|
+
if (headers.length && fs.existsSync(`${state.config.paths.output}/${loaderTarget.jsName}`)) {
|
|
423
|
+
const entry = `${state.config.paths.output}/node/napi.mjs`;
|
|
424
|
+
writeRuntimeEntry(entry, headers, ENTRY_RUNTIMES.node(loaderTarget.jsName));
|
|
425
|
+
if (imports.size) writeImportHooks(entry, headers, imports, nativeExtensions(state.config.ext));
|
|
426
|
+
else removeImportHooks(entry);
|
|
406
427
|
}
|
|
407
428
|
}
|
|
408
429
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
430
|
+
// The Node and edge wasm builds are CommonJS with a .js extension, which a "type": "module" package loads as
|
|
431
|
+
// ESM. A package.json of the output's own scopes them back, unless the output is a package root.
|
|
432
|
+
const OUTPUT_SCOPE = '{\n "type": "commonjs"\n}\n';
|
|
433
|
+
function scopeOutputToCommonJs() {
|
|
434
|
+
const file = `${state.config.paths.output}/package.json`;
|
|
435
|
+
if (!fs.existsSync(file)) {
|
|
436
|
+
fs.mkdirSync(state.config.paths.output, { recursive: true });
|
|
437
|
+
fs.writeFileSync(file, OUTPUT_SCOPE);
|
|
412
438
|
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function createWasmJs(targetParams, wasmOptions = {}) {
|
|
413
442
|
const targets = getFilteredBuildTargets(targetParams, { platform: 'wasm' });
|
|
414
443
|
if (targets.length === 0) {
|
|
415
444
|
return;
|
|
416
445
|
}
|
|
417
|
-
const
|
|
446
|
+
const headers = createBridges();
|
|
447
|
+
const bindsImports = (target) => bindsAppImports(target, wasmOptions);
|
|
448
|
+
const imports = targets.some(bindsImports) ? findAppNativeImports([state.targets.find((t) => t.platform === 'wasm')]) : new Map();
|
|
449
|
+
const appHeaders = [...headers, ...createImportedBridges(headers, imports)];
|
|
450
|
+
const optOf = (bridged) => ({
|
|
418
451
|
buildSource: false,
|
|
419
452
|
nativeGlob: [
|
|
420
453
|
`${state.config.paths.cli}/assets/cpp-runtime/commonBridges.cpp`,
|
|
421
|
-
...
|
|
454
|
+
...bridged.flatMap((header) => header.bridge ?? []),
|
|
422
455
|
],
|
|
423
|
-
};
|
|
456
|
+
});
|
|
424
457
|
|
|
425
458
|
for (const target of targets) {
|
|
426
459
|
// An artifact merely existing in dist used to short-circuit this whole
|
|
@@ -430,7 +463,7 @@ async function createWasmJs(targetParams) {
|
|
|
430
463
|
// link and the copy step below moved build/data away on the last run.
|
|
431
464
|
const distComplete = fs.existsSync(`${state.config.paths.output}/${target.jsName}`)
|
|
432
465
|
&& fs.existsSync(`${state.config.paths.output}/${target.wasmName}`);
|
|
433
|
-
createLib(target, 'Bridge',
|
|
466
|
+
createLib(target, 'Bridge', optOf(bindsImports(target) ? appHeaders : headers));
|
|
434
467
|
const built = await buildWasm(target, { force: !distComplete });
|
|
435
468
|
if (!built) {
|
|
436
469
|
continue;
|
|
@@ -447,11 +480,35 @@ async function createWasmJs(targetParams) {
|
|
|
447
480
|
fs.copyFileSync(`${state.config.paths.build}/${target.dataTxtName}`, `${state.config.paths.output}/${target.dataTxtName}`);
|
|
448
481
|
}
|
|
449
482
|
}
|
|
483
|
+
|
|
484
|
+
if (targets.some((target) => ['node', 'edge'].includes(target.runtimeEnv))) {
|
|
485
|
+
scopeOutputToCommonJs();
|
|
486
|
+
}
|
|
487
|
+
// The list a page that maps the app's imports itself reads; a build without --bind-imports is not for one.
|
|
488
|
+
if (targets.some((target) => target.runtimeEnv === 'browser')) {
|
|
489
|
+
const manifest = `${state.config.paths.output}/browser/imports.json`;
|
|
490
|
+
if (wasmOptions.bindImports) writeImportManifest(manifest, appHeaders, imports, state.config.paths.project);
|
|
491
|
+
else fs.rmSync(manifest, { force: true });
|
|
492
|
+
}
|
|
493
|
+
// A runtime's entry loads its single-thread wasm32 release build.
|
|
494
|
+
const entryTargetOf = (runtimeEnv) => targets.find((target) => target.runtimeEnv === runtimeEnv && target.arch === 'wasm32'
|
|
495
|
+
&& target.runtime === 'st' && target.buildType === 'release' && fs.existsSync(`${state.config.paths.output}/${target.jsName}`));
|
|
496
|
+
const nodeTarget = entryTargetOf('node');
|
|
497
|
+
const edgeTarget = entryTargetOf('edge');
|
|
498
|
+
if (appHeaders.length && nodeTarget) {
|
|
499
|
+
const entry = `${state.config.paths.output}/node/wasm.mjs`;
|
|
500
|
+
writeRuntimeEntry(entry, appHeaders, ENTRY_RUNTIMES.node(nodeTarget.jsName));
|
|
501
|
+
if (imports.size) writeImportHooks(entry, appHeaders, imports, nativeExtensions(state.config.ext));
|
|
502
|
+
else removeImportHooks(entry);
|
|
503
|
+
}
|
|
504
|
+
if (headers.length && edgeTarget) {
|
|
505
|
+
writeRuntimeEntry(`${state.config.paths.output}/edge/wasm.mjs`, headers, ENTRY_RUNTIMES.edge(edgeTarget.jsName, edgeTarget.wasmName));
|
|
506
|
+
}
|
|
450
507
|
}
|
|
451
508
|
|
|
452
|
-
function createListParser(validList) {
|
|
509
|
+
function createListParser(validList, resolve = (items) => items) {
|
|
453
510
|
return (value, previous) => {
|
|
454
|
-
const items = value.split(',').map(item => item.trim());
|
|
511
|
+
const items = resolve(value.split(',').map(item => item.trim()));
|
|
455
512
|
for (const item of items) {
|
|
456
513
|
if (!validList.includes(item)) {
|
|
457
514
|
throw new Error(`Invalid value: "${item}". Allowed values: ${validList.join(', ')}`);
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { addSystemCommands, createProgram, handleFatal } from './program.js';
|
|
4
|
+
|
|
5
|
+
// bin.js loads the project before it reads the command line, which fails outside a project; these commands need none.
|
|
6
|
+
const program = createProgram();
|
|
7
|
+
addSystemCommands(program);
|
|
8
|
+
const withoutProject = [...program.commands.map((command) => command.name()), '-V', '--version'];
|
|
9
|
+
|
|
10
|
+
if (withoutProject.includes(process.argv[2])) {
|
|
11
|
+
await program.parseAsync().catch(handleFatal);
|
|
12
|
+
} else {
|
|
13
|
+
await import('./bin.js');
|
|
14
|
+
}
|
package/src/index.js
CHANGED
|
@@ -13,9 +13,12 @@ export { getTargetParams, getBuildTargets, getFilteredBuildTargets, getFilteredT
|
|
|
13
13
|
export { default as getCrossbindScript, getRustJsScript } from './integration/getCrossbindScript.js';
|
|
14
14
|
export { default as getDependFilePath } from './integration/getDependFilePath.js';
|
|
15
15
|
export { default as buildDependencies, getDependenciesStamp } from './actions/buildDependencies.js';
|
|
16
|
+
export { default as prepareConanDependencies } from './actions/prepareConanDependencies.js';
|
|
16
17
|
export { default as findFiles } from './utils/findFiles.js';
|
|
17
18
|
export { getContentHash, getFileHash } from './utils/hash.js';
|
|
18
19
|
export {
|
|
19
20
|
collectInputFiles, collectRustBridgeFiles, collectRustSources, computeInputStamp,
|
|
20
21
|
} from './utils/inputStamp.js';
|
|
21
22
|
export { default as buildAppRustCrates } from './utils/appRustCrates.js';
|
|
23
|
+
export { default as appSourceFiles, isAppSource } from './utils/appSources.js';
|
|
24
|
+
export { default as bindHeaderImports } from './integration/bindHeaderImports.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import upath from 'upath';
|
|
3
|
+
import state from '../state/index.js';
|
|
4
|
+
import createBridgeFile from '../actions/createInterface.js';
|
|
5
|
+
import resolveNativeImport from './resolveNativeImport.js';
|
|
6
|
+
import { isAppSource } from '../utils/appSources.js';
|
|
7
|
+
import { findHeaderImports } from '../utils/headerImports.js';
|
|
8
|
+
|
|
9
|
+
// Metro keeps a header's module as it first transformed it, so a source of the app that imports the header binds it
|
|
10
|
+
// again: the functions it names reach the next native build or page load.
|
|
11
|
+
export default function bindHeaderImports(source, file, target) {
|
|
12
|
+
if (!isAppSource(state.config.paths.project, file)) return [];
|
|
13
|
+
const headers = findHeaderImports(source, state.config.ext.header)
|
|
14
|
+
.map(({ specifier }) => resolveNativeImport(specifier, file, target))
|
|
15
|
+
.filter((header) => header && fs.existsSync(header))
|
|
16
|
+
.map((header) => upath.normalize(header));
|
|
17
|
+
return [...new Set(headers)].map((header) => createBridgeFile(header, target));
|
|
18
|
+
}
|