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
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { rollup } from 'rollup';
|
|
7
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
8
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
9
|
+
import virtual from '@rollup/plugin-virtual';
|
|
10
|
+
import run from './run.js';
|
|
11
|
+
import createLib from './createLib.js';
|
|
12
|
+
import getData from './getData.js';
|
|
13
|
+
import getEnv from './getEnv.js';
|
|
14
|
+
import { copyWindowsNotices, filesUnder, linkInputs } from './buildNode.js';
|
|
15
|
+
import { suppressNodeBuiltinWarnings } from './buildJs.js';
|
|
16
|
+
import { isNativeSourceNewerThan } from './isSourceNewer.js';
|
|
17
|
+
import { createNativeBridges } from './nativeBridges.js';
|
|
18
|
+
import { getTargetParams } from './target.js';
|
|
19
|
+
import writeXcodeProject, { HERMES_SLICES, IOS_SDKS } from './xcodeProject.js';
|
|
20
|
+
import writeGradleProject, { ANDROID_ABIS } from './gradleProject.js';
|
|
21
|
+
import { attachedDevice } from './androidDevice.js';
|
|
22
|
+
import { packageAndroidRelease } from './androidGradle.js';
|
|
23
|
+
import state from '../state/index.js';
|
|
24
|
+
import logger from '../utils/logger.js';
|
|
25
|
+
import findFiles from '../utils/findFiles.js';
|
|
26
|
+
import { getContentHash, getFilesFingerprint } from '../utils/hash.js';
|
|
27
|
+
import { xmlPlist } from '../utils/plist.js';
|
|
28
|
+
import { writeNativeSourceStamp } from '../utils/nativeSourceStamp.js';
|
|
29
|
+
import resetCMakeCacheForSource from '../utils/resetCMakeCacheForSource.js';
|
|
30
|
+
import resolveAppHostRoot, { resolveAppEmbindJsiRoot } from '../utils/resolveAppHost.js';
|
|
31
|
+
import resolveHermes, { HERMES } from '../utils/hermes.js';
|
|
32
|
+
import { getDockerImage, imageRoleFor } from '../utils/pullDockerImage.js';
|
|
33
|
+
import { isUnderBase } from '../utils/replaceBasePathForDocker.js';
|
|
34
|
+
import { chosenRunner } from '../utils/selectRunner.js';
|
|
35
|
+
import { DARWIN_DEPLOYMENT_TARGET } from '../utils/darwinToolchain.js';
|
|
36
|
+
|
|
37
|
+
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
38
|
+
const BRIDGE = 'AppBridge';
|
|
39
|
+
const LINUX_MULTIARCH = { x64: 'x86_64-linux-gnu', arm64: 'aarch64-linux-gnu' };
|
|
40
|
+
// Where the android-app, linux and windows images keep the Hermes of the release crossbind pins.
|
|
41
|
+
const IMAGE_HERMES = `/opt/crossbind/hermes/${HERMES.version}`;
|
|
42
|
+
|
|
43
|
+
// Where an app keeps its executable and its files: a macOS bundle, a Linux or Windows directory, or the app folder of an
|
|
44
|
+
// iOS app's Xcode project or of an Android app's Gradle project, which link the executable themselves.
|
|
45
|
+
export function appLayout(target) {
|
|
46
|
+
const { name } = state.config.general;
|
|
47
|
+
if (target.platform === 'darwin') return { executable: `Contents/MacOS/${name}`, resources: 'Contents/Resources' };
|
|
48
|
+
if (target.platform === 'ios') return { executable: null, resources: 'app' };
|
|
49
|
+
if (target.platform === 'android') return { executable: null, resources: 'app/src/main/assets/app' };
|
|
50
|
+
return { executable: target.platform === 'win32' ? `${name}.exe` : name, resources: 'resources' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The name a bundler gives the app beside the page it built. An iOS app is one Xcode project for both SDKs, and an
|
|
54
|
+
// Android app one Gradle project for both ABIs.
|
|
55
|
+
export function appOutputName(target) {
|
|
56
|
+
const { name } = state.config.general;
|
|
57
|
+
if (target.platform === 'darwin') return `${name}.app`;
|
|
58
|
+
if (['ios', 'android'].includes(target.platform)) return `${name}-${target.platform}`;
|
|
59
|
+
return `${name}-${target.platform}-${target.arch}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// An Android, Linux or Windows app builds in one of crossbind's images, which a local or remote runner does not have.
|
|
63
|
+
export function assertAppRunner(target) {
|
|
64
|
+
if (['darwin', 'ios'].includes(target.platform)) return;
|
|
65
|
+
const runner = chosenRunner(state.config.system);
|
|
66
|
+
if (['DOCKER_RUN', 'DOCKER_EXEC'].includes(runner)) return;
|
|
67
|
+
const app = { android: 'an Android', win32: 'a Windows' }[target.platform] ?? 'a Linux';
|
|
68
|
+
throw new Error(`crossbind: ${app} app builds in crossbind's ${imageRoleFor(target)} image, which RUNNER=${runner} does not use. Build it with RUNNER=DOCKER_RUN.`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The app a bundler builds: this machine's desktop app, or with CROSSBIND_APP_PLATFORM an iOS app for both SDKs, an
|
|
72
|
+
// Android app for both ABIs, or a Linux or Windows app for both arches, which builds in Docker on any machine. During
|
|
73
|
+
// development only what runs it: the Simulator, the attached device, or this machine.
|
|
74
|
+
export function appTargetParams({ dev = false } = {}, env = process.env) {
|
|
75
|
+
const platform = env.CROSSBIND_APP_PLATFORM;
|
|
76
|
+
const app = (platforms, archs) => {
|
|
77
|
+
platforms.forEach((name) => assertAppRunner({ platform: name, runtimeEnv: 'app' }));
|
|
78
|
+
return getTargetParams({ platform: platforms, arch: archs, runtime: ['mt'], runtimeEnv: ['app'] }, true);
|
|
79
|
+
};
|
|
80
|
+
if (!platform) return app([process.platform], [process.arch]);
|
|
81
|
+
if (platform === 'android') return app(['android'], dev ? [attachedDevice().abi] : ANDROID_ABIS);
|
|
82
|
+
if (platform === 'linux') {
|
|
83
|
+
if (dev && process.platform !== 'linux') {
|
|
84
|
+
throw new Error('crossbind: a Linux app runs on Linux, and so does its development session; build it here with crossbind app build -p linux.');
|
|
85
|
+
}
|
|
86
|
+
return app(['linux'], dev ? [process.arch] : Object.keys(LINUX_MULTIARCH));
|
|
87
|
+
}
|
|
88
|
+
if (platform === 'macos') {
|
|
89
|
+
if (process.platform !== 'darwin') throw new Error('crossbind: macOS apps build on macOS.');
|
|
90
|
+
return app(['darwin'], [process.arch]);
|
|
91
|
+
}
|
|
92
|
+
if (platform === 'windows') {
|
|
93
|
+
if (dev && process.platform !== 'win32') {
|
|
94
|
+
throw new Error('crossbind: a Windows app runs on Windows, and so does its development session; build it here with crossbind app build -p windows.');
|
|
95
|
+
}
|
|
96
|
+
return app(['win32'], dev ? [process.arch] : ['x64', 'arm64']);
|
|
97
|
+
}
|
|
98
|
+
if (platform !== 'ios') {
|
|
99
|
+
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.`);
|
|
100
|
+
}
|
|
101
|
+
if (process.platform !== 'darwin') throw new Error('crossbind: iOS apps build on macOS, with Xcode.');
|
|
102
|
+
return getTargetParams({ platform: ['ios'], arch: dev ? ['iphonesimulator'] : IOS_SDKS, runtime: ['mt'], runtimeEnv: ['app'] }, true);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// An app's bundle id: the project's app.id, else one made from the project's name.
|
|
106
|
+
function appId() {
|
|
107
|
+
const id = state.config.app.id ?? `dev.crossbind.${state.config.general.name.replace(/[^A-Za-z0-9.-]/g, '-')}`;
|
|
108
|
+
if (!/^[A-Za-z0-9.-]+$/.test(id)) {
|
|
109
|
+
throw new Error(`crossbind: app.id ${JSON.stringify(id)} is no bundle id, which takes letters, digits, hyphens and dots, as com.example.app does.`);
|
|
110
|
+
}
|
|
111
|
+
return id;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// The Apple team that signs an iOS app: the project's app.team, else the machine's XCODE_DEVELOPMENT_TEAM, else none.
|
|
115
|
+
function appTeam() {
|
|
116
|
+
const team = state.config.app.team ?? state.config.system.XCODE_DEVELOPMENT_TEAM ?? '';
|
|
117
|
+
if (team && !/^[A-Z0-9]{10}$/.test(team)) {
|
|
118
|
+
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
|
+
}
|
|
120
|
+
return team;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// An Android app's id: the project's app.id, else one made from the project's name.
|
|
124
|
+
function androidAppId() {
|
|
125
|
+
const id = state.config.app.id ?? `dev.crossbind.${state.config.general.name.replace(/[^A-Za-z0-9.]/g, '_')}`;
|
|
126
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/.test(id)) {
|
|
127
|
+
throw new Error(`crossbind: ${JSON.stringify(id)} (app.id, else made from the project's name) is no Android application id, which takes dot-separated parts of letters, digits and underscores that each start with a letter, as com.example.app does. Set app.id in crossbind.config.js.`);
|
|
128
|
+
}
|
|
129
|
+
return id;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Apple takes dot-separated integers alone as an app's version.
|
|
133
|
+
const appVersion = () => (state.config.package?.version ?? '1.0.0').replace(/[-+].*$/, '');
|
|
134
|
+
|
|
135
|
+
// Android takes any name for an app's version, and a positive integer that grows from release to release as its code.
|
|
136
|
+
function androidVersion() {
|
|
137
|
+
const [major = 0, minor = 0, patch = 0] = appVersion().split('.').map(Number);
|
|
138
|
+
return { name: state.config.package?.version ?? '1.0.0', code: Math.max(1, major * 1000000 + minor * 1000 + patch) };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The apps this run built or found current. A part of an iOS app that an earlier run built may be stale.
|
|
142
|
+
const currentApps = new Set();
|
|
143
|
+
|
|
144
|
+
// What a host compiles and links against beside the project's archives: Hermes, on Linux WebKitGTK and on Windows the
|
|
145
|
+
// WebView2 SDK's headers. Apple's Hermes is React Native's release on this machine; the others are in the image the host
|
|
146
|
+
// builds in, android-app, linux or windows, whose reference stands for them in the fingerprint. An Android host shares
|
|
147
|
+
// the C++ runtime of Hermes' libraries.
|
|
148
|
+
async function hostInputs(target) {
|
|
149
|
+
if (['darwin', 'ios'].includes(target.platform)) {
|
|
150
|
+
const hermes = await resolveHermes();
|
|
151
|
+
return { hermes, cmakeArgs: [], inputs: [hermes] };
|
|
152
|
+
}
|
|
153
|
+
assertAppRunner(target);
|
|
154
|
+
const inputs = [getDockerImage(imageRoleFor(target), target.platform === 'android' ? 'linux/amd64' : undefined)];
|
|
155
|
+
if (target.platform === 'android') {
|
|
156
|
+
return { hermes: `${IMAGE_HERMES}/android`, cmakeArgs: ['-DANDROID_STL=c++_shared'], inputs };
|
|
157
|
+
}
|
|
158
|
+
if (target.platform === 'win32') {
|
|
159
|
+
return { hermes: `${IMAGE_HERMES}/win32-${target.arch}`, cmakeArgs: ['-DCROSSBIND_WEBVIEW2_INCLUDE=/opt/crossbind/webview2/include'], inputs };
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
hermes: `${IMAGE_HERMES}/linux-${target.arch}`,
|
|
163
|
+
cmakeArgs: [
|
|
164
|
+
`-DCROSSBIND_GTK_SYSROOT=/opt/crossbind/webkitgtk/${LINUX_MULTIARCH[target.arch]}`,
|
|
165
|
+
`-DCROSSBIND_GTK_MULTIARCH=${LINUX_MULTIARCH[target.arch]}`,
|
|
166
|
+
],
|
|
167
|
+
inputs,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// The app host package's scripts import crossbind's own runtime modules under crossbind/runtime/.
|
|
172
|
+
const runtimeModules = () => ({
|
|
173
|
+
name: 'crossbind-runtime-modules',
|
|
174
|
+
resolveId: (id) => (id.startsWith('crossbind/runtime/')
|
|
175
|
+
? `${state.config.paths.cli}/assets/js-runtime/${id.slice('crossbind/runtime/'.length)}`
|
|
176
|
+
: null),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
async function bundle(input, file, plugins) {
|
|
180
|
+
const build = await rollup({ input, plugins, onwarn: suppressNodeBuiltinWarnings });
|
|
181
|
+
await build.write({ file, format: 'iife' });
|
|
182
|
+
await build.close();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const infoPlist = (name, version) => xmlPlist({
|
|
186
|
+
CFBundleExecutable: name,
|
|
187
|
+
CFBundleIdentifier: appId(),
|
|
188
|
+
CFBundleName: name,
|
|
189
|
+
CFBundlePackageType: 'APPL',
|
|
190
|
+
CFBundleShortVersionString: version,
|
|
191
|
+
CFBundleVersion: version,
|
|
192
|
+
LSMinimumSystemVersion: DARWIN_DEPLOYMENT_TARGET,
|
|
193
|
+
NSPrincipalClass: 'NSApplication',
|
|
194
|
+
NSHighResolutionCapable: true,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// The host's bytecode and the data the packages ship, beside the page's folder.
|
|
198
|
+
function copyAppFiles(dir, hbc, target) {
|
|
199
|
+
fs.mkdirSync(`${dir}/page`, { recursive: true });
|
|
200
|
+
fs.copyFileSync(hbc, `${dir}/host.hbc`);
|
|
201
|
+
Object.entries(getData('data', target)).forEach(([source, dataName]) => {
|
|
202
|
+
if (fs.existsSync(source)) fs.cpSync(source, `${dir}/data/${dataName}`, { recursive: true });
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Everything but the page: the host and the app's files, and on macOS Hermes beside them.
|
|
207
|
+
function assembleApp(target, { executable, hbc, hermes }) {
|
|
208
|
+
const app = `${state.config.paths.build}/${target.appName}`;
|
|
209
|
+
const { name } = state.config.general;
|
|
210
|
+
const layout = appLayout(target);
|
|
211
|
+
fs.rmSync(app, { recursive: true, force: true });
|
|
212
|
+
fs.mkdirSync(path.dirname(`${app}/${layout.executable}`), { recursive: true });
|
|
213
|
+
fs.copyFileSync(executable, `${app}/${layout.executable}`);
|
|
214
|
+
copyAppFiles(`${app}/${layout.resources}`, hbc, target);
|
|
215
|
+
if (target.platform !== 'darwin') return;
|
|
216
|
+
fs.writeFileSync(`${app}/Contents/Info.plist`, infoPlist(name, appVersion()));
|
|
217
|
+
const framework = `${app}/Contents/Frameworks/hermesvm.framework`;
|
|
218
|
+
fs.cpSync(`${hermes}/Library/Frameworks/macosx/hermesvm.framework`, framework, { recursive: true, verbatimSymlinks: true });
|
|
219
|
+
// The release carries Intel and Apple silicon; an app of one arch needs its own half.
|
|
220
|
+
const library = `${framework}/Versions/Current/hermesvm`;
|
|
221
|
+
run(null, ['lipo', library, '-thin', target.arch === 'x64' ? 'x86_64' : 'arm64', '-output', `${library}.thin`], null, target);
|
|
222
|
+
fs.renameSync(`${library}.thin`, library);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// What an iOS SDK target brings to the app's Xcode project: the host and the archives it links, copied beside a link
|
|
226
|
+
// line that names them under {lib}, its slice of Hermes, and the app's files.
|
|
227
|
+
function assembleIosPart(target, { library, linkArgs, hbc, hermes }) {
|
|
228
|
+
const part = `${state.config.paths.build}/${target.appName}`;
|
|
229
|
+
fs.rmSync(part, { recursive: true, force: true });
|
|
230
|
+
fs.mkdirSync(`${part}/lib`, { recursive: true });
|
|
231
|
+
let count = 0;
|
|
232
|
+
const place = (file) => {
|
|
233
|
+
const name = `${count++}-${path.basename(file)}`;
|
|
234
|
+
fs.copyFileSync(file, `${part}/lib/${name}`);
|
|
235
|
+
return `{lib}/${name}`;
|
|
236
|
+
};
|
|
237
|
+
const args = [`-Wl,-force_load,${place(library)}`, ...linkArgs.map((arg) => {
|
|
238
|
+
const archive = arg.match(/^(-Wl,-force_load,)?(\/.+\.a)$/);
|
|
239
|
+
if (archive) return `${archive[1] ?? ''}${place(archive[2])}`;
|
|
240
|
+
if (arg.endsWith('.a')) throw new Error(`crossbind: the iOS app cannot take ${arg} into its Xcode project, as it names no absolute path.`);
|
|
241
|
+
return arg;
|
|
242
|
+
})];
|
|
243
|
+
fs.writeFileSync(`${part}/link.json`, JSON.stringify(args, null, 2));
|
|
244
|
+
fs.cpSync(`${hermes}/Library/Frameworks/universal/hermesvm.xcframework/${HERMES_SLICES[target.arch]}/hermesvm.framework`,
|
|
245
|
+
`${part}/hermesvm.framework`, { recursive: true, verbatimSymlinks: true });
|
|
246
|
+
copyAppFiles(`${part}/app`, hbc, target);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// What an Android ABI target brings to the app's Gradle project: the host, the libraries beside it and the packages'
|
|
250
|
+
// shared libraries it links, and the app's files.
|
|
251
|
+
function assembleAndroidPart(target, { libDir, sharedLibs, hbc }) {
|
|
252
|
+
const part = `${state.config.paths.build}/${target.appName}`;
|
|
253
|
+
fs.rmSync(part, { recursive: true, force: true });
|
|
254
|
+
fs.mkdirSync(`${part}/lib`, { recursive: true });
|
|
255
|
+
[...['libcrossbind_app.so', 'libjsi.so', 'libc++_shared.so'].map((file) => `${libDir}/${file}`), ...sharedLibs]
|
|
256
|
+
.forEach((file) => fs.copyFileSync(file, `${part}/lib/${path.basename(file)}`));
|
|
257
|
+
copyAppFiles(`${part}/app`, hbc, target);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// The parts of an iOS or Android app this run has for its build type, one per SDK or ABI.
|
|
261
|
+
const appParts = (target) => state.targets
|
|
262
|
+
.filter((t) => t.platform === target.platform && t.runtimeEnv === 'app' && t.buildType === target.buildType && currentApps.has(t.appName))
|
|
263
|
+
.map((t) => ({ arch: t.arch, dir: `${state.config.paths.build}/${t.appName}` }));
|
|
264
|
+
|
|
265
|
+
// A Linux or Windows app also ships as one archive, next to its directory: a Windows one as a zip, made in the windows
|
|
266
|
+
// image.
|
|
267
|
+
function archiveApp(app, target) {
|
|
268
|
+
if (target.platform === 'win32') {
|
|
269
|
+
if (!isUnderBase(app, state.config.paths.base)) {
|
|
270
|
+
throw new Error(`crossbind: the Windows app ${app} is outside ${state.config.paths.base}, the project's base and all the windows container sees. Build the app into a directory under it.`);
|
|
271
|
+
}
|
|
272
|
+
fs.rmSync(`${app}.zip`, { force: true });
|
|
273
|
+
run(null, ['zip', '-q', '-r', '-X', `${path.basename(app)}.zip`, path.basename(app)], null, target, { workdir: path.dirname(app) });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const result = spawnSync('tar', ['-czf', `${app}.tar.gz`, '-C', path.dirname(app), path.basename(app)], {
|
|
277
|
+
encoding: 'utf8',
|
|
278
|
+
env: { ...process.env, COPYFILE_DISABLE: '1' },
|
|
279
|
+
});
|
|
280
|
+
if (result.status !== 0) throw new Error(`crossbind: cannot archive ${app}: ${result.stderr || result.error?.message}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Apple silicon runs signed code only, and a bundle's signature covers every file in it, so an app is signed once the
|
|
284
|
+
// page is in: ad hoc, which runs on the machine that built it.
|
|
285
|
+
function signApp(app, target) {
|
|
286
|
+
run(null, ['codesign', '--force', '--sign', '-', '--timestamp=none', `${app}/Contents/Frameworks/hermesvm.framework`], null, target);
|
|
287
|
+
run(null, ['codesign', '--force', '--sign', '-', '--timestamp=none', app], null, target);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Where a build without a bundler puts an app: under its own name, or for iOS and Android as the project of its build
|
|
291
|
+
// type.
|
|
292
|
+
export function appOutputPath(target) {
|
|
293
|
+
const { output } = state.config.paths;
|
|
294
|
+
if (!['ios', 'android'].includes(target.platform)) return `${output}/${target.appName}`;
|
|
295
|
+
return `${output}/${appOutputName(target)}${target.buildType === 'debug' ? '.debug' : ''}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// The built app with a page's files at `to`: the bundler's output for its page, or the project's output for a build
|
|
299
|
+
// with none. An iOS app is its Xcode project and an Android app its Gradle project, with every SDK or ABI built so far.
|
|
300
|
+
export function publishApp(target, { to = appOutputPath(target), page = null } = {}) {
|
|
301
|
+
const built = `${state.config.paths.build}/${target.appName}`;
|
|
302
|
+
if (target.platform === 'ios') {
|
|
303
|
+
const parts = appParts(target).map(({ arch, dir }) => ({ sdk: arch, dir }));
|
|
304
|
+
writeXcodeProject(to, { parts, identity: { id: appId(), team: appTeam() }, version: appVersion() });
|
|
305
|
+
} else if (target.platform === 'android') {
|
|
306
|
+
writeGradleProject(to, { parts: appParts(target), id: androidAppId(), version: androidVersion() });
|
|
307
|
+
} else if (path.resolve(to) !== path.resolve(built)) {
|
|
308
|
+
fs.rmSync(to, { recursive: true, force: true });
|
|
309
|
+
fs.cpSync(built, to, { recursive: true, verbatimSymlinks: true });
|
|
310
|
+
}
|
|
311
|
+
if (page) {
|
|
312
|
+
// The app may land inside the page's own directory, which it then leaves out, with every app built there and what
|
|
313
|
+
// packages it.
|
|
314
|
+
const apps = new Set([path.basename(to), ...state.targets.filter((t) => t.runtimeEnv === 'app').map(appOutputName)]);
|
|
315
|
+
const isApp = (entry) => entry.endsWith('.app') || apps.has(entry.replace(/\.(tar\.gz|zip|apk|aab)$/, ''));
|
|
316
|
+
fs.readdirSync(page).filter((entry) => !isApp(entry)).forEach((entry) => {
|
|
317
|
+
fs.cpSync(`${page}/${entry}`, `${to}/${appLayout(target).resources}/page/${entry}`, { recursive: true });
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
if (target.platform === 'darwin') signApp(to, target);
|
|
321
|
+
else if (target.platform === 'android' && target.buildType === 'release') packageAndroidRelease(to);
|
|
322
|
+
else if (!['ios', 'android'].includes(target.platform)) archiveApp(to, target);
|
|
323
|
+
return to;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Builds an app's host from the project's archives and the given bridges, its Hermes bytecode and the page runtime the
|
|
327
|
+
// page loads as crossbind.js, and puts them into an app without a page.
|
|
328
|
+
export default async function buildApp(target, { bridges = [], force = false } = {}) {
|
|
329
|
+
if (['darwin', 'ios'].includes(target.platform) && process.platform !== 'darwin') {
|
|
330
|
+
logger.info(`[${target.path}] app skipped (macOS and iOS apps build on a macOS host)`);
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
if (state.config.export.type === 'cargo') {
|
|
334
|
+
logger.info(`[${target.path}] app skipped (cargo package - staticlib only)`);
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const buildType = target.buildType === 'release' ? 'Release' : 'Debug';
|
|
339
|
+
const { build } = state.config.paths;
|
|
340
|
+
const appHostRoot = resolveAppHostRoot();
|
|
341
|
+
const jsiRoot = resolveAppEmbindJsiRoot();
|
|
342
|
+
const { hermes, cmakeArgs, inputs } = await hostInputs(target);
|
|
343
|
+
|
|
344
|
+
// The bridges compile against the embind runtime, which speaks the JSI of the Hermes the host links. Android's
|
|
345
|
+
// Hermes comes without JSI's headers, which the host package has from the same release.
|
|
346
|
+
const headerDirs = [`${jsiRoot}/cpp/src`, `${hermes}/include`, ...(target.platform === 'android' ? [`${appHostRoot}/third_party/jsi/include`] : [])];
|
|
347
|
+
createLib(target, BRIDGE, {
|
|
348
|
+
buildSource: false,
|
|
349
|
+
nativeGlob: [`${state.config.paths.cli}/assets/cpp-runtime/commonBridges.cpp`, ...bridges],
|
|
350
|
+
headerDirs,
|
|
351
|
+
// Headers in the image count by their path, which names the Hermes release.
|
|
352
|
+
inputs: headerDirs.flatMap((dir) => (fs.existsSync(dir) ? findFiles('**/*.h', { cwd: dir }) : [dir])),
|
|
353
|
+
});
|
|
354
|
+
const { libs, linkArgs, extraSources } = linkInputs(target, { bridge: BRIDGE });
|
|
355
|
+
|
|
356
|
+
const fingerprintFile = `${build}/${target.appName}.fingerprint`;
|
|
357
|
+
const fingerprint = getContentHash(JSON.stringify({
|
|
358
|
+
builder: getFilesFingerprint([fileURLToPath(import.meta.url)]),
|
|
359
|
+
hermes: HERMES.version,
|
|
360
|
+
inputs,
|
|
361
|
+
linkArgs,
|
|
362
|
+
libs: libs.map((lib) => {
|
|
363
|
+
const stat = fs.existsSync(lib) ? fs.statSync(lib) : null;
|
|
364
|
+
return { lib, size: stat ? stat.size : null, mtimeMs: stat ? stat.mtimeMs : null };
|
|
365
|
+
}),
|
|
366
|
+
env: getEnv(target),
|
|
367
|
+
data: getData('data', target),
|
|
368
|
+
version: state.config.package?.version,
|
|
369
|
+
runtime: getFilesFingerprint(filesUnder([
|
|
370
|
+
`${appHostRoot}/cpp`, `${appHostRoot}/js`, `${appHostRoot}/third_party`, `${jsiRoot}/cpp/src`, `${jsiRoot}/js`,
|
|
371
|
+
`${state.config.paths.cli}/assets/js-runtime/adapters`,
|
|
372
|
+
])),
|
|
373
|
+
extraSources: getFilesFingerprint(extraSources),
|
|
374
|
+
}));
|
|
375
|
+
const isChanged = !fs.existsSync(fingerprintFile) || fs.readFileSync(fingerprintFile, { encoding: 'utf8' }) !== fingerprint;
|
|
376
|
+
if (!force && !isChanged && fs.existsSync(`${build}/${target.appName}`) && fs.existsSync(`${build}/${target.appJsName}`)) {
|
|
377
|
+
logger.cachedStep(target, 'app');
|
|
378
|
+
currentApps.add(target.appName);
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const platformPrefix = `App-${buildType}`;
|
|
383
|
+
const workDir = `${build}/${platformPrefix}/${target.path}`;
|
|
384
|
+
const { name } = state.config.general;
|
|
385
|
+
// An Android app's activity loads its host by this one name.
|
|
386
|
+
const hostName = target.platform === 'android' ? 'crossbind_app' : name;
|
|
387
|
+
logger.startStep(target, 'app');
|
|
388
|
+
resetCMakeCacheForSource(workDir, `${appHostRoot}/cpp`, target);
|
|
389
|
+
run(null, [
|
|
390
|
+
// An iOS host builds with crossbind's iOS toolchain file rather than as an Xcode project of its own.
|
|
391
|
+
target.platform === 'ios' ? 'ios-cmake' : 'cmake', `${appHostRoot}/cpp`,
|
|
392
|
+
`-DCMAKE_BUILD_TYPE=${buildType}`,
|
|
393
|
+
`-DCROSSBIND_APP_FILE=${hostName}`,
|
|
394
|
+
`-DCROSSBIND_JSI_ROOT=${jsiRoot}`,
|
|
395
|
+
`-DCROSSBIND_HERMES=${hermes}`,
|
|
396
|
+
`-DCROSSBIND_EXTRA_SOURCES=${extraSources.join(';')}`,
|
|
397
|
+
`-DCROSSBIND_LINK_ARGS=${linkArgs.join(';')}`,
|
|
398
|
+
`-DCROSSBIND_LINK_DEPENDS=${libs.join(';')}`,
|
|
399
|
+
...cmakeArgs,
|
|
400
|
+
], platformPrefix, target);
|
|
401
|
+
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target, { exclusive: true });
|
|
402
|
+
if (target.platform === 'win32') copyWindowsNotices(target);
|
|
403
|
+
|
|
404
|
+
const systemConfig = `export default ${JSON.stringify({ env: getEnv(target) })};`;
|
|
405
|
+
await bundle(`${appHostRoot}/js/host.js`, `${workDir}/host.js`, [
|
|
406
|
+
virtual({ 'crossbind/systemConfig': systemConfig }), runtimeModules(), nodeResolve(), commonjs(),
|
|
407
|
+
]);
|
|
408
|
+
// Without -Xes6-block-scoping, hermesc compiles let and const as var, and closures made in a loop share one binding.
|
|
409
|
+
run(null, [`${hermes}/bin/hermesc`, '-O', '-Xes6-block-scoping', '-emit-binary', '-out', `${workDir}/host.hbc`, `${workDir}/host.js`],
|
|
410
|
+
platformPrefix, target);
|
|
411
|
+
await bundle(`${appHostRoot}/js/page.js`, `${build}/${target.appJsName}`, []);
|
|
412
|
+
if (target.platform === 'ios') {
|
|
413
|
+
assembleIosPart(target, { library: `${workDir}/lib${name}.a`, linkArgs, hbc: `${workDir}/host.hbc`, hermes });
|
|
414
|
+
} else if (target.platform === 'android') {
|
|
415
|
+
assembleAndroidPart(target, { libDir: workDir, sharedLibs: libs.filter((lib) => lib.endsWith('.so')), hbc: `${workDir}/host.hbc` });
|
|
416
|
+
} else {
|
|
417
|
+
assembleApp(target, { executable: `${workDir}/${path.basename(appLayout(target).executable)}`, hbc: `${workDir}/host.hbc`, hermes });
|
|
418
|
+
}
|
|
419
|
+
logger.doneStep(target, 'app');
|
|
420
|
+
|
|
421
|
+
fs.writeFileSync(fingerprintFile, fingerprint);
|
|
422
|
+
writeNativeSourceStamp(`${build}/${target.appName}`, state.config.paths.native);
|
|
423
|
+
currentApps.add(target.appName);
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// The app of the project as it stands, for a bundler plugin, which needs the host before the page asks for anything:
|
|
428
|
+
// the bridges a plain build binds, found in the project's headers and sources, then its own archive and the host.
|
|
429
|
+
export async function buildProjectApp(target) {
|
|
430
|
+
const bridges = createNativeBridges([target]).headers.flatMap((header) => header.bridge ?? []);
|
|
431
|
+
const force = isNativeSourceNewerThan(`${state.config.paths.build}/${target.appName}`);
|
|
432
|
+
createLib(target, 'Source', { force, buildSource: true });
|
|
433
|
+
return buildApp(target, { bridges, force });
|
|
434
|
+
}
|
|
Binary file
|
|
@@ -83,12 +83,13 @@ async function buildMissingCargoDependencies(buildTargets, targetParams) {
|
|
|
83
83
|
const bridgeTarget = buildTargets.some(isWasm) ? state.targets.find(isWasm) : undefined;
|
|
84
84
|
const extraTarget = bridgeTarget && !buildTargets.some((target) => target.path === bridgeTarget.path) ? bridgeTarget : undefined;
|
|
85
85
|
const targets = extraTarget ? [...buildTargets, extraTarget] : buildTargets;
|
|
86
|
-
// A prebuilt from another embind-rs
|
|
87
|
-
|
|
86
|
+
// A prebuilt from another embind-rs, or for an image's target from another rustc, counts as missing: linked, it
|
|
87
|
+
// carries stale glue or clashes with the others.
|
|
88
88
|
const prebuiltOf = (dep, target) => [target.path, target.releasePath]
|
|
89
89
|
.map((targetPath) => `${dep.paths.output}/prebuilt/${targetPath}`)
|
|
90
90
|
.find((dir) => fs.existsSync(dir)) ?? `${dep.paths.output}/prebuilt/${target.path}`;
|
|
91
|
-
const isBuilt = (dep, target) => dep.functions.isEnabled(target)
|
|
91
|
+
const isBuilt = (dep, target) => dep.functions.isEnabled(target)
|
|
92
|
+
&& !isEmbindRsFingerprintStale(prebuiltOf(dep, target), getEmbindRsFingerprint(target));
|
|
92
93
|
const missing = cargoDependencies.filter((dep) => targets.some((target) => !isBuilt(dep, target)));
|
|
93
94
|
|
|
94
95
|
for (const dep of missing) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import replace from 'replace';
|
|
3
|
-
import downloadAndExtractFile from '../utils/downloadAndExtractFile.js';
|
|
3
|
+
import downloadAndExtractFile, { sourcesOf } from '../utils/downloadAndExtractFile.js';
|
|
4
4
|
import copyToDistSource from '../utils/copyToDistSource.js';
|
|
5
5
|
import { getBuildTargets } from './target.js';
|
|
6
6
|
import state from '../state/index.js';
|
|
@@ -14,8 +14,8 @@ export default async function buildExternal(targetParams, options = {}) {
|
|
|
14
14
|
throw new Error(`crossbind: "${state.config.general?.name}" has a crossbind.build recipe but no "nativeVersion" in package.json — cannot build from source.`);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const {
|
|
18
|
-
const isNewlyCreated = await downloadAndExtractFile(
|
|
17
|
+
const { sha256, replaceList, copyToSource, copyToDist } = state.config.build;
|
|
18
|
+
const isNewlyCreated = await downloadAndExtractFile(sourcesOf(state.config.build, version), state.config.paths.build, sha256);
|
|
19
19
|
const sourcePath = `${state.config.paths.build}/source`;
|
|
20
20
|
if (isNewlyCreated && replaceList) {
|
|
21
21
|
replaceList.forEach(({ regex, replacement, paths }) => {
|
package/src/actions/buildJs.js
CHANGED
|
@@ -5,7 +5,7 @@ import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
|
5
5
|
import commonjs from '@rollup/plugin-commonjs';
|
|
6
6
|
import virtual from '@rollup/plugin-virtual';
|
|
7
7
|
import state from '../state/index.js';
|
|
8
|
-
import
|
|
8
|
+
import getEnv from './getEnv.js';
|
|
9
9
|
|
|
10
10
|
const SUPPRESSED_WARN_CODES = new Set([
|
|
11
11
|
'MISSING_GLOBAL_NAME',
|
|
@@ -53,14 +53,7 @@ options.edge = options.browser;
|
|
|
53
53
|
|
|
54
54
|
export default async function buildJS(target) {
|
|
55
55
|
const entryJS = `${state.config.paths.cli}/assets/js-runtime/${target.runtimeEnv}.js`;
|
|
56
|
-
const
|
|
57
|
-
const resolvedEnv = Object.fromEntries(
|
|
58
|
-
Object.entries(rawEnv).map(([key, value]) => [
|
|
59
|
-
key,
|
|
60
|
-
typeof value === 'function' ? value(state, target) : value,
|
|
61
|
-
]),
|
|
62
|
-
);
|
|
63
|
-
const env = JSON.stringify(resolvedEnv);
|
|
56
|
+
const env = JSON.stringify(getEnv(target));
|
|
64
57
|
const systemConfig = `export default {
|
|
65
58
|
env: ${env},
|
|
66
59
|
useWorker: !!globalThis.Worker,
|
package/src/actions/buildLib.js
CHANGED
|
@@ -54,7 +54,7 @@ export default function buildLib(targetParams, options = {}) {
|
|
|
54
54
|
fs.copyFileSync(modulePath, `${state.config.paths.output}/prebuilt/${target.path}/swig/${fileName}`);
|
|
55
55
|
});
|
|
56
56
|
writeSourceFingerprint(libdir, sourceFingerprint);
|
|
57
|
-
if (isCargo) writeEmbindRsFingerprint(libdir, getEmbindRsFingerprint());
|
|
57
|
+
if (isCargo) writeEmbindRsFingerprint(libdir, getEmbindRsFingerprint(target));
|
|
58
58
|
isChanged = true;
|
|
59
59
|
} else {
|
|
60
60
|
logger.cachedStep(target, 'lib');
|
package/src/actions/buildNode.js
CHANGED
|
@@ -8,6 +8,8 @@ import virtual from '@rollup/plugin-virtual';
|
|
|
8
8
|
import run from './run.js';
|
|
9
9
|
import getLinkInputs from './getLinkInputs.js';
|
|
10
10
|
import getData from './getData.js';
|
|
11
|
+
import getEnv from './getEnv.js';
|
|
12
|
+
import resetCMakeCacheForSource from '../utils/resetCMakeCacheForSource.js';
|
|
11
13
|
import { suppressNodeBuiltinWarnings } from './buildJs.js';
|
|
12
14
|
import state from '../state/index.js';
|
|
13
15
|
import logger from '../utils/logger.js';
|
|
@@ -22,7 +24,7 @@ import toolchainNoticesDir from '../utils/toolchainNotices.js';
|
|
|
22
24
|
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
23
25
|
const WINDOWS_IMAGE_NOTICES = '/opt/licenses/llvm-mingw';
|
|
24
26
|
|
|
25
|
-
function filesUnder(dirs) {
|
|
27
|
+
export function filesUnder(dirs) {
|
|
26
28
|
return dirs.flatMap((dir) => (fs.existsSync(dir)
|
|
27
29
|
? fs.readdirSync(dir, { recursive: true })
|
|
28
30
|
.map((file) => `${dir}/${file}`)
|
|
@@ -30,18 +32,21 @@ function filesUnder(dirs) {
|
|
|
30
32
|
: []));
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
// The addon's link, and an app host's, whose bridges come from an archive of their own.
|
|
36
|
+
export function linkInputs(target, { bridge } = {}) {
|
|
37
|
+
const isMachO = ['darwin', 'ios'].includes(target.platform);
|
|
34
38
|
const {
|
|
35
39
|
libs, appRustLibs, wholeArchiveAll, wholeArchiveNames, rustKeepFlags, hasRust,
|
|
36
40
|
} = getLinkInputs(target, {
|
|
37
41
|
// The linker loads the archive member that defines a -u symbol; Mach-O prefixes C names with _.
|
|
38
|
-
keepFlag: (name) => `-Wl,-u,${
|
|
42
|
+
keepFlag: (name) => `-Wl,-u,${isMachO ? '_' : ''}crossbind_keep_${name}`,
|
|
43
|
+
bridge,
|
|
39
44
|
});
|
|
40
45
|
// The app's Rust goes first: what a package's kept member needs from std and rustc's allocator shims is then
|
|
41
46
|
// already the app's, and the package's own copies stay in its archive instead of defining them twice.
|
|
42
47
|
const ordered = [...appRustLibs, ...libs.filter((lib) => !appRustLibs.includes(lib))];
|
|
43
48
|
const linkArgs = [
|
|
44
|
-
...buildLinkLibArgs(ordered, { wholeArchiveAll, wholeArchiveNames, forceLoad:
|
|
49
|
+
...buildLinkLibArgs(ordered, { wholeArchiveAll, wholeArchiveNames, forceLoad: isMachO }),
|
|
45
50
|
...rustKeepFlags,
|
|
46
51
|
// The system libraries the archives need, declared by the packages that bring them in.
|
|
47
52
|
...(getData('binary', target)?.addonFlags ?? []),
|
|
@@ -52,11 +57,6 @@ function linkInputs(target) {
|
|
|
52
57
|
return { libs, linkArgs, extraSources };
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
function envOf(target) {
|
|
56
|
-
return Object.fromEntries(Object.entries(getData('env', target))
|
|
57
|
-
.map(([key, value]) => [key, typeof value === 'function' ? value(state, target) : value]));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
60
|
// A package publishes its addons in <package>-<platform>-<arch> by listing them as optional
|
|
61
61
|
// dependencies, which is also how npm learns to install only the one for its machine.
|
|
62
62
|
function listsAddonPackage(target) {
|
|
@@ -82,7 +82,7 @@ export function nodeLoaderConfig(target) {
|
|
|
82
82
|
const addonPackage = addonPackagePattern(served);
|
|
83
83
|
const loaded = addonPackage ? served.filter(listsAddonPackage) : served;
|
|
84
84
|
return {
|
|
85
|
-
env: Object.fromEntries(loaded.map((t) => [`${t.platform}-${t.arch}`,
|
|
85
|
+
env: Object.fromEntries(loaded.map((t) => [`${t.platform}-${t.arch}`, getEnv(t)])),
|
|
86
86
|
general: { name: state.config.general.name },
|
|
87
87
|
paths: { addon: target.addonPattern, ...(addonPackage ? { addonPackage } : {}) },
|
|
88
88
|
};
|
|
@@ -119,6 +119,13 @@ export function bundleNodeLoader(target) {
|
|
|
119
119
|
return bundleLoader(target, resolveEmbindNapiRoot(), nodeLoaderConfig(target));
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// The mingw-w64 runtime and winpthreads in a Windows addon or app ask for their notices in binary
|
|
123
|
+
// distributions. The windows image carries them; a host toolchain may not.
|
|
124
|
+
export function copyWindowsNotices(target) {
|
|
125
|
+
const notices = toolchainNoticesDir('win32');
|
|
126
|
+
run(null, ['sh', '-c', `if [ -d ${WINDOWS_IMAGE_NOTICES} ]; then mkdir -p ${notices} && cp ${WINDOWS_IMAGE_NOTICES}/* ${notices}/; fi`], null, target);
|
|
127
|
+
}
|
|
128
|
+
|
|
122
129
|
// Links one Node-API addon from the project's archives and bundles the loader that picks the
|
|
123
130
|
// addon matching the running process.
|
|
124
131
|
export default async function buildNode(target, options = {}) {
|
|
@@ -162,6 +169,7 @@ export default async function buildNode(target, options = {}) {
|
|
|
162
169
|
|
|
163
170
|
const platformPrefix = `Node-${buildType}`;
|
|
164
171
|
logger.startStep(target, 'addon');
|
|
172
|
+
resetCMakeCacheForSource(`${build}/${platformPrefix}/${target.path}`, `${napiRoot}/cpp`, target);
|
|
165
173
|
run(null, [
|
|
166
174
|
'cmake', `${napiRoot}/cpp`,
|
|
167
175
|
`-DCMAKE_BUILD_TYPE=${buildType}`,
|
|
@@ -172,12 +180,7 @@ export default async function buildNode(target, options = {}) {
|
|
|
172
180
|
`-DCROSSBIND_LINK_DEPENDS=${libs.join(';')}`,
|
|
173
181
|
], platformPrefix, target);
|
|
174
182
|
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target, { exclusive: true });
|
|
175
|
-
if (target.platform === 'win32')
|
|
176
|
-
// The mingw-w64 runtime and winpthreads in the addon ask for their notices in binary
|
|
177
|
-
// distributions. The windows image carries them; a host toolchain may not.
|
|
178
|
-
const notices = toolchainNoticesDir('win32');
|
|
179
|
-
run(null, ['sh', '-c', `if [ -d ${WINDOWS_IMAGE_NOTICES} ]; then mkdir -p ${notices} && cp ${WINDOWS_IMAGE_NOTICES}/* ${notices}/; fi`], null, target);
|
|
180
|
-
}
|
|
183
|
+
if (target.platform === 'win32') copyWindowsNotices(target);
|
|
181
184
|
fs.copyFileSync(`${build}/${platformPrefix}/${target.path}/${target.addonName}`, `${build}/${target.addonName}`);
|
|
182
185
|
logger.doneStep(target, 'addon');
|
|
183
186
|
|