crossbind 2.0.0-beta.74 → 2.0.0-beta.76
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/package.json +3 -3
- package/src/actions/androidGradle.js +47 -4
- package/src/actions/buildApp.js +4 -3
- package/src/actions/buildNode.js +2 -10
- package/src/actions/buildWasm.js +20 -1
- package/src/actions/createInterface.js +266 -38
- package/src/actions/createLib.js +4 -1
- package/src/actions/getCmakeParameters.js +0 -1
- package/src/actions/licenses.js +65 -19
- package/src/actions/nativeNotices.js +43 -0
- package/src/actions/run.js +72 -6
- package/src/actions/wasmNotices.js +32 -0
- package/src/app/index.js +1 -1
- package/src/assets/js-runtime/adapters/worker-comlink.js +11 -0
- package/src/assets/toolchain-digests.json +20 -20
- package/src/integration/getDependFilePath.js +8 -1
- package/src/state/loadConfig.js +1 -1
- package/src/utils/androidToolchain.js +4 -0
- package/src/utils/bridgeRuntimeFixes.js +24 -11
- package/src/utils/embindPointerOwners.js +42 -0
- package/src/utils/sourceFingerprint.js +5 -1
- package/src/utils/swigInterface.js +22 -7
- package/src/utils/toolchainNotices.js +20 -0
- package/src/utils/typeClosure.js +170 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crossbind",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.76",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://crossbind.dev",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"vitest": "^5.0.1"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@crossbind/extension-conan": "2.0.0-beta.
|
|
41
|
+
"@crossbind/extension-conan": "2.0.0-beta.76",
|
|
42
42
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
43
43
|
"@rollup/plugin-json": "^6.1.0",
|
|
44
44
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"upath": "^3.0.8"
|
|
56
56
|
},
|
|
57
57
|
"optionalDependencies": {
|
|
58
|
-
"@crossbind/bridge-tools": "2.0.0-beta.
|
|
58
|
+
"@crossbind/bridge-tools": "2.0.0-beta.76"
|
|
59
59
|
},
|
|
60
60
|
"keywords": [
|
|
61
61
|
"webassembly",
|
|
@@ -119,9 +119,15 @@ export function packageAndroidRelease(project) {
|
|
|
119
119
|
const key = machineUploadKey();
|
|
120
120
|
const env = key ? { ...process.env, ...uploadKeyEnv(key) } : process.env;
|
|
121
121
|
const signing = releaseSigning(env);
|
|
122
|
+
const workdir = inContainer(project);
|
|
122
123
|
pullDockerImage(ROLE, PLATFORM);
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
removeOrphanedDevContainers();
|
|
125
|
+
try {
|
|
126
|
+
docker(['run', '--rm', ...containerArgs(signing, isOnline(project)), '-w', workdir, getDockerImage(ROLE, PLATFORM),
|
|
127
|
+
...gradleOf(project), '--no-daemon', 'assembleRelease', ...(signing ? ['bundleRelease'] : [])], `packaging ${path.basename(project)}`, env);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw withCacheHolders(error);
|
|
130
|
+
}
|
|
125
131
|
const outputs = `${project}/app/build/outputs`;
|
|
126
132
|
fs.copyFileSync(`${outputs}/apk/release/app-release.apk`, `${project}.apk`);
|
|
127
133
|
fs.rmSync(`${project}.aab`, { force: true });
|
|
@@ -138,6 +144,42 @@ export function packageAndroidRelease(project) {
|
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
|
|
147
|
+
// Every Android build on this machine shares Gradle's caches, which a development container's daemon holds until the
|
|
148
|
+
// container goes: Gradle cannot hand them to another container. A development container carries the PID of the session it
|
|
149
|
+
// serves, so that one a session left behind without its exit hooks, as a crash or a SIGKILL leaves, goes before the next
|
|
150
|
+
// build instead of stopping it.
|
|
151
|
+
const SESSION_LABEL = 'dev.crossbind.android-app.session';
|
|
152
|
+
|
|
153
|
+
const isRunning = (pid) => {
|
|
154
|
+
try {
|
|
155
|
+
process.kill(pid, 0);
|
|
156
|
+
return true;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return error.code === 'EPERM';
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
function removeOrphanedDevContainers() {
|
|
163
|
+
const ps = spawnSync('docker', ['ps', '--all', '--filter', `label=${SESSION_LABEL}`, '--format', `{{.Names}} {{.Label "${SESSION_LABEL}"}}`], { encoding: 'utf8' });
|
|
164
|
+
if (ps.status !== 0) return;
|
|
165
|
+
for (const line of ps.stdout.split('\n')) {
|
|
166
|
+
const [name, pid] = line.trim().split(' ');
|
|
167
|
+
if (!name || !/^\d+$/.test(pid ?? '') || isRunning(Number(pid))) continue;
|
|
168
|
+
console.warn(`crossbind: removing ${name}, the Android development container of a session that has ended.`);
|
|
169
|
+
spawnSync('docker', ['rm', '--force', name], { stdio: 'ignore' });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// What holds Gradle's caches when a release cannot take them: the containers of development sessions that run, and those of
|
|
174
|
+
// a crossbind before the label, which name no session.
|
|
175
|
+
function withCacheHolders(error) {
|
|
176
|
+
if (!error.message.includes('Timeout waiting to lock')) return error;
|
|
177
|
+
const ps = spawnSync('docker', ['ps', '--filter', 'name=crossbind-android-app-', '--format', '{{.Names}}'], { encoding: 'utf8' });
|
|
178
|
+
const names = ps.status === 0 ? ps.stdout.split('\n').map((name) => name.trim()).filter(Boolean) : [];
|
|
179
|
+
if (names.length === 0) return error;
|
|
180
|
+
return new Error(`${error.message}\ncrossbind: Gradle's caches are held by ${names.join(', ')}, the container ${names.length > 1 ? 'each ' : ''}of a \`crossbind app dev\`: end that session, or run \`docker rm --force ${names.join(' ')}\` if none is running.`, { cause: error });
|
|
181
|
+
}
|
|
182
|
+
|
|
141
183
|
// During development one container per project, whose Gradle daemon stays warm from one build to the next; it goes with
|
|
142
184
|
// the process, and comes anew when the project comes to need the network, or no longer does.
|
|
143
185
|
let devContainer = null;
|
|
@@ -145,10 +187,11 @@ let devContainer = null;
|
|
|
145
187
|
function startDevContainer(isNetworked) {
|
|
146
188
|
const name = `crossbind-android-app-${getContentHash(state.config.paths.project).slice(0, 12)}`;
|
|
147
189
|
pullDockerImage(ROLE, PLATFORM);
|
|
190
|
+
removeOrphanedDevContainers();
|
|
148
191
|
// One a crashed session, or the project's need of the network before, left behind holds the name.
|
|
149
192
|
spawnSync('docker', ['rm', '--force', name], { stdio: 'ignore' });
|
|
150
|
-
docker(['run', '--detach', '--rm', '--init', '--name', name, ...containerArgs(null, isNetworked),
|
|
151
|
-
'starting the development container');
|
|
193
|
+
docker(['run', '--detach', '--rm', '--init', '--name', name, '--label', `${SESSION_LABEL}=${process.pid}`, ...containerArgs(null, isNetworked),
|
|
194
|
+
getDockerImage(ROLE, PLATFORM), 'sleep', 'infinity'], 'starting the development container');
|
|
152
195
|
if (!devContainer) {
|
|
153
196
|
process.once('exit', () => spawnSync('docker', ['rm', '--force', name], { stdio: 'ignore' }));
|
|
154
197
|
// A closed terminal ends the session through its exit hooks, as Ctrl-C does.
|
package/src/actions/buildApp.js
CHANGED
|
@@ -10,7 +10,8 @@ import run from './run.js';
|
|
|
10
10
|
import createLib from './createLib.js';
|
|
11
11
|
import getData from './getData.js';
|
|
12
12
|
import getEnv from './getEnv.js';
|
|
13
|
-
import {
|
|
13
|
+
import { filesUnder, linkInputs } from './buildNode.js';
|
|
14
|
+
import copyNativeNotices from './nativeNotices.js';
|
|
14
15
|
import { suppressNodeBuiltinWarnings } from './buildJs.js';
|
|
15
16
|
import { isNativeSourceNewerThan } from './isSourceNewer.js';
|
|
16
17
|
import { createNativeBridges } from './nativeBridges.js';
|
|
@@ -180,7 +181,7 @@ async function hostInputs(target) {
|
|
|
180
181
|
assertAppRunner(target);
|
|
181
182
|
const inputs = [getDockerImage(imageRoleFor(target), target.platform === 'android' ? 'linux/amd64' : undefined)];
|
|
182
183
|
if (target.platform === 'android') {
|
|
183
|
-
return { hermes: `${IMAGE_HERMES}/android`, cmakeArgs: [
|
|
184
|
+
return { hermes: `${IMAGE_HERMES}/android`, cmakeArgs: [], inputs };
|
|
184
185
|
}
|
|
185
186
|
if (target.platform === 'win32') {
|
|
186
187
|
return { hermes: `${IMAGE_HERMES}/win32-${target.arch}`, cmakeArgs: ['-DCROSSBIND_WEBVIEW2_INCLUDE=/opt/crossbind/webview2/include'], inputs };
|
|
@@ -667,7 +668,7 @@ export default async function buildApp(target, { bridges = [], force = false } =
|
|
|
667
668
|
...cmakeArgs,
|
|
668
669
|
], platformPrefix, target);
|
|
669
670
|
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target, { exclusive: true });
|
|
670
|
-
|
|
671
|
+
copyNativeNotices(target);
|
|
671
672
|
|
|
672
673
|
const systemConfig = `export default ${JSON.stringify({ env: getEnv(target) })};`;
|
|
673
674
|
await bundle(`${appHostRoot}/js/host.js`, `${workDir}/host.js`, [
|
package/src/actions/buildNode.js
CHANGED
|
@@ -19,10 +19,9 @@ import resolveEmbindNapiRoot, { resolveEmbindJsiRoot } from '../utils/resolveEmb
|
|
|
19
19
|
import scopedEmbind from '../utils/scopedEmbind.js';
|
|
20
20
|
import resolveEmbindRustRoot from '../utils/resolveEmbindRust.js';
|
|
21
21
|
import { rustStdLibsFor } from '../utils/cargoTarget.js';
|
|
22
|
-
import
|
|
22
|
+
import copyNativeNotices from './nativeNotices.js';
|
|
23
23
|
|
|
24
24
|
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
25
|
-
const WINDOWS_IMAGE_NOTICES = '/opt/licenses/llvm-mingw';
|
|
26
25
|
|
|
27
26
|
// What a build's output, a package's own packages and a repository's history hold, which no build reads.
|
|
28
27
|
const UNREAD_FOLDERS = ['.crossbind', 'node_modules', '.git'];
|
|
@@ -124,13 +123,6 @@ export function bundleNodeLoader(target) {
|
|
|
124
123
|
return bundleLoader(target, resolveEmbindNapiRoot(), nodeLoaderConfig(target));
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
// The mingw-w64 runtime and winpthreads in a Windows addon or app ask for their notices in binary
|
|
128
|
-
// distributions. The windows image carries them; a host toolchain may not.
|
|
129
|
-
export function copyWindowsNotices(target) {
|
|
130
|
-
const notices = toolchainNoticesDir('win32');
|
|
131
|
-
run(null, ['sh', '-c', `if [ -d ${WINDOWS_IMAGE_NOTICES} ]; then mkdir -p ${notices} && cp ${WINDOWS_IMAGE_NOTICES}/* ${notices}/; fi`], null, target);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
126
|
// Links one Node-API addon from the project's archives and bundles the loader that picks the
|
|
135
127
|
// addon matching the running process.
|
|
136
128
|
export default async function buildNode(target, options = {}) {
|
|
@@ -185,7 +177,7 @@ export default async function buildNode(target, options = {}) {
|
|
|
185
177
|
`-DCROSSBIND_LINK_DEPENDS=${libs.join(';')}`,
|
|
186
178
|
], platformPrefix, target);
|
|
187
179
|
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target, { exclusive: true });
|
|
188
|
-
|
|
180
|
+
copyNativeNotices(target);
|
|
189
181
|
fs.copyFileSync(`${build}/${platformPrefix}/${target.path}/${target.addonName}`, `${build}/${target.addonName}`);
|
|
190
182
|
logger.doneStep(target, 'addon');
|
|
191
183
|
|
package/src/actions/buildWasm.js
CHANGED
|
@@ -6,6 +6,7 @@ import getLinkInputs from './getLinkInputs.js';
|
|
|
6
6
|
import getData from './getData.js';
|
|
7
7
|
import getEnv from './getEnv.js';
|
|
8
8
|
import buildJs from './buildJs.js';
|
|
9
|
+
import copyWasmNotices, { hasWasmNotices } from './wasmNotices.js';
|
|
9
10
|
import state from '../state/index.js';
|
|
10
11
|
import resolveEmbindRustRoot from '../utils/resolveEmbindRust.js';
|
|
11
12
|
import logger from '../utils/logger.js';
|
|
@@ -15,9 +16,10 @@ import { guardBigIntArguments, guardEmbindArguments } from '../utils/embindArgum
|
|
|
15
16
|
import { separateCallArguments } from '../utils/embindCallArguments.js';
|
|
16
17
|
import { queueJspiCalls } from '../utils/embindJspiQueue.js';
|
|
17
18
|
import { releaseOwnedHandles } from '../utils/embindObjectLifetime.js';
|
|
19
|
+
import { keepPointerOwners } from '../utils/embindPointerOwners.js';
|
|
18
20
|
import { spawnPthreadsFromMainScript } from '../utils/pthreadMainScript.js';
|
|
19
21
|
|
|
20
|
-
const GLUE_REWRITES = ['../utils/embindArgumentGuards.js', '../utils/embindCallArguments.js', '../utils/embindJspiQueue.js', '../utils/embindObjectLifetime.js', '../utils/pthreadMainScript.js']
|
|
22
|
+
const GLUE_REWRITES = ['../utils/embindArgumentGuards.js', '../utils/embindCallArguments.js', '../utils/embindJspiQueue.js', '../utils/embindObjectLifetime.js', '../utils/embindPointerOwners.js', '../utils/pthreadMainScript.js']
|
|
21
23
|
.map((file) => fileURLToPath(new URL(file, import.meta.url)));
|
|
22
24
|
|
|
23
25
|
// embind's bigint converter turns any Number into a BigInt, so 2^53+1 silently becomes 2^53.
|
|
@@ -76,6 +78,17 @@ function releaseOwnedObjects(target) {
|
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
// A pointer a member hands out outlived the object it points into once JavaScript freed that object
|
|
82
|
+
// (utils/embindPointerOwners.js); each runtimeEnv links its own glue, so each one calls this after its link.
|
|
83
|
+
function keepPointerOwnersAlive(target) {
|
|
84
|
+
const gluePath = `${state.config.paths.build}/${target.rawJsName}`;
|
|
85
|
+
const { text, missed } = keepPointerOwners(fs.readFileSync(gluePath, 'utf8'));
|
|
86
|
+
fs.writeFileSync(gluePath, text);
|
|
87
|
+
if (missed) {
|
|
88
|
+
logger.error('embind pointer owner rewrite missed (emscripten glue format changed?): a pointer a method returns can outlive the object it points into');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
79
92
|
// A direct-mode mt init in a browser spawns its pthreads from "/undefined" (utils/pthreadMainScript.js).
|
|
80
93
|
function pointPthreadSpawn(target) {
|
|
81
94
|
const gluePath = `${state.config.paths.build}/${target.rawJsName}`;
|
|
@@ -192,9 +205,12 @@ export default async function buildWasm(target, options = {}) {
|
|
|
192
205
|
|| fs.readFileSync(linkFingerprintFile, { encoding: 'utf8' }) !== linkFingerprint;
|
|
193
206
|
|
|
194
207
|
if (!options.force && !linkChanged && fs.existsSync(`${state.config.paths.build}/${target.jsName}`) && fs.existsSync(`${state.config.paths.build}/${target.wasmName}`)) {
|
|
208
|
+
// A build made before crossbind kept the toolchain's notices has none.
|
|
209
|
+
if (!hasWasmNotices()) copyWasmNotices(target);
|
|
195
210
|
logger.cachedStep(target, 'wasm+js');
|
|
196
211
|
return false;
|
|
197
212
|
}
|
|
213
|
+
copyWasmNotices(target);
|
|
198
214
|
|
|
199
215
|
if (target.runtimeEnv === 'browser') {
|
|
200
216
|
logger.startStep(target, 'wasm');
|
|
@@ -244,6 +260,7 @@ export default async function buildWasm(target, options = {}) {
|
|
|
244
260
|
separateEmbindCalls(target);
|
|
245
261
|
queueJspiEmbindCalls(target);
|
|
246
262
|
releaseOwnedObjects(target);
|
|
263
|
+
keepPointerOwnersAlive(target);
|
|
247
264
|
await buildJs(target);
|
|
248
265
|
// fs.rmSync(`${state.config.paths.build}/${state.config.general.name}.js`);
|
|
249
266
|
// fs.copyFileSync(`${state.config.paths.build}/${state.config.general.name}.browser.js`, `${state.config.paths.build}/${state.config.general.name}.js`);
|
|
@@ -286,6 +303,7 @@ export default async function buildWasm(target, options = {}) {
|
|
|
286
303
|
separateEmbindCalls(target);
|
|
287
304
|
queueJspiEmbindCalls(target);
|
|
288
305
|
releaseOwnedObjects(target);
|
|
306
|
+
keepPointerOwnersAlive(target);
|
|
289
307
|
await buildJs(target);
|
|
290
308
|
logger.doneStep(target, 'js');
|
|
291
309
|
}
|
|
@@ -323,6 +341,7 @@ export default async function buildWasm(target, options = {}) {
|
|
|
323
341
|
separateEmbindCalls(target);
|
|
324
342
|
queueJspiEmbindCalls(target);
|
|
325
343
|
releaseOwnedObjects(target);
|
|
344
|
+
keepPointerOwnersAlive(target);
|
|
326
345
|
await buildJs(target);
|
|
327
346
|
if (emccFlags.includes('FETCH')) {
|
|
328
347
|
fs.appendFileSync(`${state.config.paths.build}/${target.jsName}`, 'var XMLHttpRequest = require(\'xhr2\');\n');
|