crossbind 0.0.1 → 2.0.0-beta.51
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 +92 -0
- package/CHANGELOG.md +156 -0
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/package.json +52 -4
- package/src/actions/buildBinTools.js +0 -0
- package/src/actions/buildCargo.js +88 -0
- package/src/actions/buildDependencies.js +157 -0
- package/src/actions/buildExternal.js +57 -0
- package/src/actions/buildJs.js +93 -0
- package/src/actions/buildLib.js +115 -0
- package/src/actions/buildTypes.js +33 -0
- package/src/actions/buildWasiCommand.js +101 -0
- package/src/actions/buildWasm.js +322 -0
- package/src/actions/createInterface.js +172 -0
- package/src/actions/createLib.js +249 -0
- package/src/actions/createXCFramework.js +82 -0
- package/src/actions/extensions.js +7 -0
- package/src/actions/getAllBridges.js +20 -0
- package/src/actions/getCmakeParameters.js +66 -0
- package/src/actions/getData.js +46 -0
- package/src/actions/getDependLibs.js +25 -0
- package/src/actions/isSourceNewer.js +35 -0
- package/src/actions/licenses.js +156 -0
- package/src/actions/run.js +307 -0
- package/src/actions/target.js +54 -0
- package/src/assets/cmake/CMakeLists.txt +61 -0
- package/src/assets/cmake/dist.cmake +57 -0
- package/src/assets/cmake/ios.toolchain.cmake +1176 -0
- package/src/assets/cpp-runtime/browser.cpp +12 -0
- package/src/assets/cpp-runtime/commonBridges.cpp +17 -0
- package/src/assets/cpp-runtime/crossbindEmptySource.cpp +9 -0
- package/src/assets/cpp-runtime/node.cpp +19 -0
- package/src/assets/js-runtime/adapters/exception-decode.js +79 -0
- package/src/assets/js-runtime/adapters/fs-browser.js +173 -0
- package/src/assets/js-runtime/adapters/fs-node.js +28 -0
- package/src/assets/js-runtime/adapters/path-fs.js +6 -0
- package/src/assets/js-runtime/adapters/path-url.js +8 -0
- package/src/assets/js-runtime/adapters/vector-coercion.js +209 -0
- package/src/assets/js-runtime/adapters/worker-comlink.js +309 -0
- package/src/assets/js-runtime/browser.js +16 -0
- package/src/assets/js-runtime/core.js +193 -0
- package/src/assets/js-runtime/edge.js +18 -0
- package/src/assets/js-runtime/node.js +20 -0
- package/src/assets/packaging/crossbind-package.podspec +17 -0
- package/src/assets/wasi-runtime/stubs.c +84 -0
- package/src/bin.js +416 -0
- package/src/index.js +19 -0
- package/src/integration/getCrossbindScript.js +180 -0
- package/src/integration/getDependFilePath.js +132 -0
- package/src/runtime/wasiRun.mjs +118 -0
- package/src/state/calculateDependencyParameters.js +97 -0
- package/src/state/index.js +128 -0
- package/src/state/loadConfig.js +215 -0
- package/src/utils/appRustCrates.js +106 -0
- package/src/utils/binTools.js +76 -0
- package/src/utils/bridgeAsyncGuard.js +37 -0
- package/src/utils/cargoTarget.js +15 -0
- package/src/utils/cppDts.js +201 -0
- package/src/utils/cppFieldBindings.js +23 -0
- package/src/utils/dependencyRebuild.js +99 -0
- package/src/utils/dirLock.js +71 -0
- package/src/utils/downloadAndExtractFile.js +102 -0
- package/src/utils/familyManifest.js +17 -0
- package/src/utils/findFiles.js +5 -0
- package/src/utils/fixPackageName.js +7 -0
- package/src/utils/flattenConfigForTable.js +75 -0
- package/src/utils/getAbsolutePath.js +14 -0
- package/src/utils/getCMakeListsFilePath.js +17 -0
- package/src/utils/getOsUserAndGroupId.js +11 -0
- package/src/utils/getParentPath.js +12 -0
- package/src/utils/hash.js +33 -0
- package/src/utils/inputStamp.js +28 -0
- package/src/utils/licenseReport.js +94 -0
- package/src/utils/linkLayout.js +34 -0
- package/src/utils/loadJs.js +31 -0
- package/src/utils/loadJson.js +16 -0
- package/src/utils/logger.js +110 -0
- package/src/utils/ociArtifact.js +124 -0
- package/src/utils/overrideDependency.js +99 -0
- package/src/utils/provenance.js +54 -0
- package/src/utils/pullDockerImage.js +105 -0
- package/src/utils/replaceBasePathForDocker.js +18 -0
- package/src/utils/resolveEmbindRust.js +66 -0
- package/src/utils/runCargo.js +228 -0
- package/src/utils/rustBridgeGen.js +1013 -0
- package/src/utils/rustMt.js +76 -0
- package/src/utils/rustSysroot.js +171 -0
- package/src/utils/rustSysrootPin.js +11 -0
- package/src/utils/systemKeys.js +29 -0
- package/src/utils/targets.js +109 -0
- package/src/utils/wasiToolchain.js +47 -0
- package/src/utils/writeIfChanged.js +8 -0
- package/src/utils/writeJson.js +9 -0
- package/vitest.config.js +46 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import replace from 'replace';
|
|
4
|
+
import getData from './getData.js';
|
|
5
|
+
import run from './run.js';
|
|
6
|
+
import buildCargo from './buildCargo.js';
|
|
7
|
+
import { cargoTripleFor } from '../utils/cargoTarget.js';
|
|
8
|
+
import getCmakeParameters from './getCmakeParameters.js';
|
|
9
|
+
import triggerExtensions from './extensions.js';
|
|
10
|
+
import state from '../state/index.js';
|
|
11
|
+
import logger from '../utils/logger.js';
|
|
12
|
+
import { WASI_EMULATION_LIBS, WASI_LINK_LIBS, wasiCFlags, wasiCxxFlags } from '../utils/wasiToolchain.js';
|
|
13
|
+
import { getFilesFingerprint, getContentHash } from '../utils/hash.js';
|
|
14
|
+
|
|
15
|
+
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
16
|
+
const sharedPlatforms = ['android'];
|
|
17
|
+
|
|
18
|
+
export default function createLib(target, fileType, options = {}) {
|
|
19
|
+
if (!target || !options || typeof options !== 'object' || Array.isArray(options)) {
|
|
20
|
+
throw new Error('invalid target or options');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (target.platform === 'ios' && process.platform !== 'darwin') {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const buildType = target.buildType === 'release' ? 'Release' : 'Debug';
|
|
28
|
+
const platformPrefix = `${fileType ? `${fileType}-` : ''}${buildType}`;
|
|
29
|
+
const libdir = `${state.config.paths.build}/${platformPrefix}/prebuilt/${target.path}`;
|
|
30
|
+
const buildPath = `${state.config.paths.build}/${platformPrefix}/${target.path}`;
|
|
31
|
+
// wasi configure tool links need the runtime stubs; compiled below, referenced from LIBS.
|
|
32
|
+
const wasiStubObj = `${buildPath}/crossbind-wasi-stubs.o`;
|
|
33
|
+
|
|
34
|
+
// The lib dir is reused while the bridge list grows, so an early smaller nativeGlob build must not satisfy later sets: fingerprint the glob and miss on mismatch.
|
|
35
|
+
const fingerprintFile = `${libdir}/crossbind-nativeglob.fingerprint`;
|
|
36
|
+
const fingerprint = options.nativeGlob ? getFilesFingerprint(options.nativeGlob) : null;
|
|
37
|
+
const fingerprintChanged = fingerprint !== null
|
|
38
|
+
&& (!fs.existsSync(fingerprintFile) || fs.readFileSync(fingerprintFile, { encoding: 'utf8' }) !== fingerprint);
|
|
39
|
+
|
|
40
|
+
// Config emccFlags feed compile-time state too (CROSSBIND_JSPI below), so a flag change must miss this cache.
|
|
41
|
+
const configEmccFlags = getData('binary', target)?.emccFlags || [];
|
|
42
|
+
const flagsFingerprintFile = `${libdir}/crossbind-emccflags.fingerprint`;
|
|
43
|
+
const flagsFingerprint = getContentHash(JSON.stringify(configEmccFlags));
|
|
44
|
+
const flagsChanged = !fs.existsSync(flagsFingerprintFile)
|
|
45
|
+
|| fs.readFileSync(flagsFingerprintFile, { encoding: 'utf8' }) !== flagsFingerprint;
|
|
46
|
+
|
|
47
|
+
// Rust packages build with cargo on the host and stage the .a like any other prebuilt; the
|
|
48
|
+
// normal prebuilt-link machinery then links it for every platform (no SWIG bridge for Rust).
|
|
49
|
+
// Runs BEFORE the existence cache below: cargo is the incremental cache, and an existence
|
|
50
|
+
// skip would keep serving a stale staged .a after crate source edits.
|
|
51
|
+
if (state.config.export?.type === 'cargo') {
|
|
52
|
+
// Platforms with no cargo triple (wasi, ...) are skipped, so a plain `crossbind build`
|
|
53
|
+
// still completes every other platform instead of dying on the first unsupported one.
|
|
54
|
+
if (!cargoTripleFor(target)) {
|
|
55
|
+
logger.info(`[${target.path}] cargo package skipped (no cargo triple for this platform)`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
logger.startStep(target, fileType);
|
|
59
|
+
const changed = buildCargo(target, libdir);
|
|
60
|
+
if (fingerprint !== null) fs.writeFileSync(fingerprintFile, fingerprint);
|
|
61
|
+
fs.writeFileSync(flagsFingerprintFile, flagsFingerprint);
|
|
62
|
+
return changed;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!options.force && !fingerprintChanged && !flagsChanged && fs.existsSync(`${libdir}/lib`)) {
|
|
66
|
+
logger.cachedStep(target, fileType);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Bridges guard _JSPI registrations behind CROSSBIND_JSPI (bridgeAsyncGuard); define it only when this target links with -sJSPI.
|
|
71
|
+
const isJspiTarget = target.platform === 'wasm' && configEmccFlags.includes('-sJSPI');
|
|
72
|
+
if (fileType === 'Bridge' && target.platform === 'wasm' && !isJspiTarget
|
|
73
|
+
&& options.nativeGlob?.some((f) => fs.existsSync(f) && fs.readFileSync(f, { encoding: 'utf8' }).includes('emscripten::async()'))) {
|
|
74
|
+
logger.info(`[${target.path}] _JSPI bindings skipped: this target links without -sJSPI (add it to binary.emccFlags to enable them)`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const buildEnv = { params: [] };
|
|
78
|
+
let buildParams;
|
|
79
|
+
const depPaths = state.config.allDependencyPaths[target.path];
|
|
80
|
+
if (state.config.build.withBuildConfig) {
|
|
81
|
+
const { getBuildParams, getExtraLibs } = state.config.build;
|
|
82
|
+
buildEnv.console = true;
|
|
83
|
+
const ext = sharedPlatforms.includes(target.platform) ? 'so' : 'a';
|
|
84
|
+
buildParams = getBuildParams ? getBuildParams(target, depPaths, ext, buildPath) : [];
|
|
85
|
+
if (state.config.build?.buildType !== 'configure') {
|
|
86
|
+
const cmakeBuildType = sharedPlatforms.includes(target.platform) ? 'SHARED' : 'STATIC';
|
|
87
|
+
// Upstream CMakeLists increasingly default BUILD_SHARED_LIBS=ON; on wasm that extra .so link fails under emsdk 6, so default to the platform's lib kind (recipe -D flags come later and can override).
|
|
88
|
+
buildParams.unshift(`-DBUILD_SHARED_LIBS=${cmakeBuildType === 'SHARED' ? 'ON' : 'OFF'}`);
|
|
89
|
+
buildParams.push(`-DCMAKE_PREFIX_PATH=${libdir}`, `-DCMAKE_FIND_ROOT_PATH=${libdir}`, `-DBUILD_TYPE=${cmakeBuildType}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const cFlags = Object.values(depPaths).filter(d => d.header).map((d) => `-I${d.header}`);
|
|
93
|
+
const ldFlags = Object.values(depPaths).filter(d => d.libPath).map((d) => `-L${d.libPath}`);
|
|
94
|
+
let dependLibs = '';
|
|
95
|
+
if (state.config.build?.buildType === 'configure') {
|
|
96
|
+
dependLibs = Object.keys(depPaths)
|
|
97
|
+
.filter(d => d && d !== 'cmake')
|
|
98
|
+
// A declared dep without a wasi archive (e.g. jpeg) would fail every configure probe.
|
|
99
|
+
.filter(d => target.platform !== 'wasi' || fs.existsSync(depPaths[d].lib))
|
|
100
|
+
.map((d) => `-l${d}`).join(' ');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const extraLibs = getExtraLibs ? getExtraLibs(target) : [];
|
|
104
|
+
|
|
105
|
+
triggerExtensions('createLib', 'setFlagWithBuildConfig', [buildEnv, cFlags, ldFlags]);
|
|
106
|
+
if (target.runtime === 'mt') {
|
|
107
|
+
cFlags.push('-pthread');
|
|
108
|
+
ldFlags.push('-pthread');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (target.platform === 'wasm') {
|
|
112
|
+
cFlags.push('-msimd128');
|
|
113
|
+
ldFlags.push('-msimd128');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (isJspiTarget) {
|
|
117
|
+
cFlags.push('-DCROSSBIND_JSPI');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (target.platform === 'wasm' && target.arch === 'wasm64') {
|
|
121
|
+
cFlags.push('-sMEMORY64=1');
|
|
122
|
+
ldFlags.push('-sMEMORY64=1');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
buildEnv.params.push('-e', `CFLAGS=${cFlags.join(' ')}`);
|
|
126
|
+
buildEnv.params.push('-e', `CXXFLAGS=${cFlags.join(' ')}`);
|
|
127
|
+
buildEnv.params.push('-e', `LDFLAGS=${ldFlags.join(' ')} ${extraLibs.join(' ')}`);
|
|
128
|
+
// Emulation archives must land in LIBS (after the objects), not LDFLAGS.
|
|
129
|
+
const wasiStubLib = state.config.build?.buildType === 'configure' ? `${wasiStubObj} ` : '';
|
|
130
|
+
const wasiLibs = target.platform === 'wasi' ? ` ${wasiStubLib}${WASI_EMULATION_LIBS.join(' ')}` : '';
|
|
131
|
+
buildEnv.params.push('-e', `LIBS=${dependLibs} ${extraLibs.join(' ')}${wasiLibs}`);
|
|
132
|
+
|
|
133
|
+
let configBuildEnv = state.config.build.env;
|
|
134
|
+
if (configBuildEnv && typeof configBuildEnv === 'function') {
|
|
135
|
+
configBuildEnv = configBuildEnv(target);
|
|
136
|
+
}
|
|
137
|
+
configBuildEnv?.forEach((e) => {
|
|
138
|
+
buildEnv.params.push('-e', e);
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
buildParams = getCmakeParameters(target, options);
|
|
142
|
+
|
|
143
|
+
triggerExtensions('createLib', 'setFlagWithoutBuildConfig', [buildEnv]);
|
|
144
|
+
|
|
145
|
+
if (target.runtime === 'mt') {
|
|
146
|
+
buildEnv.params.push('-e', `CFLAGS=-pthread`);
|
|
147
|
+
buildEnv.params.push('-e', `CXXFLAGS=-pthread`);
|
|
148
|
+
buildEnv.params.push('-e', `LDFLAGS=-pthread`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (target.platform === 'wasm') {
|
|
152
|
+
buildEnv.params.push('-e', `CFLAGS=-msimd128`);
|
|
153
|
+
buildEnv.params.push('-e', `CXXFLAGS=-msimd128`);
|
|
154
|
+
buildEnv.params.push('-e', `LDFLAGS=-msimd128`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (target.platform === 'wasm' && target.arch === 'wasm64') {
|
|
158
|
+
buildEnv.params.push('-e', `CFLAGS=-sMEMORY64=1`);
|
|
159
|
+
buildEnv.params.push('-e', `CXXFLAGS=-sMEMORY64=1`);
|
|
160
|
+
buildEnv.params.push('-e', `LDFLAGS=-sMEMORY64=1`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (isJspiTarget) {
|
|
164
|
+
buildEnv.params.push('-e', `CFLAGS=-DCROSSBIND_JSPI`);
|
|
165
|
+
buildEnv.params.push('-e', `CXXFLAGS=-DCROSSBIND_JSPI`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
logger.startStep(target, fileType);
|
|
170
|
+
const t0 = performance.now();
|
|
171
|
+
const cmakeDir = state.config.build.withBuildConfig ? `${state.config.paths.build}/source` : state.config.paths.cmakeDir;
|
|
172
|
+
|
|
173
|
+
if (state.config.build?.beforeRun) {
|
|
174
|
+
const dataList = state.config.build?.beforeRun(cmakeDir);
|
|
175
|
+
dataList.forEach((data) => {
|
|
176
|
+
run(data.program, data.parameters || [], platformPrefix, target, buildEnv);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// stubs.c lives outside the docker mount (paths.cli); stage it into the build tree.
|
|
181
|
+
const stageWasiStubs = () => {
|
|
182
|
+
const staged = `${buildPath}/crossbind-wasi-stubs.c`;
|
|
183
|
+
fs.mkdirSync(buildPath, { recursive: true });
|
|
184
|
+
fs.copyFileSync(`${state.config.paths.cli}/assets/wasi-runtime/stubs.c`, staged);
|
|
185
|
+
run(null, [
|
|
186
|
+
'wasi-clang', ...wasiCFlags(), '-c', staged,
|
|
187
|
+
'-o', wasiStubObj,
|
|
188
|
+
], platformPrefix, target, { console: buildEnv.console });
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
if (!options.bypassCmake) {
|
|
192
|
+
if (state.config.build?.buildType === 'configure') {
|
|
193
|
+
fs.cpSync(cmakeDir, buildPath, { recursive: true });
|
|
194
|
+
if (state.config.build?.sourceReplaceList) {
|
|
195
|
+
state.config.build.sourceReplaceList(target, depPaths)?.forEach(({ regex, replacement, paths }) => {
|
|
196
|
+
replace({
|
|
197
|
+
regex, replacement, paths: paths.map((p) => `${buildPath}/${p}`), recursive: false, silent: true,
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (target.platform === 'wasi') {
|
|
202
|
+
// LIBS references the stub object before the first configure link probe.
|
|
203
|
+
stageWasiStubs();
|
|
204
|
+
}
|
|
205
|
+
run(null, [
|
|
206
|
+
'./configure',
|
|
207
|
+
...buildParams,
|
|
208
|
+
`--prefix=${libdir}`,
|
|
209
|
+
], platformPrefix, target, buildEnv);
|
|
210
|
+
} else {
|
|
211
|
+
if (target.platform === 'wasi') {
|
|
212
|
+
// Upstream exe links (BUILD_APPS etc.) need the stubs + wasm-EH tail on CMAKE_EXE_LINKER_FLAGS so try_compile probes link the same way.
|
|
213
|
+
stageWasiStubs();
|
|
214
|
+
buildParams.push(`-DCMAKE_EXE_LINKER_FLAGS=${[...wasiCxxFlags(), wasiStubObj, ...WASI_LINK_LIBS].join(' ')}`);
|
|
215
|
+
}
|
|
216
|
+
run(null, [
|
|
217
|
+
target.platform === 'ios' && state.config.build?.useIOSCMake ? 'ios-cmake' : 'cmake', cmakeDir,
|
|
218
|
+
`-DCMAKE_BUILD_TYPE=${buildType}`,
|
|
219
|
+
`-DCMAKE_INSTALL_PREFIX=${libdir}`,
|
|
220
|
+
...buildParams,
|
|
221
|
+
], platformPrefix, target, buildEnv);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const t1 = performance.now();
|
|
225
|
+
if (target.platform === 'ios' && state.config.build?.buildType !== 'configure') {
|
|
226
|
+
const iOSCMakeBuilder = state.config.build?.useIOSCMake ? 'ios-cmake' : 'cmake';
|
|
227
|
+
run(null, [iOSCMakeBuilder, '--build', '.', '-j', cpuCount, '--config', buildType, '--target', 'install'], platformPrefix, target, { console: buildEnv.console });
|
|
228
|
+
} else {
|
|
229
|
+
// Some upstream makefiles race when one -j invocation carries multiple goals (openssl's
|
|
230
|
+
// install builds apps twice); recipes can split phases via build.makePhases.
|
|
231
|
+
const makePhases = state.config.build?.makePhases || [['install']];
|
|
232
|
+
makePhases.forEach((phase) => {
|
|
233
|
+
run(null, ['make', `-j${cpuCount}`, ...phase], platformPrefix, target, { console: buildEnv.console });
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const t2 = performance.now();
|
|
237
|
+
const cmakeMs = Math.round(t1 - t0);
|
|
238
|
+
const buildMs = Math.round(t2 - t1);
|
|
239
|
+
const detail = `cmake ${cmakeMs < 1000 ? `${cmakeMs}ms` : `${(cmakeMs / 1000).toFixed(1)}s`}, make ${buildMs < 1000 ? `${buildMs}ms` : `${(buildMs / 1000).toFixed(1)}s`}, j${cpuCount}`;
|
|
240
|
+
logger.doneStep(target, fileType, detail);
|
|
241
|
+
|
|
242
|
+
fs.mkdirSync(libdir, { recursive: true });
|
|
243
|
+
if (fingerprint !== null) {
|
|
244
|
+
fs.writeFileSync(fingerprintFile, fingerprint);
|
|
245
|
+
}
|
|
246
|
+
fs.writeFileSync(flagsFingerprintFile, flagsFingerprint);
|
|
247
|
+
// Callers can force the final link when a lib actually rebuilt.
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import upath from 'upath';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import state from '../state/index.js';
|
|
5
|
+
import logger from '../utils/logger.js';
|
|
6
|
+
import { getTargetParams, getFilteredBuildTargets } from './target.js';
|
|
7
|
+
|
|
8
|
+
const iOSDevPath = '/Applications/Xcode.app/Contents/Developer';
|
|
9
|
+
const iosBinPath = `${iOSDevPath}/Toolchains/XcodeDefault.xctoolchain/usr/bin`;
|
|
10
|
+
const iosRanLibBin = `${iosBinPath}/ranlib`;
|
|
11
|
+
|
|
12
|
+
function runQuiet(bin, params, options) {
|
|
13
|
+
try {
|
|
14
|
+
execFileSync(bin, params, { ...options, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
15
|
+
} catch (e) {
|
|
16
|
+
if (e.stdout?.length) process.stderr.write(e.stdout);
|
|
17
|
+
if (e.stderr?.length) process.stderr.write(e.stderr);
|
|
18
|
+
throw e;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function createXCFramework(overrideConfig = null) {
|
|
23
|
+
if (process.platform !== 'darwin') {
|
|
24
|
+
logger.info('XCFramework not created because platform is not darwin.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const output = overrideConfig?.paths?.output || state.config.paths.output;
|
|
29
|
+
const projectPath = overrideConfig?.paths?.project || state.config.paths.project;
|
|
30
|
+
const libName = overrideConfig?.export?.libName || state.config.export.libName;
|
|
31
|
+
|
|
32
|
+
const options = { cwd: projectPath };
|
|
33
|
+
|
|
34
|
+
const relativeOutput = upath.relative(projectPath, output);
|
|
35
|
+
|
|
36
|
+
const targetParams = overrideConfig?.targetParams || getTargetParams();
|
|
37
|
+
const buildTargets = getFilteredBuildTargets(targetParams, { platform: 'ios', runtime: 'mt', buildType: (targetParams.buildType && targetParams.buildType.length > 0) ? targetParams.buildType[0] : 'release' });
|
|
38
|
+
|
|
39
|
+
if (buildTargets.some(t => !fs.existsSync(`${output}/prebuilt/${t.path}/lib`))) {
|
|
40
|
+
logger.info('XCFramework not created because some of the build targets are not built.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const targets = {};
|
|
45
|
+
buildTargets.forEach((target) => {
|
|
46
|
+
const p = `${target.platform}-${target.runtime}-${target.buildType}`;
|
|
47
|
+
if (!targets[p]) {
|
|
48
|
+
targets[p] = {};
|
|
49
|
+
}
|
|
50
|
+
targets[p][target.arch] = target;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
libName.forEach((fileName) => {
|
|
54
|
+
Object.values(targets).forEach((a) => {
|
|
55
|
+
const targets = Object.values(a);
|
|
56
|
+
// const targetName = `${fileName}-${targets[0].runtime}-${targets[0].buildType}.xcframework`;
|
|
57
|
+
const targetName = `${fileName}.xcframework`;
|
|
58
|
+
const targetPath = `${projectPath}/${targetName}`;
|
|
59
|
+
// Always recreate: the xcframework wraps the freshly built static libs which contain
|
|
60
|
+
// the user's bridge bindings. If we keep a stale xcframework around, the linker
|
|
61
|
+
// (via the podspec's vendored_frameworks + force_load) will pull in old bindings
|
|
62
|
+
// and runtime calls like `Module.<UserClass>` resolve to undefined. xcodebuild
|
|
63
|
+
// -create-xcframework refuses to overwrite, so delete first.
|
|
64
|
+
if (fs.existsSync(targetPath)) {
|
|
65
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
const taskLabel = `xcframework ${targetName}`;
|
|
68
|
+
logger.startTask(taskLabel);
|
|
69
|
+
const params = ['-create-xcframework'];
|
|
70
|
+
targets.forEach((target) => {
|
|
71
|
+
runQuiet(iosRanLibBin, [`${relativeOutput}/prebuilt/${target.path}/lib/lib${fileName}.a`], options);
|
|
72
|
+
params.push(
|
|
73
|
+
'-library', `${relativeOutput}/prebuilt/${target.path}/lib/lib${fileName}.a`,
|
|
74
|
+
'-headers', `${relativeOutput}/prebuilt/${target.path}/include`,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
params.push('-output', targetName);
|
|
78
|
+
runQuiet('xcodebuild', params, options);
|
|
79
|
+
logger.doneTask(taskLabel);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import findFiles from '../utils/findFiles.js';
|
|
4
|
+
import state from '../state/index.js';
|
|
5
|
+
|
|
6
|
+
export default function getAllBridges(log = console.log) {
|
|
7
|
+
return findFiles(`${state.config.paths.build}/bridge/*.i.cpp`).filter((bridge) => {
|
|
8
|
+
// The .source sidecar records the header this bridge was generated from. When that
|
|
9
|
+
// header is gone, the stale .i.cpp would #include a missing file and break the native
|
|
10
|
+
// build - the same failure a deleted .rs used to cause. Bridges without a sidecar
|
|
11
|
+
// predate it and are left alone.
|
|
12
|
+
const sidecar = `${bridge}.source`;
|
|
13
|
+
if (!fs.existsSync(sidecar)) return true;
|
|
14
|
+
const source = fs.readFileSync(sidecar, 'utf8').trim();
|
|
15
|
+
if (!source || fs.existsSync(source)) return true;
|
|
16
|
+
[bridge, `${bridge}.exports.json`, sidecar].forEach((f) => fs.rmSync(f, { force: true }));
|
|
17
|
+
log(`crossbind: pruned stale bridge '${path.basename(bridge)}' - its header is gone`);
|
|
18
|
+
return false;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import state from '../state/index.js';
|
|
3
|
+
import getData from './getData.js';
|
|
4
|
+
|
|
5
|
+
export default function getCmakeParameters(target, options = {}) {
|
|
6
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
7
|
+
throw new Error('invalid options');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const params = [];
|
|
11
|
+
// if (isBuildBridge) params.push('-DBUILD_BRIDGE=TRUE');
|
|
12
|
+
|
|
13
|
+
const dependParams = state.config.dependencyParameters;
|
|
14
|
+
const externalNativeGlob = [
|
|
15
|
+
...(options.nativeGlob || []),
|
|
16
|
+
];
|
|
17
|
+
const externalBridgeGlob = [
|
|
18
|
+
`${state.config.paths.cli}/assets/cpp-runtime/commonBridges.cpp`,
|
|
19
|
+
...(options.bridgeGlob || []),
|
|
20
|
+
];
|
|
21
|
+
const nativeGlob = [
|
|
22
|
+
`${state.config.paths.cli}/assets/cpp-runtime/crossbindEmptySource.cpp`,
|
|
23
|
+
...(dependParams.nativeGlob || []),
|
|
24
|
+
];
|
|
25
|
+
const headerGlob = [
|
|
26
|
+
...(dependParams.headerGlob || []),
|
|
27
|
+
...(options.headerGlob || []),
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const headerDirs = [
|
|
31
|
+
dependParams.headerPathWithDepends,
|
|
32
|
+
...(options.headerDirs || []),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const sharedPlatforms = ['android'];
|
|
36
|
+
const buildType = sharedPlatforms.includes(target.platform) ? 'SHARED' : 'STATIC';
|
|
37
|
+
const otherBuildType = buildType === 'STATIC' ? 'SHARED' : 'STATIC';
|
|
38
|
+
|
|
39
|
+
const cmakeCompileOptions = [...new Set(getData('cmake', target)?.compileOptions || [])];
|
|
40
|
+
|
|
41
|
+
params.push(...[
|
|
42
|
+
`-DPROJECT_NAME=${options.name || state.config.general.name}`,
|
|
43
|
+
// `-DPACKAGE_NAME_SUFFIX=${target.platform === 'ios' ? `-${target.runtime}-${target.buildType}` : ''}`,
|
|
44
|
+
`-DPROJECT_TARGET_PLATFORM=${target.platform}`,
|
|
45
|
+
`-DPROJECT_TARGET_ARCH=${target.arch}`,
|
|
46
|
+
`-DPROJECT_TARGET_RUNTIME=${target.runtime}`,
|
|
47
|
+
`-DPROJECT_TARGET_HOST=${target.platform}-${target.arch}-${target.runtime}-${target.buildType}`,
|
|
48
|
+
`-DPROJECT_TARGET_HOST_RELEASE=${target.platform}-${target.arch}-${target.runtime}-release`,
|
|
49
|
+
`-DBASE_DIR=${state.config.paths.project}`,
|
|
50
|
+
`-DEXTERNAL_NATIVE_GLOB=${externalNativeGlob.join(';')}`,
|
|
51
|
+
`-DEXTERNAL_BRIDGE_GLOB=${externalBridgeGlob.join(';')}`,
|
|
52
|
+
`-DNATIVE_GLOB=${nativeGlob.join(';')}`,
|
|
53
|
+
`-DHEADER_GLOB=${headerGlob.join(';')}`,
|
|
54
|
+
`-DHEADER_DIR=${headerDirs.join(';')}`,
|
|
55
|
+
`-DDEPENDS_CMAKE_PATHS=${dependParams.getCmakeDependsPathAndName(target).pathsOfCmakeDepends.join(';')}`,
|
|
56
|
+
`-DDEPENDS_CMAKE_NAMES=${dependParams.getCmakeDependsPathAndName(target).nameOfCmakeDepends.join(';')}`,
|
|
57
|
+
`-DBRIDGE_DIR=${state.config.paths.build}/bridge`,
|
|
58
|
+
`-DBUILD_TYPE=${buildType}`,
|
|
59
|
+
`-DBUILD_${otherBuildType}_LIBS=OFF`,
|
|
60
|
+
`-DCOMPILE_OPTIONS=${cmakeCompileOptions.join(';')}`,
|
|
61
|
+
// '-DCMAKE_CXX_COMPILER_LAUNCHER=ccache',
|
|
62
|
+
...(options.buildSource !== false ? ['-DBUILD_SOURCE=TRUE'] : []),
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
return params;
|
|
66
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import state from '../state/index.js';
|
|
3
|
+
import { getFilteredTargetSpec, getBuildTargets } from './target.js';
|
|
4
|
+
|
|
5
|
+
function getRecursiveData(obj, config, dependency, field, target) {
|
|
6
|
+
const entryArray = getFilteredTargetSpec(dependency?.targetSpecs, target).map(s => s[field]);
|
|
7
|
+
const entries = Object.assign({}, ...entryArray);
|
|
8
|
+
Object.entries(entries).forEach(([dKey, value]) => {
|
|
9
|
+
if (field === 'data') {
|
|
10
|
+
let key;
|
|
11
|
+
if (fs.existsSync(`${dependency.paths.output}/prebuilt/${target.path}`)) {
|
|
12
|
+
key = `${dependency.paths.output}/prebuilt/${target.path}/${dKey}`;
|
|
13
|
+
} else {
|
|
14
|
+
const releaseTarget = getBuildTargets({
|
|
15
|
+
platform: [target.platform], arch: [target.arch], runtime: [target.runtime],
|
|
16
|
+
runtimeEnv: [target.runtimeEnv], buildType: ['release']
|
|
17
|
+
})?.[0];
|
|
18
|
+
if (releaseTarget) {
|
|
19
|
+
key = `${dependency.paths.output}/prebuilt/${releaseTarget.path}/${dKey}`;
|
|
20
|
+
} else {
|
|
21
|
+
throw new Error('Data not found');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
obj[key] = value;
|
|
25
|
+
} else {
|
|
26
|
+
if (typeof value === 'object' && Array.isArray(value)) {
|
|
27
|
+
obj[dKey] = [...(obj[dKey] || []), ...value];
|
|
28
|
+
} else if (typeof value === 'object') {
|
|
29
|
+
obj[dKey] = { ...(obj[dKey] || {}), ...value };
|
|
30
|
+
} else {
|
|
31
|
+
obj[dKey] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
dependency.dependencies.forEach((dep) => {
|
|
37
|
+
getRecursiveData(obj, config, dep, field, target);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default function getData(field, target) {
|
|
42
|
+
const output = {};
|
|
43
|
+
getRecursiveData(output, state.config, state.config, field, target);
|
|
44
|
+
|
|
45
|
+
return output;
|
|
46
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import findFiles from '../utils/findFiles.js';
|
|
2
|
+
import state from '../state/index.js';
|
|
3
|
+
import { getFilteredTargetSpec } from './target.js';
|
|
4
|
+
|
|
5
|
+
export default function getDependLibs(target) {
|
|
6
|
+
let dependLibs = [
|
|
7
|
+
...findFiles(`${state.config.paths.build}/Source-Release/${target.path}/dependencies/**/*.a`, { cwd: state.config.paths.project }),
|
|
8
|
+
];
|
|
9
|
+
state.config.dependencyParameters.getCmakeDepends(target).forEach((d) => {
|
|
10
|
+
if (d.export.libName) {
|
|
11
|
+
const ignoreLibNames = getFilteredTargetSpec(d?.targetSpecs, target).map(s => s.ignoreLibName).flat();
|
|
12
|
+
d.export.libName.forEach((fileName) => {
|
|
13
|
+
if (ignoreLibNames?.includes(fileName)) return;
|
|
14
|
+
let libPaths = findFiles(`${d.paths.output}/prebuilt/${target.path}/lib/lib${fileName}.a`, { cwd: d.paths.project });
|
|
15
|
+
if (libPaths.length === 0) {
|
|
16
|
+
libPaths = findFiles(`${d.paths.output}/prebuilt/${target.releasePath}/lib/lib${fileName}.a`, { cwd: d.paths.project });
|
|
17
|
+
}
|
|
18
|
+
dependLibs.push(...libPaths);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
dependLibs = [...new Set(dependLibs)];
|
|
24
|
+
return dependLibs;
|
|
25
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import p from 'node:path';
|
|
3
|
+
import state from '../state/index.js';
|
|
4
|
+
|
|
5
|
+
function newestMtimeInDir(dir) {
|
|
6
|
+
let newest = 0;
|
|
7
|
+
if (!fs.existsSync(dir)) return newest;
|
|
8
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
9
|
+
const full = p.join(dir, entry.name);
|
|
10
|
+
if (entry.isDirectory()) {
|
|
11
|
+
const m = newestMtimeInDir(full);
|
|
12
|
+
if (m > newest) newest = m;
|
|
13
|
+
} else if (entry.isFile()) {
|
|
14
|
+
const m = fs.statSync(full).mtimeMs;
|
|
15
|
+
if (m > newest) newest = m;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return newest;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// "Did any native source change after this artifact was produced?" - artifact is a file
|
|
22
|
+
// (bundler plugins: the built js) or a directory (CLI lib cache: the staged prebuilt dir).
|
|
23
|
+
export function isNativeSourceNewerThan(artifactPath) {
|
|
24
|
+
if (!fs.existsSync(artifactPath)) return false;
|
|
25
|
+
const stat = fs.statSync(artifactPath);
|
|
26
|
+
const artifactMtime = stat.isDirectory() ? newestMtimeInDir(artifactPath) : stat.mtimeMs;
|
|
27
|
+
for (const dir of state.config.paths.native) {
|
|
28
|
+
if (newestMtimeInDir(dir) > artifactMtime) return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default function isSourceNewer(target) {
|
|
34
|
+
return isNativeSourceNewerThan(`${state.config.paths.build}/${target.jsName}`);
|
|
35
|
+
}
|