crossbind 2.0.0-beta.67 → 2.0.0-beta.69
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/README.md +33 -24
- package/package.json +69 -59
- package/src/actions/addExtension.js +191 -0
- package/src/actions/androidGradle.js +69 -25
- package/src/actions/appInstaller.js +69 -153
- package/src/actions/appSwift.js +66 -0
- package/src/actions/appleSigning.js +32 -14
- package/src/actions/buildApp.js +229 -67
- package/src/actions/buildNode.js +6 -1
- package/src/actions/buildWasm.js +0 -9
- package/src/actions/createInterface.js +17 -8
- package/src/actions/createLib.js +5 -7
- package/src/actions/extensionApi.js +298 -0
- package/src/actions/extensionCommands.js +41 -0
- package/src/actions/extensionsInApp.js +19 -0
- package/src/actions/getCmakeParameters.js +2 -3
- package/src/actions/gradleProject.js +199 -16
- package/src/actions/iosDevice.js +200 -0
- package/src/actions/iosSimulator.js +3 -3
- package/src/actions/licenses.js +83 -34
- package/src/actions/linuxPackages.js +48 -0
- package/src/actions/mavenVerification.js +37 -0
- package/src/actions/publish.js +220 -0
- package/src/actions/uploadKey.js +134 -0
- package/src/actions/windowsSigning.js +1 -1
- package/src/actions/xcodeProject.js +63 -17
- package/src/actions/xcodeTargets.js +122 -0
- package/src/actions/xcodebuild.js +65 -0
- package/src/app/index.d.ts +126 -133
- package/src/app/index.js +126 -188
- package/src/assets/configure/install.sh +0 -0
- package/src/assets/toolchain-digests.json +20 -20
- package/src/bin.js +79 -26
- package/src/index.js +5 -1
- package/src/program.js +35 -4
- package/src/state/index.js +2 -1
- package/src/state/loadConfig.js +16 -5
- package/src/utils/appApiHeader.js +4 -0
- package/src/utils/appExtensions.js +761 -0
- package/src/utils/appFiles.js +58 -0
- package/src/utils/appLaunches.js +249 -0
- package/src/utils/appOptions.js +144 -95
- package/src/utils/appTargets.js +168 -0
- package/src/utils/ask.js +38 -0
- package/src/utils/bridgeTools.js +66 -0
- package/src/utils/configChanges.js +30 -0
- package/src/utils/configWriter.js +181 -0
- package/src/utils/extensionSteps.js +135 -0
- package/src/utils/hermes.js +24 -0
- package/src/utils/keychain.js +54 -0
- package/src/utils/openUrl.js +12 -0
- package/src/utils/password.js +34 -0
- package/src/utils/plist.js +5 -0
- package/src/utils/requiredReasonApis.js +77 -0
- package/src/utils/resolveAppHost.js +7 -0
- package/src/utils/secretFile.js +23 -0
- package/src/utils/systemConfig.js +19 -0
- package/src/utils/systemKeys.js +16 -0
- package/vitest.config.js +6 -1
- package/src/actions/appUpdate.js +0 -68
- package/src/actions/extensions.js +0 -7
package/src/actions/buildApp.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { spawnSync } from 'node:child_process';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
6
5
|
import { rollup } from 'rollup';
|
|
7
6
|
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
@@ -20,8 +19,8 @@ import writeXcodeProject, { HERMES_SLICES, IOS_SDKS } from './xcodeProject.js';
|
|
|
20
19
|
import writeGradleProject, { ANDROID_ABIS } from './gradleProject.js';
|
|
21
20
|
import { attachedDevice } from './androidDevice.js';
|
|
22
21
|
import { packageAndroidRelease } from './androidGradle.js';
|
|
23
|
-
import
|
|
24
|
-
import
|
|
22
|
+
import { compileSwift, swiftLinkArgs } from './appSwift.js';
|
|
23
|
+
import { unpackPackages } from './linuxPackages.js';
|
|
25
24
|
import { APPLE_SIGNING, appleSigning, notarizeApp } from './appleSigning.js';
|
|
26
25
|
import { WINDOWS_SIGNING, signWindows, windowsSigning } from './windowsSigning.js';
|
|
27
26
|
import state from '../state/index.js';
|
|
@@ -30,8 +29,15 @@ import findFiles from '../utils/findFiles.js';
|
|
|
30
29
|
import { getContentHash, getFilesFingerprint } from '../utils/hash.js';
|
|
31
30
|
import { xmlPlist } from '../utils/plist.js';
|
|
32
31
|
import {
|
|
33
|
-
appBackgroundColor, appCategory, appCopyright, appDisplayName, appHeader, appId, appPermissions,
|
|
32
|
+
androidAppId, appBackgroundColor, appCategory, appCopyright, appDisplayName, appEncryption, appFiles, appHeader, appId, appPermissions, appPrivacy,
|
|
33
|
+
appUrlSchemes, iosAppGroup, iosMultipleWindows, urlSchemeOwners, usageDescriptions,
|
|
34
34
|
} from '../utils/appOptions.js';
|
|
35
|
+
import { carriedFilesOf, copyCarriedFiles } from '../utils/appFiles.js';
|
|
36
|
+
import { activityTypeOwners, fileTypeOwners, linkOwners, macDocumentTypes, shortcutItemOwners } from '../utils/appLaunches.js';
|
|
37
|
+
import { unreasonedApis, unreasonedWarning } from '../utils/requiredReasonApis.js';
|
|
38
|
+
import {
|
|
39
|
+
appExtensionsOf, extensionCmakeArgs, extensionPart, mergedEntitlements, mergedPlist, mergedPrivacy, registrationSource,
|
|
40
|
+
} from '../utils/appExtensions.js';
|
|
35
41
|
import { androidIcons, iosIcon, linuxIcon, macIcon, readAppIcon, windowsIcon } from '../utils/appIcon.js';
|
|
36
42
|
import writeIfChanged from '../utils/writeIfChanged.js';
|
|
37
43
|
import { writeNativeSourceStamp } from '../utils/nativeSourceStamp.js';
|
|
@@ -39,8 +45,10 @@ import resetCMakeCacheForSource from '../utils/resetCMakeCacheForSource.js';
|
|
|
39
45
|
import resolveAppHostRoot, { resolveAppEmbindJsiRoot } from '../utils/resolveAppHost.js';
|
|
40
46
|
import resolveHermes, { HERMES } from '../utils/hermes.js';
|
|
41
47
|
import { getDockerImage, imageRoleFor } from '../utils/pullDockerImage.js';
|
|
48
|
+
import { isUnderBase } from '../utils/replaceBasePathForDocker.js';
|
|
42
49
|
import { chosenRunner } from '../utils/selectRunner.js';
|
|
43
50
|
import { DARWIN_DEPLOYMENT_TARGET } from '../utils/darwinToolchain.js';
|
|
51
|
+
import extensionsInApp from './extensionsInApp.js';
|
|
44
52
|
|
|
45
53
|
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
46
54
|
const BRIDGE = 'AppBridge';
|
|
@@ -48,6 +56,17 @@ const LINUX_MULTIARCH = { x64: 'x86_64-linux-gnu', arm64: 'aarch64-linux-gnu' };
|
|
|
48
56
|
// Where the android-app, linux and windows images keep the Hermes of the release crossbind pins.
|
|
49
57
|
const IMAGE_HERMES = `/opt/crossbind/hermes/${HERMES.version}`;
|
|
50
58
|
|
|
59
|
+
// What the app's extensions add to an app of this platform.
|
|
60
|
+
const extensionsOf = (platform) => extensionPart(extensionsInApp(platform, (message) => console.warn(`crossbind: ${message}.`)), platform);
|
|
61
|
+
|
|
62
|
+
// The App Group an iOS app shares with the targets of its extensions' that ask for one, or none; app.ios.appGroup is
|
|
63
|
+
// checked all the same.
|
|
64
|
+
function appGroupOf(platform, extensions) {
|
|
65
|
+
if (platform !== 'ios') return null;
|
|
66
|
+
const group = iosAppGroup(state.config);
|
|
67
|
+
return extensions.targets.some(({ appGroup }) => appGroup) ? group : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
// Where an app keeps its executable and its files: a macOS bundle, a Linux or Windows directory, or the app folder of an
|
|
52
71
|
// iOS app's Xcode project or of an Android app's Gradle project, which link the executable themselves.
|
|
53
72
|
export function appLayout(target) {
|
|
@@ -78,7 +97,8 @@ export function assertAppRunner(target) {
|
|
|
78
97
|
|
|
79
98
|
// The app a bundler builds: this machine's desktop app, or with CROSSBIND_APP_PLATFORM an iOS app for both SDKs, an
|
|
80
99
|
// Android app for both ABIs, or a Linux or Windows app for both arches, which builds in Docker on any machine. During
|
|
81
|
-
// development only what runs it: the Simulator, the
|
|
100
|
+
// development only what runs it: the Simulator, or the iPhone or iPad CROSSBIND_APP_DEVICE names, the attached Android
|
|
101
|
+
// device, or this machine.
|
|
82
102
|
export function appTargetParams({ dev = false } = {}, env = process.env) {
|
|
83
103
|
const platform = env.CROSSBIND_APP_PLATFORM;
|
|
84
104
|
const app = (platforms, archs) => {
|
|
@@ -107,11 +127,12 @@ export function appTargetParams({ dev = false } = {}, env = process.env) {
|
|
|
107
127
|
throw new Error(`crossbind: CROSSBIND_APP_PLATFORM=${platform} names no platform an app builds for. Set it to android, ios, linux, macos or windows, or leave it unset for this machine's desktop app.`);
|
|
108
128
|
}
|
|
109
129
|
if (process.platform !== 'darwin') throw new Error('crossbind: iOS apps build on macOS, with Xcode.');
|
|
110
|
-
|
|
130
|
+
const devSdk = env.CROSSBIND_APP_DEVICE ? 'iphoneos' : 'iphonesimulator';
|
|
131
|
+
return getTargetParams({ platform: ['ios'], arch: dev ? [devSdk] : IOS_SDKS, runtime: ['mt'], runtimeEnv: ['app'] }, true);
|
|
111
132
|
}
|
|
112
133
|
|
|
113
134
|
// The Apple team that signs an iOS app: the project's app.team, else the machine's XCODE_DEVELOPMENT_TEAM, else none.
|
|
114
|
-
function appTeam() {
|
|
135
|
+
export function appTeam() {
|
|
115
136
|
const team = state.config.app.team ?? state.config.system.XCODE_DEVELOPMENT_TEAM ?? '';
|
|
116
137
|
if (team && !/^[A-Z0-9]{10}$/.test(team)) {
|
|
117
138
|
throw new Error(`crossbind: ${JSON.stringify(team)} (app.team or XCODE_DEVELOPMENT_TEAM) is no Apple team id, which is ten capital letters and digits.`);
|
|
@@ -119,22 +140,29 @@ function appTeam() {
|
|
|
119
140
|
return team;
|
|
120
141
|
}
|
|
121
142
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
143
|
+
// Apple takes dot-separated integers alone as an app's version.
|
|
144
|
+
export const appVersion = () => (state.config.package?.version ?? '1.0.0').replace(/[-+].*$/, '');
|
|
145
|
+
|
|
146
|
+
const BUILD_NUMBER = 'CROSSBIND_APP_BUILD_NUMBER';
|
|
147
|
+
// Google Play takes no larger version code.
|
|
148
|
+
export const MAX_VERSION_CODE = 2100000000;
|
|
149
|
+
|
|
150
|
+
// The number CI or a publish gives a build, which iOS takes as its build and Android as its version code:
|
|
151
|
+
// CROSSBIND_APP_BUILD_NUMBER, a whole number both stores take, else none.
|
|
152
|
+
export function appBuildNumber(env = process.env) {
|
|
153
|
+
const value = env[BUILD_NUMBER];
|
|
154
|
+
if (!value) return null;
|
|
155
|
+
if (!/^[1-9]\d*$/.test(value) || Number(value) > MAX_VERSION_CODE) {
|
|
156
|
+
throw new Error(`crossbind: ${BUILD_NUMBER} ${JSON.stringify(value)} is no build number, which is a whole number from 1 to ${MAX_VERSION_CODE}, as both stores take.`);
|
|
127
157
|
}
|
|
128
|
-
return
|
|
158
|
+
return Number(value);
|
|
129
159
|
}
|
|
130
160
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Android takes any name for an app's version, and a positive integer that grows from release to release as its code.
|
|
135
|
-
function androidVersion() {
|
|
161
|
+
// Android takes any name for an app's version, and a positive integer that grows from release to release as its code:
|
|
162
|
+
// the build number, else one made of the version's numbers.
|
|
163
|
+
export function androidVersion(env = process.env) {
|
|
136
164
|
const [major = 0, minor = 0, patch = 0] = appVersion().split('.').map(Number);
|
|
137
|
-
return { name: state.config.package?.version ?? '1.0.0', code: Math.max(1, major * 1000000 + minor * 1000 + patch) };
|
|
165
|
+
return { name: state.config.package?.version ?? '1.0.0', code: appBuildNumber(env) ?? Math.max(1, major * 1000000 + minor * 1000 + patch) };
|
|
138
166
|
}
|
|
139
167
|
|
|
140
168
|
// The apps this run built or found current. A part of an iOS app that an earlier run built may be stale.
|
|
@@ -181,11 +209,16 @@ async function bundle(input, file, plugins) {
|
|
|
181
209
|
await build.close();
|
|
182
210
|
}
|
|
183
211
|
|
|
184
|
-
// A development build claims no scheme: the built app, of the same id, owns them, and
|
|
212
|
+
// A development build claims no scheme, no file type and no user activity: the built app, of the same id, owns them, and
|
|
213
|
+
// a link it opens shows its own page.
|
|
185
214
|
const urlSchemesOf = (target) => (target.buildType === 'release' ? appUrlSchemes(state.config) : []);
|
|
215
|
+
const fileTypesOf = (target) => (target.buildType === 'release' ? fileTypeOwners(state.config) : []);
|
|
216
|
+
const activityTypesOf = (target) => (target.buildType === 'release' ? activityTypeOwners(state.config).map(({ type }) => type) : []);
|
|
186
217
|
|
|
187
|
-
|
|
218
|
+
// The app's Info.plist, with the keys its extensions give.
|
|
219
|
+
const infoPlist = (name, version, hasIcon, urlSchemes, fileTypes, activityTypes) => xmlPlist(mergedPlist({
|
|
188
220
|
CFBundleDisplayName: appDisplayName(state.config),
|
|
221
|
+
...(fileTypes.length > 0 ? { CFBundleDocumentTypes: macDocumentTypes(fileTypes) } : {}),
|
|
189
222
|
CFBundleExecutable: name,
|
|
190
223
|
// Contents/Resources/icon.icns.
|
|
191
224
|
...(hasIcon ? { CFBundleIconFile: 'icon' } : {}),
|
|
@@ -201,8 +234,12 @@ const infoPlist = (name, version, hasIcon, urlSchemes) => xmlPlist({
|
|
|
201
234
|
NSHighResolutionCapable: true,
|
|
202
235
|
// The app menu's About item shows it.
|
|
203
236
|
...(appCopyright(state.config) ? { NSHumanReadableCopyright: appCopyright(state.config) } : {}),
|
|
237
|
+
...(activityTypes.length > 0 ? { NSUserActivityTypes: activityTypes } : {}),
|
|
204
238
|
...usageDescriptions(state.config, 'darwin'),
|
|
205
|
-
});
|
|
239
|
+
}, extensionsOf('darwin').plist));
|
|
240
|
+
|
|
241
|
+
// The app's own files, as app.files lists them, by the project's folder.
|
|
242
|
+
const ownFiles = () => ({ root: state.config.paths.project, paths: appFiles(state.config) });
|
|
206
243
|
|
|
207
244
|
// The host's bytecode and the data the packages ship, beside the page's folder.
|
|
208
245
|
function copyAppFiles(dir, hbc, target) {
|
|
@@ -211,6 +248,7 @@ function copyAppFiles(dir, hbc, target) {
|
|
|
211
248
|
Object.entries(getData('data', target)).forEach(([source, dataName]) => {
|
|
212
249
|
if (fs.existsSync(source)) fs.cpSync(source, `${dir}/data/${dataName}`, { recursive: true });
|
|
213
250
|
});
|
|
251
|
+
copyCarriedFiles(dir, extensionsOf(target.platform).files, ownFiles());
|
|
214
252
|
}
|
|
215
253
|
|
|
216
254
|
// Everything but the page: the host and the app's files, its icon, and on macOS Hermes beside them. A Windows host
|
|
@@ -225,7 +263,7 @@ function assembleApp(target, { executable, hbc, hermes, icon }) {
|
|
|
225
263
|
copyAppFiles(`${app}/${layout.resources}`, hbc, target);
|
|
226
264
|
if (target.platform === 'linux' && icon) fs.writeFileSync(`${app}/${layout.resources}/icon.png`, linuxIcon(icon));
|
|
227
265
|
if (target.platform !== 'darwin') return;
|
|
228
|
-
fs.writeFileSync(`${app}/Contents/Info.plist`, infoPlist(name, appVersion(), Boolean(icon), urlSchemesOf(target)));
|
|
266
|
+
fs.writeFileSync(`${app}/Contents/Info.plist`, infoPlist(name, appVersion(), Boolean(icon), urlSchemesOf(target), fileTypesOf(target), activityTypesOf(target)));
|
|
229
267
|
if (icon) fs.writeFileSync(`${app}/Contents/Resources/icon.icns`, macIcon(icon));
|
|
230
268
|
const framework = `${app}/Contents/Frameworks/hermesvm.framework`;
|
|
231
269
|
fs.cpSync(`${hermes}/Library/Frameworks/macosx/hermesvm.framework`, framework, { recursive: true, verbatimSymlinks: true });
|
|
@@ -260,13 +298,16 @@ function assembleIosPart(target, { library, linkArgs, hbc, hermes }) {
|
|
|
260
298
|
}
|
|
261
299
|
|
|
262
300
|
// What an Android ABI target brings to the app's Gradle project: the host, the libraries beside it and the packages'
|
|
263
|
-
// shared libraries it links, and the app's files.
|
|
301
|
+
// shared libraries it links, and the app's files. A release's host build left the packages' libraries stripped beside
|
|
302
|
+
// the host, and every library as linked in symbols/.
|
|
264
303
|
function assembleAndroidPart(target, { libDir, sharedLibs, hbc }) {
|
|
265
304
|
const part = `${state.config.paths.build}/${target.appName}`;
|
|
266
305
|
fs.rmSync(part, { recursive: true, force: true });
|
|
267
306
|
fs.mkdirSync(`${part}/lib`, { recursive: true });
|
|
268
|
-
|
|
307
|
+
const shipped = sharedLibs.map((file) => (fs.existsSync(`${libDir}/${path.basename(file)}`) ? `${libDir}/${path.basename(file)}` : file));
|
|
308
|
+
[...['libcrossbind_app.so', 'libjsi.so', 'libc++_shared.so'].map((file) => `${libDir}/${file}`), ...shipped]
|
|
269
309
|
.forEach((file) => fs.copyFileSync(file, `${part}/lib/${path.basename(file)}`));
|
|
310
|
+
if (fs.existsSync(`${libDir}/symbols`)) fs.cpSync(`${libDir}/symbols`, `${part}/symbols`, { recursive: true });
|
|
270
311
|
copyAppFiles(`${part}/app`, hbc, target);
|
|
271
312
|
}
|
|
272
313
|
|
|
@@ -275,20 +316,10 @@ const appParts = (target) => state.targets
|
|
|
275
316
|
.filter((t) => t.platform === target.platform && t.runtimeEnv === 'app' && t.buildType === target.buildType && currentApps.has(t.appName))
|
|
276
317
|
.map((t) => ({ arch: t.arch, dir: `${state.config.paths.build}/${t.appName}` }));
|
|
277
318
|
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
assertUnderBase(app, target);
|
|
283
|
-
fs.rmSync(`${app}.zip`, { force: true });
|
|
284
|
-
run(null, ['zip', '-q', '-r', '-X', `${path.basename(app)}.zip`, path.basename(app)], null, target, { workdir: path.dirname(app) });
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const result = spawnSync('tar', ['-czf', `${app}.tar.gz`, '-C', path.dirname(app), path.basename(app)], {
|
|
288
|
-
encoding: 'utf8',
|
|
289
|
-
env: { ...process.env, COPYFILE_DISABLE: '1' },
|
|
290
|
-
});
|
|
291
|
-
if (result.status !== 0) throw new Error(`crossbind: cannot archive ${app}: ${result.stderr || result.error?.message}`);
|
|
319
|
+
// The container that signs a Windows app sees the project's base alone.
|
|
320
|
+
function assertUnderBase(app, target) {
|
|
321
|
+
if (isUnderBase(app, state.config.paths.base)) return;
|
|
322
|
+
throw new Error(`crossbind: the Windows app ${app} is outside ${state.config.paths.base}, the project's base and all the ${imageRoleFor(target)} container sees. Build the app into a directory under it.`);
|
|
292
323
|
}
|
|
293
324
|
|
|
294
325
|
const ENTITLEMENTS = {
|
|
@@ -303,10 +334,12 @@ const ENTITLEMENTS = {
|
|
|
303
334
|
function signApp(app, target, signing) {
|
|
304
335
|
const sign = signing ? ['--sign', signing.identity, '--options', 'runtime', '--timestamp'] : ['--sign', '-', '--timestamp=none'];
|
|
305
336
|
run(null, ['codesign', '--force', ...sign, `${app}/Contents/Frameworks/hermesvm.framework`], null, target);
|
|
306
|
-
// The hardened runtime keeps the app from the devices of app.permissions unless its signature claims them
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
|
|
337
|
+
// The hardened runtime keeps the app from the devices of app.permissions unless its signature claims them, beside what
|
|
338
|
+
// its extensions claim.
|
|
339
|
+
const devices = Object.fromEntries(Object.keys(appPermissions(state.config)).map((name) => [ENTITLEMENTS[name], true]));
|
|
340
|
+
const claims = mergedEntitlements([['app.permissions', devices], ...extensionsOf('darwin').entitlements]);
|
|
341
|
+
const entitlements = Object.keys(claims).length > 0 ? `${state.config.paths.build}/${path.basename(app)}.entitlements` : null;
|
|
342
|
+
if (entitlements) fs.writeFileSync(entitlements, xmlPlist(claims));
|
|
310
343
|
run(null, ['codesign', '--force', ...sign, ...(entitlements ? ['--entitlements', entitlements] : []), app], null, target);
|
|
311
344
|
}
|
|
312
345
|
|
|
@@ -327,6 +360,64 @@ function shipMacApp(app, signing) {
|
|
|
327
360
|
else console.warn(`crossbind: ${path.basename(app)} is signed ad hoc, which opens it on this Mac alone. Set ${APPLE_SIGNING.identity} to sign it with your Developer ID, and ${notarize}.`);
|
|
328
361
|
}
|
|
329
362
|
|
|
363
|
+
// App Store Connect refuses an iOS app whose archives call required reason API without app.privacy's reasons; the build
|
|
364
|
+
// names them, from the archives a device gets.
|
|
365
|
+
function warnUnreasonedApis(project, privacy) {
|
|
366
|
+
const device = `${project}/lib/iphoneos`;
|
|
367
|
+
if (!fs.existsSync(device)) return;
|
|
368
|
+
const archives = fs.readdirSync(device).filter((file) => file.endsWith('.a')).map((file) => `${device}/${file}`);
|
|
369
|
+
const warning = unreasonedWarning(unreasonedApis(archives, privacy));
|
|
370
|
+
if (warning) console.warn(warning);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Where publishApp last put this project's apps, for `crossbind app publish`, which runs Vite and so cannot know the
|
|
374
|
+
// outDir it builds into: an iOS or Android app's project, and each arch's desktop app under its platform's name on the
|
|
375
|
+
// command line.
|
|
376
|
+
const appOutputsFile = () => `${state.config.paths.build}/app-outputs.json`;
|
|
377
|
+
const readAppOutputs = () => (fs.existsSync(appOutputsFile()) ? JSON.parse(fs.readFileSync(appOutputsFile(), 'utf8')) : {});
|
|
378
|
+
// What each desktop app's installers wrote beside it, as builds recorded them.
|
|
379
|
+
const shippedFiles = () => new Set(Object.values(readAppOutputs()).filter(Array.isArray).flat()
|
|
380
|
+
.flatMap(({ installers = [] }) => installers.flatMap(({ files }) => files)));
|
|
381
|
+
// The command line's names of the desktop platforms.
|
|
382
|
+
export const DESKTOP_PLATFORMS = { darwin: 'macos', linux: 'linux', win32: 'windows' };
|
|
383
|
+
|
|
384
|
+
// Forgets what the builds wrote, as a publish's own build writes it anew, but for the installers' files, which a folder the
|
|
385
|
+
// bundler kept holds still, for the page to leave out.
|
|
386
|
+
export function clearAppOutputs() {
|
|
387
|
+
const earlier = [...shippedFiles()];
|
|
388
|
+
if (earlier.length > 0) fs.writeFileSync(appOutputsFile(), JSON.stringify({ earlier: [{ installers: [{ files: earlier }] }] }));
|
|
389
|
+
else fs.rmSync(appOutputsFile(), { force: true });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// What a publisher's upload gets of the app the build wrote for a platform: a desktop platform's apps, one for each arch.
|
|
393
|
+
export function appOutputOf(platform) {
|
|
394
|
+
const output = readAppOutputs()[platform];
|
|
395
|
+
if (!output) throw new Error(`crossbind: the build wrote no ${platform} app, so there is nothing to publish.`);
|
|
396
|
+
return Object.values(DESKTOP_PLATFORMS).includes(platform) ? { apps: output } : output;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const madeFile = (file) => (fs.existsSync(file) ? file : null);
|
|
400
|
+
|
|
401
|
+
// What a publisher's upload takes of an app: an iOS app's project, an Android app's with its APK, bundle and native
|
|
402
|
+
// debug symbols where the build made them, and each arch's desktop app with its installers.
|
|
403
|
+
export function recordAppOutput(target, to, { installers = [] } = {}) {
|
|
404
|
+
const outputs = readAppOutputs();
|
|
405
|
+
const app = path.resolve(to);
|
|
406
|
+
const desktop = DESKTOP_PLATFORMS[target.platform];
|
|
407
|
+
const mobile = target.platform === 'android'
|
|
408
|
+
? { project: app, apk: madeFile(`${app}.apk`), bundle: madeFile(`${app}.aab`), symbols: madeFile(`${app}-symbols.zip`) }
|
|
409
|
+
: { project: app };
|
|
410
|
+
const recorded = desktop
|
|
411
|
+
? { [desktop]: [...(outputs[desktop] ?? []).filter((entry) => entry.app !== app), {
|
|
412
|
+
app,
|
|
413
|
+
arch: target.arch,
|
|
414
|
+
installers: installers.map(({ name, files }) => ({ name, files: files.map((file) => path.resolve(file)) })),
|
|
415
|
+
}] }
|
|
416
|
+
: { [target.platform]: mobile };
|
|
417
|
+
fs.mkdirSync(state.config.paths.build, { recursive: true });
|
|
418
|
+
fs.writeFileSync(appOutputsFile(), `${JSON.stringify({ ...outputs, ...recorded }, null, 4)}\n`);
|
|
419
|
+
}
|
|
420
|
+
|
|
330
421
|
// Where a build without a bundler puts an app: under its own name, or for iOS and Android as the project of its build
|
|
331
422
|
// type.
|
|
332
423
|
export function appOutputPath(target) {
|
|
@@ -342,15 +433,34 @@ export function publishApp(target, { to = appOutputPath(target), page = null } =
|
|
|
342
433
|
if (target.platform === 'ios') {
|
|
343
434
|
const parts = appParts(target).map(({ arch, dir }) => ({ sdk: arch, dir }));
|
|
344
435
|
const icon = readAppIcon(state.config);
|
|
436
|
+
const ownPrivacy = appPrivacy(state.config);
|
|
437
|
+
const build = appBuildNumber();
|
|
438
|
+
// The app's own options are checked first, then what its extensions add.
|
|
439
|
+
const options = {
|
|
440
|
+
parts, identity: { id: appId(state.config), team: appTeam() }, label: appDisplayName(state.config), version: appVersion(),
|
|
441
|
+
build: build === null ? appVersion() : String(build), icon: icon && iosIcon(icon), urlSchemes: appUrlSchemes(state.config),
|
|
442
|
+
fileTypes: fileTypeOwners(state.config), links: linkOwners(state.config).map(({ link }) => link),
|
|
443
|
+
activityTypes: activityTypeOwners(state.config).map(({ type }) => type), shortcutItems: shortcutItemOwners(state.config),
|
|
444
|
+
background: appBackgroundColor(state.config), usage: usageDescriptions(state.config, 'ios'), encryption: appEncryption(state.config),
|
|
445
|
+
multipleWindows: iosMultipleWindows(state.config),
|
|
446
|
+
};
|
|
447
|
+
const extensions = extensionsOf('ios');
|
|
448
|
+
const privacy = mergedPrivacy(ownPrivacy, extensions.privacy);
|
|
345
449
|
writeXcodeProject(to, {
|
|
346
|
-
|
|
347
|
-
|
|
450
|
+
...options, privacy, entitlements: mergedEntitlements(extensions.entitlements), plist: extensions.plist, targets: extensions.targets,
|
|
451
|
+
appGroup: appGroupOf('ios', extensions),
|
|
348
452
|
});
|
|
453
|
+
if (target.buildType === 'release') warnUnreasonedApis(to, privacy);
|
|
349
454
|
} else if (target.platform === 'android') {
|
|
350
455
|
const icon = readAppIcon(state.config);
|
|
351
|
-
|
|
352
|
-
parts: appParts(target), id: androidAppId(), label: appDisplayName(state.config), version: androidVersion(), icons: icon && androidIcons(icon),
|
|
456
|
+
const options = {
|
|
457
|
+
parts: appParts(target), id: androidAppId(state.config), label: appDisplayName(state.config), version: androidVersion(), icons: icon && androidIcons(icon),
|
|
353
458
|
urlSchemes: appUrlSchemes(state.config), background: appBackgroundColor(state.config), permissions: Object.keys(appPermissions(state.config)),
|
|
459
|
+
fileTypes: fileTypeOwners(state.config), links: linkOwners(state.config).map(({ link }) => link), shortcutItems: shortcutItemOwners(state.config),
|
|
460
|
+
};
|
|
461
|
+
const extensions = extensionsOf('android');
|
|
462
|
+
writeGradleProject(to, {
|
|
463
|
+
...options, java: extensions.java, extensionPermissions: extensions.permissions, manifests: extensions.manifests, resources: extensions.resources, maven: extensions.maven,
|
|
354
464
|
});
|
|
355
465
|
} else if (path.resolve(to) !== path.resolve(built)) {
|
|
356
466
|
fs.rmSync(to, { recursive: true, force: true });
|
|
@@ -358,11 +468,14 @@ export function publishApp(target, { to = appOutputPath(target), page = null } =
|
|
|
358
468
|
}
|
|
359
469
|
if (page) {
|
|
360
470
|
// The app may land inside the page's own directory, which it then leaves out, with every app built there and what
|
|
361
|
-
// packages it
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
471
|
+
// packages it: what is named after a target of the app, and every installer's files as a build recorded them, which
|
|
472
|
+
// a folder the bundler left as it was holds still.
|
|
473
|
+
const apps = state.targets.filter((t) => t.runtimeEnv === 'app')
|
|
474
|
+
.flatMap((t) => [appOutputName(t), `${state.config.general.name}-${t.platform}-${t.arch}`]).filter((name) => !name.endsWith('.app'));
|
|
475
|
+
const shipped = shippedFiles();
|
|
476
|
+
const isApp = (entry) => entry === path.basename(to) || entry.endsWith('.app')
|
|
477
|
+
|| apps.some((app) => entry === app || entry.startsWith(`${app}.`) || entry.startsWith(`${app}-`))
|
|
478
|
+
|| shipped.has(path.resolve(page, entry));
|
|
366
479
|
fs.readdirSync(page).filter((entry) => !isApp(entry)).forEach((entry) => {
|
|
367
480
|
fs.cpSync(`${page}/${entry}`, `${to}/${appLayout(target).resources}/page/${entry}`, { recursive: true });
|
|
368
481
|
});
|
|
@@ -373,15 +486,9 @@ export function publishApp(target, { to = appOutputPath(target), page = null } =
|
|
|
373
486
|
if (page) shipMacApp(to, signing);
|
|
374
487
|
} else if (target.platform === 'android' && target.buildType === 'release') {
|
|
375
488
|
packageAndroidRelease(to);
|
|
376
|
-
} else if (!['ios', 'android'].includes(target.platform)) {
|
|
377
|
-
if (target.platform === 'win32') signWindowsApp(to, target, page);
|
|
378
|
-
archiveApp(to, target);
|
|
379
|
-
}
|
|
380
|
-
// A desktop app with its page ships with what installs it, and with an updater's key with its update too.
|
|
381
|
-
if (page && ['darwin', 'linux', 'win32'].includes(target.platform)) {
|
|
382
|
-
writeInstaller(to, target);
|
|
383
|
-
writeUpdate(to, target);
|
|
384
489
|
}
|
|
490
|
+
if (['ios', 'android'].includes(target.platform)) recordAppOutput(target, to);
|
|
491
|
+
else if (target.platform === 'win32') signWindowsApp(to, target, page);
|
|
385
492
|
return to;
|
|
386
493
|
}
|
|
387
494
|
|
|
@@ -396,14 +503,32 @@ export default async function buildApp(target, { bridges = [], force = false } =
|
|
|
396
503
|
logger.info(`[${target.path}] app skipped (cargo package - staticlib only)`);
|
|
397
504
|
return false;
|
|
398
505
|
}
|
|
399
|
-
//
|
|
400
|
-
// app section stops the build before anything compiles.
|
|
401
|
-
const header = appHeader(state.config, target.platform);
|
|
506
|
+
// A desktop host's title and window, a Windows one's version, the app's icon and its extensions with their options, as
|
|
507
|
+
// where the page opens files: a bad app section stops the build before anything compiles.
|
|
402
508
|
const icon = readAppIcon(state.config);
|
|
509
|
+
const extensions = extensionsOf(target.platform);
|
|
510
|
+
const header = appHeader(state.config, target.platform, appGroupOf(target.platform, extensions));
|
|
511
|
+
const carried = carriedFilesOf(extensions.files, ownFiles());
|
|
512
|
+
const carriedPaths = new Set(carried);
|
|
513
|
+
// Whether the extension that owns a launch has native code, or for the app's own one that holds the role.
|
|
514
|
+
const hasNative = (extension, role) => extensions.native.some(({ name, roles }) => (extension === null ? roles.includes(role) : name === extension));
|
|
515
|
+
// A desktop's system hands an app no https link: its owners there are those with native code, whose onOpenUrl a
|
|
516
|
+
// phone's code may share.
|
|
517
|
+
const isPhone = ['ios', 'android'].includes(target.platform);
|
|
518
|
+
const links = linkOwners(state.config).filter(({ extension }) => isPhone || hasNative(extension, 'links'));
|
|
519
|
+
// Another system continues no user activity: its owners there are those with native code, whose onUserActivity
|
|
520
|
+
// Apple's code may share.
|
|
521
|
+
const isApple = ['darwin', 'ios'].includes(target.platform);
|
|
522
|
+
const activities = activityTypeOwners(state.config).filter(({ extension }) => isApple || hasNative(extension, 'activities'));
|
|
523
|
+
// A desktop has no quick actions either.
|
|
524
|
+
const shortcuts = shortcutItemOwners(state.config).filter(({ extension }) => isPhone || hasNative(extension, 'shortcuts'));
|
|
525
|
+
const registration = registrationSource(extensions.native, urlSchemeOwners(state.config), fileTypeOwners(state.config), links, activities, shortcuts);
|
|
403
526
|
|
|
404
527
|
const buildType = target.buildType === 'release' ? 'Release' : 'Debug';
|
|
405
528
|
const { build } = state.config.paths;
|
|
406
529
|
const appHostRoot = resolveAppHostRoot();
|
|
530
|
+
appExtensionsOf(state.config.extensions).filter(({ replaces }) => replaces)
|
|
531
|
+
.forEach(({ name, root, replaces }) => logger.info(`[${target.path}] the extension ${name} of ${root} takes the host's ${replaces.join(', ')}`));
|
|
407
532
|
const jsiRoot = resolveAppEmbindJsiRoot();
|
|
408
533
|
const { hermes, cmakeArgs, inputs } = await hostInputs(target);
|
|
409
534
|
|
|
@@ -418,6 +543,10 @@ export default async function buildApp(target, { bridges = [], force = false } =
|
|
|
418
543
|
inputs: headerDirs.flatMap((dir) => (fs.existsSync(dir) ? findFiles('**/*.h', { cwd: dir }) : [dir])),
|
|
419
544
|
});
|
|
420
545
|
const { libs, linkArgs, extraSources } = linkInputs(target, { bridge: BRIDGE });
|
|
546
|
+
const sharedLibs = libs.filter((lib) => lib.endsWith('.so'));
|
|
547
|
+
// The extensions' sources compile with the host, which links what they need: a framework on macOS, a library elsewhere.
|
|
548
|
+
// An iOS host is a static library, whose frameworks the app's Xcode project links.
|
|
549
|
+
const hostLinkArgs = [...linkArgs, ...(target.platform === 'darwin' ? extensions.frameworks.map((framework) => `-framework ${framework}`) : extensions.libraries)];
|
|
421
550
|
|
|
422
551
|
const fingerprintFile = `${build}/${target.appName}.fingerprint`;
|
|
423
552
|
const fingerprint = getContentHash(JSON.stringify({
|
|
@@ -426,7 +555,8 @@ export default async function buildApp(target, { bridges = [], force = false } =
|
|
|
426
555
|
// A desktop app carries its icon; an iOS or Android one gets it as its project is written.
|
|
427
556
|
icon: ['ios', 'android'].includes(target.platform) ? null : icon?.hash ?? null,
|
|
428
557
|
// A macOS app's bundle takes its id, name and version from it.
|
|
429
|
-
infoPlist: target.platform === 'darwin'
|
|
558
|
+
infoPlist: target.platform === 'darwin'
|
|
559
|
+
? infoPlist(state.config.general.name, appVersion(), Boolean(icon), urlSchemesOf(target), fileTypesOf(target), activityTypesOf(target)) : null,
|
|
430
560
|
hermes: HERMES.version,
|
|
431
561
|
inputs,
|
|
432
562
|
linkArgs,
|
|
@@ -436,12 +566,25 @@ export default async function buildApp(target, { bridges = [], force = false } =
|
|
|
436
566
|
}),
|
|
437
567
|
env: getEnv(target),
|
|
438
568
|
data: getData('data', target),
|
|
569
|
+
// What the app carries, by its size and time, as a large model would take long to read at each build.
|
|
570
|
+
carried: carried.map((file) => {
|
|
571
|
+
const stat = fs.statSync(file);
|
|
572
|
+
return { file, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
573
|
+
}),
|
|
439
574
|
version: state.config.package?.version,
|
|
440
575
|
runtime: getFilesFingerprint(filesUnder([
|
|
441
576
|
`${appHostRoot}/cpp`, `${appHostRoot}/js`, `${appHostRoot}/third_party`, `${jsiRoot}/cpp/src`, `${jsiRoot}/js`,
|
|
442
577
|
`${state.config.paths.cli}/assets/js-runtime/adapters`,
|
|
443
578
|
])),
|
|
444
579
|
extraSources: getFilesFingerprint(extraSources),
|
|
580
|
+
// An iOS app's frameworks reach its Xcode project through what the host's build writes.
|
|
581
|
+
extensions: {
|
|
582
|
+
registration,
|
|
583
|
+
files: getFilesFingerprint(filesUnder(extensions.roots).filter((file) => !carriedPaths.has(path.resolve(file)))),
|
|
584
|
+
compiled: extensions.compiled,
|
|
585
|
+
link: hostLinkArgs.slice(linkArgs.length),
|
|
586
|
+
frameworks: extensions.frameworks,
|
|
587
|
+
},
|
|
445
588
|
}));
|
|
446
589
|
const isChanged = !fs.existsSync(fingerprintFile) || fs.readFileSync(fingerprintFile, { encoding: 'utf8' }) !== fingerprint;
|
|
447
590
|
if (!force && !isChanged && fs.existsSync(`${build}/${target.appName}`) && fs.existsSync(`${build}/${target.appJsName}`)) {
|
|
@@ -459,11 +602,25 @@ export default async function buildApp(target, { bridges = [], force = false } =
|
|
|
459
602
|
resetCMakeCacheForSource(workDir, `${appHostRoot}/cpp`, target);
|
|
460
603
|
// The host includes it from its build directory; written only when it changed, it recompiles nothing otherwise.
|
|
461
604
|
writeIfChanged(`${workDir}/crossbind_app.h`, header);
|
|
605
|
+
writeIfChanged(`${workDir}/extensions.cpp`, registration);
|
|
462
606
|
// A Windows host's executable carries the app's icon, which its resource script takes from beside the header.
|
|
463
607
|
if (target.platform === 'win32') {
|
|
464
608
|
if (icon) writeIfChanged(`${workDir}/icon.ico`, windowsIcon(icon));
|
|
465
609
|
else fs.rmSync(`${workDir}/icon.ico`, { force: true });
|
|
466
610
|
}
|
|
611
|
+
// An Apple app's Swift, which CMake's Makefiles take none of: each extension's compiles first, its header joins the
|
|
612
|
+
// extension's includes, and its library the host's link, or an iOS app's Xcode project's.
|
|
613
|
+
const swift = ['darwin', 'ios'].includes(target.platform) ? compileSwift(target, extensions.compiled, workDir, buildType === 'Release') : [];
|
|
614
|
+
const swiftLink = swiftLinkArgs(target, swift.map(({ library }) => library));
|
|
615
|
+
// A Linux app's extensions' packages beside crossbind's sysroot, each extension's in a sysroot of its own, whose
|
|
616
|
+
// headers its sources include and whose libraries the host links against.
|
|
617
|
+
const sysroots = target.platform === 'linux' ? await unpackPackages(target, extensions.compiled, {
|
|
618
|
+
cache: `${build}/linux-packages`, workDir, unpack: (deb, root) => run(null, ['dpkg-deb', '-x', deb, root], platformPrefix, target),
|
|
619
|
+
}) : [];
|
|
620
|
+
const compiled = extensions.compiled.map((part) => ({
|
|
621
|
+
...part, includes: [...part.includes, ...[...swift, ...sysroots].filter(({ name: owner }) => owner === part.name).flatMap(({ includes }) => includes)],
|
|
622
|
+
}));
|
|
623
|
+
const ownLink = target.platform === 'darwin' ? swiftLink : sysroots.flatMap(({ libDir }) => [`-L${libDir}`, `-Wl,-rpath-link,${libDir}`]);
|
|
467
624
|
run(null, [
|
|
468
625
|
// An iOS host builds with crossbind's iOS toolchain file rather than as an Xcode project of its own.
|
|
469
626
|
target.platform === 'ios' ? 'ios-cmake' : 'cmake', `${appHostRoot}/cpp`,
|
|
@@ -472,8 +629,12 @@ export default async function buildApp(target, { bridges = [], force = false } =
|
|
|
472
629
|
`-DCROSSBIND_JSI_ROOT=${jsiRoot}`,
|
|
473
630
|
`-DCROSSBIND_HERMES=${hermes}`,
|
|
474
631
|
`-DCROSSBIND_EXTRA_SOURCES=${extraSources.join(';')}`,
|
|
475
|
-
`-
|
|
632
|
+
`-DCROSSBIND_EXTENSION_REGISTRATION=${workDir}/extensions.cpp`,
|
|
633
|
+
...extensionCmakeArgs(compiled),
|
|
634
|
+
`-DCROSSBIND_LINK_ARGS=${[...hostLinkArgs, ...ownLink].join(';')}`,
|
|
476
635
|
`-DCROSSBIND_LINK_DEPENDS=${libs.join(';')}`,
|
|
636
|
+
// An Android release ships the packages' shared libraries stripped as well, and keeps them as they come.
|
|
637
|
+
...(target.platform === 'android' ? [`-DCROSSBIND_SHARED_LIBS=${sharedLibs.join(';')}`] : []),
|
|
477
638
|
...cmakeArgs,
|
|
478
639
|
], platformPrefix, target);
|
|
479
640
|
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target, { exclusive: true });
|
|
@@ -488,9 +649,10 @@ export default async function buildApp(target, { bridges = [], force = false } =
|
|
|
488
649
|
platformPrefix, target);
|
|
489
650
|
await bundle(`${appHostRoot}/js/page.js`, `${build}/${target.appJsName}`, []);
|
|
490
651
|
if (target.platform === 'ios') {
|
|
491
|
-
|
|
652
|
+
const frameworks = extensions.frameworks.flatMap((framework) => ['-framework', framework]);
|
|
653
|
+
assembleIosPart(target, { library: `${workDir}/lib${name}.a`, linkArgs: [...linkArgs, ...frameworks, ...swiftLink], hbc: `${workDir}/host.hbc`, hermes });
|
|
492
654
|
} else if (target.platform === 'android') {
|
|
493
|
-
assembleAndroidPart(target, { libDir: workDir, sharedLibs
|
|
655
|
+
assembleAndroidPart(target, { libDir: workDir, sharedLibs, hbc: `${workDir}/host.hbc` });
|
|
494
656
|
} else {
|
|
495
657
|
assembleApp(target, { executable: `${workDir}/${path.basename(appLayout(target).executable)}`, hbc: `${workDir}/host.hbc`, hermes, icon });
|
|
496
658
|
}
|
package/src/actions/buildNode.js
CHANGED
|
@@ -24,11 +24,16 @@ import toolchainNoticesDir from '../utils/toolchainNotices.js';
|
|
|
24
24
|
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
25
25
|
const WINDOWS_IMAGE_NOTICES = '/opt/licenses/llvm-mingw';
|
|
26
26
|
|
|
27
|
+
// What a build's output, a package's own packages and a repository's history hold, which no build reads.
|
|
28
|
+
const UNREAD_FOLDERS = ['.crossbind', 'node_modules', '.git'];
|
|
29
|
+
|
|
30
|
+
// The files under these folders a build reads, a link that leads nowhere left out.
|
|
27
31
|
export function filesUnder(dirs) {
|
|
28
32
|
return dirs.flatMap((dir) => (fs.existsSync(dir)
|
|
29
33
|
? fs.readdirSync(dir, { recursive: true })
|
|
34
|
+
.filter((file) => !file.split(/[\\/]/).some((part) => UNREAD_FOLDERS.includes(part)))
|
|
30
35
|
.map((file) => `${dir}/${file}`)
|
|
31
|
-
.filter((file) => fs.statSync(file)
|
|
36
|
+
.filter((file) => fs.statSync(file, { throwIfNoEntry: false })?.isFile())
|
|
32
37
|
: []));
|
|
33
38
|
}
|
|
34
39
|
|
package/src/actions/buildWasm.js
CHANGED
|
@@ -6,7 +6,6 @@ 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 triggerExtensions from './extensions.js';
|
|
10
9
|
import state from '../state/index.js';
|
|
11
10
|
import resolveEmbindRustRoot from '../utils/resolveEmbindRust.js';
|
|
12
11
|
import logger from '../utils/logger.js';
|
|
@@ -128,8 +127,6 @@ export default async function buildWasm(target, options = {}) {
|
|
|
128
127
|
if (!isProd && !/-s\s*STACK_OVERFLOW_CHECK=/.test(set)) emccFlags.push('-sSTACK_OVERFLOW_CHECK=1');
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
triggerExtensions('buildWasm', 'beforeBuild', [emccFlags]);
|
|
132
|
-
|
|
133
130
|
if (target.runtime === 'mt' && !emccFlags.includes('-pthread')) {
|
|
134
131
|
emccFlags.push('-pthread');
|
|
135
132
|
emccFlags.push('-sPTHREAD_POOL_SIZE=Math.min(navigator.hardwareConcurrency || 1, 2)');
|
|
@@ -203,8 +200,6 @@ export default async function buildWasm(target, options = {}) {
|
|
|
203
200
|
logger.startStep(target, 'wasm');
|
|
204
201
|
const t0 = performance.now();
|
|
205
202
|
|
|
206
|
-
triggerExtensions('buildWasm', 'beforeBuildBrowser', [emccFlags]);
|
|
207
|
-
|
|
208
203
|
const data = Object.entries(getData('data', target)).map(([key, value]) => ['--preload-file', `${key.replaceAll('@', '@@')}@/crossbind/${value}`]).flat();
|
|
209
204
|
run('em++', [
|
|
210
205
|
'-lembind',
|
|
@@ -260,8 +255,6 @@ export default async function buildWasm(target, options = {}) {
|
|
|
260
255
|
logger.startStep(target, 'wasm');
|
|
261
256
|
const t0 = performance.now();
|
|
262
257
|
|
|
263
|
-
triggerExtensions('buildWasm', 'beforeBuildEdge', [emccFlags]);
|
|
264
|
-
|
|
265
258
|
const data = Object.entries(getData('data', target)).map(([key, value]) => ['--preload-file', `${key.replaceAll('@', '@@')}@/crossbind/${value}`]).flat();
|
|
266
259
|
run('em++', [
|
|
267
260
|
'-lembind',
|
|
@@ -300,8 +293,6 @@ export default async function buildWasm(target, options = {}) {
|
|
|
300
293
|
if (target.runtimeEnv === 'node') {
|
|
301
294
|
logger.startStep(target, 'wasm');
|
|
302
295
|
|
|
303
|
-
triggerExtensions('buildWasm', 'beforeBuildNodeJS', [emccFlags]);
|
|
304
|
-
|
|
305
296
|
run('em++', [
|
|
306
297
|
'-lembind',
|
|
307
298
|
// The config's emccFlags follow the default level, so its -Oz or -Os wins (the last -O does).
|