crossbind 2.0.0-beta.63 → 2.0.0-beta.65
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/package.json +2 -2
- package/src/actions/androidDevice.js +77 -0
- package/src/actions/androidGradle.js +121 -0
- package/src/actions/buildApp.js +434 -0
- package/src/actions/buildBinTools.js +0 -0
- package/src/actions/buildDependencies.js +4 -3
- package/src/actions/buildExternal.js +3 -3
- package/src/actions/buildJs.js +2 -9
- package/src/actions/buildLib.js +1 -1
- package/src/actions/buildNode.js +19 -16
- package/src/actions/buildWasiCommand.js +10 -5
- package/src/actions/buildWasm.js +24 -3
- package/src/actions/createImportedBridges.js +7 -0
- package/src/actions/createInterface.js +4 -5
- package/src/actions/createLib.js +17 -2
- package/src/actions/getCmakeParameters.js +2 -1
- package/src/actions/getData.js +2 -1
- package/src/actions/getDependLibs.js +8 -2
- package/src/actions/getEnv.js +7 -0
- package/src/actions/getLinkInputs.js +10 -7
- package/src/actions/gradleProject.js +110 -0
- package/src/actions/iosSimulator.js +63 -0
- package/src/actions/licenses.js +160 -14
- package/src/actions/nativeBridges.js +35 -0
- package/src/actions/run.js +3 -3
- package/src/actions/runnerCommands.js +16 -4
- package/src/actions/target.js +2 -1
- package/src/actions/writeRuntimeEntry.js +18 -7
- package/src/actions/xcodeProject.js +152 -0
- package/src/assets/cpp-runtime/node.cpp +18 -1
- package/src/assets/js-runtime/adapters/worker-comlink.js +58 -0
- package/src/assets/js-runtime/core.js +5 -4
- package/src/assets/toolchain-digests.json +25 -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/bin.js +102 -209
- package/src/cli.js +14 -0
- package/src/index.js +6 -0
- package/src/integration/getCrossbindScript.js +19 -4
- package/src/program.js +169 -0
- package/src/state/index.js +8 -3
- package/src/state/loadConfig.js +6 -11
- package/src/utils/appRustCrates.js +7 -1
- package/src/utils/bridgeExtras.js +74 -6
- package/src/utils/cargoLock.js +19 -0
- package/src/utils/cppDts.js +2 -0
- package/src/utils/downloadAndExtractFile.js +42 -14
- package/src/utils/embindObjectLifetime.js +35 -0
- package/src/utils/embindRsFingerprint.js +10 -3
- package/src/utils/hermes.js +40 -0
- package/src/utils/packageLicense.js +4 -2
- package/src/utils/plist.js +42 -0
- package/src/utils/pullDockerImage.js +9 -3
- package/src/utils/replaceBasePathForDocker.js +8 -0
- package/src/utils/resetCMakeCacheForSource.js +26 -0
- package/src/utils/resolveAppHost.js +21 -0
- package/src/utils/rustBridgeGen.js +13 -4
- package/src/utils/rustMt.js +5 -0
- package/src/utils/rustSysroot.js +7 -5
- package/src/utils/selectRunner.js +1 -1
- package/src/utils/swigInterface.js +5 -4
- package/src/utils/systemConfig.js +13 -0
- package/src/utils/systemKeys.js +4 -0
- package/src/utils/targets.js +30 -3
- package/src/utils/wasiHttp.js +50 -0
- package/vitest.config.js +5 -0
package/src/bin.js
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
8
|
+
import { Option } from 'commander';
|
|
6
9
|
|
|
7
10
|
import { state } from './index.js';
|
|
8
|
-
import
|
|
11
|
+
import { addSystemCommands, createProgram, handleFatal } from './program.js';
|
|
9
12
|
import createLib from './actions/createLib.js';
|
|
10
13
|
import buildWasm from './actions/buildWasm.js';
|
|
11
14
|
import buildWasiCommand from './actions/buildWasiCommand.js';
|
|
12
15
|
import buildNode, { addonsInPackages, bundleNodeLoader, publishNodeData } from './actions/buildNode.js';
|
|
13
16
|
import buildNativeCommand from './actions/buildNativeCommand.js';
|
|
17
|
+
import buildApp, { appOutputPath, assertAppRunner, publishApp } from './actions/buildApp.js';
|
|
18
|
+
import { releaseSigning } from './actions/androidGradle.js';
|
|
14
19
|
import buildExternal from './actions/buildExternal.js';
|
|
15
20
|
import buildPackageTypes from './actions/buildTypes.js';
|
|
16
21
|
import buildLib from './actions/buildLib.js';
|
|
17
22
|
import buildDependencies from './actions/buildDependencies.js';
|
|
18
|
-
import writeRuntimeEntry, { removeImportHooks, writeImportHooks } from './actions/writeRuntimeEntry.js';
|
|
19
|
-
import { createImportedBridges, findAppNativeImports } from './actions/createImportedBridges.js';
|
|
23
|
+
import writeRuntimeEntry, { removeImportHooks, writeImportHooks, writeImportManifest } from './actions/writeRuntimeEntry.js';
|
|
24
|
+
import { bindsAppImports, createImportedBridges, findAppNativeImports } from './actions/createImportedBridges.js';
|
|
20
25
|
import { nativeExtensions } from './utils/nativeImports.js';
|
|
21
|
-
import
|
|
22
|
-
import { boundHeaderSpecifiers, bridgeTargets, nodeBridgeTarget, resolveBoundHeaders } from './utils/boundHeaders.js';
|
|
26
|
+
import { createBridges, createNativeBridges } from './actions/nativeBridges.js';
|
|
23
27
|
import { ENTRY_RUNTIMES } from './utils/runtimeEntries.js';
|
|
24
28
|
import { publishNativeCommand, publishNodeAddon, publishWasiCommand } from './actions/publishBinary.js';
|
|
25
29
|
import runCrossbindApp from './actions/run.js';
|
|
@@ -28,18 +32,11 @@ import { OPT_IN_PLATFORMS, selectRuntimeEnvs } from './utils/targets.js';
|
|
|
28
32
|
import { resolveHostPlatform } from './utils/hostPlatform.js';
|
|
29
33
|
import resolveEmbindNapiRoot, { resolveEmbindJsiRoot } from './utils/resolveEmbindNapi.js';
|
|
30
34
|
|
|
31
|
-
import writeJson from './utils/writeJson.js';
|
|
32
|
-
import flattenConfigForTable from './utils/flattenConfigForTable.js';
|
|
33
|
-
import systemKeys from './utils/systemKeys.js';
|
|
34
35
|
import logger from './utils/logger.js';
|
|
35
36
|
import { getDockerImage, getDockerContainerName } from './utils/pullDockerImage.js';
|
|
36
37
|
import {
|
|
37
|
-
RUNNER_PLATFORMS, RUNNER_ROLES, connectCommands, deploySteps, initRunner, newRunnerToken, runnerHostPort, startRunner,
|
|
38
|
+
RUNNER_PLATFORMS, RUNNER_ROLES, connectCommands, deleteRunner, deploySteps, initRunner, newRunnerToken, runnerHostPort, startRunner,
|
|
38
39
|
} from './actions/runnerCommands.js';
|
|
39
|
-
import {
|
|
40
|
-
deleteAccount as deleteCloudAccount, fetchUsage, login as loginToCloud, logout as logoutOfCloud, usageLines,
|
|
41
|
-
} from './actions/cloudCommands.js';
|
|
42
|
-
import { cloudUrl } from './utils/cloudAccount.js';
|
|
43
40
|
import { cargoHome } from './utils/runCargo.js';
|
|
44
41
|
import { conanRoot } from './utils/runConan.js';
|
|
45
42
|
import { cleanDepsCache } from './utils/dependencyRebuild.js';
|
|
@@ -48,36 +45,16 @@ import { formatNoticesMarkdown, formatCycloneDxSbom, validateSpdx } from './util
|
|
|
48
45
|
import { SBOM_FILE, nodePackageMeta, writeNodePackageLicense } from './utils/packageLicense.js';
|
|
49
46
|
import findFiles from './utils/findFiles.js';
|
|
50
47
|
|
|
51
|
-
const packageJSON = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url)));
|
|
52
|
-
|
|
53
48
|
const platforms = [...new Set(state.targets.map(target => target.platform).filter(t => t))];
|
|
54
49
|
const archs = [...new Set(state.targets.map(target => target.arch).filter(t => t))];
|
|
55
50
|
const runtimes = [...new Set(state.targets.map(target => target.runtime).filter(t => t))];
|
|
56
51
|
const buildTypes = [...new Set(state.targets.map(target => target.buildType).filter(t => t))];
|
|
57
52
|
const runtimeEnvs = [...new Set(state.targets.map(target => target.runtimeEnv).filter(t => t))];
|
|
58
53
|
|
|
59
|
-
// Build failures bubble up as exceptions with a `cause` chain (see actions/run.js);
|
|
60
|
-
// show the operator a single line — DEBUG=1 for the full stack — and exit non-zero.
|
|
61
|
-
const handleFatal = (e) => {
|
|
62
|
-
if (process.env.DEBUG) {
|
|
63
|
-
console.error(e);
|
|
64
|
-
} else {
|
|
65
|
-
let message = e?.message || String(e);
|
|
66
|
-
if (e?.cause?.message) message += `\n caused by: ${e.cause.message.split('\n')[0]}`;
|
|
67
|
-
console.error(message);
|
|
68
|
-
}
|
|
69
|
-
process.exit(1);
|
|
70
|
-
};
|
|
71
54
|
process.on('uncaughtException', handleFatal);
|
|
72
55
|
process.on('unhandledRejection', handleFatal);
|
|
73
56
|
|
|
74
|
-
const program =
|
|
75
|
-
|
|
76
|
-
program
|
|
77
|
-
.name('crossbind')
|
|
78
|
-
.description('Compile C++ files to WebAssembly and native platforms.')
|
|
79
|
-
.version(packageJSON.version)
|
|
80
|
-
.showHelpAfterError();
|
|
57
|
+
const program = createProgram();
|
|
81
58
|
|
|
82
59
|
program.command('build')
|
|
83
60
|
.description('compile the project that was set up using crossbind')
|
|
@@ -87,8 +64,9 @@ program.command('build')
|
|
|
87
64
|
.addOption(new Option('-b, --build-type <buildType>', 'target build type').argParser(createListParser(buildTypes)))
|
|
88
65
|
.addOption(new Option('-e, --runtime-env <runtimeEnv>', 'binaries to make, by runtime environment (without it, only the archives are built)').argParser(createListParser(runtimeEnvs)))
|
|
89
66
|
.option('--rebuild-deps [list]', 'rebuild dependencies from source instead of using prebuilt (all, or a comma-separated list of names)')
|
|
67
|
+
.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")
|
|
90
68
|
.action((options) => {
|
|
91
|
-
const { rebuildDeps, ...rest } = options;
|
|
69
|
+
const { rebuildDeps, bindImports, ...rest } = options;
|
|
92
70
|
const targetParams = { ...rest };
|
|
93
71
|
|
|
94
72
|
if (!targetParams.arch) {
|
|
@@ -109,7 +87,8 @@ program.command('build')
|
|
|
109
87
|
if (state.config.build.withBuildConfig) {
|
|
110
88
|
buildExternal(targetParams);
|
|
111
89
|
} else {
|
|
112
|
-
|
|
90
|
+
// An app builds only for the platforms -p names, so a plain build keeps making the archives alone.
|
|
91
|
+
build(targetParams, rebuildDeps, binaryRuntimeEnvs, { bindImports }, rest.platform ?? []);
|
|
113
92
|
}
|
|
114
93
|
});
|
|
115
94
|
|
|
@@ -124,13 +103,50 @@ function declaredSbomPath(projectDir) {
|
|
|
124
103
|
}
|
|
125
104
|
}
|
|
126
105
|
|
|
106
|
+
// An app's page is the project's Vite build, which @crossbind/plugin-vite makes into the app of the platform named.
|
|
107
|
+
const APP_PLATFORMS = ['android', 'ios', 'linux', 'macos', 'windows'];
|
|
108
|
+
|
|
109
|
+
function projectVite() {
|
|
110
|
+
try {
|
|
111
|
+
const manifest = createRequire(`${state.config.paths.project}/package.json`).resolve('vite/package.json');
|
|
112
|
+
return path.join(path.dirname(manifest), JSON.parse(fs.readFileSync(manifest, 'utf8')).bin.vite);
|
|
113
|
+
} catch {
|
|
114
|
+
throw new Error("crossbind: crossbind app builds the app's page with the project's Vite, which the project does not have. Add vite and @crossbind/plugin-vite to it, as the desktop-app template has them.");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const commandApp = program.command('app').description('build the project as an app, or open it on the dev server');
|
|
119
|
+
[
|
|
120
|
+
['dev', 'open the app on the dev server, and open it anew as its native code changes'],
|
|
121
|
+
['build', 'build the app with its page: for Android also its APK, and with an upload key its bundle'],
|
|
122
|
+
].forEach(([name, description]) => commandApp.command(name).description(description)
|
|
123
|
+
.addOption(new Option('-p, --platform <platform>', "the app's platform (default: CROSSBIND_APP_PLATFORM, else this machine's)").choices(APP_PLATFORMS))
|
|
124
|
+
// Vite's own options, as --port or --mode, go on to Vite.
|
|
125
|
+
.allowUnknownOption()
|
|
126
|
+
.allowExcessArguments()
|
|
127
|
+
.action(({ platform }, command) => {
|
|
128
|
+
if (state.config.target.runtimeEnv !== 'app') {
|
|
129
|
+
throw new Error("crossbind: crossbind app builds an app, which this project is not: set target: { runtimeEnv: 'app' } in crossbind.config.js, as the desktop-app template does.");
|
|
130
|
+
}
|
|
131
|
+
// An Android build checks its upload key before the native build, not after it.
|
|
132
|
+
if (name === 'build' && (platform ?? process.env.CROSSBIND_APP_PLATFORM) === 'android') releaseSigning();
|
|
133
|
+
const vite = spawn(process.execPath, [projectVite(), ...(name === 'build' ? ['build'] : []), ...command.args], {
|
|
134
|
+
stdio: 'inherit',
|
|
135
|
+
cwd: state.config.paths.project,
|
|
136
|
+
env: platform ? { ...process.env, CROSSBIND_APP_PLATFORM: platform } : process.env,
|
|
137
|
+
});
|
|
138
|
+
// A signal sent to crossbind alone reaches Vite too, which ends through its exit hooks; Ctrl-C reaches both.
|
|
139
|
+
['SIGINT', 'SIGTERM', 'SIGHUP'].forEach((signal) => process.prependListener(signal, () => vite.kill(signal)));
|
|
140
|
+
vite.on('exit', (code, signal) => process.exit(code ?? 128 + os.constants.signals[signal]));
|
|
141
|
+
}));
|
|
142
|
+
|
|
127
143
|
program.command('licenses')
|
|
128
144
|
.description('list bundled native dependencies with SPDX license, versions and source URL')
|
|
129
145
|
.option('--notices [file]', 'write a THIRD-PARTY-NOTICES markdown file (default: THIRD-PARTY-NOTICES.md)')
|
|
130
146
|
.option('--sbom [file]', 'write a CycloneDX SBOM json file (default: sbom.cdx.json)')
|
|
131
147
|
.option('--check', 'exit non-zero when a license field is missing or not valid SPDX')
|
|
132
148
|
.option('--platform <platform>', "also list what this platform's artifact statically links beyond the package graph (vendored copies, toolchain runtime)")
|
|
133
|
-
.addOption(new Option('-e, --runtime-env <runtimeEnv>',
|
|
149
|
+
.addOption(new Option('-e, --runtime-env <runtimeEnv>', "also list the crossbind runtime an addon of --platform compiles in (without --platform the one its loader bundles), or what an app's host carries").choices(['node', 'app']))
|
|
134
150
|
.option('--json [file]', 'write the rows, license texts included, as JSON (default: licenses.json)')
|
|
135
151
|
.option('--package', "write this Node-API package's LICENSE, sbom.cdx.json and package.json license field (with -e node)")
|
|
136
152
|
.action(async (options) => {
|
|
@@ -200,7 +216,7 @@ program.command('clean-deps')
|
|
|
200
216
|
});
|
|
201
217
|
|
|
202
218
|
// A container belongs to one image: the web container carries no NDK, and android runs a forced platform.
|
|
203
|
-
const IMAGE_ROLES = ['web', 'android', 'linux', 'windows'];
|
|
219
|
+
const IMAGE_ROLES = ['web', 'android', 'android-app', 'linux', 'windows'];
|
|
204
220
|
const dockerContainerName = (role) => {
|
|
205
221
|
if (!IMAGE_ROLES.includes(role)) {
|
|
206
222
|
console.error(`crossbind: unknown image "${role}" - expected one of ${IMAGE_ROLES.join(', ')}.`);
|
|
@@ -230,8 +246,8 @@ const imageArgument = (command) => command.argument('[image]', `image to act on
|
|
|
230
246
|
|
|
231
247
|
imageArgument(commandDocker.command('create').description('create docker container'))
|
|
232
248
|
.action((image) => {
|
|
233
|
-
// Google ships the linux NDK for x86_64 only, so an android container is always amd64.
|
|
234
|
-
const platform =
|
|
249
|
+
// Google ships the linux NDK for x86_64 only, so an android or android-app container is always amd64.
|
|
250
|
+
const platform = ['android', 'android-app'].includes(image) ? 'linux/amd64' : undefined;
|
|
235
251
|
// Docker on Linux makes a missing mount source owned by root, which the user's builds cannot write.
|
|
236
252
|
[cargoHome(), conanRoot()].forEach((dir) => fs.mkdirSync(dir, { recursive: true }));
|
|
237
253
|
dockerExec([
|
|
@@ -284,10 +300,10 @@ commandRunner.command('start')
|
|
|
284
300
|
connectCommands(options.role, runner.url, runner.displayToken).forEach((line) => console.log(` ${line}`));
|
|
285
301
|
}));
|
|
286
302
|
|
|
287
|
-
commandRunner.command('
|
|
288
|
-
.description('stop and
|
|
303
|
+
commandRunner.command('delete')
|
|
304
|
+
.description('stop and delete the local runner')
|
|
289
305
|
.addOption(roleOption())
|
|
290
|
-
.action(runnerAction((options) =>
|
|
306
|
+
.action(runnerAction((options) => deleteRunner({ role: options.role })));
|
|
291
307
|
|
|
292
308
|
commandRunner.command('init')
|
|
293
309
|
.description('write a folder that deploys a runner to a hosting platform')
|
|
@@ -306,76 +322,7 @@ commandRunner.command('init')
|
|
|
306
322
|
deploySteps(platform, dir, newRunnerToken(), options.role, url).forEach((step) => console.log(` ${step}`));
|
|
307
323
|
}));
|
|
308
324
|
|
|
309
|
-
program
|
|
310
|
-
.description('sign in to crossbind cloud with your GitHub account')
|
|
311
|
-
.action(async () => {
|
|
312
|
-
const url = cloudUrl(state.config.system);
|
|
313
|
-
const { login } = await loginToCloud({ url });
|
|
314
|
-
console.log(`crossbind: signed in to ${url} as ${login}.`);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
program.command('logout')
|
|
318
|
-
.description('sign out of crossbind cloud on this machine')
|
|
319
|
-
.option('--all', 'sign out every machine signed in to this account')
|
|
320
|
-
.action(async (options) => {
|
|
321
|
-
const url = cloudUrl(state.config.system);
|
|
322
|
-
const result = await logoutOfCloud({ url, all: Boolean(options.all) });
|
|
323
|
-
if (!result) {
|
|
324
|
-
console.log(`crossbind: not signed in to ${url}.`);
|
|
325
|
-
} else if (result.revoked) {
|
|
326
|
-
console.log(`crossbind: signed ${result.login} out${options.all ? ' on every machine' : ''}.`);
|
|
327
|
-
} else {
|
|
328
|
-
console.warn(`crossbind: signed out here, but crossbind cloud did not revoke the token (${result.reason.replace(/^crossbind: /, '')}). Sign in again and run crossbind logout --all to revoke it.`);
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
program.command('usage')
|
|
333
|
-
.description('show what you used of crossbind cloud this month and what is left')
|
|
334
|
-
.action(async () => {
|
|
335
|
-
usageLines(await fetchUsage({ url: cloudUrl(state.config.system) })).forEach((line) => console.log(line));
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
program.command('account')
|
|
339
|
-
.description('manage your crossbind cloud account')
|
|
340
|
-
.command('delete')
|
|
341
|
-
.description('delete your crossbind cloud account, with the sign-in of every machine (https://crossbind.dev/privacy/)')
|
|
342
|
-
.option('--yes', 'delete it; without this, nothing is deleted')
|
|
343
|
-
.action(async (options) => {
|
|
344
|
-
const url = cloudUrl(state.config.system);
|
|
345
|
-
if (!options.yes) {
|
|
346
|
-
throw new Error(`crossbind: this deletes your crossbind cloud account at ${url}, with the sign-in of every machine. Run crossbind account delete --yes to go ahead.`);
|
|
347
|
-
}
|
|
348
|
-
const { login } = await deleteCloudAccount({ url });
|
|
349
|
-
console.log(`crossbind: deleted the crossbind cloud account of ${login}.`);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
const commandConfig = program.command('config')
|
|
353
|
-
.description('manage the crossbind configuration files');
|
|
354
|
-
|
|
355
|
-
commandConfig.command('get')
|
|
356
|
-
.description('get the crossbind system configuration')
|
|
357
|
-
.argument('<key>', 'configuration key to read')
|
|
358
|
-
.action((key) => getSystemConfig(key));
|
|
359
|
-
|
|
360
|
-
commandConfig.command('set')
|
|
361
|
-
.description('set the crossbind system configuration')
|
|
362
|
-
.argument('<key>', 'configuration key to set')
|
|
363
|
-
.argument('<value>', 'value to assign')
|
|
364
|
-
.action((key, value) => setSystemConfig(key, value));
|
|
365
|
-
|
|
366
|
-
commandConfig.command('delete')
|
|
367
|
-
.description('delete the crossbind system configuration')
|
|
368
|
-
.argument('<key>', 'configuration key to remove')
|
|
369
|
-
.action((key) => deleteSystemConfig(key));
|
|
370
|
-
|
|
371
|
-
commandConfig.command('list')
|
|
372
|
-
.description('list the crossbind configurations')
|
|
373
|
-
.addOption(new Option('-t, --type <type>', 'config type').default('system').choices(['all', 'system', 'project']))
|
|
374
|
-
.action((options) => listSystemConfig(options.type));
|
|
375
|
-
|
|
376
|
-
commandConfig.command('keys')
|
|
377
|
-
.description('list all available system configuration keys for crossbind')
|
|
378
|
-
.action(() => listSystemKeys());
|
|
325
|
+
addSystemCommands(program);
|
|
379
326
|
|
|
380
327
|
// `pnpm run <script> -- --flag` forwards the `--` literally and commander would then
|
|
381
328
|
// treat the flags as positionals. Strip the first separator for commands where every
|
|
@@ -388,67 +335,6 @@ if (['build', 'clean-deps', 'licenses'].includes(argv[2])) {
|
|
|
388
335
|
|
|
389
336
|
program.parse(argv);
|
|
390
337
|
|
|
391
|
-
function listSystemKeys() {
|
|
392
|
-
console.info('Available configurations:');
|
|
393
|
-
console.table(systemKeys);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function getSystemConfig(systemKey) {
|
|
397
|
-
if (!systemKey) {
|
|
398
|
-
listSystemConfig('system');
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
const key = systemKey.toUpperCase();
|
|
402
|
-
const output = {};
|
|
403
|
-
if (key in state.config.system) {
|
|
404
|
-
output[key] = { value: state.config.system[key] || 'undefined', default: systemKeys[key].default };
|
|
405
|
-
}
|
|
406
|
-
Object.keys(systemKeys).filter((k) => k.includes(key)).forEach((k) => {
|
|
407
|
-
if (k in state.config.system) {
|
|
408
|
-
output[k] = { value: state.config.system[k] || 'undefined', default: systemKeys[k].default };
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
console.table(output);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function setSystemConfig(key, value) {
|
|
416
|
-
if (!systemKeys[key]) {
|
|
417
|
-
throw new Error(`Configuration ${key} is not available. Please choose from the following available configurations: ${Object.keys(systemKeys).join(', ')}`);
|
|
418
|
-
}
|
|
419
|
-
if (systemKeys[key].options && !systemKeys[key].options.includes(value)) {
|
|
420
|
-
throw new Error(`Value ${value} is not available. Please choose from the following available values: ${systemKeys[key].options.join(', ')}`);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
state.config.system[key] = value;
|
|
424
|
-
writeJson(state.config.paths.systemConfig, state.config.system);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function deleteSystemConfig(key) {
|
|
428
|
-
if (!systemKeys[key]) {
|
|
429
|
-
throw new Error(`Configuration ${key} is not available. Please choose from the following available configurations: ${Object.keys(systemKeys).join(', ')}`);
|
|
430
|
-
}
|
|
431
|
-
delete state.config.system[key];
|
|
432
|
-
writeJson(state.config.paths.systemConfig, state.config.system);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function listSystemConfig(type) {
|
|
436
|
-
const { system: systemConfig, ...projectConfig } = state.config;
|
|
437
|
-
if (type === 'all' || type === 'system') {
|
|
438
|
-
console.log('System Configuration');
|
|
439
|
-
console.table(systemConfig);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (type === 'all') {
|
|
443
|
-
console.log('');
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (type === 'all' || type === 'project') {
|
|
447
|
-
console.log(`Project Configuration (paths relative to ${projectConfig.paths.base})`);
|
|
448
|
-
console.table(flattenConfigForTable(projectConfig, { base: projectConfig.paths.base }));
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
338
|
function run(programName, params) {
|
|
453
339
|
runCrossbindApp(programName, params, null, null, { console: true });
|
|
454
340
|
}
|
|
@@ -461,18 +347,20 @@ function onlyLoadsPackagedAddons(binaryParams) {
|
|
|
461
347
|
return targets.every((target) => target.runtimeEnv === 'node' && target.platform !== 'wasm') && addonsInPackages();
|
|
462
348
|
}
|
|
463
349
|
|
|
464
|
-
async function build(targetParams, rebuildOption, binaryRuntimeEnvs) {
|
|
350
|
+
async function build(targetParams, rebuildOption, binaryRuntimeEnvs, wasmOptions = {}, appPlatforms = []) {
|
|
465
351
|
await buildDependencies({ targetParams, rebuildOption });
|
|
466
352
|
const binaryParams = { ...targetParams, runtimeEnv: binaryRuntimeEnvs };
|
|
467
353
|
if (!onlyLoadsPackagedAddons(binaryParams)) {
|
|
468
|
-
|
|
354
|
+
// An app links its archives into itself, so it publishes no xcframework or podspec of them.
|
|
355
|
+
buildLib(targetParams, { skipXcframework: binaryRuntimeEnvs.includes('app') });
|
|
469
356
|
}
|
|
470
357
|
if (binaryRuntimeEnvs.length === 0) {
|
|
471
358
|
logger.info(`crossbind: no runtime environment was selected, so only the archives were built - pass -e (${runtimeEnvs.join(', ')}) for a binary.`);
|
|
472
359
|
} else {
|
|
473
|
-
await createWasmJs(binaryParams);
|
|
360
|
+
await createWasmJs(binaryParams, wasmOptions);
|
|
474
361
|
await createNodeAddons(binaryParams);
|
|
475
362
|
await createNativeCommands(binaryParams);
|
|
363
|
+
await createApps({ ...binaryParams, platform: appPlatforms });
|
|
476
364
|
await createWasiCommands(binaryParams);
|
|
477
365
|
}
|
|
478
366
|
buildPackageTypes();
|
|
@@ -491,6 +379,26 @@ async function createNativeCommands(targetParams) {
|
|
|
491
379
|
}
|
|
492
380
|
}
|
|
493
381
|
|
|
382
|
+
// An app built without a bundler has no page; the Vite plugin puts one in.
|
|
383
|
+
async function createApps(targetParams) {
|
|
384
|
+
const targets = getBuildTargets(targetParams).filter((target) => target.runtimeEnv === 'app');
|
|
385
|
+
if (targets.length === 0) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
targets.forEach(assertAppRunner);
|
|
389
|
+
const bridges = createNativeBridges(targets).headers.flatMap((header) => header.bridge ?? []);
|
|
390
|
+
// An iOS or Android app is one project for all its SDKs or ABIs, written once they are built, and every time, as it
|
|
391
|
+
// also takes the app's identity and the host package's template.
|
|
392
|
+
const projects = new Map();
|
|
393
|
+
for (const target of targets) {
|
|
394
|
+
const isProject = ['ios', 'android'].includes(target.platform);
|
|
395
|
+
const built = await buildApp(target, { bridges, force: !isProject && !fs.existsSync(appOutputPath(target)) });
|
|
396
|
+
if (isProject) projects.set(appOutputPath(target), target);
|
|
397
|
+
else if (built) publishApp(target);
|
|
398
|
+
}
|
|
399
|
+
projects.forEach((target) => publishApp(target));
|
|
400
|
+
}
|
|
401
|
+
|
|
494
402
|
async function createWasiCommands(targetParams) {
|
|
495
403
|
const targets = getFilteredBuildTargets(targetParams, { platform: 'wasi' });
|
|
496
404
|
if (targets.length === 0) {
|
|
@@ -506,23 +414,6 @@ async function createWasiCommands(targetParams) {
|
|
|
506
414
|
}
|
|
507
415
|
}
|
|
508
416
|
|
|
509
|
-
function createBridges(target) {
|
|
510
|
-
let headers = [];
|
|
511
|
-
state.config.paths.header.forEach((headerPath) => {
|
|
512
|
-
headers.push(findFiles('**/*.h', { cwd: headerPath }));
|
|
513
|
-
});
|
|
514
|
-
headers = headers.filter((path) => !!path.toString()).flat();
|
|
515
|
-
|
|
516
|
-
return headers.map((header) => ({ file: header, bridge: createBridgeFile(header, target) }));
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// The dependency headers the config binds whole, each read where one of the targets' packages ships it.
|
|
520
|
-
function createBoundBridges(targets) {
|
|
521
|
-
const headers = resolveBoundHeaders(boundHeaderSpecifiers(state.config), bridgeTargets(targets, state.targets), getDependFilePath);
|
|
522
|
-
const wholeHeaders = headers.map((header) => header.file);
|
|
523
|
-
return headers.map((header) => ({ ...header, bridge: createBridgeFile(header.file, header.target, { wholeHeaders }) }));
|
|
524
|
-
}
|
|
525
|
-
|
|
526
417
|
// A package whose platform packages link its addons ships what loads them: the loader and the data.
|
|
527
418
|
async function publishLoaders(targets) {
|
|
528
419
|
fs.mkdirSync(state.config.paths.output, { recursive: true });
|
|
@@ -544,10 +435,7 @@ async function createNodeAddons(targetParams) {
|
|
|
544
435
|
`${resolveEmbindJsiRoot()}/cpp/src`,
|
|
545
436
|
`${resolveEmbindNapiRoot()}/third_party/node-api-jsi/jsi`,
|
|
546
437
|
];
|
|
547
|
-
const
|
|
548
|
-
const ownHeaders = [...createBridges(nodeBridgeTarget(targets)), ...boundHeaders];
|
|
549
|
-
const imports = findAppNativeImports(bridgeTargets(targets, state.targets));
|
|
550
|
-
const headers = [...ownHeaders, ...createImportedBridges(ownHeaders, imports)];
|
|
438
|
+
const { headers, imports } = createNativeBridges(targets);
|
|
551
439
|
const opt = {
|
|
552
440
|
buildSource: false,
|
|
553
441
|
nativeGlob: [
|
|
@@ -593,16 +481,15 @@ function scopeOutputToCommonJs() {
|
|
|
593
481
|
}
|
|
594
482
|
}
|
|
595
483
|
|
|
596
|
-
async function createWasmJs(targetParams) {
|
|
484
|
+
async function createWasmJs(targetParams, wasmOptions = {}) {
|
|
597
485
|
const targets = getFilteredBuildTargets(targetParams, { platform: 'wasm' });
|
|
598
486
|
if (targets.length === 0) {
|
|
599
487
|
return;
|
|
600
488
|
}
|
|
601
489
|
const headers = createBridges();
|
|
602
|
-
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
const nodeHeaders = [...headers, ...createImportedBridges(headers, imports)];
|
|
490
|
+
const bindsImports = (target) => bindsAppImports(target, wasmOptions);
|
|
491
|
+
const imports = targets.some(bindsImports) ? findAppNativeImports([state.targets.find((t) => t.platform === 'wasm')]) : new Map();
|
|
492
|
+
const appHeaders = [...headers, ...createImportedBridges(headers, imports)];
|
|
606
493
|
const optOf = (bridged) => ({
|
|
607
494
|
buildSource: false,
|
|
608
495
|
nativeGlob: [
|
|
@@ -619,7 +506,7 @@ async function createWasmJs(targetParams) {
|
|
|
619
506
|
// link and the copy step below moved build/data away on the last run.
|
|
620
507
|
const distComplete = fs.existsSync(`${state.config.paths.output}/${target.jsName}`)
|
|
621
508
|
&& fs.existsSync(`${state.config.paths.output}/${target.wasmName}`);
|
|
622
|
-
createLib(target, 'Bridge', optOf(
|
|
509
|
+
createLib(target, 'Bridge', optOf(bindsImports(target) ? appHeaders : headers));
|
|
623
510
|
const built = await buildWasm(target, { force: !distComplete });
|
|
624
511
|
if (!built) {
|
|
625
512
|
continue;
|
|
@@ -640,15 +527,21 @@ async function createWasmJs(targetParams) {
|
|
|
640
527
|
if (targets.some((target) => ['node', 'edge'].includes(target.runtimeEnv))) {
|
|
641
528
|
scopeOutputToCommonJs();
|
|
642
529
|
}
|
|
530
|
+
// The list a page that maps the app's imports itself reads; a build without --bind-imports is not for one.
|
|
531
|
+
if (targets.some((target) => target.runtimeEnv === 'browser')) {
|
|
532
|
+
const manifest = `${state.config.paths.output}/browser/imports.json`;
|
|
533
|
+
if (wasmOptions.bindImports) writeImportManifest(manifest, appHeaders, imports, state.config.paths.project);
|
|
534
|
+
else fs.rmSync(manifest, { force: true });
|
|
535
|
+
}
|
|
643
536
|
// A runtime's entry loads its single-thread wasm32 release build.
|
|
644
537
|
const entryTargetOf = (runtimeEnv) => targets.find((target) => target.runtimeEnv === runtimeEnv && target.arch === 'wasm32'
|
|
645
538
|
&& target.runtime === 'st' && target.buildType === 'release' && fs.existsSync(`${state.config.paths.output}/${target.jsName}`));
|
|
646
539
|
const nodeTarget = entryTargetOf('node');
|
|
647
540
|
const edgeTarget = entryTargetOf('edge');
|
|
648
|
-
if (
|
|
541
|
+
if (appHeaders.length && nodeTarget) {
|
|
649
542
|
const entry = `${state.config.paths.output}/node/wasm.mjs`;
|
|
650
|
-
writeRuntimeEntry(entry,
|
|
651
|
-
if (imports.size) writeImportHooks(entry,
|
|
543
|
+
writeRuntimeEntry(entry, appHeaders, ENTRY_RUNTIMES.node(nodeTarget.jsName));
|
|
544
|
+
if (imports.size) writeImportHooks(entry, appHeaders, imports, nativeExtensions(state.config.ext));
|
|
652
545
|
else removeImportHooks(entry);
|
|
653
546
|
}
|
|
654
547
|
if (headers.length && edgeTarget) {
|
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
|
@@ -3,6 +3,12 @@ export { default as getParentPath } from './utils/getParentPath.js';
|
|
|
3
3
|
export { default as createLib } from './actions/createLib.js';
|
|
4
4
|
export { default as createBridgeFile } from './actions/createInterface.js';
|
|
5
5
|
export { default as buildWasm } from './actions/buildWasm.js';
|
|
6
|
+
export {
|
|
7
|
+
default as buildApp, buildProjectApp, publishApp, appLayout, appOutputName, appTargetParams,
|
|
8
|
+
} from './actions/buildApp.js';
|
|
9
|
+
export { default as openInSimulator, buildForSimulator } from './actions/iosSimulator.js';
|
|
10
|
+
export { default as openOnDevice } from './actions/androidDevice.js';
|
|
11
|
+
export { buildApk } from './actions/androidGradle.js';
|
|
6
12
|
export { default as isSourceNewer } from './actions/isSourceNewer.js';
|
|
7
13
|
export { default as getData } from './actions/getData.js';
|
|
8
14
|
export { default as getCmakeParameters } from './actions/getCmakeParameters.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import
|
|
3
|
+
import getEnv from '../actions/getEnv.js';
|
|
4
4
|
import loadJson from '../utils/loadJson.js';
|
|
5
5
|
import state from '../state/index.js';
|
|
6
6
|
import { parseSurface, createRustBridgeCrate, createCrateImportBridge } from '../utils/rustBridgeGen.js';
|
|
@@ -70,6 +70,7 @@ export function getRustSymbols(rsFile) {
|
|
|
70
70
|
modulePath,
|
|
71
71
|
spec: cargoDeps[crateName],
|
|
72
72
|
cacheDir: state.config.paths.cache,
|
|
73
|
+
projectDir: state.config.paths.project,
|
|
73
74
|
dtsMode: state.config.dts,
|
|
74
75
|
log: () => {},
|
|
75
76
|
});
|
|
@@ -110,8 +111,10 @@ function buildScript(target, symbols, base) {
|
|
|
110
111
|
if (!target) {
|
|
111
112
|
throw new Error('The target is not available!');
|
|
112
113
|
}
|
|
113
|
-
const env = JSON.stringify(
|
|
114
|
-
|
|
114
|
+
const env = JSON.stringify(getEnv(target));
|
|
115
|
+
let getPlatformScript = getReactNativeScript;
|
|
116
|
+
if (target.platform === 'wasm') getPlatformScript = getWebScript;
|
|
117
|
+
else if (target.runtimeEnv === 'app') getPlatformScript = getAppScript;
|
|
115
118
|
|
|
116
119
|
let symbolExportDefineString = '';
|
|
117
120
|
let symbolExportAssignString = '';
|
|
@@ -178,7 +181,7 @@ function getReactNativeScript(env) {
|
|
|
178
181
|
const RNJsiLib = TurboModuleRegistry.get('RNJsiLib');
|
|
179
182
|
|
|
180
183
|
function setEnv() {
|
|
181
|
-
const env =
|
|
184
|
+
const env = ${env};
|
|
182
185
|
const CROSSBIND_DATA_PATH = Module.Crossbind.getEnv('CROSSBIND_DATA_PATH');
|
|
183
186
|
|
|
184
187
|
Object.entries(env).forEach(([key, value]) => {
|
|
@@ -199,6 +202,18 @@ function getReactNativeScript(env) {
|
|
|
199
202
|
`;
|
|
200
203
|
}
|
|
201
204
|
|
|
205
|
+
// An app's page loads the runtime the app serves as crossbind.js, which asks the app's host for the module. The build's
|
|
206
|
+
// env is the host's own, so the page passes on only what initNative() is given.
|
|
207
|
+
function getAppScript(env, base) {
|
|
208
|
+
return `
|
|
209
|
+
function __crossbindBoot(config) {
|
|
210
|
+
const base = new URL((typeof __webpack_public_path__ === 'string' ? __webpack_public_path__ : ${JSON.stringify(base)}) || './', document.baseURI).href;
|
|
211
|
+
return import(/* webpackIgnore: true */ /* @vite-ignore */ base + 'crossbind.js')
|
|
212
|
+
.then(() => window.Crossbind.initNative(config));
|
|
213
|
+
}
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
|
|
202
217
|
function getWebScript(env, base) {
|
|
203
218
|
const params = `{
|
|
204
219
|
path: base,
|