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/actions/licenses.js
CHANGED
|
@@ -8,6 +8,11 @@ import familyManifestOf from '../utils/familyManifest.js';
|
|
|
8
8
|
import { wasiToolchainIdentity } from '../utils/provenance.js';
|
|
9
9
|
import { resolveWasiSdkPath } from '../utils/wasiToolchain.js';
|
|
10
10
|
import resolveEmbindNapiRoot, { resolveEmbindJsiRoot } from '../utils/resolveEmbindNapi.js';
|
|
11
|
+
import resolveAppHostRoot, { resolveAppEmbindJsiRoot } from '../utils/resolveAppHost.js';
|
|
12
|
+
import {
|
|
13
|
+
HERMES, HERMES_ANDROID, HERMES_SOURCE, hermesUrl,
|
|
14
|
+
} from '../utils/hermes.js';
|
|
15
|
+
import { ANDROIDX_WEBKIT } from './gradleProject.js';
|
|
11
16
|
import { shippedBundledLicense } from '../utils/bundledLicenses.js';
|
|
12
17
|
import toolchainNoticesDir from '../utils/toolchainNotices.js';
|
|
13
18
|
|
|
@@ -103,7 +108,7 @@ function wasiToolchainRows() {
|
|
|
103
108
|
];
|
|
104
109
|
}
|
|
105
110
|
|
|
106
|
-
// The notices a build copied out of its toolchain image (
|
|
111
|
+
// The notices a build copied out of its toolchain image (a Windows addon's or app's does).
|
|
107
112
|
function toolchainNoticeTexts(platform) {
|
|
108
113
|
const build = state.config?.paths?.build;
|
|
109
114
|
const dir = build ? path.join(build, toolchainNoticesDir(platform)) : null;
|
|
@@ -130,33 +135,32 @@ function addonToolchainRows(platform) {
|
|
|
130
135
|
};
|
|
131
136
|
const llvmText = 'license text: https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT';
|
|
132
137
|
if (platform === 'linux' || platform === 'linuxmusl') {
|
|
133
|
-
return [{ ...llvmRuntimes, licenseNotes: `libc++ and libc++abi of the linux toolchain image, statically linked into the
|
|
138
|
+
return [{ ...llvmRuntimes, licenseNotes: `libc++ and libc++abi of the linux toolchain image, statically linked into the binary; ${llvmText}` }];
|
|
134
139
|
}
|
|
135
140
|
const mingwLicense = 'LicenseRef-MinGW-w64-runtime';
|
|
136
141
|
return [
|
|
137
|
-
{ ...llvmRuntimes, licenseNotes: `libc++, libc++abi, libunwind and compiler-rt of the windows toolchain image (llvm-mingw), statically linked into the
|
|
142
|
+
{ ...llvmRuntimes, licenseNotes: `libc++, libc++abi, libunwind and compiler-rt of the windows toolchain image (llvm-mingw), statically linked into the binary; ${llvmText}` },
|
|
138
143
|
{
|
|
139
144
|
...shared,
|
|
140
145
|
name: 'mingw-w64-runtime',
|
|
141
146
|
license: mingwLicense,
|
|
142
147
|
licenseDeclared: mingwLicense,
|
|
143
148
|
sourceUrl: 'https://github.com/mingw-w64/mingw-w64',
|
|
144
|
-
licenseNotes: 'the mingw-w64 startup code and winpthreads, statically linked into the
|
|
149
|
+
licenseNotes: 'the mingw-w64 startup code and winpthreads, statically linked into the binary; a binary distribution includes their notices: https://github.com/mingw-w64/mingw-w64/blob/master/COPYING.MinGW-w64-runtime/COPYING.MinGW-w64-runtime.txt and https://github.com/mingw-w64/mingw-w64/blob/master/mingw-w64-libraries/winpthreads/COPYING (the windows image carries both in /opt/licenses/llvm-mingw)',
|
|
145
150
|
licenseText: toolchainNoticeTexts('win32'),
|
|
146
151
|
},
|
|
147
152
|
];
|
|
148
153
|
}
|
|
149
154
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
const versionOf = (dir) => JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).version;
|
|
156
|
+
const textOf = (file) => fs.readFileSync(file, 'utf8').trim();
|
|
157
|
+
|
|
158
|
+
// crossbind's runtime, which an addon and an app host compile in: embind-jsi, which adapts Emscripten's embind.
|
|
159
|
+
function runtimeRows(jsiRoot, notes) {
|
|
155
160
|
const shared = {
|
|
156
161
|
nativeVersion: null, sha256: null, licenseSelected: null, isCopyleft: false,
|
|
157
162
|
};
|
|
158
|
-
|
|
159
|
-
const rows = [
|
|
163
|
+
return [
|
|
160
164
|
{
|
|
161
165
|
...shared,
|
|
162
166
|
name: 'crossbind',
|
|
@@ -165,7 +169,7 @@ function nodeRuntimeRows({ isAddon }) {
|
|
|
165
169
|
license: 'MIT',
|
|
166
170
|
licenseDeclared: 'MIT',
|
|
167
171
|
sourceUrl: 'https://github.com/crossbind/crossbind',
|
|
168
|
-
licenseNotes:
|
|
172
|
+
licenseNotes: notes,
|
|
169
173
|
licenseText: textOf(path.join(CROSSBIND_ROOT, 'LICENSE')),
|
|
170
174
|
},
|
|
171
175
|
{
|
|
@@ -180,10 +184,19 @@ function nodeRuntimeRows({ isAddon }) {
|
|
|
180
184
|
licenseText: textOf(path.join(jsiRoot, 'LICENSE')),
|
|
181
185
|
},
|
|
182
186
|
];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// A Node-API addon compiles in crossbind's runtime and node-api-jsi. The loader bundles the JavaScript half of the
|
|
190
|
+
// same runtime.
|
|
191
|
+
function nodeRuntimeRows({ isAddon }) {
|
|
192
|
+
const rows = runtimeRows(resolveEmbindJsiRoot(), 'the embind-jsi and embind-napi runtime and the bindings crossbind generates');
|
|
183
193
|
if (!isAddon) return rows;
|
|
184
194
|
const napiRoot = resolveEmbindNapiRoot();
|
|
185
195
|
return [...rows, {
|
|
186
|
-
|
|
196
|
+
nativeVersion: null,
|
|
197
|
+
sha256: null,
|
|
198
|
+
licenseSelected: null,
|
|
199
|
+
isCopyleft: false,
|
|
187
200
|
name: 'node-api-jsi',
|
|
188
201
|
npmName: '@crossbind/core-embind-napi',
|
|
189
202
|
version: versionOf(napiRoot),
|
|
@@ -195,6 +208,137 @@ function nodeRuntimeRows({ isAddon }) {
|
|
|
195
208
|
}];
|
|
196
209
|
}
|
|
197
210
|
|
|
211
|
+
// What an app's host carries beside crossbind's runtime, pinned as the third_party READMEs of @crossbind/core-app-host
|
|
212
|
+
// record: React Native's Hermes, JSI's jsi.cpp from the same release, webview/webview, and on Windows the WebView2 SDK's
|
|
213
|
+
// headers.
|
|
214
|
+
const WEBVIEW = { version: 'cbbdee44afff22867de9fd88a9fc8350d9bdd399', sha256: '10e972a2327b5681474f4aa4499e505eaa4ff659aa995380386f08fd6fc1b763' };
|
|
215
|
+
const WEBVIEW2_SDK = { version: '1.0.4258.31', sha256: '56f7f4b8bf9aee4b8efefbbdd4f67d5f74ebd1b100ed0806da71bf76af481aa9' };
|
|
216
|
+
const JSI_SHA256 = 'cb6274a22abc93ecc970066539815e716d3e47716e8f93a9971cf4eb5918ffc1';
|
|
217
|
+
// The fbjni the Hermes release for Android depends on.
|
|
218
|
+
const FBJNI_VERSION = '0.7.0';
|
|
219
|
+
|
|
220
|
+
const HERMES_BUILDS = {
|
|
221
|
+
linux: {
|
|
222
|
+
sha256: HERMES_SOURCE.sha256,
|
|
223
|
+
sourceUrl: HERMES_SOURCE.url,
|
|
224
|
+
licenseNotes: "Hermes built from its release's source in the linux toolchain image, statically linked into the host",
|
|
225
|
+
},
|
|
226
|
+
win32: {
|
|
227
|
+
sha256: HERMES_SOURCE.sha256,
|
|
228
|
+
sourceUrl: HERMES_SOURCE.url,
|
|
229
|
+
licenseNotes: "Hermes built from its release's source in the windows toolchain image (llvm-mingw), statically linked into the host",
|
|
230
|
+
},
|
|
231
|
+
android: {
|
|
232
|
+
sha256: HERMES_ANDROID.sha256,
|
|
233
|
+
sourceUrl: HERMES_ANDROID.url,
|
|
234
|
+
licenseNotes: "React Native's Hermes for Android, whose libraries the app's Gradle build takes from Maven Central",
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// What an Android app's Gradle build adds from Maven, and the C++ runtime the app carries beside its host.
|
|
239
|
+
function androidAppRows() {
|
|
240
|
+
const shared = {
|
|
241
|
+
npmName: null, sha256: null, licenseSelected: null, licenseText: null, isCopyleft: false,
|
|
242
|
+
};
|
|
243
|
+
const apache = { ...shared, license: 'Apache-2.0', licenseDeclared: 'Apache-2.0' };
|
|
244
|
+
const llvmLicense = 'Apache-2.0 WITH LLVM-exception';
|
|
245
|
+
return [
|
|
246
|
+
{
|
|
247
|
+
...apache,
|
|
248
|
+
name: 'fbjni',
|
|
249
|
+
version: FBJNI_VERSION,
|
|
250
|
+
nativeVersion: FBJNI_VERSION,
|
|
251
|
+
sourceUrl: 'https://github.com/facebookincubator/fbjni',
|
|
252
|
+
licenseNotes: 'libfbjni.so, which Hermes loads and Gradle takes with it; license text: https://github.com/facebookincubator/fbjni/blob/main/LICENSE',
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
...apache,
|
|
256
|
+
name: 'androidx-webkit',
|
|
257
|
+
version: ANDROIDX_WEBKIT,
|
|
258
|
+
nativeVersion: ANDROIDX_WEBKIT,
|
|
259
|
+
sourceUrl: 'https://developer.android.com/jetpack/androidx/releases/webkit',
|
|
260
|
+
licenseNotes: "androidx.webkit, the WebView's message ports and start scripts, from Google's Maven repository; license text: https://www.apache.org/licenses/LICENSE-2.0.txt",
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
...shared,
|
|
264
|
+
name: 'llvm-runtimes',
|
|
265
|
+
version: null,
|
|
266
|
+
nativeVersion: null,
|
|
267
|
+
license: llvmLicense,
|
|
268
|
+
licenseDeclared: llvmLicense,
|
|
269
|
+
sourceUrl: 'https://github.com/llvm/llvm-project',
|
|
270
|
+
licenseNotes: "libc++ of the android image's NDK, which the app carries as libc++_shared.so; license text: https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT",
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// The WebView2 SDK headers a Windows host compiles against. webview/webview loads the WebView2 Runtime itself, without
|
|
276
|
+
// the SDK's loader.
|
|
277
|
+
function windowsAppRows(hostRoot) {
|
|
278
|
+
const license = 'BSD-3-Clause';
|
|
279
|
+
return [{
|
|
280
|
+
npmName: '@crossbind/core-app-host',
|
|
281
|
+
name: 'webview2-sdk',
|
|
282
|
+
version: WEBVIEW2_SDK.version,
|
|
283
|
+
nativeVersion: WEBVIEW2_SDK.version,
|
|
284
|
+
sha256: WEBVIEW2_SDK.sha256,
|
|
285
|
+
license,
|
|
286
|
+
licenseDeclared: license,
|
|
287
|
+
licenseSelected: null,
|
|
288
|
+
isCopyleft: false,
|
|
289
|
+
sourceUrl: `https://api.nuget.org/v3-flatcontainer/microsoft.web.webview2/${WEBVIEW2_SDK.version}/microsoft.web.webview2.${WEBVIEW2_SDK.version}.nupkg`,
|
|
290
|
+
licenseNotes: "the WebView2 SDK's headers from the windows toolchain image, whose interface IDs the host compiles in",
|
|
291
|
+
licenseText: textOf(path.join(hostRoot, 'third_party', 'webview2', 'LICENSE')),
|
|
292
|
+
}];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function appRuntimeRows(platform) {
|
|
296
|
+
const hostRoot = resolveAppHostRoot();
|
|
297
|
+
const hermesLicense = textOf(path.join(hostRoot, 'third_party', 'jsi', 'LICENSE'));
|
|
298
|
+
const mit = {
|
|
299
|
+
npmName: '@crossbind/core-app-host', license: 'MIT', licenseDeclared: 'MIT', licenseSelected: null, isCopyleft: false,
|
|
300
|
+
};
|
|
301
|
+
const hermes = HERMES_BUILDS[platform] ?? {
|
|
302
|
+
sha256: HERMES.sha256,
|
|
303
|
+
sourceUrl: hermesUrl(HERMES.version),
|
|
304
|
+
licenseNotes: "React Native's Hermes for Apple platforms, which the app carries as hermesvm.framework",
|
|
305
|
+
};
|
|
306
|
+
return [
|
|
307
|
+
...runtimeRows(resolveAppEmbindJsiRoot(), 'the embind-jsi runtime, the app host and the bindings crossbind generates'),
|
|
308
|
+
{
|
|
309
|
+
...mit,
|
|
310
|
+
...hermes,
|
|
311
|
+
name: 'hermes',
|
|
312
|
+
version: HERMES.version,
|
|
313
|
+
nativeVersion: HERMES.version,
|
|
314
|
+
licenseText: hermesLicense,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
...mit,
|
|
318
|
+
name: 'jsi',
|
|
319
|
+
version: HERMES.version,
|
|
320
|
+
nativeVersion: HERMES.version,
|
|
321
|
+
sha256: JSI_SHA256,
|
|
322
|
+
sourceUrl: 'https://github.com/facebook/hermes',
|
|
323
|
+
licenseNotes: "JSI's jsi.cpp from the same Hermes release, compiled into the host",
|
|
324
|
+
licenseText: hermesLicense,
|
|
325
|
+
},
|
|
326
|
+
...(platform === 'android' ? androidAppRows() : []),
|
|
327
|
+
...(platform === 'win32' ? windowsAppRows(hostRoot) : []),
|
|
328
|
+
// A mobile app makes its window with UIKit or an Android activity, without webview/webview.
|
|
329
|
+
...(['ios', 'android'].includes(platform) ? [] : [{
|
|
330
|
+
...mit,
|
|
331
|
+
name: 'webview',
|
|
332
|
+
version: WEBVIEW.version,
|
|
333
|
+
nativeVersion: WEBVIEW.version,
|
|
334
|
+
sha256: WEBVIEW.sha256,
|
|
335
|
+
sourceUrl: 'https://github.com/webview/webview',
|
|
336
|
+
licenseNotes: 'webview/webview, with the local patch @crossbind/core-app-host lists',
|
|
337
|
+
licenseText: textOf(path.join(hostRoot, 'third_party', 'webview', 'LICENSE')),
|
|
338
|
+
}]),
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
|
|
198
342
|
async function buildRow(node) {
|
|
199
343
|
const recipe = await loadJs(node.paths.project, 'crossbind.build');
|
|
200
344
|
const manifest = node.package || null;
|
|
@@ -284,7 +428,8 @@ function assertConanStaged() {
|
|
|
284
428
|
// platform (optional, e.g. 'wasi') additionally includes what that platform's
|
|
285
429
|
// artifact statically links beyond the package graph: recipe-declared vendored
|
|
286
430
|
// copies and the toolchain runtime. runtimeEnv 'node' adds the crossbind runtime an
|
|
287
|
-
// addon of that platform compiles in, or without a platform the one its loader bundles
|
|
431
|
+
// addon of that platform compiles in, or without a platform the one its loader bundles;
|
|
432
|
+
// runtimeEnv 'app' adds what an app's host carries.
|
|
288
433
|
export default async function collectLicenseRows(platform = null, { runtimeEnv = null } = {}) {
|
|
289
434
|
assertConanStaged();
|
|
290
435
|
// The root package is a component too when it is a port: leaf -wasi packages have no deps but ship
|
|
@@ -310,5 +455,6 @@ export default async function collectLicenseRows(platform = null, { runtimeEnv =
|
|
|
310
455
|
if (platform === 'wasi') rows.push(...wasiToolchainRows());
|
|
311
456
|
if (['linux', 'linuxmusl', 'win32'].includes(platform)) rows.push(...addonToolchainRows(platform));
|
|
312
457
|
if (runtimeEnv === 'node') rows.push(...nodeRuntimeRows({ isAddon: platform !== null }));
|
|
458
|
+
if (runtimeEnv === 'app') rows.push(...appRuntimeRows(platform));
|
|
313
459
|
return rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
314
460
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import createBridgeFile from './createInterface.js';
|
|
2
|
+
import { createImportedBridges, findAppNativeImports } from './createImportedBridges.js';
|
|
3
|
+
import getDependFilePath from '../integration/getDependFilePath.js';
|
|
4
|
+
import state from '../state/index.js';
|
|
5
|
+
import findFiles from '../utils/findFiles.js';
|
|
6
|
+
import {
|
|
7
|
+
boundHeaderSpecifiers, bridgeTargets, nodeBridgeTarget, resolveBoundHeaders,
|
|
8
|
+
} from '../utils/boundHeaders.js';
|
|
9
|
+
|
|
10
|
+
// The project's own headers, each with its bridge.
|
|
11
|
+
export function createBridges(target) {
|
|
12
|
+
let headers = [];
|
|
13
|
+
state.config.paths.header.forEach((headerPath) => {
|
|
14
|
+
headers.push(findFiles('**/*.h', { cwd: headerPath }));
|
|
15
|
+
});
|
|
16
|
+
headers = headers.filter((path) => !!path.toString()).flat();
|
|
17
|
+
|
|
18
|
+
return headers.map((header) => ({ file: header, bridge: createBridgeFile(header, target) }));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The dependency headers the config binds whole, each read where one of the targets' packages ships it.
|
|
22
|
+
export function createBoundBridges(targets) {
|
|
23
|
+
const headers = resolveBoundHeaders(boundHeaderSpecifiers(state.config), bridgeTargets(targets, state.targets), getDependFilePath);
|
|
24
|
+
const wholeHeaders = headers.map((header) => header.file);
|
|
25
|
+
return headers.map((header) => ({ ...header, bridge: createBridgeFile(header.file, header.target, { wholeHeaders }) }));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// What a native binary binds, an addon or an app host: the project's headers, the dependency headers the config binds
|
|
29
|
+
// whole, and the ones the app's sources import.
|
|
30
|
+
export function createNativeBridges(targets) {
|
|
31
|
+
const boundHeaders = createBoundBridges(targets);
|
|
32
|
+
const ownHeaders = [...createBridges(nodeBridgeTarget(targets)), ...boundHeaders];
|
|
33
|
+
const imports = findAppNativeImports(bridgeTargets(targets, state.targets));
|
|
34
|
+
return { headers: [...ownHeaders, ...createImportedBridges(ownHeaders, imports)], imports };
|
|
35
|
+
}
|
package/src/actions/run.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from '../utils/darwinToolchain.js';
|
|
23
23
|
|
|
24
24
|
// Native builds can outrun Node's 1 MiB default pipe buffer; without a raised cap a successful build dies with ENOBUFS.
|
|
25
|
-
const EXEC_MAX_BUFFER = 512 * 1024 * 1024;
|
|
25
|
+
export const EXEC_MAX_BUFFER = 512 * 1024 * 1024;
|
|
26
26
|
// Containers share the memory of Docker's VM, and one compile at full -j can take most of a desktop VM
|
|
27
27
|
// (GDAL's bindings peak at 7.5 of 7.7 GiB), so the exclusive steps of parallel builds take turns.
|
|
28
28
|
const DOCKER_COMPILE_LOCK = path.join(os.homedir(), '.crossbind', 'docker-compile.lock');
|
|
@@ -124,7 +124,7 @@ const iosParams = [
|
|
|
124
124
|
|
|
125
125
|
// The C++ compiler of the image that runs SWIG for the target, so its -dM macro table matches that parse.
|
|
126
126
|
export function cxxPreprocessorFor(target) {
|
|
127
|
-
if (
|
|
127
|
+
if (target?.platform !== 'android') return 'em++';
|
|
128
128
|
return `${t}/${target.arch === 'x86_64' ? CROSSCOMPILER_x86_64 : CROSSCOMPILER_ARM64}-clang++`;
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -196,7 +196,7 @@ export default function run(program, params = [], platformPrefix = null, target
|
|
|
196
196
|
case 'android':
|
|
197
197
|
[dProgram, ...dParams] = params;
|
|
198
198
|
platformParams = target.arch === 'x86_64' ? androidParamsX86_64 : androidParamsArm64;
|
|
199
|
-
if (dProgram === 'cmake') {
|
|
199
|
+
if (dProgram === 'cmake' && dParams[0] !== '--build' && dParams[0] !== '--install') {
|
|
200
200
|
dParams = [
|
|
201
201
|
...dParams,
|
|
202
202
|
`-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake`,
|
|
@@ -40,6 +40,7 @@ const BOOT_SCRIPT = [
|
|
|
40
40
|
const VERSION = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version;
|
|
41
41
|
|
|
42
42
|
const runnerName = (role) => `crossbind-runner-${role}`;
|
|
43
|
+
const localRunnerName = (role) => `crossbind-local-runner-${role}`;
|
|
43
44
|
const runnerFiles = () => fs.readdirSync(RUNNER_SOURCE).filter((file) => file.endsWith('.js'));
|
|
44
45
|
// Google ships the linux NDK for x86_64 only, so an android runner is always amd64.
|
|
45
46
|
const platformOf = (role) => (role === 'android' ? 'linux/amd64' : undefined);
|
|
@@ -62,7 +63,7 @@ function assertRole(role) {
|
|
|
62
63
|
export function runnerStartArgs({ role, port, host }) {
|
|
63
64
|
const platform = platformOf(role);
|
|
64
65
|
return [
|
|
65
|
-
'run', '-d', '--name',
|
|
66
|
+
'run', '-d', '--name', localRunnerName(role), '--restart', 'unless-stopped',
|
|
66
67
|
...(platform ? ['--platform', platform] : []),
|
|
67
68
|
...DOCKER_RUN_SECURITY_ARGS,
|
|
68
69
|
'-p', `${host}:${port}:${RUNNER_PORT}`,
|
|
@@ -89,13 +90,24 @@ export function startRunner({ role = 'web', port = runnerHostPort(role), host =
|
|
|
89
90
|
});
|
|
90
91
|
// A token taken from the environment is shown by its variable, so the line printed for it puts no secret in a log.
|
|
91
92
|
return {
|
|
92
|
-
name:
|
|
93
|
+
name: localRunnerName(role), url: `http://${host}:${port}`, token: secret, displayToken: fromEnvironment ? '$CROSSBIND_RUNNER_TOKEN' : secret,
|
|
93
94
|
};
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
// A container that is not there is already deleted: docker reports one on stderr even with -f, and some engines exit 1.
|
|
98
|
+
function removeContainer(name) {
|
|
99
|
+
try {
|
|
100
|
+
execFileSync('docker', ['rm', '-f', name], { stdio: ['ignore', 'inherit', 'pipe'] });
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (!/no such container/i.test(String(error.stderr))) throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function deleteRunner({ role = 'web' } = {}) {
|
|
97
107
|
assertRole(role);
|
|
98
|
-
|
|
108
|
+
removeContainer(localRunnerName(role));
|
|
109
|
+
// An older crossbind started its local runner under the name the deployed runners take.
|
|
110
|
+
removeContainer(runnerName(role));
|
|
99
111
|
}
|
|
100
112
|
|
|
101
113
|
function dockerfile(role) {
|
package/src/actions/target.js
CHANGED
|
@@ -28,7 +28,8 @@ export function getTargetParams(givenTargetParams = {}, noParamCheck = false) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function getBuildTargets(targetParams) {
|
|
31
|
-
|
|
31
|
+
// A lookup that names no runtime environment finds the targets that have none: the archives' alone.
|
|
32
|
+
const { platform, arch, runtime, buildType, runtimeEnv = [] } = targetParams;
|
|
32
33
|
return state.targets.filter(t => (
|
|
33
34
|
(!t.platform || platform.includes(t.platform))
|
|
34
35
|
&& (!t.arch || arch.includes(t.arch))
|
|
@@ -36,13 +36,10 @@ export default function writeRuntimeEntry(entry, headers, runtime) {
|
|
|
36
36
|
|
|
37
37
|
const IMPORT_HOOKS = new URL('../assets/node-import-hooks.mjs', import.meta.url);
|
|
38
38
|
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Node runs the register file from its real path, so the files it names are relative to that.
|
|
44
|
-
const dir = upath.normalize(fs.realpathSync(upath.dirname(entry)));
|
|
45
|
-
const natives = headers.map((header) => {
|
|
39
|
+
// Each bound header and Rust import by its real path relative to dir, with the bare specifiers that name it (imports,
|
|
40
|
+
// from findAppNativeImports) and its names, each with the module's name for it where the two differ.
|
|
41
|
+
function nativesOf(headers, imports, dir) {
|
|
42
|
+
return headers.map((header) => {
|
|
46
43
|
const file = upath.normalize(fs.realpathSync(header.file));
|
|
47
44
|
return {
|
|
48
45
|
file: upath.relative(dir, file),
|
|
@@ -50,6 +47,13 @@ export function writeImportHooks(entry, headers, imports, extensions) {
|
|
|
50
47
|
names: uniqueBindings(namesOf(header)).map(({ local, wire }) => (local === wire ? local : { local, wire })),
|
|
51
48
|
};
|
|
52
49
|
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The module `node --import` loads ahead of the app, so the app imports the bound headers themselves.
|
|
53
|
+
export function writeImportHooks(entry, headers, imports, extensions) {
|
|
54
|
+
// Node runs the register file from its real path, so the files it names are relative to that.
|
|
55
|
+
const dir = upath.normalize(fs.realpathSync(upath.dirname(entry)));
|
|
56
|
+
const natives = nativesOf(headers, imports, dir);
|
|
53
57
|
writeIfChanged(`${dir}/hooks.mjs`, fs.readFileSync(IMPORT_HOOKS, 'utf8'));
|
|
54
58
|
writeIfChanged(entry.replace(/\.mjs$/, '.register.mjs'), [
|
|
55
59
|
'// Generated by crossbind - do not edit.',
|
|
@@ -60,6 +64,13 @@ export function writeImportHooks(entry, headers, imports, extensions) {
|
|
|
60
64
|
].join('\n'));
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
// Beside a browser binary built with --bind-imports: what a page that resolves the app's imports itself (the
|
|
68
|
+
// playground) maps them with, each file relative to the project.
|
|
69
|
+
export function writeImportManifest(file, headers, imports, projectDir) {
|
|
70
|
+
const natives = nativesOf(headers, imports, upath.normalize(fs.realpathSync(projectDir)));
|
|
71
|
+
writeIfChanged(file, `${JSON.stringify({ natives }, null, 4)}\n`);
|
|
72
|
+
}
|
|
73
|
+
|
|
63
74
|
// Once the sources import nothing native, no start loads hooks an earlier build left; hooks.mjs stays while the other
|
|
64
75
|
// binary's hooks still import it.
|
|
65
76
|
export function removeImportHooks(entry) {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import state from '../state/index.js';
|
|
4
|
+
import { IOS_DEPLOYMENT_TARGET } from '../utils/iosToolchain.js';
|
|
5
|
+
import { textPlist, xmlPlist } from '../utils/plist.js';
|
|
6
|
+
|
|
7
|
+
// The SDKs an iOS app builds for, one target each, and the slice of Hermes' release each takes.
|
|
8
|
+
export const IOS_SDKS = ['iphoneos', 'iphonesimulator'];
|
|
9
|
+
export const HERMES_SLICES = { iphoneos: 'ios-arm64', iphonesimulator: 'ios-arm64_x86_64-simulator' };
|
|
10
|
+
// What the host's own code needs from the system: Xcode adds it only for the sources it compiles itself.
|
|
11
|
+
const SYSTEM_LIBS = ['-framework', 'UIKit', '-framework', 'WebKit', '-framework', 'CoreGraphics', '-lc++'];
|
|
12
|
+
|
|
13
|
+
const MAIN = `// crossbind writes this project anew on every build: the app comes prebuilt in lib/, and its files are in app/.
|
|
14
|
+
int crossbind_app_main(int argc, char *argv[]);
|
|
15
|
+
|
|
16
|
+
int main(int argc, char *argv[]) { return crossbind_app_main(argc, argv); }
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
const OBJECT_NAMES = ['project', 'mainGroup', 'products', 'target', 'product', 'main', 'info', 'app', 'hermes', 'mainBuild',
|
|
20
|
+
'appBuild', 'hermesLink', 'hermesEmbed', 'sources', 'frameworks', 'resources', 'embed', 'projectDebug', 'projectRelease',
|
|
21
|
+
'targetDebug', 'targetRelease', 'projectConfigs', 'targetConfigs'];
|
|
22
|
+
// Xcode names its objects by 24 hex digits. Fixed ones leave the project unchanged while what it holds is.
|
|
23
|
+
const ID = Object.fromEntries(OBJECT_NAMES.map((name, index) => [name, `CB${(index + 1).toString(16).toUpperCase().padStart(22, '0')}`]));
|
|
24
|
+
|
|
25
|
+
const PROJECT_SETTINGS = {
|
|
26
|
+
SDKROOT: 'iphoneos',
|
|
27
|
+
SUPPORTED_PLATFORMS: 'iphoneos iphonesimulator',
|
|
28
|
+
IPHONEOS_DEPLOYMENT_TARGET: IOS_DEPLOYMENT_TARGET,
|
|
29
|
+
TARGETED_DEVICE_FAMILY: '1,2',
|
|
30
|
+
// crossbind's iOS archives are arm64 alone, for devices and the Simulator alike.
|
|
31
|
+
ARCHS: 'arm64',
|
|
32
|
+
ALWAYS_SEARCH_USER_PATHS: 'NO',
|
|
33
|
+
CLANG_ENABLE_OBJC_ARC: 'YES',
|
|
34
|
+
DEAD_CODE_STRIPPING: 'YES',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const ORIENTATIONS = ['UIInterfaceOrientationPortrait', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight'];
|
|
38
|
+
|
|
39
|
+
const infoPlist = (name) => xmlPlist({
|
|
40
|
+
CFBundleDevelopmentRegion: 'en',
|
|
41
|
+
CFBundleDisplayName: name,
|
|
42
|
+
CFBundleExecutable: '$(EXECUTABLE_NAME)',
|
|
43
|
+
CFBundleIdentifier: '$(PRODUCT_BUNDLE_IDENTIFIER)',
|
|
44
|
+
CFBundleInfoDictionaryVersion: '6.0',
|
|
45
|
+
CFBundleName: '$(PRODUCT_NAME)',
|
|
46
|
+
CFBundlePackageType: 'APPL',
|
|
47
|
+
CFBundleShortVersionString: '$(MARKETING_VERSION)',
|
|
48
|
+
CFBundleVersion: '$(CURRENT_PROJECT_VERSION)',
|
|
49
|
+
LSRequiresIPhoneOS: true,
|
|
50
|
+
UIApplicationSceneManifest: { UIApplicationSupportsMultipleScenes: false },
|
|
51
|
+
UILaunchScreen: {},
|
|
52
|
+
UISupportedInterfaceOrientations: ORIENTATIONS,
|
|
53
|
+
'UISupportedInterfaceOrientations~ipad': [...ORIENTATIONS, 'UIInterfaceOrientationPortraitUpsideDown'],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function objects(name, targetSettings) {
|
|
57
|
+
const file = (path, type) => ({ isa: 'PBXFileReference', lastKnownFileType: type, path, sourceTree: '<group>' });
|
|
58
|
+
const phase = (isa, files, extra = {}) => ({ isa, buildActionMask: 2147483647, files, runOnlyForDeploymentPostprocessing: 0, ...extra });
|
|
59
|
+
const configuration = (configurationName, buildSettings) => ({ isa: 'XCBuildConfiguration', buildSettings, name: configurationName });
|
|
60
|
+
const configurations = (debug, release) => ({
|
|
61
|
+
isa: 'XCConfigurationList', buildConfigurations: [debug, release], defaultConfigurationIsVisible: 0, defaultConfigurationName: 'Release',
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
[ID.main]: file('main.m', 'sourcecode.c.objc'),
|
|
65
|
+
[ID.info]: file('Info.plist', 'text.plist.xml'),
|
|
66
|
+
[ID.app]: file('app', 'folder'),
|
|
67
|
+
[ID.hermes]: file('Frameworks/hermesvm.xcframework', 'wrapper.xcframework'),
|
|
68
|
+
[ID.product]: {
|
|
69
|
+
isa: 'PBXFileReference', explicitFileType: 'wrapper.application', includeInIndex: 0, path: `${name}.app`, sourceTree: 'BUILT_PRODUCTS_DIR',
|
|
70
|
+
},
|
|
71
|
+
[ID.mainBuild]: { isa: 'PBXBuildFile', fileRef: ID.main },
|
|
72
|
+
[ID.appBuild]: { isa: 'PBXBuildFile', fileRef: ID.app },
|
|
73
|
+
[ID.hermesLink]: { isa: 'PBXBuildFile', fileRef: ID.hermes },
|
|
74
|
+
[ID.hermesEmbed]: { isa: 'PBXBuildFile', fileRef: ID.hermes, settings: { ATTRIBUTES: ['CodeSignOnCopy', 'RemoveHeadersOnCopy'] } },
|
|
75
|
+
[ID.sources]: phase('PBXSourcesBuildPhase', [ID.mainBuild]),
|
|
76
|
+
[ID.frameworks]: phase('PBXFrameworksBuildPhase', [ID.hermesLink]),
|
|
77
|
+
[ID.resources]: phase('PBXResourcesBuildPhase', [ID.appBuild]),
|
|
78
|
+
[ID.embed]: phase('PBXCopyFilesBuildPhase', [ID.hermesEmbed], { dstPath: '', dstSubfolderSpec: 10, name: 'Embed Frameworks' }),
|
|
79
|
+
[ID.mainGroup]: { isa: 'PBXGroup', children: [ID.main, ID.info, ID.app, ID.hermes, ID.products], sourceTree: '<group>' },
|
|
80
|
+
[ID.products]: { isa: 'PBXGroup', children: [ID.product], name: 'Products', sourceTree: '<group>' },
|
|
81
|
+
[ID.target]: {
|
|
82
|
+
isa: 'PBXNativeTarget',
|
|
83
|
+
buildConfigurationList: ID.targetConfigs,
|
|
84
|
+
buildPhases: [ID.sources, ID.frameworks, ID.resources, ID.embed],
|
|
85
|
+
buildRules: [],
|
|
86
|
+
dependencies: [],
|
|
87
|
+
name,
|
|
88
|
+
productName: name,
|
|
89
|
+
productReference: ID.product,
|
|
90
|
+
productType: 'com.apple.product-type.application',
|
|
91
|
+
},
|
|
92
|
+
[ID.project]: {
|
|
93
|
+
isa: 'PBXProject',
|
|
94
|
+
buildConfigurationList: ID.projectConfigs,
|
|
95
|
+
compatibilityVersion: 'Xcode 14.0',
|
|
96
|
+
developmentRegion: 'en',
|
|
97
|
+
hasScannedForEncodings: 0,
|
|
98
|
+
knownRegions: ['en', 'Base'],
|
|
99
|
+
mainGroup: ID.mainGroup,
|
|
100
|
+
productRefGroup: ID.products,
|
|
101
|
+
projectDirPath: '',
|
|
102
|
+
projectRoot: '',
|
|
103
|
+
targets: [ID.target],
|
|
104
|
+
},
|
|
105
|
+
[ID.projectDebug]: configuration('Debug', PROJECT_SETTINGS),
|
|
106
|
+
[ID.projectRelease]: configuration('Release', PROJECT_SETTINGS),
|
|
107
|
+
[ID.targetDebug]: configuration('Debug', targetSettings),
|
|
108
|
+
[ID.targetRelease]: configuration('Release', targetSettings),
|
|
109
|
+
[ID.projectConfigs]: configurations(ID.projectDebug, ID.projectRelease),
|
|
110
|
+
[ID.targetConfigs]: configurations(ID.targetDebug, ID.targetRelease),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// An iOS app as an Xcode project, written anew from the parts its SDK targets built: each brings its archives and
|
|
115
|
+
// their link line, and its slice of Hermes; the first also the app's files. Xcode signs the app and runs it, in the
|
|
116
|
+
// Simulator or on a device.
|
|
117
|
+
export default function writeXcodeProject(to, { parts, identity, version }) {
|
|
118
|
+
const { name } = state.config.general;
|
|
119
|
+
if (parts.length === 0) throw new Error(`crossbind: no part of the iOS app ${name} is built, so there is no Xcode project to write.`);
|
|
120
|
+
fs.rmSync(to, { recursive: true, force: true });
|
|
121
|
+
fs.mkdirSync(`${to}/${name}.xcodeproj`, { recursive: true });
|
|
122
|
+
const linkFlags = {};
|
|
123
|
+
parts.forEach(({ sdk, dir }) => {
|
|
124
|
+
fs.cpSync(`${dir}/lib`, `${to}/lib/${sdk}`, { recursive: true });
|
|
125
|
+
// The quotes keep a project path with spaces in one argument.
|
|
126
|
+
const args = JSON.parse(fs.readFileSync(`${dir}/link.json`, 'utf8'))
|
|
127
|
+
.map((arg) => arg.replace(/\{lib\}\/(.+)$/, `"$(PROJECT_DIR)/lib/${sdk}/$1"`));
|
|
128
|
+
linkFlags[`OTHER_LDFLAGS[sdk=${sdk}*]`] = [...args, ...SYSTEM_LIBS];
|
|
129
|
+
});
|
|
130
|
+
fs.cpSync(`${parts[0].dir}/app`, `${to}/app`, { recursive: true });
|
|
131
|
+
const frameworks = parts.flatMap(({ dir }) => ['-framework', `${dir}/hermesvm.framework`]);
|
|
132
|
+
const result = spawnSync('xcodebuild', ['-create-xcframework', ...frameworks, '-output', `${to}/Frameworks/hermesvm.xcframework`], { encoding: 'utf8' });
|
|
133
|
+
if (result.status !== 0) throw new Error(`crossbind: cannot make Hermes' xcframework for ${to}: ${result.stderr || result.error?.message}`);
|
|
134
|
+
|
|
135
|
+
fs.writeFileSync(`${to}/main.m`, MAIN);
|
|
136
|
+
fs.writeFileSync(`${to}/Info.plist`, infoPlist(name));
|
|
137
|
+
const targetSettings = {
|
|
138
|
+
PRODUCT_NAME: name,
|
|
139
|
+
PRODUCT_BUNDLE_IDENTIFIER: identity.id,
|
|
140
|
+
DEVELOPMENT_TEAM: identity.team,
|
|
141
|
+
CODE_SIGN_STYLE: 'Automatic',
|
|
142
|
+
INFOPLIST_FILE: 'Info.plist',
|
|
143
|
+
MARKETING_VERSION: version,
|
|
144
|
+
CURRENT_PROJECT_VERSION: version,
|
|
145
|
+
LD_RUNPATH_SEARCH_PATHS: ['$(inherited)', '@executable_path/Frameworks'],
|
|
146
|
+
...linkFlags,
|
|
147
|
+
};
|
|
148
|
+
fs.writeFileSync(`${to}/${name}.xcodeproj/project.pbxproj`, textPlist({
|
|
149
|
+
archiveVersion: 1, classes: {}, objectVersion: 56, objects: objects(name, targetSettings), rootObject: ID.project,
|
|
150
|
+
}));
|
|
151
|
+
return to;
|
|
152
|
+
}
|
|
@@ -2,9 +2,26 @@
|
|
|
2
2
|
#include <emscripten/wasmfs.h>
|
|
3
3
|
#include <limits.h>
|
|
4
4
|
#include <unistd.h>
|
|
5
|
+
#include <wasi/api.h>
|
|
6
|
+
|
|
7
|
+
static backend_t hostBackend;
|
|
5
8
|
|
|
6
9
|
backend_t wasmfs_create_root_dir(void) {
|
|
7
|
-
|
|
10
|
+
hostBackend = wasmfs_create_node_backend("/");
|
|
11
|
+
return hostBackend;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Emscripten's Node backend traps on flush, which fsync and fdatasync call. A host file's writes have already
|
|
15
|
+
// reached the operating system, so both succeed there, as with NODERAWFS. buildWasm links these with --wrap.
|
|
16
|
+
extern "C" __wasi_errno_t __real___wasi_fd_sync(__wasi_fd_t fd);
|
|
17
|
+
extern "C" int __real___syscall_fdatasync(int fd);
|
|
18
|
+
|
|
19
|
+
extern "C" __wasi_errno_t __wrap___wasi_fd_sync(__wasi_fd_t fd) {
|
|
20
|
+
return wasmfs_get_backend_by_fd(fd) == hostBackend ? __WASI_ERRNO_SUCCESS : __real___wasi_fd_sync(fd);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
extern "C" int __wrap___syscall_fdatasync(int fd) {
|
|
24
|
+
return wasmfs_get_backend_by_fd(fd) == hostBackend ? 0 : __real___syscall_fdatasync(fd);
|
|
8
25
|
}
|
|
9
26
|
|
|
10
27
|
void wasmfs_before_preload(void) {
|